[php-src] Issue #24493: 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)

From: Date: Wed, 21 Oct 2026 18:13:56 +0000
Subject: [php-src] Issue #24493: 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)
Groups: php.bugs 
Request: Send a blank email to php-bugs+get-253060@lists.php.net to get a copy of this message
Issue: https://github.com/php/php-src/issues/24493 Author: VijhnAustin ### Description 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): 1. A session stores an object with one of those property names — e.g. from a caller-controlled key: ```php <?php session_start(); $o = new stdClass(); $o->{'__PHP_Incomplete_Class_Name'} = 'boo'; // e.g. $_SESSION['x'] = json_decode($userInput) $_SESSION['x'] = $o; ``` 2. 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 ```plain 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)

« previous php.bugs (#253060) next »