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

openSUSE Build Service is sponsored by