CVE-2016-6874 (PHP, published 2017-02-17T17:59Z)
Description: value: The array_*_recursive functions in Facebook HHVM before 3.15.0 allows attackers to have unspecified impact via unknown vectors, related to recursion.
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/array/ext_array.cpp
hphp/test/slow/array_functions/self_recursive.php
hphp/test/slow/array_functions/self_recursive.php.expectf
Commit message: Fix recursion checks in array_*_recursive

Summary:
array_merge_recursive and array_replace_recursive do recursion checks,
but use the fact that normal arrays can't contain cycles except
through references to avoid most of the checking.

Unfortunately the $GLOBALS array is special, and /can/ contain cycles
without references, and ProxyArrays could potentially do the same (via
an as-yet unimplemented extension).

Reviewed By: mxw

Differential Revision: D3622612

fbshipit-source-id: ed90b747096a05919a80c4793e2a2b7c57584d56


Vulnerable code:
  if (check) {
    if (seen.find((void*)arr1.get()) != seen.end()) {
      raise_warning("array_merge_recursive(): recursion detected");
      return;
    }
    seen.insert((void*)arr1.get());
      php_array_merge_recursive(seen, v.isReferenced(), subarr1,
  if (check) {
    if (seen.find((void*)arr1.get()) != seen.end()) {
      raise_warning("array_replace_recursive(): recursion detected");
      return;
    }
    seen.insert((void*)arr1.get());
        php_array_replace_recursive(seen, v.isReferenced(), subarr1,
                                    arr_value);

Fixed code:
static bool couldRecur(const Variant& v, const Array& arr) {
  return v.isReferenced() ||
    arr.get()->kind() == ArrayData::kGlobalsKind ||
    arr.get()->kind() == ArrayData::kProxyKind;
}

  if (check && !seen.insert((void*)arr1.get()).second) {
    raise_warning("array_merge_recursive(): recursion detected");
    return;
      php_array_merge_recursive(seen,
                                couldRecur(v, subarr1),
                                subarr1,
  if (arr1.get() == arr2.get()) {
    // This is an optimization, but it also avoids an assert in
    // setWithRef (Variant::setWithRef asserts that its source
    // and destination are not the same).
    // If the arrays are self recursive, this does change the behavior
    // slightly - it skips the "recursion detected" warning.
    return;
  }

  if (check && !seen.insert((void*)arr1.get()).second) {
    raise_warning("array_replace_recursive(): recursion detected");
    return;
        php_array_replace_recursive(seen, couldRecur(v, subarr1),
                                    subarr1, arr_value);
<?php

function test($g) {
  $GLOBALS['g'] = $GLOBALS;

  array_replace_recursive($GLOBALS, $g);

  $GLOBALS['g'] = $GLOBALS;
  array_merge_recursive($GLOBALS, $g);
}

function main() {
  $a = array();
  $a['g'] = &$a;

  test($a);
  test($GLOBALS);
}

main();

Warning: array_replace_recursive(): recursion detected in %s/test/slow/array_functions/self_recursive.php on line 6

Warning: array_merge_recursive(): recursion detected in %s/test/slow/array_functions/self_recursive.php on line 9

Warning: array_merge_recursive(): recursion detected in %s/test/slow/array_functions/self_recursive.php on line 9

Warning: array_merge_recursive(): recursion detected in %s/test/slow/array_functions/self_recursive.php on line 9

Warning: array_merge_recursive(): recursion detected in %s/test/slow/array_functions/self_recursive.php on line 9

Warning: array_merge_recursive(): recursion detected in %s/test/slow/array_functions/self_recursive.php on line 9