Skip to content

Heap Buffer Overflow in zval_undefined_cv. #11028

Closed

Description

@Changochen

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

Activity

  1. ndossche commented on Apr 7, 2023

    @ndossche
    Member

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

    zend_iterator_dtor(iter);
    commenting this line "fixes" both my test case and OP's for some reason 🤔 (but ofc introduces leakage etc etc)

  2. ndossche commented on Apr 8, 2023

    @ndossche
    Member

    I attempted a fix here: ndossche@6650263
    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: ndossche@6650263#diff-e61ec0f8972fbb546701b53555d59a0bfe720abdf3040f6b87c23a8a7f94db40
    I'm not sure what's going on in that case tbh

  3. iluuu1994 commented on Apr 13, 2023

    @iluuu1994
    Member

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

  4. arnaud-lb commented on Apr 14, 2023

    @arnaud-lb
    Member

    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.

    • ZEND_ADD_ARRAY_UNPACK throws an Error("Keys must be of type int|string during array unpacking"). EG(exception) is set, and EG(current_execute_data)->opline is set to EG(exception_op)
    • The generator is closed, which resumes in the finally block
    • ZEND_DISCARD_EXCEPTION clears EG(exception), but the execute_data->opline of the parent frame remains EG(exception_op)
    • When resuming the parent frame, we are in an unexpected state: execute_data->opline is EG(exception_op), but EG(exception) is NULL
    • ZEND_HANDLE_EXCEPTION_SPEC_HANDLER cleanups live variables (including the array being built), and calls zend_leave_helper_SPEC
    • In zend_leave_helper_SPEC jumps to the next opline (ZEND_FETCH_DIM) because EG(exception) is NULL

    @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.

  5. ndossche commented on Apr 14, 2023

    @ndossche
    Member

    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.

  6. added 2 commits that reference this issue on Apr 15, 2023
    1799327
    a44acde
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions