[Mime][RateLimiter][Routing][Security] Harden __unserialize against __toString trampolines#64343
Merged
nicolas-grekas merged 1 commit intoMay 23, 2026
symfony:6.4symfony/symfony:6.4from
nicolas-grekas:harden-unserialize-trampolinesnicolas-grekas/symfony:harden-unserialize-trampolinesCopy head branch name to clipboard
Merged
[Mime][RateLimiter][Routing][Security] Harden __unserialize against __toString trampolines#64343nicolas-grekas merged 1 commit intosymfony:6.4symfony/symfony:6.4from nicolas-grekas:harden-unserialize-trampolinesnicolas-grekas/symfony:harden-unserialize-trampolinesCopy head branch name to clipboard
nicolas-grekas merged 1 commit into
symfony:6.4symfony/symfony:6.4from
nicolas-grekas:harden-unserialize-trampolinesnicolas-grekas/symfony:harden-unserialize-trampolinesCopy head branch name to clipboard
Conversation
…_toString trampolines
1ffdba4 to
dbf3c45
Compare
nicolas-grekas
added a commit
that referenced
this pull request
May 23, 2026
…uring __unserialize (nicolas-grekas) This PR was merged into the 7.4 branch. Discussion ---------- [Mime][String] Reject objects in typed-string properties during __unserialize | Q | A | ------------- | --- | Branch? | 7.4 | Bug fix? | yes | New feature? | no | Deprecations? | no | Issues | - | License | MIT Same class of issue as #64343 on 6.4: a `__unserialize(array $data)` that assigns `$data[...]` to a typed `string` (or union-with-`string`) property lets PHP's type coercion invoke `__toString()` on any attacker-supplied object before the value is stored. A post-assignment `is_string()` guard runs too late. This PR covers the surface that exists on 7.4 and is not addressed in #64343: | Class | Typed-string slots assigned from raw `$data` | | ---------------------- | -------------------------------------------------------------------------------------------------- | | `String\UnicodeString` | `string $string` | | `Mime\Part\TextPart` | `?string $charset`, `string $subtype`, `?string $disposition`, `?string $name`, `string $encoding` | | `Mime\Part\SMimePart` | `iterable\|string $body`, `string $type`, `string $subtype` | Union types that include `string` (e.g. `iterable|string`) also trigger `__toString()` coercion; for `SMimePart::$body` the guard is `instanceof \Stringable && !is_iterable($body)` since PHP prefers the iterable branch when an object satisfies both. Each `__unserialize()` now validates the shape of the raw `$data` before any assignment, throwing `BadMethodCallException` on mismatch. `UnicodeString`'s existing post-check is moved to run before the assignment; `TextPart` and `SMimePart` gain explicit pre-checks for every typed-string slot (in every key-prefix variant: bare, `\0Class\0...`, and `\0*\0...`). The 6.4 hardening for `Route`, `CompiledRoute`, `Window`, `SlidingWindow`, `TokenBucket`, and `Email` lands on 7.4 via the upmerge. Commits ------- d64ccd7 [String][Mime] Reject objects in typed-string properties during __unserialize
This was referenced May 27, 2026
Merged
Merged
Merged
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reject objects in typed-string properties during
__unserializeWhen a class declares an
__unserialize(array $data)that assigns$data[...]to a typedstring(or union-with-string) property, PHP's type coercion invokes__toString()on any object placed in that slot before the value is stored. A post-assignmentis_string()guard runs too late: the gadget has already fired.The following 6.4 classes have that shape:
$dataRouting\Routestring $path,string $host,string $conditionRouting\CompiledRoutestring $staticPrefix,string $regex,?string $hostRegexRateLimiter\Policy\Windowstring $id(BC path)RateLimiter\Policy\SlidingWindowstring $id(BC path)RateLimiter\Policy\TokenBucketstring $id(BC path)Mime\Email?string $textCharset,?string $htmlCharsetTyped
int/float/boolproperties are not exploitable in the same way: PHP rejects object-to-scalar coercion for those with aTypeErrorwithout calling__toString().Each
__unserialize()now validates the shape of the raw$databefore any assignment, throwingBadMethodCallExceptionon mismatch.Drop the legacy
Serializableinterface fromRoute,CompiledRoute,AbstractToken33969158d0 ("Remove Serializable implementations", 2021) kept
\Serializableon these three classes so existing payloads using the legacyC:...:"ClassName":...wire format could still be read;serialize()was made to throw so no new such payloads were written.The retained
unserialize($serialized)calls plainunserialize()on the inner payload with noallowed_classesrestriction. A forgedC:N:"Route":...(orAbstractTokensubclass) payload bypasses the trampoline guard above by instantiating arbitrary classes via the nested call, firing any__wakeup/__destruct/ typed-property assignment side-effect their code may have.The supported upgrade path is 3.4 -> 4.4 -> 5.4 -> 6.4 with cache and session refreshes along the way, so legacy
C:payloads are no longer expected in any storage. Following the same rationale as #64195, this PR dropsimplements \Serializableand the two legacy methods from each class. The corresponding v4 fixture test inSecurity/Core/Tests/Role/LegacyRoleTest.phpis removed.