COM delegation using COM channel hook mechanism
In one of my projects I needed a delegation feature for COM mechanism. The server, running under high-privileged account needed to issue COM calls on behalf of low-privileged clients to another COM server. After searching in MSDN I have found that the current situation is pretty simple: "COM does not support delegation". Although named pipes in WindowsNT environment support it for a single machine operations - COM does not. To overcome this situation I created my own user impersonation mechanism based on the usage of COM channel hook interface.
The idea behind the usage of COM channel hook mechanism is described in the very good
and comprehensive way in January'98 issue of MSJ. In short, application could register COM
callback interface that will be called by COM engine when it sends or receives a call. So
application can add a whatever information it needs in the transparent for the interface
user way. The callback interface IChannelHook consists of the following functions:
void ClientGetSize(REFGUID uExtent, REFIID riid, ULONG *pDataSize);
void ClientFillBuffer(REFGUID uExtent, REFIID riid, ULONG *pDataSize, void *pDataBuffer);
void ClientNotify(REFGUID uExtent, REFIID riid, ULONG cbDataSize, void *pDataBuffer, DWORD
lDataRep, HRESULT hrFault);
void ServerNotify(REFGUID uExtent, REFIID riid, ULONG cbDataSize, void *pDataBuffer, DWORD
lDataRep);
void ServerGetSize(REFGUID uExtent, REFIID riid, HRESULT hrFault, ULONG *pDataSize);
void ServerFillBuffer(REFGUID uExtent, REFIID riid, ULONG *pDataSize, void *pDataBuffer,
HRESULT hrFault);
When a COM client issues call to server, first ClientGetSize is called on the client's
side to determine size of additional data and then ClientFillBuffer to fill buffer with
actual data. When COM call arrives to the server - server side gets notified about arrived
data through ServerNotify function. Functions ServerGetSize and ServerFillBuffer are
called on the way back on the server's side and, finally, ClientNotify when the call
returns on the client's side.
To make delegation possible I attach the following information to the COM hook buffer:
- process ID of the process that issued a call
- network name of the computer that issued a call
- user token of the thread that issued a call (if it exists) or process-wide user token
(if it does not)
When the data arrives on the server's side the code first checks whether the client and
server reside on the same machine, using transferred machine name, because user token that
we have transferred as well makes sense only on the same machine. If machine is the same,
server tries to open a process that issued a call and duplicate token, for it to be valid
for the server's process. And then it uses the duplicated token to call
ImpersonateLoggedOnUser function to do actual user impersonation.
All information about incoming calls is kept in the thread local storage (TLS) on
per-thread basis. To avoid losing information in case of recursive COM calls, the
information about incoming calls is kept as a stack and every new call pushes its new
context on the stack and removes it on the way back to server.
The COM channel hook will be installed automatically as long as DLL is loaded into memory
of the process. This could be done either by explicit call to LoadLibrary function or
implicitly by CoCreateInstance call, creating IClientInfo interface.
For the proper functionality of the discussed user impersonation method the channel hook
must be installed both in the client and server processes. For my project I managed to do
it automatically for client and for server by initializing interface to the IClientInfo
from the Proxy-Stub DLL that is automatically generated by MSVC Wizard. To do so you need
to create a new "c" file that looks like this :
//CODE----------------------- (file rcserverps_dllmain.c)
-----------------------------------------------------------------------------
#include <windows.h>
#include "..\COMchannelinfo\dcom_channel_info.h"
#include "..\dcomchannelinfo\dcom_channel_info_i.c"
//previous DLLMain function
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved);
BOOL WINAPI newDllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
HRESULT hr;
static IClientInfo* client_info_iface;
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
{
client_info_iface = NULL;
hr = CoCreateInstance(&CLSID_ClientInfo, NULL,
CLSCTX_INPROC_SERVER, &IID_IClientInfo, (void**)&client_info_iface);
if (FAILED(hr))
{
return FALSE;
};
}; break;
case DLL_PROCESS_DETACH:
{
__try
{
if (client_info_iface != NULL)
{
client_info_iface->lpVtbl->Release(client_info_iface);
};
} __except (1)
{
//we do not care if something has happened on
shutdown
};
}; break;
};
return DllMain(hinstDLL, fdwReason, lpvReserved);
}
//CODE----------------------------------------------------------------------------------------------------
And then patch a generated make-file for your proxy-stub DLL (usually it has name of the
project + "ps.mk" at the end) and make the following changes to it:
//CODE----------------------------------------------------------------------------------------------------
rcserverps.dll: dlldata.obj rcserver_p.obj rcserver_i.obj rcserverps_dllmain.obj
(this is a new object file)
link /dll /out:rcserverps.dll /def:rcserverps.def /entry:newDllMain
(this is new DLL entry point) dlldata.obj rcserver_p.obj \
rcserver_i.obj rcserverps_dllmain.obj (this
is a new object file) \
kernel32.lib rpcndr.lib rpcns4.lib rpcrt4.lib oleaut32.lib uuid.lib ole32.lib
(additional standard library)
//CODE----------------------------------------------------------------------------------------------------
The effect of this operation is the following: as soon as you make a first call to your
COM interface - proxy-stub DLL will be loaded automatically by COM engine and in its
DLLMain it loads and installs our COM channel hook that transfers required information
about called user automatically! You have an advanced impersonation routine for free! :)
Usage of the interface in applications.
You need to register the DLL with the call to regsvr32.exe dcom_channel_info.dll.
Sources, demos, updates and legal stuff.
Latest sources and updates for the application could be found here.
Interface itself is used in the Remote Control application (in its server part) which
could be used as an example on usage of this impersonation method. It also could be found here.
You can drop an e-mail to author here.
Big thanks to Don Box for pointing an elegant
solution to my problem.