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

Date:October 26, 2007 / year-entry #388
Tags:code
Orig Link:https://blogs.msdn.microsoft.com/oldnewthing/20071026-00/?p=24683
Comments:    12
Summary: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) {...

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. Wesha 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. Igor says:

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

  3. bw says:

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

  4. IgorD 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. Jules 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. John says:

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

  7. IgorD 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. Tim 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. Igor 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. IgorD 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. Igor 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. IgorD 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.


*DISCLAIMER: I DO NOT OWN THIS CONTENT. If you are the owner and would like it removed, please contact me. The content herein is an archived reproduction of entries from Raymond Chen's "Old New Thing" Blog (most recent link is here). It may have slight formatting modifications for consistency and to improve readability.

WHY DID I DUPLICATE THIS CONTENT HERE? Let me first say this site has never had anything to sell and has never shown ads of any kind. I have nothing monetarily to gain by duplicating content here. Because I had made my own local copy of this content throughout the years, for ease of using tools like grep, I decided to put it online after I discovered some of the original content previously and publicly available, had disappeared approximately early to mid 2019. At the same time, I present the content in an easily accessible theme-agnostic way.

The information provided by Raymond's blog is, for all practical purposes, more authoritative on Windows Development than Microsoft's own MSDN documentation and should be considered supplemental reading to that documentation. The wealth of missing details provided by this blog that Microsoft could not or did not document about Windows over the years is vital enough, many would agree an online "backup" of these details is a necessary endeavor. Specifics include:

<-- Back to Old New Thing Archive Index