From: Frederic Lecaille Date: Mon, 14 Sep 2026 14:14:01 +0000 (+0200) Subject: BUG/MINOR: lua: return a binary sample for a Lua string holding a \0 byte X-Git-Url: https://git.kaiwu.me/?a=commitdiff_plain;h=e24285480cc8c9426e6fb728f1299a4dc09fa03c;p=haproxy.git BUG/MINOR: lua: return a binary sample for a Lua string holding a \0 byte A string sample cannot contain a \0 byte, but hlua_lua2smp() turned any Lua string into one with its full length, and a Lua string may hold \0 bytes. A Lua fetch, converter or set_var() copying client data could thus produce a string sample matched by "-m str" on its prefix: "public\0x" matched "public". A Lua string is a byte sequence and is the only way for a script to return binary data, so such a value is now typed as a binary sample: its bytes are all kept for binary converters such as hex, and the usual binary to string cast, which stops at the first \0, applies for text consumers. A Lua string without a \0 byte is still a string. A reg-test is added to check this for a Lua fetch, a Lua converter and a variable set from Lua. This must be backported to all stable branches. --- diff --git a/reg-tests/lua/lua_str_embedded_nul.lua b/reg-tests/lua/lua_str_embedded_nul.lua new file mode 100644 index 000000000..9b366b297 --- /dev/null +++ b/reg-tests/lua/lua_str_embedded_nul.lua @@ -0,0 +1,17 @@ +-- Lua strings may contain a \0 byte, a sample string may not. + +core.register_fetches("plain_str", function(txn) + return "public" +end) + +core.register_fetches("nul_str", function(txn) + return "public\0x" +end) + +core.register_converters("nul_conv", function(str) + return str .. "\0x" +end) + +core.register_action("nul_var", { "http-req" }, function(txn) + txn:set_var("txn.lua", "public\0x") +end) diff --git a/reg-tests/lua/lua_str_embedded_nul.vtc b/reg-tests/lua/lua_str_embedded_nul.vtc new file mode 100644 index 000000000..d74ebbc69 --- /dev/null +++ b/reg-tests/lua/lua_str_embedded_nul.vtc @@ -0,0 +1,45 @@ +varnishtest "Lua strings with a \0 byte are binary samples" +feature cmd "$HAPROXY_PROGRAM -cc 'feature(LUA)'" +feature ignore_unknown_macro + +# A string sample cannot contain a \0 byte, but a Lua string can. A Lua +# fetch, converter or set_var() returning such a string must produce a +# binary sample: all its bytes are kept for binary converters such as hex, +# and it is cut at the first \0 when used as a string. A Lua string without +# a \0 byte is still a string sample. + +haproxy h1 -conf { + global + .if feature(THREAD) + thread-groups 1 + .endif + tune.lua.bool-sample-conversion normal + lua-load ${testdir}/lua_str_embedded_nul.lua + + defaults + mode http + timeout connect "${HAPROXY_TEST_TIMEOUT-5s}" + timeout client "${HAPROXY_TEST_TIMEOUT-5s}" + timeout server "${HAPROXY_TEST_TIMEOUT-5s}" + + frontend fe1 + bind "fd@${fe1}" + http-request lua.nul_var + http-request set-var-fmt(txn.plain) match if { lua.plain_str -m str public } + http-request set-var-fmt(txn.plain) nomatch if !{ lua.plain_str -m str public } + http-request return status 200 hdr plain %[var(txn.plain)] hdr fetch-hex %[lua.nul_str,hex] hdr fetch-len %[lua.nul_str,length] hdr conv-hex %[req.hdr(x-u),lua.nul_conv,hex] hdr conv-len %[req.hdr(x-u),lua.nul_conv,length] hdr var-hex %[var(txn.lua),hex] hdr var-len %[var(txn.lua),length] +} -start + +client c1 -connect ${h1_fe1_sock} { + txreq -url "/" -hdr "X-U: public" + rxresp + expect resp.status == 200 + expect resp.http.plain == "match" + # "public\0x": 8 bytes kept as binary, 6 when used as a string + expect resp.http.fetch-hex == "7075626C69630078" + expect resp.http.fetch-len == "6" + expect resp.http.conv-hex == "7075626C69630078" + expect resp.http.conv-len == "6" + expect resp.http.var-hex == "7075626C69630078" + expect resp.http.var-len == "6" +} -run diff --git a/src/hlua.c b/src/hlua.c index 67f13483d..2d4ef55e5 100644 --- a/src/hlua.c +++ b/src/hlua.c @@ -1281,9 +1281,10 @@ static int hlua_lua2smp(lua_State *L, int ud, struct sample *smp) break; case LUA_TSTRING: - smp->data.type = SMP_T_STR; smp->flags |= SMP_F_CONST; smp->data.u.str.area = (char *)lua_tolstring(L, ud, &smp->data.u.str.data); + /* a string sample cannot contain a NUL byte, a Lua string can */ + smp->data.type = memchr(smp->data.u.str.area, 0, smp->data.u.str.data) ? SMP_T_BIN : SMP_T_STR; /* We don't know the actual size of the underlying allocation, so be conservative. */ smp->data.u.str.size = smp->data.u.str.data+1; /* count the terminating null byte */ smp->data.u.str.head = 0;