1 #include "Python.h"
2 #ifdef MS_WINDOWS
3 #include <windows.h>
4 #else
5 #include <fcntl.h>
6 #if 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 #elif defined(HAVE_GETENTROPY) && !defined(sun)
102 #define PY_GETENTROPY 1
103
104 /* Fill buffer with size pseudo-random bytes generated by getentropy().
105 Return 0 on success, or raise an exception and return -1 on error.
106 If fatal is nonzero, call Py_FatalError() instead of raising an exception
107 on error. */
108 static int
py_getentropy(unsigned char * buffer,Py_ssize_t size,int fatal)109 py_getentropy(unsigned char *buffer, Py_ssize_t size, int fatal)
110 {
111 while (size > 0) {
112 Py_ssize_t len = size < 256 ? size : 256;
113 int res;
114
115 if (!fatal) {
116 Py_BEGIN_ALLOW_THREADS
117 res = getentropy(buffer, len);
118 Py_END_ALLOW_THREADS
119
120 if (res < 0) {
121 PyErr_SetFromErrno(PyExc_OSError);
122 return -1;
123 }
124 }
125 else {
126 res = getentropy(buffer, len);
127 if (res < 0)
128 Py_FatalError("getentropy() failed");
129 }
130
131 buffer += len;
132 size -= len;
133 }
134 return 0;
135 }
136 #endif
137
138 #ifdef __VMS
139 /* Use openssl random routine */
140 #include <openssl/rand.h>
141 static int
vms_urandom(unsigned char * buffer,Py_ssize_t size,int raise)142 vms_urandom(unsigned char *buffer, Py_ssize_t size, int raise)
143 {
144 if (RAND_pseudo_bytes(buffer, size) < 0) {
145 if (raise) {
146 PyErr_Format(PyExc_ValueError,
147 "RAND_pseudo_bytes");
148 } else {
149 Py_FatalError("Failed to initialize the randomized hash "
150 "secret using RAND_pseudo_bytes");
151 }
152 return -1;
153 }
154 return 0;
155 }
156 #endif /* __VMS */
157
158
159 #if !defined(MS_WINDOWS) && !defined(__VMS)
160
161 static struct {
162 int fd;
163 dev_t st_dev;
164 ino_t st_ino;
165 } urandom_cache = { -1 };
166
167 /* Read size bytes from /dev/urandom into buffer.
168 Call Py_FatalError() on error. */
169 static void
dev_urandom_noraise(unsigned char * buffer,Py_ssize_t size)170 dev_urandom_noraise(unsigned char *buffer, Py_ssize_t size)
171 {
172 int fd;
173 Py_ssize_t n;
174
175 assert (0 < size);
176
177 fd = open("/dev/urandom", O_RDONLY);
178 if (fd < 0)
179 Py_FatalError("Failed to open /dev/urandom");
180
181 while (0 < size)
182 {
183 do {
184 n = read(fd, buffer, (size_t)size);
185 } while (n < 0 && errno == EINTR);
186 if (n <= 0)
187 {
188 /* stop on error or if read(size) returned 0 */
189 Py_FatalError("Failed to read bytes from /dev/urandom");
190 break;
191 }
192 buffer += n;
193 size -= (Py_ssize_t)n;
194 }
195 close(fd);
196 }
197
198 /* Read size bytes from /dev/urandom into buffer.
199 Return 0 on success, raise an exception and return -1 on error. */
200 static int
dev_urandom_python(char * buffer,Py_ssize_t size)201 dev_urandom_python(char *buffer, Py_ssize_t size)
202 {
203 int fd;
204 Py_ssize_t n;
205 struct stat st;
206 int attr;
207
208 if (size <= 0)
209 return 0;
210
211 if (urandom_cache.fd >= 0) {
212 /* Does the fd point to the same thing as before? (issue #21207) */
213 if (fstat(urandom_cache.fd, &st)
214 || st.st_dev != urandom_cache.st_dev
215 || st.st_ino != urandom_cache.st_ino) {
216 /* Something changed: forget the cached fd (but don't close it,
217 since it probably points to something important for some
218 third-party code). */
219 urandom_cache.fd = -1;
220 }
221 }
222 if (urandom_cache.fd >= 0)
223 fd = urandom_cache.fd;
224 else {
225 Py_BEGIN_ALLOW_THREADS
226 fd = open("/dev/urandom", O_RDONLY);
227 Py_END_ALLOW_THREADS
228 if (fd < 0)
229 {
230 if (errno == ENOENT || errno == ENXIO ||
231 errno == ENODEV || errno == EACCES)
232 PyErr_SetString(PyExc_NotImplementedError,
233 "/dev/urandom (or equivalent) not found");
234 else
235 PyErr_SetFromErrno(PyExc_OSError);
236 return -1;
237 }
238
239 /* try to make the file descriptor non-inheritable, ignore errors */
240 attr = fcntl(fd, F_GETFD);
241 if (attr >= 0) {
242 attr |= FD_CLOEXEC;
243 (void)fcntl(fd, F_SETFD, attr);
244 }
245
246 if (urandom_cache.fd >= 0) {
247 /* urandom_fd was initialized by another thread while we were
248 not holding the GIL, keep it. */
249 close(fd);
250 fd = urandom_cache.fd;
251 }
252 else {
253 if (fstat(fd, &st)) {
254 PyErr_SetFromErrno(PyExc_OSError);
255 close(fd);
256 return -1;
257 }
258 else {
259 urandom_cache.fd = fd;
260 urandom_cache.st_dev = st.st_dev;
261 urandom_cache.st_ino = st.st_ino;
262 }
263 }
264 }
265
266 Py_BEGIN_ALLOW_THREADS
267 do {
268 do {
269 n = read(fd, buffer, (size_t)size);
270 } while (n < 0 && errno == EINTR);
271 if (n <= 0)
272 break;
273 buffer += n;
274 size -= (Py_ssize_t)n;
275 } while (0 < size);
276 Py_END_ALLOW_THREADS
277
278 if (n <= 0)
279 {
280 /* stop on error or if read(size) returned 0 */
281 if (n < 0)
282 PyErr_SetFromErrno(PyExc_OSError);
283 else
284 PyErr_Format(PyExc_RuntimeError,
285 "Failed to read %zi bytes from /dev/urandom",
286 size);
287 return -1;
288 }
289 return 0;
290 }
291
292 static void
dev_urandom_close(void)293 dev_urandom_close(void)
294 {
295 if (urandom_cache.fd >= 0) {
296 close(urandom_cache.fd);
297 urandom_cache.fd = -1;
298 }
299 }
300
301
302 #endif /* !defined(MS_WINDOWS) && !defined(__VMS) */
303
304 /* Fill buffer with pseudo-random bytes generated by a linear congruent
305 generator (LCG):
306
307 x(n+1) = (x(n) * 214013 + 2531011) % 2^32
308
309 Use bits 23..16 of x(n) to generate a byte. */
310 static void
lcg_urandom(unsigned int x0,unsigned char * buffer,size_t size)311 lcg_urandom(unsigned int x0, unsigned char *buffer, size_t size)
312 {
313 size_t index;
314 unsigned int x;
315
316 x = x0;
317 for (index=0; index < size; index++) {
318 x *= 214013;
319 x += 2531011;
320 /* modulo 2 ^ (8 * sizeof(int)) */
321 buffer[index] = (x >> 16) & 0xff;
322 }
323 }
324
325 /* Fill buffer with size pseudo-random bytes from the operating system random
326 number generator (RNG). It is suitable for most cryptographic purposes
327 except long living private keys for asymmetric encryption.
328
329 Return 0 on success, raise an exception and return -1 on error. */
330 int
_PyOS_URandom(void * buffer,Py_ssize_t size)331 _PyOS_URandom(void *buffer, Py_ssize_t size)
332 {
333 if (size < 0) {
334 PyErr_Format(PyExc_ValueError,
335 "negative argument not allowed");
336 return -1;
337 }
338 if (size == 0)
339 return 0;
340
341 #ifdef MS_WINDOWS
342 return win32_urandom((unsigned char *)buffer, size, 1);
343 #elif defined(PY_GETENTROPY)
344 return py_getentropy(buffer, size, 0);
345 #else
346 # ifdef __VMS
347 return vms_urandom((unsigned char *)buffer, size, 1);
348 # else
349 return dev_urandom_python((char*)buffer, size);
350 # endif
351 #endif
352 }
353
354 void
_PyRandom_Init(void)355 _PyRandom_Init(void)
356 {
357 char *env;
358 void *secret = &_Py_HashSecret;
359 Py_ssize_t secret_size = sizeof(_Py_HashSecret_t);
360
361 if (_Py_HashSecret_Initialized)
362 return;
363 _Py_HashSecret_Initialized = 1;
364
365 /*
366 By default, hash randomization is disabled, and only
367 enabled if PYTHONHASHSEED is set to non-empty or if
368 "-R" is provided at the command line:
369 */
370 if (!Py_HashRandomizationFlag) {
371 /* Disable the randomized hash: */
372 memset(secret, 0, secret_size);
373 return;
374 }
375
376 /*
377 Hash randomization is enabled. Generate a per-process secret,
378 using PYTHONHASHSEED if provided.
379 */
380
381 env = Py_GETENV("PYTHONHASHSEED");
382 if (env && *env != '\0' && strcmp(env, "random") != 0) {
383 char *endptr = env;
384 unsigned long seed;
385 seed = strtoul(env, &endptr, 10);
386 if (*endptr != '\0'
387 || seed > 4294967295UL
388 || (errno == ERANGE && seed == ULONG_MAX))
389 {
390 Py_FatalError("PYTHONHASHSEED must be \"random\" or an integer "
391 "in range [0; 4294967295]");
392 }
393 if (seed == 0) {
394 /* disable the randomized hash */
395 memset(secret, 0, secret_size);
396 }
397 else {
398 lcg_urandom(seed, (unsigned char*)secret, secret_size);
399 }
400 }
401 else {
402 #ifdef MS_WINDOWS
403 (void)win32_urandom((unsigned char *)secret, secret_size, 0);
404 #elif __VMS
405 vms_urandom((unsigned char *)secret, secret_size, 0);
406 #elif defined(PY_GETENTROPY)
407 (void)py_getentropy(secret, secret_size, 1);
408 #else
409 dev_urandom_noraise(secret, secret_size);
410 #endif
411 }
412 }
413
414 void
_PyRandom_Fini(void)415 _PyRandom_Fini(void)
416 {
417 #ifdef MS_WINDOWS
418 if (hCryptProv) {
419 CryptReleaseContext(hCryptProv, 0);
420 hCryptProv = 0;
421 }
422 #elif defined(PY_GETENTROPY)
423 /* nothing to clean */
424 #else
425 dev_urandom_close();
426 #endif
427 }
428