CVE-2020-1916 (C++, published 2021-03-10T16:15Z)
Description: value: An incorrect size calculation in ldap_escape may lead to an integer overflow when overly long input is passed in, resulting in an out-of-bounds write. This issue affects HHVM prior to 4.56.2, all versions between 4.57.0 and 4.78.0, 4.79.0, 4.80.0, 4.81.0, 4.82.0, 4.83.0.
Severity: HIGH (CVSS v3 base score: 9.8)
Weakness: CWE-787 \u2014 Out-of-bounds Write
The product writes data past the end, or before the beginning, of the intended buffer.
Repository: https://github.com/facebook/hhvm
Files changed:
hphp/runtime/base/preg.cpp
hphp/runtime/base/preg.h
hphp/runtime/base/runtime-option.cpp
hphp/runtime/base/runtime-option.h
hphp/runtime/base/string-util.cpp
hphp/runtime/ext/domdocument/ext_domdocument.h
hphp/runtime/ext/gd/libgd/gd.cpp
hphp/runtime/ext/ldap/ext_ldap.cpp
hphp/runtime/ext/xmlreader/ext_xmlreader.cpp
hphp/runtime/server/admin-request-handler.cpp
hphp/runtime/server/admin-request-handler.h
hphp/test/slow/ext_ldap/t78806688.php
hphp/test/slow/ext_ldap/t78806688.php.expectf
hphp/test/slow/ext_string/t76103217.php
hphp/test/slow/ext_string/t76103217.php.expect
hphp/test/slow/ext_xml/xmlreader_empty_expand.php
hphp/test/slow/ext_xml/xmlreader_empty_expand.php.expectf
hphp/util/light-process.cpp
hphp/util/light-process.h
hphp/zend/crypt-blowfish.cpp
hphp/zend/zend-string.cpp
Commit message: security fixes

https://hhvm.com/blog/2020/11/12/security-update.html


Vulnerable code:
  void dump(const std::string& filename);
void PCRECache::dump(const std::string& filename) {
  std::ofstream out(filename.c_str());
        out << it.first->data() << "\n";
          out << key.c_str() << "\n";
  out.close();
void pcre_dump_cache(const std::string& filename) {
  s_pcreCache.dump(filename);
 * Dump the contents of the PCRE cache to filename.
void pcre_dump_cache(const std::string& filename);
  if (salt && salt[0] == '\0') {
  req::ptr<XMLDocumentData> doc() const { return m_node->doc(); }
    + sy * (sizeof(int *) + sizeof(unsigned char *))
    + sx * sy * (sizeof(int) + sizeof(unsigned char));
  String result(3 * value.size(), ReserveString);
    docp = doc->docp();
    if (docp == nullptr) {
      auto filename = transport->getParam("file");
      if (filename == "") filename = "/tmp/static_strings";
      handleDumpStaticStringsRequest(cmd, filename);
      transport->sendString("OK\n");
      auto filename = transport->getParam("file");
      if (filename == "") filename = "/tmp/pcre_cache";
      pcre_dump_cache(filename);
      transport->sendString("OK\n");
bool AdminRequestHandler::handleDumpStaticStringsRequest(
  const std::string& /*cmd*/, const std::string& filename) {
  std::ofstream out(filename.c_str());
  SCOPE_EXIT { out.close(); };
    out << formatStaticString(item);
  bool handleDumpStaticStringsRequest(const std::string &cmd,
                                      const std::string &filename);
    // TODO(alexeyt) should we log something and/or fail to start?
    // TODO(alexeyt) should we log something and/or fail to start?

    initgroups(pw->pw_name, pw->pw_gid);
    setgid(pw->pw_gid);
    setuid(pw->pw_uid);
  if (count < min || BF_decode(data.binary.salt, &setting[7], 16)) {

  assert(key);
  assert(salt);
  if ((strlen(salt) > sizeof("$2X$00$")) &&
    if (php_crypt_blowfish_rn(key, salt, output, sizeof(output))) {

Fixed code:
#include <folly/FileUtil.h>
  void dump(folly::File& file);
void PCRECache::dump(folly::File& file) {
        folly::writeFull(file.fd(), it.first->data(), it.first->size());
        folly::writeFull(file.fd(), "\n", 1);
          folly::writeFull(file.fd(), key.data(), key.size());
          folly::writeFull(file.fd(), "\n", 1);
void pcre_dump_cache(folly::File& file) {
  s_pcreCache.dump(file);
#include <folly/File.h>
 * Dump the contents of the PCRE cache to the given file.
void pcre_dump_cache(folly::File& file);
#include "hphp/util/light-process.h"
std::string RuntimeOption::AdminDumpPath;
    Config::Bind(LightProcess::g_strictUser, ini, config,
                 "Server.LightProcessStrictUser", false);
    Config::Bind(AdminDumpPath, ini, config,
                 "AdminServer.DumpPath", "/tmp/hhvm_admin_dump");
  static std::string AdminDumpPath;

  assertx(salt);
  if (salt[0] == '\0') {
  req::ptr<XMLDocumentData> doc() const {
    return m_node ? m_node->doc() : nullptr;
  }
    + (sizeof(int *) + sizeof(unsigned char *)) * sy
    + (sizeof(int) + sizeof(unsigned char)) * sx * sy;
  String result(3UL * value.size(), ReserveString);
    if (doc == nullptr || doc->docp() == nullptr) {
    docp = doc->docp();
#include <folly/File.h>
#include <folly/FileUtil.h>
#include <folly/Optional.h>
namespace {

// When this struct is destroyed, it will close the file.
struct DumpFile {
  std::string path;
  folly::File file;
};

folly::Optional<DumpFile> dump_file(const char* name) {
  auto const path = folly::sformat("{}/{}", RO::AdminDumpPath, name);

  // mkdir -p the directory prefix of `path`
  if (FileUtil::mkdir(path) != 0) return folly::none;

  // If remove fails because of a permissions issue, then we won't be
  // able to open the file for exclusive write below.
  remove(path.c_str());

  // Create the file, failing if it already exists. Doing so ensures
  // that we have write access to the file and that no other user does.
  auto const fd = open(path.c_str(), O_CREAT|O_EXCL|O_RDWR, 0666);
  if (fd < 0) return folly::none;

  return DumpFile{path, folly::File(fd, /*owns=*/true)};
}

}

      if (auto file = dump_file("static_strings")) {
        handleDumpStaticStringsRequest(file->file);
        transport->sendString(folly::sformat("dumped to {}\n", file->path));
      } else {
        transport->sendString("Unable to mkdir or file already exists.\n");
      }
      if (auto file = dump_file("pcre_cache")) {
        pcre_dump_cache(file->file);
        transport->sendString(folly::sformat("dumped to {}\n", file->path));
      } else {
        transport->sendString("Unable to mkdir or file already exists.\n");
      }
bool AdminRequestHandler::handleDumpStaticStringsRequest(folly::File& file) {
    auto const line = formatStaticString(item);
    folly::writeFull(file.fd(), line.data(), line.size());
#include <folly/File.h>

  bool handleDumpStaticStringsRequest(folly::File& file);
<?hh

<<__EntryPoint>>
function main(): void {
  $multiplier = 1431655769;
  $s = str_repeat("a", $multiplier);
  ldap_escape($s);
  echo "FAIL!\n";
}
Fatal error: String length exceeded: 4294967307 > %d in %s/t78806688.php on line 7
<?hh

<<__EntryPoint>>
function main(): void {
  $key = "\x26\xbd\xbd\xbd\xff\x60\xbf\xff\xff\x60";
  $salt1 = "\x24\x32\x78\x24\x31\x30\x24\x24\x35\x24\xad\x20\x20\x26\xff\x60\xbf\xff\xff\x60\x24\x31\x78\xa8\xa8\xa0\x01\x01\x01\x01\x01\x01";
  $salt2 = "\x24\x32\x78\x24\x31\x30\x24\x24\x35";

  var_dump(base64_encode(crypt($key, $salt1)));
  var_dump(base64_encode(crypt($key, $salt2)));
}
string(4) "KjA="
string(12) "JDJ4JDEwJCQ1"
<?hh

<<__EntryPoint>>
function main(): void {
    $a = new XMLReader();
    $b = new DOMNode();
    $a->expand($b);
    $a->expand($a);
}
Warning: Invalid State Error in %s on line %d

Warning: Invalid State Error in %s on line %d
#include "hphp/util/struct-log.h"
bool LightProcess::g_strictUser = false;

  StructuredLogEntry* log = nullptr;
  int err = 0;
  SCOPE_EXIT {
    if (log) {
      log->setInt("errno", err);
      log->setStr("new_user", uname);
      StructuredLog::log("hhvm_lightprocess_error", *log);
      delete log;
    }
  };
    err = errno;
    log = new StructuredLogEntry();
    log->setStr("function", "getpwnam_r");
    if (LightProcess::g_strictUser) {
      throw std::runtime_error{"getpwnam_r(): " + folly::errnoStr(err)};
    }
    log = new StructuredLogEntry();
    log->setStr("function", "getpwnam_r");
    if (LightProcess::g_strictUser) {
      throw std::runtime_error{"getpwnam_r(): not found"};
    }
    if (initgroups(pw->pw_name, pw->pw_gid)) {
      err = errno;
      log = new StructuredLogEntry();
      log->setStr("function", "initgroups");
    }
    if (setgid(pw->pw_gid)) {
      if (!log) {
        err = errno;
        log = new StructuredLogEntry();
        log->setStr("function", "setgid");
        if (LightProcess::g_strictUser) {
          throw std::runtime_error{"setgid():" + folly::errnoStr(err)};
        }
      }
    }
    if (setuid(pw->pw_uid)) {
      if (!log) {
        err = errno;
        log = new StructuredLogEntry();
        log->setStr("function", "setuid");
        if (LightProcess::g_strictUser) {
          throw std::runtime_error{"setuid():" + folly::errnoStr(err)};
        }
      }
    }

 public:
  // whether to abort when user change fails.
  static bool g_strictUser;
  if (count < min ||
      BF_decode(data.binary.salt, &setting[7], 16) ||
      ((unsigned int)(setting[7 + 22 - 1] - 0x20) >= 0x60)
     ) {
  assertx(key);
  assertx(salt);
  auto const saltLen = strlen(salt);
  if ((saltLen > sizeof("$2X$00$")) &&

    static constexpr size_t maxSaltLength = 123;
    char paddedSalt[maxSaltLength + 1];
    paddedSalt[0] = paddedSalt[maxSaltLength] = '\0';

    memset(&paddedSalt[1], '$', maxSaltLength - 1);
    memcpy(paddedSalt, salt, std::min(maxSaltLength, saltLen));
    paddedSalt[saltLen] = '\0';

    if (php_crypt_blowfish_rn(key, paddedSalt, output, sizeof(output))) {