Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Content-Lenth and Content-Type in bodyless message #2341

Merged
merged 3 commits into from
Feb 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions etc/tempesta_fw.conf
Original file line number Diff line number Diff line change
Expand Up @@ -1189,6 +1189,18 @@
# http_max_header_list_size 4096;
#

# TAG: http_allow_empty_body_content_type
#
# This directive controls whether HTTP requests are allowed to include
# a Content-Type header even when the message body is empty.
#
# Syntax:
# http_allow_empty_body_content_type true|false
#
# Example:
# http_allow_empty_body_content_type true;
#

#
# Health monitoring configuration.
#
Expand Down
16 changes: 16 additions & 0 deletions fw/http.c
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ static struct {
* here, because it refers to HTTP layer.
*/
unsigned int max_header_list_size = 0;
bool allow_empty_body_content_type;

#define S_CRLFCRLF "\r\n\r\n"
#define S_HTTP "http://"
Expand Down Expand Up @@ -7908,6 +7909,12 @@ tfw_cfgop_cleanup_max_header_list_size(TfwCfgSpec *cs)
max_header_list_size = 0;
}

static void
tfw_cfgop_cleanup_allow_empty_body_content_type(TfwCfgSpec *cs)
{
allow_empty_body_content_type = false;
}

static TfwCfgSpec tfw_http_specs[] = {
{
.name = "block_action",
Expand Down Expand Up @@ -7995,6 +8002,15 @@ static TfwCfgSpec tfw_http_specs[] = {
.allow_none = true,
.cleanup = tfw_cfgop_cleanup_max_header_list_size,
},
{
.name = "http_allow_empty_body_content_type",
.deflt = "false",
.handler = tfw_cfg_set_bool,
.dest = &allow_empty_body_content_type,
.allow_none = true,
.allow_repeat = false,
.cleanup = tfw_cfgop_cleanup_allow_empty_body_content_type,
},
{
.name = "ja5h",
.deflt = NULL,
Expand Down
3 changes: 2 additions & 1 deletion fw/http.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Tempesta FW
*
* Copyright (C) 2014 NatSys Lab. ([email protected]).
* Copyright (C) 2015-2024 Tempesta Technologies, Inc.
* Copyright (C) 2015-2025 Tempesta Technologies, Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -736,6 +736,7 @@ typedef void (*tfw_http_cache_cb_t)(TfwHttpMsg *);
(TFW_MSG_H2(hmmsg) ? HTTP2_EXTRA_HDR_OVERHEAD : 0)

extern unsigned int max_header_list_size;
extern bool allow_empty_body_content_type;

/* External HTTP functions. */
int tfw_http_msg_process(TfwConn *conn, struct sk_buff *skb,
Expand Down
39 changes: 25 additions & 14 deletions fw/http_parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -1968,18 +1968,6 @@ __parse_content_length(TfwHttpMsg *hm, unsigned char *data, size_t len)
__FSM_REQUIRE_FIRST_DIGIT(I_ContLenBeg, I_ContLen);

__FSM_STATE(I_ContLen) {
/*
* A server MUST NOT send a Content-Length header field in any response
* with a status code of 1xx (Informational) or 204 (No Content).
* TODO: server MUST NOT send a Content-Length header field in any 2xx
* (Successful) response to a CONNECT request
*/
if (TFW_CONN_TYPE(msg->conn) & Conn_Srv) {
TfwHttpResp *resp = (TfwHttpResp *)msg;
if (resp->status - 100U < 100U || resp->status == 204)
return CSTR_NEQ;
}

/*
* According to RFC 7230 3.3.2, in cases of multiple Content-Length
* header fields with field-values consisting of the same decimal
Expand All @@ -1996,6 +1984,26 @@ __parse_content_length(TfwHttpMsg *hm, unsigned char *data, size_t len)
T_DBG3("%s: content_length=%lu\n", __func__, msg->content_length);
__set_bit(TFW_HTTP_B_REQ_CONTENT_LENGTH_PARSED, hm->flags);

/*
* A server MUST NOT send a Content-Length header field in any response
* with a status code of 1xx (Informational) or 204 (No Content).
* TODO: server MUST NOT send a Content-Length header field in any 2xx
* (Successful) response to a CONNECT request
*
* Treat `Content-Length: 0` as the absence of a Content-Length
* header. Some implementations send `Content-Length: 0` within
* 204 (No Content) or 1xx responses, to be able to process such
* messages the rule from RFC has been relaxed.
*/
if (TFW_CONN_TYPE(msg->conn) & Conn_Srv
&& msg->content_length > 0)
{
TfwHttpResp *resp = (TfwHttpResp *)msg;

if (resp->status - 100U < 100U || resp->status == 204)
return CSTR_NEQ;
}

return r;
}
}
Expand Down Expand Up @@ -4881,8 +4889,11 @@ tfw_http_parse_check_bodyless_meth(TfwHttpReq *req)
{
TfwStr *tbl = req->h_tbl->tbl;

if (!TFW_STR_EMPTY(&tbl[TFW_HTTP_HDR_CONTENT_LENGTH])
|| !TFW_STR_EMPTY(&tbl[TFW_HTTP_HDR_CONTENT_TYPE]))
/* Treat 'Content-Length: 0' as the empty body. */
if ((!TFW_STR_EMPTY(&tbl[TFW_HTTP_HDR_CONTENT_LENGTH])
&& req->content_length > 0)
|| (!TFW_STR_EMPTY(&tbl[TFW_HTTP_HDR_CONTENT_TYPE])
&& !allow_empty_body_content_type))
{
return check_bodyless_meth(req);
}
Expand Down
8 changes: 5 additions & 3 deletions fw/t/fuzzer.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*
* Tempesta HTTP fuzzer.
*
* Copyright (C) 2015-2022 Tempesta Technologies, Inc.
* Copyright (C) 2015-2025 Tempesta Technologies, Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -92,8 +92,9 @@ static FuzzMsg host_val[] = {
static FuzzMsg content_type[] = {
{"text/html;charset=utf-8"}, {"image/jpeg"}, {"text/plain"}
};
#define FUZZ_FLD_F_ZERO_CL (0x0001 << FUZZ_MSG_F_SHIFT)
static FuzzMsg content_len[] = {
{"10000"}, {"0"}, {"-42", FUZZ_MSG_F_INVAL},
{"10000"}, {"0", FUZZ_FLD_F_ZERO_CL}, {"-42", FUZZ_MSG_F_INVAL},
{"146"}, {"0100"}, {"100500"}
};
#define FUZZ_FLD_F_CHUNKED (0x0001 << FUZZ_MSG_F_SHIFT)
Expand Down Expand Up @@ -762,7 +763,8 @@ fuzz_hdrs_compatible(TfwFuzzContext *ctx, int type, unsigned int v)
if ((ctx->fld_flags[RESP_CODE]
& (FUZZ_FLD_F_STATUS_100
| FUZZ_FLD_F_STATUS_204))
&& (ctx->hdr_flags & (1 << CONTENT_LENGTH)))
&& (ctx->hdr_flags & (1 << CONTENT_LENGTH))
&& !(ctx->fld_flags[CONTENT_LENGTH] & FUZZ_FLD_F_ZERO_CL))
{
return false;
}
Expand Down
26 changes: 17 additions & 9 deletions fw/t/unit/test_http1_parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -1275,7 +1275,7 @@ TEST(http_parser, upgrade)

#define EXPECT_BLOCK_BODYLESS_REQ(METHOD) \
EXPECT_BLOCK_REQ(#METHOD " / HTTP/1.1\r\n" \
"Content-Length: 0\r\n" \
"Content-Length: 1\r\n" \
"\r\n") \
{ \
EXPECT_EQ(req->method, TFW_HTTP_METH_##METHOD); \
Expand All @@ -1289,23 +1289,23 @@ TEST(http_parser, upgrade)

#define EXPECT_BLOCK_BODYLESS_REQ_OVERRIDE(METHOD) \
EXPECT_BLOCK_REQ("PUT / HTTP/1.1\r\n" \
"Content-Length: 0\r\n" \
"Content-Length: 1\r\n" \
"X-Method-Override: " #METHOD "\r\n" \
"\r\n") \
{ \
EXPECT_EQ(req->method, TFW_HTTP_METH_PUT); \
EXPECT_EQ(req->method_override, TFW_HTTP_METH_##METHOD); \
} \
EXPECT_BLOCK_REQ("PUT / HTTP/1.1\r\n" \
"Content-Length: 0\r\n" \
"Content-Length: 1\r\n" \
"X-HTTP-Method-Override: " #METHOD "\r\n" \
"\r\n") \
{ \
EXPECT_EQ(req->method, TFW_HTTP_METH_PUT); \
EXPECT_EQ(req->method_override, TFW_HTTP_METH_##METHOD); \
} \
EXPECT_BLOCK_REQ("PUT / HTTP/1.1\r\n" \
"Content-Length: 0\r\n" \
"Content-Length: 1\r\n" \
"X-HTTP-Method: " #METHOD "\r\n" \
"\r\n") \
{ \
Expand Down Expand Up @@ -1390,8 +1390,16 @@ TEST(http1_parser, content_length)
/* Content-Length is mandatory for responses. */
EXPECT_BLOCK_RESP("HTTP/1.1 200 OK\r\n\r\n");

/* Content-Length must not be present in GET requests */
EXPECT_BLOCK_REQ_SIMPLE("Content-Length: 0");
/* Content-Length greater than zero must not be present in GET requests */
EXPECT_BLOCK_REQ_SIMPLE("Content-Length: 1");

/* Content-Length: 0 treats as the absence of a Content-Length. */
FOR_REQ("GET / HTTP/1.1\r\n"
"Content-Length: 0\r\n"
"\r\n")
{
EXPECT_TRUE(req->content_length == 0);
}

FOR_REQ("POST / HTTP/1.1\r\n"
"Content-Length: 0\r\n"
Expand Down Expand Up @@ -1492,23 +1500,23 @@ TEST(http1_parser, content_length)
"\r\n"
"dummy");
EXPECT_BLOCK_RESP("HTTP/1.1 101 Switching Protocols\r\n"
"Content-Length: 0\r\n"
"Content-Length: 1\r\n"
"\r\n");

EXPECT_BLOCK_RESP("HTTP/1.1 199 Dummy\r\n"
"Content-Length: 5\r\n"
"\r\n"
"dummy");
EXPECT_BLOCK_RESP("HTTP/1.1 199 Dummy\r\n"
"Content-Length: 0\r\n"
"Content-Length: 1\r\n"
"\r\n");

EXPECT_BLOCK_RESP("HTTP/1.0 204 No Content\r\n"
"Content-Length: 5\r\n"
"\r\n"
"dummy");
EXPECT_BLOCK_RESP("HTTP/1.0 204 No Content\r\n"
"Content-Length: 0\r\n"
"Content-Length: 1\r\n"
"\r\n");

FOR_RESP("HTTP/1.0 205 Reset Content\r\n"
Expand Down
10 changes: 5 additions & 5 deletions fw/t/unit/test_http2_parser.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Tempesta FW
*
* Copyright (C) 2022-2024 Tempesta Technologies, Inc.
* Copyright (C) 2022-2025 Tempesta Technologies, Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -848,7 +848,7 @@ TEST(http2_parser, suspicious_x_forwarded_for)
HEADER(WO_IND(NAME(":method"), VALUE(#METHOD))); \
HEADER(WO_IND(NAME(":scheme"), VALUE("https"))); \
HEADER(WO_IND(NAME(":path"), VALUE("/filename"))); \
HEADER(WO_IND(NAME("content-length"), VALUE("0"))); \
HEADER(WO_IND(NAME("content-length"), VALUE("1"))); \
HEADERS_FRAME_END(); \
) \
{ \
Expand All @@ -872,7 +872,7 @@ TEST(http2_parser, suspicious_x_forwarded_for)
HEADER(WO_IND(NAME(":method"), VALUE("PUT"))); \
HEADER(WO_IND(NAME(":scheme"), VALUE("https"))); \
HEADER(WO_IND(NAME(":path"), VALUE("/filename"))); \
HEADER(WO_IND(NAME("content-length"), VALUE("0"))); \
HEADER(WO_IND(NAME("content-length"), VALUE("1"))); \
HEADER(WO_IND(NAME("x-method-override"), VALUE(#METHOD))); \
HEADERS_FRAME_END(); \
) \
Expand All @@ -898,7 +898,7 @@ TEST(http2_parser, suspicious_x_forwarded_for)
HEADER(WO_IND(NAME(":method"), VALUE("PUT"))); \
HEADER(WO_IND(NAME(":scheme"), VALUE("https"))); \
HEADER(WO_IND(NAME(":path"), VALUE("/filename"))); \
HEADER(WO_IND(NAME("content-length"), VALUE("0"))); \
HEADER(WO_IND(NAME("content-length"), VALUE("1"))); \
HEADER(WO_IND(NAME("x-http-method-override"), VALUE(#METHOD))); \
HEADERS_FRAME_END(); \
) \
Expand All @@ -924,7 +924,7 @@ TEST(http2_parser, suspicious_x_forwarded_for)
HEADER(WO_IND(NAME(":method"), VALUE("PUT"))); \
HEADER(WO_IND(NAME(":scheme"), VALUE("https"))); \
HEADER(WO_IND(NAME(":path"), VALUE("/filename"))); \
HEADER(WO_IND(NAME("content-length"), VALUE("0"))); \
HEADER(WO_IND(NAME("content-length"), VALUE("1"))); \
HEADER(WO_IND(NAME("x-http-method"), VALUE(#METHOD))); \
HEADERS_FRAME_END(); \
) \
Expand Down