Package: release.debian.org Severity: normal Tags: trixie X-Debbugs-Cc: [email protected] Control: affects -1 + src:php-guzzlehttp-psr7 User: [email protected] Usertags: pu
Hi, As agreed with the security team, I’d like to fix CVE-2026-55766 in the point release since it doesn’t deserve a DSA. [ Checklist ] [x] *all* changes are documented in the d/changelog [x] I reviewed all changes and I approve them [x] attach debdiff against the package in (old)stable [x] the issue is verified as fixed in unstable Thanks in advance for considering. Regards, taffit
diff -Nru php-guzzlehttp-psr7-2.7.1/debian/changelog php-guzzlehttp-psr7-2.7.1/debian/changelog --- php-guzzlehttp-psr7-2.7.1/debian/changelog 2026-05-30 13:39:01.000000000 +0200 +++ php-guzzlehttp-psr7-2.7.1/debian/changelog 2026-06-19 08:13:30.000000000 +0200 @@ -1,3 +1,10 @@ +php-guzzlehttp-psr7 (2.7.1-1+deb13u2) trixie; urgency=medium + + * Reject CR/LF in HTTP method, protocol version, and reason phrase + (GHSA-vm85-hxw5-5432) [CVE-2026-55766] + + -- David Prévot <[email protected]> Fri, 19 Jun 2026 08:13:30 +0200 + php-guzzlehttp-psr7 (2.7.1-1+deb13u1) trixie; urgency=medium * Backport fixes from upstream diff -Nru php-guzzlehttp-psr7-2.7.1/debian/patches/0004-Modernize-PHPUnit-syntax.patch php-guzzlehttp-psr7-2.7.1/debian/patches/0004-Modernize-PHPUnit-syntax.patch --- php-guzzlehttp-psr7-2.7.1/debian/patches/0004-Modernize-PHPUnit-syntax.patch 2026-05-30 13:39:01.000000000 +0200 +++ php-guzzlehttp-psr7-2.7.1/debian/patches/0004-Modernize-PHPUnit-syntax.patch 2026-06-19 08:13:30.000000000 +0200 @@ -9,8 +9,8 @@ tests/MessageTest.php | 9 +++------ tests/PumpStreamTest.php | 5 ++--- tests/QueryTest.php | 9 +++------ - tests/RequestTest.php | 21 ++++++--------------- - tests/ResponseTest.php | 21 +++++++-------------- + tests/RequestTest.php | 37 ++++++++++--------------------------- + tests/ResponseTest.php | 37 +++++++++++-------------------------- tests/ServerRequestTest.php | 13 ++++--------- tests/StreamDecoratorTraitTest.php | 5 ++--- tests/StreamTest.php | 12 ++++-------- @@ -20,7 +20,7 @@ tests/UriResolverTest.php | 13 ++++--------- tests/UriTest.php | 25 +++++++------------------ tests/UtilsTest.php | 9 ++++----- - 17 files changed, 66 insertions(+), 138 deletions(-) + 17 files changed, 74 insertions(+), 162 deletions(-) diff --git a/tests/AppendStreamTest.php b/tests/AppendStreamTest.php index 302470a..b0c6a2e 100644 @@ -113,7 +113,7 @@ { self::assertSame($result, Psr7\Header::splitList($header)); diff --git a/tests/MessageTest.php b/tests/MessageTest.php -index 42909be..4df397a 100644 +index 1862a95..31f3bb1 100644 --- a/tests/MessageTest.php +++ b/tests/MessageTest.php @@ -6,6 +6,7 @@ namespace GuzzleHttp\Tests\Psr7; @@ -204,7 +204,7 @@ { $result = Psr7\Query::parse($input, false); diff --git a/tests/RequestTest.php b/tests/RequestTest.php -index 3a62c57..0ea5069 100644 +index 9acc022..ec5c44b 100644 --- a/tests/RequestTest.php +++ b/tests/RequestTest.php @@ -7,6 +7,7 @@ namespace GuzzleHttp\Tests\Psr7; @@ -236,7 +236,51 @@ public function testWithInvalidMethods($method): void { $r = new Request('get', '/'); -@@ -198,9 +195,7 @@ class RequestTest extends TestCase +@@ -125,9 +122,7 @@ class RequestTest extends TestCase + ]; + } + +- /** +- * @dataProvider startLineSeparatorProvider +- */ ++ #[DataProvider('startLineSeparatorProvider')] + public function testConstructWithLineSeparatorsInMethod(string $lineSeparator): void + { + $this->expectException(\InvalidArgumentException::class); +@@ -135,9 +130,7 @@ class RequestTest extends TestCase + new Request('GET'.$lineSeparator.'X-Injected: yes', '/'); + } + +- /** +- * @dataProvider startLineSeparatorProvider +- */ ++ #[DataProvider('startLineSeparatorProvider')] + public function testWithMethodRejectsLineSeparators(string $lineSeparator): void + { + $r = new Request('GET', '/'); +@@ -147,9 +140,7 @@ class RequestTest extends TestCase + $r->withMethod('GET'.$lineSeparator.'X-Injected: yes'); + } + +- /** +- * @dataProvider startLineSeparatorProvider +- */ ++ #[DataProvider('startLineSeparatorProvider')] + public function testConstructWithLineSeparatorsInProtocolVersion(string $lineSeparator): void + { + $this->expectException(\InvalidArgumentException::class); +@@ -157,9 +148,7 @@ class RequestTest extends TestCase + new Request('GET', '/', [], null, '1.1'.$lineSeparator.'X-Injected: yes'); + } + +- /** +- * @dataProvider startLineSeparatorProvider +- */ ++ #[DataProvider('startLineSeparatorProvider')] + public function testWithProtocolVersionRejectsLineSeparators(string $lineSeparator): void + { + $r = new Request('GET', '/'); +@@ -249,9 +238,7 @@ class RequestTest extends TestCase self::assertSame('', $r->getHeaderLine('Bar')); } @@ -247,7 +291,7 @@ public function testContainsNotAllowedCharsOnHeaderField($header): void { $this->expectExceptionMessage( -@@ -223,9 +218,7 @@ class RequestTest extends TestCase +@@ -274,9 +261,7 @@ class RequestTest extends TestCase return [[' key '], ['key '], [' key'], ['key/'], ['key('], ['key\\'], [' ']]; } @@ -258,7 +302,7 @@ public function testContainsAllowedCharsOnHeaderField($header): void { $r = new Request( -@@ -329,9 +322,7 @@ class RequestTest extends TestCase +@@ -380,9 +365,7 @@ class RequestTest extends TestCase self::assertSame('example.com:8080', $request->getHeaderLine('Host')); } @@ -270,7 +314,7 @@ { $this->expectException(\InvalidArgumentException::class); diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php -index 70d3ecc..2ce74e4 100644 +index d15d726..c05eb9e 100644 --- a/tests/ResponseTest.php +++ b/tests/ResponseTest.php @@ -6,6 +6,7 @@ namespace GuzzleHttp\Tests\Psr7; @@ -281,7 +325,51 @@ use PHPUnit\Framework\TestCase; use Psr\Http\Message\StreamInterface; -@@ -253,9 +254,7 @@ class ResponseTest extends TestCase +@@ -135,9 +136,7 @@ class ResponseTest extends TestCase + self::assertSame('1000', $r->getProtocolVersion()); + } + +- /** +- * @dataProvider startLineSeparatorProvider +- */ ++ #[DataProvider('startLineSeparatorProvider')] + public function testConstructWithLineSeparatorsInProtocolVersion(string $lineSeparator): void + { + $this->expectException(\InvalidArgumentException::class); +@@ -145,9 +144,7 @@ class ResponseTest extends TestCase + new Response(200, [], null, '1.1'.$lineSeparator.'X-Injected: yes'); + } + +- /** +- * @dataProvider startLineSeparatorProvider +- */ ++ #[DataProvider('startLineSeparatorProvider')] + public function testWithProtocolVersionRejectsLineSeparators(string $lineSeparator): void + { + $r = new Response(); +@@ -157,9 +154,7 @@ class ResponseTest extends TestCase + $r->withProtocolVersion('1.1'.$lineSeparator.'X-Injected: yes'); + } + +- /** +- * @dataProvider startLineSeparatorProvider +- */ ++ #[DataProvider('startLineSeparatorProvider')] + public function testConstructWithLineSeparatorsInReasonPhrase(string $lineSeparator): void + { + $this->expectException(\InvalidArgumentException::class); +@@ -167,9 +162,7 @@ class ResponseTest extends TestCase + new Response(200, [], null, '1.1', 'OK'.$lineSeparator.'X-Injected: yes'); + } + +- /** +- * @dataProvider startLineSeparatorProvider +- */ ++ #[DataProvider('startLineSeparatorProvider')] + public function testWithStatusRejectsLineSeparatorsInReasonPhrase(string $lineSeparator): void + { + $r = new Response(); +@@ -304,9 +297,7 @@ class ResponseTest extends TestCase self::assertSame('bar', $r->getHeaderLine('123')); } @@ -292,7 +380,7 @@ public function testConstructResponseInvalidHeader($header, $headerValue, $expectedMessage): void { $this->expectException(\InvalidArgumentException::class); -@@ -272,9 +271,7 @@ class ResponseTest extends TestCase +@@ -323,9 +314,7 @@ class ResponseTest extends TestCase ]; } @@ -303,7 +391,7 @@ public function testWithInvalidHeader($header, $headerValue, $expectedMessage): void { $r = new Response(); -@@ -323,10 +320,9 @@ class ResponseTest extends TestCase +@@ -374,10 +363,9 @@ class ResponseTest extends TestCase } /** @@ -315,7 +403,7 @@ public function testConstructResponseWithNonIntegerStatusCode($invalidValues): void { $this->expectException(\TypeError::class); -@@ -334,10 +330,9 @@ class ResponseTest extends TestCase +@@ -385,10 +373,9 @@ class ResponseTest extends TestCase } /** @@ -327,7 +415,7 @@ public function testResponseChangeStatusCodeWithNonInteger($invalidValues): void { $response = new Response(); -@@ -357,10 +352,9 @@ class ResponseTest extends TestCase +@@ -408,10 +395,9 @@ class ResponseTest extends TestCase } /** @@ -339,7 +427,7 @@ public function testConstructResponseWithInvalidRangeStatusCode($invalidValues): void { $this->expectException(\InvalidArgumentException::class); -@@ -369,10 +363,9 @@ class ResponseTest extends TestCase +@@ -420,10 +406,9 @@ class ResponseTest extends TestCase } /** diff -Nru php-guzzlehttp-psr7-2.7.1/debian/patches/0009-Mitigate-CRLF-Injection-in-HTTP-Start-Line-Serializa.patch php-guzzlehttp-psr7-2.7.1/debian/patches/0009-Mitigate-CRLF-Injection-in-HTTP-Start-Line-Serializa.patch --- php-guzzlehttp-psr7-2.7.1/debian/patches/0009-Mitigate-CRLF-Injection-in-HTTP-Start-Line-Serializa.patch 1970-01-01 01:00:00.000000000 +0100 +++ php-guzzlehttp-psr7-2.7.1/debian/patches/0009-Mitigate-CRLF-Injection-in-HTTP-Start-Line-Serializa.patch 2026-06-19 08:13:30.000000000 +0200 @@ -0,0 +1,297 @@ +From: Graham Campbell <[email protected]> +Date: Thu, 18 Jun 2026 10:47:13 +0100 +Subject: Mitigate CRLF Injection in HTTP Start-Line Serialization (#798) + +Origin: backport, https://github.com/guzzle/psr7/commit/f3f94b4d344fa9788afb9558ac032923a9767380 +Bug: https://github.com/guzzle/psr7/security/advisories/GHSA-vm85-hxw5-5432 +Bug-Debian: https://security-tracker.debian.org/tracker/CVE-2026-55766 +--- + src/Message.php | 8 ++++++++ + src/MessageTrait.php | 19 +++++++++++++++++++ + src/Request.php | 4 ++++ + src/Response.php | 12 +++++++++--- + tests/MessageTest.php | 14 ++++++++++++++ + tests/RequestTest.php | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++ + tests/ResponseTest.php | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++ + 7 files changed, 156 insertions(+), 3 deletions(-) + +diff --git a/src/Message.php b/src/Message.php +index d3aafca..3bd88eb 100644 +--- a/src/Message.php ++++ b/src/Message.php +@@ -219,6 +219,10 @@ final class Message + public static function parseRequest(string $message): RequestInterface + { + $data = self::parseMessage($message); ++ if (strpbrk($data['start-line'], "\r\n") !== false) { ++ throw new \InvalidArgumentException('Invalid request string'); ++ } ++ + $matches = []; + if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) { + throw new \InvalidArgumentException('Invalid request string'); +@@ -245,6 +249,10 @@ final class Message + public static function parseResponse(string $message): ResponseInterface + { + $data = self::parseMessage($message); ++ if (strpbrk($data['start-line'], "\r\n") !== false) { ++ throw new \InvalidArgumentException('Invalid response string'); ++ } ++ + // According to https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2 + // the space between status-code and reason-phrase is required. But + // browsers accept responses without space and reason as well. +diff --git a/src/MessageTrait.php b/src/MessageTrait.php +index 65dbc4b..8341ddb 100644 +--- a/src/MessageTrait.php ++++ b/src/MessageTrait.php +@@ -31,6 +31,8 @@ trait MessageTrait + + public function withProtocolVersion($version): MessageInterface + { ++ $this->assertProtocolVersion($version); ++ + if ($this->protocol === $version) { + return $this; + } +@@ -233,6 +235,23 @@ trait MessageTrait + } + } + ++ /** ++ * @param mixed $version ++ */ ++ private function assertProtocolVersion($version): void ++ { ++ if (is_string($version)) { ++ $this->assertNoLineSeparators($version, 'Protocol version'); ++ } ++ } ++ ++ private function assertNoLineSeparators(string $value, string $field): void ++ { ++ if (strpbrk($value, "\r\n") !== false) { ++ throw new \InvalidArgumentException($field.' must not contain CR or LF characters.'); ++ } ++ } ++ + /** + * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2 + * +diff --git a/src/Request.php b/src/Request.php +index 743c5d2..bae3349 100644 +--- a/src/Request.php ++++ b/src/Request.php +@@ -40,6 +40,8 @@ class Request implements RequestInterface + string $version = '1.1' + ) { + $this->assertMethod($method); ++ $this->assertProtocolVersion($version); ++ + if (!($uri instanceof UriInterface)) { + $uri = new Uri($uri); + } +@@ -159,5 +161,7 @@ class Request implements RequestInterface + if (!is_string($method) || $method === '') { + throw new InvalidArgumentException('Method must be a non-empty string.'); + } ++ ++ $this->assertNoLineSeparators($method, 'Method'); + } + } +diff --git a/src/Response.php b/src/Response.php +index 34e612f..1f10b67 100644 +--- a/src/Response.php ++++ b/src/Response.php +@@ -99,6 +99,7 @@ class Response implements ResponseInterface + ?string $reason = null + ) { + $this->assertStatusCodeRange($status); ++ $this->assertProtocolVersion($version); + + $this->statusCode = $status; + +@@ -108,11 +109,14 @@ class Response implements ResponseInterface + + $this->setHeaders($headers); + if ($reason == '' && isset(self::PHRASES[$this->statusCode])) { +- $this->reasonPhrase = self::PHRASES[$this->statusCode]; ++ $reasonPhrase = self::PHRASES[$this->statusCode]; + } else { +- $this->reasonPhrase = (string) $reason; ++ $reasonPhrase = (string) $reason; + } + ++ $this->assertNoLineSeparators($reasonPhrase, 'Reason phrase'); ++ $this->reasonPhrase = $reasonPhrase; ++ + $this->protocol = $version; + } + +@@ -137,7 +141,9 @@ class Response implements ResponseInterface + if ($reasonPhrase == '' && isset(self::PHRASES[$new->statusCode])) { + $reasonPhrase = self::PHRASES[$new->statusCode]; + } +- $new->reasonPhrase = (string) $reasonPhrase; ++ $reasonPhrase = (string) $reasonPhrase; ++ $this->assertNoLineSeparators($reasonPhrase, 'Reason phrase'); ++ $new->reasonPhrase = $reasonPhrase; + + return $new; + } +diff --git a/tests/MessageTest.php b/tests/MessageTest.php +index 42909be..1862a95 100644 +--- a/tests/MessageTest.php ++++ b/tests/MessageTest.php +@@ -231,6 +231,13 @@ class MessageTest extends TestCase + Psr7\Message::parseRequest("HTTP/1.1 200 OK\r\n\r\n"); + } + ++ public function testParseRequestRejectsStartLineWithBareCarriageReturn(): void ++ { ++ $this->expectException(\InvalidArgumentException::class); ++ ++ Psr7\Message::parseRequest("GET / HTTP/1.1\rX-Injected: yes\nHost: foo.com\n\n"); ++ } ++ + public function testParsesResponseMessages(): void + { + $res = "HTTP/1.0 200 OK\r\nFoo: Bar\r\nBaz: Bam\r\nBaz: Qux\r\n\r\nTest"; +@@ -309,6 +316,13 @@ class MessageTest extends TestCase + Psr7\Message::parseResponse("GET / HTTP/1.1\r\n\r\n"); + } + ++ public function testParseResponseRejectsStartLineWithBareCarriageReturn(): void ++ { ++ $this->expectException(\InvalidArgumentException::class); ++ ++ Psr7\Message::parseResponse("HTTP/1.1 200 OK\rX-Injected: yes\n\n"); ++ } ++ + public function testMessageBodySummaryWithSmallBody(): void + { + $message = new Psr7\Response(200, [], 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'); +diff --git a/tests/RequestTest.php b/tests/RequestTest.php +index 3a62c57..9acc022 100644 +--- a/tests/RequestTest.php ++++ b/tests/RequestTest.php +@@ -125,6 +125,57 @@ class RequestTest extends TestCase + ]; + } + ++ /** ++ * @dataProvider startLineSeparatorProvider ++ */ ++ public function testConstructWithLineSeparatorsInMethod(string $lineSeparator): void ++ { ++ $this->expectException(\InvalidArgumentException::class); ++ ++ new Request('GET'.$lineSeparator.'X-Injected: yes', '/'); ++ } ++ ++ /** ++ * @dataProvider startLineSeparatorProvider ++ */ ++ public function testWithMethodRejectsLineSeparators(string $lineSeparator): void ++ { ++ $r = new Request('GET', '/'); ++ ++ $this->expectException(\InvalidArgumentException::class); ++ ++ $r->withMethod('GET'.$lineSeparator.'X-Injected: yes'); ++ } ++ ++ /** ++ * @dataProvider startLineSeparatorProvider ++ */ ++ public function testConstructWithLineSeparatorsInProtocolVersion(string $lineSeparator): void ++ { ++ $this->expectException(\InvalidArgumentException::class); ++ ++ new Request('GET', '/', [], null, '1.1'.$lineSeparator.'X-Injected: yes'); ++ } ++ ++ /** ++ * @dataProvider startLineSeparatorProvider ++ */ ++ public function testWithProtocolVersionRejectsLineSeparators(string $lineSeparator): void ++ { ++ $r = new Request('GET', '/'); ++ ++ $this->expectException(\InvalidArgumentException::class); ++ ++ $r->withProtocolVersion('1.1'.$lineSeparator.'X-Injected: yes'); ++ } ++ ++ public static function startLineSeparatorProvider(): iterable ++ { ++ yield 'line feed' => ["\n"]; ++ yield 'carriage return' => ["\r"]; ++ yield 'CRLF' => ["\r\n"]; ++ } ++ + public function testSameInstanceWhenSameUri(): void + { + $r1 = new Request('GET', 'http://foo.com'); +diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php +index 70d3ecc..d15d726 100644 +--- a/tests/ResponseTest.php ++++ b/tests/ResponseTest.php +@@ -135,6 +135,57 @@ class ResponseTest extends TestCase + self::assertSame('1000', $r->getProtocolVersion()); + } + ++ /** ++ * @dataProvider startLineSeparatorProvider ++ */ ++ public function testConstructWithLineSeparatorsInProtocolVersion(string $lineSeparator): void ++ { ++ $this->expectException(\InvalidArgumentException::class); ++ ++ new Response(200, [], null, '1.1'.$lineSeparator.'X-Injected: yes'); ++ } ++ ++ /** ++ * @dataProvider startLineSeparatorProvider ++ */ ++ public function testWithProtocolVersionRejectsLineSeparators(string $lineSeparator): void ++ { ++ $r = new Response(); ++ ++ $this->expectException(\InvalidArgumentException::class); ++ ++ $r->withProtocolVersion('1.1'.$lineSeparator.'X-Injected: yes'); ++ } ++ ++ /** ++ * @dataProvider startLineSeparatorProvider ++ */ ++ public function testConstructWithLineSeparatorsInReasonPhrase(string $lineSeparator): void ++ { ++ $this->expectException(\InvalidArgumentException::class); ++ ++ new Response(200, [], null, '1.1', 'OK'.$lineSeparator.'X-Injected: yes'); ++ } ++ ++ /** ++ * @dataProvider startLineSeparatorProvider ++ */ ++ public function testWithStatusRejectsLineSeparatorsInReasonPhrase(string $lineSeparator): void ++ { ++ $r = new Response(); ++ ++ $this->expectException(\InvalidArgumentException::class); ++ ++ $r->withStatus(200, 'OK'.$lineSeparator.'X-Injected: yes'); ++ } ++ ++ public static function startLineSeparatorProvider(): iterable ++ { ++ yield 'line feed' => ["\n"]; ++ yield 'carriage return' => ["\r"]; ++ yield 'CRLF' => ["\r\n"]; ++ } ++ + public function testSameInstanceWhenSameProtocol(): void + { + $r = new Response(); diff -Nru php-guzzlehttp-psr7-2.7.1/debian/patches/series php-guzzlehttp-psr7-2.7.1/debian/patches/series --- php-guzzlehttp-psr7-2.7.1/debian/patches/series 2026-05-30 13:39:01.000000000 +0200 +++ php-guzzlehttp-psr7-2.7.1/debian/patches/series 2026-06-19 08:13:30.000000000 +0200 @@ -6,4 +6,5 @@ 0006-Normalize-global-header-values-718.patch 0007-Reject-malformed-Host-authorities-717.patch 0008-Reject-control-characters-in-URI-hosts-715.patch +0009-Mitigate-CRLF-Injection-in-HTTP-Start-Line-Serializa.patch 0004-Modernize-PHPUnit-syntax.patch
signature.asc
Description: PGP signature

