random.c 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. /*
  2. +----------------------------------------------------------------------+
  3. | PHP Version 7 |
  4. +----------------------------------------------------------------------+
  5. | Copyright (c) 1997-2018 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. #include <stdlib.h>
  19. #include <sys/stat.h>
  20. #include <fcntl.h>
  21. #include <math.h>
  22. #include "php.h"
  23. #include "zend_exceptions.h"
  24. #include "php_random.h"
  25. #ifdef PHP_WIN32
  26. # include "win32/winutil.h"
  27. #endif
  28. #ifdef __linux__
  29. # include <sys/syscall.h>
  30. #endif
  31. #if defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__)
  32. # include <sys/param.h>
  33. # if __FreeBSD__ && __FreeBSD_version > 1200000
  34. # include <sys/random.h>
  35. # endif
  36. #endif
  37. #ifdef ZTS
  38. int random_globals_id;
  39. #else
  40. php_random_globals random_globals;
  41. #endif
  42. static void random_globals_ctor(php_random_globals *random_globals_p)
  43. {
  44. random_globals_p->fd = -1;
  45. }
  46. static void random_globals_dtor(php_random_globals *random_globals_p)
  47. {
  48. if (random_globals_p->fd > 0) {
  49. close(random_globals_p->fd);
  50. random_globals_p->fd = -1;
  51. }
  52. }
  53. /* {{{ */
  54. PHP_MINIT_FUNCTION(random)
  55. {
  56. #ifdef ZTS
  57. ts_allocate_id(&random_globals_id, sizeof(php_random_globals), (ts_allocate_ctor)random_globals_ctor, (ts_allocate_dtor)random_globals_dtor);
  58. #else
  59. random_globals_ctor(&random_globals);
  60. #endif
  61. return SUCCESS;
  62. }
  63. /* }}} */
  64. /* {{{ */
  65. PHP_MSHUTDOWN_FUNCTION(random)
  66. {
  67. #ifndef ZTS
  68. random_globals_dtor(&random_globals);
  69. #endif
  70. return SUCCESS;
  71. }
  72. /* }}} */
  73. /* {{{ php_random_bytes */
  74. PHPAPI int php_random_bytes(void *bytes, size_t size, zend_bool should_throw)
  75. {
  76. #ifdef PHP_WIN32
  77. /* Defer to CryptGenRandom on Windows */
  78. if (php_win32_get_random_bytes(bytes, size) == FAILURE) {
  79. if (should_throw) {
  80. zend_throw_exception(zend_ce_exception, "Could not gather sufficient random data", 0);
  81. }
  82. return FAILURE;
  83. }
  84. #elif HAVE_DECL_ARC4RANDOM_BUF && ((defined(__OpenBSD__) && OpenBSD >= 201405) || (defined(__NetBSD__) && __NetBSD_Version__ >= 700000001))
  85. arc4random_buf(bytes, size);
  86. #else
  87. size_t read_bytes = 0;
  88. ssize_t n;
  89. #if (defined(__linux__) && defined(SYS_getrandom)) || (defined(__FreeBSD__) && __FreeBSD_version >= 1200000)
  90. /* Linux getrandom(2) syscall or FreeBSD getrandom(2) function*/
  91. /* Keep reading until we get enough entropy */
  92. while (read_bytes < size) {
  93. /* Below, (bytes + read_bytes) is pointer arithmetic.
  94. bytes read_bytes size
  95. | | |
  96. [#######=============] (we're going to write over the = region)
  97. \\\\\\\\\\\\\
  98. amount_to_read
  99. */
  100. size_t amount_to_read = size - read_bytes;
  101. #if defined(__linux__)
  102. n = syscall(SYS_getrandom, bytes + read_bytes, amount_to_read, 0);
  103. #else
  104. n = getrandom(bytes + read_bytes, amount_to_read, 0);
  105. #endif
  106. if (n == -1) {
  107. if (errno == ENOSYS) {
  108. /* This can happen if PHP was compiled against a newer kernel where getrandom()
  109. * is available, but then runs on an older kernel without getrandom(). If this
  110. * happens we simply fall back to reading from /dev/urandom. */
  111. ZEND_ASSERT(read_bytes == 0);
  112. break;
  113. } else if (errno == EINTR || errno == EAGAIN) {
  114. /* Try again */
  115. continue;
  116. } else {
  117. /* If the syscall fails, fall back to reading from /dev/urandom */
  118. break;
  119. }
  120. }
  121. read_bytes += (size_t) n;
  122. }
  123. #endif
  124. if (read_bytes < size) {
  125. int fd = RANDOM_G(fd);
  126. struct stat st;
  127. if (fd < 0) {
  128. #if HAVE_DEV_URANDOM
  129. fd = open("/dev/urandom", O_RDONLY);
  130. #endif
  131. if (fd < 0) {
  132. if (should_throw) {
  133. zend_throw_exception(zend_ce_exception, "Cannot open source device", 0);
  134. }
  135. return FAILURE;
  136. }
  137. /* Does the file exist and is it a character device? */
  138. if (fstat(fd, &st) != 0 ||
  139. # ifdef S_ISNAM
  140. !(S_ISNAM(st.st_mode) || S_ISCHR(st.st_mode))
  141. # else
  142. !S_ISCHR(st.st_mode)
  143. # endif
  144. ) {
  145. close(fd);
  146. if (should_throw) {
  147. zend_throw_exception(zend_ce_exception, "Error reading from source device", 0);
  148. }
  149. return FAILURE;
  150. }
  151. RANDOM_G(fd) = fd;
  152. }
  153. for (read_bytes = 0; read_bytes < size; read_bytes += (size_t) n) {
  154. n = read(fd, bytes + read_bytes, size - read_bytes);
  155. if (n <= 0) {
  156. break;
  157. }
  158. }
  159. if (read_bytes < size) {
  160. if (should_throw) {
  161. zend_throw_exception(zend_ce_exception, "Could not gather sufficient random data", 0);
  162. }
  163. return FAILURE;
  164. }
  165. }
  166. #endif
  167. return SUCCESS;
  168. }
  169. /* }}} */
  170. /* {{{ proto string random_bytes(int length)
  171. Return an arbitrary length of pseudo-random bytes as binary string */
  172. PHP_FUNCTION(random_bytes)
  173. {
  174. zend_long size;
  175. zend_string *bytes;
  176. ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1)
  177. Z_PARAM_LONG(size)
  178. ZEND_PARSE_PARAMETERS_END();
  179. if (size < 1) {
  180. zend_throw_exception(zend_ce_error, "Length must be greater than 0", 0);
  181. return;
  182. }
  183. bytes = zend_string_alloc(size, 0);
  184. if (php_random_bytes_throw(ZSTR_VAL(bytes), size) == FAILURE) {
  185. zend_string_release_ex(bytes, 0);
  186. return;
  187. }
  188. ZSTR_VAL(bytes)[size] = '\0';
  189. RETURN_STR(bytes);
  190. }
  191. /* }}} */
  192. /* {{{ */
  193. PHPAPI int php_random_int(zend_long min, zend_long max, zend_long *result, zend_bool should_throw)
  194. {
  195. zend_ulong umax;
  196. zend_ulong trial;
  197. if (min == max) {
  198. *result = min;
  199. return SUCCESS;
  200. }
  201. umax = max - min;
  202. if (php_random_bytes(&trial, sizeof(trial), should_throw) == FAILURE) {
  203. return FAILURE;
  204. }
  205. /* Special case where no modulus is required */
  206. if (umax == ZEND_ULONG_MAX) {
  207. *result = (zend_long)trial;
  208. return SUCCESS;
  209. }
  210. /* Increment the max so the range is inclusive of max */
  211. umax++;
  212. /* Powers of two are not biased */
  213. if ((umax & (umax - 1)) != 0) {
  214. /* Ceiling under which ZEND_LONG_MAX % max == 0 */
  215. zend_ulong limit = ZEND_ULONG_MAX - (ZEND_ULONG_MAX % umax) - 1;
  216. /* Discard numbers over the limit to avoid modulo bias */
  217. while (trial > limit) {
  218. if (php_random_bytes(&trial, sizeof(trial), should_throw) == FAILURE) {
  219. return FAILURE;
  220. }
  221. }
  222. }
  223. *result = (zend_long)((trial % umax) + min);
  224. return SUCCESS;
  225. }
  226. /* }}} */
  227. /* {{{ proto int random_int(int min, int max)
  228. Return an arbitrary pseudo-random integer */
  229. PHP_FUNCTION(random_int)
  230. {
  231. zend_long min;
  232. zend_long max;
  233. zend_long result;
  234. ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 2, 2)
  235. Z_PARAM_LONG(min)
  236. Z_PARAM_LONG(max)
  237. ZEND_PARSE_PARAMETERS_END();
  238. if (min > max) {
  239. zend_throw_exception(zend_ce_error, "Minimum value must be less than or equal to the maximum value", 0);
  240. return;
  241. }
  242. if (php_random_int_throw(min, max, &result) == FAILURE) {
  243. return;
  244. }
  245. RETURN_LONG(result);
  246. }
  247. /* }}} */
  248. /*
  249. * Local variables:
  250. * tab-width: 4
  251. * c-basic-offset: 4
  252. * End:
  253. * vim600: sw=4 ts=4 fdm=marker
  254. * vim<600: sw=4 ts=4
  255. */