CVE-2019-11936 (published 2019-12-04T17:16Z)

Various APC functions accept keys containing null bytes as input, leading to premature truncation of input. This issue affects HHVM versions prior to 3.30.12, all versions between 4.0.0 and 4.8.5, all versions between 4.9.0 and 4.23.1, as well as 4.24.0, 4.25.0, 4.26.0, 4.27.0, 4.28.0, and 4.28.1.

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.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/facebook/hhvm
commit_details: repo_url: https://github.com/facebook/hhvm
author: William Lee
author_date: 2019-10-28 12:52:00-07:00
msg: Prevent APC keys with nulls

Summary:
This diff prevents storing APC keys that contain the null byte.
APC uses a backing data structure ConcurrentTableSharedStore that uses const char* as its key type. This leads to situations where inserting APC keys with null bytes will cause key truncation, like so...
localhost> p apc_clear_cache();
true
localhost> p apc_fetch("test")
false
localhost> p apc_fetch("test\x00suffix")
false
localhost> p apc_add("test\x00suffix", 5)
true
localhost> p apc_fetch("test")
5
localhost> p apc_fetch("test\x00suffix")
false
This change will make APC throw an error if a key containing a null byte is passed to the store/add methods.

Reviewed By: markw65

Differential Revision: D17608626

fbshipit-source-id: 915e179f41e66c99c718364ec4a8639d204c4ea2
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_apc.cpp
change_type: ModificationType.MODIFY
diff: @@ -95,6 +95,11 @@ ConcurrentTableSharedStore& apc_store() {
   return *static_cast<ConcurrentTableSharedStore*>(vpStore);
 }
 
+bool isKeyInvalid(const String &key) {
+  // T39154441 - check if invalid chars exist
+  return key.find('\0') != -1;
+}
+
 }
 
 //////////////////////////////////////////////////////////////////////
@@ -307,9 +312,14 @@ Variant HHVM_FUNCTION(apc_store,
         return Variant(false);
       }
       Variant v = iter.second();
-      apc_store().set(key.toString(), v, ttl);
-    }
 
+      auto const& strKey = key.toCStrRef();
+      if (isKeyInvalid(strKey)) {
+        throw_invalid_argument("apc key: (contains invalid characters)");
+        return Variant(false);
+      }
+      apc_store().set(strKey, v, ttl);
+    }
     return Variant(ArrayData::Create());
   }
 
@@ -318,6 +328,11 @@ Variant HHVM_FUNCTION(apc_store,
     return Variant(false);
   }
   String strKey = key_or_array.toString();
+
+  if (isKeyInvalid(strKey)) {
+    throw_invalid_argument("apc key: (contains invalid characters)");
+    return Variant(false);
+  }
   apc_store().set(strKey, var, ttl);
   return Variant(true);
 }
@@ -330,6 +345,10 @@ bool HHVM_FUNCTION(apc_store_as_primed_do_not_use,
                    const String& key,
                    const Variant& var) {
   if (!apcExtension::Enable) return false;
+  if (isKeyInvalid(key)) {
+    throw_invalid_argument("apc key: (contains invalid characters)");
+    return false;
+  }
   apc_store().setWithoutTTL(key, var);
   return true;
 }
@@ -353,8 +372,15 @@ Variant HHVM_FUNCTION(apc_add,
         return false;
       }
       Variant v = iter.second();
-      if (!apc_store().add(key.toString(), v, ttl)) {
-        errors.add(key, -1);
+
+      auto const& strKey = key.toCStrRef();
+      if (isKeyInvalid(strKey)) {
+        throw_invalid_argument("apc key: (contains invalid characters)");
+        return false;
+      }
+
+      if (!apc_store().add(strKey, v, ttl)) {
+        errors.add(strKey, -1);
       }
     }
     return errors.toVariant();
@@ -365,6 +391,10 @@ Variant HHVM_FUNCTION(apc_add,
     return false;
   }
   String strKey = key_or_array.toString();
+  if (isKeyInvalid(strKey)) {
+    throw_invalid_argument("apc key: (contains invalid characters)");
+    return false;
+  }
   return apc_store().add(strKey, var, ttl);
 }
 

programming_language: C++
filename: apc.php
change_type: ModificationType.MODIFY
diff: @@ -61,6 +61,16 @@ function testKeyTypes() {
   }
 }
 
+function testInvalidKeys() {
+    // Reject keys with null bytes
+    apc_add("bar\x00baz", 10);
+    apc_store("test\x00xyz", "hello");
+    apc_store(array("validkey" => "validvalue", "invalid\x00key" => "value"));
+    foreach (array('bar', 'test', 'validkey', 'invalid') as $k) {
+        var_dump(__hhvm_intrinsics\apc_fetch_no_check($k));
+    }
+}
+
 <<__EntryPoint>> function main(): void {
   testApc(array(7, 4, 1776));
   testApc(array("sv0", "sv1"));
@@ -73,4 +83,5 @@ function testKeyTypes() {
   var_dump($b);
 
   testKeyTypes();
+  testInvalidKeys();
 }

programming_language: PHP
filename: apc.php.expectf
change_type: ModificationType.MODIFY
diff: @@ -72,3 +72,13 @@ string(3) "two"
 Undefined index: 3
 Undefined index: 2
 string(5) "three"
+
+Warning: Invalid argument: apc key: (contains invalid characters)%S
+
+Warning: Invalid argument: apc key: (contains invalid characters)%S
+
+Warning: Invalid argument: apc key: (contains invalid characters)%S
+bool(false)
+bool(false)
+string(10) "validvalue"
+bool(false)

programming_language: Scala