September, 2011

  • The Old New Thing

    Throwing garbage on the sidewalk: The sad history of the rundll32 program

    • 51 Comments

    During the development of Windows Vista, the application comaptibility team traced a bunch of issues back to people corrupting the stack by using the rundll32 program to call functions that were not designed to be called by rundll32.

    The problems were often subtle. For example, a batch file which used rundll32 incorrectly ended up hanging because the rundll32 process never returned. The misaligned stack resulted in registers being restored from the stack incorrectly, and then the cleanup code inside rundll32 ends up getting confused and wedging itself. The programs got away with it on previous versions of Windows by sheer luck. The version of the compiler used by Windows Vista contains different optimizations, and it ended up arranging stack variables and using registers differently, and what in previous versions of Windows was some corruption that went largely unnoticed became corruption that resulted in the program getting stuck in an infinite loop. Lucky no longer.

    I was asked to come up with a solution for this problem, to fix the rundll32 program so it was more resilient to people who used it incorrectly. To fix other people's bugs for them.

    The solution: Before calling the function, push a hundred bytes of garbage onto the stack (in case the called function pops too many bytes off the stack) and save the stack pointer in a global variable. After the function returns, restore the stack pointer, in case the called function pops too many or too few bytes off the stack. I think I may even have saved the processor registers in global variables, I forget.

    Do not consider this free license to continue abusing the rundll32 program. When the pet store opens on Sundays, that doesn't mean that it's okay to keep throwing garbage on the sidewalk.

  • The Old New Thing

    Why do Windows functions all begin with a pointless MOV EDI, EDI instruction?

    • 37 Comments

    If you look at the disassembly of functions inside Windows DLLs, you'll find that they begin with the seemingly pointless instruction MOV EDI, EDI. This instruction copies a register to itself and updates no flags; it is completely meaningless. So why is it there?

    It's a hot-patch point.

    The MOV EDI, EDI instruction is a two-byte NOP, which is just enough space to patch in a jump instruction so that the function can be updated on the fly. The intention is that the MOV EDI, EDI instruction will be replaced with a two-byte JMP $-5 instruction to redirect control to five bytes of patch space that comes immediately before the start of the function. Five bytes is enough for a full jump instruction, which can send control to the replacement function installed somewhere else in the address space.

    Although the five bytes of patch space before the start of the function consists of five one-byte NOP instructions, the function entry point uses a single two-byte NOP.

    Why not use Detours to hot-patch the function, then you don't need any patch space at all.

    The problem with Detouring a function during live execution is that you can never be sure that at the moment you are patching in the Detour, another thread isn't in the middle of executing an instruction that overlaps the first five bytes of the function. (And you have to alter the code generation so that no instruction starting at offsets 1 through 4 of the function is ever the target of a jump.) You could work around this by suspending all the threads while you're patching, but that still won't stop somebody from doing a CreateRemoteThread after you thought you had successfully suspended all the threads.

    Why not just use two NOP instructions at the entry point?

    Well, because a NOP instruction consumes one clock cycle and one pipe, so two of them would consume two clock cycles and two pipes. (The instructions will likely be paired, one in each pipe, so the combined execution will take one clock cycle.) On the other hand, the MOV EDI, EDI instruction consumes one clock cycle and one pipe. (In practice, the instruction will occupy one pipe, leaving the other available to execute another instruction in parallel. You might say that the instruction executes in half a cycle.) However you calculate it, the MOV EDI, EDI instruction executes in half the time of two NOP instructions.

    On the other hand, the five NOPs inserted before the start of the function are never executed, so it doesn't matter what you use to pad them. It could've been five garbage bytes for all anybody cares.

    But much more important than cycle-counting is that the use of a two-byte NOP avoids the Detours problem: If the code had used two single-byte NOP instructions, then there is the risk that you will install your patch just as a thread has finished executing the first single-byte NOP and is about to begin executing the second single-byte NOP, resulting in the thread treating the second half of your JMP $-5 as the start of a new instruction.

    There's a lot of patching machinery going on that most people don't even realize. Maybe at some point, I'll get around to writing about how the operating system manages patches for software that isn't installed yet, so that when you do install the software, the patch is already there, thereby closing the vulnerability window between installing the software and downloading the patches.

  • The Old New Thing

    Why does my asynchronous I/O complete synchronously?

    • 36 Comments

    A customer was creating a large file and found that, even though the file was opened with FILE_FLAG_OVERLAPPED and the Write­File call was being made with an OVERLAPPED structure, the I/O was nevertheless completing synchronously.

    Knowledge Base article 156932 covers some cases in which asynchronous I/O will be converted to synchronous I/O. And in this case, it was scenario number three in that document.

    The reason the customer's asynchronous writes were completing synchronously is that all of the writes were to the end of the file. It so happens that in the current implementation of NTFS, writes which extend the length of the file always complete synchronously. (More specifically, writes which extend the valid data length are forced synchronous.)

    We saw last time that merely calling Set­End­Of­File to pre-extend the file to the final size doesn't help, because that updates the file size but not the valid data length. To avoid synchronous behavior, you need to make sure your writes do not extend the valid data length. The suggestions provided in yesterday's article apply here as well.

  • The Old New Thing

    Appearing to succeed is a valid form of undefined behavior, but it's still undefined

    • 35 Comments

    A customer requested a clarification on the MSDN documentation for the HeapFree function.

    The MSDN documentation says that if the lpMem parameter is NULL, then the behavior is undefined. Is this true?

    As explicitly stated in MSDN, the behavior is undefined. Observe that the annotation on the lpMem parameter is __in, which means that the parameter must be a non-NULL value provided by the caller. (If NULL were permitted, the annotation would have been __in_opt.)

    Undefined behavior means that anything can happen. The program might crash immediately. It might crash five minutes later. It might send email to your boss saying that you screwed up and then read you Vogon poetry. Or maybe not.

    MSDN says don't do it, so don't do it.

    The customer explained why they were interested in knowing more information about undefined behavior:

    We were interested because there is a mismatch between the semantics of a function we are implementing (where NULL is valid and ignored) and the function HeapFree we are using as the implementation. It looks like Windows Vista returns TRUE if you pass NULL.

    If there is a mismatch in semantics between the function you are implementing and the function you are calling, it is your responsibility as the programmer to bridge the gap. The customer didn't say what function they were implementing, but I'm guessing it was something like operator delete. Since your function accepts NULL but HeapFree doesn't, it is your responsibility to filter out NULL parameters.

    void operator delete(void* ptr) throw ()
    {
     if (ptr != NULL)
      HeapFree(CustomHeap, 0, ptr);
    }
    

    This concept goes by the fancy name of the Adapter Pattern. The less fancy name is wrapper function.

    And the value returned by HeapFree on Windows Vista is irrelevant. Pretending to succeed is a valid form of undefined behavior, because anything qualifies as undefined behavior.

    (Of course, you can't assume that returning TRUE will always be the result of triggering undefined behavior. After all, if you could rely on it, then it wouldn't be undefined any more!)

  • The Old New Thing

    Why is the registry a hierarchical database instead of a relational one?

    • 33 Comments

    Commenter ton asks why the registry was defined as a hierarchical database instead of a relational database.

    Heck, it's not even a hierarchical database!

    The original registry was just a dictionary; i.e., a list of name/value pairs, accessed by name. In other words, it was a flat database.

    .txt txtfile
    txtfile Text Document
    txtfile\DefaultIcon notepad.exe,1
    txtfile\shell open
    txtfile\shell\open\command notepad %1

    If you turned your head sideways and treated the backslashes as node separators, you could sort of trick yourself into believing that this resulted in something vaguely approximating a hierarchical database, and a really lame one at that (since each node held only one piece of data).

    When you choose your data structures, you necessarily are guided by the intended use pattern and the engineering constraints. One important engineering constraint was that you have to minimize memory consumption. All of the registry code fit in 16KB of memory. (Recall that Windows 3.1 had to run on machines with only 1MB of memory.)

    Okay, what is the usage pattern of the registry? As originally designed, the registry was for recording information about file types. We have the file types themselves (txtfile), properties about those file types (DefaultIcon), verbs associated with those file types (open), and verb implementations (command or ddeexec). Some verb implementations are simple (command involves just a single string describing the command line); others are complex (ddeexec requires the execute string, the application, and the topic, plus an optional alternate execute string).

    • Given a file type and a property, retrieve the value of that property.
    • Given a file type and a verb, retrieve information about how to perform that verb.
    • The set of properties can be extended.
    • The set of property schemata can be extended.
    • The set of verbs can be extended.
    • The set of verb implementations can be extended.

    Since the properties and verb implementations can be extended, you can't come up with a single schema that covers everything. For example, over the years, new file type properties have been added such as ContentType, OpenWithList, and ShellNew. The first one is a simple string; the second is a list of strings, and the third is a complex key with multiple variants. Meanwhile, additional verb implementations have been added, such as DropTarget.

    Given the heterogeneity of the data the registry needs to keep track of, imposing some sort of uniform schema is doomed to failure.

    "But you can just update the schemata each time the registration is extended."

    That creates its own problems. For example, to support roaming user profiles, you need a single registry hive to work on multiple versions of the operating system. If version N+1 adds a new schema, but then the profile roams to a machine running version N, then that registry hive will be interpreted as corrupted since it contains data that matches no valid schema.

    "Well, then include the schemata with the roaming profile so that when the older operating system sees the hive, it also sees the updated schemata."

    This is trickier than it sounds, because when the profile roams to the newer operating system, you presumably want the schemata to be upgraded and written back into the user profile. It also assumes that the versioning of the schemata is strictly linear. (What if you roam a user profile from a Windows XP machine to a Server 2003 machine? Neither is a descendant of the other.)

    But what kills this proposal is that it makes it impossible for a program to "pre-register" properties for a future version of the operating system. Suppose a new schema is added in version N+1, like, say, the IDropTarget verb implementation. You write a program that you want to run on version N as well as on version N+1. If your installer tries to register the version N+1 information, it will fail since there is no schema for it. But that means that when the user upgrades to version N+1, they don't get the benefit of the version N+1 feature. In order to get the version N+1 feature to work, they have to reinstall the program so the installer says, "Oh, now I can register the version +1 information."

    "Well, then allow applications to install a new schema whenever they need to."

    In other words, make it a total free-for-all. In which case, why do you need a schema at all? Just leave it as an unregulated collection of name/value pairs governed by convention rather than rigid rules, as long as the code which writes the information and the code which reads it agree on the format of the information and where to look for it.

    Hey, wow, that's what the registry already is!

    And besides, if you told somebody, "Hi, yeah, in order to support looking up four pieces of information about file types, Windows 3.1 comes with a copy of SQL Server," they would think you were insane. That's like using a bazooka to kill a mosquito.

    What are you planning on doing with this relational database anyway? Are you thinking of doing an INNER JOIN on the registry? (Besides, the registry is already being abused enough already. Imagine if it were a SQL server: Everybody would store all their data in it!)

    ton explains one way applications could use this advanced functionality:

    An application would have a table or group of tables in relational style registry. A group of settings would be a row. A single setting would be a column. Is it starting to become clearer now how SQL like statements could now be used to constrain what gets deleted and added? How good is your understanding of SQL and DBMS?

    You know what most application authors would say? They would say "Are you mad? You're saying that I need to create a table with one column for each setting? And this table would have a single row (since I have only one application)? All this just so I can save my window position? Screw it, I'm going back to INI files." What'll happen in practice is that everybody will create a table with two columns, a string called name and a blob called value. Now we've come full circle: We have our flat database again.

    And how would they make sure the name of their table doesn't collide with the name of a table created by another application? Probably by encoding the company name and application name into the name of the table, according to some agreed-upon convention. Like say, the Settings table used by the LitSoft program written by LitWare would be called LitWare_LitSoft_Settings. So querying a value from this table would go something like

    SELECT value FROM PerUser.LitWare_LitSoft_Settings
        WHERE name = "WindowPosition"
    

    Hey, this looks an awful lot like

    Registry.CurrentUser.OpenSubKey(@"LitWare\LitSoft\Settings")
            .GetValue("WindowPosition");
    

    One of ton's arguments for using a relational database is that it permits enforcement of referential integrity. But I would argue that in the general case, you don't want strict enforcement of referential integrity. Suppose you uninstall a program. The uninstaller tries to delete the program registration, but that registration is being referenced by foreign keys in other tables. These references were not created by the application itself; perhaps the shell common dialog created them as part of its internal bookkeeping. If the registry blocked the deletion, then the uninstall would fail. "Cannot uninstall application because there's still a reference to it somewhere." And that reference might be in Bob's user profile, from that time Bob said, "Hey can I log onto your machine quickly? I need to look up something." Bob is unlikely to come back to your machine any time soon, so his user profile is just going to sit there holding a reference to that application you want to uninstall for an awfully long time. "Hi, Bob, can you come by my office? I need you to log on so I can uninstall an app."

    So let's assume it goes the other way: The registry automatically deletes orphaned foreign key rows. (And for hives that are not currently available, it just remembers that those foreign key rows should be deleted the next time they are loaded. Nevermind that that list of "foreign key rows that should be deleted the next time Bob logs on" is going to get pretty long.)

    Now suppose you're uninstalling a program not because you want to get rid of it, but because you're doing an uninstall/reinstall troubleshooting step. You uninstall the program, all the orphaned foreign key rows are automatically deleted, then you reinstall the program. Those orphaned foreign key rows are not undeleted; they remain deleted. Result: You lost some settings. This is the reason why you don't clean up per-user data when uninstalling programs.

    Enforcing referential integrity also means that you can't create anticipatory references. One example of this was given earlier, where you register something on version N even though the feature doesn't get activated until the user upgrades to version N+1. More generally, Program X may want to create a reference to Program Y at installation, even if program Y isn't installed yet. (For example, X is a Web browser and Y is a popular plug-in.) The Program Y features remain dormant, because the attempt by Program X to access Program Y will fail, but once the user installs Program Y, then the Program Y features are magically "turned on" in Program X.

    Consider, as an even more specific example, the "kill bit" database. There, the goal isn't to "turn on" features of Program Y but to turn them off. Imagine if referential integrity were enforced: You couldn't kill an ActiveX control until after it was installed!

  • The Old New Thing

    Some preliminary notes from //build/ 2011

    • 29 Comments

    Hey everybody, I'm down at the //build/ conference. (The extra slash is to keep the d from falling over.) I'm not speaking this year, but you can find me in the Apps area of the Expo room today until 3:30pm (except lunchtime), and Friday morning before lunch. I'll also be at Ask the Experts tonight.

    There are so many great sessions to choose from. The one I would attend if I weren't working that time slot would be Bring apps to life with Metro style animations in HTML5. Instead, I'll probably go to Building high performance Metro style apps using HTML5. Fortunately, the sessions are being recorded, so I can catch up later.

    (At PDC 2008, I learned of a class of conference attendee known as the overflow vulture. These people decide which sessions to attend by looking for the ones that are close to filling up, on the theory that "500 people can't be wrong." These people often fail to take into account the room size. A talk in a 200-person room which fills up is not necessarily more popular than a talk in a 500-person room which doesn't.)

    Here are my observations so far:

    • At the airport, I heard a page for "Katy Perry". Normally, my reaction would be, "Oh, that poor woman has the same name as the singer." But since I'm in Los Angeles, I have to give consideration to the possibility that it really is the singer.
    • On the ride from the airport to the hotel, I observed part of a police car chase, or at least two police cars rushing through traffic with lights on. Welcome to Los Angeles.
    • I decided to walk from my hotel to the convention center rather than taking the shuttle bus. Along the way, I spotted a bus coming down the street. The driver parked the bus in the right-hand lane (a lane which is normally used for driving), got off, and walked into the Carl's Jr. I took a peek inside, and he was at the counter ordering breakfast. I guess he figured the bus wouldn't fit in the drive-through. Welcome to Los Angeles.
    • I thought it would have been funny if Michael Anguilo had said, "And we're making these devices available to attendees for just $500. [beat] Just kidding. You're each getting one for free." Or pulled an Oprah. "Everybody, look under your chair! Ha-ha, made you look!"
    • You spend a good amount of time listening to the music that plays before the keynote begins. Imagine having that as your job. "I write music for conferences. My music is peppy, but not too much; hopeful, but with a little bit of attitude. And not so good you want to dance to it. And I have to write a dozen different versions, each one exactly fifteen seconds longer than the previous one. Oh, and it needs to segue into a higher-energy version when the speaker arrives on stage."
    • The City National Grove at Anaheim is not a city, not national, and not a grove. I do concede, however that it is in Anaheim.
    • If you look closely at the //build/ logo, you'll also notice that the second slash has partially decapitated the b. I tried reproducing the effect here, but my CSS-fu isn't powerful enough.
    • Bonus: The hotel I'm staying at is hosting a conference on hotel conference security. I wonder who provides security for that conference.
  • The Old New Thing

    Sending a window a WM_DESTROY message is like prank calling somebody pretending to be the police

    • 27 Comments

    A customer was trying to track down a memory leak in their program. Their leak tracking tool produced the stacks which allocated memory that was never freed, and they all seemed to come from uxtheme.dll, which is a DLL that comes with Windows. The customer naturally contacted Microsoft to report what appeared to be a memory leak in Windows.

    I was one of the people who investigated this case, and the customer was able to narrow down the scenario which was triggering the leak. Eventually, I tracked it down. First, here's the thread that caused the leak:

    DWORD CALLBACK ThreadProc(void *lpParameter)
    {
     ...
     // This CreateWindow caused uxtheme to allocate some memory
     HWND hwnd = CreateWindow(...);
     RememberWorkerWindow(hwnd);
     MSG msg;
     while (GetMessage(&msg, NULL, 0, 0)) {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
     }
     return 0;
    }
    

    This thread creates an invisible window whose job is to do something until it is destroyed, at which point the thread is no longer needed. The window procedure for the window looks like this:

    LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg,
                             WPARAM wParam, LPARAM lParam)
    {
     ...
     switch (uMsg) {
     ... business logic deleted ...
    
     case WM_DESTROY:
      ForgetWorkerWindow(hwnd);
      PostQuitMessage(0);
      break;
     ...
     }
     return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
    

    Sinec this is the main window on the thread, its destruction posts a quit message to signal the message loop to exit.

    There's nothing obviously wrong here that would cause uxtheme to leak memory. And yet it does. The memory is allocated when the window is created, and it's supposed to be freed when the window is destroyed. And the only time we exit the message loop is when the window is destroyed. So how is it that this thread manages to exit without destroying the window?

    The key is how the program signals this window that it should go away.

    void MakeWorkerGoAway()
    {
     // Find the worker window if it is registered
     HWND hwnd = GetWorkerWindow();
     // If we have one, destroy it
     if (hwnd) {
      // DestroyWindow doesn't work for windows that belong
      // to other threads.
      // DestroyWindow(hwnd);
      SendMessage(hwnd, WM_DESTROY, 0, 0);
     }
    }
    

    The authors of this code first tried destroying the window with DestroyWindow but ran into the problem that you cannot destroy a window that belongs to a different thread. "But aha, since the DestroyWindow function sends the WM_DESTROY message, we can just cut out the middle man and send the message directly."

    Well, yeah, you can do that, but that doesn't actually destroy the window. It just pretends to destroy the window by prank-calling the window procedure and saying "Ahem, um, yeah, this is the, um, window manager? (stifled laughter) And, um, like, we're just calling you to tell you, um, you're being destroyed. (giggle) So, um, you should like pack up your bags and (snort) sell all your furniture! (raucous laughter)"

    The window manager sends the WM_DESTROY message to a window as part of the window destruction process. If you send the message yourself, then you're making the window think that it's being destroyed, even though it isn't. (Because it's DestroyWindow that destroys windows.)

    The victim window procedure goes through its "Oh dear, I'm being destroyed, I guess I'd better clean up my stuff" logic, and in this case, it unregisters the worker window and posts a quit message to the message loop. The message loop picks up the WM_QUIT and exits the thread.

    And that's the memory leak: The thread exited before all its windows were destroyed. That worker window is still there, because it never got DestroyWindow'd. Since the window wasn't actually destroyed, the internal memory used to keep track of the window didn't get freed, and there you have your leak.

    "You just got punk'd!"

    The correct solution is for the MakeWorkerGoAway function to send a message to the worker window to tell it, "Hey, I'd like you to go away. Please call DestroyWindow on yourself." You can invent a private message for this, or you can take advantage of the fact that the default behavior of the WM_CLOSE message is to destroy the window. Since our window procedure doesn't override WM_CLOSE, the message will fall through to DefWindowProc which will convert the WM_CLOSE into a DestroyWindow.

    Now that you understand the difference between destroying a window and prank-calling a window telling it is being destroyed, you might be able to help Arno with his problem.

  • The Old New Thing

    Why doesn't the Disk Management snap-in incorporate S.M.A.R.T. data?

    • 27 Comments

    My article a while back on Why the Disk Management snap-in reports my volume as Healthy when the drive is dying gave the low-level explanation of why the Disk Management snap-in does not incorporate SMART information: because the Disk Management snap-in is concerned with volume partitioning. DWalker59 noted that the use of the word "Healthy" carries more meaning than the authors of the snap-in intended. The authors of the snap-in assumed that everybody knew what the Disk Management snap-in was for, and therefore everybody know that the word "Healthy" applied to the state of the file system.

    I never said that this was a good situation, and commenter Dog interpreted that since I didn't say whether this was a good situation or a bad situation, I must be saying that it's a good situation. Actually, since I didn't say whether this was a good situation or a bad situation, this means that I'm not saying whether this is a good situation or a bad situation. The article was posted in the Tips/Support category, which is about helping you cope with the frustrations of using Windows, not about passing value judgements on what is good or bad. The point was not to say what is good and what is bad, but merely to say what is.

    Dog thinks that the blog would be far more interesting if I shared my opinion on things. Actually, I try not to share my opinion on things, because the Web site isn't about opinionating on Windows; it's about practical programming on Windows. Practicality means that you have to set aside whether something is good or bad, because it's there and you have to deal with it regardless. If you want opinionated writing, check out Robert Scoble or Michael Kaplan. Dog also assumes that Microsoft's PR department has told me not to opinionate on things. In fact, they haven't told me anything one way or the other (yet, and I hope it stays that way).

    (I found it interesting that Dog claims that "the act of reporting on [something] gives the appearance of support unless otherwise stated." I wonder if people who cover armed conflicts have to add an explicit statement along the lines of "killing is bad" so Dog won't think they support people shooting at each other.)

    From a historical standpoint, the situation is a bit more understandable. After all, the Disk Management snap-in was written long before support for S.M.A.R.T. information showed up in Windows Vista. You can't fault the original authors of the Disk Management snap-in for not taking into account data which didn't exist yet.

    As for why the Disk Management snap-in didn't incorporate this information when it became available, this assumes that there were resources available to do the work. Disk Management is a very old snap-in that hasn't changed much since it was first written. My suspicion is that maintenance of the Disk Management snap-in is assigned to a group which has as its primary goal some other part of the system; they were just given Disk Management because it has to belong to somebody. Consequently, that group has very little incentive to make any changes to Disk Management at all, and certainly has very little incentive to add features to it.

  • The Old New Thing

    A common control for associating extensions is well overdue

    • 23 Comments

    Mark complained that a common control for associating extensions is *well* overdue.

    This is a recurring theme I see in the comments: People complaining that Windows lacks some critical feature that it in fact already has. (In the case, Windows had the feature for over two years at the time the question was asked. Maybe the SDK needs a ribbon? j/k)

    Windows Vista added the Default Programs UI as a control panel program, and it also has a programmable interface. You can use IApplication­Association­Registration to query and set default associations, and you can use IApplication­Association­Registration­UI to invoke the control panel itself on a set of associations associated with your program.

  • The Old New Thing

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

    • 23 Comments

    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."

Page 1 of 3 (26 items) 123