February, 2010

  • The Old New Thing

    The normal string manipulation functions stop on a null terminator, so be careful when manipulating double-null-terminated strings


    One of the many gotchas of working with double-null-terminated strings is accidentally using functions on them which were designed to operate on single-null-terminated strings. Now, you do need to use those single-null-terminated strings, but you also need to know when they won't do what you want.

    One of the responses to my psychic detection that somebody passed a single-null-terminated string to SHFileOperation is, "Oh, no, I double-null-terminated it. Look:

    sprintf(szDeletePath, "%s\0", file_to_delete);

    See, I put an extra \0 at the end."

    Well, yeah, you put an extra \0 at the end, but all that does is terminate the format string. The sprintf function accepts its format string as a traditional null-terminated string. When it sees the \0 you stuck into the string, it thinks it found the end of the string. It can't read your mind and say, "Oh, this null is not a terminator. It's an embedded null."

    A simple mistake, but something that needs to be called out. To be fair, most people recognize this mistake as soon as it's pointed out to them. You just have to remember to point it out to them.

  • The Old New Thing

    Don't forget to double-null-terminate those strings you pass to SHFileOperation


    About once every two months for the past six months (I stopped checking further back), somebody reports a problem with the SHFileOperation function. Often, they don't include very much information at all. They just say, "I call the function and it doesn't work." Here's an example:

    I'm hitting a problem with SHFileOperation when using it to frob files in the gonzo directory when the user's SID ends in an odd number.

        // Delete the file.
        // szDeletePath contains the full path to the file.
        shFileOp.hwnd = NULL;
        shFileOp.wFunc = FO_DELETE;
        shFileOp.pFrom = szDeletePath;
        shFileOp.pTo = NULL;
        shFileOp.fFlags = FOF_NO_UI;
        iRet = SHFileOperation( &shFileOp );

    The function returns file not found, but the file is definitely there.

    If you read the variable names carefully, you can see the problem.

    The pFrom and pTo members of the SHFILEOPSTRUCT structure are double-null-terminated strings. (There's even a callout box for this in the MSDN documentation.) But a variable named szDeletePath is probably a single-null-terminated string. (The name for a double-null-terminated string would be szzDeletePath.)

    My psychic powers tell me that szDeletePath is not double-null-terminated.

    So far, my psychic powers haven't failed.

    Now, you might say that the fact that people make this mistake so often is a sign that the function is flawed. And if the function were designed today, I would agree with you. But this function in its public form is over fifteen years old (and in its private form, is around 20 years old), and back in those days, programmers were assumed to have the time to understand the subtleties of what they were doing.

  • The Old New Thing

    Happy birthday, Windows 2000, and try not to get too hung over


    On this date ten years ago, Windows 2000 launched in San Francisco.

    One of my colleagues was working as a staff member at the Windows 2000 Conference and Expo in San Francisco, an event which accompanied the Windows 2000 launch event. Also working at the event was his boss's boss, and the two shared a hotel room.

    Their flight back to Redmond wasn't until late in the afternoon, so they decided to spend their last day in San Francisco being tourists in their host city. Hopping on a cable car, walking down the crooked street, seeing the sights in Chinatown and Fisherman's Wharf, all the standard tourist stuff. My flight was also not until late in the afternoon, so I decided to join them.

    The morning of our planned day as tourists, my colleague lay in his bed hung over from a night out drinking with local friends at a place called The Thirsty Bear.

    We gently woke him up. "Hey, Bob, it's N o'clock." (Where N = the time they agreed to get up.)

    grmgmergmrgmrgmmrgmrmgrmgm. Why don't you go down and have some breakfast.

    We went downstairs and had a leisurely breakfast in the hotel restaurant.

    "Hey, Bob, I'm back. Are you ready?"

    frgfrgfrgfggfrgrfrgr. Okay, I'll be down in the lobby in 30 minutes.

    He pulled himself out of bed and managed to make himself presentable enough for the day.

    When my colleague returned home and told the story to his wife, she was absolutely horrified. "You went on a business trip with your boss's boss, and then you went out drinking with your friends, and then the next morning, you were too hung over to wake up on time and made your boss's boss wait in the hotel lobby for you!?!?"

    Well, yeah.

    What she didn't know was that Bob and his boss's boss were actually good friends, so this wasn't actually a career-limiting move.

    Though I personally wouldn't recommend it.

    Bonus chatter: When asked where to get off for Chinatown, the cable car operator said, "You want to get off at the next stop, and then walk in that direction until you can't understand the signs any more."

  • The Old New Thing

    The fundamental rule of rocket science


    The rocket scientist who taught me that most rocket science isn't rocket science also taught me the fundamental rule of rocket science:

    Pointy end up.

  • The Old New Thing

    It rather involved being on the other side of this airtight hatchway: Dubious escalation


    Consider this type of dubious security vulnerability:

    There is a buffer overflow bug in kernel driver X. To exploit it, call this function with these strange parameters. The exploit works only if you are logged on as administrator, because non-administrators will get ERROR_ACCESS_DENIED.

    Yes, this is a bug, and yes it needs to be fixed, but it's not a security bug because of that only if you are logged on as an administrator clause.

    It's another variation of the dubious elevation to administrator vulnerability. After all, if you're already an administrator, then why bother attacking kernel mode in this complicated way? Just use your administrator powers to do whatever you want to do directly. You're an administrator; you already pwn the machine. All you're doing now is flexing your muscles showing how cleverly you can take down a machine that's already yours.

  • The Old New Thing

    Advocating the overthrow of the government of the United States by force or subversion


    It has been widely reported that South Carolina now requires "subversive groups" to register with the Secretary of State (and pay a $5 filing fee).

    Curiously, the list of organizations which must register include "an organization subject to foreign control." I wonder if this means that all consulates have to register, and that when any foreign dignitary visits South Carolina, they have to pay a $5 filing fee. (Not to mention all foreign-owned companies like Shell Oil.)

    Actually, it has been pointed out that a "subversive organization" includes one which advocates, teaches, or practices the propriety of controlling the government of the United States. I guess this means all political parties are subversive organizations. (Something most of us knew already.)

    And apparently, in your registration, you also have to include the bylaws or minutes of meetings from the last year. I wonder whether you have to resubmit the minutes each year. I'm sure somebody could keep a government bureaucrat busy for a long time by submitting hundreds of pages of "minutes".

    Anyway, this is a long and largely superfluous set-up for a different story. The mother of a colleague of mine came to visit from Canada. For some reason, the United States requires visitors to fill out a questionnaire asking them whether they are a drug dealer, whether they are a Nazi war criminal, and this question:

    Do you advocate the overthrow of the United States government by force or subversion?

    The sweet old lady studied the question for a while, then circled force.

    Bonus weirdness: On the form, it also says "Answering Yes will not necessarily exclude you from admission to the United States."

  • The Old New Thing

    Private classes, superclassing, and global subclassing


    In the suggestion box, A. Skrobov asks why it's impossible to superclass WC_DIALOG, but the example that follows is not actually superclassing.

    When I register my own class under this atom, and leave NULL in WNDCLASS.hInstance, Windows fills it in for me. Then I have two distinct classes registered: (0,WC_DIALOG) and (hMyInstance,WC_DIALOG), and DialogBox functions all use the first one.

    This question is a bit confused, since it says that the goal is to superclass the dialog class, but registering WC_DIALOG is not superclassing.

    First, I'll refer everyone to this MSDN article which describes the various ways of manipulating a window class: Subclassing, superclassing, and global subclassing.

    To superclass the dialog class, you retrieve information about the class by calling GetClassInfo and then register a new class based on the original class. But you don't need to go to all that effort to superclass the dialog class, because you already know what you need to know: The number of extra bytes is DLGWINDOWEXTRA, and the dialog procedure is DefDlgProc. You can just register your superclass directly, as we saw last time.

    Superclassing is done by registering your custom class under a different name, and using that class name if you want to obtain the new behavior. On the other hand, the question about talks about registering a class under the same name as the original (namely, WC_DIALOG). This isn't subclassing, nor is it superclassing, nor is it even global subclassing.

    Before continuing the discussion, I'll first address the issue of leaving NULL in WNDCLASS.hInstance: The value NULL for the instance handle is not legal when registering a class. Each class is associated with a module instance, and NULL is not a module instance. The window manager autocorrects this mistake by registering the class under the module corresponding to the executable. This is the same special-case behavior you get if you call GetModuleHandle(NULL), so it's not something completely out of the blue. It looks like A. Skrobov is being confused by the window manager's attempt to do what you mean. So much for being helpful.

    Okay, back to the original problem. Recall that the HINSTANCE member of the WNDCLASS structure is used to specify the class namespace. If you register a class against the handle of the current executable, then in order to create a window with that class, you need to create it with that same instance handle.

    Now we can put all the pieces together: Registering the class with WNDCLASS.hInstance = NULL is autocorrected to registering it with WNDCLASS.hInstance = GetModuleHandle(NULL), which places the class in the window class namespace of the current module. This is a separate class from the system dialog class, which is registered against GetModuleHandle(TEXT("USER32")). The two are registered against different modules, so they live independent lives. They just happen to have the same name.

    As we learned a few years ago, the instance handle you pass to the CreateWindow (or related) function is used to look up the window class, and as we also learned, the HINSTANCE you pass to the DialogBox (or related) function is used to look up the template as well as to create the frame window. The class name comes from the template, and if you didn't specify an explicit class in your template, then the dialog manager will use WC_DIALOG.

    You now have all the pieces necessary to understand what is going on. When you register the class against your executable's instance, you need to use that same instance when creating the dialog box so that your private class is found instead of the global one.

    To show how this all fits together, I've written a little program which registers a private class which happens to have the name WC_DIALOG and then uses it to create a dialog box.

    // scratch.rc
    #include <windows.h>
    // A pointless dialog box, for illustration only
    1 DIALOG 0,0,150,50
    CAPTION "Pointless"
    FONT 8, "MS Shell Dlg"
        DEFPUSHBUTTON "Cancel",IDCANCEL,50,18,50,14
    // scratch.cpp
    #include <windows.h>
    SuperDlgProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
      switch (uiMsg) {
      case WM_ERASEBKGND:
        return DefWindowProc(hwnd, uiMsg, wParam, lParam);
      return DefDlgProc(hwnd, uiMsg, wParam, lParam);
    DlgProc(HWND hwnd, UINT wm, WPARAM wParam, LPARAM lParam)
      switch (wm) {
      case WM_INITDIALOG: return TRUE;
      case WM_CLOSE: EndDialog(hwnd, 0); return TRUE;
      return FALSE;
    int CALLBACK
    WinMain(HINSTANCE hinst, HINSTANCE hinstPrev,
            LPSTR pszCmdLine, int nShowCmd)
      WNDCLASS wc;
      wc.style = 0;
      wc.lpfnWndProc = SuperDlgProc;
      wc.cbClsExtra = 0;
      wc.cbWndExtra = DLGWINDOWEXTRA;
      wc.hInstance = hinst;
      wc.hIcon = NULL;
      wc.hCursor = LoadCursor(NULL, IDC_ARROW);
      wc.hbrBackground = (HBRUSH)(COLOR_INFOBK + 1);
      wc.lpszMenuName = NULL;
      wc.lpszClassName = WC_DIALOG;
      if (RegisterClass(&wc))
        DialogBox(hinst, MAKEINTRESOURCE(1), NULL, DlgProc);
      return 0;

    The dialog template is itself entirely unremarkable; it looks like any old dialog template.

    Our superclass takes the regular dialog box class and gives it a custom background color, namely COLOR_INFOBK.

    The program registers this private version of WC_DIALOG and creates a dialog box based on it. Since we passed the same HINSTANCE in the WNDCLASS.hInstance as we did to DialogBox, the lookup of the WC_DIALOG class will find our private version and use it instead of the global version.

  • The Old New Thing

    A decidedly Canadian response to the shambles that was the running portion of the modern pentathlon in Beijing


    Living so close to the United States-Canada border means that there's a lot of friendly teasing of the many Canadians in our midst. It's a good thing Canadians as a whole seem to have a pretty good sense of humor about it. (Well, except the Quebecers. Those humorless grumps.)

    The final stage of the modern pentathlon is supposed to be a cross-country course, run through grassy fields, with occasional obstacles like a brook that needs to be hurdled. At the 2008 Olympics in Beijing, however, the course didn't so much resemble a cross-country run as it did waiting in line at Disneyland. Instead of traversing an outdoor course, the runners ran through a labyrinth constructed on the outer track of the athletic field, navigating dozens of switchbacks that converted the 1000-meter track into a test of how well you can make sharp turns. I remember being completely flabbergasted when I saw the course. It was a total disaster.

    Which is why I was amused at the response from Monica Pinette, a Canadian athlete who participated in the competition. Summing up her displeasure, she said, "I'm pretty angry. I'm going to write a nasty letter."

    That just struck me as a quintessentially Canadian way of expressing extreme dissatisfaction.

    Today marks the opening of the 2010 Winter Olympic Games in Vancouver. Best wishes to all the Canadians out there. Even if I still tease you every so often.

  • The Old New Thing

    How do I get information about the target of a symbolic link?


    Functions like GetFileAttributes and FindFirstFile, when asked to provide information about a symbolic link, returns information about the link itself and not the link destination. If you use the FindFirstFile function, you can tell that you have a symbolic link because the file attributes will have the FILE_ATTRIBUTES_REPARSE_POINT flag set, and the dwReserved0 member will contain the special value IO_REPARSE_TAG_SYMLINK.

    Okay, great, so now I know I have a symbolic link, but what if I want information about the link target? For example, I want to know the size of the link target, its last-modified time, and its name.

    To do this, you open the symbolic link. The I/O manager dereferences the symbolic link and gives you a handle to the link destination. You can then call functions like GetFileSize, GetFileInformationByHandleEx, or GetFinalPathNameByHandle to obtain information about the symbolic link target.

    Exercise: If the field is called dwReserved0, shouldn't it be off limits? Why isn't the field called dwReparsePointType?

  • The Old New Thing

    A sense of the term anonymous with which I had previously been unfamiliar


    I was filling out one of those online satisfaction surveys, and in the introduction, it reassured me that

    This survey is anonymous.

    The first question on the survey:

    Enter the support request (SR) number below.

    Yeah, because they'll never be able to trace the support request number back to me.

    (I suspect that the reason for this contradiction is that the organization that wanted to conduct the survey used a site that supports either anonymous surveys or tracked surveys, and they opted for the anonymous survey for whatever reason—maybe it's cheaper?—but they actually wanted a tracked survey, so they made the tracking number part of the survey itself.)

Page 2 of 4 (35 items) 1234