Skip to content

Stack limit #166

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 11, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions include/json/reader.h
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,9 @@ class JSON_API CharReaderBuilder : public CharReader::Factory {
- true if dropped null placeholders are allowed. (See StreamWriterBuilder.)
- "allowNumericKeys": false or true
- true if numeric object keys are allowed.
- "stackLimit": integer
- This is a security issue (seg-faults caused by deeply nested JSON),
so the default is low.

You can examine 'settings_` yourself
to see the defaults. You can also write and read them just like any
Expand Down
26 changes: 24 additions & 2 deletions src/lib_json/json_reader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
#pragma warning(disable : 4996)
#endif

static int const stackLimit_g = 1000;
static int stackDepth_g = 0; // see readValue()

namespace Json {

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

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

bool Reader::readValue() {
// This is a non-reentrant way to support a stackLimit. Terrible!
// But this deprecated class has a security problem: Bad input can
// cause a seg-fault. This seems like a fair, binary-compatible way
// to prevent the problem.
if (stackDepth_g >= stackLimit_g) throw std::runtime_error("Exceeded stackLimit in readValue().");
++stackDepth_g;

Token token;
skipCommentTokens(token);
bool successful = true;
Expand Down Expand Up @@ -211,6 +222,7 @@ bool Reader::readValue() {
lastValue_ = &currentValue();
}

--stackDepth_g;
return successful;
}

Expand Down Expand Up @@ -902,7 +914,8 @@ class OurFeatures {
bool strictRoot_;
bool allowDroppedNullPlaceholders_;
bool allowNumericKeys_;
}; // OldFeatures
int stackLimit_;
}; // OurFeatures

// exact copy of Implementation of class Features
// ////////////////////////////////
Expand Down Expand Up @@ -1033,7 +1046,9 @@ class OurReader {
Location lastValueEnd_;
Value* lastValue_;
std::string commentsBefore_;
OurFeatures features_;
int stackDepth_;

OurFeatures const features_;
bool collectComments_;
}; // OurReader

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

stackDepth_ = 0;
bool successful = readValue();
Token token;
skipCommentTokens(token);
Expand All @@ -1086,6 +1102,8 @@ bool OurReader::parse(const char* beginDoc,
}

bool OurReader::readValue() {
if (stackDepth_ >= features_.stackLimit_) throw std::runtime_error("Exceeded stackLimit in readValue().");
++stackDepth_;
Token token;
skipCommentTokens(token);
bool successful = true;
Expand Down Expand Up @@ -1157,6 +1175,7 @@ bool OurReader::readValue() {
lastValue_ = &currentValue();
}

--stackDepth_;
return successful;
}

Expand Down Expand Up @@ -1853,6 +1872,7 @@ CharReader* CharReaderBuilder::newCharReader() const
features.strictRoot_ = settings_["strictRoot"].asBool();
features.allowDroppedNullPlaceholders_ = settings_["allowDroppedNullPlaceholders"].asBool();
features.allowNumericKeys_ = settings_["allowNumericKeys"].asBool();
features.stackLimit_ = settings_["stackLimit"].asInt();
return new OurCharReader(collectComments, features);
}
static void getValidReaderKeys(std::set<std::string>* valid_keys)
Expand All @@ -1863,6 +1883,7 @@ static void getValidReaderKeys(std::set<std::string>* valid_keys)
valid_keys->insert("strictRoot");
valid_keys->insert("allowDroppedNullPlaceholders");
valid_keys->insert("allowNumericKeys");
valid_keys->insert("stackLimit");
}
bool CharReaderBuilder::validate(Json::Value* invalid) const
{
Expand Down Expand Up @@ -1901,6 +1922,7 @@ void CharReaderBuilder::setDefaults(Json::Value* settings)
(*settings)["strictRoot"] = false;
(*settings)["allowDroppedNullPlaceholders"] = false;
(*settings)["allowNumericKeys"] = false;
(*settings)["stackLimit"] = 1000;
//! [CharReaderBuilderDefaults]
}

Expand Down
29 changes: 29 additions & 0 deletions src/test_lib_json/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1713,6 +1713,34 @@ JSONTEST_FIXTURE(CharReaderTest, parseWithDetailError) {
delete reader;
}

JSONTEST_FIXTURE(CharReaderTest, parseWithStackLimit) {
Json::CharReaderBuilder b;
Json::Value root;
char const doc[] =
"{ \"property\" : \"value\" }";
{
b.settings_["stackLimit"] = 2;
Json::CharReader* reader(b.newCharReader());
std::string errs;
bool ok = reader->parse(
doc, doc + std::strlen(doc),
&root, &errs);
JSONTEST_ASSERT(ok);
JSONTEST_ASSERT(errs == "");
JSONTEST_ASSERT_EQUAL("value", root["property"]);
delete reader;
}
{
b.settings_["stackLimit"] = 1;
Json::CharReader* reader(b.newCharReader());
std::string errs;
JSONTEST_ASSERT_THROWS(reader->parse(
doc, doc + std::strlen(doc),
&root, &errs));
delete reader;
}
}

int main(int argc, const char* argv[]) {
JsonTest::Runner runner;
JSONTEST_REGISTER_FIXTURE(runner, ValueTest, checkNormalizeFloatingPointStr);
Expand Down Expand Up @@ -1749,6 +1777,7 @@ int main(int argc, const char* argv[]) {
JSONTEST_REGISTER_FIXTURE(runner, CharReaderTest, parseWithOneError);
JSONTEST_REGISTER_FIXTURE(runner, CharReaderTest, parseChineseWithOneError);
JSONTEST_REGISTER_FIXTURE(runner, CharReaderTest, parseWithDetailError);
JSONTEST_REGISTER_FIXTURE(runner, CharReaderTest, parseWithStackLimit);

JSONTEST_REGISTER_FIXTURE(runner, WriterTest, dropNullPlaceholders);
JSONTEST_REGISTER_FIXTURE(runner, StreamWriterTest, dropNullPlaceholders);
Expand Down