Add recursion limit to folly::parseJson.
authorKyle Nekritz <knekritz@fb.com>
Wed, 27 Apr 2016 16:42:56 +0000 (09:42 -0700)
committerFacebook Github Bot 6 <facebook-github-bot-6-bot@fb.com>
Wed, 27 Apr 2016 16:50:29 +0000 (09:50 -0700)
Summary: Without this, malicious inputs can crash anything using folly::parseJson.

Reviewed By: yfeldblum

Differential Revision: D3219036

fb-gh-sync-id: 3604a060170c0201473c420035b21b018383789c
fbshipit-source-id: 3604a060170c0201473c420035b21b018383789c

folly/json.cpp
folly/json.h
folly/test/JsonTest.cpp

index 9d2a53656fb8dbc5aad8f1dada924273dc082b42..86600fd5d7ca21d011136b1e87fa498aaede8d95 100644 (file)
@@ -379,7 +379,18 @@ struct Input {
     return opts_;
   }
 
-private:
+  void incrementRecursionLevel() {
+    if (currentRecursionLevel_ > opts_.recursion_limit) {
+      error("recursion limit exceeded");
+    }
+    currentRecursionLevel_++;
+  }
+
+  void decrementRecursionLevel() {
+    currentRecursionLevel_--;
+  }
+
+ private:
   void storeCurrent() {
     current_ = range_.empty() ? EOF : range_.front();
   }
@@ -389,6 +400,21 @@ private:
   json::serialization_opts const& opts_;
   unsigned lineNum_;
   int current_;
+  unsigned int currentRecursionLevel_{0};
+};
+
+class RecursionGuard {
+ public:
+  explicit RecursionGuard(Input& in) : in_(in) {
+    in_.incrementRecursionLevel();
+  }
+
+  ~RecursionGuard() {
+    in_.decrementRecursionLevel();
+  }
+
+ private:
+  Input& in_;
 };
 
 dynamic parseValue(Input& in);
@@ -627,6 +653,8 @@ std::string parseString(Input& in) {
 }
 
 dynamic parseValue(Input& in) {
+  RecursionGuard guard(in);
+
   in.skipWhitespace();
   return *in == '[' ? parseArray(in) :
          *in == '{' ? parseObject(in) :
index 272d610b1010d3a86702cd811a449c56b084cbea..158352471f264682c858ca717a71df04985e9fdb 100644 (file)
@@ -54,20 +54,20 @@ namespace json {
 
   struct serialization_opts {
     explicit serialization_opts()
-      : allow_non_string_keys(false)
-      , javascript_safe(false)
-      , pretty_formatting(false)
-      , encode_non_ascii(false)
-      , validate_utf8(false)
-      , allow_trailing_comma(false)
-      , sort_keys(false)
-      , skip_invalid_utf8(false)
-      , allow_nan_inf(false)
-      , double_mode(double_conversion::DoubleToStringConverter::SHORTEST)
-      , double_num_digits(0) // ignored when mode is SHORTEST
-      , double_fallback(false)
-      , parse_numbers_as_strings(false)
-    {}
+        : allow_non_string_keys(false),
+          javascript_safe(false),
+          pretty_formatting(false),
+          encode_non_ascii(false),
+          validate_utf8(false),
+          allow_trailing_comma(false),
+          sort_keys(false),
+          skip_invalid_utf8(false),
+          allow_nan_inf(false),
+          double_mode(double_conversion::DoubleToStringConverter::SHORTEST),
+          double_num_digits(0), // ignored when mode is SHORTEST
+          double_fallback(false),
+          parse_numbers_as_strings(false),
+          recursion_limit(100) {}
 
     // If true, keys in an object can be non-strings.  (In strict
     // JSON, object keys must be strings.)  This is used by dynamic's
@@ -115,6 +115,9 @@ namespace json {
     // Do not parse numbers. Instead, store them as strings and leave the
     // conversion up to the user.
     bool parse_numbers_as_strings;
+
+    // Recursion limit when parsing.
+    unsigned int recursion_limit;
   };
 
   /*
index 64430ff51b95cc84edd394308f5e8882c44334eb..a9bca3bab9edfd1bef848ca1f9a361b7d628da8d 100644 (file)
@@ -517,3 +517,19 @@ TEST(Json, PrintTo) {
   PrintTo(value, &oss);
   EXPECT_EQ(expected, oss.str());
 }
+
+TEST(Json, RecursionLimit) {
+  std::string in;
+  for (int i = 0; i < 1000; i++) {
+    in.append("{\"x\":");
+  }
+  in.append("\"hi\"");
+  for (int i = 0; i < 1000; i++) {
+    in.append("}");
+  }
+  EXPECT_ANY_THROW(parseJson(in));
+
+  folly::json::serialization_opts opts_high_recursion_limit;
+  opts_high_recursion_limit.recursion_limit = 10000;
+  parseJson(in, opts_high_recursion_limit);
+}