mirror of
https://github.com/Icinga/icinga2.git
synced 2026-07-16 12:13:06 -04:00
JsonDecode: include path in JSON depth error
If parsing JSON is rejected due to the depth limit introduced in the last
commit, also include the path (like root["object"]["children"]...) that exceeds
the allowed nesting depth.
(cherry picked from commit 14d6ee9cdf)
This commit is contained in:
parent
ed163a8aa9
commit
b80132db87
2 changed files with 82 additions and 38 deletions
|
|
@ -343,12 +343,22 @@ public:
|
|||
Value GetResult();
|
||||
|
||||
private:
|
||||
struct Node
|
||||
{
|
||||
Dictionary::Ptr m_Dictionary{};
|
||||
Array::Ptr m_Array{};
|
||||
String m_CurrentKey{};
|
||||
|
||||
explicit Node(Dictionary::Ptr dict) : m_Dictionary(std::move(dict)) {}
|
||||
explicit Node(Array::Ptr array) : m_Array(std::move(array)) {}
|
||||
};
|
||||
|
||||
Value m_Root;
|
||||
std::stack<std::pair<Dictionary*, Array*>> m_CurrentSubtree;
|
||||
String m_CurrentKey;
|
||||
std::vector<Node> m_Stack;
|
||||
std::size_t m_DepthLimit;
|
||||
|
||||
void FillCurrentTarget(Value value);
|
||||
void CheckDepthLimit() const;
|
||||
};
|
||||
|
||||
String icinga::JsonEncode(const Value& value, bool prettify)
|
||||
|
|
@ -455,14 +465,9 @@ inline
|
|||
bool JsonSax::start_object(std::size_t)
|
||||
{
|
||||
auto object (new Dictionary());
|
||||
|
||||
FillCurrentTarget(object);
|
||||
|
||||
if (m_CurrentSubtree.size() >= m_DepthLimit) {
|
||||
BOOST_THROW_EXCEPTION(std::runtime_error("JSON decoding recursion limit reached"));
|
||||
}
|
||||
|
||||
m_CurrentSubtree.push({object, nullptr});
|
||||
CheckDepthLimit();
|
||||
m_Stack.push_back(Node{object});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -470,7 +475,7 @@ bool JsonSax::start_object(std::size_t)
|
|||
inline
|
||||
bool JsonSax::key(JsonSax::string_t& val)
|
||||
{
|
||||
m_CurrentKey = String(std::move(val));
|
||||
m_Stack.back().m_CurrentKey = String(std::move(val));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -478,8 +483,7 @@ bool JsonSax::key(JsonSax::string_t& val)
|
|||
inline
|
||||
bool JsonSax::end_object()
|
||||
{
|
||||
m_CurrentSubtree.pop();
|
||||
m_CurrentKey = String();
|
||||
m_Stack.pop_back();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -488,14 +492,9 @@ inline
|
|||
bool JsonSax::start_array(std::size_t)
|
||||
{
|
||||
auto array (new Array());
|
||||
|
||||
FillCurrentTarget(array);
|
||||
|
||||
if (m_CurrentSubtree.size() >= m_DepthLimit) {
|
||||
BOOST_THROW_EXCEPTION(std::runtime_error("JSON decoding recursion limit reached"));
|
||||
}
|
||||
|
||||
m_CurrentSubtree.push({nullptr, array});
|
||||
CheckDepthLimit();
|
||||
m_Stack.push_back(Node{array});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -503,7 +502,7 @@ bool JsonSax::start_array(std::size_t)
|
|||
inline
|
||||
bool JsonSax::end_array()
|
||||
{
|
||||
m_CurrentSubtree.pop();
|
||||
m_Stack.pop_back();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -523,15 +522,34 @@ Value JsonSax::GetResult()
|
|||
inline
|
||||
void JsonSax::FillCurrentTarget(Value value)
|
||||
{
|
||||
if (m_CurrentSubtree.empty()) {
|
||||
if (m_Stack.empty()) {
|
||||
m_Root = value;
|
||||
} else {
|
||||
auto& node (m_CurrentSubtree.top());
|
||||
auto& node (m_Stack.back());
|
||||
|
||||
if (node.first) {
|
||||
node.first->Set(m_CurrentKey, value);
|
||||
if (node.m_Dictionary) {
|
||||
node.m_Dictionary->Set(node.m_CurrentKey, value);
|
||||
} else {
|
||||
node.second->Add(value);
|
||||
node.m_Array->Add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void JsonSax::CheckDepthLimit() const
|
||||
{
|
||||
if (m_Stack.size() >= m_DepthLimit) {
|
||||
std::ostringstream buf;
|
||||
buf << "JSON decoding recursion limit reached (path: root";
|
||||
for (const auto& node : m_Stack) {
|
||||
buf << "[";
|
||||
if (node.m_Dictionary) {
|
||||
buf << JsonEncode(node.m_CurrentKey);
|
||||
} else {
|
||||
buf << node.m_Array->GetLength() - 1;
|
||||
}
|
||||
buf << "]";
|
||||
}
|
||||
buf << ")";
|
||||
BOOST_THROW_EXCEPTION(std::runtime_error(buf.str()));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -171,8 +171,23 @@ static std::string MakeNestedJsonObject(size_t depth)
|
|||
|
||||
BOOST_AUTO_TEST_CASE(decode_depth_limit)
|
||||
{
|
||||
auto isDepthLimit = [](const std::exception& ex) {
|
||||
return std::string_view(ex.what()) == "JSON decoding recursion limit reached";
|
||||
auto ExpectLimitExceeded = [](std::string_view input, size_t limit, std::string_view path) {
|
||||
try {
|
||||
JsonDecode(std::string(input), limit);
|
||||
|
||||
boost::test_tools::assertion_result result{false};
|
||||
result.message() << "Decoding '" << input << "' with limit " << limit << " did not throw an exception";
|
||||
return result;
|
||||
} catch (const std::exception& ex) {
|
||||
std::ostringstream expected;
|
||||
expected << "JSON decoding recursion limit reached (path: " << path << ")";
|
||||
|
||||
boost::test_tools::assertion_result result{ex.what() == expected.str()};
|
||||
result.message() << "Decoding '" << input << "' with limit " << limit << ":\n"
|
||||
<< " got exception: " << ex.what() << "\n"
|
||||
<< " expected exception: " << expected.str() << "\n";
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
// Scalars parse even with depth limit 0.
|
||||
|
|
@ -181,21 +196,21 @@ BOOST_AUTO_TEST_CASE(decode_depth_limit)
|
|||
BOOST_CHECK_EQUAL(JsonDecode("\"test\"", 0), Value("test"));
|
||||
|
||||
// Arrays and objects require at least depth limit 1.
|
||||
BOOST_CHECK_EXCEPTION(JsonDecode("[]", 0), std::exception, isDepthLimit);
|
||||
BOOST_CHECK_EXCEPTION(JsonDecode("{}", 0), std::exception, isDepthLimit);
|
||||
BOOST_CHECK_EXCEPTION(JsonDecode("[42]", 0), std::exception, isDepthLimit);
|
||||
BOOST_CHECK_EXCEPTION(JsonDecode("{\"foo\": 23}", 0), std::exception, isDepthLimit);
|
||||
BOOST_CHECK(ExpectLimitExceeded("[]", 0, "root"));
|
||||
BOOST_CHECK(ExpectLimitExceeded("{}", 0, "root"));
|
||||
BOOST_CHECK(ExpectLimitExceeded("[42]", 0, "root"));
|
||||
BOOST_CHECK(ExpectLimitExceeded("{\"foo\": 23}", 0, "root"));
|
||||
|
||||
// Array in array and object in object require at least depth limit 2.
|
||||
BOOST_CHECK_EXCEPTION(JsonDecode("[[]]", 1), std::exception, isDepthLimit);
|
||||
BOOST_CHECK(ExpectLimitExceeded("[[]]", 1, "root[0]"));
|
||||
BOOST_CHECK_NO_THROW(JsonDecode("[[]]", 2));
|
||||
BOOST_CHECK_EXCEPTION(JsonDecode(R"({"a":{}})", 1), std::exception, isDepthLimit);
|
||||
BOOST_CHECK(ExpectLimitExceeded(R"({"a":{}})", 1, R"(root["a"])"));
|
||||
BOOST_CHECK_NO_THROW(JsonDecode(R"({"a":{}})", 2));
|
||||
|
||||
// Mixed nesting of arrays and objects is both counted towards the same limit.
|
||||
BOOST_CHECK_EXCEPTION(JsonDecode("[{}]", 1), std::exception, isDepthLimit);
|
||||
BOOST_CHECK(ExpectLimitExceeded("[{}]", 1, "root[0]"));
|
||||
BOOST_CHECK_NO_THROW(JsonDecode("[{}]", 2));
|
||||
BOOST_CHECK_EXCEPTION(JsonDecode(R"({"a":[]})", 1), std::exception, isDepthLimit);
|
||||
BOOST_CHECK(ExpectLimitExceeded(R"({"a":[]})", 1, R"(root["a"])"));
|
||||
BOOST_CHECK_NO_THROW(JsonDecode(R"({"a":[]})", 2));
|
||||
|
||||
// Siblings are not added up for the depth check.
|
||||
|
|
@ -204,12 +219,22 @@ BOOST_AUTO_TEST_CASE(decode_depth_limit)
|
|||
|
||||
// Some deeper nested array.
|
||||
std::string arrayWithNesting42 = MakeNestedJsonArray(42);
|
||||
BOOST_CHECK_EXCEPTION(JsonDecode(arrayWithNesting42, 41), std::exception, isDepthLimit);
|
||||
std::ostringstream arrayPathWithNesting41;
|
||||
arrayPathWithNesting41 << "root";
|
||||
for (size_t i = 0; i < 41; ++i) {
|
||||
arrayPathWithNesting41 << "[0]";
|
||||
}
|
||||
BOOST_CHECK(ExpectLimitExceeded(arrayWithNesting42, 41, arrayPathWithNesting41.str()));
|
||||
BOOST_CHECK_NO_THROW(JsonDecode(arrayWithNesting42, 42));
|
||||
|
||||
// Some deeper nested object.
|
||||
std::string objectWithNesting42 = MakeNestedJsonObject(42);
|
||||
BOOST_CHECK_EXCEPTION(JsonDecode(objectWithNesting42, 41), std::exception, isDepthLimit);
|
||||
std::ostringstream objectPathWithNesting41;
|
||||
objectPathWithNesting41 << "root";
|
||||
for (size_t i = 0; i < 41; ++i) {
|
||||
objectPathWithNesting41 << "[\"" << i << "\"]";
|
||||
}
|
||||
BOOST_CHECK(ExpectLimitExceeded(objectWithNesting42, 41, objectPathWithNesting41.str()));
|
||||
BOOST_CHECK_NO_THROW(JsonDecode(objectWithNesting42, 42));
|
||||
|
||||
// And some deeper nested mixed containers.
|
||||
|
|
@ -239,7 +264,8 @@ BOOST_AUTO_TEST_CASE(decode_depth_limit)
|
|||
}
|
||||
]
|
||||
})";
|
||||
BOOST_CHECK_EXCEPTION(JsonDecode(deeperMixedNesting, 9), std::exception, isDepthLimit);
|
||||
auto deeperPath = R"(root["1st-level"][2]["3rd-level"]["4th-level"][1]["6th-level"]["7th-level"]["8th-level"][1])";
|
||||
BOOST_CHECK(ExpectLimitExceeded(deeperMixedNesting, 9, deeperPath));
|
||||
BOOST_CHECK_NO_THROW(JsonDecode(deeperMixedNesting, 10));
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue