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.

304 lines
7.4 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. #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(unsigned 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 from the operating system random
  194. number generator (RNG). It is suitable for for most cryptographic purposes
  195. except long living private keys for asymmetric encryption.
  196. Return 0 on success, raise an exception and return -1 on error. */
  197. int
  198. _PyOS_URandom(void *buffer, Py_ssize_t size)
  199. {
  200. if (size < 0) {
  201. PyErr_Format(PyExc_ValueError,
  202. "negative argument not allowed");
  203. return -1;
  204. }
  205. if (size == 0)
  206. return 0;
  207. #ifdef MS_WINDOWS
  208. return win32_urandom((unsigned char *)buffer, size, 1);
  209. #else
  210. # ifdef __VMS
  211. return vms_urandom((unsigned char *)buffer, size, 1);
  212. # else
  213. return dev_urandom_python((char*)buffer, size);
  214. # endif
  215. #endif
  216. }
  217. void
  218. _PyRandom_Init(void)
  219. {
  220. char *env;
  221. unsigned char *secret = (unsigned char *)&_Py_HashSecret.uc;
  222. Py_ssize_t secret_size = sizeof(_Py_HashSecret_t);
  223. assert(secret_size == sizeof(_Py_HashSecret.uc));
  224. if (_Py_HashSecret_Initialized)
  225. return;
  226. _Py_HashSecret_Initialized = 1;
  227. /*
  228. Hash randomization is enabled. Generate a per-process secret,
  229. using PYTHONHASHSEED if provided.
  230. */
  231. env = Py_GETENV("PYTHONHASHSEED");
  232. if (env && *env != '\0' && strcmp(env, "random") != 0) {
  233. char *endptr = env;
  234. unsigned long seed;
  235. seed = strtoul(env, &endptr, 10);
  236. if (*endptr != '\0'
  237. || seed > 4294967295UL
  238. || (errno == ERANGE && seed == ULONG_MAX))
  239. {
  240. Py_FatalError("PYTHONHASHSEED must be \"random\" or an integer "
  241. "in range [0; 4294967295]");
  242. }
  243. if (seed == 0) {
  244. /* disable the randomized hash */
  245. memset(secret, 0, secret_size);
  246. }
  247. else {
  248. lcg_urandom(seed, secret, secret_size);
  249. }
  250. }
  251. else {
  252. #ifdef MS_WINDOWS
  253. (void)win32_urandom(secret, secret_size, 0);
  254. #else /* #ifdef MS_WINDOWS */
  255. # ifdef __VMS
  256. vms_urandom(secret, secret_size, 0);
  257. # else
  258. dev_urandom_noraise(secret, secret_size);
  259. # endif
  260. #endif
  261. }
  262. }
  263. void
  264. _PyRandom_Fini(void)
  265. {
  266. #if !defined(MS_WINDOWS) && !defined(__VMS)
  267. dev_urandom_close();
  268. #endif
  269. }