Commit 3dece34c authored by Chris Davis's avatar Chris Davis Committed by Commit Bot

Ensure we protect against stack overflow in JSONWriter

When importing bookmarks from an html file with a deep
folder structure (>10k) we hit a stack overflow due to
the recursive calls in JSONWriter. Fix is to check we
do not exceed a max depth similar to JSONReader.

Bug: 1007626,355722

Change-Id: Iedd9d543b7168a08af1a325059860d79d4900661
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1823809
Commit-Queue: Chris Davis <chrdavis@microsoft.com>
Reviewed-by: default avatarGabriel Charette <gab@chromium.org>
Reviewed-by: default avatarRobert Sesek <rsesek@chromium.org>
Reviewed-by: default avatarBruce Dawson <brucedawson@chromium.org>
Cr-Commit-Position: refs/heads/master@{#703999}
parent 5d920648
...@@ -318,6 +318,7 @@ jumbo_component("base") { ...@@ -318,6 +318,7 @@ jumbo_component("base") {
"ios/scoped_critical_action.mm", "ios/scoped_critical_action.mm",
"ios/weak_nsobject.h", "ios/weak_nsobject.h",
"ios/weak_nsobject.mm", "ios/weak_nsobject.mm",
"json/json_common.h",
"json/json_file_value_serializer.cc", "json/json_file_value_serializer.cc",
"json/json_file_value_serializer.h", "json/json_file_value_serializer.h",
"json/json_parser.cc", "json/json_parser.cc",
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_JSON_JSON_COMMON_H_
#define BASE_JSON_JSON_COMMON_H_
#include <stddef.h>
#include "base/logging.h"
#include "base/macros.h"
namespace base {
namespace internal {
// Chosen to support 99.9% of documents found in the wild late 2016.
// http://crbug.com/673263
const size_t kAbsoluteMaxDepth = 200;
// Simple class that checks for maximum recursion/stack overflow.
class StackMarker {
public:
StackMarker(size_t max_depth, size_t* depth)
: max_depth_(max_depth), depth_(depth) {
++(*depth_);
DCHECK_LE(*depth_, max_depth_);
}
~StackMarker() { --(*depth_); }
bool IsTooDeep() const { return *depth_ >= max_depth_; }
private:
const size_t max_depth_;
size_t* const depth_;
DISALLOW_COPY_AND_ASSIGN(StackMarker);
};
} // namespace internal
} // namespace base
#endif // BASE_JSON_JSON_COMMON_H_
...@@ -26,28 +26,6 @@ namespace internal { ...@@ -26,28 +26,6 @@ namespace internal {
namespace { namespace {
const int32_t kExtendedASCIIStart = 0x80; const int32_t kExtendedASCIIStart = 0x80;
// Simple class that checks for maximum recursion/"stack overflow."
class StackMarker {
public:
StackMarker(int max_depth, int* depth)
: max_depth_(max_depth), depth_(depth) {
++(*depth_);
DCHECK_LE(*depth_, max_depth_);
}
~StackMarker() {
--(*depth_);
}
bool IsTooDeep() const { return *depth_ >= max_depth_; }
private:
const int max_depth_;
int* const depth_;
DISALLOW_COPY_AND_ASSIGN(StackMarker);
};
constexpr uint32_t kUnicodeReplacementPoint = 0xFFFD; constexpr uint32_t kUnicodeReplacementPoint = 0xFFFD;
} // namespace } // namespace
...@@ -55,7 +33,7 @@ constexpr uint32_t kUnicodeReplacementPoint = 0xFFFD; ...@@ -55,7 +33,7 @@ constexpr uint32_t kUnicodeReplacementPoint = 0xFFFD;
// This is U+FFFD. // This is U+FFFD.
const char kUnicodeReplacementString[] = "\xEF\xBF\xBD"; const char kUnicodeReplacementString[] = "\xEF\xBF\xBD";
JSONParser::JSONParser(int options, int max_depth) JSONParser::JSONParser(int options, size_t max_depth)
: options_(options), : options_(options),
max_depth_(max_depth), max_depth_(max_depth),
index_(0), index_(0),
...@@ -65,7 +43,7 @@ JSONParser::JSONParser(int options, int max_depth) ...@@ -65,7 +43,7 @@ JSONParser::JSONParser(int options, int max_depth)
error_code_(JSONReader::JSON_NO_ERROR), error_code_(JSONReader::JSON_NO_ERROR),
error_line_(0), error_line_(0),
error_column_(0) { error_column_(0) {
CHECK_LE(max_depth, JSONReader::kStackMaxDepth); CHECK_LE(max_depth, kAbsoluteMaxDepth);
} }
JSONParser::~JSONParser() = default; JSONParser::~JSONParser() = default;
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include "base/base_export.h" #include "base/base_export.h"
#include "base/compiler_specific.h" #include "base/compiler_specific.h"
#include "base/gtest_prod_util.h" #include "base/gtest_prod_util.h"
#include "base/json/json_common.h"
#include "base/json/json_reader.h" #include "base/json/json_reader.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/optional.h" #include "base/optional.h"
...@@ -43,7 +44,7 @@ class JSONParserTest; ...@@ -43,7 +44,7 @@ class JSONParserTest;
// of the next token. // of the next token.
class BASE_EXPORT JSONParser { class BASE_EXPORT JSONParser {
public: public:
JSONParser(int options, int max_depth = JSONReader::kStackMaxDepth); JSONParser(int options, size_t max_depth = kAbsoluteMaxDepth);
~JSONParser(); ~JSONParser();
// Parses the input string according to the set options and returns the // Parses the input string according to the set options and returns the
...@@ -215,7 +216,7 @@ class BASE_EXPORT JSONParser { ...@@ -215,7 +216,7 @@ class BASE_EXPORT JSONParser {
const int options_; const int options_;
// Maximum depth to parse. // Maximum depth to parse.
const int max_depth_; const size_t max_depth_;
// The input stream being parsed. Note: Not guaranteed to NUL-terminated. // The input stream being parsed. Note: Not guaranteed to NUL-terminated.
StringPiece input_; StringPiece input_;
...@@ -224,7 +225,7 @@ class BASE_EXPORT JSONParser { ...@@ -224,7 +225,7 @@ class BASE_EXPORT JSONParser {
int index_; int index_;
// The number of times the parser has recursed (current stack depth). // The number of times the parser has recursed (current stack depth).
int stack_depth_; size_t stack_depth_;
// The line number that the parser is at currently. // The line number that the parser is at currently.
int line_number_; int line_number_;
......
...@@ -13,10 +13,6 @@ ...@@ -13,10 +13,6 @@
namespace base { namespace base {
// Chosen to support 99.9% of documents found in the wild late 2016.
// http://crbug.com/673263
const int JSONReader::kStackMaxDepth = 200;
// Values 1000 and above are used by JSONFileValueSerializer::JsonFileError. // Values 1000 and above are used by JSONFileValueSerializer::JsonFileError.
static_assert(JSONReader::JSON_PARSE_ERROR_COUNT < 1000, static_assert(JSONReader::JSON_PARSE_ERROR_COUNT < 1000,
"JSONReader error out of bounds"); "JSONReader error out of bounds");
...@@ -49,20 +45,22 @@ JSONReader::ValueWithError::~ValueWithError() = default; ...@@ -49,20 +45,22 @@ JSONReader::ValueWithError::~ValueWithError() = default;
JSONReader::ValueWithError& JSONReader::ValueWithError::operator=( JSONReader::ValueWithError& JSONReader::ValueWithError::operator=(
ValueWithError&& other) = default; ValueWithError&& other) = default;
JSONReader::JSONReader(int options, int max_depth) JSONReader::JSONReader(int options, size_t max_depth)
: parser_(new internal::JSONParser(options, max_depth)) {} : parser_(new internal::JSONParser(options, max_depth)) {}
JSONReader::~JSONReader() = default; JSONReader::~JSONReader() = default;
// static // static
Optional<Value> JSONReader::Read(StringPiece json, int options, int max_depth) { Optional<Value> JSONReader::Read(StringPiece json,
int options,
size_t max_depth) {
internal::JSONParser parser(options, max_depth); internal::JSONParser parser(options, max_depth);
return parser.Parse(json); return parser.Parse(json);
} }
std::unique_ptr<Value> JSONReader::ReadDeprecated(StringPiece json, std::unique_ptr<Value> JSONReader::ReadDeprecated(StringPiece json,
int options, int options,
int max_depth) { size_t max_depth) {
Optional<Value> value = Read(json, options, max_depth); Optional<Value> value = Read(json, options, max_depth);
return value ? Value::ToUniquePtrValue(std::move(*value)) : nullptr; return value ? Value::ToUniquePtrValue(std::move(*value)) : nullptr;
} }
......
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
#include <string> #include <string>
#include "base/base_export.h" #include "base/base_export.h"
#include "base/json/json_common.h"
#include "base/optional.h" #include "base/optional.h"
#include "base/strings/string_piece.h" #include "base/strings/string_piece.h"
#include "base/values.h" #include "base/values.h"
...@@ -58,8 +59,6 @@ enum JSONParserOptions { ...@@ -58,8 +59,6 @@ enum JSONParserOptions {
class BASE_EXPORT JSONReader { class BASE_EXPORT JSONReader {
public: public:
static const int kStackMaxDepth;
// Error codes during parsing. // Error codes during parsing.
enum JsonParseError { enum JsonParseError {
JSON_NO_ERROR = 0, JSON_NO_ERROR = 0,
...@@ -105,7 +104,8 @@ class BASE_EXPORT JSONReader { ...@@ -105,7 +104,8 @@ class BASE_EXPORT JSONReader {
static const char kInputTooLarge[]; static const char kInputTooLarge[];
// Constructs a reader. // Constructs a reader.
JSONReader(int options = JSON_PARSE_RFC, int max_depth = kStackMaxDepth); JSONReader(int options = JSON_PARSE_RFC,
size_t max_depth = internal::kAbsoluteMaxDepth);
~JSONReader(); ~JSONReader();
...@@ -113,16 +113,17 @@ class BASE_EXPORT JSONReader { ...@@ -113,16 +113,17 @@ class BASE_EXPORT JSONReader {
// If |json| is not a properly formed JSON string, returns base::nullopt. // If |json| is not a properly formed JSON string, returns base::nullopt.
static Optional<Value> Read(StringPiece json, static Optional<Value> Read(StringPiece json,
int options = JSON_PARSE_RFC, int options = JSON_PARSE_RFC,
int max_depth = kStackMaxDepth); size_t max_depth = internal::kAbsoluteMaxDepth);
// Deprecated. Use the Read() method above. // Deprecated. Use the Read() method above.
// Reads and parses |json|, returning a Value. // Reads and parses |json|, returning a Value.
// If |json| is not a properly formed JSON string, returns nullptr. // If |json| is not a properly formed JSON string, returns nullptr.
// Wrap this in base::FooValue::From() to check the Value is of type Foo and // Wrap this in base::FooValue::From() to check the Value is of type Foo and
// convert to a FooValue at the same time. // convert to a FooValue at the same time.
static std::unique_ptr<Value> ReadDeprecated(StringPiece json, static std::unique_ptr<Value> ReadDeprecated(
StringPiece json,
int options = JSON_PARSE_RFC, int options = JSON_PARSE_RFC,
int max_depth = kStackMaxDepth); size_t max_depth = internal::kAbsoluteMaxDepth);
// Reads and parses |json| like Read(). Returns a ValueWithError, which on // Reads and parses |json| like Read(). Returns a ValueWithError, which on
// error, will be populated with a formatted error message, an error code, and // error, will be populated with a formatted error message, an error code, and
......
...@@ -25,19 +25,20 @@ const char kPrettyPrintLineEnding[] = "\n"; ...@@ -25,19 +25,20 @@ const char kPrettyPrintLineEnding[] = "\n";
#endif #endif
// static // static
bool JSONWriter::Write(const Value& node, std::string* json) { bool JSONWriter::Write(const Value& node, std::string* json, size_t max_depth) {
return WriteWithOptions(node, 0, json); return WriteWithOptions(node, 0, json, max_depth);
} }
// static // static
bool JSONWriter::WriteWithOptions(const Value& node, bool JSONWriter::WriteWithOptions(const Value& node,
int options, int options,
std::string* json) { std::string* json,
size_t max_depth) {
json->clear(); json->clear();
// Is there a better way to estimate the size of the output? // Is there a better way to estimate the size of the output?
json->reserve(1024); json->reserve(1024);
JSONWriter writer(options, json); JSONWriter writer(options, json, max_depth);
bool result = writer.BuildJSONString(node, 0U); bool result = writer.BuildJSONString(node, 0U);
if (options & OPTIONS_PRETTY_PRINT) if (options & OPTIONS_PRETTY_PRINT)
...@@ -46,16 +47,23 @@ bool JSONWriter::WriteWithOptions(const Value& node, ...@@ -46,16 +47,23 @@ bool JSONWriter::WriteWithOptions(const Value& node,
return result; return result;
} }
JSONWriter::JSONWriter(int options, std::string* json) JSONWriter::JSONWriter(int options, std::string* json, size_t max_depth)
: omit_binary_values_((options & OPTIONS_OMIT_BINARY_VALUES) != 0), : omit_binary_values_((options & OPTIONS_OMIT_BINARY_VALUES) != 0),
omit_double_type_preservation_( omit_double_type_preservation_(
(options & OPTIONS_OMIT_DOUBLE_TYPE_PRESERVATION) != 0), (options & OPTIONS_OMIT_DOUBLE_TYPE_PRESERVATION) != 0),
pretty_print_((options & OPTIONS_PRETTY_PRINT) != 0), pretty_print_((options & OPTIONS_PRETTY_PRINT) != 0),
json_string_(json) { json_string_(json),
max_depth_(max_depth),
stack_depth_(0) {
DCHECK(json); DCHECK(json);
CHECK_LE(max_depth, internal::kAbsoluteMaxDepth);
} }
bool JSONWriter::BuildJSONString(const Value& node, size_t depth) { bool JSONWriter::BuildJSONString(const Value& node, size_t depth) {
internal::StackMarker depth_check(max_depth_, &stack_depth_);
if (depth_check.IsTooDeep())
return false;
switch (node.type()) { switch (node.type()) {
case Value::Type::NONE: case Value::Type::NONE:
json_string_->append("null"); json_string_->append("null");
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include <string> #include <string>
#include "base/base_export.h" #include "base/base_export.h"
#include "base/json/json_common.h"
#include "base/macros.h" #include "base/macros.h"
namespace base { namespace base {
...@@ -42,16 +43,21 @@ class BASE_EXPORT JSONWriter { ...@@ -42,16 +43,21 @@ class BASE_EXPORT JSONWriter {
// TODO(tc): Should we generate json if it would be invalid json (e.g., // TODO(tc): Should we generate json if it would be invalid json (e.g.,
// |node| is not a DictionaryValue/ListValue or if there are inf/-inf float // |node| is not a DictionaryValue/ListValue or if there are inf/-inf float
// values)? Return true on success and false on failure. // values)? Return true on success and false on failure.
static bool Write(const Value& node, std::string* json); static bool Write(const Value& node,
std::string* json,
size_t max_depth = internal::kAbsoluteMaxDepth);
// Same as above but with |options| which is a bunch of JSONWriter::Options // Same as above but with |options| which is a bunch of JSONWriter::Options
// bitwise ORed together. Return true on success and false on failure. // bitwise ORed together. Return true on success and false on failure.
static bool WriteWithOptions(const Value& node, static bool WriteWithOptions(const Value& node,
int options, int options,
std::string* json); std::string* json,
size_t max_depth = internal::kAbsoluteMaxDepth);
private: private:
JSONWriter(int options, std::string* json); JSONWriter(int options,
std::string* json,
size_t max_depth = internal::kAbsoluteMaxDepth);
// Called recursively to build the JSON string. When completed, // Called recursively to build the JSON string. When completed,
// |json_string_| will contain the JSON. // |json_string_| will contain the JSON.
...@@ -67,6 +73,12 @@ class BASE_EXPORT JSONWriter { ...@@ -67,6 +73,12 @@ class BASE_EXPORT JSONWriter {
// Where we write JSON data as we generate it. // Where we write JSON data as we generate it.
std::string* json_string_; std::string* json_string_;
// Maximum depth to write.
const size_t max_depth_;
// The number of times the writer has recursed (current stack depth).
size_t stack_depth_;
DISALLOW_COPY_AND_ASSIGN(JSONWriter); DISALLOW_COPY_AND_ASSIGN(JSONWriter);
}; };
......
...@@ -154,4 +154,36 @@ TEST(JSONWriterTest, DoublesAsInts) { ...@@ -154,4 +154,36 @@ TEST(JSONWriterTest, DoublesAsInts) {
EXPECT_EQ("10000000000", output_js); EXPECT_EQ("10000000000", output_js);
} }
TEST(JSONWriterTest, StackOverflow) {
std::string output_js;
ListValue deep_list;
ListValue* next_list = &deep_list;
const size_t max_depth = 100000;
for (size_t i = 0; i < max_depth; ++i) {
ListValue inner_list;
next_list->Append(std::move(inner_list));
next_list->GetList(0, &next_list);
}
EXPECT_FALSE(JSONWriter::Write(deep_list, &output_js));
EXPECT_FALSE(JSONWriter::WriteWithOptions(
deep_list, JSONWriter::OPTIONS_PRETTY_PRINT, &output_js));
// We cannot just let deep_list tear down since it
// would cause a stack overflow. Therefore, we tear
// down from the inner lists outwards safely.
const size_t step = 200;
for (size_t i = max_depth - step; i > 0; i -= step) {
next_list = &deep_list;
for (size_t curr_depth = 0; curr_depth < i && next_list; ++curr_depth) {
if (!next_list->GetList(0, &next_list))
next_list = nullptr;
}
if (next_list)
next_list->Remove(0, nullptr);
}
}
} // namespace base } // namespace base
...@@ -54,6 +54,13 @@ namespace { ...@@ -54,6 +54,13 @@ namespace {
// Some extensions we'll tack on to copies of the Preferences files. // Some extensions we'll tack on to copies of the Preferences files.
const base::FilePath::CharType kBadExtension[] = FILE_PATH_LITERAL("bad"); const base::FilePath::CharType kBadExtension[] = FILE_PATH_LITERAL("bad");
bool BackupPrefsFile(const base::FilePath& path) {
const base::FilePath bad = path.ReplaceExtension(kBadExtension);
const bool bad_existed = base::PathExists(bad);
base::Move(path, bad);
return bad_existed;
}
PersistentPrefStore::PrefReadError HandleReadErrors( PersistentPrefStore::PrefReadError HandleReadErrors(
const base::Value* value, const base::Value* value,
const base::FilePath& path, const base::FilePath& path,
...@@ -78,13 +85,10 @@ PersistentPrefStore::PrefReadError HandleReadErrors( ...@@ -78,13 +85,10 @@ PersistentPrefStore::PrefReadError HandleReadErrors(
// We keep the old file for possible support and debugging assistance // We keep the old file for possible support and debugging assistance
// as well as to detect if they're seeing these errors repeatedly. // as well as to detect if they're seeing these errors repeatedly.
// TODO(erikkay) Instead, use the last known good file. // TODO(erikkay) Instead, use the last known good file.
base::FilePath bad = path.ReplaceExtension(kBadExtension);
// If they've ever had a parse error before, put them in another bucket. // If they've ever had a parse error before, put them in another bucket.
// TODO(erikkay) if we keep this error checking for very long, we may // TODO(erikkay) if we keep this error checking for very long, we may
// want to differentiate between recent and long ago errors. // want to differentiate between recent and long ago errors.
bool bad_existed = base::PathExists(bad); const bool bad_existed = BackupPrefsFile(path);
base::Move(path, bad);
return bad_existed ? PersistentPrefStore::PREF_READ_ERROR_JSON_REPEAT return bad_existed ? PersistentPrefStore::PREF_READ_ERROR_JSON_REPEAT
: PersistentPrefStore::PREF_READ_ERROR_JSON_PARSE; : PersistentPrefStore::PREF_READ_ERROR_JSON_PARSE;
} }
...@@ -476,8 +480,16 @@ bool JsonPrefStore::SerializeData(std::string* output) { ...@@ -476,8 +480,16 @@ bool JsonPrefStore::SerializeData(std::string* output) {
// readable prefs for debugging purposes, you can dump your prefs into any // readable prefs for debugging purposes, you can dump your prefs into any
// command-line or online JSON pretty printing tool. // command-line or online JSON pretty printing tool.
serializer.set_pretty_print(false); serializer.set_pretty_print(false);
bool success = serializer.Serialize(*prefs_); const bool success = serializer.Serialize(*prefs_);
DCHECK(success); if (!success) {
// Failed to serialize prefs file. Backup the existing prefs file and
// reset our existing prefs.
BackupPrefsFile(path_);
CHECK(false) << "Failed to serialize preferences : " << path_
<< "\nBacked up under "
<< path_.ReplaceExtension(kBadExtension);
prefs_.reset(new base::DictionaryValue());
}
return success; return success;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment