January 1999
Building a Lightweight COM Interception Framework Part 1: The Universal Delegator |
I wrote a component that I called the delegator. The delegator was a simple COM object that would wrap any other COM object, without requiring type information. The only interface the delegator actually implemented was IUnknown, but the implementation supported aggregation. |
This article assumes you're familiar with C++, COM |
Code for this article: Delegate.exe (82KB)
Keith Brown works at DevelopMentor, developing the COM and Windows NT Security curriculum. He is coauthor of Effective COM (Addison-Wesley, 1998), and is writing a developer's guide to dis-tributed security. Reach Keith at http://www.develop.com/kbrown.
|
In March of 1997, I fell in love with the idea of smart proxies. But I was unhappy to discover that handler marshaling, while mentioned in the COM spec (two whole paragraphs), didn't work as advertised. The idea behind handler marshaling is that an object can simply implement IStdMarshalInfo and specify the CLSID of an in-process handler (otherwise known as a custom proxy), which will be instantiated in place of the standard proxy. The custom proxy can then aggregate any of the standard proxy's interfaces to get the best of both worlds: hands-off remoting for interfaces the proxy doesn't particularly care about, and full in-process implementations of interfaces the proxy does care about. It sounded like a silver bullet for achieving the ideal of local/remote transparency that Kraig Brockschmidt wrote about long ago.
I quickly discovered that in Windows NT® 4.0, while CoMarshalInterface queries for IStdMarshalInfo, it does absolutely nothing with it. Even if it did, there is no mechanism to aggregate the standard proxy; handler marshaling is apparently reserved for the special case of OLE rendering handlers, which unfortunately isn't much help to those of us developing distributed systems. While this is fixed in Windows 2000, I want a solution that works now. Don Box introduced me to a neat idea with which you can fake handler marshaling. The idea is elegant: simply implement custom marshaling on an object, specify the CLSID of a custom proxy via IMarshal::GetUnmarshalClass, then implement IMarshal::MarshalInterface by asking the standard marshaler to write a STDOBJREF into the stream, which also sets up the stub manager. In the custom proxy's implementation of IMarshal::UnmarshalInterface, simply call CoUnmarshalInterface to unmarshal a standard proxy, which will connect back to the stub. This mechanism allows you to inject a bit of in-process code (a smart proxy) into the client's process, which starts life holding a standard proxy it can use to access the remote object using COM. One flaw of this scheme is that it still offers no way to aggre-gate the standard proxy, so the custom proxy has to implement all the interfaces of the remote object explicitly, even if it means simply writing code to delegate each method call to the standard proxy. To try to solve this problem, I wrote a component that I called the delegator. The delegator was a simple COM object that would wrap any other COM object, without requiring type information. The only interface the delegator actually implemented was IUnknown, but the implementation supported aggregationthe delegator could be used to wrap a standard proxy so that my custom proxy could aggregate it without having to write all the delegation code by hand. I used inline assembler to provide a generic vtbl that would automatically delegate all other method calls directly to the wrapped object with very little overhead. From such humble beginnings, I soon discovered a whole host of other applications for the delegatorfrom transparent tracing of COM method calls across the network (implemented in conjunction with Chris Sells and presented at the Software Development conference in San Francisco earlier this year), to automating the adjustment of security blankets on proxies. The Universal Delegator (UD) described here is the culmination of this work. It's a lightweight interception framework that lets you aggregate any object and, much more importantly, compose generic services onto any object, à la Microsoft® Transaction Server (MTS) and COM+.
Motivation
|
Figure 1 Mirroring COM Objects |
One obvious problem with this approach is its tediousness. For each class of object, the auditing layer must provide an in-process implementation of all of the interfaces exposed from the underlying object via QueryInterface. Any sane developer faced with this sort of tedium will prefer to develop a code generator that automates this task (having a type library is helpful here). However, even this approach has its limits. First, it adds unnecessary code bloat, as each class must be mirrored individually in the in-process auditing layer. Second, unless the auditing layer is designed carefully, it may introduce new and undesirable semantics as well as significant performance penalties. To understand this, imagine that an existing object runs in apartment A, and that some code in apartment B holds a standard proxy to that object. Proxies have an interesting property that allows them to be exported to other apartments without generating an intermediary stub. In other words, using standard marshaling, COM never creates proxies to proxies. The standard proxy accomplishes this magic by implementing IMarshal and effectively marshaling the same OBJREF from which it was originally created. It's as if the proxy marshals itself by value because, upon unmarshaling, COM creates a new proxy that directly references the original stub. Figure 2 shows the effect of this behavior, which is not only more efficient than creating proxies to proxies, but also imperative due to the lifetime issues I'll discuss later. |
Figure 2 Multiple Proxies-Single Stub |
Unfortunately, the naïve use of layering can break the standard proxy's marshaling scheme and cause a middleman stub to be created. Figure 3 shows the marshaling behavior when an in-process layer is added on top of a standard proxy. If an interface pointer to the layer is marshaled to another apartment, unless the layer explicitly custom marshals, the standard marshaling architecture will take over and create another proxy/stub pair, causing the original layer object to act as a middleman. Even if apartment B releases the layer object, the stub still holds references to it and will keep it alive to service requests from apartment C. Apartment B isn't aware of this, however, and may shut down prematurely, abruptly tearing down the connection between apartments A and C. The obvious solution is to add custom marshaling to each class in the auditing layer, but there's got to be a better way. |
Figure 3 Multiple Proxies-Multiple (Middleman) Stubs |
Interception
Today's Limitations
|
Figure 4 UD Architecture |
Before diving into the fun-but-grungy ASM, it would be useful to see the big picture. Figure 4 demonstrates the overall UD architecture. All the tricky interception plumbing is hidden away inside the generic UD component, while the interception policy (auditing, for instance) is factored into a separate pluggable COM component called a hook that can preprocess or postprocess each method call. This makes it relatively easy to design interesting services that can be composed with existing objects.
Using the Universal Delegator
Using the UD in
Client Code
|
Figure 8 Using the UD in Multiple Apartments |
The only problem with this picture is that the delegator drops off the top of the proxy in the new apartment (see Figure 8). Imagine using the Alternate Credentials Hook on a proxy, and then tucking the UD away into the Global Interface Table (GIT) so it can be shared with other apartments in the process. If the UD doesn't do something special, each apartment that calls GetInterfaceFromGlobal will retrieve the original proxy, not the UD, which means another UD will need to be wrapped around the proxy in the new apartment. This is tedious at best, and motivates the need for the DO_MBV_XXX flags that can be passed to Create-Delegator. Specifying one or more of these flags causes the delegator to expose its own implementation of IMarshal to marshal itself by value, taking the hook along with it. Figure 9 shows the behavior when using this type of delegator. |
Figure 9 Using the UD's IMarshal Interface |
Normally, when importing an interface pointer into an apartment (via the GIT, for example), the interface proxies are not copied when a new proxy manager is created. Instead, the proxy manager regenerates them on demand, so the security settings held by the interface proxies in the original apartment are lost. A UD created with the DO_MBV_INPROC option and the Alternate Credentials Hook prevents this annoying situation, since the proxy can be wrapped a single time, placed into the GIT, and the security settings (stored in the hook) will appear to follow the proxy from apartment to apartment transparently. I will explain how the delegator implements this feature in my next article. Careless use of this feature can lead to a security breach, however. Recall that when specifying alternate credentials for a proxy, a clear-text password is required. If a UD created with the DO_MBV_ALL flag (which is just a bitwise OR of all the other DO_MBV_ XXX flags) is exported out of the process, potentially to another machine, the initialized hook goes with it. In the case of the Alternate Credentials Hook, which may store a cleartext password as part of its state, this causes the password to be transmitted across the wire in the clear unless the PKT_PRIVACY authn level is used for the call. Even with PKT_ PRIVACY, it is not normally desirable to propagate passwords without bound. Anyone obtaining the wrapped proxy can easily call Query-Blanket to obtain a user account along with its corresponding password. This was one of the motivating factors for providing clear boundaries for the DO_MBV_ XXX flags. When using these flags, carefully consider how far the delegator should propagate itself. Often, DO_ MBV_INPROC is best. Figure 10 shows a summary of the DO_MBV_XXX options and their associated boundaries.
Using the UD in Server Code
From the January 1999 issue of . Get it at your local newsstand, or better yet, subscribe.
|
For related information see: The Basics of Programming Model Design http://msdn.microsoft.com/library/techart/msdn_basicpmd.htm. Also check http://msdn.microsoft.com for daily updates on developer programs, resources and events. |
From the January 1999 issue of Microsoft Systems Journal.
|