diff --git a/wp-includes/pluggable.php b/wp-includes/pluggable.php index 6a19509c55..0bbd91b6b7 100644 --- a/wp-includes/pluggable.php +++ b/wp-includes/pluggable.php @@ -2188,7 +2188,7 @@ function wp_rand( $min = 0, $max = 0 ) { } else { $use_random_int_functionality = false; } - } catch ( Throwable $t ) { + } catch ( Error $e ) { $use_random_int_functionality = false; } catch ( Exception $e ) { $use_random_int_functionality = false; diff --git a/wp-includes/random_compat/byte_safe_strings.php b/wp-includes/random_compat/byte_safe_strings.php index c6dc2a865a..a3cc90b146 100644 --- a/wp-includes/random_compat/byte_safe_strings.php +++ b/wp-includes/random_compat/byte_safe_strings.php @@ -27,7 +27,10 @@ */ if (!function_exists('RandomCompat_strlen')) { - if (defined('MB_OVERLOAD_STRING') && ini_get('mbstring.func_overload') & MB_OVERLOAD_STRING) { + if ( + defined('MB_OVERLOAD_STRING') && + ini_get('mbstring.func_overload') & MB_OVERLOAD_STRING + ) { /** * strlen() implementation that isn't brittle to mbstring.func_overload * @@ -74,7 +77,10 @@ if (!function_exists('RandomCompat_strlen')) { } if (!function_exists('RandomCompat_substr')) { - if (defined('MB_OVERLOAD_STRING') && ini_get('mbstring.func_overload') & MB_OVERLOAD_STRING) { + if ( + defined('MB_OVERLOAD_STRING') && + ini_get('mbstring.func_overload') & MB_OVERLOAD_STRING + ) { /** * substr() implementation that isn't brittle to mbstring.func_overload * diff --git a/wp-includes/random_compat/cast_to_int.php b/wp-includes/random_compat/cast_to_int.php new file mode 100644 index 0000000000..474ce64008 --- /dev/null +++ b/wp-includes/random_compat/cast_to_int.php @@ -0,0 +1,64 @@ + operators might accidentally let a float + * through. + * + * @param numeric $number The number we want to convert to an int + * @param boolean $fail_open Set to true to not throw an exception + * + * @return int (or float if $fail_open) + */ + function RandomCompat_intval($number, $fail_open = false) + { + if (is_numeric($number)) { + $number += 0; + } + if ( + is_float($number) && + $number > ~PHP_INT_MAX && + $number < PHP_INT_MAX + ) { + $number = (int) $number; + } + if (is_int($number) || $fail_open) { + return $number; + } + throw new TypeError( + 'Expected an integer.' + ); + } +} diff --git a/wp-includes/random_compat/error_polyfill.php b/wp-includes/random_compat/error_polyfill.php index 9c73649289..57cfefdcd1 100644 --- a/wp-includes/random_compat/error_polyfill.php +++ b/wp-includes/random_compat/error_polyfill.php @@ -26,15 +26,9 @@ * SOFTWARE. */ -if (!interface_exists('Throwable', false)) { - interface Throwable - { - } -} - if (!class_exists('Error', false)) { // We can't really avoid making this extend Exception in PHP 5. - class Error extends Exception implements Throwable + class Error extends Exception { } diff --git a/wp-includes/random_compat/random.php b/wp-includes/random_compat/random.php index 1530f7ee25..1a4b326ed4 100644 --- a/wp-includes/random_compat/random.php +++ b/wp-includes/random_compat/random.php @@ -28,15 +28,18 @@ if (!defined('PHP_VERSION_ID')) { // This constant was introduced in PHP 5.2.7 - $version = explode('.', PHP_VERSION); - define('PHP_VERSION_ID', ($version[0] * 10000 + $version[1] * 100 + $version[2])); + $RandomCompatversion = explode('.', PHP_VERSION); + define('PHP_VERSION_ID', ($RandomCompatversion[0] * 10000 + $RandomCompatversion[1] * 100 + $RandomCompatversion[2])); + unset($RandomCompatversion); } if (PHP_VERSION_ID < 70000) { if (!defined('RANDOM_COMPAT_READ_BUFFER')) { define('RANDOM_COMPAT_READ_BUFFER', 8); } - require_once "byte_safe_strings.php"; - require_once "error_polyfill.php"; + $__DIR__ = dirname(__FILE__); + require_once $__DIR__.'/byte_safe_strings.php'; + require_once $__DIR__.'/cast_to_int.php'; + require_once $__DIR__.'/error_polyfill.php'; if (!function_exists('random_bytes')) { /** * PHP 5.2.0 - 5.6.x way to implement random_bytes() @@ -45,26 +48,68 @@ if (PHP_VERSION_ID < 70000) { * to the operating environment. It's a micro-optimization. * * In order of preference: - * 1. fread() /dev/urandom if available - * 2. mcrypt_create_iv($bytes, MCRYPT_CREATE_IV) - * 3. COM('CAPICOM.Utilities.1')->GetRandom() - * 4. openssl_random_pseudo_bytes() + * 1. Use libsodium if available. + * 2. fread() /dev/urandom if available (never on Windows) + * 3. mcrypt_create_iv($bytes, MCRYPT_CREATE_IV) + * 4. COM('CAPICOM.Utilities.1')->GetRandom() + * 5. openssl_random_pseudo_bytes() (absolute last resort) * * See ERRATA.md for our reasoning behind this particular order */ - if (!ini_get('open_basedir') && is_readable('/dev/urandom')) { + if (extension_loaded('libsodium')) { + // See random_bytes_libsodium.php + require_once $__DIR__.'/random_bytes_libsodium.php'; + } + if ( + !function_exists('random_bytes') && + DIRECTORY_SEPARATOR === '/' && + @is_readable('/dev/urandom') + ) { + // DIRECTORY_SEPARATOR === '/' on Unix-like OSes -- this is a fast + // way to exclude Windows. + // + // Error suppression on is_readable() in case of an open_basedir or + // safe_mode failure. All we care about is whether or not we can + // read it at this point. If the PHP environment is going to panic + // over trying to see if the file can be read in the first place, + // that is not helpful to us here. + // See random_bytes_dev_urandom.php - require_once "random_bytes_dev_urandom.php"; - } elseif (PHP_VERSION_ID >= 50307 && function_exists('mcrypt_create_iv')) { + require_once $__DIR__.'/random_bytes_dev_urandom.php'; + } + if ( + !function_exists('random_bytes') && + PHP_VERSION_ID >= 50307 && + extension_loaded('mcrypt') + ) { // See random_bytes_mcrypt.php - require_once "random_bytes_mcrypt.php"; - } elseif (extension_loaded('com_dotnet')) { - // See random_bytes_com_dotnet.php - require_once "random_bytes_com_dotnet.php"; - } elseif (function_exists('openssl_random_pseudo_bytes')) { + require_once $__DIR__.'/random_bytes_mcrypt.php'; + } + if ( + !function_exists('random_bytes') && + extension_loaded('com_dotnet') && + class_exists('COM') + ) { + try { + $RandomCompatCOMtest = new COM('CAPICOM.Utilities.1'); + if (method_exists($RandomCompatCOMtest, 'GetRandom')) { + // See random_bytes_com_dotnet.php + require_once $__DIR__.'/random_bytes_com_dotnet.php'; + } + } catch (com_exception $e) { + // Don't try to use it. + } + unset($RandomCompatCOMtest); + } + if ( + !function_exists('random_bytes') && + extension_loaded('openssl') && + PHP_VERSION_ID >= 50300 + ) { // See random_bytes_openssl.php - require_once "random_bytes_openssl.php"; - } else { + require_once $__DIR__.'/random_bytes_openssl.php'; + } + if (!function_exists('random_bytes')) { /** * We don't have any more options, so let's throw an exception right now * and hope the developer won't let it fail silently. @@ -78,6 +123,7 @@ if (PHP_VERSION_ID < 70000) { } } if (!function_exists('random_int')) { - require_once "random_int.php"; + require_once $__DIR__.'/random_int.php'; } + unset($__DIR__); } diff --git a/wp-includes/random_compat/random_bytes_com_dotnet.php b/wp-includes/random_compat/random_bytes_com_dotnet.php index cebe616ab2..c0ae639a8d 100644 --- a/wp-includes/random_compat/random_bytes_com_dotnet.php +++ b/wp-includes/random_compat/random_bytes_com_dotnet.php @@ -39,9 +39,11 @@ */ function random_bytes($bytes) { - if (!is_int($bytes)) { + try { + $bytes = RandomCompat_intval($bytes); + } catch (TypeError $ex) { throw new TypeError( - 'Length must be an integer' + 'random_bytes(): $bytes must be an integer' ); } if ($bytes < 1) { diff --git a/wp-includes/random_compat/random_bytes_dev_urandom.php b/wp-includes/random_compat/random_bytes_dev_urandom.php index 09bd49f551..5606dbbec9 100644 --- a/wp-includes/random_compat/random_bytes_dev_urandom.php +++ b/wp-includes/random_compat/random_bytes_dev_urandom.php @@ -74,9 +74,11 @@ function random_bytes($bytes) stream_set_read_buffer($fp, RANDOM_COMPAT_READ_BUFFER); } } - if (!is_int($bytes)) { + try { + $bytes = RandomCompat_intval($bytes); + } catch (TypeError $ex) { throw new TypeError( - 'Length must be an integer' + 'random_bytes(): $bytes must be an integer' ); } if ($bytes < 1) { diff --git a/wp-includes/random_compat/random_bytes_libsodium.php b/wp-includes/random_compat/random_bytes_libsodium.php new file mode 100644 index 0000000000..796ba6a626 --- /dev/null +++ b/wp-includes/random_compat/random_bytes_libsodium.php @@ -0,0 +1,84 @@ + 2147483647) { + $buf = ''; + for ($i = 0; $i < $bytes; $i += 1073741824) { + $n = ($bytes - $i) > 1073741824 + ? 1073741824 + : $bytes - $i; + $buf .= \Sodium\randombytes_buf($n); + } + } else { + $buf = \Sodium\randombytes_buf($bytes); + } + + if ($buf !== false) { + if (RandomCompat_strlen($buf) === $bytes) { + return $buf; + } + } + + /** + * If we reach here, PHP has failed us. + */ + throw new Exception( + 'PHP failed to generate random data.' + ); +} diff --git a/wp-includes/random_compat/random_bytes_mcrypt.php b/wp-includes/random_compat/random_bytes_mcrypt.php index 8d3b728371..8524c52245 100644 --- a/wp-includes/random_compat/random_bytes_mcrypt.php +++ b/wp-includes/random_compat/random_bytes_mcrypt.php @@ -41,9 +41,11 @@ */ function random_bytes($bytes) { - if (!is_int($bytes)) { + try { + $bytes = RandomCompat_intval($bytes); + } catch (TypeError $ex) { throw new TypeError( - 'Length must be an integer' + 'random_bytes(): $bytes must be an integer' ); } if ($bytes < 1) { @@ -52,7 +54,7 @@ function random_bytes($bytes) ); } - $buf = mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM); + $buf = @mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM); if ($buf !== false) { if (RandomCompat_strlen($buf) === $bytes) { /** diff --git a/wp-includes/random_compat/random_bytes_openssl.php b/wp-includes/random_compat/random_bytes_openssl.php index 075fd8d89b..db05e1fe3c 100644 --- a/wp-includes/random_compat/random_bytes_openssl.php +++ b/wp-includes/random_compat/random_bytes_openssl.php @@ -41,9 +41,11 @@ */ function random_bytes($bytes) { - if (!is_int($bytes)) { + try { + $bytes = RandomCompat_intval($bytes); + } catch (TypeError $ex) { throw new TypeError( - 'Length must be an integer' + 'random_bytes(): $bytes must be an integer' ); } if ($bytes < 1) { diff --git a/wp-includes/random_compat/random_int.php b/wp-includes/random_compat/random_int.php index 7fd50e7efd..6c91dbc516 100644 --- a/wp-includes/random_compat/random_int.php +++ b/wp-includes/random_compat/random_int.php @@ -40,21 +40,34 @@ function random_int($min, $max) { /** * Type and input logic checks + * + * If you pass it a float in the range (~PHP_INT_MAX, PHP_INT_MAX) + * (non-inclusive), it will sanely cast it to an int. If you it's equal to + * ~PHP_INT_MAX or PHP_INT_MAX, we let it fail as not an integer. Floats + * lose precision, so the <= and => operators might accidentally let a float + * through. */ - if (!is_numeric($min)) { + + try { + $min = RandomCompat_intval($min); + } catch (TypeError $ex) { throw new TypeError( 'random_int(): $min must be an integer' ); } - if (!is_numeric($max)) { + try { + $max = RandomCompat_intval($max); + } catch (TypeError $ex) { throw new TypeError( 'random_int(): $max must be an integer' ); } - - $min = (int) $min; - $max = (int) $max; - + + /** + * Now that we've verified our weak typing system has given us an integer, + * let's validate the logic then we can move forward with generating random + * integers along a given range. + */ if ($min > $max) { throw new Error( 'Minimum value must be less than or equal to the maximum value' @@ -164,7 +177,7 @@ function random_int($min, $max) /** * If $val overflows to a floating point number, * ... or is larger than $max, - * ... or smaller than $int, + * ... or smaller than $min, * then try again. */ } while (!is_int($val) || $val > $max || $val < $min);