5

Issue:

I successfully call CoSetProxyBlanket on a proxy (if that's the right term for it) and then I call QueryInterface on that same proxy, but I receive a result of 0x80070005 ("Access Denied"). However, if I first call CoInitializeSecurity (which I am trying to avoid) with that same credentials then the call succeeds.

Question:

How can I successfully get the interface I need without having to call CoInitializeSecurity? From what I understand, a process can only call this method once so its not compatible with making a dll and can usually be substituted with calls to CoSetProxyBlanket.

Details:

I am experimenting with building my own OPC client that can communicate to computers running on different domains without matching user accounts.

First, I create an identity structure with a domain, username, and password that are valid on the server:

COAUTHINFO      authInfo;
COAUTHIDENTITY  authIdentity;

authIdentity.Domain             = (unsigned short *) w_domain;
authIdentity.DomainLength       = wcslen( w_domain);
authIdentity.Flags              = SEC_WINNT_AUTH_IDENTITY_UNICODE;
authIdentity.Password           = (unsigned short *) w_password;
authIdentity.PasswordLength     = wcslen(w_password);
authIdentity.User               = (unsigned short *) w_username;
authIdentity.UserLength         = wcslen(w_username);

authInfo.dwAuthnLevel           = RPC_C_AUTHN_LEVEL_CALL;
authInfo.dwAuthnSvc             = RPC_C_AUTHN_WINNT;
authInfo.dwAuthzSvc             = RPC_C_AUTHZ_NONE;
authInfo.dwCapabilities         = EOAC_NONE;
authInfo.dwImpersonationLevel   = RPC_C_IMP_LEVEL_IMPERSONATE;
authInfo.pAuthIdentityData      = &authIdentity;
authInfo.pwszServerPrincName    = NULL;

ServerInfo.pAuthInfo = &authInfo;

Then I am able to call CoCreateInstanceEx with this server info an obtain a handle (m_IOPCServer) to my OPC server (IID_IOPCServer).

After I obtain the handle, I've found that it is necessary to once again set more permissions (see How does impersonation in DCOM work?) with this call:

hr = CoSetProxyBlanket(m_IOPCServer, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, 
         NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, 
         &authIdentity, EOAC_NONE);

After this I am able to successfully obtain a handle to a OPC Item Group:

hr = m_IOPCServer->AddGroup(L"", FALSE, reqUptRate, clientHandle, 
         NULL, NULL, lcid, &m_hServerGroup, &revisedUptRate, 
         IID_IOPCItemMgt,(LPUNKNOWN*)&m_IOPCItemMgt);

However, when I try to use this code:

hr = m_IOPCItemMgt->QueryInterface(IID_IOPCSyncIO, (void**)&m_IOPCSyncIO);

The result is 0x80070005 ("Access Denied"). This is the case even if I successfully call CoSetProxyBlanket on m_IOPCItemMgt. However if I first call CoInitializeSecurity, then the call succeeds.

I believe the issue related to How does impersonation in DCOM work? in that the QueryInterface function is a form of object creation so it doesn't use the same security as the other method calls like AddGroup. However in the Microsoft reference QueryInterface, under notes to implementer, it makes it sound like QueryInterface shouldn't be checking ACLs and under return values, Access Denied is not mentioned as a possibility. I don't think that this issue is implementation specific though because I have tried my code on some well known commercial OPC servers (e.g. Matrikon Simulation Server) as well as the opensource LightOPC which doesn't implement any extra security.

I am guessing that what I need to do is to find a way to replicate this command

hr = m_IOPCItemMgt->QueryInterface(IID_IOPCSyncIO, (void**)&m_IOPCSyncIO);

but do so while also supplying authIdentity. Is this possible? Can it be done with CoCreateInstanceEx or CoGetClassObject or some other COM call?

Community
  • 1
  • 1
bruceceng
  • 1,844
  • 18
  • 23

3 Answers3

1

Without going into too much detail: CoInitializeSecurity is always invoked at least once per process. This can be done implicitly or explicitly. If your code doesn't make an explicit call, the DCOM runtime does it for you with parameters populated from the registry. You can try to tweak the appropriate registry values to force DCOm using values similar to those used in your explicit call. The registry key that holds those values is "HKEY_LOCAL_MACHINE\SOFTWARE\Classes\AppID{AppID_GUID}" This key is described here:https://msdn.microsoft.com/en-us/library/windows/desktop/ms693736(v=vs.85).aspx

Victor Havin
  • 1,023
  • 7
  • 11
  • That doesn't help me. How can I call QueryInterface with a different security context than the process? For example, how could I create a program that connected to multiple OPC servers using different credentials for each? – bruceceng Sep 13 '16 at 03:59
  • 1
    Just a note to your "under return values, Access Denied is not mentioned as a possibility": In COM, a set of success codes is considered a part of the interface contract, while the set of error codes is not. That is, an interface method is allowed to return ANY error it wants, even if not documented, without breaking the "interface contract". There are many error codes (such as the RPC_xxxx ones) that just about any (D)COM method can return, and they are not documented with each method. The documented error codes are thus "for illustration", those that deserve explanation, but nothing more. – ZbynekZ Sep 13 '16 at 04:27
  • You don't have much control over the security context in a QuerryInterface call. You can probably implement a custom surrogate and tweak CoInitializeSecurity in the surrogate, but you will have one security context per surrogate. More about custom surrogates here: https://msdn.microsoft.com/en-us/library/windows/desktop/ms682432(v=vs.85).aspx – Victor Havin Sep 13 '16 at 05:36
1

You have to call CoSetProxyBlanket on every new COM object instance, so in your case you have to call it even for m_IOPCItemMgt.

0

You need to call CoSetProxyBlanket in the IUnknown interface before using QueryInterface

CComPtr<IUnknown> pUnknown;
hr = m_IOPCItemMgt->QueryInterface(IID_IUnknown, (void**)&pUnknown);
hr = CoSetProxyBlanket(pUnknown, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, 
         NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, 
         &authIdentity, EOAC_NONE);
hr = pUnknown->QueryInterface(IID_IOPCSyncIO, (void**)&m_IOPCSyncIO);

You can check this for more info.

Cristian
  • 26
  • 6