Skip to content

Prevent resumption of generator suspended in yield from #19315

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from

Conversation

arnaud-lb
Copy link
Member

@arnaud-lb arnaud-lb commented Jul 30, 2025

Normally we prevent generators from being resumed while they are already running, but we failed to do so for generators delegating to non-Generators. As a result such generator can be resumed, terminated, which causes unexpected results (crashes) later.

In GH-19306 in particular, the generator delegate It::getIterator() suspends while being called by generator g(). We then resume g(), which throws while trying to resume It::getIterator(). This causes g() and It::getIterator() to be released. We then crash while resuming the Fiber in It::getIterator().

Fix this by ensuring that generators are marked as running while they fetch the next value from the delegate.

Fixes GH-19306

@arnaud-lb arnaud-lb changed the base branch from master to PHP-8.3 July 30, 2025 15:45
@iluuu1994
Copy link
Member

Our PRs clashed. #19313 But mine fails, so I trust your solution is better. 😄

@arnaud-lb
Copy link
Member Author

arnaud-lb commented Jul 30, 2025

Oops :) I did assign the bug to myself before starting working on the fix, but I know it's easy to miss as I did a few times in the past.

@iluuu1994
Copy link
Member

I found a way to make this still crash:

class It implements IteratorAggregate {
    public function getIterator(): Generator {
        yield "";
        Fiber::suspend();
    }
}

function g() {
    yield from new It();
}

$b = g();
$b->rewind();

$fiber = new Fiber(function () use ($b) {
    $b->next();
});

$fiber->start();

try {
    $b->throw(new Exception('test'));
} catch (Error $e) {
    echo $e->getMessage(), "\n";
}

@bwoebi
Copy link
Member

bwoebi commented Jul 30, 2025

This PR seems right to me.

@iluuu1994 Your crash is unrelated, e.g. $g = (function () use (&$g) { yield; $g->throw(new Exception); })(); $g->next(); ends up with memory exhaustion (the prev_execute_data ends up forming a cycle).
That's worthy of a separate bug.

@iluuu1994
Copy link
Member

@bwoebi Don't think it's unrelated. It results in the same uaf. It happens for the same reason (throw() frees generator->values).

Details

Cannot resume an already running generator
=================================================================
==74285==ERROR: AddressSanitizer: heap-use-after-free on address 0x50b0000026c0 at pc 0x000001386ec7 bp 0x7f62ae3ff370 sp 0x7f62ae3ff368
READ of size 8 at 0x50b0000026c0 thread T0
    #0 0x1386ec6 in zend_rethrow_exception /home/ilutov/Developer/php-src/Zend/zend_exceptions.h:85
    #1 0x13dae94 in ZEND_DO_FCALL_SPEC_RETVAL_UNUSED_HANDLER /home/ilutov/Developer/php-src/Zend/zend_vm_execute.h:1904
    #2 0x16678a9 in execute_ex /home/ilutov/Developer/php-src/Zend/zend_vm_execute.h:57304
    #3 0x171572b in zend_generator_resume /home/ilutov/Developer/php-src/Zend/zend_generators.c:823
    #4 0x1719fd4 in zend_generator_iterator_move_forward /home/ilutov/Developer/php-src/Zend/zend_generators.c:1126
    #5 0x17138e4 in zend_generator_get_next_delegated_value /home/ilutov/Developer/php-src/Zend/zend_generators.c:700
    #6 0x1714ff0 in zend_generator_resume /home/ilutov/Developer/php-src/Zend/zend_generators.c:794
    #7 0x17178a9 in zim_Generator_next /home/ilutov/Developer/php-src/Zend/zend_generators.c:972
    #8 0x13da64a in ZEND_DO_FCALL_SPEC_RETVAL_UNUSED_HANDLER /home/ilutov/Developer/php-src/Zend/zend_vm_execute.h:1867
    #9 0x16678a9 in execute_ex /home/ilutov/Developer/php-src/Zend/zend_vm_execute.h:57304
    #10 0x124946c in zend_call_function /home/ilutov/Developer/php-src/Zend/zend_execute_API.c:961
    #11 0x17c2c69 in zend_fiber_execute /home/ilutov/Developer/php-src/Zend/zend_fibers.c:597
    #12 0x17c0322 in zend_fiber_trampoline /home/ilutov/Developer/php-src/Zend/zend_fibers.c:380
    #13 0x1030bfe in make_fcontext /home/ilutov/Developer/php-src/Zend/asm/make_x86_64_sysv_elf_gas.S:174

0x50b0000026c0 is located 0 bytes inside of 112-byte region [0x50b0000026c0,0x50b000002730)
freed by thread T0 here:
    #0 0x7f62b4afb3f8 in free.part.0 (/nix/store/bmi5znnqk4kg2grkrhk6py0irc8phf6l-gcc-14.2.1.20250322-lib/lib/libasan.so.8+0xfb3f8)
    #1 0x11a1a5b in tracked_free /home/ilutov/Developer/php-src/Zend/zend_alloc.c:2871
    #2 0x119fbb1 in _efree_custom /home/ilutov/Developer/php-src/Zend/zend_alloc.c:2503
    #3 0x119febe in _efree /home/ilutov/Developer/php-src/Zend/zend_alloc.c:2623
    #4 0x170b84a in zend_generator_close /home/ilutov/Developer/php-src/Zend/zend_generators.c:176
    #5 0x170e49d in zend_generator_free_storage /home/ilutov/Developer/php-src/Zend/zend_generators.c:369
    #6 0x176aa42 in zend_objects_store_del /home/ilutov/Developer/php-src/Zend/zend_objects_API.c:202
    #7 0x12a753f in rc_dtor_func /home/ilutov/Developer/php-src/Zend/zend_variables.c:57
    #8 0x12a735f in i_zval_ptr_dtor /home/ilutov/Developer/php-src/Zend/zend_variables.h:44
    #9 0x12a7985 in zval_ptr_dtor /home/ilutov/Developer/php-src/Zend/zend_variables.c:84
    #10 0x171979b in zend_generator_iterator_dtor /home/ilutov/Developer/php-src/Zend/zend_generators.c:1074
    #11 0x1698db5 in iter_wrapper_free /home/ilutov/Developer/php-src/Zend/zend_iterators.c:66
    #12 0x176aa42 in zend_objects_store_del /home/ilutov/Developer/php-src/Zend/zend_objects_API.c:202
    #13 0x12a753f in rc_dtor_func /home/ilutov/Developer/php-src/Zend/zend_variables.c:57
    #14 0x12a735f in i_zval_ptr_dtor /home/ilutov/Developer/php-src/Zend/zend_variables.h:44
    #15 0x12a7985 in zval_ptr_dtor /home/ilutov/Developer/php-src/Zend/zend_variables.c:84
    #16 0x17100f9 in zend_generator_throw_exception /home/ilutov/Developer/php-src/Zend/zend_generators.c:517
    #17 0x1718d8e in zim_Generator_throw /home/ilutov/Developer/php-src/Zend/zend_generators.c:1029
    #18 0x13da64a in ZEND_DO_FCALL_SPEC_RETVAL_UNUSED_HANDLER /home/ilutov/Developer/php-src/Zend/zend_vm_execute.h:1867
    #19 0x16678a9 in execute_ex /home/ilutov/Developer/php-src/Zend/zend_vm_execute.h:57304
    #20 0x1682cde in zend_execute /home/ilutov/Developer/php-src/Zend/zend_vm_execute.h:61660
    #21 0x12b8fb7 in zend_execute_scripts /home/ilutov/Developer/php-src/Zend/zend.c:1895
    #22 0x103fd36 in php_execute_script /home/ilutov/Developer/php-src/main/main.c:2529
    #23 0x1a16f12 in do_cli /home/ilutov/Developer/php-src/sapi/cli/php_cli.c:966
    #24 0x1a196f5 in main /home/ilutov/Developer/php-src/sapi/cli/php_cli.c:1341
    #25 0x7f62b3e2a47d in __libc_start_call_main (/nix/store/zdpby3l6azi78sl83cpad2qjpfj25aqx-glibc-2.40-66/lib/libc.so.6+0x2a47d) (BuildId: 184c6644327611cadef0a544327ebb842fceaa2c)

previously allocated by thread T0 here:
    #0 0x7f62b4afc757 in malloc (/nix/store/bmi5znnqk4kg2grkrhk6py0irc8phf6l-gcc-14.2.1.20250322-lib/lib/libasan.so.8+0xfc757)
    #1 0x11a160e in tracked_malloc /home/ilutov/Developer/php-src/Zend/zend_alloc.c:2846
    #2 0x119fa27 in _malloc_custom /home/ilutov/Developer/php-src/Zend/zend_alloc.c:2494
    #3 0x119fdf3 in _emalloc /home/ilutov/Developer/php-src/Zend/zend_alloc.c:2613
    #4 0x13de3e3 in ZEND_GENERATOR_CREATE_SPEC_HANDLER /home/ilutov/Developer/php-src/Zend/zend_vm_execute.h:2156
    #5 0x16679d2 in execute_ex /home/ilutov/Developer/php-src/Zend/zend_vm_execute.h:57316
    #6 0x124946c in zend_call_function /home/ilutov/Developer/php-src/Zend/zend_execute_API.c:961
    #7 0x124ad83 in zend_call_known_function /home/ilutov/Developer/php-src/Zend/zend_execute_API.c:1055
    #8 0x169b66c in zend_call_known_instance_method /home/ilutov/Developer/php-src/Zend/zend_API.h:853
    #9 0x169b6a6 in zend_call_known_instance_method_with_0_params /home/ilutov/Developer/php-src/Zend/zend_API.h:859
    #10 0x169d3e5 in zend_user_it_new_iterator /home/ilutov/Developer/php-src/Zend/zend_interfaces.c:92
    #11 0x169e89a in zend_user_it_get_new_iterator /home/ilutov/Developer/php-src/Zend/zend_interfaces.c:242
    #12 0x1489829 in ZEND_YIELD_FROM_SPEC_TMPVAR_HANDLER /home/ilutov/Developer/php-src/Zend/zend_vm_execute.h:14968
    #13 0x16715fc in execute_ex /home/ilutov/Developer/php-src/Zend/zend_vm_execute.h:58785
    #14 0x171572b in zend_generator_resume /home/ilutov/Developer/php-src/Zend/zend_generators.c:823
    #15 0x171660b in zend_generator_ensure_initialized /home/ilutov/Developer/php-src/Zend/zend_generators.c:879
    #16 0x1716707 in zend_generator_rewind /home/ilutov/Developer/php-src/Zend/zend_generators.c:887
    #17 0x17168d7 in zim_Generator_rewind /home/ilutov/Developer/php-src/Zend/zend_generators.c:904
    #18 0x13da64a in ZEND_DO_FCALL_SPEC_RETVAL_UNUSED_HANDLER /home/ilutov/Developer/php-src/Zend/zend_vm_execute.h:1867
    #19 0x16678a9 in execute_ex /home/ilutov/Developer/php-src/Zend/zend_vm_execute.h:57304
    #20 0x1682cde in zend_execute /home/ilutov/Developer/php-src/Zend/zend_vm_execute.h:61660
    #21 0x12b8fb7 in zend_execute_scripts /home/ilutov/Developer/php-src/Zend/zend.c:1895
    #22 0x103fd36 in php_execute_script /home/ilutov/Developer/php-src/main/main.c:2529
    #23 0x1a16f12 in do_cli /home/ilutov/Developer/php-src/sapi/cli/php_cli.c:966
    #24 0x1a196f5 in main /home/ilutov/Developer/php-src/sapi/cli/php_cli.c:1341
    #25 0x7f62b3e2a47d in __libc_start_call_main (/nix/store/zdpby3l6azi78sl83cpad2qjpfj25aqx-glibc-2.40-66/lib/libc.so.6+0x2a47d) (BuildId: 184c6644327611cadef0a544327ebb842fceaa2c)

SUMMARY: AddressSanitizer: heap-use-after-free /home/ilutov/Developer/php-src/Zend/zend_exceptions.h:85 in zend_rethrow_exception
Shadow bytes around the buggy address:
  0x50b000002400: 00 00 00 00 00 00 00 00 00 00 00 00 00 fa fa fa
  0x50b000002480: fa fa fa fa fa fa 00 00 00 00 00 00 00 00 00 00
  0x50b000002500: 00 00 00 00 fa fa fa fa fa fa fa fa 00 00 00 00
  0x50b000002580: 00 00 00 00 00 00 00 00 00 fa fa fa fa fa fa fa
  0x50b000002600: fa fa fd fd fd fd fd fd fd fd fd fd fd fd fd fa
=>0x50b000002680: fa fa fa fa fa fa fa fa[fd]fd fd fd fd fd fd fd
  0x50b000002700: fd fd fd fd fd fd fa fa fa fa fa fa fa fa fa fa
  0x50b000002780: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x50b000002800: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x50b000002880: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x50b000002900: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==74285==ABORTING

@bwoebi
Copy link
Member

bwoebi commented Jul 30, 2025

I'm saying that the problem here is that throw() is being permitted to be called regardless of ZEND_GENERATOR_CURRENTLY_RUNNING (as long as they're not actually resumed), which is a much broader bug, unlike send() and next() which fail early.

Specifically: zend_generator_throw_exception needs to check for it.

@iluuu1994
Copy link
Member

Ah ok. Yes, preventing throw() for genrators currently running should also fix this issue.

@arnaud-lb arnaud-lb marked this pull request as ready for review July 30, 2025 17:05
@arnaud-lb
Copy link
Member Author

I will send a separate PR for the throw issue then.

Copy link
Member

@bwoebi bwoebi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, then let's merge this one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Segmentation fault in zend_unfinished_execution_gc_ex found by php-execute-fuzz
3 participants