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.

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