CVE-2016-6871: Integer Overflow or Wraparound (CWE-190)
value: Integer overflow in bcmath in Facebook HHVM before 3.15.0 allows attackers to have unspecified impact via unknown vectors, which triggers a buffer overflow.
CWE description: The product performs a calculation that can
         produce an integer overflow or wraparound when the logic
         assumes that the resulting value will always be larger than
         the original value. This occurs when an integer value is
         incremented to a value that is too large to store in the
         associated representation. When this occurs, the value may
         become a very small or negative number.
CVSS v3 base score: 9.8
Published: 2017-02-17T17:59Z
Repository: https://github.com/facebook/hhvm
Language: PHP
Files changed: hphp/runtime/ext/bcmath/ext_bcmath.cpp, hphp/test/slow/ext_bcmath/bccomp_overflow.php, hphp/test/slow/ext_bcmath/bccomp_overflow.php.expect
Commit date: 2016-08-01 15:53:27 -0700
Commit message: Fix buffer overrun due to integer overflow in bcmath

Summary: scale gets passed around as an int inside the library.

Reviewed By: mxw

Differential Revision: D3624520

fbshipit-source-id: d39927413cec24fda2e475a296ad5d9019ccef0a

Diff: diff --git a/hphp/runtime/ext/bcmath/ext_bcmath.cpp b/hphp/runtime/ext/bcmath/ext_bcmath.cpp
index e9b3c9d2680..e5b037a6060 100644
--- a/hphp/runtime/ext/bcmath/ext_bcmath.cpp
+++ b/hphp/runtime/ext/bcmath/ext_bcmath.cpp
@@ -37,10 +37,19 @@ struct bcmath_data {
 };
 static IMPLEMENT_THREAD_LOCAL(bcmath_data, s_globals);
 
 ///////////////////////////////////////////////////////////////////////////////
 
+static int64_t adjust_scale(int64_t scale) {
+  if (scale < 0) {
+    scale = BCG(bc_precision);
+    if (scale < 0) scale = 0;
+  }
+  if ((uint64_t)scale > StringData::MaxSize) return StringData::MaxSize;
+  return scale;
+}
+
 static void php_str2num(bc_num *num, const char *str) {
   const char *p;
   if (!(p = strchr(str, '.'))) {
     bc_str2num(num, (char*)str, 0);
   } else {
@@ -53,11 +62,11 @@ static bool HHVM_FUNCTION(bcscale, int64_t scale) {
   return true;
 }
 
 static String HHVM_FUNCTION(bcadd, const String& left, const String& right,
                             int64_t scale /* = -1 */) {
-  if (scale < 0) scale = BCG(bc_precision);
+  scale = adjust_scale(scale);
   bc_num first, second, result;
   bc_init_num(&first);
   bc_init_num(&second);
   bc_init_num(&result);
   php_str2num(&first, (char*)left.data());
@@ -73,11 +82,11 @@ static String HHVM_FUNCTION(bcadd, const String& left, const String& right,
   return ret;
 }
 
 static String HHVM_FUNCTION(bcsub, const String& left, const String& right,
                             int64_t scale /* = -1 */) {
-  if (scale < 0) scale = BCG(bc_precision);
+  scale = adjust_scale(scale);
   bc_num first, second, result;
   bc_init_num(&first);
   bc_init_num(&second);
   bc_init_num(&result);
   php_str2num(&first, (char*)left.data());
@@ -93,11 +102,11 @@ static String HHVM_FUNCTION(bcsub, const String& left, const String& right,
   return ret;
 }
 
 static int64_t HHVM_FUNCTION(bccomp, const String& left, const String& right,
                              int64_t scale /* = -1 */) {
-  if (scale < 0) scale = BCG(bc_precision);
+  scale = adjust_scale(scale);
   bc_num first, second;
   bc_init_num(&first);
   bc_init_num(&second);
   bc_str2num(&first, (char*)left.data(), scale);
   bc_str2num(&second, (char*)right.data(), scale);
@@ -107,11 +116,11 @@ static int64_t HHVM_FUNCTION(bccomp, const String& left, const String& right,
   return ret;
 }
 
 static String HHVM_FUNCTION(bcmul, const String& left, const String& right,
                             int64_t scale /* = -1 */) {
-  if (scale < 0) scale = BCG(bc_precision);
+  scale = adjust_scale(scale);
   bc_num first, second, result;
   bc_init_num(&first);
   bc_init_num(&second);
   bc_init_num(&result);
   php_str2num(&first, (char*)left.data());
@@ -127,11 +136,11 @@ static String HHVM_FUNCTION(bcmul, const String& left, const String& right,
   return ret;
 }
 
 static Variant HHVM_FUNCTION(bcdiv, const String& left, const String& right,
                int64_t scale /* = -1 */) {
-  if (scale < 0) scale = BCG(bc_precision);
+  scale = adjust_scale(scale);
   bc_num first, second, result;
   bc_init_num(&first);
   bc_init_num(&second);
   bc_init_num(&result);
   SCOPE_EXIT {
@@ -169,11 +178,11 @@ static Variant HHVM_FUNCTION(bcmod, const String& left, const String& right) {
   return ret;
 }
 
 static String HHVM_FUNCTION(bcpow, const String& left, const String& right,
                            int64_t scale /* = -1 */) {
-  if (scale < 0) scale = BCG(bc_precision);
+  scale = adjust_scale(scale);
   bc_num first, second, result;
   bc_init_num(&first);
   bc_init_num(&second);
   bc_init_num(&result);
   SCOPE_EXIT {
@@ -191,11 +200,11 @@ static String HHVM_FUNCTION(bcpow, const String& left, const String& right,
   return ret;
 }
 
 static Variant HHVM_FUNCTION(bcpowmod, const String& left, const String& right,
                              const String& modulus, int64_t scale /* = -1 */) {
-  if (scale < 0) scale = BCG(bc_precision);
+  scale = adjust_scale(scale);
   bc_num first, second, mod, result;
   bc_init_num(&first);
   bc_init_num(&second);
   bc_init_num(&mod);
   bc_init_num(&result);
@@ -218,11 +227,11 @@ static Variant HHVM_FUNCTION(bcpowmod, const String& left, const String& right,
   return ret;
 }
 
 static Variant HHVM_FUNCTION(bcsqrt, const String& operand,
                              int64_t scale /* = -1 */) {
-  if (scale < 0) scale = BCG(bc_precision);
+  scale = adjust_scale(scale);
   bc_num result;
   bc_init_num(&result);
   SCOPE_EXIT {
     bc_free_num(&result);
   };
diff --git a/hphp/test/slow/ext_bcmath/bccomp_overflow.php b/hphp/test/slow/ext_bcmath/bccomp_overflow.php
new file mode 100644
index 00000000000..5c5c419a2a3
--- /dev/null
+++ b/hphp/test/slow/ext_bcmath/bccomp_overflow.php
@@ -0,0 +1,7 @@
+<?php
+
+$intMaxPre = PHP_INT_MAX - 1;
+$stringNormal = 'abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+
+$number_bccomp_10 = bccomp("2015.5", $stringNormal, $intMaxPre);
+var_dump($number_bccomp_10);
diff --git a/hphp/test/slow/ext_bcmath/bccomp_overflow.php.expect b/hphp/test/slow/ext_bcmath/bccomp_overflow.php.expect
new file mode 100644
index 00000000000..82693695945
--- /dev/null
+++ b/hphp/test/slow/ext_bcmath/bccomp_overflow.php.expect
@@ -0,0 +1 @@
+int(1)

Vulnerable code:   if (scale < 0) scale = BCG(bc_precision);
  if (scale < 0) scale = BCG(bc_precision);
  if (scale < 0) scale = BCG(bc_precision);
  if (scale < 0) scale = BCG(bc_precision);
  if (scale < 0) scale = BCG(bc_precision);
  if (scale < 0) scale = BCG(bc_precision);
  if (scale < 0) scale = BCG(bc_precision);
  if (scale < 0) scale = BCG(bc_precision);
Fixed code: static int64_t adjust_scale(int64_t scale) {
  if (scale < 0) {
    scale = BCG(bc_precision);
    if (scale < 0) scale = 0;
  }
  if ((uint64_t)scale > StringData::MaxSize) return StringData::MaxSize;
  return scale;
}

  scale = adjust_scale(scale);
  scale = adjust_scale(scale);
  scale = adjust_scale(scale);
  scale = adjust_scale(scale);
  scale = adjust_scale(scale);
  scale = adjust_scale(scale);
  scale = adjust_scale(scale);
  scale = adjust_scale(scale);
<?php

$intMaxPre = PHP_INT_MAX - 1;
$stringNormal = 'abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ';

$number_bccomp_10 = bccomp("2015.5", $stringNormal, $intMaxPre);
var_dump($number_bccomp_10);
int(1)