You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
unserialize() teardown should reset freed var entries before running destructors #24532
Resulted in an AddressSanitizer heap-use-after-free in php_var_unserialize(). var_destroy() frees the extended var_entries chain before releasing values that may invoke destructors, but leaves last and entries.next pointing to the freed chain; as a hardening measure, the context should be reset to its embedded entries block before invoking userland code.
The write side of this reproduces standalone on stock 8.2.35 (NTS, unpatched) — a ~20 line payload, no harness: the re-entrant var_push() write lands at a wild index derived from the reused var_entries chunk and the process dies with SIGSEGV (1016 elements clean, 1017 fatal, with USE_ZEND_ALLOC=0).
I put the reproducer and a proposed regression test for the write path in a comment on #24538, since gh24532.phpt covers the lifetime side and the re-entrant write is the part that turns this into memory corruption rather than a crash.
One thing worth having in the scoping: there is a remote delivery path for this that needs no gadget class — two unauthenticated requests via session.upload_progress.enabled=1 plus session.serialize_handler=php_serialize, where the multipart payload is written into the session store verbatim and is then unserialised by any endpoint that reads session data. php_serialize is the default in several frameworks. I can share the request/response capture.
The ASAN trace, the trigger-threshold sweep across 8.5.12 / 8.4.27 / 8.3.36 / 8.2.35 / 7.4.33, and the gdb capture of the free, the re-entrant read and the wild write are in my comments on #24481.
Activity
alexandre-daubois commentedon Oct 22, 2026
cc @DanielEScherzer
omaraltamimi1 commentedon Oct 22, 2026
The write side of this reproduces standalone on stock 8.2.35 (NTS, unpatched) — a ~20 line payload, no harness: the re-entrant
var_push()write lands at a wild index derived from the reusedvar_entrieschunk and the process dies with SIGSEGV (1016 elements clean, 1017 fatal, withUSE_ZEND_ALLOC=0).I put the reproducer and a proposed regression test for the write path in a comment on #24538, since
gh24532.phptcovers the lifetime side and the re-entrant write is the part that turns this into memory corruption rather than a crash.One thing worth having in the scoping: there is a remote delivery path for this that needs no gadget class — two unauthenticated requests via
session.upload_progress.enabled=1plussession.serialize_handler=php_serialize, where the multipart payload is written into the session store verbatim and is then unserialised by any endpoint that reads session data.php_serializeis the default in several frameworks. I can share the request/response capture.The ASAN trace, the trigger-threshold sweep across 8.5.12 / 8.4.27 / 8.3.36 / 8.2.35 / 7.4.33, and the gdb capture of the free, the re-entrant read and the wild write are in my comments on #24481.