CVE-2016-6875 (PHP, published 2017-02-17T17:59Z)
Description: value: Infinite recursion in wddx in Facebook HHVM before 3.15.0 allows attackers to have unspecified impact via unknown vectors.
Severity: HIGH (CVSS v3 base score: 9.8)
Weakness: NVD-CWE-Other \u2014 Other
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.
Repository: https://github.com/facebook/hhvm
Files changed:
hphp/runtime/ext/wddx/ext_wddx.cpp
hphp/runtime/ext/wddx/ext_wddx.h
hphp/test/slow/ext_wddx/recursion.php
hphp/test/slow/ext_wddx/recursion.php.expectf
Commit message: 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


Vulnerable code:
    Array varAsArray;
    Object varAsObject = varVariant.toObject();
    if (isArray) varAsArray = varVariant.toArray();
    if (isObject) varAsArray = varAsObject.toArray();

        Variant key = it.first();
        Variant value = it.second();
        recursiveAddVar(key.toString(), value, isObject);
                       bool hasVarTag );

Fixed code:
  SeenContainers seen;
  return recursiveAddVarImpl(varName, varVariant, hasVarTag, seen);
}
bool WddxPacket::recursiveAddVarImpl(const String& varName,
                                     const Variant& varVariant,
                                     bool hasVarTag,
                                     SeenContainers& seen) {
    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); };

        auto key = it.first();
        auto const& value = it.secondRef();
        recursiveAddVarImpl(key.toString(), value, isObject, seen);
                       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);

<?php
$s1 = wddx_serialize_value(function () {});
var_dump($s1);

$a = []; $a[] =& $a;
$s2 = wddx_serialize_value($a);
var_dump($s2);

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>"