CVE-2022-41915: Improper Neutralization of CRLF Sequences in HTTP Headers ('HTTP Request/Response Splitting') (CWE-113)
value: Netty project is an event-driven asynchronous network application framework. Starting in version 4.1.83.Final and prior to 4.1.86.Final, when calling `DefaultHttpHeadesr.set` with an _iterator_ of values, header value validation was not performed, allowing malicious header values in the iterator to perform HTTP Response Splitting. This issue has been patched in version 4.1.86.Final. Integrators can work around the issue by changing the `DefaultHttpHeaders.set(CharSequence, Iterator<?>)` call, into a `remove()` call, and call `add()` in a loop over the iterator of values.
CWE description: The product receives data from an HTTP agent/component (e.g., web server, proxy, browser, etc.), but it does not neutralize or incorrectly neutralizes CR and LF characters before the data is included in outgoing HTTP headers.
CVSS v3 base score: 6.5
Published: 2022-12-13T07:15Z
Repository: https://github.com/netty/netty
Language: Java
Files changed: codec-http/src/test/java/io/netty/handler/codec/http/DefaultHttpHeadersTest.java, codec/src/main/java/io/netty/handler/codec/DefaultHeaders.java
Commit date: 2022-12-12 14:04:58 +0100
Commit message: Merge pull request from GHSA-hh82-3pmq-7frp

Motivation:
The setObject methods that took arrays and iterators as arguments provided a way to bypass value validation.

Modification:
Add the missing value validation checks.

Result:
It is no longer possibel to bypass value validation in DefaultHeaders based implementations, including DefaultHttpHeaders.
Diff: diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/DefaultHttpHeadersTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/DefaultHttpHeadersTest.java
index 2c175978b89..4cd1beedfe3 100644
--- a/codec-http/src/test/java/io/netty/handler/codec/http/DefaultHttpHeadersTest.java
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/DefaultHttpHeadersTest.java
@@ -20,11 +20,11 @@ import io.netty.util.AsciiString;
 import io.netty.util.internal.EmptyArrays;
 import io.netty.util.internal.StringUtil;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.function.Executable;
 
-import java.util.Arrays;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 
 import static io.netty.handler.codec.http.HttpHeadersTestUtils.of;
@@ -39,10 +39,11 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class DefaultHttpHeadersTest {
     private static final CharSequence HEADER_NAME = "testHeader";
+    private static final CharSequence ILLEGAL_VALUE = "testHeader\r\nContent-Length:45\r\n\r\n";
 
     @Test
     public void nullHeaderNameNotAllowed() {
         assertThrows(IllegalArgumentException.class, new Executable() {
             @Override
@@ -232,10 +233,32 @@ public class DefaultHttpHeadersTest {
         final DefaultHttpHeaders headers = newDefaultDefaultHttpHeaders();
         headers.set(HEADER_NAME, HeaderValue.THREE.asList());
         assertDefaultValues(headers, HeaderValue.THREE);
     }
 
+    @Test
+    public void setCharSequenceValidatesValue() {
+        final DefaultHttpHeaders headers = newDefaultDefaultHttpHeaders();
+        assertThrows(IllegalArgumentException.class, new Executable() {
+            @Override
+            public void execute() throws Throwable {
+                headers.set(HEADER_NAME, ILLEGAL_VALUE);
+            }
+        });
+    }
+
+    @Test
+    public void setIterableValidatesValue() {
+        final DefaultHttpHeaders headers = newDefaultDefaultHttpHeaders();
+        assertThrows(IllegalArgumentException.class, new Executable() {
+            @Override
+            public void execute() throws Throwable {
+                headers.set(HEADER_NAME, Collections.singleton(ILLEGAL_VALUE));
+            }
+        });
+    }
+
     @Test
     public void toStringOnEmptyHeaders() {
         assertEquals("DefaultHttpHeaders[]", newDefaultDefaultHttpHeaders().toString());
     }
 
diff --git a/codec/src/main/java/io/netty/handler/codec/DefaultHeaders.java b/codec/src/main/java/io/netty/handler/codec/DefaultHeaders.java
index ae62bdffbcb..6abe71be0e2 100644
--- a/codec/src/main/java/io/netty/handler/codec/DefaultHeaders.java
+++ b/codec/src/main/java/io/netty/handler/codec/DefaultHeaders.java
@@ -530,11 +530,13 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
         remove0(h, i, name);
         for (Object v: values) {
             if (v == null) {
                 break;
             }
-            add0(h, i, name, fromObject(name, v));
+            V converted = fromObject(name, v);
+            validateValue(valueValidator, name, converted);
+            add0(h, i, name, converted);
         }
 
         return thisT();
     }
 
@@ -548,11 +550,13 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
         remove0(h, i, name);
         for (Object v: values) {
             if (v == null) {
                 break;
             }
-            add0(h, i, name, fromObject(name, v));
+            V converted = fromObject(name, v);
+            validateValue(valueValidator, name, converted);
+            add0(h, i, name, converted);
         }
 
         return thisT();
     }
 

Vulnerable code: import java.util.Arrays;
            add0(h, i, name, fromObject(name, v));
            add0(h, i, name, fromObject(name, v));
Fixed code: import java.util.Collections;
    private static final CharSequence ILLEGAL_VALUE = "testHeader\r\nContent-Length:45\r\n\r\n";
    @Test
    public void setCharSequenceValidatesValue() {
        final DefaultHttpHeaders headers = newDefaultDefaultHttpHeaders();
        assertThrows(IllegalArgumentException.class, new Executable() {
            @Override
            public void execute() throws Throwable {
                headers.set(HEADER_NAME, ILLEGAL_VALUE);
            }
        });
    }

    @Test
    public void setIterableValidatesValue() {
        final DefaultHttpHeaders headers = newDefaultDefaultHttpHeaders();
        assertThrows(IllegalArgumentException.class, new Executable() {
            @Override
            public void execute() throws Throwable {
                headers.set(HEADER_NAME, Collections.singleton(ILLEGAL_VALUE));
            }
        });
    }

            V converted = fromObject(name, v);
            validateValue(valueValidator, name, converted);
            add0(h, i, name, converted);
            V converted = fromObject(name, v);
            validateValue(valueValidator, name, converted);
            add0(h, i, name, converted);