Virtual Server and COM Security
The current beta of Microsoft Virtual Server 2005 includes some changes to the API when compared to the original alpha build that had been around for quite a while and was effectively a Microsoft-labeled Connectix build. Besides for some changes to the interface and class names to improve consistency, the most noticeable change to the API is in the security requirements.
Virtual Server is exposed to programmers as a local or remote COM server, hosted in a Windows service. To create an instance of the VMVirtualServer coclass you can use the following pretty typical C++ code. Assume CheckError is a function that checks for a successful HRESULT return code and throws an exception if it is not.
COSERVERINFO serverInfo = { 0 };
serverInfo.pwszName = L"server";
MULTI_QI multiQi = { &__uuidof(IVMVirtualServer) };
CheckError(::CoCreateInstanceEx(__uuidof(VMVirtualServer),
0,
CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER,
&serverInfo,
1,
&multiQi));
CComPtr<IVMVirtualServer> virtualServer;
virtualServer.Attach(static_cast<IVMVirtualServer*>(multiQi.pItf));
So far so good. The trouble comes when you want to actually use the API. Here is some code to enumerate the VM’s hosted by Virtual Server.
CComPtr<IVMVirtualMachineCollection> virtualMachines;
CheckError(virtualServer->get_VirtualMachines(&virtualMachines));
long count = 0;
CheckError(virtualMachines->get_Count(&count));
for (long i = 0; i < count; ++i)
{
CComPtr<IVMVirtualMachine> virtualMachine;
CheckError(virtualMachines->get_Item(i + 1,
&virtualMachine));
CComBSTR name;
CheckError(virtualMachine->get_Name(&name));
std::wcout << static_cast<PCWSTR>(name) << std::endl;
}
This will compile and might even work depending on how the defaults for DCOM are configured on your computer. This relates to the security changes I mentioned previously. Ignoring the interface name changes, this code would have worked in the original Virtual Server build. The reason it doesn’t work anymore is that Virtual Server’s COM server now requires the ability to impersonate COM clients. If you use Virtual Server’s web administration tool to create a VM and then look at one of the files created for the VM, such as the hard disk image (.VHD) file, you should notice that the creator/owner of the file is your identity as apposed to the identity of the COM server. This indicates that the server is impersonating the COM client in order to access the file system. In previous builds where the server was not impersonating, the files would have been created as the server identity, typically the built-in Network Service account. This sounds great until you realize that the default impersonation level for COM clients allows the server only to get the client’s identity but does not allow it to impersonate the client. This is a good thing as it limits what a malicious or compromised server could do with the client’s identity.
There are a few ways to change the impersonation level, all with different tradeoffs.
The first option is to change the machine-wide default impersonation level. This can be done using the Component Services snap-in, on the Default Properties tab of the property sheet for the My Computer node. This is obviously not what you want to do as it could affect every other COM application running on your computer and leaves them unnecessarily vulnerable to exploits.
The second option is to set the process-wide default impersonation level. This can be done using the CoInitializeSecurity function. There are two problems with this approach. The first is that it has too wide an impact. It affects all the COM clients within the process and also affects any COM servers hosted in the process. The second problem is that you can only call CoInitializeSecurity once per process, so if your Virtual Server code resides in DLL it would not be appropriate for you to call it since you don’t know when you will be loaded.
This leaves one more option and that is to set the impersonation level on every proxy before calling any interface methods through it. You can do this using the CoSetProxyBlanket function. Internally, CoSetProxyBlanket gets the IClientSecurity interface implemented by the proxy and calls the SetBlanket method to set the authentication information that will be used in calls on the proxy. This results in a bit more coding, but far less unnecessary impact on other COM clients that may not want to be exposed to impersonation.
So given the following function to set the impersonation level of a proxy,
void SetImpersonationLevel(IUnknown* proxy)
{
CheckError(::CoSetProxyBlanket(proxy,
RPC_C_AUTHN_DEFAULT,
RPC_C_AUTHZ_DEFAULT,
COLE_DEFAULT_PRINCIPAL,
RPC_C_AUTHN_LEVEL_DEFAULT,
RPC_C_IMP_LEVEL_IMPERSONATE,
COLE_DEFAULT_AUTHINFO,
EOAC_DEFAULT));
}
you could change the previous example of enumerating the VM’s hosted by Virtual Server to work reliably no matter what the default impersonation level is on the machine or the process.
SetImpersonationLevel(virtualServer);
CComPtr<IVMVirtualMachineCollection> virtualMachines;
CheckError(virtualServer->get_VirtualMachines(&virtualMachines));
SetImpersonationLevel(virtualMachines);
long count = 0;
CheckError(virtualMachines->get_Count(&count));
for (long i = 0; i < count; ++i)
{
CComPtr<IVMVirtualMachine> virtualMachine;
CheckError(virtualMachines->get_Item(i + 1,
&virtualMachine));
SetImpersonationLevel(virtualMachine);
CComBSTR name;
CheckError(virtualMachine->get_Name(&name));
std::wcout << static_cast<PCWSTR>(name) << std::endl;
}
© 2004 Kenny Kerr