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.

302 lines
7.2 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. static int urandom_fd = -1;
  82. /* Read size bytes from /dev/urandom into buffer.
  83. Call Py_FatalError() on error. */
  84. static void
  85. dev_urandom_noraise(char *buffer, Py_ssize_t size)
  86. {
  87. int fd;
  88. Py_ssize_t n;
  89. assert (0 < size);
  90. fd = _Py_open("/dev/urandom", O_RDONLY);
  91. if (fd < 0)
  92. Py_FatalError("Failed to open /dev/urandom");
  93. while (0 < size)
  94. {
  95. do {
  96. n = read(fd, buffer, (size_t)size);
  97. } while (n < 0 && errno == EINTR);
  98. if (n <= 0)
  99. {
  100. /* stop on error or if read(size) returned 0 */
  101. Py_FatalError("Failed to read bytes from /dev/urandom");
  102. break;
  103. }
  104. buffer += n;
  105. size -= (Py_ssize_t)n;
  106. }
  107. close(fd);
  108. }
  109. /* Read size bytes from /dev/urandom into buffer.
  110. Return 0 on success, raise an exception and return -1 on error. */
  111. static int
  112. dev_urandom_python(char *buffer, Py_ssize_t size)
  113. {
  114. int fd;
  115. Py_ssize_t n;
  116. if (size <= 0)
  117. return 0;
  118. if (urandom_fd >= 0)
  119. fd = urandom_fd;
  120. else {
  121. Py_BEGIN_ALLOW_THREADS
  122. fd = _Py_open("/dev/urandom", O_RDONLY);
  123. Py_END_ALLOW_THREADS
  124. if (fd < 0)
  125. {
  126. if (errno == ENOENT || errno == ENXIO ||
  127. errno == ENODEV || errno == EACCES)
  128. PyErr_SetString(PyExc_NotImplementedError,
  129. "/dev/urandom (or equivalent) not found");
  130. else
  131. PyErr_SetFromErrno(PyExc_OSError);
  132. return -1;
  133. }
  134. if (urandom_fd >= 0) {
  135. /* urandom_fd was initialized by another thread while we were
  136. not holding the GIL, keep it. */
  137. close(fd);
  138. fd = urandom_fd;
  139. }
  140. else
  141. urandom_fd = fd;
  142. }
  143. Py_BEGIN_ALLOW_THREADS
  144. do {
  145. do {
  146. n = read(fd, buffer, (size_t)size);
  147. } while (n < 0 && errno == EINTR);
  148. if (n <= 0)
  149. break;
  150. buffer += n;
  151. size -= (Py_ssize_t)n;
  152. } while (0 < size);
  153. Py_END_ALLOW_THREADS
  154. if (n <= 0)
  155. {
  156. /* stop on error or if read(size) returned 0 */
  157. if (n < 0)
  158. PyErr_SetFromErrno(PyExc_OSError);
  159. else
  160. PyErr_Format(PyExc_RuntimeError,
  161. "Failed to read %zi bytes from /dev/urandom",
  162. size);
  163. return -1;
  164. }
  165. return 0;
  166. }
  167. static void
  168. dev_urandom_close(void)
  169. {
  170. if (urandom_fd >= 0) {
  171. close(urandom_fd);
  172. urandom_fd = -1;
  173. }
  174. }
  175. #endif /* !defined(MS_WINDOWS) && !defined(__VMS) */
  176. /* Fill buffer with pseudo-random bytes generated by a linear congruent
  177. generator (LCG):
  178. x(n+1) = (x(n) * 214013 + 2531011) % 2^32
  179. Use bits 23..16 of x(n) to generate a byte. */
  180. static void
  181. lcg_urandom(unsigned int x0, unsigned char *buffer, size_t size)
  182. {
  183. size_t index;
  184. unsigned int x;
  185. x = x0;
  186. for (index=0; index < size; index++) {
  187. x *= 214013;
  188. x += 2531011;
  189. /* modulo 2 ^ (8 * sizeof(int)) */
  190. buffer[index] = (x >> 16) & 0xff;
  191. }
  192. }
  193. /* Fill buffer with size pseudo-random bytes, not suitable for cryptographic
  194. use, from the operating random number generator (RNG).
  195. Return 0 on success, raise an exception and return -1 on error. */
  196. int
  197. _PyOS_URandom(void *buffer, Py_ssize_t size)
  198. {
  199. if (size < 0) {
  200. PyErr_Format(PyExc_ValueError,
  201. "negative argument not allowed");
  202. return -1;
  203. }
  204. if (size == 0)
  205. return 0;
  206. #ifdef MS_WINDOWS
  207. return win32_urandom((unsigned char *)buffer, size, 1);
  208. #else
  209. # ifdef __VMS
  210. return vms_urandom((unsigned char *)buffer, size, 1);
  211. # else
  212. return dev_urandom_python((char*)buffer, size);
  213. # endif
  214. #endif
  215. }
  216. void
  217. _PyRandom_Init(void)
  218. {
  219. char *env;
  220. void *secret = &_Py_HashSecret;
  221. Py_ssize_t secret_size = sizeof(_Py_HashSecret_t);
  222. if (_Py_HashSecret_Initialized)
  223. return;
  224. _Py_HashSecret_Initialized = 1;
  225. /*
  226. Hash randomization is enabled. Generate a per-process secret,
  227. using PYTHONHASHSEED if provided.
  228. */
  229. env = Py_GETENV("PYTHONHASHSEED");
  230. if (env && *env != '\0' && strcmp(env, "random") != 0) {
  231. char *endptr = env;
  232. unsigned long seed;
  233. seed = strtoul(env, &endptr, 10);
  234. if (*endptr != '\0'
  235. || seed > 4294967295UL
  236. || (errno == ERANGE && seed == ULONG_MAX))
  237. {
  238. Py_FatalError("PYTHONHASHSEED must be \"random\" or an integer "
  239. "in range [0; 4294967295]");
  240. }
  241. if (seed == 0) {
  242. /* disable the randomized hash */
  243. memset(secret, 0, secret_size);
  244. }
  245. else {
  246. lcg_urandom(seed, (unsigned char*)secret, secret_size);
  247. }
  248. }
  249. else {
  250. #ifdef MS_WINDOWS
  251. (void)win32_urandom((unsigned char *)secret, secret_size, 0);
  252. #else /* #ifdef MS_WINDOWS */
  253. # ifdef __VMS
  254. vms_urandom((unsigned char *)secret, secret_size, 0);
  255. # else
  256. dev_urandom_noraise((char*)secret, secret_size);
  257. # endif
  258. #endif
  259. }
  260. }
  261. void
  262. _PyRandom_Fini(void)
  263. {
  264. #if !defined(MS_WINDOWS) && !defined(__VMS)
  265. dev_urandom_close();
  266. #endif
  267. }