1 #include "Python.h"
2 #ifdef MS_WINDOWS
3 #include <windows.h>
4 #else
5 #include <fcntl.h>
6 #if defined(HAVE_SYS_RANDOM_H) && (defined(HAVE_GETRANDOM) || defined(HAVE_GETENTROPY))
7 #include <sys/random.h>
8 #endif
9 #endif
10
11 #ifdef Py_DEBUG
12 int _Py_HashSecret_Initialized = 0;
13 #else
14 static int _Py_HashSecret_Initialized = 0;
15 #endif
16
17 #ifdef MS_WINDOWS
18 typedef BOOL (WINAPI *CRYPTACQUIRECONTEXTA)(HCRYPTPROV *phProv,\
19 LPCSTR pszContainer, LPCSTR pszProvider, DWORD dwProvType,\
20 DWORD dwFlags );
21 typedef BOOL (WINAPI *CRYPTGENRANDOM)(HCRYPTPROV hProv, DWORD dwLen,\
22 BYTE *pbBuffer );
23
24 static CRYPTGENRANDOM pCryptGenRandom = NULL;
25 /* This handle is never explicitly released. Instead, the operating
26 system will release it when the process terminates. */
27 static HCRYPTPROV hCryptProv = 0;
28
29 static int
win32_urandom_init(int raise)30 win32_urandom_init(int raise)
31 {
32 HINSTANCE hAdvAPI32 = NULL;
33 CRYPTACQUIRECONTEXTA pCryptAcquireContext = NULL;
34
35 /* Obtain handle to the DLL containing CryptoAPI. This should not fail. */
36 hAdvAPI32 = GetModuleHandle("advapi32.dll");
37 if(hAdvAPI32 == NULL)
38 goto error;
39
40 /* Obtain pointers to the CryptoAPI functions. This will fail on some early
41 versions of Win95. */
42 pCryptAcquireContext = (CRYPTACQUIRECONTEXTA)GetProcAddress(
43 hAdvAPI32, "CryptAcquireContextA");
44 if (pCryptAcquireContext == NULL)
45 goto error;
46
47 pCryptGenRandom = (CRYPTGENRANDOM)GetProcAddress(hAdvAPI32,
48 "CryptGenRandom");
49 if (pCryptGenRandom == NULL)
50 goto error;
51
52 /* Acquire context */
53 if (! pCryptAcquireContext(&hCryptProv, NULL, NULL,
54 PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
55 goto error;
56
57 return 0;
58
59 error:
60 if (raise)
61 PyErr_SetFromWindowsErr(0);
62 else
63 Py_FatalError("Failed to initialize Windows random API (CryptoGen)");
64 return -1;
65 }
66
67 /* Fill buffer with size pseudo-random bytes generated by the Windows CryptoGen
68 API. Return 0 on success, or -1 on error. */
69 static int
win32_urandom(unsigned char * buffer,Py_ssize_t size,int raise)70 win32_urandom(unsigned char *buffer, Py_ssize_t size, int raise)
71 {
72 Py_ssize_t chunk;
73
74 if (hCryptProv == 0)
75 {
76 if (win32_urandom_init(raise) == -1)
77 return -1;
78 }
79
80 while (size > 0)
81 {
82 chunk = size > INT_MAX ? INT_MAX : size;
83 if (!pCryptGenRandom(hCryptProv, chunk, buffer))
84 {
85 /* CryptGenRandom() failed */
86 if (raise)
87 PyErr_SetFromWindowsErr(0);
88 else
89 Py_FatalError("Failed to initialized the randomized hash "
90 "secret using CryptoGen)");
91 return -1;
92 }
93 buffer += chunk;
94 size -= chunk;
95 }
96 return 0;
97 }
98
99 /* Issue #25003: Don't use getentropy() on Solaris (available since
100 Solaris 11.3), it is blocking whereas os.urandom() should not block.
101
102 Issue #29188: Don't use getentropy() on Linux since the glibc 2.24
103 implements it with the getrandom() syscall which can fail with ENOSYS,
104 and this error is not supported in py_getentropy() and getrandom() is called
105 with flags=0 which blocks until system urandom is initialized, which is not
106 the desired behaviour to seed the Python hash secret nor for os.urandom():
107 see the PEP 524 which was only implemented in Python 3.6. */
108 #elif defined(HAVE_GETENTROPY) && !defined(sun) && !defined(linux)
109 #define PY_GETENTROPY 1
110
111 /* Fill buffer with size pseudo-random bytes generated by getentropy().
112 Return 0 on success, or raise an exception and return -1 on error.
113 If fatal is nonzero, call Py_FatalError() instead of raising an exception
114 on error. */
115 static int
py_getentropy(unsigned char * buffer,Py_ssize_t size,int fatal)116 py_getentropy(unsigned char *buffer, Py_ssize_t size, int fatal)
117 {
118 while (size > 0) {
119 Py_ssize_t len = size < 256 ? size : 256;
120 int res;
121
122 if (!fatal) {
123 Py_BEGIN_ALLOW_THREADS
124 res = getentropy(buffer, len);
125 Py_END_ALLOW_THREADS
126
127 if (res < 0) {
128 PyErr_SetFromErrno(PyExc_OSError);
129 return -1;
130 }
131 }
132 else {
133 res = getentropy(buffer, len);
134 if (res < 0)
135 Py_FatalError("getentropy() failed");
136 }
137
138 buffer += len;
139 size -= len;
140 }
141 return 0;
142 }
143 #endif
144
145 #ifdef __VMS
146 /* Use openssl random routine */
147 #include <openssl/rand.h>
148 static int
vms_urandom(unsigned char * buffer,Py_ssize_t size,int raise)149 vms_urandom(unsigned char *buffer, Py_ssize_t size, int raise)
150 {
151 if (RAND_pseudo_bytes(buffer, size) < 0) {
152 if (raise) {
153 PyErr_Format(PyExc_ValueError,
154 "RAND_pseudo_bytes");
155 } else {
156 Py_FatalError("Failed to initialize the randomized hash "
157 "secret using RAND_pseudo_bytes");
158 }
159 return -1;
160 }
161 return 0;
162 }
163 #endif /* __VMS */
164
165
166 #if !defined(MS_WINDOWS) && !defined(__VMS)
167
168 static struct {
169 int fd;
170 dev_t st_dev;
171 ino_t st_ino;
172 } urandom_cache = { -1 };
173
174 /* Read size bytes from /dev/urandom into buffer.
175 Call Py_FatalError() on error. */
176 static void
dev_urandom_noraise(unsigned char * buffer,Py_ssize_t size)177 dev_urandom_noraise(unsigned char *buffer, Py_ssize_t size)
178 {
179 int fd;
180 Py_ssize_t n;
181
182 assert (0 < size);
183
184 fd = open("/dev/urandom", O_RDONLY);
185 if (fd < 0)
186 Py_FatalError("Failed to open /dev/urandom");
187
188 while (0 < size)
189 {
190 do {
191 n = read(fd, buffer, (size_t)size);
192 } while (n < 0 && errno == EINTR);
193 if (n <= 0)
194 {
195 /* stop on error or if read(size) returned 0 */
196 Py_FatalError("Failed to read bytes from /dev/urandom");
197 break;
198 }
199 buffer += n;
200 size -= (Py_ssize_t)n;
201 }
202 close(fd);
203 }
204
205 /* Read size bytes from /dev/urandom into buffer.
206 Return 0 on success, raise an exception and return -1 on error. */
207 static int
dev_urandom_python(char * buffer,Py_ssize_t size)208 dev_urandom_python(char *buffer, Py_ssize_t size)
209 {
210 int fd;
211 Py_ssize_t n;
212 struct stat st;
213 int attr;
214
215 if (size <= 0)
216 return 0;
217
218 if (urandom_cache.fd >= 0) {
219 /* Does the fd point to the same thing as before? (issue #21207) */
220 if (fstat(urandom_cache.fd, &st)
221 || st.st_dev != urandom_cache.st_dev
222 || st.st_ino != urandom_cache.st_ino) {
223 /* Something changed: forget the cached fd (but don't close it,
224 since it probably points to something important for some
225 third-party code). */
226 urandom_cache.fd = -1;
227 }
228 }
229 if (urandom_cache.fd >= 0)
230 fd = urandom_cache.fd;
231 else {
232 Py_BEGIN_ALLOW_THREADS
233 fd = open("/dev/urandom", O_RDONLY);
234 Py_END_ALLOW_THREADS
235 if (fd < 0)
236 {
237 if (errno == ENOENT || errno == ENXIO ||
238 errno == ENODEV || errno == EACCES)
239 PyErr_SetString(PyExc_NotImplementedError,
240 "/dev/urandom (or equivalent) not found");
241 else
242 PyErr_SetFromErrno(PyExc_OSError);
243 return -1;
244 }
245
246 /* try to make the file descriptor non-inheritable, ignore errors */
247 attr = fcntl(fd, F_GETFD);
248 if (attr >= 0) {
249 attr |= FD_CLOEXEC;
250 (void)fcntl(fd, F_SETFD, attr);
251 }
252
253 if (urandom_cache.fd >= 0) {
254 /* urandom_fd was initialized by another thread while we were
255 not holding the GIL, keep it. */
256 close(fd);
257 fd = urandom_cache.fd;
258 }
259 else {
260 if (fstat(fd, &st)) {
261 PyErr_SetFromErrno(PyExc_OSError);
262 close(fd);
263 return -1;
264 }
265 else {
266 urandom_cache.fd = fd;
267 urandom_cache.st_dev = st.st_dev;
268 urandom_cache.st_ino = st.st_ino;
269 }
270 }
271 }
272
273 Py_BEGIN_ALLOW_THREADS
274 do {
275 do {
276 n = read(fd, buffer, (size_t)size);
277 } while (n < 0 && errno == EINTR);
278 if (n <= 0)
279 break;
280 buffer += n;
281 size -= (Py_ssize_t)n;
282 } while (0 < size);
283 Py_END_ALLOW_THREADS
284
285 if (n <= 0)
286 {
287 /* stop on error or if read(size) returned 0 */
288 if (n < 0)
289 PyErr_SetFromErrno(PyExc_OSError);
290 else
291 PyErr_Format(PyExc_RuntimeError,
292 "Failed to read %zi bytes from /dev/urandom",
293 size);
294 return -1;
295 }
296 return 0;
297 }
298
299 static void
dev_urandom_close(void)300 dev_urandom_close(void)
301 {
302 if (urandom_cache.fd >= 0) {
303 close(urandom_cache.fd);
304 urandom_cache.fd = -1;
305 }
306 }
307
308
309 #endif /* !defined(MS_WINDOWS) && !defined(__VMS) */
310
311 /* Fill buffer with pseudo-random bytes generated by a linear congruent
312 generator (LCG):
313
314 x(n+1) = (x(n) * 214013 + 2531011) % 2^32
315
316 Use bits 23..16 of x(n) to generate a byte. */
317 static void
lcg_urandom(unsigned int x0,unsigned char * buffer,size_t size)318 lcg_urandom(unsigned int x0, unsigned char *buffer, size_t size)
319 {
320 size_t index;
321 unsigned int x;
322
323 x = x0;
324 for (index=0; index < size; index++) {
325 x *= 214013;
326 x += 2531011;
327 /* modulo 2 ^ (8 * sizeof(int)) */
328 buffer[index] = (x >> 16) & 0xff;
329 }
330 }
331
332 /* Fill buffer with size pseudo-random bytes from the operating system random
333 number generator (RNG). It is suitable for most cryptographic purposes
334 except long living private keys for asymmetric encryption.
335
336 Return 0 on success, raise an exception and return -1 on error. */
337 int
_PyOS_URandom(void * buffer,Py_ssize_t size)338 _PyOS_URandom(void *buffer, Py_ssize_t size)
339 {
340 if (size < 0) {
341 PyErr_Format(PyExc_ValueError,
342 "negative argument not allowed");
343 return -1;
344 }
345 if (size == 0)
346 return 0;
347
348 #ifdef MS_WINDOWS
349 return win32_urandom((unsigned char *)buffer, size, 1);
350 #elif defined(PY_GETENTROPY)
351 return py_getentropy(buffer, size, 0);
352 #else
353 # ifdef __VMS
354 return vms_urandom((unsigned char *)buffer, size, 1);
355 # else
356 return dev_urandom_python((char*)buffer, size);
357 # endif
358 #endif
359 }
360
361 void
_PyRandom_Init(void)362 _PyRandom_Init(void)
363 {
364 char *env;
365 void *secret = &_Py_HashSecret;
366 Py_ssize_t secret_size = sizeof(_Py_HashSecret_t);
367
368 if (_Py_HashSecret_Initialized)
369 return;
370 _Py_HashSecret_Initialized = 1;
371
372 /*
373 By default, hash randomization is disabled, and only
374 enabled if PYTHONHASHSEED is set to non-empty or if
375 "-R" is provided at the command line:
376 */
377 if (!Py_HashRandomizationFlag) {
378 /* Disable the randomized hash: */
379 memset(secret, 0, secret_size);
380 return;
381 }
382
383 /*
384 Hash randomization is enabled. Generate a per-process secret,
385 using PYTHONHASHSEED if provided.
386 */
387
388 env = Py_GETENV("PYTHONHASHSEED");
389 if (env && *env != '\0' && strcmp(env, "random") != 0) {
390 char *endptr = env;
391 unsigned long seed;
392 seed = strtoul(env, &endptr, 10);
393 if (*endptr != '\0'
394 || seed > 4294967295UL
395 || (errno == ERANGE && seed == ULONG_MAX))
396 {
397 Py_FatalError("PYTHONHASHSEED must be \"random\" or an integer "
398 "in range [0; 4294967295]");
399 }
400 if (seed == 0) {
401 /* disable the randomized hash */
402 memset(secret, 0, secret_size);
403 }
404 else {
405 lcg_urandom(seed, (unsigned char*)secret, secret_size);
406 }
407 }
408 else {
409 #ifdef MS_WINDOWS
410 (void)win32_urandom((unsigned char *)secret, secret_size, 0);
411 #elif __VMS
412 vms_urandom((unsigned char *)secret, secret_size, 0);
413 #elif defined(PY_GETENTROPY)
414 (void)py_getentropy(secret, secret_size, 1);
415 #else
416 dev_urandom_noraise(secret, secret_size);
417 #endif
418 }
419 }
420
421 void
_PyRandom_Fini(void)422 _PyRandom_Fini(void)
423 {
424 #ifdef MS_WINDOWS
425 if (hCryptProv) {
426 CryptReleaseContext(hCryptProv, 0);
427 hCryptProv = 0;
428 }
429 #elif defined(PY_GETENTROPY)
430 /* nothing to clean */
431 #else
432 dev_urandom_close();
433 #endif
434 }
435