FAQ
============ 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.

Search Discussions

  • Richard Lynch at Dec 9, 2006 at 8:05 pm
    I wish I could write bug reports this good!
    :-)

    Nice work!
    On Tue, December 5, 2006 11:22 pm, Kevin Hoffman wrote:
    ============ 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.
    --
    PHP Internals - PHP Runtime Development Mailing List
    To unsubscribe, visit: http://www.php.net/unsub.php

    --
    Some people have a "gift" link here.
    Know what I want?
    I want you to buy a CD from some starving artist.
    http://cdbaby.com/browse/from/lynch
    Yeah, I get a buck. So?

Related Discussions

Discussion Navigation
viewthread | post
Discussion Overview
groupphp-internals @
categoriesphp
postedDec 6, '06 at 5:30a
activeDec 9, '06 at 8:05p
posts2
users2
websitephp.net

2 users in discussion

Kevin Hoffman: 1 post Richard Lynch: 1 post

People

Translate

site design / logo © 2022 Grokbase