Valid HTML 4.01 Transitional Valid CSS Valid SVG 1.0

Me, myself & IT

Odd, surprising, undocumented or weird (mis)behaviour of Microsoft® Windows® NT

Purpose
Quirk № 1
Demonstration
Background Information
Quirk № 2
Background Information
Demonstration
Remediation
Quirk № 3
Demonstration
Quirk № 4
Demonstration
Security Impact
MSRC Case 59749
Quirk № 5
Demonstration
Quirk № 6
Demonstration
Quirk № 7
Demonstration
Quirk № 8
Demonstration
Quirk № 9
Demonstration
Quirk № 10
Demonstration
Trivia

Purpose

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

Quirk № 1

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).
[Screenshot of default 'User Account Control Settings' from Windows 7] Windows Vista® introduced the security feature User Account Control: programs which want to be run with administrative rights need 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 administrative user account created during Windows setup for their daily work (instead to follow best practice and use a limited or 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: Windows 7 introduced auto elevation and enabled it for some 70 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, this security feature (really: security theatre) can be bypassed trivially in numerous ways when auto elevation is enabled. As result, arbitrary programs can then be run with administrative rights without prompting the user for consent.
To defeat 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.

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

[Screenshot of 'Group Policy Object Editor' from Windows 7] On default installations of Windows 7 or newer versions of Windows NT perform the following 9 simple steps.
  1. Log on to the user account created during Windows setup.

  2. Start one of the programs which have auto elevation enabled, MMC.exe or WUSA.exe for example: they start without prompting for consent.

  3. Open Control Panel, then User Accounts and click Change User Account Control setting, then 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 Policies snap-in, answer the prompt for consent, then open the Local Policies folder and the Security Options subfolder below: 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.: both 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.

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

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 now 25 year old Designed for Windows guidelines!

Background Information

Windows NT supports the following evaluation order or hierarchy and rules for program defaults, settings and policies:
  1. hard-coded program defaults are in effect only when neither a setting 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 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 settings and policies;
  7. policies override settings;
  8. when a policy is present for a 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 № 2

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, where they are stored, and how to add or modify them: 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 system environment variables are obscured by user 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 %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 direction 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 4 simple steps to show the (mis)behaviour.
  1. Create the text file quirk2.vbs with the following content in an arbitrary directory:

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

    CScript.exe quirk2.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
  3. Start the Command Processor under the LocalSystem user account, then list (the contents of) the directory %USERPROFILE%\AppData\Local\Temp\ alias %SystemRoot%\System32\Config\SystemProfile\AppData\Local\Temp\ and all environment variables:

    DIR "%USERPROFILE%\AppData\Local\Temp" /B
    SET
    File Not Found
    
    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
  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 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 comparision 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 setting and restart the system.

Quirk № 3

Multiple undocumented dependencies on environment variables … … …

Note: a proper implementation calls functions like GetSystemDirectory(), 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 commands to remove all environment variables and (try to) execute the applications Write.exe, RegEdit.exe, NotePad.exe, Explorer.exe as well as IExplore.exe and WordPad.exe afterwards:
REM Copyright © 2004-2020, 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
Write.exe
RegEdit.exe /M
NotePad.exe /P Win.ini
Explorer.exe /E,/Separate,.
START IExplore.exe
START WordPad.exe
EXIT
[Screenshot of error message from 'WordPad' on Windows 7]
explorer.exe
notepad.exe
regedit.exe
write.exe
win.ini

Quirk № 4

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

Demonstration

Start the Command Processor, then run the following commands to prove the documentation cited above wrong, and also show some undocumented (mis)behaviour:
REM Copyright © 2004-2020, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
CHDIR /D "%USERPROFILE%"
PUSHD "%SystemRoot%"
FOR /F "Delims==" %? IN ('SET') DO @SET %?=
START /B CMD /C SET
SET COMSPEC=%CD%\System32\Reg.exe
POPD
COPY NUL: Cmd.exe
FOR /F "Delims=" %? IN ('PAUSE') DO ECHO %?
FOR /F "Delims= UseBackQ" %? IN (`PAUSE`) DO ECHO %?
START CMD /C PAUSE
START /B ECHO
ASSOC | BREAK
FTYPE | SHIFT
SET | Cmd.exe
ERASE Cmd.exe
EXIT
COMSPEC=C:\Windows\System32\cmd.exe
PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC
PROMPT=$P$G

        1 File(s) copied.

Press any key . . .

Access Denied

Access Denied

ERROR: Invalid Argument/Option - '/c'.
Type "REG /?" for usage.

ERROR: Invalid Argument/Option - '/K'.
Type "REG /?" for usage.

ERROR: Invalid Argument/Option - '/S'.
Type "REG /?" for usage.
ERROR: Invalid Argument/Option - '/S'.
Type "REG /?" for usage.

ERROR: Invalid Argument/Option - '/S'.
Type "REG /?" for usage.
ERROR: Invalid Argument/Option - '/S'.
Type "REG /?" for usage.

ERROR: Invalid Argument/Option - '/S'.
Type "REG /?" for usage.
Access Denied
OUCH¹: contrary to its documentation, FOR /F … IN ('…') DO … as well as FOR /F "UseBackQ" … IN (`…`) DO … executes the application determined by the value of the environment variable COMSPEC (else the current process’ image) instead of Cmd.exe as child process.

OUCH²: contrary to its documentation, START CMD … does not evaluate the environment variable COMSPEC, but executes Cmd.exe from the CWD (else the current process᾿ image)!

OUCH³: when run in a pipeline, builtin commands are executed in a child process determined by the value of the environment variable COMSPEC.

The Command Processor sets the environment variables COMSPEC, PATHEXT and PROMPT if these are not provided by its caller.

Security Impact

The (unintended) execution of a (rogue) 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, allowing well-known attacks like CAPEC-13: Subverting Environment Variable Values documented in the CAPEC.

The (unintended) execution of (a rogue) Cmd.exe from the CWD constitutes a security vulnerability, similar to CVE-2014-0315 alias MS14-019 I discovered about 6 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, the well-known attack is documented as CAPEC-471: Search Order Hijacking.

MSRC Case 59749

Due 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!

Quirk № 5

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.

Demonstration

Start the Command Processor, then run the following commands to prove the documentation cited above wrong, and again show undocumented (mis)behaviour:
REM Copyright © 2004-2020, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
CALL Reg(Dummy
CALL Reg,Dummy
CALL Reg;Dummy
CALL Reg=Dummy
FOR %? IN (First,Second;Third=Fourth) DO @ECHO %?
EXIT
ERROR: Invalid Argument/Option - '(Dummy'.
Type "REG /?" for usage.

ERROR: Invalid Argument/Option - ',Dummy'.
Type "REG /?" for usage.

ERROR: Invalid Argument/Option - ';Dummy'.
Type "REG /?" for usage.

ERROR: Invalid Argument/Option - '=Dummy'.
Type "REG /?" for usage.

First
Second
Third
Fourth
OUCH¹: the Command Processor mistreats valid filenames containing an opening parenthesis '(', a comma ',', a semicolon ';' or an equals sign '=', and interprets these characters as separator!

OUCH²: contrary to its documentation, CALL does not just call batch scripts and has effect at the command prompt.

OUCH³: in builtin commands, the Command Processor mistreats comma, semicolon and equals sign almost always as delimiter or separator, and sometimes like white space.

Quirk № 6

The Win32 function GetFileMUIInfo() is documented in the MSDN as follows:
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) with error 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 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 quirk6.c with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2009-2020, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #pragma comment(compiler)
    #pragma comment(linker, "/DEFAULTLIB:KERNEL32.LIB")
    #pragma comment(linker, "/DEFAULTLIB:USER32.LIB")
    #pragma comment(linker, "/ENTRY:wmainCRTStartup")
    #pragma comment(linker, "/SUBSYSTEM:CONSOLE")
    #pragma comment(user, __TIMESTAMP__)
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    __declspec(safebuffers)
    BOOL	PrintConsole(HANDLE hConsole, LPCWSTR lpFormat, ...)
    {
    	WCHAR	szBuffer[1025];
    	DWORD	dwBuffer;
    	DWORD	dwConsole;
    
    	va_list	vaInserts;
    	va_start(vaInserts, lpFormat);
    
    	dwBuffer = wvsprintf(szBuffer, lpFormat, vaInserts);
    
    	va_end(vaInserts);
    
    	if (dwBuffer == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szBuffer, dwBuffer, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwBuffer;
    }
    
    const	WCHAR	szModule[] = L"C:\\Windows\\RegEdit.exe";
    
    __declspec(noreturn)
    VOID	WINAPI	wmainCRTStartup(VOID)
    {
    	PFILEMUIINFO	lpFileMUIInfo = NULL;
    	DWORD		dwFileMUIInfo = 0;
    	DWORD		dwError = ERROR_SUCCESS;
    	HANDLE		hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	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",
    					             GetLastError());
    			}
    		}
    	}
    
    	ExitProcess(dwError);
    }
  2. Build the application quirk6.exe from the source file quirk6.c created in step 1.:

    CL.EXE /GA /GF /Tcquirk6.c /W4 /Zl
    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk6.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.
    
    quirk6.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /out:quirk6.exe
    quirk6.obj
  3. Execute the application quirk6.exe built in step 2. to demonstrate the (mis)behaviour of the Win32 function GetFileMUIInfo():

    .\quirk6.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'

Quirk № 7

The Win32 functions GetTempPath() and GetTempFileName() are documented in the MSDN as follows:
DWORD GetTempPath(
  DWORD  nBufferLength,
  LPTSTR lpBuffer
);
[…]

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.
In addition to the quirks bugs demonstrated below, the documentation fails to tell that lpBuffer can be 0 alias NULL if nBufferLength is 0.
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.

Demonstration

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

    // Copyright © 2004-2020, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #pragma comment(compiler)
    #pragma comment(linker, "/DEFAULTLIB:KERNEL32.LIB")
    #pragma comment(linker, "/DEFAULTLIB:USER32.LIB")
    #pragma comment(linker, "/ENTRY:wmainCRTStartup")
    #pragma comment(linker, "/SUBSYSTEM:CONSOLE")
    #pragma comment(user, __TIMESTAMP__)
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    __declspec(safebuffers)
    BOOL	PrintConsole(HANDLE hConsole, LPCWSTR lpFormat, ...)
    {
    	WCHAR	szBuffer[1025];
    	DWORD	dwBuffer;
    	DWORD	dwConsole;
    
    	va_list	vaInserts;
    	va_start(vaInserts, lpFormat);
    
    	dwBuffer = wvsprintf(szBuffer, lpFormat, vaInserts);
    
    	va_end(vaInserts);
    
    	if (dwBuffer == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szBuffer, dwBuffer, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwBuffer;
    }
    
    __declspec(noreturn)
    VOID	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 length %lu\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());
    			}
    		}
    	}
    
    	ExitProcess(dwError);
    }
  2. Build the application quirk7.exe from the source file quirk7.c created in step 1.:

    CL.EXE /GA /GF /Tcquirk7.c /W4 /Zl
    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk7.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.
    
    quirk7.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /out:quirk7.exe
    quirk7.obj
  3. Create the text file quirk7.cmd with the following content in the directory used in the previous steps 1. and 2.:

    REM Copyright © 2004-2020, 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"
    EXIT /B
  4. Execute the batch script quirk7.cmd created in step 3. on Windows 7 (or an earlier version):

    .\quirk7.cmd
    REM Copyright © 2004-2020, 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 length 35
    GetTempFileName() returned pathname 'C:\Users\Stefan\AppData\Local\Temp\tmp8D8B.tmp'
    
    SET TMP=
    GetTempPath() returned pathname 'C:\Users\Stefan\AppData\Local\Temp\' of length 35
    GetTempFileName() returned pathname 'C:\Users\Stefan\AppData\Local\Temp\tmp8D9B.tmp'
    
    SET TEMP=
    GetTempPath() returned pathname 'C:\Users\Stefan\' of length 16
    GetTempFileName() returned pathname 'C:\Users\Stefan\tmp8DAB.tmp'
    
    SET USERPROFILE=
    GetTempPath() returned pathname 'C:\Windows\' of length 11
    GetTempFileName() returned pathname 'C:\Windows\tmp8E01.tmp'
    
    SET USERPROFILE=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
    GetTempPath() returned pathname 'C:\Windows\' of length 11
    GetTempFileName() returned pathname 'C:\Windows\tmp8E33.tmp'
    
    SET USERPROFILE=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
    GetTempPath() returned pathname 'C:\Windows\' of length 11
    GetTempFileName() returned pathname 'C:\Windows\tmp8E55.tmp'
    
    SET TEMP=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxy
    GetTempPath() returned pathname 'C:\ProgramData\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxy\' of length 145
    GetTempFileName() returned error 267
    
    SET TMP=C:\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvw
    GetTempPath() returned pathname 'C:\ProgramData\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxy\' of length 145
    GetTempFileName() returned error 267
    
    SET TMP=C:\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv
    GetTempPath() returned pathname 'C:\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv\' of length 130
    GetTempFileName() returned error 267
    
    SET TMP=.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.
    GetTempPath() returned pathname 'C:\ProgramData\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxy\' of length 145
    GetTempFileName() returned error 267
    
    SET TMP=.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.
    GetTempPath() returned pathname 'C:\ProgramData\' of length 15
    GetTempFileName() returned pathname 'C:\ProgramData\tmp8EDD.tmp'
    
    SET TMP=..
    GetTempPath() returned pathname 'C:\' of length 3
    GetTempFileName() returned pathname 'C:\tmp8EFF.tmp'
    
    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 267 alias ERROR_DIRECTORY, i.e. The directory name is invalid. instead of error 3 alias ERROR_PATH_NOT_FOUND, i.e. The system cannot find the path specified.

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

    .\quirk7.cmd
    REM Copyright © 2004-2020, 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 length 35
    GetTempFileName() returned pathname 'C:\Users\Stefan\AppData\Local\Temp\tmpF791.tmp'
    
    SET TMP=
    GetTempPath() returned pathname 'C:\Users\Stefan\AppData\Local\Temp\' of length 35
    GetTempFileName() returned pathname 'C:\Users\Stefan\AppData\Local\Temp\tmpF7A1.tmp'
    
    SET TEMP=
    GetTempPath() returned pathname 'C:\Users\Stefan\' of length 16
    GetTempFileName() returned pathname 'C:\Users\Stefan\tmpF7A9.tmp'
    
    SET USERPROFILE=
    GetTempPath() returned pathname 'C:\Windows\' of length 11
    GetTempFileName() returned pathname 'C:\Windows\tmpF7B1.tmp'
    
    SET USERPROFILE=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
    GetTempPath() returned pathname '' of length 277
    GetTempFileName() returned pathname '\tmpF7C7.tmp'
    
    SET USERPROFILE=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
    GetTempPath() returned pathname 'C:\ProgramData\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\' of length 146
    GetTempFileName() returned error 267
    
    SET TEMP=C:\ProgramData\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxy
    GetTempPath() returned pathname 'C:\ProgramData\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxy\' of length 145
    GetTempFileName() returned error 267
    
    SET TMP=C:\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvw
    GetTempPath() returned pathname 'C:\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvw\' of length 131
    GetTempFileName() returned error 267
    
    SET TMP=C:\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv
    GetTempPath() returned pathname 'C:\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv\' of length 130
    GetTempFileName() returned error 267
    
    SET TMP=.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.
    GetTempPath() returned pathname '' of length 276
    GetTempFileName() returned pathname '\tmpF7D1.tmp'
    
    SET TMP=.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.
    GetTempPath() returned pathname 'C:\ProgramData\' of length 15
    GetTempFileName() returned pathname 'C:\ProgramData\tmpF7D9.tmp'
    
    SET TMP=..
    GetTempPath() returned pathname 'C:\' of length 3
    GetTempFileName() returned pathname 'C:\tmpF7DF.tmp'
    
    EXIT /B
    OUCH³: contrary to its documentation cited above, the Win32 function GetTempPath() returns values greater than MAX_PATH + 1!

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

Quirk № 8

The Win32 function GetEnvironmentVariable() is documented in the MSDN as follows:
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!

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

It also shows a VERY common mistake: there is no null-terminating character, but a (string-)terminating NUL character!

Demonstration

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

    // Copyright © 2004-2020, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #pragma comment(compiler)
    #pragma comment(linker, "/DEFAULTLIB:KERNEL32.LIB")
    #pragma comment(linker, "/DEFAULTLIB:USER32.LIB")
    #pragma comment(linker, "/ENTRY:wmainCRTStartup")
    #pragma comment(linker, "/SUBSYSTEM:CONSOLE")
    #pragma comment(user, __TIMESTAMP__)
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    __declspec(safebuffers)
    BOOL	PrintConsole(HANDLE hConsole, LPCWSTR lpFormat, ...)
    {
    	WCHAR	szBuffer[1025];
    	DWORD	dwBuffer;
    	DWORD	dwConsole;
    
    	va_list	vaInserts;
    	va_start(vaInserts, lpFormat);
    
    	dwBuffer = wvsprintf(szBuffer, lpFormat, vaInserts);
    
    	va_end(vaInserts);
    
    	if (dwBuffer == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szBuffer, dwBuffer, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwBuffer;
    }
    
    __declspec(noreturn)
    VOID	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
    		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());
    			}
    		}
    
    	ExitProcess(dwError);
    }
  2. Build the application quirk8.exe from the source file quirk8.c created in step 1.:

    CL.EXE /GA /GF /Tcquirk8.c /W4 /Zl
    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: quirk8.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.
    
    quirk8.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /out:quirk8.exe
    quirk8.obj
  3. Execute the application quirk8.exe built in step 2. to demonstrate the (mis)behaviour:

    .\quirk8.exe
    GetEnvironmentVariable("EMPTY", 0x0037F884, 0) returned buffer size 1
    GetEnvironmentVariable("EMPTY", 0x0037F884, 1) returned error 1178683724
    OUCH: for environment variables with empty value, the Win32 function GetEnvironmentVariable() returns 0, but fails to (re)set the Win32 error 0 alias ERROR_SUCCESS to indicate no error!

Quirk № 9

The Win32 function GetDllDirectory() is documented in the MSDN as follows:
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!

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

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-2020, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #pragma comment(compiler)
    #pragma comment(linker, "/DEFAULTLIB:KERNEL32.LIB")
    #pragma comment(linker, "/DEFAULTLIB:USER32.LIB")
    #pragma comment(linker, "/ENTRY:wmainCRTStartup")
    #pragma comment(linker, "/SUBSYSTEM:CONSOLE")
    #pragma comment(user, __TIMESTAMP__)
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    __declspec(safebuffers)
    BOOL	PrintConsole(HANDLE hConsole, LPCWSTR lpFormat, ...)
    {
    	WCHAR	szBuffer[1025];
    	DWORD	dwBuffer;
    	DWORD	dwConsole;
    
    	va_list	vaInserts;
    	va_start(vaInserts, lpFormat);
    
    	dwBuffer = wvsprintf(szBuffer, lpFormat, vaInserts);
    
    	va_end(vaInserts);
    
    	if (dwBuffer == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szBuffer, dwBuffer, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwBuffer;
    }
    
    __declspec(noreturn)
    VOID	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());
    		}
    	}
    
    	ExitProcess(dwError);
    }
  2. Build the application quirk9.exe from the source file quirk9.c created in step 1.:

    CL.EXE /GA /GF /Tcquirk9.c /W4 /Zl
    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 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.
    
    /out:quirk9.exe
    quirk9.obj
  3. Execute the application quirk9.exe built in step 2. to demonstrate the (mis)behaviour:

    .\quirk9.exe
    GetDllDirectory(0, 0x003EF948) returned buffer size 1
    GetDllDirectory(1, 0x003EF948) returned error 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 0 alias ERROR_SUCCESS to indicate no error!

Quirk № 10

The DefWindowProc() and PostQuitMessage() functions 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.

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-2020, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #pragma comment(compiler)
    #pragma comment(linker, "/DEFAULTLIB:KERNEL32.LIB")
    #pragma comment(linker, "/DEFAULTLIB:USER32.LIB")
    #pragma comment(linker, "/ENTRY:wmainCRTStartup")
    #pragma comment(linker, "/SUBSYSTEM:CONSOLE")
    #pragma comment(user, __TIMESTAMP__)
    
    #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	// DefWindowProc() must be called in response to the
    		//  WM_NCCREATE message to register the window title!
    
    	case WM_NCCREATE:
    		return (LRESULT) TRUE;
    #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;
    
    __declspec(noreturn)
    VOID	wmainCRTStartup(VOID)
    {
    	DWORD	dwError;
    	LRESULT lResult;
    	BOOL	bResult;
    	HWND	hWindow;
    	MSG	msg;
    	WNDCLASSEX	wce = {sizeof(WNDCLASSEX),
    			       CS_GLOBALCLASS,
    #if QUIRKS == 2	// 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};
    	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 application quirk10.exe from the source file quirk10.c created in step 1. with the macro QUIRKS defined as 2:

    CL.EXE /DQUIRKS=2 /GA /GF /Tcquirk10.c /W4 /Zl
    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 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.
    
    /out:quirk10.exe
    quirk10.obj
  3. Start the application quirk10.exe built in step 2. and close its window to demonstrate the first (mis)behaviour:

    .\quirk10.exe
    OUCH¹: although the application window titled Quirks Demonstration Window was closed, the Command Processor waits for the child process to terminate … until you press CtrlC!

    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(), so the process’ primary thread does not terminate!

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

    CL.EXE /DQUIRKS=1 /GA /GF /Tcquirk10.c /W4 /Zl
    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.
    
    /out:quirk10.exe
    quirk10.obj
  5. Start the application quirk10.exe built in step 4. to demonstrate the second (mis)behaviour:

    .\quirk10.exe
    [Screenshot of untitled application window on Windows XP] [Screenshot of untitled application window on Windows 7] [Screenshot 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!

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–2020 • Stefan Kanthak • <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>