Here's an interesting lesson which, quite honestly, I haven't thought about for a while. But it turns out it's rather important.
A little over a month ago, I talked about a change to Windows Vista in SP1 regarding per-user COM registration, indicating that we changed the behavior and it would now work with UAC disabled. Let's dissect how this is implemented specifically. First, I disassembled the function, and then I focused in on the CALL instructions, to find:
0:010> uf ole32!OpenClassesRootKeyExW 251 760878ba e888ffffff call ole32!RegHelpSuspendImpersonate (76087847) 257 760878c5 ff15e4110676 call dword ptr [ole32!_imp__GetCurrentProcess (760611e4)] 257 760878cc ff159c160676 call dword ptr [ole32!_imp__OpenProcessToken (7606169c)] 261 760878e1 e8c9000000 call ole32!IsUserHiveOK (760879af) 265 760878fd ff1554160676 call dword ptr [ole32!_imp__RegOpenUserClassesRoot (76061654)] 269 760f9014 e8fe430100 call ole32!WPP_SF_d (7610d417) 283 76087922 ff15fc160676 call dword ptr [ole32!_imp__RegOpenKeyExW (760616fc)] 286 760f905b e8b7430100 call ole32!WPP_SF_d (7610d417) 290 76087935 ff15e4130676 call dword ptr [ole32!_imp__CloseHandle (760613e4)] 294 760f9065 ff150c140676 call dword ptr [ole32!_imp__GetLastError (7606140c)] 295 760f90a1 e871430100 call ole32!WPP_SF_d (7610d417) 298 7608793e e811010000 call ole32!RegHelpResumeImpersonate (76087a54) 304 76087966 ff15fc160676 call dword ptr [ole32!_imp__RegOpenKeyExW (760616fc)] 307 760f90e3 e8dfd70100 call ole32!WPP_SF_Sdd (761168c7) 309 76087979 ff15f4160676 call dword ptr [ole32!_imp__RegCloseKey (760616f4)]
That ole32!IsUserHiveOK looks interesting, so let's chase that down and see what we find, again just focusing in on the call statements:
0:010> uf ole32!IsUserHiveOK 200 760879cc e83c000000 call ole32!IsElevatedToken (76087a0d) 205 760879e5 e83ae2ffff call ole32!GetTokenElevationType (76085c24) 221 760941e8 e81a000000 call ole32!IsUIAccessToken (76094207)
This is, indeed, interesting. We're checking IsElevatedToken, and then we're calling GetTokenElevationType. What can TokenElevationType tell us? Let's check MSDN:
TOKEN_ELEVATION_TYPE Enumeration The TOKEN_ELEVATION_TYPE enumeration indicates the type of token linked to the token being queried by the GetTokenInformation function or set by the SetTokenInformation function. Syntaxtypedef enum { TokenElevationTypeDefault = 1, TokenElevationTypeFull, TokenElevationTypeLimited } TOKEN_ELEVATION_TYPE , *PTOKEN_ELEVATION_TYPE; Constants TokenElevationTypeDefault The token does not have a linked token. TokenElevationTypeFull The token is linked to an elevated token. TokenElevationTypeLimited The token is linked to a limited token.
The TOKEN_ELEVATION_TYPE enumeration indicates the type of token linked to the token being queried by the GetTokenInformation function or set by the SetTokenInformation function.
typedef enum { TokenElevationTypeDefault = 1, TokenElevationTypeFull, TokenElevationTypeLimited } TOKEN_ELEVATION_TYPE , *PTOKEN_ELEVATION_TYPE;
The token does not have a linked token.
The token is linked to an elevated token.
The token is linked to a limited token.
We can see how, using both elevation state and TokenElevationType, we can determine both the elevation state and if UAC is enabled or disabled. So, we've unraveled what's happening for COM.
But, almost immediately after posting, people began posting about exceptions to the rule, which we began investigating. The lesson here: OLE != OLE Automation. They are different things. And it turns out that OLE Automation doesn't have the fix we incorporated into COM itself. They're different code, incorporated into different DLLs, maintained by different teams. What does OLE Automation do? Let's take the same approach, disassembling the function and zeroing in on the calls it makes:
0:010> uf oleaut32!OpenClassesRootKeyExW 150 75a49f80 e8c7feffff call OLEAUT32!SuspendImpersonate (75a49e4c) 151 75a49f8b ff151412a475 call dword ptr [OLEAUT32!_imp__GetCurrentProcess (75a41214)] 151 75a49f92 ff157413a475 call dword ptr [OLEAUT32!_imp__OpenProcessToken (75a41374)] 155 75a49fad e8cafeffff call OLEAUT32!IsProcessElevated (75a49e7c) 159 75a5ab15 ff153413a475 call dword ptr [OLEAUT32!_imp__RegOpenUserClassesRoot (75a41334)] 173 75a49fd4 ffd3 call ebx 178 75a49fdc ff151012a475 call dword ptr [OLEAUT32!_imp__CloseHandle (75a41210)] 182 75a78782 ff15ec11a475 call dword ptr [OLEAUT32!_imp__GetLastError (75a411ec)] 185 75a49fe8 e842000000 call OLEAUT32!ResumeImpersonate (75a4a02f) 191 75a4a05e ffd3 call ebx 192 75a4a066 ff158013a475 call dword ptr [OLEAUT32!_imp__RegCloseKey (75a41380)]
Things look a bit different here - all we call is IsProcessElevated. If you're running as admin with UAC disabled, this returns true - and the function never calls in to determine the token elevation type. OLE Automation did not get the same update that COM did!
Now, from the Windows side of things, we already have a bug open to fix this, but since I spread the word that it's working and some of you have contacted me suggesting that it isn't, I wanted to make sure it was clear: COM was, indeed, fixed to work with per-user registrations and UAC disabled, but OLE Automation was not.
What can you do in the interim? These are the workarounds I have heard about so far:
Incidentally, for those of you who were perplexed that Live Mesh was released but didn't support running as admin with UAC disabled until Windows Vista SP1 ... what do you think the odds are that there are some per-user COM registrations in there? Hmm...
About a month ago (can you tell I'm a little behind?) we launched the Windows Client TechCenter. My little corner of the site (which is technically at http://technet.microsoft.com/en-us/windows/aa905066.aspx but it's easier to remember http://technet.com/appcompat or http://technet.microsoft.com/en-us/appcompat/default.aspx) dives in to application compatibility and UAC.
There are two things I've been hearing a lot from customers lately. The first is that we need to focus more on the process, providing project plans and making it easier to get started while helping people feel more confident that they haven't missed something along the way. That's a long-term project I'm working on - I presented an overview of our current best practices thinking at both MMS and TechEd this year. The second is to organize all of the knowledge that we have. I remember when we used to struggle just to make knowledge available. Now, we have so much knowledge available, it's hard to find the information which you need.
The TechCenter is my first attempt at helping with the second item. I included key resources and overview materials, and then split out deeper dive materials by role: Project Managers, Testers, Debuggers, and Developers. Within each role, I have attempted to find the best documents (most of which include links for further reading if you find something which strikes a chord in you).
I welcome any feedback - have we hit the right roles? Have we given enough information per role? Collecting knowledge is the easy part - indexing it is harder. It's not quite an end to end learning plan, but I hope it's a valuable first step which we can evolve and continue to make better!
I send out a lot of links to my articles in response to questions that come up, but the other day I had a chance to use a pile of them to solve a fairly complicated problem end to end. So, I figured I'd share how we can piece together all of this knowledge to solve a more sophisticated problem.
Let's begin at the beginning. The CorrectFilePaths shim wasn't working. We were trying to redirect c:\somedata.txt to %userappdata%\somedata.txt, but it wasn't working. Why not? We weren't sure.
So, we started out by turning on shim debug spew to see if the shim was being wired up. Indeed it was - we could see it picking up the shim when we launched the process. We could also look at the process in Process Monitor and see in the stack that the call to write c:\somedata.txt was passing through AcLayers!NS_CorrectFilePaths::APIHook_CreateFileA. So, we knew that the shim wasn't improperly applied.
Our next hypothesis, then, was that the shim's command line was not configured properly. I tried copying and pasting from Process Monitor in case there was a copy error, but no such luck. So, it was time to go in with a debugger and see what was happening.
Now, where do we set the debugger breakpoint? Well, why not when we enter the shim? So, I set a bp on AcLayers!NS_CorrectFilePaths::APIHook_CreateFileA and took a look at the arguments we were passing to this function. What did I see?
\somedata.txt
Indeed, it never specified the C drive - just the root of whatever drive you happened to be running on. And, since CorrectFilePaths just uses a literal string match, the c: was causing the match to fail.
Replace that argument with \somedata.txt;%userappdata\somedata.txt and everything worked fine!
So, using a bit of knowledge of Process Monitor, a bit of knowledge on the Debugging Tools for Windows, and knowledge from this blog, we could actually solve a real-world problem. And that's what I like to see. Let me know if you are finding gaps that nobody is talking about yet, and we'll see if we can get them filled here!