Does this operation work when impersonating? The default answer is NO

Date:September 28, 2011 / year-entry #232
Tags:code
Orig Link:https://blogs.msdn.microsoft.com/oldnewthing/20110928-00/?p=9533
Comments:    23
Summary:I'll often see a customer ask for assistance with a scenario like this: "We're having trouble doing X. We're doing X1, X2, and X3, but it looks like we're getting the wrong answer back." The next step in the conversation goes something like "There must be something else going on, because X1, X2 and X3...

I'll often see a customer ask for assistance with a scenario like this: "We're having trouble doing X. We're doing X1, X2, and X3, but it looks like we're getting the wrong answer back."

The next step in the conversation goes something like "There must be something else going on, because X1, X2 and X3 is the correct way of doing X. To demonstrate, I've written the following sample program that illustrates doing X by the X1, X2, X3 technique. When I run it, I get the correct answer. What do you get when you run it?"

"When we run your program we get the correct answer, but it doesn't work when we do it from our program." And then, as if by afterthought, "Could the problem be that we're impersonating?"

Ohhhhhh, you're impersonating. Thanks for not mentioning that.

By default, nothing works when impersonating. Impersonation requires end-to-end awareness. A function might create a worker thread—the worker thread runs with the identity of the process. A function might use a function like Queue­Usere­Work­Item—by default, the work item runs with the identity of the process. (You have to pass WT_TRANSFER_IMPERSONATION if you want the work item to respect impersonation.) A function might send a message to another window—that window will do its work under its own security token, not the token of the sender. A function might invoke a method on a remote COM object—that object will run under its own security token, not the token of the invoker. (COM requires you to call Co­Set­Proxy­Blanket to enable impersonation transfer during marshaling, and the server needs to call CoImpersonateClient. For some reason, this is called cloaking.) The registry keys HKEY_CURRENT_USER and HKEY_CLASSES_ROOT don't work when you're impersonating. (You have to use RegOpenCurrentUser or RegOpenUserClassesRoot.) Functions like SHGetKnownFolderPath have a token parameter which is used when impersonating; if you pass NULL, then it assumes you aren't impersonating.

The requirements go beyond just code that runs during the execution of the function in question. If you have a function which caches information across calls, the cache needs to be made impersonation-aware so that a value calculated when called while impersonating user X isn't mistakenly used while impersonating user Y.

In order for impersonation to work, every function all the way down the chain needs to be impersonation-safe. Sure, you might be careful to call QueueUserWorkItem with the WT_TRANSFER_IMPERSONATION flag, and your work item is careful to call SetProxyBlanket on its COM objects, and your COM server is careful to call CoImpersonateClient when servicing the call, but if your COM server then calls a helper object which calls SHGetKnownFolderPath and passes NULL for the impersonation token, then all your careful work has been for naught.

This is another special case of When you create an object with constraints, you have to make sure everybody who uses the object understands those constraints.

The Programming Golden Rule can be applied here as well: When you write your own code, do you do this? Since most people who write code do not think about impersonation (indeed, the operating system even encourages not-impersonation-safe coding when it provides conveniences like HKEY_CURRENT_USER) the default answer to "Does this work when I'm impersonating" is "No."


Comments (23)
  1. Ian says:

    "Ohhhhhh, you're impersonating. Thanks for not mentioning that."

    This perfectly illustrates the importance of boiling down your problem to the smallest and simplest test case that still exhibits the problem before calling for support. It is a shame so few people seem to do this vital step. If this customer had, he would almost certainly have discovered that as soon as he removed impersonation from his test case everything would have started working and he would have had his answer before even contacting support.

  2. Joshua says:

    No wonder people want cheaper process create.

  3. ipoverscsi says:

    If impersonation is so hard to implement, under what circumstances would you actually bother using it?

  4. Joe Dietz says:

    Impersonation is important for any sort of server process you might need to implement that needs to do work for and on-behalf of a user.  Its actually a very important security tool as well.  You could write a service that acts with super-user authority, but the you would need to ensure that you are providing all of the necessary security checks yourself, and of course you will get it wrong.  But aside from that without impersonation your service won't be able to interact with other services that have per-user behaviors like file system encryption, file servers etc.

  5. David Walker says:

    I like the comment "By default, nothing works when impersonating".  That is very important to remember when debugging (and when programming, too).

    I often run SQL Server Management Studio using RunAs (VPN does weird things and my domain is not trusted otherwise).  I'm not sure if this is the same as impersonation, but still, my local SQL Server Management Studio client doesn't display the right user information in the UI in some cases.  It's not a big deal, or I would open a ticket.  But when I'm trying to diagnose problems like this, I need to remember that by default, nothing works when you're pretending to be someone you're not.

  6. Joshua says:

    @David Walker: I run that way as well. Disable UAC. It fixes almost all the glitches in that environment.

  7. Bill Edmonds says:

    @JamesJohnston: Raymond is over-shortening the URLs. Try adding a /library to the path, for example msdn.microsoft.com/en-us/ms724836.aspx (or whatever language/dialect you get redirected to) becomes msdn.microsoft.com/en-us/library/ms724836.aspx .

  8. Alex Grigoriev says:

    @DW:

    RunAs, at least in some OS versions, may not establish the complete environment of the target user. If I remember correctly, sometimes the env vars are not set, or like that. I had some programs not working even in Win7. A workaround that made them work: Start CMD by RunAs, then start your target from it.

  9. JM says:

    Note that RunAs has nothing to do with impersonation — it's a separate login and the process starts with a security token of the other account. Problems you have with RunAs are related to the environment variables or the user profile — runas /? gives you some options on that. If RunAs used impersonation, it would have a very low chance of accomplishing anything, per the problems Raymond has noted.

  10. zonk says:

    Then why impersonation was ever invented? Some kind of suid and common transport like messages or pipes that can cross boundaries will be just fine.

  11. Unrelated: does anybody else dislike the new MSDN styles?  The MSDN link for "(You have to use RegOpenCurrentUser or RegOpenUserClassesRoot.)" doesn't even have navigation.  Personally, I can't stand anything but MSDN Classic, since it's the only view I've found that has a tree view on the left.  I'm *not* looking forward to the day they remove that tree view, though I'm sure it's coming as probably "classic" == "legacy code we want to get rid of"…

  12. Yuhong Bao says:

    Note for the registry there is RegDisablePredefinedCache which can disable the cache for HKCU.

  13. Ian Boyd says:

    Can anyone point to any MSDN documentation that explains what Impersonation is, why it exists (the problem it solves), the caveats when using it, how to use it, and why things don't work when using it?

  14. alegr1 says:

    @zonk:

    Suppose you have a server. Remote clients connect to it and request to open files or other secure resources, or perform other actions on behalf of the remote user. To achieve that, the service thread assumes the identity of the client (impersonates).

  15. zonk says:

    zonk: "Then why impersonation was ever invented?"

    To allow impersonation. (msdn.microsoft.com/…/ms691341(v=vs.85).aspx)

  16. Crescens2k says:

    zonk:

    "Some kind of suid and common transport like messages or pipes that can cross boundaries will be just fine."

    Except that the security checks will not be done for the user you want to impersonate and so things may be inaccurate. Two potential problems are, access to HKCU, if you are running a service which wants to access this key, then it is easier to impersonate and access HKCU that way. Two, you are also assuming that the process which you want to access things on behalf of a particular user can access everything that user can. Now what happens if you are being security aware and program your application/service to run as a limited user and the user you want to work on behalf of is an administrator.

    There are other things too, usually the best way of checking if a user has access to a resource relatively cheaply is to just try to open it. Of course, without impersonation then you are going to have to do things the hard way and it is likely you will still get it wrong. So instead of trying to guess or check the security information in that token, why not just impersonate and use the regular function to do the work. The other thing is that if you want to create files on behalf of a user, you could do the file access and then change the owner and the acl on that file, but it is again much easier to impersonate and work on that file, the security information is then set to that user by default.

    Ian Boyd:

    Simply put, impersonation is when you want to do something using a different user's identity. It isn't a common occurance and you usually find it happening in server processes. As an example, have you ever checked the owner of say any of the visual studio files? You should find that they are all owned by System. This is because they were written to disk by the Windows Installer service, and that runs as System. But what happened if you had a service which writes to disk like Windows Installer, but you wanted to set the owner of the files to the user who invoked it. The service itself would either have to be started every single time with credentials for that user, making it lose effectiveness as a service, write files and then change the owner and acl afterwards which requires an extra step or two, or you can use impersonation. With impersonation, the service could run as any user account which allows it to do its job, and when you want to write files you set the impersonation token and just write. The owner and acl will be set automatically to the defaults for the impersonated user rather than the service's defaults. Of course, this is just one of many uses, but it should illustrate that while it isn't something you will use often, when you do need to use it then it will be extremly useful.

  17. Deduplicator says:

    As such, impersonation is a tool for avoiding the inherent overhead of process creation for "just a few measly operations" as a different user. That would still be worthwhile if you could avoid the userland process-startup as with unix-like fork(), as there would still be all the unavoidable in-kernel overhead.

  18. Simon Smith says:

    The main user of impersonation here is IIS: the ASP.NET worker process runs as the user defined by its App Pool, but we specify impersonation in the Web.Config file. The thread in the worker process handling a request impersonates the calling user and does things like check AD for the relevant permissions.

  19. David Walker says:

    Thanks for the comments on RunAs; I'll check it out.  It's only a display issue in the status bar of SQL Server Management Studio, so it's not crucial, but maybe getting the environment variables set right would help.  

    On the other hand, the incorrect display doesn't bother me much.  The other side does trust me when I use RunAs and doesn't trust me when I don't, so the important things work under RunAs.  And now I know that RunAs is not impersonation.  :-)

  20. cheong00 says:

    Imperonation is essential for actions like remote starting / stopping service on another server using web interface, without granting right to access these service with current server machine account. (For things like administrative panels on web based systems and virtualization solutions)

  21. cheong00 says:

    I think a lot of parallelization libraries creates worker thread for you. Does that mean if your code ever need impersaontion, it'd be unsafe to call them?

    I think it'd be common for web developers to call functions on PLINQ or just LINQ and others that have similar effect.

  22. cheong00 says:

    Ok, maybe database related libraries doesn't matter (SSPI is aware of that I think) but the point on parallelization libraries get across.

  23. gdalsnes says:

    I don't understand why it was designed not to work. If you are impersonating a thread, the system knows and could do the right thing. If you are impersonating and wanted to do some superuser stuff on the side, _then_ you could pass a token to override. The current design it the opposite of what seems logical (to me).

    [I think you missed the point. The system (i.e., kernel) is fine with impersonation. The problem is all the code that's not part of the system which assumes identity does not change (because it rarely does). You probably write code like that all the time, for example, any time you implement a cache. You pass the superuser token to do some side work, but oh sorry, that side work fails because somebody cached the fact that the process is not running as superuser. -Raymond]

Comments are closed.


*DISCLAIMER: I DO NOT OWN THIS CONTENT. If you are the owner and would like it removed, please contact me. The content herein is an archived reproduction of entries from Raymond Chen's "Old New Thing" Blog (most recent link is here). It may have slight formatting modifications for consistency and to improve readability.

WHY DID I DUPLICATE THIS CONTENT HERE? Let me first say this site has never had anything to sell and has never shown ads of any kind. I have nothing monetarily to gain by duplicating content here. Because I had made my own local copy of this content throughout the years, for ease of using tools like grep, I decided to put it online after I discovered some of the original content previously and publicly available, had disappeared approximately early to mid 2019. At the same time, I present the content in an easily accessible theme-agnostic way.

The information provided by Raymond's blog is, for all practical purposes, more authoritative on Windows Development than Microsoft's own MSDN documentation and should be considered supplemental reading to that documentation. The wealth of missing details provided by this blog that Microsoft could not or did not document about Windows over the years is vital enough, many would agree an online "backup" of these details is a necessary endeavor. Specifics include:

<-- Back to Old New Thing Archive Index