root/ext/standard/random.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. random_globals_ctor
  2. random_globals_dtor
  3. PHP_MINIT_FUNCTION
  4. PHP_MSHUTDOWN_FUNCTION
  5. php_random_bytes
  6. PHP_FUNCTION
  7. PHP_FUNCTION

   1 /*
   2    +----------------------------------------------------------------------+
   3    | PHP Version 7                                                        |
   4    +----------------------------------------------------------------------+
   5    | Copyright (c) 1997-2016 The PHP Group                                |
   6    +----------------------------------------------------------------------+
   7    | This source file is subject to version 3.01 of the PHP license,      |
   8    | that is bundled with this package in the file LICENSE, and is        |
   9    | available through the world-wide-web at the following url:           |
  10    | http://www.php.net/license/3_01.txt                                  |
  11    | If you did not receive a copy of the PHP license and are unable to   |
  12    | obtain it through the world-wide-web, please send a note to          |
  13    | license@php.net so we can mail you a copy immediately.               |
  14    +----------------------------------------------------------------------+
  15    | Authors: Sammy Kaye Powers <me@sammyk.me>                            |
  16    +----------------------------------------------------------------------+
  17 */
  18 
  19 /* $Id$ */
  20 
  21 #include <stdlib.h>
  22 #include <sys/stat.h>
  23 #include <fcntl.h>
  24 #include <math.h>
  25 
  26 #include "php.h"
  27 #include "zend_exceptions.h"
  28 #include "php_random.h"
  29 
  30 #if PHP_WIN32
  31 # include "win32/winutil.h"
  32 #endif
  33 #ifdef __linux__
  34 # include <sys/syscall.h>
  35 #endif
  36 #if defined(__OpenBSD__) || defined(__NetBSD__)
  37 # include <sys/param.h>
  38 #endif
  39 
  40 #ifdef ZTS
  41 int random_globals_id;
  42 #else
  43 php_random_globals random_globals;
  44 #endif
  45 
  46 static void random_globals_ctor(php_random_globals *random_globals_p)
  47 {
  48         random_globals_p->fd = -1;
  49 }
  50 
  51 static void random_globals_dtor(php_random_globals *random_globals_p)
  52 {
  53         if (random_globals_p->fd > 0) {
  54                 close(random_globals_p->fd);
  55                 random_globals_p->fd = -1;
  56         }
  57 }
  58 
  59 /* {{{ */
  60 PHP_MINIT_FUNCTION(random)
  61 {
  62 #ifdef ZTS
  63         ts_allocate_id(&random_globals_id, sizeof(php_random_globals), (ts_allocate_ctor)random_globals_ctor, (ts_allocate_dtor)random_globals_dtor);
  64 #else
  65         random_globals_ctor(&random_globals);
  66 #endif
  67 
  68         return SUCCESS;
  69 }
  70 /* }}} */
  71 
  72 /* {{{ */
  73 PHP_MSHUTDOWN_FUNCTION(random)
  74 {
  75 #ifndef ZTS
  76         random_globals_dtor(&random_globals);
  77 #endif
  78 
  79         return SUCCESS;
  80 }
  81 /* }}} */
  82 
  83 /* {{{ */
  84 
  85 PHPAPI int php_random_bytes(void *bytes, size_t size, zend_bool should_throw)
  86 {
  87 #if PHP_WIN32
  88         /* Defer to CryptGenRandom on Windows */
  89         if (php_win32_get_random_bytes(bytes, size) == FAILURE) {
  90                 if (should_throw) {
  91                         zend_throw_exception(zend_ce_exception, "Could not gather sufficient random data", 0);
  92                 }
  93                 return FAILURE;
  94         }
  95 #elif HAVE_DECL_ARC4RANDOM_BUF && ((defined(__OpenBSD__) && OpenBSD >= 201405) || (defined(__NetBSD__) && __NetBSD_Version__ >= 700000001))
  96         arc4random_buf(bytes, size);
  97 #elif HAVE_DECL_GETRANDOM
  98         /* Linux getrandom(2) syscall */
  99         size_t read_bytes = 0;
 100         size_t amount_to_read = 0;
 101         ssize_t n;
 102 
 103         /* Keep reading until we get enough entropy */
 104         do {
 105                 /* Below, (bytes + read_bytes)  is pointer arithmetic.
 106 
 107                    bytes   read_bytes  size
 108                      |      |           |
 109                     [#######=============] (we're going to write over the = region)
 110                              \\\\\\\\\\\\\
 111                               amount_to_read
 112 
 113                 */
 114                 amount_to_read = size - read_bytes;
 115                 n = syscall(SYS_getrandom, bytes + read_bytes, amount_to_read, 0);
 116 
 117                 if (n == -1) {
 118                         if (errno == EINTR || errno == EAGAIN) {
 119                                 /* Try again */
 120                                 continue;
 121                         }
 122                         /*
 123                                 If the syscall fails, we are doomed. The loop that calls
 124                                 php_random_bytes should be terminated by the exception instead
 125                                 of proceeding to demand more entropy.
 126                         */
 127                         if (should_throw) {
 128                                 zend_throw_exception(zend_ce_exception, "Could not gather sufficient random data", errno);
 129                         }
 130                         return FAILURE;
 131                 }
 132 
 133                 read_bytes += (size_t) n;
 134         } while (read_bytes < size);
 135 #else
 136         int    fd = RANDOM_G(fd);
 137         struct stat st;
 138         size_t read_bytes = 0;
 139         ssize_t n;
 140 
 141         if (fd < 0) {
 142 #if HAVE_DEV_URANDOM
 143                 fd = open("/dev/urandom", O_RDONLY);
 144 #endif
 145                 if (fd < 0) {
 146                         if (should_throw) {
 147                                 zend_throw_exception(zend_ce_exception, "Cannot open source device", 0);
 148                         }
 149                         return FAILURE;
 150                 }
 151                 /* Does the file exist and is it a character device? */
 152                 if (fstat(fd, &st) != 0 || 
 153 # ifdef S_ISNAM
 154                 !(S_ISNAM(st.st_mode) || S_ISCHR(st.st_mode))
 155 # else
 156                 !S_ISCHR(st.st_mode)
 157 # endif
 158                 ) {
 159                         close(fd);
 160                         if (should_throw) {
 161                                 zend_throw_exception(zend_ce_exception, "Error reading from source device", 0);
 162                         }
 163                         return FAILURE;
 164                 }
 165                 RANDOM_G(fd) = fd;
 166         }
 167 
 168         while (read_bytes < size) {
 169                 n = read(fd, bytes + read_bytes, size - read_bytes);
 170                 if (n <= 0) {
 171                         break;
 172                 }
 173                 read_bytes += n;
 174         }
 175 
 176         if (read_bytes < size) {
 177                 if (should_throw) {
 178                         zend_throw_exception(zend_ce_exception, "Could not gather sufficient random data", 0);
 179                 }
 180                 return FAILURE;
 181         }
 182 #endif
 183 
 184         return SUCCESS;
 185 }
 186 /* }}} */
 187 
 188 /* {{{ proto string random_bytes(int length)
 189 Return an arbitrary length of pseudo-random bytes as binary string */
 190 PHP_FUNCTION(random_bytes)
 191 {
 192         zend_long size;
 193         zend_string *bytes;
 194 
 195         if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "l", &size) == FAILURE) {
 196                 return;
 197         }
 198 
 199         if (size < 1) {
 200                 zend_throw_exception(zend_ce_error, "Length must be greater than 0", 0);
 201                 return;
 202         }
 203 
 204         bytes = zend_string_alloc(size, 0);
 205 
 206         if (php_random_bytes_throw(ZSTR_VAL(bytes), size) == FAILURE) {
 207                 zend_string_release(bytes);
 208                 return;
 209         }
 210 
 211         ZSTR_VAL(bytes)[size] = '\0';
 212 
 213         RETURN_STR(bytes);
 214 }
 215 /* }}} */
 216 
 217 /* {{{ proto int random_int(int min, int max)
 218 Return an arbitrary pseudo-random integer */
 219 PHP_FUNCTION(random_int)
 220 {
 221         zend_long min;
 222         zend_long max;
 223         zend_ulong umax;
 224         zend_ulong result;
 225 
 226         if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "ll", &min, &max) == FAILURE) {
 227                 return;
 228         }
 229 
 230         if (min > max) {
 231                 zend_throw_exception(zend_ce_error, "Minimum value must be less than or equal to the maximum value", 0);
 232                 return;
 233         }
 234 
 235         if (min == max) {
 236                 RETURN_LONG(min);
 237         }
 238 
 239         umax = max - min;
 240 
 241         if (php_random_bytes_throw(&result, sizeof(result)) == FAILURE) {
 242                 return;
 243         }
 244 
 245         /* Special case where no modulus is required */
 246         if (umax == ZEND_ULONG_MAX) {
 247                 RETURN_LONG((zend_long)result);
 248         }
 249 
 250         /* Increment the max so the range is inclusive of max */
 251         umax++;
 252 
 253         /* Powers of two are not biased */
 254         if ((umax & (umax - 1)) != 0) {
 255                 /* Ceiling under which ZEND_LONG_MAX % max == 0 */
 256                 zend_ulong limit = ZEND_ULONG_MAX - (ZEND_ULONG_MAX % umax) - 1;
 257 
 258                 /* Discard numbers over the limit to avoid modulo bias */
 259                 while (result > limit) {
 260                         if (php_random_bytes_throw(&result, sizeof(result)) == FAILURE) {
 261                                 return;
 262                         }
 263                 }
 264         }
 265 
 266         RETURN_LONG((zend_long)((result % umax) + min));
 267 }
 268 /* }}} */
 269 
 270 /*
 271  * Local variables:
 272  * tab-width: 4
 273  * c-basic-offset: 4
 274  * End:
 275  * vim600: sw=4 ts=4 fdm=marker
 276  * vim<600: sw=4 ts=4
 277  */

/* [<][>][^][v][top][bottom][index][help] */