Skip to content

Commit acbf4eb

Browse files
committed
Merge pull request #166 from cdunn2001/stackLimit
Fixes #88 and #56.
2 parents 2474989 + 56df206 commit acbf4eb

File tree

3 files changed

+56
-2
lines changed

3 files changed

+56
-2
lines changed

include/json/reader.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,9 @@ class JSON_API CharReaderBuilder : public CharReader::Factory {
310310
- true if dropped null placeholders are allowed. (See StreamWriterBuilder.)
311311
- "allowNumericKeys": false or true
312312
- true if numeric object keys are allowed.
313+
- "stackLimit": integer
314+
- This is a security issue (seg-faults caused by deeply nested JSON),
315+
so the default is low.
313316
314317
You can examine 'settings_` yourself
315318
to see the defaults. You can also write and read them just like any

src/lib_json/json_reader.cpp

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
#pragma warning(disable : 4996)
2929
#endif
3030

31+
static int const stackLimit_g = 1000;
32+
static int stackDepth_g = 0; // see readValue()
33+
3134
namespace Json {
3235

3336
#if __cplusplus >= 201103L
@@ -118,6 +121,7 @@ bool Reader::parse(const char* beginDoc,
118121
nodes_.pop();
119122
nodes_.push(&root);
120123

124+
stackDepth_g = 0; // Yes, this is bad coding, but options are limited.
121125
bool successful = readValue();
122126
Token token;
123127
skipCommentTokens(token);
@@ -140,6 +144,13 @@ bool Reader::parse(const char* beginDoc,
140144
}
141145

142146
bool Reader::readValue() {
147+
// This is a non-reentrant way to support a stackLimit. Terrible!
148+
// But this deprecated class has a security problem: Bad input can
149+
// cause a seg-fault. This seems like a fair, binary-compatible way
150+
// to prevent the problem.
151+
if (stackDepth_g >= stackLimit_g) throw std::runtime_error("Exceeded stackLimit in readValue().");
152+
++stackDepth_g;
153+
143154
Token token;
144155
skipCommentTokens(token);
145156
bool successful = true;
@@ -211,6 +222,7 @@ bool Reader::readValue() {
211222
lastValue_ = &currentValue();
212223
}
213224

225+
--stackDepth_g;
214226
return successful;
215227
}
216228

@@ -902,7 +914,8 @@ class OurFeatures {
902914
bool strictRoot_;
903915
bool allowDroppedNullPlaceholders_;
904916
bool allowNumericKeys_;
905-
}; // OldFeatures
917+
int stackLimit_;
918+
}; // OurFeatures
906919

907920
// exact copy of Implementation of class Features
908921
// ////////////////////////////////
@@ -1033,7 +1046,9 @@ class OurReader {
10331046
Location lastValueEnd_;
10341047
Value* lastValue_;
10351048
std::string commentsBefore_;
1036-
OurFeatures features_;
1049+
int stackDepth_;
1050+
1051+
OurFeatures const features_;
10371052
bool collectComments_;
10381053
}; // OurReader
10391054

@@ -1064,6 +1079,7 @@ bool OurReader::parse(const char* beginDoc,
10641079
nodes_.pop();
10651080
nodes_.push(&root);
10661081

1082+
stackDepth_ = 0;
10671083
bool successful = readValue();
10681084
Token token;
10691085
skipCommentTokens(token);
@@ -1086,6 +1102,8 @@ bool OurReader::parse(const char* beginDoc,
10861102
}
10871103

10881104
bool OurReader::readValue() {
1105+
if (stackDepth_ >= features_.stackLimit_) throw std::runtime_error("Exceeded stackLimit in readValue().");
1106+
++stackDepth_;
10891107
Token token;
10901108
skipCommentTokens(token);
10911109
bool successful = true;
@@ -1157,6 +1175,7 @@ bool OurReader::readValue() {
11571175
lastValue_ = &currentValue();
11581176
}
11591177

1178+
--stackDepth_;
11601179
return successful;
11611180
}
11621181

@@ -1853,6 +1872,7 @@ CharReader* CharReaderBuilder::newCharReader() const
18531872
features.strictRoot_ = settings_["strictRoot"].asBool();
18541873
features.allowDroppedNullPlaceholders_ = settings_["allowDroppedNullPlaceholders"].asBool();
18551874
features.allowNumericKeys_ = settings_["allowNumericKeys"].asBool();
1875+
features.stackLimit_ = settings_["stackLimit"].asInt();
18561876
return new OurCharReader(collectComments, features);
18571877
}
18581878
static void getValidReaderKeys(std::set<std::string>* valid_keys)
@@ -1863,6 +1883,7 @@ static void getValidReaderKeys(std::set<std::string>* valid_keys)
18631883
valid_keys->insert("strictRoot");
18641884
valid_keys->insert("allowDroppedNullPlaceholders");
18651885
valid_keys->insert("allowNumericKeys");
1886+
valid_keys->insert("stackLimit");
18661887
}
18671888
bool CharReaderBuilder::validate(Json::Value* invalid) const
18681889
{
@@ -1901,6 +1922,7 @@ void CharReaderBuilder::setDefaults(Json::Value* settings)
19011922
(*settings)["strictRoot"] = false;
19021923
(*settings)["allowDroppedNullPlaceholders"] = false;
19031924
(*settings)["allowNumericKeys"] = false;
1925+
(*settings)["stackLimit"] = 1000;
19041926
//! [CharReaderBuilderDefaults]
19051927
}
19061928

src/test_lib_json/main.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1713,6 +1713,34 @@ JSONTEST_FIXTURE(CharReaderTest, parseWithDetailError) {
17131713
delete reader;
17141714
}
17151715

1716+
JSONTEST_FIXTURE(CharReaderTest, parseWithStackLimit) {
1717+
Json::CharReaderBuilder b;
1718+
Json::Value root;
1719+
char const doc[] =
1720+
"{ \"property\" : \"value\" }";
1721+
{
1722+
b.settings_["stackLimit"] = 2;
1723+
Json::CharReader* reader(b.newCharReader());
1724+
std::string errs;
1725+
bool ok = reader->parse(
1726+
doc, doc + std::strlen(doc),
1727+
&root, &errs);
1728+
JSONTEST_ASSERT(ok);
1729+
JSONTEST_ASSERT(errs == "");
1730+
JSONTEST_ASSERT_EQUAL("value", root["property"]);
1731+
delete reader;
1732+
}
1733+
{
1734+
b.settings_["stackLimit"] = 1;
1735+
Json::CharReader* reader(b.newCharReader());
1736+
std::string errs;
1737+
JSONTEST_ASSERT_THROWS(reader->parse(
1738+
doc, doc + std::strlen(doc),
1739+
&root, &errs));
1740+
delete reader;
1741+
}
1742+
}
1743+
17161744
int main(int argc, const char* argv[]) {
17171745
JsonTest::Runner runner;
17181746
JSONTEST_REGISTER_FIXTURE(runner, ValueTest, checkNormalizeFloatingPointStr);
@@ -1749,6 +1777,7 @@ int main(int argc, const char* argv[]) {
17491777
JSONTEST_REGISTER_FIXTURE(runner, CharReaderTest, parseWithOneError);
17501778
JSONTEST_REGISTER_FIXTURE(runner, CharReaderTest, parseChineseWithOneError);
17511779
JSONTEST_REGISTER_FIXTURE(runner, CharReaderTest, parseWithDetailError);
1780+
JSONTEST_REGISTER_FIXTURE(runner, CharReaderTest, parseWithStackLimit);
17521781

17531782
JSONTEST_REGISTER_FIXTURE(runner, WriterTest, dropNullPlaceholders);
17541783
JSONTEST_REGISTER_FIXTURE(runner, StreamWriterTest, dropNullPlaceholders);

0 commit comments

Comments
 (0)