[值得一读]Some security considerations for systems with events

原文地址: http://weblogs.asp.net/Justin_Rogers/archive/2004/09/22/233128.aspx

For just a moment, relax your guard and don't think about the common usages of eventing that occur every day. The quick answer to solving any security concerns is to do a code review, run your application in a debugger to find offending code, and claim that since you own all of the source you don't need extra measures to protect yourself. I'm fine with that. This model is for plug-in architectures that allow eventing as a core messaging feature. We'll examine the concepts of messaging and break-downs in the system from a couple of different angles.

No Twosies
This is a prominent issue. User code identifies a location where they need to add an event handler for something and this location is possibly re-entrant. Normally, you might call this a mistake, but I think it is a mistake that can be easily rectified by some custom event code. Not only that, but user code fixes for these issues require that the offending code be replaced, a state variable protect whether or not the event has been added, or finding the proper location to remove the event. All of these can be difficult compared to the custom event code.

add { customEvent = (EventHandler) Delegate.Combine(Delegate.RemoveAll(customEvent, value), value); }

We end up using removal code to ensure that the delegate doesn't already exist before adding the new delegate. We can also use locks to ensure multi-threaded protection during these operations.

First Come, First Serve
Another prominent issue. If you always know your event is only going to be consumed by one object at a time, then you can easily protect yourself from adding multiple handlers. This is generally found in places where it only makes sense for one piece of code to handle an event. Have you ever seen what the effects of 10 paint event handlers? Again, you might call this poor code, but each time you want to paint a new glyph, you might think it smart to add a new event handler while the option is checked in place of yet another branching block. Let's ensure we keep it to one at a time.

add { if ( customEvent == null ) { customEvent = value; } }
remove { if ( customEvent == value ) { customEvent = null; } }

What is the Password?
The majority of the events you see are communal beasts of multi-casting, but you may not always want someone to be able to add themselves as an event source. Here is a game related scenario that might be of interest... As AI players walk around the world, they look for event sources. These sources are objects in the world that might perform some actions and they give notification before they do. A naively programmed AI tries adding event sources that don't have any impact on them, they simply aren't in the effect zone, and so that AI builds up more information about the world and uses more resources than the other AI. On the same note, pushing the requirements of adding events to the eventing zones themselves, means that AI not even interested will be notified and thus again resources are wasted. The solution is to enforce some sort of password.

private event FireEventHandler BelchFlames {
    add {
        Player p = value.Target as Player;
        if ( p == null || p is Mech ) { return; } // Only add players and non mechs that are affected
        belchFlames = (FireEventHandler) Delegate.Combine(Delegate.RemoveAll(belchFlames, value), value);
    }
    remove {...}
}

Get Out of Here!
Another odd moment in the life of an event is when you realize that when it fires and when the delegates are called are two different times. The rationale here is another game concept. While the game engine needs events from each game object to schedule things to happen, AI components need to run in a specialized sand-box, timed, and most likely run at a different time from when the original event first fired. One of the larger mistakes in AI programming is nesting scripts too deeply and events are a form of nesting. An example would be a group leader subscribed to picking up base communications that in turn relays the information to the group participants. That is three levels already and you might not be prepared for all that mayhem when the event is fired.

add {
    if ( value.Target is GameEngine ) { gameEngineSink = value; }
    else { fullSink = (EventHandler) Delegate.Combine(fullSink, value); }
}

Notice how we collect events from different sources (by examining the Target) into different collections. The game engine, when it wants to be notified of a particular event, will be placed in a specialized holder, while the rest of the objects get collected into a secondary holder. We'll pass the secondary holder off to the game engine at some point and allow it to call each of the objects from within the timing sand-box.

I'm Tired of Waiting!
While the above solves locality issues, it doesn't necessarily solve concurrency issues. While running AI code in the sand-box is definitely a great thing to do, we may also want a bunch of code to run in response to an event while we finish some other processing. You can toss individual delegates off onto separate threads. These are single-cast, non-combinable delegates that you get from calling GetInvocationList. You have to be far more careful with respect to thread-safety and the operations in the delegates really need to be designed so they can run asynchronously while the rest of the application continues on.

private void OnMessageReceived(MessageInfo mi) {
    if ( messageReceived != null ) {
        Delegate[] callbacks = messageReceived.GetInvocationList();
        for(int i = 0; i < callbacks.Length; i++) {
            ThreadPool.QueueUserWorkItem(new WaitCallback(RunMessage), new Package(callbacks[i], mi));
        }
    }
}
private void RunMessage(object state) {
    Package p = state as Package;
    if ( p != null ) { p.Callback.DynamicInvoke( new object[] { p.MessageInfo } ); }
}

Now, as messages are received, we'll quickly dispatch them off to other threads to be handled. If the other threads are running neural networks, doing character recognition, or running variations of a specific algorithm, they can update their internal state and return the threads to the pool after they are complete. This never stops the rest of the game from doing its thing and back-ups in the thread pool can result in some auxillary code to throw out messages until things catch up.

Quit Throwing Stuff at Me!
The code running in an event handler can crash and burn just like any other code. If this happens during a normal event invocation, you may not process other, operable events. One option is to break out the invocation lists and call each delegate safely, while removing the offending delegates that are causing problems. I think this is an excellent pattern, especially in plug-in architectures where you expect plug-ins to surface errors through exceptions since they have no other manner of indicating an exceptional situation.

A secondary issue would be disposed objects. A Form for instance can be in an unusable state, but still be the target of a delegate. Since most event oriented code is terrible about removing or unhooking events (where do you put the code, did you put it in the right place, does it cover all code-paths that lead to an unusable object, is the remote object still alive even) it might be smart to protect against such a common mistake.

private void OnFilterImage(Bitmap b) {
    if ( messageReceived != null ) {
        Delegate[] callbacks = messageReceived.GetInvocationList();
        for(int i = 0; i < callbacks.Length; i++) {
            try {
                callbacks[i].DynamicInvoke(new object[] { this, b });
            } catch {
                messageReceived = (BitmapHandler) Delegate.Remove(messageReceived, callbacks[i]);
            }
        }
    }
}

Now, if you have an error in a single filter, the rest won't affect you. You can even notify the user in the catch block, and ask if they would like to proceed with the rest of the events, especially if this is a run of sequential filters that all need to complete to achieve a desired effect. However, if you've been processing for 10 minutes, and this is the last filter, you may just want to get it over with and finish the process.

All of these considerations are for systems where events are considered important. You can fix each of the issues in turn by removing events and using another pattern entirely. In fact in most cases I don't use events myself. The direction I'm taking here is that of a highly dynamic plug-in oriented architecture, where eventing solves many information and messaging problems without a lot of code.

posted @ 2004-10-03 10:19  dudu  阅读(924)  评论(0编辑  收藏  举报