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
8.5.12 upload-progress handler destroys the whole session when a stored object has a property named __PHP_Incomplete_Class_Name/_Serialized/__PHP_Incomplete_Enum_Case (regression) #24493
Since commit 31c0ea7d ("Avoid state corruption via bailout", CVE-2026-109618/109619), the pre-RINIT upload-progress session decoder runs in incomplete_classes_only mode and rejects any object property named __PHP_Incomplete_Class_Name, __PHP_Incomplete_Class_Serialized, or __PHP_Incomplete_Enum_Case (is_incomplete_class_reserved_member() in ext/standard/var_unserializer.re). The rejection is fail-closed via goto failure, which aborts the entire decode, so the whole session file is deleted ("Failed to decode session object. Session has been destroyed"). These are legal dynamic property names that PHP's own serialize() / session_encode() can emit. In 8.5.11 the same session round-trips unchanged.
Repro (default session.upload_progress.*; the session record is produced by session_encode(), not crafted bytes):
A session stores an object with one of those property names — e.g. from a caller-controlled key:
Any later multipart POST that carries the PHPSESSID field and the PHP_SESSION_UPLOAD_PROGRESS field triggers the pre-RINIT handler for that session.
Resulted in this output (8.5.12 / 8.4.27):
Warning: PHP Request Startup: Failed to decode session object. Session has been destroyed in Unknown on line 0
…and the session file is deleted.
But I expected this output instead (the 8.5.11 behavior): no warning, the session file kept byte-identical.
A/B over the three reserved names + a control property (a), records built by session_encode():
case
8.5.11
8.5.12
control (a)
kept
kept
__PHP_Incomplete_Class_Name
kept (66 B)
destroyed
__PHP_Incomplete_Class_Serialized
kept (72 B)
destroyed
__PHP_Incomplete_Enum_Case
kept (65 B)
destroyed
Mechanism: in process_nested_object_data(), when (*var_hash)->incomplete_classes_only is set and the key matches a reserved member, the code does zval_ptr_dtor(&key); goto failure; — aborting the whole record rather than dropping just that key. Rejecting the reserved names is necessary (they collide with the mode's own MAGIC_MEMBER* bookkeeping), but the blast radius could be narrowed: skip only the offending property (or mangle the key on write, like other internal-key handling) and keep the rest of the session, plus a regression test asserting the record survives.
Impact: forced session loss (logout / data loss) and log noise for apps that store object properties under caller-controlled names; otherwise self-inflicted. No memory-safety, confidentiality, or integrity impact — low severity, filed as a functional regression.
PHP Version
PHP 8.5.12 (cli) (built: Oct 20 2026 04:19:56) (NTS Visual C++ 2022 x64)
Copyright (c) The PHP Group
Built by The PHP Group
Zend Engine v4.5.12, Copyright (c) Zend Technologies
Operating System
Windows 11 (code path is OS-independent — ext/standard/var_unserializer.re)
Activity
Fix phpGH-24493: Upload progress destroys session on reserved propert…