• The Old New Thing

    Tinkering with the look

    • 6 Comments
    I've been tinkering with the look of the comments section, based on initial feedback. Took me a while but I think I found something that works pretty well.
  • The Old New Thing

    The unsafe device removal dialog

    • 27 Comments

    In a comment, somebody asked what the deal was with the unsafe device removal dialog in Windows 2000 and why it's gone in Windows XP.

    I wasn't involved with that dialog, but here's what I remember: The device was indeed removed unsafely. If it was a USB storage device, for example, there may have been unflushed I/O buffers. If it were a printer, there may have been an active print job. The USB stack doesn't know for sure (those are concepts at a higher layer that the stack doesn't know about) - all it knows is that it had an active channel with the device and now the device is gone, so it gets upset and yells at you.

    In Windows XP, it still gets upset but it now keeps its mouth shut. You're now on your honor not to rip out your USB drive before waiting two seconds for all I/O to flush, not to unplug your printer while a job is printing, etc. If you do, then your drive gets corrupted / print job is lost / etc. and you're on your own.
  • The Old New Thing

    The migration continues

    • 4 Comments
    Hey folks, this blog is moving to a new home. It'll take me a while to set up shop there, though, so please be patient. Some time after the new year, the content will likely migrate to the new location.
  • The Old New Thing

    Welcome to the New Old New Thing

    • 4 Comments
    Hey there, everybody. It's going to take me a while to settle in, so please be patient. It seems I always celebrate a new blog by designing some insane multi-part series on some obscure aspect of Win32, so I think I'll welcome this site with a series of articles that demonstrate various things you can do with the shell namespace. I haven't planned it out yet, though, so I don't know how many parts it'll be or how much I'll manage to cover before I get bored.
  • The Old New Thing

    Tote Hose in Weilburg

    • 32 Comments

    Wenn Teenies shoppen gehen: Flugzeuge im Warenkorb. This article caught my eye because it opens with a German slang phrase I learned just a few weeks ago: "Tote Hose" which means roughly "Absolutely nothing doing". Here's an English version of the article for those whose German isn't quite up to snuff.

    A question for Germans: Since when did "shoppen" become a German word? What happened to "einkaufen"? Do people prefer the English words because they sound "cooler"?
  • The Old New Thing

    Why are structure sizes checked strictly?

    • 40 Comments

    You may have noticed that Windows as a general rule checks structure sizes strictly. For example, consider the MENUITEMINFO structure:

    typedef struct tagMENUITEMINFO {
      UINT    cbSize; 
      UINT    fMask; 
      UINT    fType; 
      UINT    fState; 
      UINT    wID; 
      HMENU   hSubMenu; 
      HBITMAP hbmpChecked; 
      HBITMAP hbmpUnchecked; 
      ULONG_PTR dwItemData; 
      LPTSTR  dwTypeData; 
      UINT    cch; 
    #if(WINVER >= 0x0500)
      HBITMAP hbmpItem; // available only on Windows 2000 and higher
    #endif
    } MENUITEMINFO, *LPMENUITEMINFO; 
    

    Notice that the size of this structure changes depending on whether WINVER >= 0x0500 (i.e., whether you are targetting Windows 2000 or higher). If you take the Windows 2000 version of this structure and pass it to Windows NT 4, the call will fail since the sizes don't match.

    "But the old version of the operating system should accept any size that is greater than or equal to the size it expects. A larger value means that the structure came from a newer version of the program, and it should just ignore the parts it doesn't understand."

    We tried that. It didn't work.

    Consider the following imaginary sized structure and a function that consumes it. This will be used as the guinea pig for the discussion to follow:

    typedef struct tagIMAGINARY {
      UINT cbSize;
      BOOL fDance;
      BOOL fSing;
    #if IMAGINARY_VERSION >= 2
      // v2 added new features
      IServiceProvider *psp; // where to get more info
    #endif
    } IMAGINARY;
    
    // perform the actions you specify
    STDAPI DoImaginaryThing(const IMAGINARY *pimg);
    
    // query what things are currently happening
    STDAPI GetImaginaryThing(IMAGINARY *pimg);
    

    First, we found lots of programs which simply forgot to initialize the cbSize member altogether.

    IMAGINARY img;
    img.fDance = TRUE;
    img.fSing = FALSE;
    DoImaginaryThing(&img);
    

    So they got stack garbage as their size. The stack garbage happened to be a large number, so it passed the "greater than or equal to the expected cbSize" test and the code worked. Then the next version of the header file expanded the structure, using the cbSize to detect whether the caller is using the old or new style. Now, the stack garbage is still greater than or equal to the new cbSize, so version 2 of DoImaginaryThing says, "Oh cool, this is somebody who wants to provide additional information via the IServiceProvider field." Except of course that it's stack garbage, so calling the IServiceProvider::QueryService method crashes.

    Now consider this related scenario:

    IMAGINARY img;
    GetImaginaryThing(&img);
    

    The next version of the header file expanded the structure, and the stack garbage happened to be a large number, so it passed the "greater than or equal to the expected cbSize" test, so it returned not just the fDance and fSing flags, but also returned an psp. Oops, but the caller was compiled with v1, so its structure doesn't have a psp member. The psp gets written past the end of the structure, corrupting whatever came after it in memory. Ah, so now we have one of those dreaded buffer overflow bugs.

    Even if you were lucky and the memory that came afterwards was safe to corrupt, you still have a bug: By the rules of COM reference counts, when a function returns an interface pointer, it is the caller's responsibility to release the pointer when no longer needed. But the v1 caller doesn't know about this psp member, so it certainly doesn't know that it needs to be psp->Release()d. So now, in addition to memory corruption (as if that wasn't bad enough), you also have a memory leak.

    Wait, I'm not done yet. Now let's see what happens when a program written in the future runs on an older system.

    Suppose somebody is writing their program intending it to be run on v2. They set the cbSize to the larger v2 structure size and set the psp member to a service provider that performs security checks before allowing any singing or dancing to take place. (E.g., makes sure everybody paid the entrance fee.) Now somebody takes this program and runs it on v1. The new v2 structure size passes the "greater than or equal to the v1 structure size" test, so v1 will accept the structure and Do the ImaginaryThing. Except that v1 didn't support the psp field, so your service provider never gets called and your security module is bypassed. Now everybody is coming into your club without paying.

    Now, you might say, "Well those are just buggy programs. They deserve to lose." If you stand by that logic, then prepare to take the heat when you read magazine articles like "Microsoft intentionally designed <Product X> to be incompatible with <software from a major competitor>. Where is the Justice Department when you need them?"
  • The Old New Thing

    Voyage to Our Hollow Earth

    • 4 Comments

    Slashdot's story about the amateur adventurer who is now stranded at McMurdo Base (because he underestimated his fuel requirements for a trip over the South Pole) reminded me of a controversy abrew at the other end of the planet: The hole at the North Pole. To resolve this matter, Steve Currey is mounting an expedition to the hole at the top of the earth. Reserve your seat now!  For only $20,000, you too can travel to the hole in the North Pole and glory at the tropical landscape that awaits you. Maybe.

  • The Old New Thing

    How do I pass a lot of data to a process when it starts up?

    • 18 Comments

    As we discussed yesterday, if you need to pass more than 32767 characters of information to a child process, you'll have to use something other than the command line.

    One method is to wait for the child process to go input idle, then FindWindow for some agreed-upon window and send it a WM_COPYDATA message. This method has a few problems:

    • You have to come up with some way of knowing that the child process has created its window so you can start looking for it. (WaitForInputIdle is helpful here.)
    • You have to make sure the window you found belongs to the child process and isn't just some other window which happens to have the same name by coincidence. Or, perhaps, not by coincidence: If there is more than once instance of the child process running, you will need to make sure you're talking to the right one. (GetWindowThreadProcessId is helpful here.)
    • You have to hope that nobody else manages to find the window and send it the WM_COPYDATA before you do. (If they do, then they have effectively taken over your child process.)
    • The child process needs to be alert for the possibility of a rogue process sending bogus WM_COPYDATA messages in an attempt to confuse it.

    The method I prefer is to use anonymous shared memory. The idea is to create a shared memory block and fill it with goodies. Mark the handle as inheritable, then spawn the child process, passing the numeric value of the handle on the command line. The child process parses the handle out of its command line and maps the shared memory block to see what's in it.

    Remarks about this method:

    • You need to be careful to validate the handle, in case somebody tries to be sneaky and pass you something bogus on your command line.
    • In order to mess with your command line, a rogue process needs PROCESS_VM_WRITE permission, and in order to mess with your handle table, it needs PROCESS_DUP_HANDLE permission. These are securable access masks, so proper choice of ACLs will protect you. (And the default ACLs are usually what you want anyway.)
    • There are no names that can be squatted or values that can be spoofed (assuming you've protected the process against PROCESS_VM_WRITE and PROCESS_DUP_HANDLE).
    • Since you're using a shared memory block, nothing actually is copied between the two processes; it is just remapped. This is more efficient for large blocks of data.

    Here's a sample program to illustrate the shared memory technique.

    #include <windows.h>
    #include <shlwapi.h>
    #include <strsafe.h>
    
    struct STARTUPPARAMS {
        int iMagic;     // just one thing
    };
    

    In principle, the STARTUPPARAMS can be arbitrarily complicated, but for illustrative purposes, I'm just going to pass a single integer.

    STARTUPPARAMS *CreateStartupParams(HANDLE *phMapping)
    {
        STARTUPPARAMS *psp = NULL;
        SECURITY_ATTRIBUTES sa;
        sa.nLength = sizeof(sa);
        sa.lpSecurityDescriptor = NULL;
        sa.bInheritHandle = TRUE;
        HANDLE hMapping = CreateFileMapping(INVALID_HANDLE_VALUE,
                    &sa, PAGE_READWRITE, 0,
                    sizeof(STARTUPPARAMS), NULL);
        if (hMapping) {
            psp = (STARTUPPARAMS *)
                        MapViewOfFile(hMapping, FILE_MAP_WRITE,
                            0, 0, 0);
            if (!psp) {
                CloseHandle(hMapping);
                hMapping = NULL;
            }
        }
    
        *phMapping = hMapping;
        return psp;
    }
    

    The CreateStartupParams function creates a STARTUPPARAMS structure in an inheritable shared memory block. First, we fill out a SECURITY_ATTRIBUTES structure so we can mark the handle as inheritable by child processes. Setting the lpSecurityDescriptor to NULL means that we will use the default security descriptor, which is fine for us. We then create a shared memory object of the appropriate size, map it into memory, and return both the handle and the mapped address.

    STARTUPPARAMS *GetStartupParams(LPSTR pszCmdLine, HANDLE *phMapping)
    {
        STARTUPPARAMS *psp = NULL;
        LONGLONG llHandle;
        if (StrToInt64ExA(pszCmdLine, STIF_DEFAULT, &llHandle)) {
            *phMapping = (HANDLE)(INT_PTR)llHandle;
            psp = (STARTUPPARAMS *)
                    MapViewOfFile(*phMapping, FILE_MAP_READ, 0, 0, 0);
            if (psp) {
                //  Now that we've mapped it, do some validation
                MEMORY_BASIC_INFORMATION mbi;
                if (VirtualQuery(psp, &mbi, sizeof(mbi)) >= sizeof(mbi) &&
                    mbi.State == MEM_COMMIT &&
                    mbi.BaseAddress == psp &&
                    mbi.RegionSize >= sizeof(STARTUPPARAMS)) {
                    // Success!
                } else {
                    // Memory block was invalid - toss it
                    UnmapViewOfFile(psp);
                    psp = NULL;
                }
            }
        }
        return psp;
    }
    

    The GetStartupParams function is the counterpart to CreateStartupParams. It parses a handle from the command line and attempts to map a view. If the handle isn't a file mapping handle, the call to MapViewOfFile will fail, so we get that part of the parameter validation for free. We use VirtualQuery to validate the size of the memory block. (We can't use a strict equality test since the value we get back will be rounded up to the nearest page multiple.)

    void FreeStartupParams(STARTUPPARAMS *psp, HANDLE hMapping)
    {
        UnmapViewOfFile(psp);
        CloseHandle(hMapping);
    }
    

    After we're done with the startup parameters (either on the creation side or the consumption side), we need to free them to avoid a memory leak. That's what FreeStartupParams is for.

    void PassNumberViaSharedMemory(HANDLE hMapping)
    {
        TCHAR szModule[MAX_PATH];
        TCHAR szCommand[MAX_PATH * 2];
        DWORD cch = GetModuleFileName(NULL, szModule, MAX_PATH);
        if (cch > 0 && cch < MAX_PATH &&
            SUCCEEDED(StringCchPrintf(szCommand, MAX_PATH * 2,
                      TEXT("\"%s\" %I64d"), szModule,
                      (INT64)(INT_PTR)hMapping))) {
            STARTUPINFO si = { sizeof(si) };
            PROCESS_INFORMATION pi;
            if (CreateProcess(szModule, szCommand, NULL, NULL,
                              TRUE,
                              0, NULL, NULL, &si, &pi)) {
                CloseHandle(pi.hProcess);
                CloseHandle(pi.hThread);
            }
        }
    }
    

    Most of the work here is just building the command line. We run ourselves (using the GetModuleFileName(NULL) trick), passing the numerical value of the handle on the command line, and passing TRUE to CreateProcess to indicate that we want inheritable handles to be inherited. Note the extra quotation marks in case our program's name contains a space.

    int CALLBACK
    WinMain(HINSTANCE hinst, HINSTANCE hinstPrev,
            LPSTR pszCmdLine, int nShowCmd)
    {
        HANDLE hMapping;
        STARTUPPARAMS *psp;
        if (pszCmdLine[0]) {
            psp = GetStartupParams(pszCmdLine, &hMapping);
            if (psp) {
                TCHAR sz[64];
                StringCchPrintf(sz, 64, TEXT("%d"), psp->iMagic);
                MessageBox(NULL, sz, TEXT("The Value"), MB_OK);
                FreeStartupParams(psp, hMapping);
            }
        } else {
            psp = CreateStartupParams(&hMapping);
            if (psp) {
                psp->iMagic = 42;
                PassNumberViaSharedMemory(hMapping);
                FreeStartupParams(psp, hMapping);
            }
        }
        return 0;
    }
    

    At last we put it all together. If we have a command line parameter, then this means that we are the child process, so we convert it into a STARTUPPARAMS and display the number that was passed. If we don't have a command line parameter, then this means that we are the parent process, so we create a STARTUPPARAMS, stuff the magic number into it (42, of course), and pass it to the child process.

    So there you have it: Passing a "large" (well, okay, small in this example, but it could have been megabytes if you wanted) amount of data securely to a child process.

  • The Old New Thing

    What is the command line length limit?

    • 16 Comments

    It depends on whom you ask.

    The maximum command line length for the CreateProcess function is 32767 characters. This limitation comes from the UNICODE_STRING structure.

    CreateProcess is the core function for creating processes, so if you are talking directly to Win32, then that's the only limit you have to worry about. But if you are reaching CreateProcess by some other means, then the path you travel through may have other limits.

    If you are using the CMD.EXE command processor, then you are also subject to the 8192 character command line length limit imposed by CMD.EXE.

    If you are using the ShellExecute/Ex function, then you become subject to the INTERNET_MAX_URL_LENGTH (around 2048) command line length limit imposed by the ShellExecute/Ex functions. (If you are running on Windows 95, then the limit is only MAX_PATH.)

    While I'm here, I may as well mention another limit: The maximum size of your environment is 32767 characters. The size of the environment includes the all the variable names plus all the values.

    Okay, but what if you want to pass more than 32767 characters of information to a process? You'll have to find something other than the command line. We'll discuss some options tomorrow.

  • The Old New Thing

    Scoble's rant on UI defaults

    • 33 Comments

    Robert Scoble posted an entry in his Longhorn blog on the subject of what the UI defaults should be. It sure has stirred up a lot of controvery. I may pick at the remarks over the upcoming days, but for now I posted responses to two of the comments he kicked up.

    We recently did a survey of users of all abilities. Beginners, intermediates, experts: The number one complaint all of them had about the user interface - 30% of all respondents mentioned this, evenly spread across all categories - was "Too many icons on the desktop." So it's not just beginners. Experts also don't like the clutter. (Yes, I was surprised by the results, too.)
Page 372 of 388 (3,875 items) «370371372373374»