It’s a bad idea to have a TEMP environment variable longer than about 130 characters

I've been working with the Win32 API for almost 20 years - literally since the very first Win32 APIs were written.  Even after all that time, I'm occasionally surprised by the API behavior.

Earlier today I was investigating a build break that took out one of our partner build labs.  Eventually I root caused it to an issue with (of all things) the GetTempName API.

Consider the following code (yeah, I don’t check for errors, <slaps wrist />):

 #include "stdafx.h"
#include <string>
#include <iostream>
#include <Windows.h>

using namespace std;

const wchar_t longEnvironmentName[] = 
        L"c:\\users\\larry\\verylongdirectory\\withaverylongsubdirectory"
        L"\\andanotherlongsubdirectory\\thatisstilldeeper\\withstilldeeper"
        L"\\andlonger\\untilyoustarttorunoutofpatience\\butstillneedtobelonger"
        L"\\untilitfinallygetslongerthanabout130characters";


int _tmain(int argc, _TCHAR* argv[])
{
    wchar_t environmentBuffer[ MAX_PATH ];
    wchar_t tempPath[ MAX_PATH ];
    SetEnvironmentVariable(L"TEMP", longEnvironmentName);
    SetEnvironmentVariable(L"TMP", longEnvironmentName);
    GetEnvironmentVariable(L"TEMP", environmentBuffer, _countof(environmentBuffer));
    wcout << L"Temp environment variable is: " << environmentBuffer << " length: " << wcslen(environmentBuffer) << endl;
    GetTempPath(_countof(tempPath), tempPath);
    wcout << L"Temp path: " << tempPath<< " length: " << wcslen(tempPath) << endl;
    return 0;
}

When I ran this program, I got the following output:

Temp environment variable is: c:\users\larry\verylongdirectory\withaverylongsubdirectory\andanotherlongsubdirectory\thatisstilldeeper\withstilldeeper\andlonger\ untilyoustarttorunoutofpatience\butstillneedtobelonger\untilitfinallygetslongerthanabout130characters length: 231 Temp path: C:\Users\larry\ length: 15

So what’s going on?  Why did GetTempPath return a pointer to my profile directory and not the (admittedly long) TEMP environment variable?

There’s a bunch of stuff here.  First off, let’s consider the documentation for GetTempPath:

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.

So that explains where the c:\Users\larry came from – something must have gone wrong retrieving the “TMP” and “TEMP” environment variables so it fell back to step 3[1].  But what could have happened?  We know that at least the TEMP environment variable was correctly set, we retrieved it in our test application.  This was where I got surprised.

It turns out that under the covers (at least on Win7), the function which retrieves the TEMP and TMP environment variables uses a UNICODE_STRING structure to initialize the string.  And, for whatever reason, they set MaximumLength to MAX_PATH+1.  If I look at the documentation for UNICODE_STRING, we find:

MaximumLength
Specifies the total size, in bytes, of memory allocated for Buffer. Up to MaximumLength bytes may be written into the buffer without trampling memory.

So the function expects at most 261 bytes,   or about 130 characters.   I often see behaviors like this in the "A" version of system APIs, but in this case both the "A" and "W" version of the API had the same unexpected behavior.

The moral of the story: If you set your TEMP environment variable to something longer than 130 characters or so, GetTempPath will return your USERPROFILE.  Which means that you may unexpectedly find temporary files scribbled all over your profile directory.

 

The fix was to replace the calls to GetTempPath with direct calls to GetEnvironmentVariable - it doesn't have the same restriction.

 

 

[1] Note that the 4th step is the Windows directory.  You can tell that this API has been around for a while because apparently the API designers thought it was a good idea to put temporary files in the windows directory.

 

EDIT: Significantly revised to improve readability - I'm rusty at this.