Skip to content

Saturation of ArrayObject iterator leads to UAF #23332

Description

@alexandre-daubois

Description

The following code:

<?php
$owner = new ArrayObject(['entry' => 1337]);

$iterators = [];
for ($i = 0; $i < 255; $i++) {
  $it = $owner->getIterator();
  $it->rewind();
  $iterators[] = $it;
}
unset($it);

$array = (array) $owner;

$pass = 0;
$retained = null;
foreach ($array as &$value) {
  if (++$pass === 1) {
    $retained = $array;
    $array = ['replacement' => 4242];
    continue;
  }
  for ($i = 0; $i < 254; $i++) {
    unset($iterators[$i]);
  }
  $retained = null;
  unset($iterators[254]);
}

echo "done\n";

Resulted in this output, on a debug build:

Assertion failed: ((iter->ht)->u.v.nIteratorsCount != 0),
function zend_hash_iterator_del, file zend_hash.c, line 662.

And on an ASan build run with USE_ZEND_ALLOC=0:

==2898==ERROR: AddressSanitizer: heap-use-after-free on address 0x60600001ceea at pc 0x00010503b8e8
READ of size 1 at 0x60600001ceea thread T0
  #0 0x00010503b8e4 in zend_hash_iterator_del zend_hash.c:661
  #1 0x00010503cdec in zend_hash_remove_iterator_copies zend_hash.c:530
  #2 0x00010503c1c4 in zend_hash_iterator_del zend_hash.c:668
  #3 0x000102a29830 in spl_array_object_free_storage spl_array.c:137
  #4 0x00010546e1d8 in zend_objects_store_del zend_objects_API.c:193
  [...]
  #10 0x000104aa3710 in ZEND_UNSET_DIM_SPEC_CV_CONST_TAILCALL_HANDLER zend_vm_execute.h:97037

0x60600001ceea is located 10 bytes inside of 56-byte region [0x60600001cee0,0x60600001cf18)
freed by thread T0 here:
  #0 0x00010874d258 in free+0x7c (libclang_rt.asan_osx_dynamic.dylib:arm64e+0x41258)
  #1 0x000103ca11d0 in __zend_free zend_alloc.c:3571
  #2 0x000103cab690 in _efree zend_alloc.c:2788
  #3 0x00010505e23c in zend_array_destroy zend_hash.c:1881
  #4 0x0001055a114c in rc_dtor_func zend_variables.c:56
  #5 0x000104eb8630 in zend_assign_to_variable zend_execute.h:184
  [...]

Because of the trace, we suspect this can happen without ArrayObject and that's just a "convenient" way to trigger this, but I wasn't able to create a reproducer that doesn't use ArrayObject.

PHP Version

master

Operating System

No response

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