You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

269 lines
6.5 KiB

  1. #include "Python.h"
  2. #ifdef MS_WINDOWS
  3. #include <windows.h>
  4. #else
  5. #include <fcntl.h>
  6. #endif
  7. #ifdef Py_DEBUG
  8. int _Py_HashSecret_Initialized = 0;
  9. #else
  10. static int _Py_HashSecret_Initialized = 0;
  11. #endif
  12. #ifdef MS_WINDOWS
  13. /* This handle is never explicitly released. Instead, the operating
  14. system will release it when the process terminates. */
  15. static HCRYPTPROV hCryptProv = 0;
  16. static int
  17. win32_urandom_init(int raise)
  18. {
  19. /* Acquire context */
  20. if (!CryptAcquireContext(&hCryptProv, NULL, NULL,
  21. PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
  22. goto error;
  23. return 0;
  24. error:
  25. if (raise)
  26. PyErr_SetFromWindowsErr(0);
  27. else
  28. Py_FatalError("Failed to initialize Windows random API (CryptoGen)");
  29. return -1;
  30. }
  31. /* Fill buffer with size pseudo-random bytes generated by the Windows CryptoGen
  32. API. Return 0 on success, or -1 on error. */
  33. static int
  34. win32_urandom(unsigned char *buffer, Py_ssize_t size, int raise)
  35. {
  36. Py_ssize_t chunk;
  37. if (hCryptProv == 0)
  38. {
  39. if (win32_urandom_init(raise) == -1)
  40. return -1;
  41. }
  42. while (size > 0)
  43. {
  44. chunk = size > INT_MAX ? INT_MAX : size;
  45. if (!CryptGenRandom(hCryptProv, chunk, buffer))
  46. {
  47. /* CryptGenRandom() failed */
  48. if (raise)
  49. PyErr_SetFromWindowsErr(0);
  50. else
  51. Py_FatalError("Failed to initialized the randomized hash "
  52. "secret using CryptoGen)");
  53. return -1;
  54. }
  55. buffer += chunk;
  56. size -= chunk;
  57. }
  58. return 0;
  59. }
  60. #endif /* MS_WINDOWS */
  61. #ifdef __VMS
  62. /* Use openssl random routine */
  63. #include <openssl/rand.h>
  64. static int
  65. vms_urandom(unsigned char *buffer, Py_ssize_t size, int raise)
  66. {
  67. if (RAND_pseudo_bytes(buffer, size) < 0) {
  68. if (raise) {
  69. PyErr_Format(PyExc_ValueError,
  70. "RAND_pseudo_bytes");
  71. } else {
  72. Py_FatalError("Failed to initialize the randomized hash "
  73. "secret using RAND_pseudo_bytes");
  74. }
  75. return -1;
  76. }
  77. return 0;
  78. }
  79. #endif /* __VMS */
  80. #if !defined(MS_WINDOWS) && !defined(__VMS)
  81. /* Read size bytes from /dev/urandom into buffer.
  82. Call Py_FatalError() on error. */
  83. static void
  84. dev_urandom_noraise(char *buffer, Py_ssize_t size)
  85. {
  86. int fd;
  87. Py_ssize_t n;
  88. assert (0 < size);
  89. fd = open("/dev/urandom", O_RDONLY);
  90. if (fd < 0)
  91. Py_FatalError("Failed to open /dev/urandom");
  92. while (0 < size)
  93. {
  94. do {
  95. n = read(fd, buffer, (size_t)size);
  96. } while (n < 0 && errno == EINTR);
  97. if (n <= 0)
  98. {
  99. /* stop on error or if read(size) returned 0 */
  100. Py_FatalError("Failed to read bytes from /dev/urandom");
  101. break;
  102. }
  103. buffer += n;
  104. size -= (Py_ssize_t)n;
  105. }
  106. close(fd);
  107. }
  108. /* Read size bytes from /dev/urandom into buffer.
  109. Return 0 on success, raise an exception and return -1 on error. */
  110. static int
  111. dev_urandom_python(char *buffer, Py_ssize_t size)
  112. {
  113. int fd;
  114. Py_ssize_t n;
  115. if (size <= 0)
  116. return 0;
  117. Py_BEGIN_ALLOW_THREADS
  118. fd = open("/dev/urandom", O_RDONLY);
  119. Py_END_ALLOW_THREADS
  120. if (fd < 0)
  121. {
  122. PyErr_SetString(PyExc_NotImplementedError,
  123. "/dev/urandom (or equivalent) not found");
  124. return -1;
  125. }
  126. Py_BEGIN_ALLOW_THREADS
  127. do {
  128. do {
  129. n = read(fd, buffer, (size_t)size);
  130. } while (n < 0 && errno == EINTR);
  131. if (n <= 0)
  132. break;
  133. buffer += n;
  134. size -= (Py_ssize_t)n;
  135. } while (0 < size);
  136. Py_END_ALLOW_THREADS
  137. if (n <= 0)
  138. {
  139. /* stop on error or if read(size) returned 0 */
  140. if (n < 0)
  141. PyErr_SetFromErrno(PyExc_OSError);
  142. else
  143. PyErr_Format(PyExc_RuntimeError,
  144. "Failed to read %zi bytes from /dev/urandom",
  145. size);
  146. close(fd);
  147. return -1;
  148. }
  149. close(fd);
  150. return 0;
  151. }
  152. #endif /* !defined(MS_WINDOWS) && !defined(__VMS) */
  153. /* Fill buffer with pseudo-random bytes generated by a linear congruent
  154. generator (LCG):
  155. x(n+1) = (x(n) * 214013 + 2531011) % 2^32
  156. Use bits 23..16 of x(n) to generate a byte. */
  157. static void
  158. lcg_urandom(unsigned int x0, unsigned char *buffer, size_t size)
  159. {
  160. size_t index;
  161. unsigned int x;
  162. x = x0;
  163. for (index=0; index < size; index++) {
  164. x *= 214013;
  165. x += 2531011;
  166. /* modulo 2 ^ (8 * sizeof(int)) */
  167. buffer[index] = (x >> 16) & 0xff;
  168. }
  169. }
  170. /* Fill buffer with size pseudo-random bytes, not suitable for cryptographic
  171. use, from the operating random number generator (RNG).
  172. Return 0 on success, raise an exception and return -1 on error. */
  173. int
  174. _PyOS_URandom(void *buffer, Py_ssize_t size)
  175. {
  176. if (size < 0) {
  177. PyErr_Format(PyExc_ValueError,
  178. "negative argument not allowed");
  179. return -1;
  180. }
  181. if (size == 0)
  182. return 0;
  183. #ifdef MS_WINDOWS
  184. return win32_urandom((unsigned char *)buffer, size, 1);
  185. #else
  186. # ifdef __VMS
  187. return vms_urandom((unsigned char *)buffer, size, 1);
  188. # else
  189. return dev_urandom_python((char*)buffer, size);
  190. # endif
  191. #endif
  192. }
  193. void
  194. _PyRandom_Init(void)
  195. {
  196. char *env;
  197. void *secret = &_Py_HashSecret;
  198. Py_ssize_t secret_size = sizeof(_Py_HashSecret_t);
  199. if (_Py_HashSecret_Initialized)
  200. return;
  201. _Py_HashSecret_Initialized = 1;
  202. /*
  203. Hash randomization is enabled. Generate a per-process secret,
  204. using PYTHONHASHSEED if provided.
  205. */
  206. env = Py_GETENV("PYTHONHASHSEED");
  207. if (env && *env != '\0' && strcmp(env, "random") != 0) {
  208. char *endptr = env;
  209. unsigned long seed;
  210. seed = strtoul(env, &endptr, 10);
  211. if (*endptr != '\0'
  212. || seed > 4294967295UL
  213. || (errno == ERANGE && seed == ULONG_MAX))
  214. {
  215. Py_FatalError("PYTHONHASHSEED must be \"random\" or an integer "
  216. "in range [0; 4294967295]");
  217. }
  218. if (seed == 0) {
  219. /* disable the randomized hash */
  220. memset(secret, 0, secret_size);
  221. }
  222. else {
  223. lcg_urandom(seed, (unsigned char*)secret, secret_size);
  224. }
  225. }
  226. else {
  227. #ifdef MS_WINDOWS
  228. (void)win32_urandom((unsigned char *)secret, secret_size, 0);
  229. #else /* #ifdef MS_WINDOWS */
  230. # ifdef __VMS
  231. vms_urandom((unsigned char *)secret, secret_size, 0);
  232. # else
  233. dev_urandom_noraise((char*)secret, secret_size);
  234. # endif
  235. #endif
  236. }
  237. }