• The Old New Thing

    How can I detect that a user's SID has changed and recover their old data?

    • 9 Comments

    A customer maintained a database which recorded information per user. The information in the database is keyed by the user's SID. This works out great most of the time, but there are cases in which a user's SID can change.

    "Wait, I thought SIDs don't change."

    While it's true that SIDs don't change, it is also true that the SID associated with a user can change. Since SIDs encode the domain to which they belong, a user which moves from one domain to another within an organization, will need to be assigned a new SID.

    But wait, does that mean that the user lost access to all their stuff? After all, all their stuff was marked "Owned by X\UserName" but the user's SID is now Y\UserName.

    No, the user doesn't lose access to their stuff thanks to SID history, and if you move users around a lot, the SID history can get quite large.

    A token for a user contains not only their current identity but also all of their earlier identities. That is what permits Y\UserName to continue to access things that was marked "Owned by X\UserName": The token for Y\UserName includes an entry that says, "Oh, I used to be X\UserName."

    The customer's database can take advantage of the SID history to match up users with their former selves. Our customer was lucky in that their database recorded only users who had logged into the local machine, so that list is typically pretty small. The simplest solution for this particular customer is just to go through all the users in the database, and for each one, see if the current user has that database user in their SID history. And the easy way to do that is to make the security system do the work for you: To see if the current user has user X in their SID history, create a security descriptor that grants access only to user X, then call Access­Check to see if the current user can access it. If so, then that means that the current user was at one point in the past known as X.

    (If you have a large database where iterating over all users is impractical, you can ask for the current user's SID-History attribute and walk through the previous identities manually.)

  • The Old New Thing

    Taking advantage of the fact that the handle returned when you create a kernel synchronization object has full access regardless of the actual ACL

    • 6 Comments

    A customer wanted some help deciding what security attributes to place on an event object intended to be used by multiple security contexts.

    We have two processes, call them A and B, running in different security contexts. I have an event that process A creates and shares with process B. The only thing process A does with the event is signal it, and the only thing process B does with the event is wait on it. Our question is what ACLs you recommend for the event. For now, we're using O:BAD:(A;;GR;;;WD)(A;;GA;;;LS)(A;;GA;;;BA). (In case it matters, process A is usually running as a service with Local System privileges, though for testing purposes it may be running as local administrator. Process B runs as a service with Local Service privileges.)

    For those who don't speak SDDL, that weird line noise is shorthand for

    • Owner: Builtin Administrators
    • DACL:
      • Allow Generic Read to Everyone (aka World).
      • Allow Generic All to Local Service.
      • Allow Generic All to Builtin Administrators.

    Given the requirements, there is no need to grant Everyone any access at all, so we can delete the (A;;GR;;;WD) ACE.

    Since process B needs only to wait on the object, granting it Generic All access is far too broad. That would allow process B to signal the event or even change its ACL! To wait on an object, all you need is Synchronize, so the second ACE can be tightened to (A;;0x00100000;;;LS). (There is no shorthand for Synchronize, so we use its hex value.)

    The intention of the third ACE is to allow process A to signal the event, but for that all it needs is EVENT_MODIFY_STATE, not Generic All. But we can do better: We can delete the ACE entirely.

    "But Mister Wizard, if you delete the third ACE, then process A won't be able to signal the event!"

    Ah yes it can, thanks to a special feature of the Create­Event function:

    The handle returned by Create­Event has the EVENT_ALL_ACCESS access right.

    If you created the event, you get full access to the event regardless of what the ACLs on the event would normally say.

    Therefore, the event can be ACL'd with simply O:BAD:(A;;0x00100000;;;LS). When process A creates the event, it needs to hold on tight to that event handle, since that is the process's only way of setting the event! (If it loses the handle, it won't be able to get it back because the attempt to reacquire the handle will be blocked by the ACL.)

    Here's a quick program that demonstrates the behavior.

    #include <windows.h>
    #include <sddl.h>
    #include <tchar.h>
    
    // This is a demonstration, so there is no error checking
    // and we leak memory.
    
    int __cdecl _tmain(int, TCHAR **)
    {
     ULONG cb;
     SECURITY_ATTRIBUTES sa = { sizeof(sa), NULL, FALSE };
    
     // Create a security descriptor that grants access to no one.
     ConvertStringSecurityDescriptorToSecurityDescriptor(TEXT("D:"),
        SDDL_REVISION_1, &sa.lpSecurityDescriptor, &cb);
    
     // Create a handle with that security descriptor
     HANDLE h = CreateEvent(&sa, TRUE, TRUE,
                 TEXT("NobodyCanAccessMeButMe"));
    
     // Even though nobody has access to the object, we can still
     // signal it using the handle returned by CreateEvent.
     SetEvent(h); // succeeds
    
     // But nobody else can obtain the handle via the object name.
     HANDLE h2 = OpenEvent(EVENT_MODIFY_STATE, FALSE,
                           TEXT("NobodyCanAccessMeButMe")); // fails
    
     return 0;
    }
    

    The customer wrote back, "This worked perfectly. Thanks!"

    For bonus points, you can be even more specific and grant Synchronize access only to process B's service SID (NT SERVICE\Service­Name) rather than to all local services.

  • The Old New Thing

    Where does the Installed Updates control panel get the install date from?

    • 12 Comments

    A corporate customer wanted to know where the Installed Updates control panel gets the Installed On information from, because they found that the values were always set to the current date regardless of when the update was actually installed.

    The algorithm goes roughly like this:

    First, ask MSI what date the update was installed by calling Msi­Get­PatchInfo­Ex and asking for the INSTALL­PROPERTY_INSTALL­DATE.

    If that doesn't work, then go to the registry key under Software\Microsoft\Windows\Current­Version\Uninstall\Unique­Id and look for a value called (surprise) Install­Date. (Note that 32-bit updates on 64-bit machines will be redirected into a Wow­64­32­Node key.)

    If that still doesn't work, then it's time to guess: Windows XP uses the last-modified date of the directory that contains the uninstaller. Windows Vista and higher use the last-modified date of the Software\Microsoft\Windows\Current­Version\Uninstall\Unique­Id registry key. (Again, possibly with a Wow­64­32­Node stuck in there.)

    Bonus chatter: Interestingly, the customer didn't phrase the problem like that. The customer said, "The first time a user logs on each day, the install date changes to the current date. Subsequent logons the same day do not change the date. But the first logon the next day changes the date again. What's so special about the first logon of each day?" What's so special about the first logon of each day is that it's a new day! I suspect that the the date is updated on every logon. It's just that they don't notice the change because the date is the same.

  • The Old New Thing

    It's time we face reality, my friends: We're not rocket scientists

    • 13 Comments

    During the development of Windows 95, it was common for team members to pay visits to other teams to touch base and let them know what's been happening on the Windows 95 side of the project.

    It was during one of these informal visits that the one of my colleagues reported that he saw that one of the members of the partner team had a Gary Larson cartoon from The Far Side depicting a group of scientists studying a multi-stage rocket ship they just assembled, but the stages are connected all crooked. One of the scientists says, "It's time we face reality, my friends. … We're not exactly rocket scientists."

    The comic was "enhanced" a bit by the partner team. They added a sign on the wall of the laboratory that says Windows 95 Development, and the stages of the rocket are alternately labeled 16-bit and 32-bit. The graffiti were clearly poking fun at Windows 95's attempt to straddle the 16-bit and 32-bit worlds.

    The Windows 95 team knew how to take a joke, and for a time, they adopted "Hey, we're not rocket scientists" as a catch phrase.

    Following up on that article from 2010: Microsoft ran a free seminar on Windows 95 development for Macintosh programmers at the 1995 MacWorld Expo. Upon successful completion, participants received T-shirts with the slogan "Windows 95 sucks less."

  • The Old New Thing

    How do I read the "Double-click to open an item (single-click to select)" setting in Folder Options?

    • 21 Comments

    Today's Little Program reports whether the Double-click to open an item (single-click to select) option is selected in the Folder Options dialog. A customer wanted to know how to do this, presumably so that their program would respect the setting and adjust its user interface to match.

    #include <windows.h>
    #include <shlobj.h>
    #include <stdio.h>
    
    BOOL IsDoubleClickToOpenEnabled()
    {
     SHELLFLAGSTATE sfs;
     SHGetSettings(&sfs, SSF_DOUBLECLICKINWEBVIEW);
     return sfs.fDoubleClickInWebView;
    }
    
    int __cdecl main(int, char**)
    {
     printf("Double-click is %s\n",
            IsDoubleClickToOpenEnabled() ? "enabled" : "disabled");
     return 0;
    }
    

    The flag and member name is kind of weird. The ability to single-click to open an item was introduced as part of the Windows Desktop Update which came with Internet Explorer 4. This update made Explorer more Web-like, with single-click to activate and underlines appearing on hover. (This was back in the day when making everything more Web-like was the new hotness.)

    The internal name of this Explorer feature was WebView, and that name is reflected in the flag and structure.

  • The Old New Thing

    Dispelling the myths, rumors, and innuendo surrounding the QueryPerformanceCounter function

    • 28 Comments

    The Query­Performance­Counter function has been the subject of much rumor and innuendo. In response to all the confusion, the kernel folks put together a page which tries to settle the controversy once and for all. It discusses the history of QPC over the ages, the problems it had on earlier versions of Windows or older firmware (which is probably where a lot of the myths started), its interaction with hypervisors, offers guidance on how to use it and its alternatives, and includes a very nice Q&A.

  • The Old New Thing

    Why does the OpenThread function behave differently when the target thread belongs to another process?

    • 2 Comments

    A customer discovered strange behavior in the Open­Thread function and wondered whether it was expected.

    We use the Open­Thread function to obtain a thread handle with THREAD_QUE­RY_LIM­IT­ED_IN­FOR­MA­TION, passing in a valid thread ID. We later pass this handle to Get­Exit­Code­Thread to get the thread exit code. We have found that the function succeeds if the thread in question belongs to another process, provided the thread is still running (has not yet exited). On the other hand, if the thread belongs to our own process, then the call always succeeds regardless of whether the thread is running or not. Is this expected behavior? And can we assume that if Open­Thread fails with ERROR_INVALID_PARAMETER, then it means that the target thread has already exited?

    The Open­Thread function fails if you pass it an invalid thread ID. Thread IDs go invalid when the corresponding thread object is destroyed, and thread objects are destroyed when the thread exits and there are no open handles to the thread. Once a thread object is destroyed, its thread ID becomes invalid and may be re-used by a future thread.

    Whether the thread belongs to the same process or a different process does not play a rôle in this determination.

    My guess is that the reason the call succeeds if the target thread belongs to the same process, even if the target thread has already exited, is something much more mundane: They have a thread handle leak in their application.

    The customer never wrote back after receiving this explanation, so we'll never know whether my guess was correct.

    Bonus chatter: If you aren't sure whether you are passing a valid thread ID to Open­Thread, then you most likely already have a bug. Since thread IDs can be reused, if you haven't taken other steps to ensure that the thread you want still exists, then it's possible that the thread you want has already exited, the corresponding thread object has been destroyed, and the thread ID has been reused by some other thread. Your Open­Thread call will now succeed, but it will refer to some totally unrelated thread. Your program will most likely get very confused at this point.

  • The Old New Thing

    Some reasons not to do anything scary in your DllMain, part 3

    • 26 Comments

    In the same week, the shell team was asked to investigate two failures.

    The first one was a deadlock in Explorer. The participating threads look like this:

    • Thread 1 called Free­Library on a shell extension as part of normal Co­Free­Unused­Libraries processing. That DLL called Ole­Uninitialize from its Dll­Main function. This thread blocked because the COM lock was held by thread 2.
    • Thread 2 called Co­Create­Instance, and COM tried to load the DLL which handles the object, but the thread blocked because the loader lock was held by thread 1.

    The shell extension caused this problem because it ignored the rule against calling shell and COM functions from the Dll­Main entry point, as specifically called out in the Dll­Main documentation as examples of functions that should not be called.

    The authors of this shell extension may never have caught this problem in their internal testing (or if they did they didn't understand what it meant) because hitting this deadlock requires that a race window be hit: The shell extension DLL needs to be unloaded on one thread at the exact same moment another thread is inside the COM global lock trying to load another DLL.

    Meanwhile, another failure was traced back to a DLL calling Co­Initialize from their Dll­Main. This extra COM initialization count means that when the thread called Co­Uninitialize thinking that it was uninitializing COM, it actually merely decremented the count to 1. The code then proceeded to do things that are not allowed in a single-threaded apartment, believing that it had already torn down the apartment. But the secret Co­Initialize performed by the shell extension violated that assumption. Result: A thread that stopped responding to messages.

    The authors of both of these shell extensions seemed be calling Co­Uninitialize/Ole­Uninitialize in order to cancel out a Co­Initialize/Ole­Initialize which they performed in their DLL_PROCESS_ATTACH. This is fundamentally unsound not only because of the general rule of not calling COM functions inside Dll­Main but also because OLE initialization is a per-thread state, whereas the thread that gets the DLL_PROCESS_DETACH notification is not necessarily the one that receives the DLL_PROCESS_ATTACH notification.

    It so happens that in the second case, the DLL in question was a shell copy hook, and the hang was occuring not in Explorer but in an application which was using SH­File­Operation to delete some files. We could at least advise the application authors to pass the FOFX_NO­COPY­HOOKS flag to IFile­Operation::Set­Operation­Flags to prevent copy hooks from being loaded.

    Previous articles in this series: Part 1, Part 2.

  • The Old New Thing

    If you're looking for the code that displays a particular dialog box, the most directly way to find it is to look for the dialog box

    • 18 Comments

    Suppose you are working in a large or unfamiliar code base and you want to know where the code is that displays a particular dialog box or message box or something. Probably the most direct way of figuring this out is to look for the strings.

    Say there is a message box that asks for user confirmation. "Are you sure you want to frobulate the flux capacitor?" Search for that string in your source code. It will probably be in a resource file.

    resource.rc:IDS_CONFIRM­FROBULATE "Are you sure you want to frobulate the flux capacitor?"

    Great, now you have the string ID for that message. You can perform a second search for that ID.

    resource.h:#define IDS_CONFIRM­FROBULATE 1024
    resource.rc:IDS_CONFIRM­FROBULATE "Are you sure you want to frobulate the flux capacitor?"
    maintenance.cpp:   strPrompt.LoadString(IDS_CONFIRM­FROBULATE);

    If the thing you are searching for is a dialog box or menu item, then be aware that there may be an accelerator in the string, so a straight grep won't find it.

    No matches for "Enter the new name of the frobulator:"

    For a dialog box, you can tap the Alt key to make the accelerator show up, so you can search for the right string. For a menu, you invoke the menu via the keyboard. Or in either case, you can disable the Hide underlined letters for keyboard navigation setting.

    resource.rc:  LTEXT "Enter the ne&w name of the frobulator:",

    I tend to be lazy and instead of using any of those tricks to make the underlines show up, I just search for a shorter string and hope that the accelerator isn't in it.

    resource.rc:  LTEXT "Enter the ne&w name of the frobulator:",

    "But Raymond, hitting the Alt is just a quick tap on the keyboard. Surely you can't be that lazy!"

    Right. If the dialog box were right in front of me, then I could tap the Alt and be done. But usually, when I am investigating this sort of thing, it's because somebody has sent a screen shot and asks, "Where is the code that displays this?" Tapping Alt on a screen shot doesn't usually get you very far.

    Once you find the code that displays the dialog box or message box or whatever, you can then study the code to answer follow-up questions like "What are the conditions under which this dialog will appear?" or "Is there a setting to suppress this dialog?"

  • The Old New Thing

    My friend and his buddy invented the online shopping cart back in 1994

    • 25 Comments

    Back in 1994 or so, my friend helped out his buddy who worked as the IT department for a local Seattle company known as Sub Pop Records. Here's what their Web site looked like back then. Oh, and in case you were wondering, when I said that his buddy worked as the IT department, I mean that the IT department consisted of one guy, namely him. And this wasn't even his real job. His main job was as their payroll guy; he just did their IT because he happened to know a little bit about computers. (If you asked him, he'd say that his main job was as a band member in Earth.)

    The mission was to make it possible for fans to buy records online. Nobody else was doing this at the time, so they had to invent it all by themselves. The natural metaphor for them was the shopping cart. You wandered through the virtual record store putting records in your basket, and then you went to check out.

    The trick here is how to keep track of the user as they wander through your store. This was 1994. Cookies hadn't been invented yet, or at least if they had been invented, support for them was very erratic, and you couldn't assume that every visitor to your site is using a browser that supported them.

    The solution was to encode the shopping cart state in the URL by making every link on the page include the session ID in the URL. It was crude but it got the job done.

    The site went online, and soon they were taking orders from excited fans around the world. The company loved it, because they probably got to charge full price for the records (rather than losing a cut to the distributor). And my friend told me the deep dark secret of his system: "We do okay if you ask for standard shipping, but the real money is when somebody is impatient and insists on overnight shipping. Overcharging for shipping is where the real money is."

    (Note: Statements about business models for a primitive online shopping site from 1994 are not necessarily accurate today.)

Page 5 of 434 (4,332 items) «34567»