From 62bd62786204584ca3496ac80d6447bbf7773990 Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Sat, 17 Oct 2026 14:32:20 -0600 Subject: [PATCH 1/2] lua: bound MessagePack reads when pushing values to Lua flb_lua_pushmpack() read strings, binaries and extensions by advancing the reader position by the declared length, without checking it against the remaining data, and ignored reader errors, so truncated input read past the end of the buffer. Nesting was not limited and the Lua stack was not grown for nested tables. Read the bytes through the reader, which checks the length, fail on reader errors, limit nesting to 512 levels as JSON packing does, grow the Lua stack for every nested table, and leave the stack unchanged on failure. Signed-off-by: Eduardo Silva --- src/flb_lua.c | 56 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/src/flb_lua.c b/src/flb_lua.c index 90fff96e9b5..c14fc8c7de3 100644 --- a/src/flb_lua.c +++ b/src/flb_lua.c @@ -103,16 +103,24 @@ static int flb_lua_is_valid_key(lua_State *l, int index) return FLB_TRUE; } -int flb_lua_pushmpack(lua_State *l, mpack_reader_t *reader) +/* nesting limit for MessagePack values pushed to Lua, as for JSON packing */ +#define FLB_LUA_MPACK_MAX_DEPTH 512 + +static int lua_pushmpack_value(lua_State *l, mpack_reader_t *reader, int depth) { int ret = 0; mpack_tag_t tag; uint32_t length; uint32_t i; int index; + const char *data; struct flb_lua_metadata meta; tag = mpack_read_tag(reader); + if (mpack_reader_error(reader) != mpack_ok) { + return -1; + } + switch (mpack_tag_type(&tag)) { case mpack_type_nil: lua_getglobal(l, FLB_LUA_VAR_FLB_NULL); @@ -135,11 +143,19 @@ int flb_lua_pushmpack(lua_State *l, mpack_reader_t *reader) case mpack_type_str: case mpack_type_bin: case mpack_type_ext: + /* the reader checks the length against the remaining data */ length = mpack_tag_bytes(&tag); - lua_pushlstring(l, reader->data, length); - reader->data += length; + data = mpack_read_bytes_inplace(reader, length); + if (mpack_reader_error(reader) != mpack_ok) { + return -1; + } + lua_pushlstring(l, data, length); + mpack_done_type(reader, mpack_tag_type(&tag)); break; case mpack_type_array: + if (depth >= FLB_LUA_MPACK_MAX_DEPTH || !lua_checkstack(l, 3)) { + return -1; + } flb_lua_metadata_init(&meta); meta.data_type = FLB_LUA_L2C_TYPE_ARRAY; @@ -147,16 +163,20 @@ int flb_lua_pushmpack(lua_State *l, mpack_reader_t *reader) lua_createtable(l, length, 0); index = lua_gettop(l); /* save index of created table */ for (i = 0; i < length; i++) { - ret = flb_lua_pushmpack(l, reader); + ret = lua_pushmpack_value(l, reader, depth + 1); if (ret) { return ret; } lua_rawseti(l, -2, i+1); } + mpack_done_array(reader); flb_lua_setmetatable(l, &meta, index); break; case mpack_type_map: + if (depth >= FLB_LUA_MPACK_MAX_DEPTH || !lua_checkstack(l, 4)) { + return -1; + } flb_lua_metadata_init(&meta); meta.data_type = FLB_LUA_L2C_TYPE_MAP; @@ -164,11 +184,11 @@ int flb_lua_pushmpack(lua_State *l, mpack_reader_t *reader) lua_createtable(l, length, 0); index = lua_gettop(l); /* save index of created table */ for (i = 0; i < length; i++) { - ret = flb_lua_pushmpack(l, reader); + ret = lua_pushmpack_value(l, reader, depth + 1); if (ret) { return ret; } - ret = flb_lua_pushmpack(l, reader); + ret = lua_pushmpack_value(l, reader, depth + 1); if (ret) { return ret; } @@ -179,15 +199,39 @@ int flb_lua_pushmpack(lua_State *l, mpack_reader_t *reader) } lua_settable(l, -3); } + mpack_done_map(reader); flb_lua_setmetatable(l, &meta, index); break; default: return -1; } + + if (mpack_reader_error(reader) != mpack_ok) { + return -1; + } + return 0; } +/* + * Push the next MessagePack value of the reader. On failure, such as truncated + * or too deeply nested data, nothing is pushed and -1 is returned. + */ +int flb_lua_pushmpack(lua_State *l, mpack_reader_t *reader) +{ + int ret; + int top; + + top = lua_gettop(l); + ret = lua_pushmpack_value(l, reader, 0); + if (ret != 0) { + lua_settop(l, top); + } + + return ret; +} + void flb_lua_pushmsgpack(lua_State *l, msgpack_object *o) { int i; From 2c64e63984b97944ea9e25cca14bf340c2447519 Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Sat, 17 Oct 2026 14:32:20 -0600 Subject: [PATCH 2/2] tests: internal: lua: cover malformed and deeply nested mpack input Signed-off-by: Eduardo Silva --- tests/internal/lua.c | 61 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/tests/internal/lua.c b/tests/internal/lua.c index 122748fbacd..7b801e58d52 100644 --- a/tests/internal/lua.c +++ b/tests/internal/lua.c @@ -144,6 +144,66 @@ static void test_pushmpack() lua_close(l); } +/* malformed or too deeply nested input is rejected without pushing values */ +static void test_pushmpack_invalid() +{ + int i; + int top; + char *nested; + size_t size; + mpack_reader_t reader; + lua_State *l = lua_setup(NULL); + /* str8 declaring 200 bytes with 3 present */ + const char truncated_str[] = "\xd9\xc8" "abc"; + /* array of 3 with a single element */ + const char truncated_array[] = "\x93\x01"; + /* bin8 declaring 10 bytes inside a map value */ + const char truncated_bin[] = "\x81\xa1k\xc4\x0a" "xy"; + + top = lua_gettop(l); + + mpack_reader_init_data(&reader, truncated_str, sizeof(truncated_str) - 1); + TEST_CHECK(flb_lua_pushmpack(l, &reader) == -1); + TEST_CHECK(lua_gettop(l) == top); + + mpack_reader_init_data(&reader, truncated_array, sizeof(truncated_array) - 1); + TEST_CHECK(flb_lua_pushmpack(l, &reader) == -1); + TEST_CHECK(lua_gettop(l) == top); + + mpack_reader_init_data(&reader, truncated_bin, sizeof(truncated_bin) - 1); + TEST_CHECK(flb_lua_pushmpack(l, &reader) == -1); + TEST_CHECK(lua_gettop(l) == top); + + /* nesting: 512 nested arrays are accepted, 513 are rejected */ + size = 514; + nested = flb_malloc(size); + TEST_ASSERT(nested != NULL); + + memset(nested, 0x91, 512); + nested[512] = 0x01; + mpack_reader_init_data(&reader, nested, 513); + TEST_CHECK(flb_lua_pushmpack(l, &reader) == 0); + TEST_CHECK(lua_gettop(l) == top + 1); + lua_settop(l, top); + + memset(nested, 0x91, 513); + nested[513] = 0x01; + mpack_reader_init_data(&reader, nested, 514); + TEST_CHECK(flb_lua_pushmpack(l, &reader) == -1); + TEST_CHECK(lua_gettop(l) == top); + + /* a valid value still decodes after rejected ones */ + for (i = 0; i < 2; i++) { + mpack_reader_init_data(&reader, "\x92\x01\xa2ok", 5); + TEST_CHECK(flb_lua_pushmpack(l, &reader) == 0); + check_equals(l, "{ [1] = 1 [2] = ok }"); + lua_settop(l, top); + } + + flb_free(nested); + lua_close(l); +} + static void test_tomsgpack() { const char expected[] = "[{\"key\"=>\"value\"}, \"msgpack-str\", 4]"; @@ -316,6 +376,7 @@ TEST_LIST = { { "lua_pushtimetable" , test_pushtimetable}, { "lua_pushmsgpack" , test_pushmsgpack }, { "lua_pushmpack" , test_pushmpack }, + { "lua_pushmpack_invalid" , test_pushmpack_invalid }, { "lua_tomsgpack" , test_tomsgpack }, { "lua_tompack" , test_tompack }, { "lua_arraylength" , test_lua_arraylength },