|
php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login |
[2011-09-21 04:53 UTC] mattjd at gmail dot com
Description: ------------ (sent to security@php.net) There is a TOCTOU-ish issue in the php function getenv() on Windows builds. See here: http://svn.php.net/viewvc/php/php-src/trunk/ext/standard/basic_functions.c? revision=316689&view=markup#l4005 Note that the size of the environment variable is retrieved first on line 4014: size = GetEnvironmentVariableA(str, &dummybuf, 0); It is then used to malloc a buffer big enough to hold the variable (at that time) on line 4025: ptr = emalloc(size); This is then used on the next line to retrieve the environment variable into: size = GetEnvironmentVariableA(str, ptr, size); What this does not take into account is that the second call to GetEnvironmentVariableA may fail due to ie. the environment variable not being found (ERROR_ENVVAR_NOT_FOUND), having been deleted in another thread. This leads to the use of the still-uninitialized buffer in RETURN_STRING which leads to a garbage string being returned, one that addresses some location on the heap. This string leaks heap information as well as allowing heap modification over the area which it spans (which may be bigger than the malloc'd buffer size, due to the lack of a null terminator). Running these two scripts, at the same time, and in a multi threaded situation (ie. using an Apache handler DLL or FastCGI) demonstrates the issue. Test script: --------------- <?php for(;;) { putenv("PHP_TEST=" . str_repeat("A", rand(0, 1024))); putenv("PHP_TEST"); } ?> <?php for(;;) { do { $r = getenv("PHP_TEST"); } while ($r === FALSE || $r === "" || $r[0] === "A"); for($i = 0; $i < strlen($r); ++$i) printf("%02x ", ord($r[$i])); print "<br>\n"; flush(); //Uncomment this to corrupt the heap: //for($i = 0; $i < strlen($r); ++$i) // $r[$i] = "\xCC"; } ?> Expected result: ---------------- No output from either script. Actual result: -------------- Second script outputs parts of the heap. Patchesgetenv_notfound (last revision 2011-09-21 06:24 UTC by pajoye@php.net)Pull RequestsHistoryAllCommentsChangesGit/SVN commits
|
|||||||||||||||||||||||||||
Copyright © 2001-2025 The PHP GroupAll rights reserved. |
Last updated: Fri Oct 24 22:00:02 2025 UTC |
As it stands now (the current code in trunk), a garbage string will be returned if the environment variable's value expands in size between calls. The second call to GetEnvironmentVariableA leaves the buffer untouched, and doesn't return 0; instead it returns the required buffer size. The logic would need to look something like this: char dummybuf; int size, size2; SetLastError(0); /*If the given bugger is not large enough to hold the data, the return value is the buffer size, in characters, required to hold the string and its terminating null character. We use this return value to alloc the final buffer. */ size = GetEnvironmentVariableA(str, &dummybuf, 0); if (size == 0) { if (GetLastError() == ERROR_ENVVAR_NOT_FOUND) { /* The environment variable doesn't exist. */ RETURN_FALSE; } /* env exists, but it is empty */ RETURN_EMPTY_STRING(); } ptr = emalloc(size); SetLastError(0); size2 = GetEnvironmentVariableA(str, ptr, size); if (size == 0) { efree(ptr); if (GetLastError() == ERROR_ENVVAR_NOT_FOUND) { /* The environment variable doesn't exist. */ RETURN_FALSE; } /* env exists, but it is empty */ RETURN_EMPTY_STRING(); } else if (size2 < size) { /* got the variable value */ RETURN_STRING(ptr, 0); } else { /* variable expanded between calls */ /* ??? */ } ..where the /* ??? */ is replaced by however you propose to handle failure, I guess :) Also, I've found that the non-Windows code path is actually also vulnerable to the same problem; running the same two scripts on Linux using Apache with the worker MPM and a threadsafe build of PHP produces the same results as Windows; the heap is leaked (and modifiable). (I guess why this is why the use of the worker MPM isn't recommended, huh) Perhaps there is another issue in that the {get,put,unset}env() family of functions need to be used in a threadsafe manner. For example, use of getenv() would involve an invocation, and then its result being copied to a local buffer, atomically. An alternative, if there are already the appropriate threadsafe data structures available, would be to make a local copy of the environment at process startup, and use that in the implementations of the PHP {get,put}env functions (and wherever else needed).