Skip to content

Commit

Permalink
Port PHP7 CSPRNG functions random_int() and random_bytes()
Browse files Browse the repository at this point in the history
Summary:
Hello! This PR is a port of the [CSPRNG functions](http://php.net/csprng) `random_*()` which were [added in PHP7](https://wiki.php.net/rfc/easy_userland_csprng).

This PR is very much a work in progress and I'll need a lot of help from you smarter kids to get this implemented properly. But it works well on my machine thanks to some one-on-one help from jmikola.

The things that I know need to be addressed:

1. The PHP version [checks for sources of random at compile time](https://github.com/php/php-src/blob/97f159d70288ae94f9a05a370121bcbea760ed9a/Zend/Zend.m4#L406-L412). Not sure how to do this in HHVM.
2. PHP [supports Windows with `arc4random_buf()`](https://github.com/php/php-src/blob/cbcacbb2dad4c97ba5653f42729f7956193d9112/ext/standard/random.c#L86). Not sure if we want to support this as well as HHVM gets more Windows friendly... is that a thing?
3. There is [an open PR](php/php-src#1397) to change the behavior of handling the cases when a reliable source of random cann

Closes #5498
Closes #5925

Reviewed By: sgolemon

Differential Revision: D2342229

Pulled By: jwatzman

fb-gh-sync-id: 419d949e3dd06fb1c13ecf86223c1b28eb57bb84
  • Loading branch information
SammyK authored and hhvm-bot committed Dec 3, 2015
1 parent 36e4279 commit df3688e
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 0 deletions.
2 changes: 2 additions & 0 deletions hphp/runtime/ext/random/config.cmake
@@ -0,0 +1,2 @@
HHVM_EXTENSION(random ext_random.cpp)
HHVM_SYSTEMLIB(random ext_random.php)
105 changes: 105 additions & 0 deletions hphp/runtime/ext/random/ext_random.cpp
@@ -0,0 +1,105 @@
/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010-2015 Facebook, Inc. (http://www.facebook.com) |
| Copyright (c) 1997-2010 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/

#include "hphp/runtime/ext/extension.h"
#include "hphp/runtime/base/execution-context.h"
#include "hphp/runtime/vm/runtime.h"

#include <folly/Random.h>

namespace HPHP {
///////////////////////////////////////////////////////////////////////////////

static bool getRandomBytes(void *bytes, size_t length) {
// TODO fix folly to better detect error cases (currently I think it just
// aborts) and catch them here.
folly::Random::secureRandom(bytes, length);
return true;
}

String HHVM_FUNCTION(random_bytes, int64_t length) {
if (length < 1) {
// TODO(https://github.com/facebook/hhvm/issues/6012)
// Make this throw PHP 7 `Error` when it's implemented
SystemLib::throwExceptionObject("Length must be greater than 0");
}

String ret(length, ReserveString);
if (!getRandomBytes(ret.mutableData(), ret.capacity())) {
SystemLib::throwExceptionObject("Could not gather sufficient random data");
}

return ret.setSize(length);
}

int64_t HHVM_FUNCTION(random_int, int64_t min, int64_t max) {
if (min > max) {
// TODO(https://github.com/facebook/hhvm/issues/6012)
// Make this throw PHP 7 `Error` when it's implemented
SystemLib::throwExceptionObject(
"Minimum value must be less than or equal to the maximum value");
}

if (min == max) {
return min;
}

uint64_t umax = max - min;
uint64_t result;
if (!getRandomBytes(&result, sizeof(result))) {
SystemLib::throwExceptionObject("Could not gather sufficient random data");
}

// Special case where no modulus is required
if (umax == std::numeric_limits<uint64_t>::max()) {
return result;
}

// Increment the max so the range is inclusive of max
umax++;

// Powers of two are not biased
if ((umax & (umax - 1)) != 0) {
// Ceiling under which std::numeric_limits<uint64_t>::max() % max == 0
int64_t limit = std::numeric_limits<uint64_t>::max() -
(std::numeric_limits<uint64_t>::max() % umax) - 1;

// Discard numbers over the limit to avoid modulo bias
while (result > limit) {
if (!getRandomBytes(&result, sizeof(result))) {
SystemLib::throwExceptionObject(
"Could not gather sufficient random data");
}
}
}

return (int64_t)((result % umax) + min);
}

static struct RandomExtension final : public Extension {
RandomExtension() : Extension("random") {}
void moduleInit() override {
HHVM_FE(random_bytes);
HHVM_FE(random_int);
loadSystemlib();
}
} s_random_extension;

HHVM_GET_MODULE(random);

///////////////////////////////////////////////////////////////////////////////
}
31 changes: 31 additions & 0 deletions hphp/runtime/ext/random/ext_random.php
@@ -0,0 +1,31 @@
<?hh

/**
* Generates cryptographically secure pseudo-random bytes that are suitable for
* use in cryptography when generating salts, keys and initialization vectors.
*
* @param int $length - The length of the random string in bytes.
*
* @return string - The crypto-secure random bytes in binary format.
*
* @throws Exception - If generating sufficiently random data fails.
*
*/
<<__Native>>
function random_bytes(int $length): string;

/**
* Generates cryptographic random integers that are suitable for use where
* unbiased results are critical (e.g. shuffling a Poker deck).
*
* @param int $min - The lowest value to be returned down to PHP_INT_MIN.
* @param int $max - The highest value to be returned up to PHP_INT_MAX.
*
* @return int - The crypto-secure random integer.
*
* @throws Exception - If generating sufficiently random data fails.
* @throws Error - If $min > $max.
*
*/
<<__Native>>
function random_int(int $min, int $max): int;
11 changes: 11 additions & 0 deletions hphp/test/slow/ext_random/random.php
@@ -0,0 +1,11 @@
<?php

var_dump(strlen(random_bytes(7)));
var_dump(random_int(1, 10));

// Astronomically low chance of a false positive here, and making sure we don't
// accidentally return a constant value is worth it.
var_dump(random_bytes(16) === random_bytes(16));
var_dump(
random_int(PHP_INT_MIN, PHP_INT_MAX) === random_int(PHP_INT_MIN, PHP_INT_MAX)
);
4 changes: 4 additions & 0 deletions hphp/test/slow/ext_random/random.php.expectf
@@ -0,0 +1,4 @@
int(7)
int(%d)
bool(false)
bool(false)

0 comments on commit df3688e

Please sign in to comment.