File 9081-Prevent-httpd-from-parsing-HTTP-requests-when-multip.patch of Package erlang
From 45921318704218ab96015302836eb71739afdce6 Mon Sep 17 00:00:00 2001
From: Konrad Pietrzak <konrad@erlang.org>
Date: Wed, 25 Feb 2026 18:09:38 +0100
Subject: [PATCH] Prevent httpd from parsing HTTP requests when multiple
Content-Length headers are present
---
lib/inets/src/http_server/httpd_request.erl | 53 ++++++++++++-------
.../src/http_server/httpd_request_handler.erl | 10 ++--
lib/inets/test/httpd_SUITE.erl | 24 ++++++++-
3 files changed, 63 insertions(+), 24 deletions(-)
diff --git a/lib/inets/src/http_server/httpd_request.erl b/lib/inets/src/http_server/httpd_request.erl
index 162b5a871b..5c6b2d256a 100644
--- a/lib/inets/src/http_server/httpd_request.erl
+++ b/lib/inets/src/http_server/httpd_request.erl
@@ -210,7 +210,7 @@ parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, Header, Headers, _, _,
Headers),
{ok, list_to_tuple(lists:reverse([Body, {http_request:headers(FinalHeaders, #http_request_h{}), FinalHeaders} | Result]))};
NewHeader ->
- case check_header(NewHeader, Options) of
+ case check_header(NewHeader, Headers, Options) of
ok ->
FinalHeaders = lists:filtermap(fun(H) ->
httpd_custom:customize_headers(Customize, request_header, H)
@@ -260,7 +260,7 @@ parse_headers(<<?CR,?LF, Octet, Rest/binary>>, Header, Headers, Current, Max,
parse_headers(Rest, [Octet], Headers,
Current, Max, Options, Result);
NewHeader ->
- case check_header(NewHeader, Options) of
+ case check_header(NewHeader, Headers, Options) of
ok ->
parse_headers(Rest, [Octet], [NewHeader | Headers],
Current, Max, Options, Result);
@@ -429,23 +429,36 @@ get_persistens(HTTPVersion,ParsedHeader,ConfigDB)->
default_version()->
"HTTP/1.1".
-check_header({"content-length", Value}, Maxsizes) ->
- Max = proplists:get_value(max_content_length, Maxsizes),
- MaxLen = length(integer_to_list(Max)),
- case length(Value) =< MaxLen of
- true ->
- try
- list_to_integer(Value)
- of
- I when I>= 0 ->
- ok;
- _ ->
- {error, {size_error, Max, 411, "negative content-length"}}
- catch _:_ ->
- {error, {size_error, Max, 411, "content-length not an integer"}}
- end;
- false ->
- {error, {size_error, Max, 413, "content-length unreasonably long"}}
+check_header({"content-length", Value}, Headers, MaxSizes) ->
+ case check_parsed_content_length_values(Value, Headers) of
+ true ->
+ check_content_length_value(Value, MaxSizes);
+ false ->
+ {error, {bad_request, 400, "Multiple Content-Length headers with different values"}}
end;
-check_header(_, _) ->
+
+check_header(_, _, _) ->
ok.
+
+check_parsed_content_length_values(CurrentValue, Headers) ->
+ ContentLengths = [V || {"content-length", _} = V <- Headers],
+ length([V || {"content-length", Value} = V <- ContentLengths, Value =:= CurrentValue]) =:= length(ContentLengths).
+
+check_content_length_value(Value, MaxSizes) ->
+ Max = proplists:get_value(max_content_length, MaxSizes),
+ MaxLen = length(integer_to_list(Max)),
+ case length(Value) =< MaxLen of
+ true ->
+ try
+ list_to_integer(Value)
+ of
+ I when I>= 0 ->
+ ok;
+ _ ->
+ {error, {size_error, Max, 411, "negative content-length"}}
+ catch _:_ ->
+ {error, {size_error, Max, 411, "content-length not an integer"}}
+ end;
+ false ->
+ {error, {size_error, Max, 413, "content-length unreasonably long"}}
+ end.
diff --git a/lib/inets/src/http_server/httpd_request_handler.erl b/lib/inets/src/http_server/httpd_request_handler.erl
index 43e916c61b..8b5e49adb1 100644
--- a/lib/inets/src/http_server/httpd_request_handler.erl
+++ b/lib/inets/src/http_server/httpd_request_handler.erl
@@ -248,12 +248,16 @@ handle_info({Proto, Socket, Data},
httpd_response:send_status(NewModData, ErrCode, ErrStr, {max_size, MaxSize}),
{stop, normal, State#state{response_sent = true,
mod = NewModData}};
-
- {error, {version_error, ErrCode, ErrStr}, Version} ->
+ {error, {version_error, ErrCode, ErrStr}, Version} ->
NewModData = ModData#mod{http_version = Version},
httpd_response:send_status(NewModData, ErrCode, ErrStr),
{stop, normal, State#state{response_sent = true,
- mod = NewModData}};
+ mod = NewModData}};
+ {error, {bad_request, ErrCode, ErrStr}, Version} ->
+ NewModData = ModData#mod{http_version = Version},
+ httpd_response:send_status(NewModData, ErrCode, ErrStr),
+ {stop, normal, State#state{response_sent = true,
+ mod = NewModData}};
{http_chunk = Module, Function, Args} when ChunkState =/= undefined ->
NewState = handle_chunk(Module, Function, Args, State),
diff --git a/lib/inets/test/httpd_SUITE.erl b/lib/inets/test/httpd_SUITE.erl
index 9ac2e07bb5..0c2d488926 100644
--- a/lib/inets/test/httpd_SUITE.erl
+++ b/lib/inets/test/httpd_SUITE.erl
@@ -125,7 +125,7 @@ groups() ->
disturbing_1_0,
reload_config_file
]},
- {post, [], [chunked_post, chunked_chunked_encoded_post, post_204]},
+ {post, [], [chunked_post, chunked_chunked_encoded_post, post_204, multiple_content_length_header]},
{basic_auth, [], [basic_auth_1_1, basic_auth_1_0, verify_href_1_1]},
{auth_api, [], [auth_api_1_1, auth_api_1_0]},
{auth_api_dets, [], [auth_api_1_1, auth_api_1_0]},
@@ -1980,6 +1980,28 @@ tls_alert(Config) when is_list(Config) ->
Port = proplists:get_value(port, Config),
{error, {tls_alert, _}} = ssl:connect("localhost", Port, [{verify, verify_peer} | SSLOpts]).
+%%-------------------------------------------------------------------------
+multiple_content_length_header() ->
+ [{doc, "Test Content-Length header"}].
+
+multiple_content_length_header(Config) when is_list(Config) ->
+ ok = http_status("POST / ",
+ {"Content-Length:0" ++ "\r\n",
+ ""},
+ [{http_version, "HTTP/1.1"} |Config],
+ [{statuscode, 501}]),
+ ok = http_status("POST / ",
+ {"Content-Length:0" ++ "\r\n" ++
+ "Content-Length:0" ++ "\r\n",
+ ""},
+ [{http_version, "HTTP/1.1"} |Config],
+ [{statuscode, 501}]),
+ ok = http_status("POST / ",
+ {"Content-Length:1" ++ "\r\n" ++
+ "Content-Length:0" ++ "\r\n",
+ "Z"},
+ [{http_version, "HTTP/1.1"} |Config],
+ [{statuscode, 400}]).
%%--------------------------------------------------------------------
%% Internal functions -----------------------------------
%%--------------------------------------------------------------------
--
2.51.0