The strangest way of detecting Windows NT

Date:October 26, 2004 / year-entry #376
Tags:other
Orig Link:https://blogs.msdn.microsoft.com/oldnewthing/20041026-00/?p=37473
Comments:    20
Summary:A colleague of mine nominated this code for Function of the Year. (This is the same person who was the first to report that a Windows beta used a suspect URL.) I have to admit that this code is pretty impressive. Of all the ways to check the operating system, you have to agree that...

A colleague of mine nominated this code for Function of the Year. (This is the same person who was the first to report that a Windows beta used a suspect URL.) I have to admit that this code is pretty impressive. Of all the ways to check the operating system, you have to agree that sniffing at an undocumented implementation detail of memory-mapped files is certainly creative!

// following the typographical convention that code
// in italics is wrong
int AreWeRunningOnWindowsNT()
{
      HANDLE hFile, hFileMapping;
      BYTE *pbFile, *pbFile2;
      char szFile[MAX_PATH];

      GetSystemDirectory(szFile, MAX_PATH);
      strcat(szFile, "\\MAIN.CPL");
      hFile = CreateFile(szFile, GENERIC_READ | GENERIC_WRITE, 0,
            NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

      hFileMapping = CreateFileMapping(hFile, NULL, PAGE_READWRITE,
            0, 0, NULL);

      pbFile = (PBYTE) MapViewOfFile(hFileMapping, FILE_MAP_WRITE,
            0, 0, 0);

      pbFile2 = (PBYTE) MapViewOfFile(hFileMapping, FILE_MAP_WRITE,
            0, 65536, 0);

      if (pbFile + 65536 != pbFile2)
            return 1;

      return 0;
}

Nevermind that the function also leaves a file locked and leaks two handles and two views each time you call it!

What's more, this function may erroneously report FALSE on a Windows NT machine if by an amazing coincidence the memory manager happens to assign the second file view to the very next 64K block of memory (which it is permitted to do since address space granularity is 64K).

It can also erroneously report TRUE on a Windows 95 machine if the MAIN.CPL file happens to be smaller than 64K, or if you don't have write permission on the file. (Notice that the program requests read-write access to the MAIN.CPL file.)

This particular function is from a library that is used by many popular multimedia titles.

The quickest way to detect whether you are running on a Windows 95-series system or a Windows NT-series system is to use the hopefully-obviously-named function GetVersion.

int AreWeRunningOnWindowsNT()
{
    return (GetVersion() & 0x80000000) == 0;
}

[Raymond is currently on vacation; this message was pre-recorded.]


Comments (20)
  1. anon says:

    It also has a potential buffer overflow.

  2. Doug says:

    It has many many bugs. What is an error?

    My question is, how could someone who knows enough to use said functions be stupid enough to make that many errors in one function? This almost looks deliberate….

  3. Shane says:

    I vote this be sent to "The Daily WTF" for inclusion!

  4. anonymous says:

    So I can avoid it..

  5. Alex Feinman says:

    I don’t think the buffer overflow is something to worry about. That would mean that the system directory is over 248 characters deep. I would bet that a number of things will get brokien if that were the case

  6. Gene Hamilton says:

    Do you know what their reasoning for detecting the OS version this way?

  7. Drew Marsh says:

    Wow, that’s just scary.

  8. Adrian says:

    Actually, it would be better to use GetVersionEx() Since GetVersion() has been superseded by GetVersionEx()

  9. Tony Cox [MS] says:

    I just had an even scarier thought: what if this code is correct?

    By which I mean, what if the thing they are trying to detect is not whether the system is NT, but whether is has this particular filemapping implementation quirk. The implication of which would be that somewhere they want to rely on it…

  10. Tom says:

    What about this

    if ( !RegisterClass(&wndclass) )

    {

    MessageBox(NULL, TEXT("This program requires Windows NT"), szAppName, MB_ICONERROR) ;

    return 0;

    }

    From Petzold. The idea being that the only reason RegisterClass would fail would be that you are running the Unicode build on Windows 9x/Me where RegisterClassW is a stub that returns FALSE.

  11. Actually the world ends if %windir% isn’t an 8.3 compatible name, so the buffer overrun is definitely conceptual not tangible. The code /shouldn’t/ be written this way; the thing to keep you awake at night isn’t whether this function in particular will have a BO but rather whether all the other functions written by the same developer will…

  12. Norman Diamond says:

    And I thought the most common uses of GetVersion() were for programs to inform Windows 2000 users that they had to get SP3 (when Windows 2000 was new and its SP3 was years in the future), or to inform Windows 2000 users that they didn’t have Direct X, or to find other random reasons why they would refuse to run themselves under Windows 2000. Hmm… since this genuinely is an abuse of tools such as GetVersion(), one might wonder how many shims have been written in order to provide compatibility for these broken applications.

  13. Tony, that may be the scariest thing I’ve heard all day…

  14. Dhericean says:

    Maybe this is to avoid the fact that a program can be lied to about the Windows version using various things such as the Application Compatibility Toolkit.

    At least they didn’t write something to parse Kernel32.dll or some such.

  15. Adrian says:

    We hear great scary stories from Raymond about the extent MS goes to in order to make Windows compatible with apps that made bad assumptions or relied on undocumented behavior. The fact that this guy is testing for NT (even though it’s poorly done) reminds us that ISVs go to lengths to be compatible with an ever-growing number of Windows versions. Some APIs have changed radically (e.g., printing), and occasionally we run into things that seem to work (or break) slightly differently on various versions of Windows.

    Here’s one that hit me last night. I have an HBITMAP that I know is really a DIB Section. I use GetObject to retrieve a DIBSECTION that describes it. This bitmap happens to have an odd width. DIBs use DWORD alignment for scanlines, and I see that for almost every version of Windows dib.dsBm.bmWidthBytes is a multiple of 4 just like I’d expect. But on Windows 2000, it’s only a multiple of 2 as though it’s a WORD-aligned device-DEpendent bitmap. If I trust 2000 and use the WORD-aligned value for indexing into the pixels, it doesn’t work–the scanlines really are DWORD aligned.

    Obviously, I can fix the problem by computing a DWORD-aligned stride without explicitly testing for the OS version. I can’t find any documentation that guarantees DWORD alignment for a DIB Section, only for DIBs, so what if the next version of Windows really does align the DIB Section scanlines to WORD boundaries? I would have to test OS versions to know whether or not to trust the alignment reported by GDI.

  16. Steve says:

    Adrian,

    I am not a GDI programmer but the info on MSDN seems to indicate that it is word aligned:

    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/bitmaps_0cqa.asp

    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/bitmaps_2h6a.asp

    bmWidthBytes

    Specifies the number of bytes in each scan line. This value must be divisible by 2, because the system assumes that the bit values of a bitmap form an array that is word aligned.

    Of course I may be missing your point.

  17. asdf says:

    DIBs (including DIBSECTIONS) are always DWORD aligned. The only things that use WORD aligned buffers are DDBs using CreateBitmap, CreateBitmapIndirect, GetBitmapBits, and SetBitmapBits (I think that is a comprehensive list, MSDN kind of sucks to navigate). The rest of the bitmap functions use DWORD aligned stuff.

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