CVE-2020-1898: Uncontrolled Recursion (CWE-674)
value: The fb_unserialize function did not impose a depth limit for nested deserialization. That meant a maliciously constructed string could cause deserialization to recurse, leading to stack exhaustion. This issue affected HHVM prior to v4.32.3, between versions 4.33.0 and 4.56.0, 4.57.0, 4.58.0, 4.58.1, 4.59.0, 4.60.0, 4.61.0, 4.62.0.
CWE description: The product does not properly control the amount of recursion that takes place,  consuming excessive resources, such as allocated memory or the program stack.
CVSS v3 base score: 7.5
Published: 2021-03-11T01:15Z
Repository: https://github.com/facebook/hhvm
Language: C
Files changed: hphp/hack/hhi/builtins_fb.hhi, hphp/runtime/ext/fb/FBSerialize/FBSerialize-inl.h, hphp/runtime/ext/fb/FBSerialize/FBSerialize.h, hphp/runtime/ext/fb/VariantController.h, hphp/runtime/ext/fb/ext_fb.cpp, hphp/test/slow/ext_fb/unserialize-bad-set.php, hphp/test/slow/ext_fb/unserialize-bad-set.php.expect, hphp/test/slow/ext_fb/unserialize_max_depth.php, hphp/test/slow/ext_fb/unserialize_max_depth.php.expect
Commit date: 2020-06-30 16:27:28 -0700
Commit message: CVE-2020-1898

Don't allow for unlimited nesting in FBUnserialize or fb_compact_unserialize

Diff: diff --git a/hphp/hack/hhi/builtins_fb.hhi b/hphp/hack/hhi/builtins_fb.hhi
index a5ac7dd69c2d..35236b8ad3b2 100644
--- a/hphp/hack/hhi/builtins_fb.hhi
+++ b/hphp/hack/hhi/builtins_fb.hhi
@@ -3,10 +3,11 @@
 namespace {
 const FB_UNSERIALIZE_NONSTRING_VALUE = 0;
 const FB_UNSERIALIZE_UNEXPECTED_END = 0;
 const FB_UNSERIALIZE_UNRECOGNIZED_OBJECT_TYPE = 0;
 const FB_UNSERIALIZE_UNEXPECTED_ARRAY_KEY_TYPE = 0;
+const FB_UNSERIALIZE_MAX_DEPTH_EXCEEDED = 0;
 
 const FB_SERIALIZE_HACK_ARRAYS = 0;
 const FB_SERIALIZE_HACK_ARRAYS_AND_KEYSETS = 0;
 const FB_SERIALIZE_VARRAY_DARRAY = 0;
 
diff --git a/hphp/runtime/ext/fb/FBSerialize/FBSerialize-inl.h b/hphp/runtime/ext/fb/FBSerialize/FBSerialize-inl.h
index 3f56c3ea8f58..5a059a9ea6db 100644
--- a/hphp/runtime/ext/fb/FBSerialize/FBSerialize-inl.h
+++ b/hphp/runtime/ext/fb/FBSerialize/FBSerialize-inl.h
@@ -404,11 +404,11 @@ size_t FBSerializer<V>::serializedSizeSet(
 template <class V>
 inline typename V::VariantType FBUnserializer<V>::unserialize(
   folly::StringPiece serialized) {
 
   FBUnserializer<V> unserializer(serialized);
-  return unserializer.unserializeThing();
+  return unserializer.unserializeThing(0);
 }
 
 template <class V>
 FBUnserializer<V>::FBUnserializer(folly::StringPiece serialized) :
     p_(serialized.data()), end_(p_ + serialized.size()) {
@@ -522,11 +522,11 @@ inline FBSerializeBase::Code FBUnserializer<V>::nextCode() const {
   need(CODE_SIZE);
   return static_cast<Code>(*p_);
 }
 
 template <class V>
-inline typename V::MapType FBUnserializer<V>::unserializeMap() {
+inline typename V::MapType FBUnserializer<V>::unserializeMap(size_t depth) {
   p_ += CODE_SIZE;
 
   typename V::MapType ret = V::createMap();
 
   size_t code = nextCode();
@@ -534,18 +534,18 @@ inline typename V::MapType FBUnserializer<V>::unserializeMap() {
     switch (code) {
       case FB_SERIALIZE_VARCHAR:
       case FB_SERIALIZE_STRING:
         {
           auto key = unserializeString();
-          auto value = unserializeThing();
+          auto value = unserializeThing(depth + 1);
           V::mapSet(ret, std::move(key), std::move(value));
         }
         break;
       default:
         {
           auto key = unserializeInt64();
-          auto value = unserializeThing();
+          auto value = unserializeThing(depth + 1);
           V::mapSet(ret, std::move(key), std::move(value));
         }
     }
 
     code = nextCode();
@@ -554,56 +554,57 @@ inline typename V::MapType FBUnserializer<V>::unserializeMap() {
 
   return ret;
 }
 
 template <class V>
-inline typename V::VectorType FBUnserializer<V>::unserializeVector() {
+inline typename V::VectorType
+FBUnserializer<V>::unserializeVector(size_t depth) {
   p_ += CODE_SIZE;
 
   typename V::VectorType ret = V::createVector();
 
   size_t code = nextCode();
   while (code != FB_SERIALIZE_STOP) {
-    V::vectorAppend(ret, unserializeThing());
+    V::vectorAppend(ret, unserializeThing(depth + 1));
     code = nextCode();
   }
   p_ += CODE_SIZE;
   return ret;
 }
 
 template <class V>
-inline typename V::VectorType FBUnserializer<V>::unserializeList() {
+inline typename V::VectorType FBUnserializer<V>::unserializeList(size_t depth) {
   p_ += CODE_SIZE;
 
   // the list size is written so we can reserve it in the vector
   // in future. Skip past it for now.
   unserializeInt64();
 
   typename V::VectorType ret = V::createVector();
 
   size_t code = nextCode();
   while (code != FB_SERIALIZE_STOP) {
-    V::vectorAppend(ret, unserializeThing());
+    V::vectorAppend(ret, unserializeThing(depth + 1));
     code = nextCode();
   }
   p_ += CODE_SIZE;
   return ret;
 }
 
 template <class V>
-inline typename V::SetType FBUnserializer<V>::unserializeSet() {
+inline typename V::SetType FBUnserializer<V>::unserializeSet(size_t depth) {
   p_ += CODE_SIZE;
 
   // the set size is written so we can reserve it in the set
   // in future. Skip past it for now.
   unserializeInt64();
 
   typename V::SetType ret = V::createSet();
 
   size_t code = nextCode();
   while (code != FB_SERIALIZE_STOP) {
-    V::setAppend(ret, unserializeThing());
+    V::setAppend(ret, unserializeThing(depth + 1));
     code = nextCode();
   }
   p_ += CODE_SIZE;
   return ret;
 }
@@ -662,11 +663,16 @@ inline folly::StringPiece FBUnserializer<V>::getSerializedMap() {
   const char* tail = p_;
   return folly::StringPiece(head, tail);
 }
 
 template <class V>
-inline typename V::VariantType FBUnserializer<V>::unserializeThing() {
+inline typename V::VariantType
+FBUnserializer<V>::unserializeThing(size_t depth) {
+  if (UNLIKELY(depth > 1024)) {
+    throw UnserializeError("depth > 1024");
+  }
+
   size_t code = nextCode();
 
   switch (code) {
     case FB_SERIALIZE_BYTE:
     case FB_SERIALIZE_I16:
@@ -675,24 +681,24 @@ inline typename V::VariantType FBUnserializer<V>::unserializeThing() {
       return V::fromInt64(unserializeInt64());
     case FB_SERIALIZE_VARCHAR:
     case FB_SERIALIZE_STRING:
       return V::fromString(unserializeString());
     case FB_SERIALIZE_STRUCT:
-      return V::fromMap(unserializeMap());
+      return V::fromMap(unserializeMap(depth));
     case FB_SERIALIZE_NULL:
       ++p_;
       return V::createNull();
     case FB_SERIALIZE_DOUBLE:
       return V::fromDouble(unserializeDouble());
     case FB_SERIALIZE_BOOLEAN:
       return V::fromBool(unserializeBoolean());
     case FB_SERIALIZE_VECTOR:
-      return V::fromVector(unserializeVector());
+      return V::fromVector(unserializeVector(depth));
     case FB_SERIALIZE_LIST:
-      return V::fromVector(unserializeList());
+      return V::fromVector(unserializeList(depth));
     case FB_SERIALIZE_SET:
-      return V::fromSet(unserializeSet());
+      return V::fromSet(unserializeSet(depth));
     default:
       throw UnserializeError("Invalid code: " + folly::to<std::string>(code)
                              + " at location " + folly::to<std::string>(p_));
   }
 }
diff --git a/hphp/runtime/ext/fb/FBSerialize/FBSerialize.h b/hphp/runtime/ext/fb/FBSerialize/FBSerialize.h
index d3981b547c55..e04b1eb05131 100644
--- a/hphp/runtime/ext/fb/FBSerialize/FBSerialize.h
+++ b/hphp/runtime/ext/fb/FBSerialize/FBSerialize.h
@@ -163,18 +163,18 @@ struct FBUnserializer : private FBSerializeBase {
   bool unserializeBoolean();
   int64_t unserializeInt64();
   double unserializeDouble();
   typename V::StringType unserializeString();
   folly::StringPiece unserializeStringPiece();
-  typename V::MapType unserializeMap();
-  typename V::VectorType unserializeVector();
-  typename V::VectorType unserializeList();
-  typename V::SetType unserializeSet();
+  typename V::MapType unserializeMap(size_t depth);
+  typename V::VectorType unserializeVector(size_t depth);
+  typename V::VectorType unserializeList(size_t depth);
+  typename V::SetType unserializeSet(size_t depth);
   // read the next map but don't unserialze it (for lazy or delay
   // unserialization)
   folly::StringPiece getSerializedMap();
-  typename V::VariantType unserializeThing();
+  typename V::VariantType unserializeThing(size_t depth);
 
   void advance(size_t delta);
   Code nextCode() const;
   bool done() const {
     return p_ == end_;
diff --git a/hphp/runtime/ext/fb/VariantController.h b/hphp/runtime/ext/fb/VariantController.h
index 9275fa055bb9..40fdad785a8a 100644
--- a/hphp/runtime/ext/fb/VariantController.h
+++ b/hphp/runtime/ext/fb/VariantController.h
@@ -281,16 +281,14 @@ struct VariantControllerImpl {
   }
   static int64_t setSize(const SetType& set) {
     return set.size();
   }
   static void setAppend(SetType& set, const VariantType& v) {
-    auto value_type = type(v);
-    if (value_type != HPHP::serialize::Type::INT64 &&
-        value_type != HPHP::serialize::Type::STRING) {
+    if (!v.isInteger() && !v.isString()) {
       throw HPHP::serialize::UnserializeError(
-          "Unsupported keyset element of type " +
-          folly::to<std::string>(value_type));
+        "Keysets can only contain integers or strings"
+      );
     }
     set.append(v);
   }
   static ArrayIter setIterator(const VectorType& set) {
     return ArrayIter(set);
diff --git a/hphp/runtime/ext/fb/ext_fb.cpp b/hphp/runtime/ext/fb/ext_fb.cpp
index 5efea9415136..a364a09e8484 100644
--- a/hphp/runtime/ext/fb/ext_fb.cpp
+++ b/hphp/runtime/ext/fb/ext_fb.cpp
@@ -64,10 +64,11 @@ static const UChar32 SUBSTITUTION_CHARACTER = 0xFFFD;
 
 #define FB_UNSERIALIZE_NONSTRING_VALUE           0x0001
 #define FB_UNSERIALIZE_UNEXPECTED_END            0x0002
 #define FB_UNSERIALIZE_UNRECOGNIZED_OBJECT_TYPE  0x0003
 #define FB_UNSERIALIZE_UNEXPECTED_ARRAY_KEY_TYPE 0x0004
+#define FB_UNSERIALIZE_MAX_DEPTH_EXCEEDED        0x0005
 
 #ifdef FACEBOOK
 # define HHVM_FACEBOOK true
 #else
 # define HHVM_FACEBOOK false
@@ -703,11 +704,14 @@ int fb_compact_unserialize_int64_from_buffer(
 }
 
 const StaticString s_empty("");
 
 int fb_compact_unserialize_from_buffer(
-  Variant& out, const char* buf, int n, int& p) {
+    Variant& out, const char* buf, int n, int& p, size_t depth) {
+  if (UNLIKELY(depth > 1024)) {
+    return FB_UNSERIALIZE_MAX_DEPTH_EXCEEDED;
+  }
 
   CHECK_ENOUGH(1, p, n);
   int code = (unsigned char)buf[p];
   if ((code & ~kCodeMask) != kCodePrefix ||
       (code & kCodeMask) == FB_CS_INT16 ||
@@ -772,11 +776,12 @@ int fb_compact_unserialize_from_buffer(
     case FB_CS_VECTOR:
     {
       Array arr = Array::CreateVArray();
       while (p < n && buf[p] != (char)(kCodePrefix | FB_CS_STOP)) {
         Variant value;
-        int err = fb_compact_unserialize_from_buffer(value, buf, n, p);
+        int err =
+          fb_compact_unserialize_from_buffer(value, buf, n, p, depth + 1);
         if (err) {
           return err;
         }
         arr.append(value);
       }
@@ -797,11 +802,12 @@ int fb_compact_unserialize_from_buffer(
         if (buf[p] == (char)(kCodePrefix | FB_CS_SKIP)) {
           ++i;
           ++p;
         } else {
           Variant value;
-          int err = fb_compact_unserialize_from_buffer(value, buf, n, p);
+          int err =
+            fb_compact_unserialize_from_buffer(value, buf, n, p, depth + 1);
           if (err) {
             return err;
           }
           arr.set(i++, value);
         }
@@ -818,16 +824,16 @@ int fb_compact_unserialize_from_buffer(
     case FB_CS_MAP:
     {
       Array arr = Array::CreateDArray();
       while (p < n && buf[p] != (char)(kCodePrefix | FB_CS_STOP)) {
         Variant key;
-        int err = fb_compact_unserialize_from_buffer(key, buf, n, p);
+        int err = fb_compact_unserialize_from_buffer(key, buf, n, p, depth + 1);
         if (err) {
           return err;
         }
         Variant value;
-        err = fb_compact_unserialize_from_buffer(value, buf, n, p);
+        err = fb_compact_unserialize_from_buffer(value, buf, n, p, depth + 1);
         if (err) {
           return err;
         }
         if (key.getType() == KindOfInt64) {
           arr.set(key.toInt64(), value);
@@ -859,11 +865,11 @@ Variant fb_compact_unserialize(const char* str, int len,
                                bool& success,
                                Variant& errcode) {
 
   Variant ret;
   int p = 0;
-  int err = fb_compact_unserialize_from_buffer(ret, str, len, p);
+  int err = fb_compact_unserialize_from_buffer(ret, str, len, p, 0);
   if (err) {
     success = false;
     errcode = err;
     return false;
   }
@@ -1311,10 +1317,11 @@ struct FBExtension : Extension {
     HHVM_RC_BOOL(HHVM_ONE_BIT_REFCOUNT, one_bit_refcount);
     HHVM_RC_INT_SAME(FB_UNSERIALIZE_NONSTRING_VALUE);
     HHVM_RC_INT_SAME(FB_UNSERIALIZE_UNEXPECTED_END);
     HHVM_RC_INT_SAME(FB_UNSERIALIZE_UNRECOGNIZED_OBJECT_TYPE);
     HHVM_RC_INT_SAME(FB_UNSERIALIZE_UNEXPECTED_ARRAY_KEY_TYPE);
+    HHVM_RC_INT_SAME(FB_UNSERIALIZE_MAX_DEPTH_EXCEEDED);
 
     HHVM_RC_INT(FB_SERIALIZE_HACK_ARRAYS, k_FB_SERIALIZE_HACK_ARRAYS);
     HHVM_RC_INT(FB_SERIALIZE_VARRAY_DARRAY, k_FB_SERIALIZE_VARRAY_DARRAY);
     HHVM_RC_INT(FB_SERIALIZE_HACK_ARRAYS_AND_KEYSETS,
                 k_FB_SERIALIZE_HACK_ARRAYS_AND_KEYSETS);
diff --git a/hphp/test/slow/ext_fb/unserialize-bad-set.php b/hphp/test/slow/ext_fb/unserialize-bad-set.php
new file mode 100644
index 000000000000..ce3f4306353b
--- /dev/null
+++ b/hphp/test/slow/ext_fb/unserialize-bad-set.php
@@ -0,0 +1,9 @@
+<?hh
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+
+<<__EntryPoint>>
+function main() {
+  $ret = null;
+  var_dump(fb_unserialize("\x14\x02\x01\x14\x02\x01\x02\x01\x01\x01", inout $ret));
+  var_dump($ret);
+}
diff --git a/hphp/test/slow/ext_fb/unserialize-bad-set.php.expect b/hphp/test/slow/ext_fb/unserialize-bad-set.php.expect
new file mode 100644
index 000000000000..74d3e6d82689
--- /dev/null
+++ b/hphp/test/slow/ext_fb/unserialize-bad-set.php.expect
@@ -0,0 +1,2 @@
+bool(false)
+bool(false)
diff --git a/hphp/test/slow/ext_fb/unserialize_max_depth.php b/hphp/test/slow/ext_fb/unserialize_max_depth.php
new file mode 100644
index 000000000000..b29c7a6bab5a
--- /dev/null
+++ b/hphp/test/slow/ext_fb/unserialize_max_depth.php
@@ -0,0 +1,53 @@
+<?hh
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+
+function make_fb_serialize_struct($size) {
+  return str_repeat("\x0a\x02\x01", $size) . "\x02\x01" . str_repeat("\x01", $size);
+}
+
+function make_fb_serialize_vector($size) {
+  return str_repeat("\x12", $size) . str_repeat("\x01", $size);
+}
+
+function make_fb_serialize_list($size) {
+  return str_repeat("\x13\x02\x01", $size) . "\x02\x01" . str_repeat("\x01", $size);
+}
+
+function make_fb_cs_list_map($size) {
+  return str_repeat("\xfa\xfd", $size) . str_repeat("\xfc", $size);
+}
+
+function make_fb_cs_map($size) {
+  return str_repeat("\xfb\x01", $size) . "\x01" . str_repeat("\xfc", $size);
+}
+
+function make_fb_cs_vector($size) {
+  return str_repeat("\xfe", $size) . str_repeat("\xfc", $size);
+}
+
+function test($serialized) {
+  $ret = null;
+  var_dump(
+    fb_unserialize(
+      $serialized,
+      inout $ret,
+      FB_SERIALIZE_HACK_ARRAYS
+    )
+  );
+  var_dump($ret);
+}
+
+function tests($size) {
+  test(make_fb_serialize_struct($size));
+  test(make_fb_serialize_vector($size));
+  test(make_fb_serialize_list($size));
+  test(make_fb_cs_list_map($size));
+  test(make_fb_cs_map($size));
+  test(make_fb_cs_vector($size));
+}
+
+<<__EntryPoint>>
+function main() {
+  tests(10);
+  tests(1026);
+}
diff --git a/hphp/test/slow/ext_fb/unserialize_max_depth.php.expect b/hphp/test/slow/ext_fb/unserialize_max_depth.php.expect
new file mode 100644
index 000000000000..b45fbb89b627
--- /dev/null
+++ b/hphp/test/slow/ext_fb/unserialize_max_depth.php.expect
@@ -0,0 +1,179 @@
+dict(1) {
+  [1]=>
+  dict(1) {
+    [1]=>
+    dict(1) {
+      [1]=>
+      dict(1) {
+        [1]=>
+        dict(1) {
+          [1]=>
+          dict(1) {
+            [1]=>
+            dict(1) {
+              [1]=>
+              dict(1) {
+                [1]=>
+                dict(1) {
+                  [1]=>
+                  dict(1) {
+                    [1]=>
+                    int(1)
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+bool(true)
+vec(1) {
+  vec(1) {
+    vec(1) {
+      vec(1) {
+        vec(1) {
+          vec(1) {
+            vec(1) {
+              vec(1) {
+                vec(1) {
+                  vec(0) {
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+bool(true)
+vec(1) {
+  vec(1) {
+    vec(1) {
+      vec(1) {
+        vec(1) {
+          vec(1) {
+            vec(1) {
+              vec(1) {
+                vec(1) {
+                  vec(1) {
+                    int(1)
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+bool(true)
+array(1) {
+  [1]=>
+  array(1) {
+    [1]=>
+    array(1) {
+      [1]=>
+      array(1) {
+        [1]=>
+        array(1) {
+          [1]=>
+          array(1) {
+            [1]=>
+            array(1) {
+              [1]=>
+              array(1) {
+                [1]=>
+                array(1) {
+                  [1]=>
+                  array(0) {
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+bool(true)
+array(1) {
+  [1]=>
+  array(1) {
+    [1]=>
+    array(1) {
+      [1]=>
+      array(1) {
+        [1]=>
+        array(1) {
+          [1]=>
+          array(1) {
+            [1]=>
+            array(1) {
+              [1]=>
+              array(1) {
+                [1]=>
+                array(1) {
+                  [1]=>
+                  array(1) {
+                    [1]=>
+                    int(1)
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+bool(true)
+array(1) {
+  [0]=>
+  array(1) {
+    [0]=>
+    array(1) {
+      [0]=>
+      array(1) {
+        [0]=>
+        array(1) {
+          [0]=>
+          array(1) {
+            [0]=>
+            array(1) {
+              [0]=>
+              array(1) {
+                [0]=>
+                array(1) {
+                  [0]=>
+                  array(0) {
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+bool(true)
+bool(false)
+bool(false)
+bool(false)
+bool(false)
+bool(false)
+bool(false)
+bool(false)
+bool(false)
+bool(false)
+bool(false)
+bool(false)
+bool(false)
\ No newline at end of file

Vulnerable code:   return unserializer.unserializeThing();
inline typename V::MapType FBUnserializer<V>::unserializeMap() {
          auto value = unserializeThing();
          auto value = unserializeThing();
inline typename V::VectorType FBUnserializer<V>::unserializeVector() {
    V::vectorAppend(ret, unserializeThing());
inline typename V::VectorType FBUnserializer<V>::unserializeList() {
    V::vectorAppend(ret, unserializeThing());
inline typename V::SetType FBUnserializer<V>::unserializeSet() {
    V::setAppend(ret, unserializeThing());
inline typename V::VariantType FBUnserializer<V>::unserializeThing() {
      return V::fromMap(unserializeMap());
      return V::fromVector(unserializeVector());
      return V::fromVector(unserializeList());
      return V::fromSet(unserializeSet());
  typename V::MapType unserializeMap();
  typename V::VectorType unserializeVector();
  typename V::VectorType unserializeList();
  typename V::SetType unserializeSet();
  typename V::VariantType unserializeThing();
    auto value_type = type(v);
    if (value_type != HPHP::serialize::Type::INT64 &&
        value_type != HPHP::serialize::Type::STRING) {
          "Unsupported keyset element of type " +
          folly::to<std::string>(value_type));
  Variant& out, const char* buf, int n, int& p) {
        int err = fb_compact_unserialize_from_buffer(value, buf, n, p);
          int err = fb_compact_unserialize_from_buffer(value, buf, n, p);
        int err = fb_compact_unserialize_from_buffer(key, buf, n, p);
        err = fb_compact_unserialize_from_buffer(value, buf, n, p);
  int err = fb_compact_unserialize_from_buffer(ret, str, len, p);
Fixed code: const FB_UNSERIALIZE_MAX_DEPTH_EXCEEDED = 0;
  return unserializer.unserializeThing(0);
inline typename V::MapType FBUnserializer<V>::unserializeMap(size_t depth) {
          auto value = unserializeThing(depth + 1);
          auto value = unserializeThing(depth + 1);
inline typename V::VectorType
FBUnserializer<V>::unserializeVector(size_t depth) {
    V::vectorAppend(ret, unserializeThing(depth + 1));
inline typename V::VectorType FBUnserializer<V>::unserializeList(size_t depth) {
    V::vectorAppend(ret, unserializeThing(depth + 1));
inline typename V::SetType FBUnserializer<V>::unserializeSet(size_t depth) {
    V::setAppend(ret, unserializeThing(depth + 1));
inline typename V::VariantType
FBUnserializer<V>::unserializeThing(size_t depth) {
  if (UNLIKELY(depth > 1024)) {
    throw UnserializeError("depth > 1024");
  }

      return V::fromMap(unserializeMap(depth));
      return V::fromVector(unserializeVector(depth));
      return V::fromVector(unserializeList(depth));
      return V::fromSet(unserializeSet(depth));
  typename V::MapType unserializeMap(size_t depth);
  typename V::VectorType unserializeVector(size_t depth);
  typename V::VectorType unserializeList(size_t depth);
  typename V::SetType unserializeSet(size_t depth);
  typename V::VariantType unserializeThing(size_t depth);
    if (!v.isInteger() && !v.isString()) {
        "Keysets can only contain integers or strings"
      );
#define FB_UNSERIALIZE_MAX_DEPTH_EXCEEDED        0x0005
    Variant& out, const char* buf, int n, int& p, size_t depth) {
  if (UNLIKELY(depth > 1024)) {
    return FB_UNSERIALIZE_MAX_DEPTH_EXCEEDED;
  }
        int err =
          fb_compact_unserialize_from_buffer(value, buf, n, p, depth + 1);
          int err =
            fb_compact_unserialize_from_buffer(value, buf, n, p, depth + 1);
        int err = fb_compact_unserialize_from_buffer(key, buf, n, p, depth + 1);
        err = fb_compact_unserialize_from_buffer(value, buf, n, p, depth + 1);
  int err = fb_compact_unserialize_from_buffer(ret, str, len, p, 0);
    HHVM_RC_INT_SAME(FB_UNSERIALIZE_MAX_DEPTH_EXCEEDED);
<?hh
// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.

<<__EntryPoint>>
function main() {
  $ret = null;
  var_dump(fb_unserialize("\x14\x02\x01\x14\x02\x01\x02\x01\x01\x01", inout $ret));
  var_dump($ret);
}
bool(false)
bool(false)
<?hh
// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.

function make_fb_serialize_struct($size) {
  return str_repeat("\x0a\x02\x01", $size) . "\x02\x01" . str_repeat("\x01", $size);
}

function make_fb_serialize_vector($size) {
  return str_repeat("\x12", $size) . str_repeat("\x01", $size);
}

function make_fb_serialize_list($size) {
  return str_repeat("\x13\x02\x01", $size) . "\x02\x01" . str_repeat("\x01", $size);
}

function make_fb_cs_list_map($size) {
  return str_repeat("\xfa\xfd", $size) . str_repeat("\xfc", $size);
}

function make_fb_cs_map($size) {
  return str_repeat("\xfb\x01", $size) . "\x01" . str_repeat("\xfc", $size);
}

function make_fb_cs_vector($size) {
  return str_repeat("\xfe", $size) . str_repeat("\xfc", $size);
}

function test($serialized) {
  $ret = null;
  var_dump(
    fb_unserialize(
      $serialized,
      inout $ret,
      FB_SERIALIZE_HACK_ARRAYS
    )
  );
  var_dump($ret);
}

function tests($size) {
  test(make_fb_serialize_struct($size));
  test(make_fb_serialize_vector($size));
  test(make_fb_serialize_list($size));
  test(make_fb_cs_list_map($size));
  test(make_fb_cs_map($size));
  test(make_fb_cs_vector($size));
}

<<__EntryPoint>>
function main() {
  tests(10);
  tests(1026);
}
dict(1) {
  [1]=>
  dict(1) {
    [1]=>
    dict(1) {
      [1]=>
      dict(1) {
        [1]=>
        dict(1) {
          [1]=>
          dict(1) {
            [1]=>
            dict(1) {
              [1]=>
              dict(1) {
                [1]=>
                dict(1) {
                  [1]=>
                  dict(1) {
                    [1]=>
                    int(1)
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
bool(true)
vec(1) {
  vec(1) {
    vec(1) {
      vec(1) {
        vec(1) {
          vec(1) {
            vec(1) {
              vec(1) {
                vec(1) {
                  vec(0) {
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
bool(true)
vec(1) {
  vec(1) {
    vec(1) {
      vec(1) {
        vec(1) {
          vec(1) {
            vec(1) {
              vec(1) {
                vec(1) {
                  vec(1) {
                    int(1)
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
bool(true)
array(1) {
  [1]=>
  array(1) {
    [1]=>
    array(1) {
      [1]=>
      array(1) {
        [1]=>
        array(1) {
          [1]=>
          array(1) {
            [1]=>
            array(1) {
              [1]=>
              array(1) {
                [1]=>
                array(1) {
                  [1]=>
                  array(0) {
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
bool(true)
array(1) {
  [1]=>
  array(1) {
    [1]=>
    array(1) {
      [1]=>
      array(1) {
        [1]=>
        array(1) {
          [1]=>
          array(1) {
            [1]=>
            array(1) {
              [1]=>
              array(1) {
                [1]=>
                array(1) {
                  [1]=>
                  array(1) {
                    [1]=>
                    int(1)
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
bool(true)
array(1) {
  [0]=>
  array(1) {
    [0]=>
    array(1) {
      [0]=>
      array(1) {
        [0]=>
        array(1) {
          [0]=>
          array(1) {
            [0]=>
            array(1) {
              [0]=>
              array(1) {
                [0]=>
                array(1) {
                  [0]=>
                  array(0) {
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
bool(true)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)