If you pass enough random numbers, eventually one of them will look valid


One customer traced a problem they were having to the way they were calling a function similar in spirit to this one:

HGLOBAL CopyClipboardData(UINT cf)
{
 HGLOBAL hglob = NULL;
 HANDLE h = GetClipboardData(cf);
 if (h) {
  void *p = GlobalLock(h);
  if (p) {
   SIZE_T size = GlobalSize(h);
   hglob = GlobalAlloc(GMEM_FIXED, size);
   if (hglob) {
    CopyMemory(hglob, p, size);
   }
   GlobalUnlock(h);
  }
 }
 return hglob;
}

This function takes a clipboard format and looks for it on the clipboard. If found, it returns a copy of the data.

Looks great, huh?

The problem is that the customer would sometimes call the function as CopyClipboardData(CF_BITMAP). The CF_BITMAP clipboard format stores its contents in the form of a HBITMAP, not an HGLOBAL.

The question from the customer:

This code was written in 2002, and we are wondering why it works "most" of the time and crashes sporadically. We expected that the call to GlobalLock would fail with an invalid parameter error, but sometimes it succeeds, and then when we call GlobalSize we crash. Why does it crash sometimes?

You already know the answer to this. GlobalAlloc works closely with GlobalLock so that GlobalLock can be fast. The bitmap handle returned by GetClipboardData usually fails the quick tests performed by GlobalLock to see whether the parameter is a fixed memory block, in which case the GlobalLock must go down its slow code path, and it is in this slow code path that the function recognizes that the the handle is downright invalid.

But once in a rare while, the bitmap handle happens to smell just enough like a fixed global handle that it passes the tests, and GlobalLock uses its highly optimized code path where it says, "Okay, this is one of those fixed global handles that GlobalAlloc created for me. I can just return the pointer back." Result: The call to GlobalLock succeeds (garbage in, garbage out), and then you crash in the GlobalSize function where it tries to use the HBITMAP as if it were a HGLOBAL and access some of the memory block metadata, which isn't there since the handle isn't valid after all.

The bitmap handle is basically a random number from the global heap's point of view, since it's just some number that some other component made up. It's not a global handle. If you generate enough random numbers, eventually one of them will look like a valid parameter.

Comments (12)
  1. Anonymous says:

    > We expected that the call to GlobalLock would fail with an invalid parameter error,

    So excuse me, if they EXPECTED it to [always] fail, why bothering writing the then-branch??? *mortally confused*

    [Remember, you have to read sentences in context. “While debugging, we were surprised that the call to GlobalLock was succeeding instead of failing with an invalid parameter error.” -Raymond]
  2. Anonymous says:

    Was there some special bitmap size and format involved which made HBITMAP look like HGLOBAL or it was truly random?

  3. Anonymous says:

    what should ordinary man do to get support like this from you Raymond (be a friend with Bill Gates maybe)? :)

  4. Anonymous says:

    OMG!

    What a scary sentence: "… usually fails the quick tests performed by GlobalLock …"!?

    I have code that looks like this:

    void  somefunction (HANDLE theHandle)

    {

      UINT  retVal = GlobalFlags (theHandle);

      if (retVal == GMEM_INVALID_HANDLE)

         return;

      …

    }

    Is this something I can count on? Or does it fall under "quick, but unreliable" tests?

  5. Anonymous says:

    IgorD: I might not be Raymond, but I’m certainly highly suspicious of your code.  Even without the problem that Raymond is talking about here (e.g. if all the handles you’re testing for validity originally came from the global heap), I don’t think your code can possibly work reliably.  Consider what happens, for example, when a handle that you were previously using is reallocated to some other purpose.

    I’d definitely suggest restructuring so an invalid handle can never get this far.

  6. Anonymous says:

    In this article Raymond Chen, the creator of Windows NT, talks about…

  7. Anonymous says:

    Jules, maybe you’re right but I cant follow.

    Most of my functions perform checks at the beginning – if pointer is not NULL or if index is within a range. Here I try to check if passed handle was really a global handle, one created with GlobalAlloc() so I can pass it few lines later to GlobalLock().

    And that was my question – is GlobalFlags() reliable to recognize such handles and will it ALWAYS return GMEM_INVALID_HANDLE for handles that were not created with GlobalAlloc()?

  8. Anonymous says:

    IgorD: The MSDN documentation is fairly explicit on what it does and what parameters it assumes: "The GlobalFlags function returns information about the specified global memory object." And then in the Parameters paragraph: "Handle to the global memory object. This handle is returned by either the GlobalAlloc or GlobalReAlloc function."

    Besides being provided only for compatibility with 16-bit versions of Windows, GlobalFlags() is most definately NOT meant to be used to verify the integrity of random input. If you don’t know what you’re passing into your function it is essentially random data.

    .t

  9. Anonymous says:

    IgorD said: "Here I try to check if passed handle was really a global handle, one created with GlobalAlloc() so I can pass it few lines later to GlobalLock()."

    Let me restate what others (including Raymond) have already said.

    You cannot check if passed handle is really a global handle. You either know it is (because you called the function with a handle you got from GlobalAlloc()) or you don’t. There is no way to be sure, because handle is after all just a number.

  10. Anonymous says:

    OK, I see the message!

    In my case, I receive a HGLOBAL handle either from GlobalAlloc() or GetClipboardData() and if it’s OK then it is OK. If it turns out to be a dud, then there’s nothing I could do about it.

    I’ll comment out GlobalFlags() call (and leave just the NULL test) and see what happens. I suppose, nothing remarkable should happen anyway ;)

    Thanks everybody for your patience!

  11. Anonymous says:

    IgorD said : "In my case, I receive a HGLOBAL handle either from GlobalAlloc() or GetClipboardData()"

    No you don’t.

    You receive HGLOBAL from GlobalAlloc(), but you receive random number from GetClipboardData() (since depending on the format asked it can return HBITMAP or something else). That was the point of Raymond’s article.

    Just blindly attempting GlobalLock() won’t save you either, because it may succeed even if you passed a value which is not HGLOBAL.

  12. Anonymous says:

    Maybe I wasn’t clear in my forst post, but I do not have one generic  function responsible for all kinds of clipboard types, so I do not have anything like Raymond’s example.

    In other words, since I call GetClipboardData() only with my own types recieved from RegisterClipboardFormat() and all I send to SetClipboardData() are global handles I think I’m safe.

Comments are closed.