============ Bug summary ============

If putenv deletes an environment variable (putenv("VAR=")) after it was set 
previously (putenv("VAR=xxx")) then pe->previous_entry (pe is the internal hash 
table entry for the environment variable) will point to a freed memory region.

This bug is triggered on windows because pe->previous_entry is set to point 
directly to the environment string (environ[...]) instead of a *copy* of the 
string. The MSVCRT library will free that string when an environment variable 
is deleted. Thus, pe->previous_entry can point to a free memory region when it 
is used later in the putenv_ht hashtable deconstructor.

============ Impact ============

Allows possible denial of service attack by crashing Apache.

============ PHP Versions ============

I've used this bug to crash Apache using PHP 5.1.2, 5.1.4, 5.2, and the latest 
version from CVS.

============ Bug Fix List ============

Fixes bug #39751 and probably bug# 36819.

============ Patch Details ============

PHP_FUNCTION(putenv) is called the first time. The TZ environment variable 
value is set and an entry is created in the putenv_ht hashtable with 
pe.previous_value=NULL.

Now the second call to PHP_FUNCTION(putenv) it first executes:
basic_functions.c line 4416
 zend_hash_del(&BG(putenv_ht), pe.key, pe.key_len+1);

This deletes the old entry from the putenv_ht hash table (ok). Then it looks 
through the C environment to see if there is a previous value 
(basic_functions.c lines 4418 - 4425).

Because this is the second time through there is a previous value for the TZ 
environment variable (in my case, it's US/Eastern).
So pe.previous_value = *env on line 4422:
 pe.previous_value = *env;

Note that its directly pointing to the string managed by the C runtime. This is 
the root cause of the bug (crash).

Now it sets the environment variable using the new value, using the putenv lib 
call. (basic_functions.c line 4435) (In this case, "TZ="). Because we're 
removing the value of the environment variable the bug will be triggered.

putenv eventually calls __crtsetenv

In __crtsetenv the remove variable is set to 1, because the string is "TZ="
setenv.c line 94
 remove = (*(equal + 1) == _T('\0'));

The memory for the old environment variable is freed. Any pointers that point 
to env[ix] are now INVALID pointers. This includes the pe.previous_value 
pointer!
setenv.c line 183
 _free_crt(env[ix]);


Now the pe information is inserted into the putenv_ht hashtable. Note that the 
pe.previous_value field is now pointing to a freed block of memory (in debug 
builds, this is immediately set to 0xDD -- in release builds it still points to 
the old data until that memory region is allocated and overwritten).

The next time the TZ environment variable is set or when the PHP interpreter is 
exiting and cleaning up (zm_deactivate_basic), the destructor on the old hash 
table entry will be called (the php_putenv_destructor function).

It then checks to see if there is a previous value (if previous_value is 
nonzero). If it is, it calls putenv with the previous value.
basic_functions.c lines 3846 - 3855
 ...
 putenv(pe->previous_value);
 ...

However, the pe.previous_value variable points to a freed block of memory. 
putenv immediately tries to copy the new value of the environment variable 
(pe->previous_value).
putenv.c lines 127 - 129:
 if ( (newoption = (_TSCHAR *)_malloc_crt((_tcslen(option)+1) *
              sizeof(_TSCHAR))) == NULL )
             return -1;

If the new value (pe->previous_value) has been overwritten and is no longer 
zero-terminated then the call to _tcslen (strlen) can possibly keep reading 
memory until it hits an invalid memory region. At this point it causes an 
invalid memory access fault.

============ Test Case ============

It's hard to reproduce this bug without a lot of complex code in between the 
place where the environment variable is set to nothing and the place where it's 
set to a value again. I have written an example script that causes some memory 
access assertions sometimes.
Index: ext/standard/basic_functions.c
===================================================================
RCS file: /repository/php-src/ext/standard/basic_functions.c,v
retrieving revision 1.829
diff -u -r1.829 basic_functions.c
--- ext/standard/basic_functions.c      5 Dec 2006 04:52:44 -0000       1.829
+++ ext/standard/basic_functions.c      6 Dec 2006 05:13:02 -0000
@@ -3852,6 +3852,9 @@
                SetEnvironmentVariable(pe->key, "bugbug");
 #endif
                putenv(pe->previous_value);
+# if defined(PHP_WIN32)
+               efree(pe->previous_value);
+# endif
        } else {
 # if HAVE_UNSETENV
                unsetenv(pe->key);
@@ -4419,7 +4422,12 @@
                pe.previous_value = NULL;
                for (env = environ; env != NULL && *env != NULL; env++) {
                        if (!strncmp(*env, pe.key, pe.key_len) && 
(*env)[pe.key_len] == '=') {  /* found it */
+#if defined(PHP_WIN32)
+                               /* must copy previous value because MSVCRT's 
putenv can free the string without notice */
+                               pe.previous_value = estrndup(*env, 1024);
+#else
                                pe.previous_value = *env;
+#endif
                                break;
                        }
                }

-- 
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php

Reply via email to