From:             Terry at ellisons dot org dot uk
Operating system: Ubuntu 12.10
PHP version:      5.4.12
Package:          Scripting Engine problem
Bug Type:         Bug
Bug description:Indeterminate GC of evaled lambda function resources

Description:
------------
The Internals thread "(non)growing memory while creating anoymous functions
via eval()" see http://marc.info/?t=135990541000003&r=1&w=2 is the
background to this report.

Description

Storage allocation and garbage collection of lambda function resources is
indeterminate an may or may not lead to memory exhaustion.  This (Example1)
script demonstrates the effect:

    <?php
    $x = "";
    while (1) {
        if (isset($argv[1])) $x = str_repeat(" " ,mt_rand(1,50000));
        eval ("\$fun = function() { $x return memory_get_usage(); };");
        echo "Mem usage= {$fun()}\n";
    }

If arg1 is set it dies with memory exhaustion, and is stable if unset. 
Replacing the eval with (giving Example2)

    $fun = function() { return memory_get_usage(); };

is also stable so this isn't a resource leakage in the string $x per se.

The issue here is that the closure creates a magic name for the function,
in PHP terms

    $function_name = sprintf("\0{closure}%s%p",name,addr);

where the name is the resolved filename of the source where the function
was defined and the addr is the absolute address of the function definition
within the memory resident copy of the source during the compilation
process, for example in Example 2 where $fun is statically defined, on my
test this is

    "\0{closure}/tmp/y.php0x7fc90b1fd083"

The compiler creates one entry for "\0{closure}/tmp/y.php0x7fc90b1fd08" in
the CG function_table, but the closure DTOR does not delete or GC this
entry.  The reason is in this logical: in Example 2 the $fun assignment
generates

     ZEND_DECLARE_LAMBDA_FUNCTION    '\0{closure}/tmp/y.php0x7fc90b1fd083'
     ASSIGN                           !2, ~5

that is the function is compiled once but rebound multiple times during
execution.

With Example 1, the where the eval is used, the closure uses a name based
on the source file and line number where the eval was executed, e.g.
"/tmp/y.php(5): eval()'d code" giving a magic name for the function of 

    "\0{closure}/tmp/y.php(5): eval()'d code0x0x7fa11ccb9ef7"

where the addr is the absolute memory location in the string being
evaluated.

Hence in this scenario, each evaluation creates a new entry in the
function_table, even though the closure is subsequently DTORed. In many
ways this is the same behaviour are similar to create_function which
generates magic names "\0Lambda%d" where the integer is the # of the lamda
generated and again these build up in the function_table and are not
GC'ed.

The interesting Q is why isn't this always the behaviour? The reason is
that the allocator includes an optimisation whereby if a string with an
RC=1 is being replaced by a string of the same size then the memory is
reused. If the new string contains a new closure function starting at the
same offset then by accident the magic name will be the same as the
previous (and different function). The compilation invokes the function
zend_do_begin_function_declaration() and here the !is_method path does a
zend_hash_update on the CG function_table. As the names happen to be the
same, the update executes the function table DTOR on the previous entry
cleaning it up.

This accidental cleanup seems like a bug.  I'll try to find an exploitable
example.


-- 
Edit bug report at https://bugs.php.net/bug.php?id=64291&edit=1
-- 
Try a snapshot (PHP 5.4):   
https://bugs.php.net/fix.php?id=64291&r=trysnapshot54
Try a snapshot (PHP 5.3):   
https://bugs.php.net/fix.php?id=64291&r=trysnapshot53
Try a snapshot (trunk):     
https://bugs.php.net/fix.php?id=64291&r=trysnapshottrunk
Fixed in SVN:               https://bugs.php.net/fix.php?id=64291&r=fixed
Fixed in release:           https://bugs.php.net/fix.php?id=64291&r=alreadyfixed
Need backtrace:             https://bugs.php.net/fix.php?id=64291&r=needtrace
Need Reproduce Script:      https://bugs.php.net/fix.php?id=64291&r=needscript
Try newer version:          https://bugs.php.net/fix.php?id=64291&r=oldversion
Not developer issue:        https://bugs.php.net/fix.php?id=64291&r=support
Expected behavior:          https://bugs.php.net/fix.php?id=64291&r=notwrong
Not enough info:            
https://bugs.php.net/fix.php?id=64291&r=notenoughinfo
Submitted twice:            
https://bugs.php.net/fix.php?id=64291&r=submittedtwice
register_globals:           https://bugs.php.net/fix.php?id=64291&r=globals
PHP 4 support discontinued: https://bugs.php.net/fix.php?id=64291&r=php4
Daylight Savings:           https://bugs.php.net/fix.php?id=64291&r=dst
IIS Stability:              https://bugs.php.net/fix.php?id=64291&r=isapi
Install GNU Sed:            https://bugs.php.net/fix.php?id=64291&r=gnused
Floating point limitations: https://bugs.php.net/fix.php?id=64291&r=float
No Zend Extensions:         https://bugs.php.net/fix.php?id=64291&r=nozend
MySQL Configuration Error:  https://bugs.php.net/fix.php?id=64291&r=mysqlcfg

Reply via email to