From e2ea359925c36e499b1c35161ff4ce7ceecbd32d Mon Sep 17 00:00:00 2001 From: Lars Mueller Date: Mon, 14 Oct 2024 15:46:25 +0200 Subject: [PATCH] JSON: Support consistent larger max. depth of 1024 --- games/devtest/mods/unittests/misc.lua | 15 +++++++++++++++ src/script/common/c_content.cpp | 12 +++++++----- src/script/common/c_content.h | 2 +- src/script/lua_api/l_util.cpp | 9 +++++++-- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/games/devtest/mods/unittests/misc.lua b/games/devtest/mods/unittests/misc.lua index 67473b8b5..b26ec753f 100644 --- a/games/devtest/mods/unittests/misc.lua +++ b/games/devtest/mods/unittests/misc.lua @@ -174,6 +174,21 @@ local function test_parse_json() end unittests.register("test_parse_json", test_parse_json) +local function test_write_json() + -- deeply nested structures should be preserved + local leaf = 42 + local data = leaf + for i = 1, 1000 do + data = {data} + end + local roundtripped = minetest.parse_json(minetest.write_json(data)) + for i = 1, 1000 do + roundtripped = roundtripped[1] + end + assert(roundtripped == 42) +end +unittests.register("test_write_json", test_write_json) + local function test_game_info() local info = minetest.get_game_info() local game_conf = Settings(info.path .. "/game.conf") diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 348c2559a..65525eed5 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -2149,11 +2149,11 @@ bool push_json_value(lua_State *L, const Json::Value &value, int nullindex) } // Converts Lua table --> JSON -void read_json_value(lua_State *L, Json::Value &root, int index, u8 recursion) +void read_json_value(lua_State *L, Json::Value &root, int index, u16 max_depth) { - if (recursion > 16) { - throw SerializationError("Maximum recursion depth exceeded"); - } + if (max_depth == 0) + throw SerializationError("depth exceeds MAX_JSON_DEPTH"); + int type = lua_type(L, index); if (type == LUA_TBOOLEAN) { root = (bool) lua_toboolean(L, index); @@ -2164,11 +2164,13 @@ void read_json_value(lua_State *L, Json::Value &root, int index, u8 recursion) const char *str = lua_tolstring(L, index, &len); root = std::string(str, len); } else if (type == LUA_TTABLE) { + // Reserve two slots for key and value. + lua_checkstack(L, 2); lua_pushnil(L); while (lua_next(L, index)) { // Key is at -2 and value is at -1 Json::Value value; - read_json_value(L, value, lua_gettop(L), recursion + 1); + read_json_value(L, value, lua_gettop(L), max_depth - 1); Json::ValueType roottype = root.type(); int keytype = lua_type(L, -1); diff --git a/src/script/common/c_content.h b/src/script/common/c_content.h index 178a86cf4..3bcc5109e 100644 --- a/src/script/common/c_content.h +++ b/src/script/common/c_content.h @@ -200,7 +200,7 @@ bool push_json_value (lua_State *L, const Json::Value &value, int nullindex); void read_json_value (lua_State *L, Json::Value &root, - int index, u8 recursion = 0); + int index, u16 max_depth); /*! * Pushes a Lua `pointed_thing` to the given Lua stack. diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index 6ead0a067..e78ac6d3f 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -93,6 +93,10 @@ int ModApiUtil::l_get_us_time(lua_State *L) return 1; } +// Maximum depth of a JSON object: +// Reading and writing should not overflow the Lua, C, or jsoncpp stacks. +constexpr static u16 MAX_JSON_DEPTH = 1024; + // parse_json(str[, nullvalue, return_error]) int ModApiUtil::l_parse_json(lua_State *L) { @@ -129,10 +133,11 @@ int ModApiUtil::l_parse_json(lua_State *L) Json::Value root; { Json::CharReaderBuilder builder; + builder.settings_["stackLimit"] = MAX_JSON_DEPTH; builder.settings_["collectComments"] = false; const std::unique_ptr reader(builder.newCharReader()); std::string errmsg; - if (!reader->parse(jsonstr.begin(), jsonstr.end(), &root, &errmsg)) + if (!reader->parse(jsonstr.data(), jsonstr.data() + jsonstr.size(), &root, &errmsg)) return handle_error(errmsg.c_str()); } @@ -155,7 +160,7 @@ int ModApiUtil::l_write_json(lua_State *L) Json::Value root; try { - read_json_value(L, root, 1); + read_json_value(L, root, 1, MAX_JSON_DEPTH); } catch (SerializationError &e) { lua_pushnil(L); lua_pushstring(L, e.what());