php / php-src

The PHP Interpreter
https://www.php.net
Other
37.93k stars 7.73k forks source link

Heap Buffer Overflow in zval_undefined_cv. #11028

Closed Changochen closed 1 year ago

Changochen commented 1 year ago

Description

The following code:

<?php
$c = (function () {
    [
        ...($r = (function () {
            try {
                yield $a => 0;
            } finally {
                return [];
            }
        })()),
    ];
})()[0];

with USE_TRACKED_ALLOC=1 USE_ZEND_ALLOC=0 Resulted in this output:

heap-buffer-overflow read

PHP Version

PHP 8.3.0-dev

Operating System

No response

nielsdos commented 1 year ago

Actually this seems to be a whole category of "yield"-related issues with finally's. This also doesn't work:

<?php
function test() {
    try {
        yield null => 0;
    } finally {
        return [];
    }
}

echo "hi\n";
var_dump([...test()]);
echo "hi2\n";

and if you replace null with (new stdClass), or false, or true, ... for example, it also doesn't work.

EDIT: progress.... https://github.com/php/php-src/blob/7de83e273642f6bdc8e0dfd052d8ecefa7c7b9e5/Zend/zend_vm_def.h#L6236 commenting this line "fixes" both my test case and OP's for some reason :thinking: (but ofc introduces leakage etc etc)

nielsdos commented 1 year ago

I attempted a fix here: https://github.com/nielsdos/php-src/commit/665026320302712d531d1e889d1abfc1fa766084 This commit fixes both OP's issue and the issue I reported in my previous comment.

However, I found a variant of my previously reported issue which still crashes (may be different root cause?). It can be found here: https://github.com/nielsdos/php-src/commit/665026320302712d531d1e889d1abfc1fa766084#diff-e61ec0f8972fbb546701b53555d59a0bfe720abdf3040f6b87c23a8a7f94db40 I'm not sure what's going on in that case tbh

iluuu1994 commented 1 year ago

Maybe @bwoebi or @arnaud-lb can help, they've touched this code most recently :slightly_smiling_face:

arnaud-lb commented 1 year ago

This script seems to result in a memory corruption since at least 7.4 according to https://3v4l.org/8SDGE

The problem seems to be that we discard an exception that was thrown by an other frame, but the frame will still try to handle it. Since EG(exception) is NULL, it will not take the right code path, and continue to execute after having freed live variables.

@nielsdos your fix looks good. The case that still crashes is because EG(opline_before_exception) points to the generator op array, so in ZEND_HANDLE_EXCEPTION we call cleanup_unfinished_calls with an invalid op_num. We should probably save/restore EG(opline_before_exception) as well.

nielsdos commented 1 year ago

Thanks. That makes sense. I missed the opline_before_exception part so that's why I didn't understand what was going on with my extra testcase. I'll update the fix and PR it when I have time.