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.

350 lines
8.8 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 raise an exception and return -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. /* Issue #25003: Don' use getentropy() on Solaris (available since
  62. * Solaris 11.3), it is blocking whereas os.urandom() should not block. */
  63. #elif defined(HAVE_GETENTROPY) && !defined(sun)
  64. #define PY_GETENTROPY 1
  65. /* Fill buffer with size pseudo-random bytes generated by getentropy().
  66. Return 0 on success, or raise an exception and return -1 on error.
  67. If fatal is nonzero, call Py_FatalError() instead of raising an exception
  68. on error. */
  69. static int
  70. py_getentropy(unsigned char *buffer, Py_ssize_t size, int fatal)
  71. {
  72. while (size > 0) {
  73. Py_ssize_t len = Py_MIN(size, 256);
  74. int res;
  75. if (!fatal) {
  76. Py_BEGIN_ALLOW_THREADS
  77. res = getentropy(buffer, len);
  78. Py_END_ALLOW_THREADS
  79. if (res < 0) {
  80. PyErr_SetFromErrno(PyExc_OSError);
  81. return -1;
  82. }
  83. }
  84. else {
  85. res = getentropy(buffer, len);
  86. if (res < 0)
  87. Py_FatalError("getentropy() failed");
  88. }
  89. buffer += len;
  90. size -= len;
  91. }
  92. return 0;
  93. }
  94. #else
  95. static struct {
  96. int fd;
  97. dev_t st_dev;
  98. ino_t st_ino;
  99. } urandom_cache = { -1 };
  100. /* Read size bytes from /dev/urandom into buffer.
  101. Call Py_FatalError() on error. */
  102. static void
  103. dev_urandom_noraise(unsigned char *buffer, Py_ssize_t size)
  104. {
  105. int fd;
  106. Py_ssize_t n;
  107. assert (0 < size);
  108. fd = _Py_open("/dev/urandom", O_RDONLY);
  109. if (fd < 0)
  110. Py_FatalError("Failed to open /dev/urandom");
  111. while (0 < size)
  112. {
  113. do {
  114. n = read(fd, buffer, (size_t)size);
  115. } while (n < 0 && errno == EINTR);
  116. if (n <= 0)
  117. {
  118. /* stop on error or if read(size) returned 0 */
  119. Py_FatalError("Failed to read bytes from /dev/urandom");
  120. break;
  121. }
  122. buffer += n;
  123. size -= (Py_ssize_t)n;
  124. }
  125. close(fd);
  126. }
  127. /* Read size bytes from /dev/urandom into buffer.
  128. Return 0 on success, raise an exception and return -1 on error. */
  129. static int
  130. dev_urandom_python(char *buffer, Py_ssize_t size)
  131. {
  132. int fd;
  133. Py_ssize_t n;
  134. struct stat st;
  135. if (size <= 0)
  136. return 0;
  137. if (urandom_cache.fd >= 0) {
  138. /* Does the fd point to the same thing as before? (issue #21207) */
  139. if (fstat(urandom_cache.fd, &st)
  140. || st.st_dev != urandom_cache.st_dev
  141. || st.st_ino != urandom_cache.st_ino) {
  142. /* Something changed: forget the cached fd (but don't close it,
  143. since it probably points to something important for some
  144. third-party code). */
  145. urandom_cache.fd = -1;
  146. }
  147. }
  148. if (urandom_cache.fd >= 0)
  149. fd = urandom_cache.fd;
  150. else {
  151. Py_BEGIN_ALLOW_THREADS
  152. fd = _Py_open("/dev/urandom", O_RDONLY);
  153. Py_END_ALLOW_THREADS
  154. if (fd < 0)
  155. {
  156. if (errno == ENOENT || errno == ENXIO ||
  157. errno == ENODEV || errno == EACCES)
  158. PyErr_SetString(PyExc_NotImplementedError,
  159. "/dev/urandom (or equivalent) not found");
  160. else
  161. PyErr_SetFromErrno(PyExc_OSError);
  162. return -1;
  163. }
  164. if (urandom_cache.fd >= 0) {
  165. /* urandom_fd was initialized by another thread while we were
  166. not holding the GIL, keep it. */
  167. close(fd);
  168. fd = urandom_cache.fd;
  169. }
  170. else {
  171. if (fstat(fd, &st)) {
  172. PyErr_SetFromErrno(PyExc_OSError);
  173. close(fd);
  174. return -1;
  175. }
  176. else {
  177. urandom_cache.fd = fd;
  178. urandom_cache.st_dev = st.st_dev;
  179. urandom_cache.st_ino = st.st_ino;
  180. }
  181. }
  182. }
  183. Py_BEGIN_ALLOW_THREADS
  184. do {
  185. do {
  186. n = read(fd, buffer, (size_t)size);
  187. } while (n < 0 && errno == EINTR);
  188. if (n <= 0)
  189. break;
  190. buffer += n;
  191. size -= (Py_ssize_t)n;
  192. } while (0 < size);
  193. Py_END_ALLOW_THREADS
  194. if (n <= 0)
  195. {
  196. /* stop on error or if read(size) returned 0 */
  197. if (n < 0)
  198. PyErr_SetFromErrno(PyExc_OSError);
  199. else
  200. PyErr_Format(PyExc_RuntimeError,
  201. "Failed to read %zi bytes from /dev/urandom",
  202. size);
  203. return -1;
  204. }
  205. return 0;
  206. }
  207. static void
  208. dev_urandom_close(void)
  209. {
  210. if (urandom_cache.fd >= 0) {
  211. close(urandom_cache.fd);
  212. urandom_cache.fd = -1;
  213. }
  214. }
  215. #endif /* HAVE_GETENTROPY */
  216. /* Fill buffer with pseudo-random bytes generated by a linear congruent
  217. generator (LCG):
  218. x(n+1) = (x(n) * 214013 + 2531011) % 2^32
  219. Use bits 23..16 of x(n) to generate a byte. */
  220. static void
  221. lcg_urandom(unsigned int x0, unsigned char *buffer, size_t size)
  222. {
  223. size_t index;
  224. unsigned int x;
  225. x = x0;
  226. for (index=0; index < size; index++) {
  227. x *= 214013;
  228. x += 2531011;
  229. /* modulo 2 ^ (8 * sizeof(int)) */
  230. buffer[index] = (x >> 16) & 0xff;
  231. }
  232. }
  233. /* Fill buffer with size pseudo-random bytes from the operating system random
  234. number generator (RNG). It is suitable for most cryptographic purposes
  235. except long living private keys for asymmetric encryption.
  236. Return 0 on success, raise an exception and return -1 on error. */
  237. int
  238. _PyOS_URandom(void *buffer, Py_ssize_t size)
  239. {
  240. if (size < 0) {
  241. PyErr_Format(PyExc_ValueError,
  242. "negative argument not allowed");
  243. return -1;
  244. }
  245. if (size == 0)
  246. return 0;
  247. #ifdef MS_WINDOWS
  248. return win32_urandom((unsigned char *)buffer, size, 1);
  249. #elif defined(PY_GETENTROPY)
  250. return py_getentropy(buffer, size, 0);
  251. #else
  252. return dev_urandom_python((char*)buffer, size);
  253. #endif
  254. }
  255. void
  256. _PyRandom_Init(void)
  257. {
  258. char *env;
  259. unsigned char *secret = (unsigned char *)&_Py_HashSecret.uc;
  260. Py_ssize_t secret_size = sizeof(_Py_HashSecret_t);
  261. assert(secret_size == sizeof(_Py_HashSecret.uc));
  262. if (_Py_HashSecret_Initialized)
  263. return;
  264. _Py_HashSecret_Initialized = 1;
  265. /*
  266. Hash randomization is enabled. Generate a per-process secret,
  267. using PYTHONHASHSEED if provided.
  268. */
  269. env = Py_GETENV("PYTHONHASHSEED");
  270. if (env && *env != '\0' && strcmp(env, "random") != 0) {
  271. char *endptr = env;
  272. unsigned long seed;
  273. seed = strtoul(env, &endptr, 10);
  274. if (*endptr != '\0'
  275. || seed > 4294967295UL
  276. || (errno == ERANGE && seed == ULONG_MAX))
  277. {
  278. Py_FatalError("PYTHONHASHSEED must be \"random\" or an integer "
  279. "in range [0; 4294967295]");
  280. }
  281. if (seed == 0) {
  282. /* disable the randomized hash */
  283. memset(secret, 0, secret_size);
  284. }
  285. else {
  286. lcg_urandom(seed, secret, secret_size);
  287. }
  288. }
  289. else {
  290. #ifdef MS_WINDOWS
  291. (void)win32_urandom(secret, secret_size, 0);
  292. #elif defined(PY_GETENTROPY)
  293. (void)py_getentropy(secret, secret_size, 1);
  294. #else
  295. dev_urandom_noraise(secret, secret_size);
  296. #endif
  297. }
  298. }
  299. void
  300. _PyRandom_Fini(void)
  301. {
  302. #ifdef MS_WINDOWS
  303. if (hCryptProv) {
  304. CryptReleaseContext(hCryptProv, 0);
  305. hCryptProv = 0;
  306. }
  307. #elif defined(PY_GETENTROPY)
  308. /* nothing to clean */
  309. #else
  310. dev_urandom_close();
  311. #endif
  312. }