]> git.kaiwu.me - haproxy.git/commitdiff
BUG/MINOR: lua: return a binary sample for a Lua string holding a \0 byte
authorFrederic Lecaille <flecaille@haproxy.com>
Mon, 14 Sep 2026 14:14:01 +0000 (16:14 +0200)
committerFrederic Lecaille <flecaille@haproxy.com>
Wed, 16 Sep 2026 07:18:10 +0000 (09:18 +0200)
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.

reg-tests/lua/lua_str_embedded_nul.lua [new file with mode: 0644]
reg-tests/lua/lua_str_embedded_nul.vtc [new file with mode: 0644]
src/hlua.c

diff --git a/reg-tests/lua/lua_str_embedded_nul.lua b/reg-tests/lua/lua_str_embedded_nul.lua
new file mode 100644 (file)
index 0000000..9b366b2
--- /dev/null
@@ -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 (file)
index 0000000..d74ebbc
--- /dev/null
@@ -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
index 67f13483d761cb71ad215151d2a228a4cccca08b..2d4ef55e5b0c5575c830f2762030ea8ab512e825 100644 (file)
@@ -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;