Commit message: Flush secureRandom buffer on fork

Summary: On fork, flush the secureRandom buffer, so that we don't share entropy between the parent and child.

Reviewed By: ricklavoie

Differential Revision: D9196474

fbshipit-source-id: 12ff8488d814466186df61328a5f1d4000beb27f
CVE: CVE-2018-6337
CWE: CWE-787
Vulnerability description: folly::secureRandom will re-use a buffer between parent and child processes when fork() is called. That will result in multiple forked children producing repeat (or similar) results. This affects HHVM 3.26 prior to 3.26.3 and the folly library between v2017.12.11.00 and v2018.08.09.00.

Project: folly
File: Random.cpp

ThreadLocalPRNG::result_type ThreadLocalPRNG::operator()() {
  struct Wrapper {
    Random::DefaultGenerator object{Random::create()};
  };
  using Single = SingletonThreadLocal<Wrapper, RandomTag>;
  return Single::get().object();
}

Project: folly
File: RandomTest.cpp

TEST(Random, Simple) {
  uint32_t prev = 0, seed = 0;
  for (int i = 0; i < 1024; ++i) {
    EXPECT_NE(seed = randomNumberSeed(), prev);
    prev = seed;
  }
}

Project: folly
File: RandomTest.cpp

TEST(Random, StateSize) {
  using namespace folly::detail;

  // uint_fast32_t is uint64_t on x86_64, w00t
  EXPECT_EQ(
      sizeof(uint_fast32_t) / 4 + 3, StateSizeT<std::minstd_rand0>::value);
  EXPECT_EQ(624, StateSizeT<std::mt19937>::value);
#if FOLLY_HAVE_EXTRANDOM_SFMT19937
  EXPECT_EQ(624, StateSizeT<__gnu_cxx::sfmt19937>::value);
#endif
  EXPECT_EQ(24, StateSizeT<std::ranlux24_base>::value);
}

Project: folly
File: Random.cpp

  void get(void* data, size_t size) {
    if (LIKELY(size <= remaining())) {
      memcpy(data, ptr_, size);
      ptr_ += size;
    } else {
      getSlow(static_cast<unsigned char*>(data), size);
    }
  }

Project: folly
File: Random.cpp

void BufferedRandomDevice::getSlow(unsigned char* data, size_t size) {
  DCHECK_GT(size, remaining());
  if (size >= bufferSize_) {
    // Just read directly.
    readRandomDevice(data, size);
    return;
  }

  size_t copied = remaining();
  memcpy(data, ptr_, copied);
  data += copied;
  size -= copied;

  // refill
  readRandomDevice(buffer_.get(), bufferSize_);
  ptr_ = buffer_.get();

  memcpy(data, ptr_, size);
  ptr_ += size;
}

Project: folly
File: Random.cpp

  inline size_t remaining() const {
    return size_t(buffer_.get() + bufferSize_ - ptr_);
  }

Project: folly
File: Random.cpp

BufferedRandomDevice::BufferedRandomDevice(size_t bufferSize)
  : bufferSize_(bufferSize),
    buffer_(new unsigned char[bufferSize]),
    ptr_(buffer_.get() + bufferSize) {  // refill on first use
  call_once(flag, [this]() {
    detail::AtFork::registerHandler(
        this,
        /*prepare*/ []() { return true; },
        /*parent*/ []() {},
        /*child*/
        []() {
          using Single = SingletonThreadLocal<BufferedRandomDevice, RandomTag>;
          auto& t = Single::get();
          // Clear out buffered data on fork.
          //
          // Ensure child and parent do not share same entropy pool.
          t.ptr_ = t.buffer_.get() + t.bufferSize_;
        });
  });
}

Project: folly
File: RandomTest.cpp

TEST(Random, FixedSeed) {
  // clang-format off
  struct ConstantRNG {
    typedef uint32_t result_type;
    result_type operator()() {
      return 4; // chosen by fair dice roll.
                // guaranteed to be random.
    }
    static constexpr result_type min() {
      return std::numeric_limits<result_type>::min();
    }
    static constexpr result_type max() {
      return std::numeric_limits<result_type>::max();
    }
  };
  // clang-format on

  ConstantRNG gen;

  // Pick a constant random number...
  auto value = Random::rand32(10, gen);

  // Loop to make sure it really is constant.
  for (int i = 0; i < 1024; ++i) {
    auto result = Random::rand32(10, gen);
    EXPECT_EQ(value, result);
  }
}

Project: folly
File: RandomTest.cpp

TEST(Random, SecureFork) {
  unsigned char buffer = 0;
  // Init random buffer
  folly::Random::secureRandom(&buffer, 1);

  auto pid = fork();
  EXPECT_NE(pid, -1);
  if (pid) {
    // parent
    int status = 0;
    folly::Random::secureRandom(&buffer, 1);
    auto pid2 = wait(&status);
    EXPECT_NE(WEXITSTATUS(status), buffer);
    EXPECT_EQ(pid, pid2);
  } else {
    // child
    folly::Random::secureRandom(&buffer, 1);
    exit(buffer); // Do not print gtest results
  }
}

Project: folly
File: Random.cpp

void Random::secureRandom(void* data, size_t size) {
  using Single = SingletonThreadLocal<BufferedRandomDevice, RandomTag>;
  Single::get().get(data, size);
}

Project: folly
File: Random.cpp

void readRandomDevice(void* data, size_t size) {
#ifdef _MSC_VER
  static folly::once_flag flag;
  static HCRYPTPROV cryptoProv;
  folly::call_once(flag, [&] {
    if (!CryptAcquireContext(
            &cryptoProv,
            nullptr,
            nullptr,
            PROV_RSA_FULL,
            CRYPT_VERIFYCONTEXT)) {
      if (GetLastError() == NTE_BAD_KEYSET) {
        // Mostly likely cause of this is that no key container
        // exists yet, so try to create one.
        PCHECK(CryptAcquireContext(
            &cryptoProv, nullptr, nullptr, PROV_RSA_FULL, CRYPT_NEWKEYSET));
      } else {
        LOG(FATAL) << "Failed to acquire the default crypto context.";
      }
    }
  });
  CHECK(size <= std::numeric_limits<DWORD>::max());
  PCHECK(CryptGenRandom(cryptoProv, (DWORD)size, (BYTE*)data));
#else
  // Keep the random device open for the duration of the program.
  static int randomFd = ::open("/dev/urandom", O_RDONLY);
  PCHECK(randomFd >= 0);
  auto bytesRead = readFull(randomFd, data, size);
  PCHECK(bytesRead >= 0 && size_t(bytesRead) == size);
#endif
}

Project: folly
File: RandomTest.cpp

TEST(Random, MultiThreaded) {
  const int n = 100;
  std::vector<uint32_t> seeds(n);
  std::vector<std::thread> threads;
  for (int i = 0; i < n; ++i) {
    threads.push_back(std::thread([i, &seeds] {
      seeds[i] = randomNumberSeed();
    }));
  }
  for (auto& t : threads) {
    t.join();
  }
  std::sort(seeds.begin(), seeds.end());
  for (int i = 0; i < n-1; ++i) {
    EXPECT_LT(seeds[i], seeds[i+1]);
  }
}

Project: folly
File: RandomTest.cpp

TEST(Random, sanity) {
  // edge cases
  EXPECT_EQ(folly::Random::rand32(0), 0);
  EXPECT_EQ(folly::Random::rand32(12, 12), 0);
  EXPECT_EQ(folly::Random::rand64(0), 0);
  EXPECT_EQ(folly::Random::rand64(12, 12), 0);

  // 32-bit repeatability, uniqueness
  constexpr int kTestSize = 1000;
  {
    std::vector<uint32_t> vals;
    folly::Random::DefaultGenerator rng;
    rng.seed(0xdeadbeef);
    for (int i = 0; i < kTestSize; ++i) {
      vals.push_back(folly::Random::rand32(rng));
    }
    rng.seed(0xdeadbeef);
    for (int i = 0; i < kTestSize; ++i) {
      EXPECT_EQ(vals[i], folly::Random::rand32(rng));
    }
    EXPECT_EQ(
        vals.size(),
        std::unordered_set<uint32_t>(vals.begin(), vals.end()).size());
  }

  // 64-bit repeatability, uniqueness
  {
    std::vector<uint64_t> vals;
    folly::Random::DefaultGenerator rng;
    rng.seed(0xdeadbeef);
    for (int i = 0; i < kTestSize; ++i) {
      vals.push_back(folly::Random::rand64(rng));
    }
    rng.seed(0xdeadbeef);
    for (int i = 0; i < kTestSize; ++i) {
      EXPECT_EQ(vals[i], folly::Random::rand64(rng));
    }
    EXPECT_EQ(
        vals.size(),
        std::unordered_set<uint64_t>(vals.begin(), vals.end()).size());
  }
}