Skip to content

Commit 07aa6b5

Browse files
authored
Merge pull request from GHSA-wx5j-54mm-rqqq
Motivation: We should validate that only OWS is allowed before / after a header name and otherwise throw. At the moment we just "strip" everything except OWS. Modifications: - Adjust code to correctly validate - Add unit tests Result: More strict and correct behaviour
1 parent d80a34e commit 07aa6b5

File tree

4 files changed

+171
-10
lines changed

4 files changed

+171
-10
lines changed

Diff for: codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java

+8
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,10 @@ public HttpHeaders copy() {
367367

368368
private static void validateHeaderNameElement(byte value) {
369369
switch (value) {
370+
case 0x1c:
371+
case 0x1d:
372+
case 0x1e:
373+
case 0x1f:
370374
case 0x00:
371375
case '\t':
372376
case '\n':
@@ -391,6 +395,10 @@ private static void validateHeaderNameElement(byte value) {
391395

392396
private static void validateHeaderNameElement(char value) {
393397
switch (value) {
398+
case 0x1c:
399+
case 0x1d:
400+
case 0x1e:
401+
case 0x1f:
394402
case 0x00:
395403
case '\t':
396404
case '\n':

Diff for: codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -824,7 +824,7 @@ private void splitHeader(AppendableCharSequence sb) {
824824
int valueStart;
825825
int valueEnd;
826826

827-
nameStart = findNonWhitespace(sb, 0, false);
827+
nameStart = findNonWhitespace(sb, 0);
828828
for (nameEnd = nameStart; nameEnd < length; nameEnd ++) {
829829
char ch = sb.charAtUnsafe(nameEnd);
830830
// https://tools.ietf.org/html/rfc7230#section-3.2.4
@@ -859,7 +859,7 @@ private void splitHeader(AppendableCharSequence sb) {
859859
}
860860

861861
name = sb.subStringUnsafe(nameStart, nameEnd);
862-
valueStart = findNonWhitespace(sb, colonEnd, true);
862+
valueStart = findNonWhitespace(sb, colonEnd);
863863
if (valueStart == length) {
864864
value = EMPTY_VALUE;
865865
} else {
@@ -898,12 +898,12 @@ private static boolean isSPLenient(char c) {
898898
return c == ' ' || c == (char) 0x09 || c == (char) 0x0B || c == (char) 0x0C || c == (char) 0x0D;
899899
}
900900

901-
private static int findNonWhitespace(AppendableCharSequence sb, int offset, boolean validateOWS) {
901+
private static int findNonWhitespace(AppendableCharSequence sb, int offset) {
902902
for (int result = offset; result < sb.length(); ++result) {
903903
char c = sb.charAtUnsafe(result);
904904
if (!Character.isWhitespace(c)) {
905905
return result;
906-
} else if (validateOWS && !isOWS(c)) {
906+
} else if (!isOWS(c)) {
907907
// Only OWS is supported for whitespace
908908
throw new IllegalArgumentException("Invalid separator, only a single space or horizontal tab allowed," +
909909
" but received a '" + c + "' (0x" + Integer.toHexString(c) + ")");

Diff for: codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java

+81-6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package io.netty.handler.codec.http;
1717

18+
import io.netty.buffer.ByteBuf;
1819
import io.netty.buffer.Unpooled;
1920
import io.netty.channel.embedded.EmbeddedChannel;
2021
import io.netty.handler.codec.TooLongFrameException;
@@ -357,6 +358,75 @@ public void testTooLargeHeaders() {
357358
assertFalse(channel.finish());
358359
}
359360

361+
@Test
362+
public void testHeaderNameStartsWithControlChar1c() {
363+
testHeaderNameStartsWithControlChar(0x1c);
364+
}
365+
366+
@Test
367+
public void testHeaderNameStartsWithControlChar1d() {
368+
testHeaderNameStartsWithControlChar(0x1d);
369+
}
370+
371+
@Test
372+
public void testHeaderNameStartsWithControlChar1e() {
373+
testHeaderNameStartsWithControlChar(0x1e);
374+
}
375+
376+
@Test
377+
public void testHeaderNameStartsWithControlChar1f() {
378+
testHeaderNameStartsWithControlChar(0x1f);
379+
}
380+
381+
@Test
382+
public void testHeaderNameStartsWithControlChar0c() {
383+
testHeaderNameStartsWithControlChar(0x0c);
384+
}
385+
386+
private void testHeaderNameStartsWithControlChar(int controlChar) {
387+
ByteBuf requestBuffer = Unpooled.buffer();
388+
requestBuffer.writeCharSequence("GET /some/path HTTP/1.1\r\n" +
389+
"Host: netty.io\r\n", CharsetUtil.US_ASCII);
390+
requestBuffer.writeByte(controlChar);
391+
requestBuffer.writeCharSequence("Transfer-Encoding: chunked\r\n\r\n", CharsetUtil.US_ASCII);
392+
testInvalidHeaders0(requestBuffer);
393+
}
394+
395+
@Test
396+
public void testHeaderNameEndsWithControlChar1c() {
397+
testHeaderNameEndsWithControlChar(0x1c);
398+
}
399+
400+
@Test
401+
public void testHeaderNameEndsWithControlChar1d() {
402+
testHeaderNameEndsWithControlChar(0x1d);
403+
}
404+
405+
@Test
406+
public void testHeaderNameEndsWithControlChar1e() {
407+
testHeaderNameEndsWithControlChar(0x1e);
408+
}
409+
410+
@Test
411+
public void testHeaderNameEndsWithControlChar1f() {
412+
testHeaderNameEndsWithControlChar(0x1f);
413+
}
414+
415+
@Test
416+
public void testHeaderNameEndsWithControlChar0c() {
417+
testHeaderNameEndsWithControlChar(0x0c);
418+
}
419+
420+
private void testHeaderNameEndsWithControlChar(int controlChar) {
421+
ByteBuf requestBuffer = Unpooled.buffer();
422+
requestBuffer.writeCharSequence("GET /some/path HTTP/1.1\r\n" +
423+
"Host: netty.io\r\n", CharsetUtil.US_ASCII);
424+
requestBuffer.writeCharSequence("Transfer-Encoding", CharsetUtil.US_ASCII);
425+
requestBuffer.writeByte(controlChar);
426+
requestBuffer.writeCharSequence(": chunked\r\n\r\n", CharsetUtil.US_ASCII);
427+
testInvalidHeaders0(requestBuffer);
428+
}
429+
360430
@Test
361431
public void testWhitespace() {
362432
String requestStr = "GET /some/path HTTP/1.1\r\n" +
@@ -366,19 +436,19 @@ public void testWhitespace() {
366436
}
367437

368438
@Test
369-
public void testWhitespaceBeforeTransferEncoding01() {
439+
public void testWhitespaceInTransferEncoding01() {
370440
String requestStr = "GET /some/path HTTP/1.1\r\n" +
371-
" Transfer-Encoding : chunked\r\n" +
441+
"Transfer-Encoding : chunked\r\n" +
372442
"Content-Length: 1\r\n" +
373443
"Host: netty.io\r\n\r\n" +
374444
"a";
375445
testInvalidHeaders0(requestStr);
376446
}
377447

378448
@Test
379-
public void testWhitespaceBeforeTransferEncoding02() {
449+
public void testWhitespaceInTransferEncoding02() {
380450
String requestStr = "POST / HTTP/1.1" +
381-
" Transfer-Encoding : chunked\r\n" +
451+
"Transfer-Encoding : chunked\r\n" +
382452
"Host: target.com" +
383453
"Content-Length: 65\r\n\r\n" +
384454
"0\r\n\r\n" +
@@ -475,6 +545,7 @@ public void testContentLengthHeaderAndChunked() {
475545
assertTrue(request.headers().contains("Transfer-Encoding", "chunked", false));
476546
assertFalse(request.headers().contains("Content-Length"));
477547
LastHttpContent c = channel.readInbound();
548+
c.release();
478549
assertFalse(channel.finish());
479550
}
480551

@@ -499,11 +570,15 @@ public void testHttpMessageDecoderResult() {
499570
}
500571

501572
private static void testInvalidHeaders0(String requestStr) {
573+
testInvalidHeaders0(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII));
574+
}
575+
576+
private static void testInvalidHeaders0(ByteBuf requestBuffer) {
502577
EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder());
503-
assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII)));
578+
assertTrue(channel.writeInbound(requestBuffer));
504579
HttpRequest request = channel.readInbound();
580+
assertThat(request.decoderResult().cause(), instanceOf(IllegalArgumentException.class));
505581
assertTrue(request.decoderResult().isFailure());
506-
assertTrue(request.decoderResult().cause() instanceof IllegalArgumentException);
507582
assertFalse(channel.finish());
508583
}
509584
}

Diff for: codec-http/src/test/java/io/netty/handler/codec/http/HttpResponseDecoderTest.java

+78
Original file line numberDiff line numberDiff line change
@@ -799,4 +799,82 @@ public void testHttpMessageDecoderResult() {
799799
c.release();
800800
assertFalse(channel.finish());
801801
}
802+
803+
@Test
804+
public void testHeaderNameStartsWithControlChar1c() {
805+
testHeaderNameStartsWithControlChar(0x1c);
806+
}
807+
808+
@Test
809+
public void testHeaderNameStartsWithControlChar1d() {
810+
testHeaderNameStartsWithControlChar(0x1d);
811+
}
812+
813+
@Test
814+
public void testHeaderNameStartsWithControlChar1e() {
815+
testHeaderNameStartsWithControlChar(0x1e);
816+
}
817+
818+
@Test
819+
public void testHeaderNameStartsWithControlChar1f() {
820+
testHeaderNameStartsWithControlChar(0x1f);
821+
}
822+
823+
@Test
824+
public void testHeaderNameStartsWithControlChar0c() {
825+
testHeaderNameStartsWithControlChar(0x0c);
826+
}
827+
828+
private void testHeaderNameStartsWithControlChar(int controlChar) {
829+
ByteBuf responseBuffer = Unpooled.buffer();
830+
responseBuffer.writeCharSequence("HTTP/1.1 200 OK\r\n" +
831+
"Host: netty.io\r\n", CharsetUtil.US_ASCII);
832+
responseBuffer.writeByte(controlChar);
833+
responseBuffer.writeCharSequence("Transfer-Encoding: chunked\r\n\r\n", CharsetUtil.US_ASCII);
834+
testInvalidHeaders0(responseBuffer);
835+
}
836+
837+
@Test
838+
public void testHeaderNameEndsWithControlChar1c() {
839+
testHeaderNameEndsWithControlChar(0x1c);
840+
}
841+
842+
@Test
843+
public void testHeaderNameEndsWithControlChar1d() {
844+
testHeaderNameEndsWithControlChar(0x1d);
845+
}
846+
847+
@Test
848+
public void testHeaderNameEndsWithControlChar1e() {
849+
testHeaderNameEndsWithControlChar(0x1e);
850+
}
851+
852+
@Test
853+
public void testHeaderNameEndsWithControlChar1f() {
854+
testHeaderNameEndsWithControlChar(0x1f);
855+
}
856+
857+
@Test
858+
public void testHeaderNameEndsWithControlChar0c() {
859+
testHeaderNameEndsWithControlChar(0x0c);
860+
}
861+
862+
private void testHeaderNameEndsWithControlChar(int controlChar) {
863+
ByteBuf responseBuffer = Unpooled.buffer();
864+
responseBuffer.writeCharSequence("HTTP/1.1 200 OK\r\n" +
865+
"Host: netty.io\r\n", CharsetUtil.US_ASCII);
866+
responseBuffer.writeCharSequence("Transfer-Encoding", CharsetUtil.US_ASCII);
867+
responseBuffer.writeByte(controlChar);
868+
responseBuffer.writeCharSequence(": chunked\r\n\r\n", CharsetUtil.US_ASCII);
869+
testInvalidHeaders0(responseBuffer);
870+
}
871+
872+
private static void testInvalidHeaders0(ByteBuf responseBuffer) {
873+
EmbeddedChannel channel = new EmbeddedChannel(new HttpResponseDecoder());
874+
assertTrue(channel.writeInbound(responseBuffer));
875+
HttpResponse response = channel.readInbound();
876+
assertThat(response.decoderResult().cause(), instanceOf(IllegalArgumentException.class));
877+
assertTrue(response.decoderResult().isFailure());
878+
assertFalse(channel.finish());
879+
}
802880
}

0 commit comments

Comments
 (0)