Hi,

Here are two small fixes for the header conversion of the Lua
httpclient, against current master (e5e52e33c). They follow up on
2db801c63 ("BUG/MINOR: hlua: fix stack overflow in httpclient headers
conversion") and are attached (git format-patch).

1/2 BUG/MINOR: httpclient/lua: fix stack overread when copying the headers
hlua_httpclient_table_to_hdrs() copies hdr_num + 1 entries out of a
stack array of tune.http.maxhdr entries that has no terminator. Since
2db801c63 hdr_num can reach tune.http.maxhdr, so a request with
exactly 101 header values reads one entry past the array (ASan:
dynamic-stack-buffer-overflow READ of size 3264). Only hdr_num
entries are copied now; the terminator is already set after the copy.

2/2 BUG/MINOR: httpclient/lua: raise an error on too many request headers
With more than tune.http.maxhdr values, the early return leaves the
outer lua_next() key and value on the stack, so the request fails
with "invalid key to 'next'", and the duplicated values leak. The
values are now freed and an explicit "too many headers" Lua error is
raised, like the other argument errors.

Tested on e5e52e33c with an ASan build (USE_LUA=1) and a Lua task
calling core.httpclient():get() with 1, 101, 102 and 300 values for one
header: on master 101 values trigger the ASan report, and without 2/2
300 values fail with "invalid key to 'next'"; with both patches 1 and
101 values send the request with no ASan report, and 102 and 300 fail
with "too many headers (tune.http.maxhdr is 101)". Both should be
backported where 2db801c63 was; before 3.5 the code is in src/hlua.c.

These were found with an LLM-Enhanced Static Analysis Engine and verified
manually.

Martin Mirchev
From 63801c7ed53dedf323855be55b538cadcf581c04 Mon Sep 17 00:00:00 2001
From: Martin Mirchev <[email protected]>
Date: Fri, 30 Oct 2026 13:45:12 +0800
Subject: [PATCH 1/2] BUG/MINOR: httpclient/lua: fix stack overread when
 copying the headers

hlua_httpclient_table_to_hdrs() collects the header values of a Lua
httpclient request into a stack array of tune.http.maxhdr entries, then
copies hdr_num + 1 entries into the allocated result, although the
array never contains a terminator: the result is allocated with calloc()
and its last entry is set to IST_NULL right after the copy.

Since commit 2db801c63 ("BUG/MINOR: hlua: fix stack overflow in
httpclient headers conversion") hdr_num may reach tune.http.maxhdr, and
the copy then reads one entry past the end of the array. E.g. with the
default value:

    local v = {}
    for i = 1, 101 do v[i] = "v" .. i end
    hc:get{ url = "http://127.0.0.1:8080/";, headers = { ["x-many"] = v } }

ASan reports a dynamic-stack-buffer-overflow READ of size 3264 (102
entries) in hlua_httpclient_table_to_hdrs(). With fewer values, the
extra entry is uninitialized stack, which is then overwritten.

Let's only copy the hdr_num collected entries.

This should be backported to all stable versions with the Lua
httpclient (2.5 and above), on top of 2db801c63 where it was
backported. Before 3.5 the function is in src/hlua.c.
---
 src/http_client.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/http_client.c b/src/http_client.c
index 3f0c0a3d4..c5b1a0a73 100644
--- a/src/http_client.c
+++ b/src/http_client.c
@@ -1745,7 +1745,7 @@ static struct http_hdr *hlua_httpclient_table_to_hdrs(lua_State *L)
 		result = calloc((hdr_num + 1), sizeof(*result));
 		if (!result)
 			goto skip_headers;
-		memcpy(result, hdrs, sizeof(struct http_hdr) * (hdr_num + 1));
+		memcpy(result, hdrs, sizeof(struct http_hdr) * hdr_num);
 
 		result[hdr_num].n = IST_NULL;
 		result[hdr_num].v = IST_NULL;
-- 
2.50.1

From b57b5b51e9cb378b0abe4a43ebfb83e12ec10cb6 Mon Sep 17 00:00:00 2001
From: Martin Mirchev <[email protected]>
Date: Fri, 30 Oct 2026 13:49:13 +0800
Subject: [PATCH 2/2] BUG/MINOR: httpclient/lua: raise an error on too many
 request headers

When a Lua httpclient request has more header values than
tune.http.maxhdr, hlua_httpclient_table_to_hdrs() pops the inner
lua_next() key and value and returns NULL. But the key and value of the
outer lua_next() loop on the headers table stay on the stack, so the
caller's lua_next() on the argument table gets the wrong key and the
request fails with a confusing error:

    runtime error: invalid key to 'next' from [C]: in method 'get'

The header values already duplicated with istdup() are also leaked.

Let's free them and raise an explicit Lua error instead, as the caller
does for the other invalid arguments:

    runtime error: too many headers (tune.http.maxhdr is 101)

The function is now marked __LJMP and its call wrapped in MAY_LJMP().

This should be backported where 2db801c63 ("BUG/MINOR: hlua: fix stack
overflow in httpclient headers conversion") was backported. Before 3.5
the function is in src/hlua.c.
---
 src/http_client.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/src/http_client.c b/src/http_client.c
index c5b1a0a73..c9d2ee0a0 100644
--- a/src/http_client.c
+++ b/src/http_client.c
@@ -1689,7 +1689,7 @@ __LJMP static int hlua_httpclient_get_headers(lua_State *L, struct hlua_httpclie
  *
  * Caller must free the result
  */
-static struct http_hdr *hlua_httpclient_table_to_hdrs(lua_State *L)
+__LJMP static struct http_hdr *hlua_httpclient_table_to_hdrs(lua_State *L)
 {
 	struct http_hdr hdrs[global.tune.max_http_hdr];
 	struct http_hdr *result = NULL;
@@ -1718,8 +1718,12 @@ static struct http_hdr *hlua_httpclient_table_to_hdrs(lua_State *L)
 			}
 
 			if (hdr_num >= global.tune.max_http_hdr) {
-				lua_pop(L, 2);
-				goto skip_headers;
+				while (hdr_num--) {
+					istfree(&hdrs[hdr_num].n);
+					istfree(&hdrs[hdr_num].v);
+				}
+				WILL_LJMP(luaL_error(L, "too many headers (tune.http.maxhdr is %d)",
+				                     global.tune.max_http_hdr));
 			}
 
 			v = lua_tolstring(L, -1, &vlen);
@@ -1914,7 +1918,7 @@ __LJMP static int hlua_httpclient_send(lua_State *L, enum http_meth_t meth)
 		} else if (strcmp(lua_tostring(L, -2), "headers") == 0) {
 			if (lua_type(L, -1) != LUA_TTABLE)
 				WILL_LJMP(luaL_error(L, "invalid parameter in 'headers', must be a table"));
-			hdrs = hlua_httpclient_table_to_hdrs(L);
+			hdrs = MAY_LJMP(hlua_httpclient_table_to_hdrs(L));
 
 		} else if (strcmp(lua_tostring(L, -2), "body") == 0) {
 			if (lua_type(L, -1) != LUA_TSTRING)
-- 
2.50.1

Reply via email to