[Git][java-team/netty][trixie] CVE-2025-67735
Bastien Roucariès (@rouca)
gitlab at salsa.debian.org
Mon Feb 9 10:24:58 GMT 2026
Bastien Roucariès pushed to branch trixie at Debian Java Maintainers / netty
Commits:
a02e7b5a by Bastien Roucariès at 2026-02-09T11:24:30+01:00
CVE-2025-67735
- - - - -
3 changed files:
- debian/changelog
- + debian/patches/CVE-2025-67735.patch
- debian/patches/series
Changes:
=====================================
debian/changelog
=====================================
@@ -33,6 +33,14 @@ netty (1:4.1.48-10+deb13u1) trixie-security; urgency=high
This bypasses standard email authentication and can
be used to impersonate executives and forge high-stakes
corporate communications.
+ * Fix CVE-2025-67735 (Closes: #1123606)
+ `io.netty.handler.codec.http.HttpRequestEncoder`
+ has a CRLF injection with the request URI when constructing
+ a request. This leads to request smuggling when
+ `HttpRequestEncoder` is used without proper sanitization
+ of the URI. Any application / framework using `HttpRequestEncoder`
+ can be subject to be abused to perform request smuggling using
+ CRLF injection
-- Bastien Roucariès <rouca at debian.org> Sat, 15 Nov 2025 10:21:34 +0100
=====================================
debian/patches/CVE-2025-67735.patch
=====================================
@@ -0,0 +1,389 @@
+From: Chris Vest <christianvest_hansen at apple.com>
+Date: Thu, 11 Dec 2025 09:20:08 -0800
+Subject: Merge commit from fork * Reject encoding of HTTP URIs that have
+ line-breaks
+MIME-Version: 1.0
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: 8bit
+
+Motivation:
+Line-breaks in user-supplied data can cause security issues like request/response splitting, request smuggling, and parser desynchronization.
+The URI was not being checked for containing line-breaks before encoding.
+
+Modification:
+When encoding the URI in HttpRequestEncoder, we now also check if it contains any line-break characters, and if so, throw an IllegalArgumentException.
+
+Result:
+Line-breaks are now being properly neutralized from the URI in HttpRequestEncoder.
+
+Unfortunately, the performance drops a bit from this check.
+
+Before:
+
+```
+Benchmark Mode Cnt Score Error Units
+HttpRequestEncoderInsertBenchmark.newEncoder thrpt 40 10169070.498 ± 27016.445 ops/s
+```
+
+Now:
+
+```
+Benchmark Mode Cnt Score Error Units
+HttpRequestEncoderInsertBenchmark.newEncoder thrpt 40 7984846.328 ± 29959.587 ops/s
+```
+
+* Move the request line encoding safety checks to DefaultHttpRequest
+
+origin: backport, https://github.com/netty/netty/commit/77e81f1e5944d98b3acf887d3aa443b252752e94
+---
+ .../handler/codec/http/DefaultFullHttpRequest.java | 10 ++-
+ .../handler/codec/http/DefaultHttpRequest.java | 16 ++++
+ .../java/io/netty/handler/codec/http/HttpUtil.java | 57 +++++++++++++-
+ .../handler/codec/http/DefaultHttpRequestTest.java | 88 +++++++++++++++++++++-
+ .../handler/codec/http/HttpRequestEncoderTest.java | 2 -
+ .../io/netty/handler/codec/http/HttpUtilTest.java | 60 ++++++++++++++-
+ 6 files changed, 222 insertions(+), 11 deletions(-)
+
+diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultFullHttpRequest.java b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultFullHttpRequest.java
+index 117e6db..d599241 100644
+--- a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultFullHttpRequest.java
++++ b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultFullHttpRequest.java
+@@ -53,7 +53,15 @@ public class DefaultFullHttpRequest extends DefaultHttpRequest implements FullHt
+
+ public DefaultFullHttpRequest(HttpVersion httpVersion, HttpMethod method, String uri,
+ ByteBuf content, HttpHeaders headers, HttpHeaders trailingHeader) {
+- super(httpVersion, method, uri, headers);
++ this(httpVersion, method, uri, content, headers, trailingHeader, true);
++ }
++
++ /**
++ * Create a full HTTP response with the given HTTP version, method, URI, contents, and header and trailer objects.
++ */
++ public DefaultFullHttpRequest(HttpVersion httpVersion, HttpMethod method, String uri,
++ ByteBuf content, HttpHeaders headers, HttpHeaders trailingHeader, boolean validateRequestLine) {
++ super(httpVersion, method, uri, headers, validateRequestLine);
+ this.content = checkNotNull(content, "content");
+ this.trailingHeader = checkNotNull(trailingHeader, "trailingHeader");
+ }
+diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpRequest.java b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpRequest.java
+index dbc7dd3..d0df1c0 100644
+--- a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpRequest.java
++++ b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpRequest.java
+@@ -61,9 +61,25 @@ public class DefaultHttpRequest extends DefaultHttpMessage implements HttpReques
+ * @param headers the Headers for this Request
+ */
+ public DefaultHttpRequest(HttpVersion httpVersion, HttpMethod method, String uri, HttpHeaders headers) {
++ this(httpVersion, method, uri, headers, true);
++ }
++
++ /**
++ * Creates a new instance.
++ *
++ * @param httpVersion the HTTP version of the request
++ * @param method the HTTP method of the request
++ * @param uri the URI or path of the request
++ * @param headers the Headers for this Request
++ */
++ public DefaultHttpRequest(HttpVersion httpVersion, HttpMethod method, String uri, HttpHeaders headers,
++ boolean validateRequestLine) {
+ super(httpVersion, headers);
+ this.method = checkNotNull(method, "method");
+ this.uri = checkNotNull(uri, "uri");
++ if (validateRequestLine) {
++ HttpUtil.validateRequestLineTokens(httpVersion, method, uri);
++ }
+ }
+
+ @Override
+diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpUtil.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpUtil.java
+index afa3ec4..512d841 100644
+--- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpUtil.java
++++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpUtil.java
+@@ -41,12 +41,13 @@ public final class HttpUtil {
+ private static final AsciiString CHARSET_EQUALS = AsciiString.of(HttpHeaderValues.CHARSET + "=");
+ private static final AsciiString SEMICOLON = AsciiString.cached(";");
+ private static final String COMMA_STRING = String.valueOf(COMMA);
++ private static final long ILLEGAL_REQUEST_LINE_TOKEN_OCTET_MASK = 1L << '\n' | 1L << '\r' | 1L << ' ';
+
+ private HttpUtil() { }
+
+ /**
+ * Determine if a uri is in origin-form according to
+- * <a href="https://tools.ietf.org/html/rfc7230#section-5.3">rfc7230, 5.3</a>.
++ * <a href="https://datatracker.ietf.org/doc/html/rfc9112#section-3.2.1">RFC 9112, 3.2.1</a>.
+ */
+ public static boolean isOriginForm(URI uri) {
+ return uri.getScheme() == null && uri.getSchemeSpecificPart() == null &&
+@@ -54,8 +55,8 @@ public final class HttpUtil {
+ }
+
+ /**
+- * Determine if a uri is in asterisk-form according to
+- * <a href="https://tools.ietf.org/html/rfc7230#section-5.3">rfc7230, 5.3</a>.
++ * Determine if a string uri is in origin-form according to
++ * <a href="https://datatracker.ietf.org/doc/html/rfc9112#section-3.2.1">RFC 9112, 3.2.1</a>.
+ */
+ public static boolean isAsteriskForm(URI uri) {
+ return "*".equals(uri.getPath()) &&
+@@ -475,6 +476,56 @@ public final class HttpUtil {
+ return null;
+ }
+
++ static void validateRequestLineTokens(HttpVersion httpVersion, HttpMethod method, String uri) {
++ // The HttpVersion class does its own validation, and it's not possible for subclasses to circumvent it.
++ // The HttpMethod class does its own validation, but subclasses might circumvent it.
++ if (method.getClass() != HttpMethod.class) {
++ if (!isEncodingSafeStartLineToken(method.asciiName())) {
++ throw new IllegalArgumentException(
++ "The HTTP method name contain illegal characters: " + method.asciiName());
++ }
++ }
++
++ if (!isEncodingSafeStartLineToken(uri)) {
++ throw new IllegalArgumentException("The URI contain illegal characters: " + uri);
++ }
++ }
++
++ /**
++ * Validate that the given request line token is safe for verbatim encoding to the network.
++ * This does not fully check that the token – HTTP method, version, or URI – is valid and formatted correctly.
++ * Only that the token does not contain characters that would break or
++ * desynchronize HTTP message parsing of the start line wherein the token would be included.
++ * <p>
++ * See <a href="https://datatracker.ietf.org/doc/html/rfc9112#name-request-line">RFC 9112, 3.</a>
++ *
++ * @param token The token to check.
++ * @return {@code true} if the token is safe to encode verbatim into the HTTP message output stream,
++ * otherwise {@code false}.
++ */
++ public static boolean isEncodingSafeStartLineToken(CharSequence token) {
++ int i = 0;
++ int lenBytes = token.length();
++ int modulo = lenBytes % 4;
++ int lenInts = modulo == 0 ? lenBytes : lenBytes - modulo;
++ for (; i < lenInts; i += 4) {
++ long chars = 1L << token.charAt(i) |
++ 1L << token.charAt(i + 1) |
++ 1L << token.charAt(i + 2) |
++ 1L << token.charAt(i + 3);
++ if ((chars & ILLEGAL_REQUEST_LINE_TOKEN_OCTET_MASK) != 0) {
++ return false;
++ }
++ }
++ for (; i < lenBytes; i++) {
++ long ch = 1L << token.charAt(i);
++ if ((ch & ILLEGAL_REQUEST_LINE_TOKEN_OCTET_MASK) != 0) {
++ return false;
++ }
++ }
++ return true;
++ }
++
+ /**
+ * Fetch MIME type part from message's Content-Type header as a char sequence.
+ *
+diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/DefaultHttpRequestTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/DefaultHttpRequestTest.java
+index cf0fa92..7f6e1a1 100644
+--- a/codec-http/src/test/java/io/netty/handler/codec/http/DefaultHttpRequestTest.java
++++ b/codec-http/src/test/java/io/netty/handler/codec/http/DefaultHttpRequestTest.java
+@@ -22,8 +22,94 @@ import static io.netty.handler.codec.http.HttpHeadersTestUtils.of;
+ import static org.junit.Assert.assertNull;
+ import static org.junit.Assert.assertTrue;
+
+-public class DefaultHttpRequestTest {
++import org.junit.runner.RunWith;
++import org.junit.runners.Parameterized;
++import org.junit.runners.Parameterized.Parameters;
++
++import java.util.Arrays;
++import java.util.Collection;
++
++import static org.junit.Assert.fail;
++
++ at RunWith(Parameterized.class)
++class DefaultHttpRequestIllegalMethodTest {
++
++ private final String method;
++
++ public DefaultHttpRequestIllegalMethodTest(String method) {
++ this.method = method;
++ }
++
++ @Parameters(name = "{index}: method={0}")
++ public static Collection<Object[]> data() {
++ return Arrays.asList(new Object[][] {
++ {"GET "}, {" GET"}, {"G ET"}, {" GET "}, {"GET\r"}, {"GET\n"},
++ {"GET\r\n"}, {"GE\rT"}, {"GE\nT"}, {"GE\r\nT"}, {"\rGET"},
++ {"\nGET"}, {"\r\nGET"}, {" \r\nGET"}, {"\r \nGET"}, {"\r\n GET"},
++ {"\r\nGET "}, {"\nGET "}, {"\rGET "}, {"\r GET"}, {" \rGET"},
++ {"\nGET "}, {"\n GET"}, {" \nGET"}, {"GET \n"}, {"GET \r"},
++ {" GET\r"}, {" GET\r"}, {"GET \n"}, {" GET\n"}, {" GET\n"},
++ {"GE\nT "}, {"GE\rT "}, {" GE\rT"}, {" GE\rT"}, {"GE\nT "},
++ {" GE\nT"}, {" GE\nT"}
++ });
++ }
++
++ @Test
++ public void constructorMustRejectIllegalHttpMethodByDefault() {
++ try {
++ new DefaultHttpRequest(HttpVersion.HTTP_1_0, new HttpMethod("GET") {
++ public AsciiString asciiName() {
++ return new AsciiString(method);
++ }
++ }, "/");
++ fail("Expected IllegalArgumentException for method: " + method);
++ } catch (IllegalArgumentException e) {
++ // Expected exception
++ }
++ }
++}
+
++ at RunWith(Parameterized.class)
++class DefaultHttpRequestIllegalUriTest {
++
++ private final String uri;
++
++ public DefaultHttpRequestIllegalUriTest(String uri) {
++ this.uri = uri;
++ }
++
++ @Parameters(name = "{index}: uri={0}")
++ public static Collection<Object[]> data() {
++ return Arrays.asList(new Object[][] {
++ {"http://localhost/\r\n"},
++ {"/r\r\n?q=1"},
++ {"http://localhost/\r\n?q=1"},
++ {"/r\r\n/?q=1"},
++ {"http://localhost/\r\n/?q=1"},
++ {"/r\r\n"},
++ {"http://localhost/ HTTP/1.1\r\n\r\nPOST /p HTTP/1.1\r\n\r\n"},
++ {"/r HTTP/1.1\r\n\r\nPOST /p HTTP/1.1\r\n\r\n"},
++ {"/ path"},
++ {"/path "},
++ {" /path"},
++ {"http://localhost/ "},
++ {" http://localhost/"},
++ {"http://local host/"}
++ });
++ }
++
++ @Test
++ public void constructorMustRejectIllegalUrisByDefault() {
++ try {
++ new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uri);
++ fail("Expected IllegalArgumentException for URI: " + uri);
++ } catch (IllegalArgumentException e) {
++ // Expected exception
++ }
++ }
++}
++
++public class DefaultHttpRequestTest {
+ @Test
+ public void testHeaderRemoval() {
+ HttpMessage m = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/");
+diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestEncoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestEncoderTest.java
+index 2f866f7..7008b4b 100644
+--- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestEncoderTest.java
++++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestEncoderTest.java
+@@ -32,8 +32,6 @@ import static org.hamcrest.Matchers.instanceOf;
+ import static org.hamcrest.Matchers.is;
+ import static org.junit.Assert.*;
+
+-/**
+- */
+ public class HttpRequestEncoderTest {
+
+ @SuppressWarnings("deprecation")
+diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpUtilTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpUtilTest.java
+index 186b498..332f57f 100644
+--- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpUtilTest.java
++++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpUtilTest.java
+@@ -15,17 +15,22 @@
+ */
+ package io.netty.handler.codec.http;
+
++import io.netty.util.CharsetUtil;
++import io.netty.util.ReferenceCountUtil;
++import io.netty.handler.codec.http.HttpHeaders;
+ import java.net.InetAddress;
+ import java.net.InetSocketAddress;
++import java.net.URI;
+ import java.nio.charset.StandardCharsets;
+ import java.util.ArrayList;
++import java.util.Arrays;
++import java.util.Collection;
+ import java.util.Collections;
+ import java.util.List;
+-
+-import io.netty.util.CharsetUtil;
+-import io.netty.util.ReferenceCountUtil;
++import org.junit.runner.RunWith;
++import org.junit.runners.Parameterized;
++import org.junit.runners.Parameterized.Parameters;
+ import org.junit.Test;
+-
+ import static io.netty.handler.codec.http.HttpHeadersTestUtils.of;
+ import static org.junit.Assert.assertEquals;
+ import static org.junit.Assert.assertFalse;
+@@ -33,6 +38,41 @@ import static org.junit.Assert.assertNull;
+ import static org.junit.Assert.assertTrue;
+ import static org.junit.Assert.fail;
+
++ at RunWith(Parameterized.class)
++class HttpUtilRequestLineTokenValidationTest {
++
++ private final String token;
++
++ public HttpUtilRequestLineTokenValidationTest(String token) {
++ this.token = token;
++ }
++
++ @Parameters(name = "{index}: token={0}")
++ public static Collection<Object[]> data() {
++ return Arrays.asList(new Object[][] {
++ {"http://localhost/\r\n"},
++ {"/r\r\n?q=1"},
++ {"http://localhost/\r\n?q=1"},
++ {"/r\r\n/?q=1"},
++ {"http://localhost/\r\n/?q=1"},
++ {"/r\r\n"},
++ {"http://localhost/ HTTP/1.1\r\n\r\nPOST /p HTTP/1.1\r\n\r\n"},
++ {"/r HTTP/1.1\r\n\r\nPOST /p HTTP/1.1\r\n\r\n"},
++ {"GET "},
++ {" GET"},
++ {"HTTP/ 1.1"},
++ {"HTTP/\r0.9"},
++ {"HTTP/\n1.1"}
++ });
++ }
++
++ @Test
++ public void requestLineTokenValidationMustRejectInvalidTokens() throws Exception {
++ assertFalse(HttpUtil.isEncodingSafeStartLineToken(token));
++ }
++}
++
++
+ public class HttpUtilTest {
+
+ @Test
+@@ -59,6 +99,18 @@ public class HttpUtilTest {
+ assertEquals("2", values.get(1));
+ }
+
++ @Test
++ public void testRecognizesAsteriskForm() {
++ // Asterisk form: https://tools.ietf.org/html/rfc7230#section-5.3.4
++ assertTrue(HttpUtil.isAsteriskForm(URI.create("*")));
++ // Origin form: https://tools.ietf.org/html/rfc7230#section-5.3.1
++ assertFalse(HttpUtil.isAsteriskForm(URI.create("/where?q=now")));
++ // Absolute form: https://tools.ietf.org/html/rfc7230#section-5.3.2
++ assertFalse(HttpUtil.isAsteriskForm(URI.create("http://www.example.org/pub/WWW/TheProject.html")));
++ // Authority form: https://tools.ietf.org/html/rfc7230#section-5.3.3
++ assertFalse(HttpUtil.isAsteriskForm(URI.create("www.example.com:80")));
++ }
++
+ @Test
+ public void testGetCharsetAsRawCharSequence() {
+ String QUOTES_CHARSET_CONTENT_TYPE = "text/html; charset=\"utf8\"";
=====================================
debian/patches/series
=====================================
@@ -31,3 +31,4 @@ CVE-2025-55163_1.patch
CVE-2025-55163_2.patch
CVE-2025-58057.patch
CVE-2025-58056.patch
+CVE-2025-67735.patch
View it on GitLab: https://salsa.debian.org/java-team/netty/-/commit/a02e7b5a4fc7531cb32ab2b35df4a3f2ba826a34
--
View it on GitLab: https://salsa.debian.org/java-team/netty/-/commit/a02e7b5a4fc7531cb32ab2b35df4a3f2ba826a34
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-java-commits/attachments/20260209/382c6959/attachment.htm>
More information about the pkg-java-commits
mailing list