CVE-2016-6875 (published 2017-02-17T17:59Z)

Infinite recursion in wddx in Facebook HHVM before 3.15.0 allows attackers to have unspecified impact via unknown vectors.

Severity: HIGH
CVSS v2 base score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 9.8, CRITICAL (CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: NVD-CWE-Other
cwe_name: Other
description: NVD is only using a subset of CWE for mapping instead of the entire CWE, and the weakness type is not covered by that subset.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/facebook/hhvm
commit_details: repo_url: https://github.com/facebook/hhvm
author: Christopher Dykes
author_date: 2016-08-01 14:21:50-07:00
msg: Fix infinite recursion in wddx

Summary:
It wasn't checking for infinite recursion due to references or self-referential
objects. As it turns out closures always return themselves when converted to an
array. Raising a warning and returning is how PHP-src deals with this problem,
nothing special is done for closures.

Reviewed By: alexmalyshev

Differential Revision: D3465655

fbshipit-source-id: a42bc34d30cf4825faf33596139c0c05f8e4f5f1
repository: repo_url: https://github.com/facebook/hhvm
repo_name: facebook/hhvm
description: A virtual machine for executing programs written in Hack.
repo_language: Hack
owner: facebook
file_changes: filename: ext_wddx.cpp
change_type: ModificationType.MODIFY
diff: @@ -66,22 +66,43 @@ bool WddxPacket::serialize_value(const Variant& varVariant) {
 bool WddxPacket::recursiveAddVar(const String& varName,
                                  const Variant& varVariant,
                                  bool hasVarTag) {
+  SeenContainers seen;
+  return recursiveAddVarImpl(varName, varVariant, hasVarTag, seen);
+}
 
+bool WddxPacket::recursiveAddVarImpl(const String& varName,
+                                     const Variant& varVariant,
+                                     bool hasVarTag,
+                                     SeenContainers& seen) {
   bool isArray = varVariant.isArray();
   bool isObject = varVariant.isObject();
 
   if (isArray || isObject) {
+    Array varAsArray;
+    Object varAsObject;
+    ArrayOrObject ptr;
+    if (isArray) {
+      varAsArray = varVariant.toArray();
+      ptr = varAsArray.get();
+    }
+    if (isObject) {
+      varAsObject = varVariant.toObject();
+      varAsArray = varAsObject.toArray();
+      ptr = varAsObject.get();
+    }
+    assert(!ptr.isNull());
+    if (!seen.emplace(ptr).second) {
+      raise_warning("recursion detected");
+      return false;
+    }
+    SCOPE_EXIT { seen.erase(ptr); };
+
     if (hasVarTag) {
       m_packetString.append("<var name='");
       m_packetString.append(varName.data());
       m_packetString.append("'>");
     }
 
-    Array varAsArray;
-    Object varAsObject = varVariant.toObject();
-    if (isArray) varAsArray = varVariant.toArray();
-    if (isObject) varAsArray = varAsObject.toArray();
-
     int length = varAsArray.length();
     if (length > 0) {
       ArrayIter it = ArrayIter(varAsArray);
@@ -99,9 +120,9 @@ bool WddxPacket::recursiveAddVar(const String& varName,
         m_packetString.append("'>");
       }
       for (ArrayIter it(varAsArray); it; ++it) {
-        Variant key = it.first();
-        Variant value = it.second();
-        recursiveAddVar(key.toString(), value, isObject);
+        auto key = it.first();
+        auto const& value = it.secondRef();
+        recursiveAddVarImpl(key.toString(), value, isObject, seen);
       }
       if (isObject) {
         m_packetString.append("</struct>");

programming_language: C++
filename: ext_wddx.h
change_type: ModificationType.MODIFY
diff: @@ -38,7 +38,7 @@ struct WddxPacket : ResourceData {
   String packet_end();
   bool serialize_value(const Variant& varVariant);
   bool recursiveAddVar(const String& varName, const Variant& varVariant,
-                       bool hasVarTag );
+                       bool hasVarTag);
 
  private:
   String getWddxEncoded(const String& varType,
@@ -52,6 +52,17 @@ struct WddxPacket : ResourceData {
                    const String& varName,
                    bool hasVarTag);
 
+  using ArrayOrObject = Either<ArrayData*,ObjectData*>;
+  struct EitherHash {
+    size_t operator()(const ArrayOrObject data) const {
+      return data.toOpaque();
+    }
+  };
+  using SeenContainers = req::hash_set<ArrayOrObject, EitherHash>;
+
+  bool recursiveAddVarImpl(const String& varName, const Variant& varVariant,
+                           bool hasVarTag, SeenContainers& seen);
+
   StringBuffer m_packetString;
   bool m_packetClosed;
   bool m_manualPacketCreation;

programming_language: C++
filename: recursion.php
change_type: ModificationType.ADD
diff: @@ -0,0 +1,7 @@
+<?php
+$s1 = wddx_serialize_value(function () {});
+var_dump($s1);
+
+$a = []; $a[] =& $a;
+$s2 = wddx_serialize_value($a);
+var_dump($s2);

programming_language: PHP
filename: recursion.php.expectf
change_type: ModificationType.ADD
diff: @@ -0,0 +1,6 @@
+
+Warning: recursion detected in %s/recursion.php on line 2
+string(%d) "<wddxPacket version='1.0'><header/><data><struct><var name='php_class_name'><string>Closure$;%s$%s$</string></var></struct></data></wddxPacket>"
+
+Warning: recursion detected in %s/recursion.php on line 6
+string(87) "<wddxPacket version='1.0'><header/><data><array length='1'></array></data></wddxPacket>"

programming_language: SQL