////////////////////////////////////////////////////////
// excerpt from delegate.idl
//
[
uuid(9b8c32f1-249a-11d2-a7bb-006008d25ccf),
object,
local
]
interface IDelegatorHookMethods : IUnknown
{
void DelegatorPreprocess( [in] DWORD nVtblIndex,
[in] void* pArgs,
[in, out] DWORD* pnCookie );
HRESULT DelegatorPostprocess( [in] DWORD nVtblIndex,
[in] HRESULT hrFromInner,
[in] DWORD nCookie );
}
[
uuid(9b8c32f9-249a-11d2-a7bb-006008d25ccf),
object,
local
]
interface IDelegatorHookQI : IUnknown
{
typedef enum _DelegatorHookOptions
{
DHO_PREPROCESS_METHODS = 0x00000001,
DHO_POSTPROCESS_METHODS = 0x00000002
} DelegatorHookOptions;
HRESULT Init( [in] IUnknown* pUnkInner );
HRESULT OnFirstDelegatorQIFor(
[in] REFIID iid,
[in, iid_is(iid)] IUnknown* pItfInner,
[out] DWORD* pgrfDelegatorHookOptions,
[in] REFIID iidMethodHook,
[out, iid_is(iidMethodHook)] void** ppMethodHook );
}
Figure 2 CoDelegator::QueryInterface Implementation
////////////////////////////////////////////////////////
// excerpt from CoDelegator.cpp
//
STDMETHODIMP CoDelegator::QueryInterface( REFIID iid, void** ppv )
{
// we subsume the identity of the inner
if ( IID_IUnknown == iid )
{
((IUnknown*)(*ppv = static_cast<IUnknown*>(this)))->AddRef();
return S_OK;
}
else if ( ( IID_IMarshal == iid ) && ( DO_MBV_ALL & m_grf ) )
{
((IUnknown*)(*ppv = static_cast<IMarshal*>(this)))->AddRef();
return S_OK;
}
*ppv = 0;
Lock lock( *this );
// see if we've already assimilated the requested interface
HRESULT hr = S_OK;
{
Delegator* pDelegator = 0;
if ( _findDelegator( iid, pDelegator, hr ) )
{
if ( SUCCEEDED( hr ) )
reinterpret_cast<IUnknown*>( *ppv =
pDelegator )->AddRef();
return hr;
}
}
// go get the requested interface from the inner and assimilate it.
IUnknown* pUnkInner = 0;
hr = m_pUnkInner->QueryInterface( iid, (void**)&pUnkInner );
if ( SUCCEEDED( hr ) )
{
DWORD grfOptions = 0;
IDelegatorHookMethods* pHookMethods = 0;
if ( m_pHook )
{
hr = m_pHook->OnFirstDelegatorQIFor( iid, pUnkInner,
&grfOptions, IID_IDelegatorHookMethods,
(void**)&pHookMethods );
if ( SUCCEEDED( hr ) )
{
// watch for invalid results from QI hook
grfOptions = grfOptions & 0x00000007;
if ( ( 0 == grfOptions ) && pHookMethods )
{
pHookMethods->Release();
pHookMethods = 0;
}
}
else if ( E_NOINTERFACE == hr )
{
// we only give the QI hook *one* chance to say
// this to a particular interface so that we
// help maintain a correct implementation of QI.
grfOptions = Delegator::DONT_EXPOSE_FROM_QI;
hr = S_OK;
}
// if postprocessing is required,
// make sure we've acquired a TLS slot
if ( SUCCEEDED( hr )
&& ( DHO_POSTPROCESS_METHODS & grfOptions )
&& !_lazyAllocTLSIndex() )
{
hr = E_OUTOFMEMORY; // sort of :-)
if ( pHookMethods )
{
pHookMethods->Release();
pHookMethods = 0;
}
}
}
if ( SUCCEEDED( hr ) )
{
// grow the array if necessary
if ( m_last == m_end )
hr = _growArray();
if ( SUCCEEDED( hr ) )
{
Delegator* pDelegator =
new Delegator( *this, pUnkInner, iid,
grfOptions, pHookMethods );
if ( pDelegator )
{
*m_last++ = pDelegator;
if ( Delegator::DONT_EXPOSE_FROM_QI &
grfOptions )
hr = E_NOINTERFACE;
else reinterpret_cast<IUnknown*>
(*ppv = pDelegator)->AddRef();
}
else hr = E_OUTOFMEMORY;
}
if ( pHookMethods )
pHookMethods->Release();
}
pUnkInner->Release();
}
return hr;
}
Figure 4 IDelegatorHook Methods
struct MethodHook : IDelegatorHookMethods
{
const IID m_iid;
MethodHook( REFIID iid ) : m_iid( iid ) {}
// this could be some smart method that maps
// method indices to names via type information
// and dumps them to debug output or a logfile
void LogMessage( const TCHAR* psz,
DWORD nMethod )
{
// implementation omitted for brevity
}
// IUnknown implementation omitted for brevity
// IDelegatorHookMethods implementation
void DelegatorPreprocess( DWORD nIndex,
void*, DWORD* )
{
LogMessage( __TEXT( "entering" ), nIndex );
}
HRESULT DelegatorPostprocess( DWORD nVtblIndex,
HRESULT, DWORD )
{
LogMessage( __TEXT( "leaving" ), nIndex );
return S_OK;
}
};
HRESULT CoAuditorHookQI::OnFirstDelegatorQIFor(
REFIID iid, IUnknown*, DWORD* pgrfOptions,
REFIID iidMethodHook, void** ppvMethodHook )
{
HRESULT hr = S_OK;
MethodHook* pHook = new MethodHook( iid );
pHook->AddRef();
hr = pHook->QueryInterface( iidMethodHook,
ppvMethodHook );
pHook->Release();
return hr;
}
Figure 6 Implementing Delegation
// delegation code
void __declspec(naked) delegate(void) {
__asm {
// stack: nVtblOffset/retaddr/this/args
mov eax, [esp+8] // eax = this (the shim)
mov eax, [eax+4] // eax = this->m_pUnkInner
mov [esp+8], eax // replace this ptr on stack
mov eax, [eax] // eax = vptr
add eax, [esp] // eax += nVtblOffset
mov eax, [eax] // eax = vptr[nVtblOffset]
add esp, 4 // pop nVtblOffset
jmp eax // delegate call
}
}
// shim entry points
void __declspec(naked) method0(void) {
__asm push 0 // zero byte offset into vtable
__asm jmp delegate
}
void __declspec(naked) method1(void) {
__asm push 4 // four byte offset into vtable
__asm jmp delegate
}
// and so on, for all methods in the interface
Figure 7 Delegator Postprocessing
////////////////////////////////////////////////////////
// excerpt from Delegator.cpp
//
// this method is called if we need to postprocess as well
DWORD __stdcall preprocess2( Delegator& d, const void* pReturnAddr,
DWORD nVtblOffset, void* pArgs )
{
// first try to acquire a buffer - if this fails,
// we cannot do any delegation at all - just shunt the method directly
// to the inner object and forget it.
CallContext* pcc = CoDelegator::PushNewCallContext( d, pReturnAddr,
nVtblOffset );
if ( !pcc )
return 0;
if ( DHO_PREPROCESS_METHODS & d.m_grf )
d.m_pHook->DelegatorPreprocess( nVtblOffset / sizeof( void* ),
pArgs, &pcc->m_nCookie );
return 1;
}
HRESULT __stdcall postprocess( HRESULT hrFromInner,
const void** ppReturnAddr )
{
// get the call context back from TLS
CallContext* const pcc = CoDelegator::PopCallContext();
HRESULT hr = pcc->m_pDelegator->m_pHook->
DelegatorPostprocess( pcc->m_nVtblOffset / sizeof( void* ),
hrFromInner, pcc->m_nCookie );
*ppReturnAddr = pcc->m_pReturnAddr;
CoDelegator::DeleteCallContext( pcc );
return hr;
}
static __declspec(naked) void delegateAndPostprocess(void)
{
__asm
{
// get the vtbl index
pop eax // eax = vtbl index (in bytes)
sub esp, 8
push eax
push ebp // set up simple stack frame
mov ebp, esp
// ebp+4 = local variable: vtbl offset (in bytes)
// ebp+8 = local variable: result of context allocation
// ebp+12 = local variable: address of inner's method
// ebp+16 = retaddr
// ebp+20 = this
// ebp+24 = args
lea eax, [ebp+24] // eax = preprocess( this, pReturnAddr,
push eax // nVtblOffset, pArgs );
push [ebp+4]
push [ebp+16]
push [ebp+20]
call preprocess2
mov [ebp+8], eax // store result of context allocation
mov eax, [ebp+20] // this = eax = pInner
mov eax, [eax+4]
mov [ebp+20], eax
mov eax, [eax] // store address of inner's virtual function
add eax, [ebp+4]
mov eax, [eax]
mov [ebp+12], eax
pop ebp // tear down stack frame
pop eax // discard vtbl offset
pop eax // was context alloc successful?
test eax, 1
jnz allocSuccessful
pop eax // delegate without postprocessing
jmp eax
allocSuccessful:
pop eax
add esp, 4 // remove caller's return addr from stack
call eax // and call inner
sub esp, 4 // make room for original return addr
push esp // eax = postprocess( eax, ppReturnAddr )
push eax
call postprocess
ret
}
}
Figure 8 aclsample.idl
/////////////////////////////////////////////////////////////////////
// aclsample.idl
//
// Sample IDL demonstrating declarative security
// using custom attributes and the access control hook
#include "ahattr.h"
import "unknwn.idl";
[
object,
uuid(69934612-7F39-11D2-A7FF-006008D25CCF),
AH_GRANT_ACCESS( "CouchPotatoes" )
]
interface ISnack : IUnknown
{
HRESULT Eat();
[AH_DENY_ACCESS( "HeavyDrinkers" )]
HRESULT Drink();
}
[
object,
uuid(69934613-7F39-11D2-A7FF-006008D25CCF)
]
interface IPretzel : IUnknown
{
[AH_GRANT_ACCESS( "FolksWithHighBloodPressure" )]
HRESULT ScrapeOffSalt();
HRESULT ChewSlowly();
}
[uuid(69934606-7F39-11D2-A7FF-006008D25CCF), version(1.0)]
library SnackLib
{
importlib("stdole32.tlb");
[
uuid(69934614-7F39-11D2-A7FF-006008D25CCF),
AH_GRANT_ACCESS( "PretzelEaters" )
]
coclass Pretzel
{
[default] interface IPretzel;
interface ISnack;
}
}