April, 2009

  • The Old New Thing

    When people ask to disable drag and drop, they often are trying to disable accidental drag and drop

    • 62 Comments

    We occasionally get customers who ask, "How do I disable drag and drop?" This is an odd request, so we ask the frequent follow-up question, "What are you really trying to do?" For many of these customers, the answer goes something like this:

    We've found that our employees often accidentally move or copy items around on the desktop and in Explorer windows because the act of pressing the mouse button causes the mouse itself to slide slightly across the table, resulting in a drag operation instead of a single click. We then have to spend a good amount of time searching for where those files ended up and trying to get their system back to its original state. We'd therefore like to disable drag and drop to avoid this problem.

    They aren't really trying to disable drag and drop. They are merely trying to disable accidental drag and drop.

    To avoid accidental drag and drop, adjust the drag sensitivity so that it takes a greater effort to trigger a drag and drop operation. By default, the mouse needs to travel four pixels with the button held down for a drag operation to be initiated. To make it harder to initiate an accidental drag operation, just crank this number higher.

    This particular customer might decide to crank the drag threshold to 50 or 100 pixels, so that the mouse has to move quite a significant distance before it is interpreted as a drag operation.

    By the way, the function for changing the drag threshold is SystemParametersInfo; check out the SPI_SETDRAGHEIGHT and SPI_SETDRAGWIDTH parameters.

    Pre-emptive correction: Do not modify the registry directly. Use the API. That's why it's there.

  • The Old New Thing

    Being able to call a function without using GetProcAddress is not a security vulnerability

    • 13 Comments

    Another genre in the sporadic category of dubious security vulnerability is people who find an unusual way of accomplishing something perfectly normal but declare it a security vulnerability because they found an unusual way of doing it.

    Security is important to all computers users, from families at home to employees of government agencies, and people who use Microsoft Windows are no exception. Trojans, backdoors, and spyware (collectively known as malware) have taken many forms, most recently those of so-called rootkits, which modify the operating system itself in order to prevent their detection. Firewalls are an important tool in the defense against malware.

    Through the following sequence of tricks, we can obtain the address of any function without using the GetProcAddress function. Once that address is obtained, the function can be called in the normal manner. First, obtain the module base address by calling the LoadLibrary function. The headers of the image are mapped into memory at the base address. From there, you can parse the headers of the module, look for the export directory, then manually parse the exported function name table until you find the function you want. In this way you can call functions like RegSetValue without detection.

    Well, sure, you can manually perform all the operations that the GetProcAddress would perform, but what's the point? Once you call RegSetValue all the normal registry security checks take place. You haven't bypassed anything. If you were so keen on calling functions surreptitiously, you could scan memory looking for the byte pattern that corresponds to the function you're looking for, or heck, just cut out the middle man and just take the code from the DLL you are trying to gain secret access to and copy it into your program!

    In other words, you just found a complicated way of doing something perfectly mundane. You can't make up for the absence of any actual vulnerability by piling on style points and cranking up the degree of difficulty.

  • The Old New Thing

    Let GDI do your RLE compression for you

    • 25 Comments

    This is another trick along the lines of using DIB sections to perform bulk color mapping. GDI will do it for you; you just have to know how to ask. Today's mission is to take a 4bpp bitmap and compress it in BI_RLE4 format. Now, sure, there are programs out there which already do this conversion, but the lesson is in the journey, not in the destination.

    The secret is the GetDIBits function. You give this function a bitmap and a bitmap format, and out come the bits in the format you requested; GDI will convert as necessary.

    Note: I'm going to take a risk and write "sloppy" code. This is code that is not production quality but is enough to get the point across, so put your nitpicking notepads away.

    void ConvertToRLE4(LPCTSTR pszSrc, LPCTSTR pszDst)
    {
        // error checking elided for expository purposes
        HBITMAP hbm = (HBITMAP)LoadImage(NULL, pszSrc, IMAGE_BITMAP,
                                         0, 0,
                                         LR_LOADFROMFILE |
                                         LR_CREATEDIBSECTION);
    
        DIBSECTION ds;
    
        // error checking elided for expository purposes
        GetObject(hbm, sizeof(ds), &ds);
    
        if (ds.dsBmih.biBitCount != 4) {
            // error - source bitmap is not 4bpp
        }
    
        struct BITMAPINFO16COLOR {
            BITMAPINFOHEADER bmih;
            RGBQUAD rgrgb[16];
        } bmi16;
        bmi16.bmih = ds.dsBmih;
    
        bmi16.bmih.biCompression = BI_RLE4;
    
        BYTE *rgbPixels = new BYTE[bmi16.bmih.biSizeImage];
        HDC hdc = GetDC(NULL);
        if (GetDIBits(hdc, hbm, 0, bmi16.bmih.biHeight, rgbPixels,
                      (LPBITMAPINFO)&bmi16, DIB_RGB_COLORS)
            != bmi16.bmih.biHeight) {
            // error - bitmap not compressible
        }
        ReleaseDC(NULL, hdc);
    
        BITMAPFILEHEADER bfh = { 0 };
        bfh.bfType = MAKEWORD('B', 'M');
        bfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(bmi16);
        bfh.bfSize = bfh.bfOffBits + bmi16.bmih.biSizeImage;
    
        // error checking elided for expository purposes
        HANDLE h = CreateFile(pszDst, GENERIC_WRITE, 0, NULL,
                              CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
        DWORD dwWritten;
        WriteFile(h, &bfh, sizeof(bfh), &dwWritten, NULL);
        WriteFile(h, &bmi16, sizeof(bmi16), &dwWritten, NULL);
        WriteFile(h, rgbPixels, bmi16.bmih.biSizeImage, &dwWritten, NULL);
        CloseHandle(h);
        delete[] rgbPixels;
    }
    

    Let's start from the top. After loading the bitmap and verifying that it is a 4bpp bitmap, we declare a BITMAPINFO16COLOR structure that is just a BITMAPINFO structure that holds 16 colors instead of just one. We copy the BITMAPINFOHEADER from the DIBSECTION to our structure for two reasons:

    1. We want to make some changes, and
    2. GDI expects the color table to come immediately after the BITMAPINFOHEADER.

    The second reason is the more important one. We can't use the BITMAPINFOHEADER that is part of the DIBSECTION structure because the DIBSECTION structure puts dsBitfields after the BITMAPINFOHEADER instead of a color table.

    After copying the BITMAPINFOHEADER, we make the key change: Changing the compression type to BI_RLE4. We allocate a pixel buffer of a size equal to the uncompressed size of the original bitmap and use GetDIBits to fill it with compressed data. Key points:

    • Before calling the GetDIBits function, we must set the biSizeImage member of the BITMAPINFO structure to the size of the buffer we passed as rgbPixels. In our case, this happened implicitly since we allocated rgbPixels based on the value of bmi16.bmih.biSizeImage.
    • On successful exit from the GetDIBits function, the GetDIBits function sets the biSizeImage member of the BITMAPINFO structure to the number of bytes actually written to the buffer.
    • On successful exit from the GetDIBits function, the GetDIBits function fills the color table if you're using a bitmap format that requires a color table. It's important that you allocate enough memory to hold the color table; if you forget, then you have a buffer overflow bug.

    Since the GetDIBits function returns the number of scan lines successfully read, if the value is different from the value we requested, then something went wrong. In our case, the most likely reason is that the bitmap is not compressible according to the BI_RLE4 algorithm.

    Now that we have the compressed bits, it's just grunt work to turn it into a BMP file. The BMP file format specifies that the file begins with a BITMAPFILEHEADER, followed by the BITMAPINFOHEADER, the color table, and the pixels. So we write them out in that order.

    Easy peasy.

  • The Old New Thing

    Clap and the filter graph claps with you

    • 19 Comments

    One of my colleagues was a fount of ideas, some of them crazy, some of them clever, and some of them both. I think this one counts as both.

    To render multimedia content with DirectShow, you build a so-called filter graph. A filter graph represents a series of transformations that are applied to data as it travels through the graph. For example, bytes from a video file may go through a splitter filter which separates the audio from the video data, then the two data streams each go through a respective audio and video decoder, which converts the compressed data into uncompressed sound or video data, and then to a render device which actually plays the sound samples out the speaker or puts the video image on the screen.

    One of the components of a filter graph is the reference clock. The purpose of the reference clock is to keep track of time, so that all the parts of the filter graph produce their output in a synchronized manner. If there weren't something keeping track of this, the audio would fall out of sync with the video.

    My colleague decided to create an unusual reference clock. Whereas most reference clocks are based off of real-world time, my colleague's reference clock started by taking input from the microphone. It did some analysis of the volume of the incoming sound stream and detected when there were loud impulses of sound. It then generated a stream of clock ticks based on the rate at which those impulses were detected.

    In other words, the clock was driven by clapping.

    I was lucky enough to be one of the first people to see this crazy reference timer in action. First, you started up the application and picked a video clip. The video clip just sat there. As you started clapping, the video clip started playing. If you clapped at about 80 beats per minute, the video clip played at its normal speed. If you clapped faster, the video clip ran faster. If you clapped slower, the video clip ran slower. If you stopped clapping, the video clip stopped.

    It was freaky cool.

    Totally useless, but freaky cool.

    Update: Commenter hexatron pointed out that the Metronome sample does the same thing as my colleague's crazy reference clock. I don't know if it's literally the same code, but it's functionally equivalent.

  • The Old New Thing

    There's nothing wrong with making bold treeview items

    • 8 Comments

    Commenter Frans Bouma asks,

    Why is the text of a treenode chopped off when you switch the font from normal to bold? It apparently is for backwards compatibility but I fail to see why this is necessary for backward compatibility...

    Actually, bold treeview items work just fine. Watch:

    Start with our scratch program and make these changes:

    BOOL
    OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
    {
      g_hwndChild = CreateWindow(
          WC_TREEVIEW, NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP |
          TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT,
          0, 0, 0, 0, hwnd, (HMENU)1, g_hinst, 0);
    
      TVINSERTSTRUCT tvis;
      tvis.hParent = TVI_ROOT;
      tvis.hInsertAfter = TVI_LAST;
      tvis.item.mask = TVIF_TEXT | TVIF_STATE;
      tvis.item.stateMask = TVIS_BOLD;
      tvis.item.state = 0;
      tvis.item.pszText = TEXT("First");
    
      tvis.hParent = TreeView_InsertItem(g_hwndChild, &tvis);
    
      tvis.item.pszText = TEXT("Second");
      tvis.item.state = TVIS_BOLD;
      TreeView_InsertItem(g_hwndChild, &tvis);
    
      tvis.item.pszText = TEXT("Third");
      tvis.item.state = 0;
      TreeView_InsertItem(g_hwndChild, &tvis);
    
      return TRUE;
    }
    

    I elided error checking for expository purposes.

    This code creates a tree view and populates it as follows:

    • First
      • Second
      • Third

    When you run this program, you can see that the text for all the items appears as expected; nothing is truncated.

    As for the backward compability remark, it's quite simple: Every change, no matter how seemingly insignificant, has compatibility consequences. The common controls are heavily used in third party programs, many of which make strange assumptions about how the controls work, relying on quirks of implementations in strange ways. For example, those who have read the first bonus chapter of my book will know that even something as seemingly harmless as fixing a flicker bug in the status bar resulted in a broken status bar in a program from a major software publisher. Every change is taken with great trepidation, and the bias is to preserve bug-for-bug compatibility.

    In this case, the issue was with the way the width of the treeview item is calculated. You can easily imagine programs which worked around the existing behavior by artificially padding the item with spaces to compensate for the miscalculation. If the treeview suddenly fixed the bug, these treeview items would now be undesirably large, possibly creating a horizontal scroll bar where there previously had been none, resulting in bugs like "After upgrading, the last item in my treeview is being covered by a scroll bar." We saw something like this before when we looked at the effects of the DS_SHELLFONT dialog style: Fixing the bug described in that article would result in property sheet pages coming out undesirably large (because their sizes were artificially inflated to compensate for the erroneous calculation).

    That doesn't mean the bug can't get fixed, however. Just as the DS_SHELLFONT flag is a signal to say that your property sheet page wants to use the new calculations, you can tell the tree view "Please give me the version of the treeview that fixes the font bug" by sending it the CCM_SETVERSION message and specifying that you want version 5. Similarly, you can opt into version 6 of the common controls by providing a manifest.

    Update: I slipped a subtlety into this article which it appears people didn't pick up on, so I'll make it explicit.

    The original question was about "switching the font from normal to bold", but there are multiple ways of doing this. My sample code used the recommended (declarative) method of setting the TVIS_BOLD flag. But if you click through the link, you'll see that the original commenter was using the procedural method of handling the NM_CUSTOMDRAW notification, selecting a new font (a boldface variation of the normal font), and returning CRF_NEWFONT. This is a technique I had illustrated previously with list views and tool tips.

    The compatibility behavior is for fonts customized via NM_CUSTOMDRAW. The declarative method was added specifically to address the bug in item size calculations when people change the font procedurally: Older versions of the treeview control asked for the custom font only when painting; it didn't ask for the custom font when measuring. Adding the font query to version 6 was actually quite risky, since the only way to ask the application for the procedurally-applied font is to actually go through the motions of drawing it, generating a dummy NM_CUSTOMDRAW notification with an empty paint rectangle. If an application painted outside the rectangle, you would have seen seen random painting on the screen.

  • The Old New Thing

    On the almost-feature of floppy insertion detection in Windows 95

    • 80 Comments

    Gosh, that floppy insertion article generated a lot of comments.

    First, to clarify the table: The table is trying to say that if you had a Style A floppy drive, then issuing the magic series of commands would return 1 if a floppy was present, or 0 if the floppy was not present. On the other hand, if you had a Style B floppy drive, then issuing the magic series of commands would return 0 if a floppy was present, or 1 if the floppy was not present. That's what I was trying to say in the table. The answer was consistent within a floppy style, but you first had to know what style you had.

    The downside of waiting until the user uses a floppy for the first time is that you have the it sometimes works and sometimes doesn't problem. Dad buys a new computer and a copy of the Happy Fun Ball game for his son. Dad turns on the computer, and then follows the instructions that come with the Happy Fun Ball package: "Just insert the floppy and follow the instructions on the screen." Dad inserts the floppy and... nothing happens because this is the first time Dad used the floppy, and he was expecting autodetection to work.

    Dad says, "Stupid program doesn't work."

    Dad complains to his co-workers at work. "He loves this game Happy Fun Ball when he visits his cousin's house, so I bought a computer and a copy of Happy Fun Ball, and it doesn't work!"

    Dad tries again that evening and this time it works, because in the meantime, he inserted a floppy to do something else (say, create an emergency boot disk). Bizarre. This just reinforces Dad's impression that computers are unpredictable and he will never understand how to use them.

    One could say that a feature that mysteriously turns itself on and off is worse than a feature that simply doesn't work. At least when it doesn't work, it predictably doesn't work. Human beings value predictability.

    You can't perform the test "the first time the drive is installed" because there is no way to tell that a drive has been installed. (Classic floppy drives are not Plug-and-Play.) Even worse, you can't tell that the user has replaced the Style A floppy drive with a Style B floppy drive. The user will see that floppy insertion detection stopped working and return the drive to the store. "This drive is broken. Floppy insertion detection doesn't work."

    It is also not the case that the ambiguity in the specification indicated a flaw in the specification. The C++ language specification, for example, leaves a lot of behaviors at the discretion of the implementation. This allows implementations to choose a behavior that works best for them. The no-spin-up floppy presence detection algorithm relied on several behaviors which were covered by the specification, and one that was not. It was not part of the original charter for the floppy specification committee to support spinless presence detection; it's just something that my colleague discovered over a decade after the specification was written.

    But the main reason for not bothering is that the benefit was minuscule compared to the cost. Nobody wants floppy drives to spin up as soon as a disk is inserted. That just makes them think they've been attacked by a computer virus. It'd all just be a lot of work for a feature nobody wants. And then you'd all be posting, "I can't believe Microsoft wasted all this effort on floppy insertion detection when they should have fixed insert favorite bug here."

  • The Old New Thing

    Windows 95 almost had floppy insertion detection but the training cost was prohibitive

    • 119 Comments

    One feature which Windows 95 almost had was floppy disk insertion detection. In other words, Windows 95 almost had the ability to detect when a floppy disk was present in the drive without spinning up the drive.

    The person responsible for Windows 95's 32-bit floppy driver studied the floppy drive hardware specification and spotted an opportunity. Working through the details of the specification revealed that, yes, if you issued just the right extremely clever sequence of commands, you could determine whether a disk was in the floppy drive without spinning up the drive. But there was a catch.

    The floppy drive hardware specification left one aspect of the drive behavior unspecified, and studying the schematics for various floppy drive units revealed that about half of the floppy drive vendors chose to implement it one way, and half the other way. Here's the matrix:

    Floppy Style Disk present Disk absent
    "A"10
    "B"01

    The results were completely reliable within each "style" of floppy drive, but the two styles produce exactly opposite results. If you knew which style of drive you had, then the results were meaningful, but the hard part was deciding which style of drive the user had.

    One idea was to have an additional "training" step built into Setup:

    • "Please insert a floppy disk into the drive and click Next."

    Once the disk was in, we could run the algorithm and see whether it returned 0 or 1; that would tell us which style of floppy drive we had.

    Unfortunately, this plan fell short for many reasons. First of all, a user who bought a computer with Windows 95 preinstalled would have bypassed the training session. You can't trust the OEM to have gone through the training, because OEMs change suppliers constantly depending on who gave them the best deal that week, and it's entirely likely that on the floor of the warehouse are a mix of both styles of floppy drive. And you certainly don't want to make the user go through this training session when they unpack their computer on Christmas morning. "Thank you for using Window 95. Before we begin, please insert a floppy disk in drive A:." You can't just try to figure out what type of drive the user has by comparing the clever technique against the boring "turn on the floppy drive light and make grinding noises" technique, at least not without displaying a warning to the user that you're about to do this—users tend to freak out when the floppy drive light turns on for no apparent reason. "Thank you for using Windows 95. Before we begin, I'm going to turn on your floppy drive light and make grinding noises. Press OK."

    Floppy disk insertion detection is not a sufficiently compelling feature that users will say, "I appreciate the benefit of going through this exercise."

    Sadly, floppy insertion detection had to be abandoned. It was one of those almost-features.

  • The Old New Thing

    Office redecoration: The classic Microsoft prank

    • 12 Comments

    Tim Sneath reminds us that it is a long-standing tradition Microsoft tradition to prank someone's office while they are out (here's a video of Larry Osterman's tales of prankdom).

    When I went to see a Thunderbirds hockey game a few years back, I avoided mentioning at the time that seeing the game served a secondary purpose. I attended the game with a couple of friends, one of whom had never seen a live hockey game before. It so happened that his fiancée wanted to prank his office, so our little hockey outing was a way to keep him occupied for a while, giving his fiancée the opportunity to sneak into his office and, along with several other friends, to give the room some of that holiday spirit.

    They went nuts with the gift wrap, wrapping not only his door, computer, keyboard, and mouse, but also his pens and coffee mug. They left figurines on his bookshelf, strung lights and garlands, and even set a half-scale papier-mâché reindeer on the floor of the office. But the best part was their calling card: They left behind a list of who's naughty and nice. In the "Nice" column, they put my friend's name. In the "Naughty" column were the names of all the pranksters.

Page 3 of 3 (28 items) 123