diff --git a/src/lua/msgpack.c b/src/lua/msgpack.c index 2126988ebe6dbefed8397f6fd6fe41925de59c50..3229e01d4b43bd6629c04e14a8201fc1538deddd 100644 --- a/src/lua/msgpack.c +++ b/src/lua/msgpack.c @@ -139,6 +139,10 @@ luamp_encode_r(struct lua_State *L, struct luaL_serializer *cfg, case MP_MAP: /* Map */ if (level >= cfg->encode_max_depth) { + if (! cfg->encode_deep_as_nil) { + return luaL_error(L, "Too high nest level - %d", + level + 1); + } mpstream_encode_nil(stream); /* Limit nested maps */ return MP_NIL; } @@ -160,6 +164,10 @@ luamp_encode_r(struct lua_State *L, struct luaL_serializer *cfg, case MP_ARRAY: /* Array */ if (level >= cfg->encode_max_depth) { + if (! cfg->encode_deep_as_nil) { + return luaL_error(L, "Too high nest level - %d", + level + 1); + } mpstream_encode_nil(stream); /* Limit nested arrays */ return MP_NIL; } diff --git a/src/lua/msgpackffi.lua b/src/lua/msgpackffi.lua index 75fcc5ba01fd32209f36c36d97b1ed4bceede2e8..63d9b33b7c05604331ec8cbe6f8690881ed076ea 100644 --- a/src/lua/msgpackffi.lua +++ b/src/lua/msgpackffi.lua @@ -206,6 +206,10 @@ local function encode_r(buf, obj, level) encode_str(buf, obj) elseif type(obj) == "table" then if level >= msgpack.cfg.encode_max_depth then + if not msgpack.cfg.encode_deep_as_nil then + error(string.format('Too high nest level - %d', + msgpack.cfg.encode_max_depth + 1)) + end encode_nil(buf) return end diff --git a/src/lua/utils.c b/src/lua/utils.c index 48401042e8c0eefe55a08faf87c2b5ceb51e1c93..6b545b816359669e77d2b5493ae24f3046f9fd27 100644 --- a/src/lua/utils.c +++ b/src/lua/utils.c @@ -235,6 +235,7 @@ static struct { OPTION(LUA_TNUMBER, encode_sparse_ratio, 2), OPTION(LUA_TNUMBER, encode_sparse_safe, 10), OPTION(LUA_TNUMBER, encode_max_depth, 32), + OPTION(LUA_TBOOLEAN, encode_deep_as_nil, 0), OPTION(LUA_TBOOLEAN, encode_invalid_numbers, 1), OPTION(LUA_TNUMBER, encode_number_precision, 14), OPTION(LUA_TBOOLEAN, encode_load_metatables, 1), diff --git a/src/lua/utils.h b/src/lua/utils.h index 2485e109060640ddfbcc00d3e239c2e5242c8a4c..8f8630f8dcdd519e8f90d4c05ecc5674e4608c70 100644 --- a/src/lua/utils.h +++ b/src/lua/utils.h @@ -210,6 +210,13 @@ struct luaL_serializer { int encode_sparse_safe; /** Max recursion depth for encoding (MsgPack, CJSON only) */ int encode_max_depth; + /** + * A flag whether a table with too high nest level should + * be cropped. The not-encoded fields are replaced with + * one null. If not set, too high nesting is considered an + * error. + */ + int encode_deep_as_nil; /** Enables encoding of NaN and Inf numbers */ int encode_invalid_numbers; /** Floating point numbers precision (YAML, CJSON only) */ diff --git a/test/app-tap/json.test.lua b/test/app-tap/json.test.lua index 0a89668665647fbfa21687bf5dad8c5775bbf1d9..cd210c85fcbd2cc8a965a1180299d1a21e1c3637 100755 --- a/test/app-tap/json.test.lua +++ b/test/app-tap/json.test.lua @@ -38,9 +38,10 @@ tap.test("json", function(test) -- -- gh-2888: Check the possibility of using options in encode()/decode(). -- + local orig_encode_deep_as_nil = serializer.cfg.encode_deep_as_nil local orig_encode_max_depth = serializer.cfg.encode_max_depth local sub = {a = 1, { b = {c = 1, d = {e = 1}}}} - serializer.cfg({encode_max_depth = 1}) + serializer.cfg({encode_max_depth = 1, encode_deep_as_nil = true}) test:ok(serializer.encode(sub) == '{"1":null,"a":1}', 'depth of encoding is 1 with .cfg') serializer.cfg({encode_max_depth = orig_encode_max_depth}) @@ -121,5 +122,6 @@ tap.test("json", function(test) rec4['b'] = rec4 test:is(serializer.encode(rec4), '{"a":{"a":null,"b":null},"b":{"a":null,"b":null}}') - serializer.cfg({encode_max_depth = orig_encode_max_depth}) + serializer.cfg({encode_max_depth = orig_encode_max_depth, + encode_deep_as_nil = orig_encode_deep_as_nil}) end) diff --git a/test/app-tap/lua/serializer_test.lua b/test/app-tap/lua/serializer_test.lua index bedbf95a58d9bfb2f254b6a65ac9a576746306df..ce655da95f50d4a8696879242cf1bd8ff830f455 100644 --- a/test/app-tap/lua/serializer_test.lua +++ b/test/app-tap/lua/serializer_test.lua @@ -390,7 +390,7 @@ local function test_ucdata(test, s) end local function test_depth(test, s) - test:plan(1) + test:plan(3) -- -- gh-4434: serializer update should be reflected in Lua. -- @@ -399,6 +399,25 @@ local function test_depth(test, s) test:is(s.cfg.encode_max_depth, max_depth + 5, "cfg({<name> = value}) is reflected in cfg.<name>") s.cfg({encode_max_depth = max_depth}) + + -- + -- gh-4434 (yes, the same issue): let users choose whether + -- they want to raise an error on tables with too high nest + -- level. + -- + local deep_as_nil = s.cfg.encode_deep_as_nil + s.cfg({encode_deep_as_nil = false}) + + local t = nil + for i = 1, max_depth + 1 do t = {t} end + local ok, err = pcall(s.encode, t) + test:ok(not ok, "too deep encode depth") + + s.cfg({encode_max_depth = max_depth + 1}) + ok, err = pcall(s.encode, t) + test:ok(ok, "no throw in a corner case") + + s.cfg({encode_deep_as_nil = deep_as_nil, encode_max_depth = max_depth}) end return { diff --git a/test/app-tap/msgpackffi.test.lua b/test/app-tap/msgpackffi.test.lua index 7277a5b89fd6b30ab2d37702a9b20f143fbfc922..1059449640048fc54965721d95f29421fe1c0269 100755 --- a/test/app-tap/msgpackffi.test.lua +++ b/test/app-tap/msgpackffi.test.lua @@ -36,7 +36,7 @@ local function test_offsets(test, s) end local function test_other(test, s) - test:plan(23) + test:plan(24) local buf = string.char(0x93, 0x6e, 0xcb, 0x42, 0x2b, 0xed, 0x30, 0x47, 0x6f, 0xff, 0xff, 0xac, 0x77, 0x6b, 0x61, 0x71, 0x66, 0x7a, 0x73, 0x7a, 0x75, 0x71, 0x71, 0x78) @@ -82,6 +82,8 @@ local function test_other(test, s) return level end local msgpack = require('msgpack') + local deep_as_nil = msgpack.cfg.encode_deep_as_nil + msgpack.cfg({encode_deep_as_nil = true}) local max_depth = msgpack.cfg.encode_max_depth local result_depth = check_depth(max_depth + 5) test:is(result_depth, max_depth, @@ -105,7 +107,12 @@ local function test_other(test, s) while t ~= nil do level = level + 1 t = t.key end test:is(level, max_depth + 5, "recursive map") - msgpack.cfg({encode_max_depth = max_depth}) + msgpack.cfg({encode_deep_as_nil = false}) + local ok = pcall(check_depth, max_depth + 6) + test:ok(not ok, "exception is thrown when crop is not allowed") + + msgpack.cfg({encode_deep_as_nil = deep_as_nil, + encode_max_depth = max_depth}) end tap.test("msgpackffi", function(test) diff --git a/test/box/tuple.result b/test/box/tuple.result index dcbec0fa02d312fb908df7a0070b88a8d185028c..1082ae84e919445346f5852d47b77558326d6fd5 100644 --- a/test/box/tuple.result +++ b/test/box/tuple.result @@ -1258,6 +1258,12 @@ s2:drop() max_depth = msgpack.cfg.encode_max_depth --- ... +deep_as_nil = msgpack.cfg.encode_deep_as_nil +--- +... +msgpack.cfg({encode_deep_as_nil = true}) +--- +... t = nil --- ... @@ -1295,6 +1301,6 @@ level == max_depth + 5 or {level, max_depth} --- - true ... -msgpack.cfg({encode_max_depth = max_depth}) +msgpack.cfg({encode_max_depth = max_depth, encode_deep_as_nil = deep_as_nil}) --- ... diff --git a/test/box/tuple.test.lua b/test/box/tuple.test.lua index b2b834376b22d144bdbe5c80270eb62794471b8c..8dd15a0a1072b032f472b07459ab500ff7cade7d 100644 --- a/test/box/tuple.test.lua +++ b/test/box/tuple.test.lua @@ -431,6 +431,8 @@ s2:drop() -- gh-4434: tuple should use global msgpack serializer. -- max_depth = msgpack.cfg.encode_max_depth +deep_as_nil = msgpack.cfg.encode_deep_as_nil +msgpack.cfg({encode_deep_as_nil = true}) t = nil for i = 1, max_depth + 5 do t = {t} end tuple = box.tuple.new(t):totable() @@ -446,4 +448,4 @@ while tuple ~= nil do level = level + 1 tuple = tuple[1] end -- serializer allows deeper tables. level == max_depth + 5 or {level, max_depth} -msgpack.cfg({encode_max_depth = max_depth}) +msgpack.cfg({encode_max_depth = max_depth, encode_deep_as_nil = deep_as_nil}) diff --git a/third_party/lua-cjson/lua_cjson.c b/third_party/lua-cjson/lua_cjson.c index be2416c6924bac512373b911cebf8fabaf80c60d..3c5bb3765c3201252201f4f656b90bf4ccd99cee 100644 --- a/third_party/lua-cjson/lua_cjson.c +++ b/third_party/lua-cjson/lua_cjson.c @@ -400,14 +400,20 @@ static void json_append_data(lua_State *l, struct luaL_serializer *cfg, json_append_nil(cfg, json); break; case MP_MAP: - if (current_depth >= cfg->encode_max_depth) + if (current_depth >= cfg->encode_max_depth) { + if (! cfg->encode_deep_as_nil) + luaL_error(l, "Too high nest level"); return json_append_nil(cfg, json); /* Limit nested maps */ + } json_append_object(l, cfg, current_depth + 1, json); return; case MP_ARRAY: /* Array */ - if (current_depth >= cfg->encode_max_depth) + if (current_depth >= cfg->encode_max_depth) { + if (! cfg->encode_deep_as_nil) + luaL_error(l, "Too high nest level"); return json_append_nil(cfg, json); /* Limit nested arrays */ + } json_append_array(l, cfg, current_depth + 1, json, field.size); return; case MP_EXT: