random.c 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. /*
  2. +----------------------------------------------------------------------+
  3. | Copyright (c) The PHP Group |
  4. +----------------------------------------------------------------------+
  5. | This source file is subject to version 3.01 of the PHP license, |
  6. | that is bundled with this package in the file LICENSE, and is |
  7. | available through the world-wide-web at the following url: |
  8. | https://www.php.net/license/3_01.txt |
  9. | If you did not receive a copy of the PHP license and are unable to |
  10. | obtain it through the world-wide-web, please send a note to |
  11. | license@php.net so we can mail you a copy immediately. |
  12. +----------------------------------------------------------------------+
  13. | Authors: Sammy Kaye Powers <me@sammyk.me> |
  14. +----------------------------------------------------------------------+
  15. */
  16. #include <stdlib.h>
  17. #include <sys/stat.h>
  18. #include <fcntl.h>
  19. #include <math.h>
  20. #include "php.h"
  21. #include "zend_exceptions.h"
  22. #include "php_random.h"
  23. #ifdef PHP_WIN32
  24. # include "win32/winutil.h"
  25. #endif
  26. #ifdef __linux__
  27. # include <sys/syscall.h>
  28. #endif
  29. #if HAVE_SYS_PARAM_H
  30. # include <sys/param.h>
  31. # if (__FreeBSD__ && __FreeBSD_version > 1200000) || (__DragonFly__ && __DragonFly_version >= 500700) || defined(__sun)
  32. # include <sys/random.h>
  33. # endif
  34. #endif
  35. #if HAVE_COMMONCRYPTO_COMMONRANDOM_H
  36. # include <CommonCrypto/CommonCryptoError.h>
  37. # include <CommonCrypto/CommonRandom.h>
  38. #endif
  39. #if __has_feature(memory_sanitizer)
  40. # include <sanitizer/msan_interface.h>
  41. #endif
  42. #ifdef ZTS
  43. int random_globals_id;
  44. #else
  45. php_random_globals random_globals;
  46. #endif
  47. static void random_globals_ctor(php_random_globals *random_globals_p)
  48. {
  49. random_globals_p->fd = -1;
  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. PHP_MINIT_FUNCTION(random)
  60. {
  61. #ifdef ZTS
  62. ts_allocate_id(&random_globals_id, sizeof(php_random_globals), (ts_allocate_ctor)random_globals_ctor, (ts_allocate_dtor)random_globals_dtor);
  63. #else
  64. random_globals_ctor(&random_globals);
  65. #endif
  66. return SUCCESS;
  67. }
  68. /* }}} */
  69. /* {{{ */
  70. PHP_MSHUTDOWN_FUNCTION(random)
  71. {
  72. #ifndef ZTS
  73. random_globals_dtor(&random_globals);
  74. #endif
  75. return SUCCESS;
  76. }
  77. /* }}} */
  78. /* {{{ php_random_bytes */
  79. PHPAPI int php_random_bytes(void *bytes, size_t size, bool should_throw)
  80. {
  81. #ifdef PHP_WIN32
  82. /* Defer to CryptGenRandom on Windows */
  83. if (php_win32_get_random_bytes(bytes, size) == FAILURE) {
  84. if (should_throw) {
  85. zend_throw_exception(zend_ce_exception, "Could not gather sufficient random data", 0);
  86. }
  87. return FAILURE;
  88. }
  89. #elif HAVE_COMMONCRYPTO_COMMONRANDOM_H
  90. /*
  91. * Purposely prioritized upon arc4random_buf for modern macOs releases
  92. * arc4random api on this platform uses `ccrng_generate` which returns
  93. * a status but silented to respect the "no fail" arc4random api interface
  94. * the vast majority of the time, it works fine ; but better make sure we catch failures
  95. */
  96. if (CCRandomGenerateBytes(bytes, size) != kCCSuccess) {
  97. if (should_throw) {
  98. zend_throw_exception(zend_ce_exception, "Error generating bytes", 0);
  99. }
  100. return FAILURE;
  101. }
  102. #elif HAVE_DECL_ARC4RANDOM_BUF && ((defined(__OpenBSD__) && OpenBSD >= 201405) || (defined(__NetBSD__) && __NetBSD_Version__ >= 700000001) || defined(__APPLE__))
  103. arc4random_buf(bytes, size);
  104. #else
  105. size_t read_bytes = 0;
  106. ssize_t n;
  107. #if (defined(__linux__) && defined(SYS_getrandom)) || (defined(__FreeBSD__) && __FreeBSD_version >= 1200000) || (defined(__DragonFly__) && __DragonFly_version >= 500700) || defined(__sun)
  108. /* Linux getrandom(2) syscall or FreeBSD/DragonFlyBSD getrandom(2) function*/
  109. /* Keep reading until we get enough entropy */
  110. while (read_bytes < size) {
  111. /* Below, (bytes + read_bytes) is pointer arithmetic.
  112. bytes read_bytes size
  113. | | |
  114. [#######=============] (we're going to write over the = region)
  115. \\\\\\\\\\\\\
  116. amount_to_read
  117. */
  118. size_t amount_to_read = size - read_bytes;
  119. #if defined(__linux__)
  120. n = syscall(SYS_getrandom, bytes + read_bytes, amount_to_read, 0);
  121. #else
  122. n = getrandom(bytes + read_bytes, amount_to_read, 0);
  123. #endif
  124. if (n == -1) {
  125. if (errno == ENOSYS) {
  126. /* This can happen if PHP was compiled against a newer kernel where getrandom()
  127. * is available, but then runs on an older kernel without getrandom(). If this
  128. * happens we simply fall back to reading from /dev/urandom. */
  129. ZEND_ASSERT(read_bytes == 0);
  130. break;
  131. } else if (errno == EINTR || errno == EAGAIN) {
  132. /* Try again */
  133. continue;
  134. } else {
  135. /* If the syscall fails, fall back to reading from /dev/urandom */
  136. break;
  137. }
  138. }
  139. #if __has_feature(memory_sanitizer)
  140. /* MSan does not instrument manual syscall invocations. */
  141. __msan_unpoison(bytes + read_bytes, n);
  142. #endif
  143. read_bytes += (size_t) n;
  144. }
  145. #endif
  146. if (read_bytes < size) {
  147. int fd = RANDOM_G(fd);
  148. struct stat st;
  149. if (fd < 0) {
  150. #if HAVE_DEV_URANDOM
  151. fd = open("/dev/urandom", O_RDONLY);
  152. #endif
  153. if (fd < 0) {
  154. if (should_throw) {
  155. zend_throw_exception(zend_ce_exception, "Cannot open source device", 0);
  156. }
  157. return FAILURE;
  158. }
  159. /* Does the file exist and is it a character device? */
  160. if (fstat(fd, &st) != 0 ||
  161. # ifdef S_ISNAM
  162. !(S_ISNAM(st.st_mode) || S_ISCHR(st.st_mode))
  163. # else
  164. !S_ISCHR(st.st_mode)
  165. # endif
  166. ) {
  167. close(fd);
  168. if (should_throw) {
  169. zend_throw_exception(zend_ce_exception, "Error reading from source device", 0);
  170. }
  171. return FAILURE;
  172. }
  173. RANDOM_G(fd) = fd;
  174. }
  175. for (read_bytes = 0; read_bytes < size; read_bytes += (size_t) n) {
  176. n = read(fd, bytes + read_bytes, size - read_bytes);
  177. if (n <= 0) {
  178. break;
  179. }
  180. }
  181. if (read_bytes < size) {
  182. if (should_throw) {
  183. zend_throw_exception(zend_ce_exception, "Could not gather sufficient random data", 0);
  184. }
  185. return FAILURE;
  186. }
  187. }
  188. #endif
  189. return SUCCESS;
  190. }
  191. /* }}} */
  192. /* {{{ Return an arbitrary length of pseudo-random bytes as binary string */
  193. PHP_FUNCTION(random_bytes)
  194. {
  195. zend_long size;
  196. zend_string *bytes;
  197. ZEND_PARSE_PARAMETERS_START(1, 1)
  198. Z_PARAM_LONG(size)
  199. ZEND_PARSE_PARAMETERS_END();
  200. if (size < 1) {
  201. zend_argument_value_error(1, "must be greater than 0");
  202. RETURN_THROWS();
  203. }
  204. bytes = zend_string_alloc(size, 0);
  205. if (php_random_bytes_throw(ZSTR_VAL(bytes), size) == FAILURE) {
  206. zend_string_release_ex(bytes, 0);
  207. RETURN_THROWS();
  208. }
  209. ZSTR_VAL(bytes)[size] = '\0';
  210. RETURN_STR(bytes);
  211. }
  212. /* }}} */
  213. /* {{{ */
  214. PHPAPI int php_random_int(zend_long min, zend_long max, zend_long *result, bool should_throw)
  215. {
  216. zend_ulong umax;
  217. zend_ulong trial;
  218. if (min == max) {
  219. *result = min;
  220. return SUCCESS;
  221. }
  222. umax = (zend_ulong) max - (zend_ulong) min;
  223. if (php_random_bytes(&trial, sizeof(trial), should_throw) == FAILURE) {
  224. return FAILURE;
  225. }
  226. /* Special case where no modulus is required */
  227. if (umax == ZEND_ULONG_MAX) {
  228. *result = (zend_long)trial;
  229. return SUCCESS;
  230. }
  231. /* Increment the max so the range is inclusive of max */
  232. umax++;
  233. /* Powers of two are not biased */
  234. if ((umax & (umax - 1)) != 0) {
  235. /* Ceiling under which ZEND_LONG_MAX % max == 0 */
  236. zend_ulong limit = ZEND_ULONG_MAX - (ZEND_ULONG_MAX % umax) - 1;
  237. /* Discard numbers over the limit to avoid modulo bias */
  238. while (trial > limit) {
  239. if (php_random_bytes(&trial, sizeof(trial), should_throw) == FAILURE) {
  240. return FAILURE;
  241. }
  242. }
  243. }
  244. *result = (zend_long)((trial % umax) + min);
  245. return SUCCESS;
  246. }
  247. /* }}} */
  248. /* {{{ Return an arbitrary pseudo-random integer */
  249. PHP_FUNCTION(random_int)
  250. {
  251. zend_long min;
  252. zend_long max;
  253. zend_long result;
  254. ZEND_PARSE_PARAMETERS_START(2, 2)
  255. Z_PARAM_LONG(min)
  256. Z_PARAM_LONG(max)
  257. ZEND_PARSE_PARAMETERS_END();
  258. if (min > max) {
  259. zend_argument_value_error(1, "must be less than or equal to argument #2 ($max)");
  260. RETURN_THROWS();
  261. }
  262. if (php_random_int_throw(min, max, &result) == FAILURE) {
  263. RETURN_THROWS();
  264. }
  265. RETURN_LONG(result);
  266. }
  267. /* }}} */