Skip to content

fix(core): bound the compact serializers' segment split - #675

Merged
Spomky merged 1 commit into
4.1.xfrom
hardening/compact-serializer-explode-limit
Aug 26, 2026
Merged

fix(core): bound the compact serializers' segment split#675
Spomky merged 1 commit into
4.1.xfrom
hardening/compact-serializer-explode-limit

Conversation

@Spomky

@Spomky Spomky commented Aug 26, 2026

Copy link
Copy Markdown
Member

What

The compact JWS and JWE serializers split the whole input on every . before checking the segment count, so a delimiter-heavy string was first expanded into one array entry per delimiter.

$parts = explode('.', $input);      // one entry per delimiter, whatever the input
if (count($parts) !== 3) {          // …only then is the shape checked
    throw new InvalidArgumentException('Unsupported input');
}

The split is now bounded to one segment more than a valid token has — 4 for JWS, 6 for JWE. That extra segment is enough to detect and reject any longer input, so the accepted and rejected inputs are unchanged: count($parts) !== 3 (resp. !== 5) still rejects everything it rejected before.

Why

Reported by Team Atlanta as a memory-exhaustion issue on JWSLoader::loadAndVerifyWithKeySet().

This is hardening rather than a vulnerability, so it is being fixed in the open with no advisory. The measured amplification is about 25×, which is the ordinary overhead of a PHP packed array and nothing more:

input explode without limit with limit
2 MB of . 2 000 001 entries, ~48 MB 4 entries, ~2 MB

Reaching a real memory limit therefore requires an application to accept multi-megabyte bearer tokens with no length cap of its own. Worth fixing because the fix is free, not because the exposure is meaningful.

The report only covered JWS; Encryption\Serializer\CompactSerializer had the same shape and is fixed alongside it.

Tests

tests/Component/{Signature,Encryption}/CompactSerializerTest.php cover the rejection of an over-long token and assert the allocation stays bounded. Both regression tests fail without the fix (33.5 MB allocated) and pass with it.

Note for anyone touching them: the bound has to be measured with memory_reset_peak_usage() + memory_get_peak_usage(). A plain memory_get_usage() delta reads as zero, because the array is freed as the exception unwinds.

Notes on the branch state

Unrelated to this change, but visible when validating it on 4.1.x:

  • The full suite has 40 pre-existing failures (Symfony bundle configuration tests, plus ComposerJsonTest diverging on the brick/math constraint). This PR adds none — checked against a stashed baseline.
  • ECS does not start at all on 4.1.x: Undefined constant SetList::PHPUNIT, hidden behind a crash in its own error printer. That was repaired on 4.2.x in Make the quality gates pass again #669 and never backported.
  • PHPStan runs and reports 42 pre-existing errors, none on the files touched here.

Reported by Team Atlanta.

@Spomky Spomky self-assigned this Aug 26, 2026
@Spomky Spomky added this to the 4.1.9 milestone Aug 26, 2026
@Spomky
Spomky force-pushed the hardening/compact-serializer-explode-limit branch from 3da88fc to c7ea9ec Compare August 26, 2026 09:02
@Spomky
Spomky changed the base branch from 4.1.x to fix/qa-tooling-4-1-x August 26, 2026 09:02
@Spomky
Spomky force-pushed the fix/qa-tooling-4-1-x branch from e96c16c to 94de666 Compare August 26, 2026 09:06
@Spomky
Spomky force-pushed the hardening/compact-serializer-explode-limit branch from c7ea9ec to c94783d Compare August 26, 2026 09:07
@Spomky
Spomky force-pushed the fix/qa-tooling-4-1-x branch from 94de666 to 9a97e9b Compare August 26, 2026 09:14
@Spomky
Spomky force-pushed the hardening/compact-serializer-explode-limit branch 4 times, most recently from e4f64a4 to 956437c Compare August 26, 2026 09:40
Base automatically changed from fix/qa-tooling-4-1-x to 4.1.x August 26, 2026 09:47
The compact JWS and JWE serializers split the whole input on every "."
before checking the segment count, so a delimiter-heavy string was first
expanded into one array entry per delimiter. That costs about 25 times
the size of the input in memory: a 2 MB token allocates ~48 MB before it
is rejected as malformed.

The split is now bounded to one more segment than a valid token has,
which is enough to detect and reject longer input while keeping the
allocation proportional to the token itself. The accepted and rejected
inputs are unchanged.

Reported by Team Atlanta.
@Spomky
Spomky force-pushed the hardening/compact-serializer-explode-limit branch from 956437c to 0e4dfbf Compare August 26, 2026 09:47
@Spomky
Spomky merged commit 13e409c into 4.1.x Aug 26, 2026
17 checks passed
@Spomky
Spomky deleted the hardening/compact-serializer-explode-limit branch August 26, 2026 09:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant