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; 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 },