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.

307 lines
7.5 KiB

  1. #include "Python.h"
  2. #ifdef MS_WINDOWS
  3. #include <windows.h>
  4. #else
  5. #include <fcntl.h>
  6. #ifdef HAVE_SYS_STAT_H
  7. #include <sys/stat.h>
  8. #endif
  9. #endif
  10. #ifdef Py_DEBUG
  11. int _Py_HashSecret_Initialized = 0;
  12. #else
  13. static int _Py_HashSecret_Initialized = 0;
  14. #endif
  15. #ifdef MS_WINDOWS
  16. static HCRYPTPROV hCryptProv = 0;
  17. static int
  18. win32_urandom_init(int raise)
  19. {
  20. /* Acquire context */
  21. if (!CryptAcquireContext(&hCryptProv, NULL, NULL,
  22. PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
  23. goto error;
  24. return 0;
  25. error:
  26. if (raise)
  27. PyErr_SetFromWindowsErr(0);
  28. else
  29. Py_FatalError("Failed to initialize Windows random API (CryptoGen)");
  30. return -1;
  31. }
  32. /* Fill buffer with size pseudo-random bytes generated by the Windows CryptoGen
  33. API. Return 0 on success, or -1 on error. */
  34. static int
  35. win32_urandom(unsigned char *buffer, Py_ssize_t size, int raise)
  36. {
  37. Py_ssize_t chunk;
  38. if (hCryptProv == 0)
  39. {
  40. if (win32_urandom_init(raise) == -1)
  41. return -1;
  42. }
  43. while (size > 0)
  44. {
  45. chunk = size > INT_MAX ? INT_MAX : size;
  46. if (!CryptGenRandom(hCryptProv, (DWORD)chunk, buffer))
  47. {
  48. /* CryptGenRandom() failed */
  49. if (raise)
  50. PyErr_SetFromWindowsErr(0);
  51. else
  52. Py_FatalError("Failed to initialized the randomized hash "
  53. "secret using CryptoGen)");
  54. return -1;
  55. }
  56. buffer += chunk;
  57. size -= chunk;
  58. }
  59. return 0;
  60. }
  61. #endif /* MS_WINDOWS */
  62. #ifndef MS_WINDOWS
  63. static struct {
  64. int fd;
  65. dev_t st_dev;
  66. ino_t st_ino;
  67. } urandom_cache = { -1 };
  68. /* Read size bytes from /dev/urandom into buffer.
  69. Call Py_FatalError() on error. */
  70. static void
  71. dev_urandom_noraise(unsigned char *buffer, Py_ssize_t size)
  72. {
  73. int fd;
  74. Py_ssize_t n;
  75. assert (0 < size);
  76. fd = _Py_open("/dev/urandom", O_RDONLY);
  77. if (fd < 0)
  78. Py_FatalError("Failed to open /dev/urandom");
  79. while (0 < size)
  80. {
  81. do {
  82. n = read(fd, buffer, (size_t)size);
  83. } while (n < 0 && errno == EINTR);
  84. if (n <= 0)
  85. {
  86. /* stop on error or if read(size) returned 0 */
  87. Py_FatalError("Failed to read bytes from /dev/urandom");
  88. break;
  89. }
  90. buffer += n;
  91. size -= (Py_ssize_t)n;
  92. }
  93. close(fd);
  94. }
  95. /* Read size bytes from /dev/urandom into buffer.
  96. Return 0 on success, raise an exception and return -1 on error. */
  97. static int
  98. dev_urandom_python(char *buffer, Py_ssize_t size)
  99. {
  100. int fd;
  101. Py_ssize_t n;
  102. struct stat st;
  103. if (size <= 0)
  104. return 0;
  105. if (urandom_cache.fd >= 0) {
  106. /* Does the fd point to the same thing as before? (issue #21207) */
  107. if (fstat(urandom_cache.fd, &st)
  108. || st.st_dev != urandom_cache.st_dev
  109. || st.st_ino != urandom_cache.st_ino) {
  110. /* Something changed: forget the cached fd (but don't close it,
  111. since it probably points to something important for some
  112. third-party code). */
  113. urandom_cache.fd = -1;
  114. }
  115. }
  116. if (urandom_cache.fd >= 0)
  117. fd = urandom_cache.fd;
  118. else {
  119. Py_BEGIN_ALLOW_THREADS
  120. fd = _Py_open("/dev/urandom", O_RDONLY);
  121. Py_END_ALLOW_THREADS
  122. if (fd < 0)
  123. {
  124. if (errno == ENOENT || errno == ENXIO ||
  125. errno == ENODEV || errno == EACCES)
  126. PyErr_SetString(PyExc_NotImplementedError,
  127. "/dev/urandom (or equivalent) not found");
  128. else
  129. PyErr_SetFromErrno(PyExc_OSError);
  130. return -1;
  131. }
  132. if (urandom_cache.fd >= 0) {
  133. /* urandom_fd was initialized by another thread while we were
  134. not holding the GIL, keep it. */
  135. close(fd);
  136. fd = urandom_cache.fd;
  137. }
  138. else {
  139. if (fstat(fd, &st)) {
  140. PyErr_SetFromErrno(PyExc_OSError);
  141. close(fd);
  142. return -1;
  143. }
  144. else {
  145. urandom_cache.fd = fd;
  146. urandom_cache.st_dev = st.st_dev;
  147. urandom_cache.st_ino = st.st_ino;
  148. }
  149. }
  150. }
  151. Py_BEGIN_ALLOW_THREADS
  152. do {
  153. do {
  154. n = read(fd, buffer, (size_t)size);
  155. } while (n < 0 && errno == EINTR);
  156. if (n <= 0)
  157. break;
  158. buffer += n;
  159. size -= (Py_ssize_t)n;
  160. } while (0 < size);
  161. Py_END_ALLOW_THREADS
  162. if (n <= 0)
  163. {
  164. /* stop on error or if read(size) returned 0 */
  165. if (n < 0)
  166. PyErr_SetFromErrno(PyExc_OSError);
  167. else
  168. PyErr_Format(PyExc_RuntimeError,
  169. "Failed to read %zi bytes from /dev/urandom",
  170. size);
  171. return -1;
  172. }
  173. return 0;
  174. }
  175. static void
  176. dev_urandom_close(void)
  177. {
  178. if (urandom_cache.fd >= 0) {
  179. close(urandom_cache.fd);
  180. urandom_cache.fd = -1;
  181. }
  182. }
  183. #endif /* MS_WINDOWS */
  184. /* Fill buffer with pseudo-random bytes generated by a linear congruent
  185. generator (LCG):
  186. x(n+1) = (x(n) * 214013 + 2531011) % 2^32
  187. Use bits 23..16 of x(n) to generate a byte. */
  188. static void
  189. lcg_urandom(unsigned int x0, unsigned char *buffer, size_t size)
  190. {
  191. size_t index;
  192. unsigned int x;
  193. x = x0;
  194. for (index=0; index < size; index++) {
  195. x *= 214013;
  196. x += 2531011;
  197. /* modulo 2 ^ (8 * sizeof(int)) */
  198. buffer[index] = (x >> 16) & 0xff;
  199. }
  200. }
  201. /* Fill buffer with size pseudo-random bytes from the operating system random
  202. number generator (RNG). It is suitable for most cryptographic purposes
  203. except long living private keys for asymmetric encryption.
  204. Return 0 on success, raise an exception and return -1 on error. */
  205. int
  206. _PyOS_URandom(void *buffer, Py_ssize_t size)
  207. {
  208. if (size < 0) {
  209. PyErr_Format(PyExc_ValueError,
  210. "negative argument not allowed");
  211. return -1;
  212. }
  213. if (size == 0)
  214. return 0;
  215. #ifdef MS_WINDOWS
  216. return win32_urandom((unsigned char *)buffer, size, 1);
  217. #else
  218. return dev_urandom_python((char*)buffer, size);
  219. #endif
  220. }
  221. void
  222. _PyRandom_Init(void)
  223. {
  224. char *env;
  225. unsigned char *secret = (unsigned char *)&_Py_HashSecret.uc;
  226. Py_ssize_t secret_size = sizeof(_Py_HashSecret_t);
  227. assert(secret_size == sizeof(_Py_HashSecret.uc));
  228. if (_Py_HashSecret_Initialized)
  229. return;
  230. _Py_HashSecret_Initialized = 1;
  231. /*
  232. Hash randomization is enabled. Generate a per-process secret,
  233. using PYTHONHASHSEED if provided.
  234. */
  235. env = Py_GETENV("PYTHONHASHSEED");
  236. if (env && *env != '\0' && strcmp(env, "random") != 0) {
  237. char *endptr = env;
  238. unsigned long seed;
  239. seed = strtoul(env, &endptr, 10);
  240. if (*endptr != '\0'
  241. || seed > 4294967295UL
  242. || (errno == ERANGE && seed == ULONG_MAX))
  243. {
  244. Py_FatalError("PYTHONHASHSEED must be \"random\" or an integer "
  245. "in range [0; 4294967295]");
  246. }
  247. if (seed == 0) {
  248. /* disable the randomized hash */
  249. memset(secret, 0, secret_size);
  250. }
  251. else {
  252. lcg_urandom(seed, secret, secret_size);
  253. }
  254. }
  255. else {
  256. #ifdef MS_WINDOWS
  257. (void)win32_urandom(secret, secret_size, 0);
  258. #else
  259. dev_urandom_noraise(secret, secret_size);
  260. #endif
  261. }
  262. }
  263. void
  264. _PyRandom_Fini(void)
  265. {
  266. #ifdef MS_WINDOWS
  267. if (hCryptProv) {
  268. CryptReleaseContext(hCryptProv, 0);
  269. hCryptProv = 0;
  270. }
  271. #else
  272. dev_urandom_close();
  273. #endif
  274. }