Multi-cast delegates are potential trojan horses for protected eventing...
I posted on some security options for eventing when you are using custom storage. While I stopped short of full examining the potential of the various systems, I also stopped short on pointing out some additional security concerns. Here is the previous posting: Some security considerations for systems with events.
The offending pointer was in regards to protecting the event model through type checking the Target of the delegates before adding them. Well, the code really isn't as accurate as it could be. What you have to realize is that all delegates defined in C# are multi-cast delegates. What in the hell is a multi-cast delegate? Well, it is a linked list of delegates with each containing a pointer ot the previous one. A reverse linked list would be a good way to describe it.
Why do they reverse it? Well, think about that for a minute. The most common way of combining delegates is to add a single new delegate to a long list of existing delegates. By taking the new delegate and setting its previous pointer to the existing delegate list, you've effectively reduced appending a delegate to a single operation.
Then how in the hell do you execute a reversed list in the proper order? Well, they bash the stack in order to make it work by recursively calling the invocation method back up to the beginning of the list. Once all of the methods have executed the stack unwinds down to the last entry and they are done. They also have to order the list correctly for GetInvocationList. For this they run through and get an accurate count, then fill the array from last to first.
So what in the hell did I do wrong? Well, by checking the Target I am checking the type of the last added delegate, but I'm not checking the types of all of the delegates. With that said, a user can hide multiple delegates that are invalid on the front end of a valid delegate. This would look something like:
EventHandler handler = new EventHandler(this.Foo);
handler = (EventHandler) Delegate.Combine(handler, new EventHandler(allow.Foo));
cea.Woot += handler;
The code is incomplete above, but the idea is to create an event handler pointing to your current instance, say an instance of BadClass, but then combine the delegate with another delegate pointing to an instance of GoodClass. Hard to believe that something so simple completely destroys our initial security model. You can get around this problem by fully examining the invocation list of a particular delegate and then adding only those delegates that are valid to your existing storage. Knowhing more about the storage, we can also use our own linked list to combine delegates together and then we can remove calls to GetInvocationList and perhaps eek some additional performance out of systems that rely heavily on eventing.
using System;
public class Runner {
private static void Main(string[] args) {
ControlledEventAttachment cea = new ControlledEventAttachment();
Console.WriteLine("Firing");
cea.Fire();
Console.WriteLine();
Console.WriteLine("Attaching");
Allowable allow = new Allowable();
allow.Attach(cea);
Console.WriteLine("Firing");
cea.Fire();
Console.WriteLine();
Console.WriteLine("Attaching");
Hidden hide = new Hidden();
hide.Attach(cea);
Console.WriteLine("Firing");
cea.Fire();
}
}
public class ControlledEventAttachment {
private EventHandler woot;
public event EventHandler Woot {
add {
if ( value.Target is Allowable ) {
woot = (EventHandler) Delegate.Combine(woot, value);
}
}
remove {
woot = (EventHandler) Delegate.Remove(woot, value);
}
}
public void Fire() {
if ( woot != null ) {
woot(this, null);
}
}
}
public class Allowable {
public void Foo(object sender, EventArgs e) {
Console.WriteLine("Allowable");
}
public void Attach(ControlledEventAttachment cea) {
cea.Woot += new EventHandler(this.Foo);
}
}
public class Hidden {
public Allowable allow = new Allowable();
public void Attach(ControlledEventAttachment cea) {
EventHandler handler = new EventHandler(this.Foo);
handler = (EventHandler) Delegate.Combine(handler, new EventHandler(allow.Foo));
cea.Woot += handler;
cea.Woot += new EventHandler(this.Foo2);
}
public void Foo(object sender, EventArgs e) {
Console.WriteLine("Hidden");
}
public void Foo2(object sender, EventArgs e) {
Console.WriteLine("Not So Hidden");
}
}