Skip to content

Commit

Permalink
Requests with invalid content-length should always be rejected
Browse files Browse the repository at this point in the history
  • Loading branch information
markt-asf committed Oct 3, 2022
1 parent 3e31c41 commit 4c7f4fd
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 13 deletions.
42 changes: 29 additions & 13 deletions java/org/apache/coyote/http11/Http11InputBuffer.java
Expand Up @@ -919,7 +919,7 @@ private HeaderParseStatus parseHeader() throws IOException {
headerData.lastSignificantChar = pos;
byteBuffer.position(byteBuffer.position() - 1);
// skipLine() will handle the error
return skipLine();
return skipLine(false);
}

// chr is next byte of header name. Convert to lowercase.
Expand All @@ -930,7 +930,7 @@ private HeaderParseStatus parseHeader() throws IOException {

// Skip the line and ignore the header
if (headerParsePos == HeaderParsePosition.HEADER_SKIPLINE) {
return skipLine();
return skipLine(false);
}

//
Expand Down Expand Up @@ -987,15 +987,11 @@ private HeaderParseStatus parseHeader() throws IOException {
// CRLF or LF is an acceptable line terminator
eol = true;
} else if (prevChr == Constants.CR) {
// Invalid value
// Delete the header (it will be the most recent one)
headers.removeHeader(headers.size() - 1);
return skipLine();
// Invalid value - also need to delete header
return skipLine(true);
} else if (chr != Constants.HT && HttpParser.isControl(chr)) {
// Invalid value
// Delete the header (it will be the most recent one)
headers.removeHeader(headers.size() - 1);
return skipLine();
// Invalid value - also need to delete header
return skipLine(true);
} else if (chr == Constants.SP || chr == Constants.HT) {
byteBuffer.put(headerData.realPos, chr);
headerData.realPos++;
Expand Down Expand Up @@ -1043,7 +1039,27 @@ private HeaderParseStatus parseHeader() throws IOException {
}


private HeaderParseStatus skipLine() throws IOException {
private HeaderParseStatus skipLine(boolean deleteHeader) throws IOException {
boolean rejectThisHeader = rejectIllegalHeader;
// Check if rejectIllegalHeader is disabled and needs to be overridden
// for this header. The header name is required to determine if this
// override is required. The header name is only available once the
// header has been created. If the header has been created then
// deleteHeader will be true.
if (!rejectThisHeader && deleteHeader) {
if (headers.getName(headers.size() - 1).equalsIgnoreCase("content-length")) {
// Malformed content-length headers must always be rejected
// RFC 9112, section 6.3, bullet 5.
rejectThisHeader = true;
} else {
// Only need to delete the header if the request isn't going to
// be rejected (it will be the most recent one)
headers.removeHeader(headers.size() - 1);
}
}

// Parse the rest of the invalid header so we can construct a useful
// exception and/or debug message.
headerParsePos = HeaderParsePosition.HEADER_SKIPLINE;
boolean eol = false;

Expand All @@ -1069,11 +1085,11 @@ private HeaderParseStatus skipLine() throws IOException {
headerData.lastSignificantChar = pos;
}
}
if (rejectIllegalHeader || log.isDebugEnabled()) {
if (rejectThisHeader || log.isDebugEnabled()) {
String message = sm.getString("iib.invalidheader",
HeaderUtil.toPrintableString(byteBuffer.array(), headerData.lineStart,
headerData.lastSignificantChar - headerData.lineStart + 1));
if (rejectIllegalHeader) {
if (rejectThisHeader) {
throw new IllegalArgumentException(message);
}
log.debug(message);
Expand Down
31 changes: 31 additions & 0 deletions test/org/apache/coyote/http11/TestHttp11InputBuffer.java
Expand Up @@ -709,6 +709,37 @@ public void testInvalidHeader01() {
}


@Test
public void testInvalidContentLength01() {
doTestInvalidContentLength(false);
}


@Test
public void testInvalidContentLength02() {
doTestInvalidContentLength(true);
}


private void doTestInvalidContentLength(boolean rejectIllegalHeader) {
getTomcatInstance().getConnector().setProperty("rejectIllegalHeader", Boolean.toString(rejectIllegalHeader));

String[] request = new String[1];
request[0] =
"POST /test HTTP/1.1" + CRLF +
"Host: localhost:8080" + CRLF +
"Content-Length: 12\u000734" + CRLF +
"Connection: close" + CRLF +
CRLF;

InvalidClient client = new InvalidClient(request);

client.doRequest();
Assert.assertTrue(client.getResponseLine(), client.isResponse400());
Assert.assertTrue(client.isResponseBodyOK());
}


/**
* Invalid request test client.
*/
Expand Down
5 changes: 5 additions & 0 deletions webapps/docs/changelog.xml
Expand Up @@ -124,6 +124,11 @@
<bug>66281</bug>: Fix unexpected timeouts that may appear as client
disconnections when using HTTP/2 and NIO2. (markt)
</fix>
<fix>
Enforce the requirement of RFC 7230 onwards that a request with a
malformed <code>content-length</code> header should always be rejected
with a 400 response. (markt)
</fix>
</changelog>
</subsection>
<subsection name="Jasper">
Expand Down

0 comments on commit 4c7f4fd

Please sign in to comment.