Skip to content

Bump symfony/http-foundation from 8.1.0 to 8.1.10 - #1680

Closed
superbot-helper wants to merge 0 commit into
symfony:mainfrom
superbot-helper:security/bump-symfony-http-foundation-8.1.10
Closed

superbot-helper wants to merge 0 commit into
symfony:mainfrom
superbot-helper:security/bump-symfony-http-foundation-8.1.10

Conversation

@superbot-helper

Copy link
Copy Markdown

Updates symfony/http-foundation from 8.1.0 to 8.1.10.

Fixes GHSA-r925-mgvr-h9cc — HeaderUtils::split() compiles a pattern whose token branch [^",=]+ also matches whitespace, so a run of whitespace before a separator can be divided among the iterations of the enclosing + in exponentially many ways and the trailing (?<!\s) assertion makes the engine try every one of them before it settles, while HeaderBag::set() runs that split on the Cache-Control header as a Request is built: one unauthenticated GET / whose 15,200-byte Cache-Control header carries 800 a + 17 spaces + , runs held the application's own listener for a median 799.7 ms (748.6-1227.9 ms) against a median 25.1 ms (18.3-51.0 ms) for the same 15,200 bytes with 17 x characters in place of each space run, a 7,999-byte version of the same pair cost a median 429.4 ms against 22.5 ms, and with 8.1.10 the crafted headers cost a median 19.9 ms and 22.8 ms, the same as their controls.

What this changes:

  • composer.lock — composer.lock:4098: symfony/http-foundation v8.1.0 -> v8.1.10 (the only package entry that moves; composer.json is unchanged).

How to verify:

composer validate --no-check-publish -> ./composer.json is valid
composer install --no-scripts from the patched lock -> vendor/symfony/http-foundation v8.1.10 (v8.1.0 from the base lock)
APP_ENV=dev APP_DEBUG=0 php -S 127.0.0.1:45180 -t public public/index.php -> the application's own listener answers GET / with 200 on both trees
send a GET / whose 15,200-byte Cache-Control header carries 800 `a` + 17 spaces + `,` runs -> 748.6-1227.9 ms at base, 18.5-34.4 ms with this change
send the same 15,200 bytes with 17 `x` characters in place of each 17-space run -> 18.3-51.0 ms on both trees (the control)

Measured before and after, same request, same server

Both trees are this repository at 8d2e2ef7; the only difference is composer.lock, which moves symfony/http-foundation v8.1.0 to v8.1.10 (diff -r --exclude=vendor reports only composer.lock, and inside vendor only that package's version differs). Every row is one raw GET / to the application's own php -S listener started from that tree's own public/index.php, five samples per row, three runs per tree, alternating trees between runs.

request base http-foundation 8.1.0 this PR 8.1.10
crafted: 15,200 B Cache-Control, 800 a+17 spaces+, runs 799.7 ms median (748.6-1227.9), HTTP 200 19.9 ms median (18.5-34.4), HTTP 200
control: 15,200 B, 17 x where each 17-space run was 25.1 ms median (18.3-51.0), HTTP 200 24.1 ms median (19.6-83.2), HTTP 200
crafted: 7,999 B, 421 runs 429.4 ms median (411.6-742.5), HTTP 200 22.8 ms median (18.2-40.9), HTTP 200
control: 7,999 B, 421 runs 22.5 ms median (18.8-34.1), HTTP 200 21.4 ms median (18.0-53.8), HTTP 200
baseline: Cache-Control: max-age=3600 25.5 ms median (19.2-107.6), HTTP 200 20.9 ms median (17.7-71.8), HTTP 200

The control is byte-for-byte the same length and the same directive shape as the crafted header; the only difference is that each run of 17 spaces is 17 x characters, so no whitespace can be handed back. On the base tree the crafted row is about 32x its control and the control matches the baseline; with 8.1.10 the crafted row costs the same as its control. Every row returns 200 on both trees, so the fix removes work and does not change the route or the response.

Absolute timings depend on machine load (the same rows moved by up to about 1.6x between runs); the ordering between the crafted row and its same-byte-count control does not.

Where the cost is

Request::createFromGlobals() (vendor/symfony/http-foundation/Request.php:328) builds a HeaderBag from the request headers (Request.php:310); HeaderBag::set() parses the Cache-Control header on entry (HeaderBag.php:150-151) through HeaderBag::parseCacheControl() (HeaderBag.php:267), which calls HeaderUtils::split($header, ',=') (HeaderUtils.php:45). That function compiles a pattern whose token branch [^",=]+ also matches whitespace under a + repetition with a trailing (?<!\s) assertion (HeaderUtils.php:53-63), so a whitespace run in front of a separator can be divided among the iterations in exponentially many ways and the engine tries all of them before the assertion settles.

The fix (8.1.7, with the whitespace-separator regression corrected in 8.1.9) makes the token branch consume whitespace only when more of the value follows and makes every quantifier possessive, so no character is given back. The advisory's own regression test splits "a" . str_repeat(" ", 25) . "," repeated 200 times; this packet sends the same shape at 17 spaces per run, the largest run that stays under pcre.backtrack_limit (18 spaces exhausts it and the split silently returns nothing).

Scope

composer.lock: symfony/http-foundation v8.1.0 -> v8.1.10, one package entry (source and dist references, release time). composer.json is unchanged because it already requires symfony/framework-bundle: ^8, which allows the patched release; no application source, template, asset, config or test changes.
The repository's other Symfony packages stay at v8.1.0; 8.1.10 satisfies framework-bundle v8.1.0's symfony/http-foundation ^8.1 constraint, and composer install from the patched lock resolves the whole tree with exit 0.

What this does not claim

Only the 8.1.0 -> 8.1.10 move was measured. The advisory also covers 6.4.x below 6.4.46, 7.x below 7.4.19 and 5.4.x below 5.4.54; those branches were not measured here and this PR does not speak for them.
The measurements are single-request costs on one listener with five samples per row; the packet does not claim a concurrent-request or sustained-throughput number.
Absolute timings depend on machine load; only the ordering between a crafted row and its same-byte-count control is claimed.

Crafted and same-byte-count control Cache-Control headers were measured through the application's own listener on both trees, and the control isolates the whitespace-run cost.

Automated security patch from Superbot.

@javiereguiluz
javiereguiluz force-pushed the security/bump-symfony-http-foundation-8.1.10 branch from a1419fd to f42980c Compare November 6, 2026 09:26
@javiereguiluz

Copy link
Copy Markdown
Member

Thanks for this contribution. Sadly, the changes included here were part of other PR that was merged earlier. That's why GitHub closed it when merging it. Sorry!

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.

2 participants