Line data Source code
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
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
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
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
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
170 0 : 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 0 : fd = open("/dev/urandom", O_RDONLY);
178 0 : if (fd < 0)
179 0 : Py_FatalError("Failed to open /dev/urandom");
180 :
181 0 : while (0 < size)
182 : {
183 : do {
184 0 : n = read(fd, buffer, (size_t)size);
185 0 : } while (n < 0 && errno == EINTR);
186 0 : if (n <= 0)
187 : {
188 : /* stop on error or if read(size) returned 0 */
189 0 : Py_FatalError("Failed to read bytes from /dev/urandom");
190 0 : break;
191 : }
192 0 : buffer += n;
193 0 : size -= (Py_ssize_t)n;
194 : }
195 0 : close(fd);
196 0 : }
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
201 3 : 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 3 : if (size <= 0)
209 0 : return 0;
210 :
211 3 : if (urandom_cache.fd >= 0) {
212 : /* Does the fd point to the same thing as before? (issue #21207) */
213 0 : if (fstat(urandom_cache.fd, &st)
214 0 : || st.st_dev != urandom_cache.st_dev
215 0 : || 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 0 : urandom_cache.fd = -1;
220 : }
221 : }
222 3 : if (urandom_cache.fd >= 0)
223 0 : fd = urandom_cache.fd;
224 : else {
225 : Py_BEGIN_ALLOW_THREADS
226 3 : fd = open("/dev/urandom", O_RDONLY);
227 : Py_END_ALLOW_THREADS
228 3 : if (fd < 0)
229 : {
230 0 : if (errno == ENOENT || errno == ENXIO ||
231 0 : errno == ENODEV || errno == EACCES)
232 0 : PyErr_SetString(PyExc_NotImplementedError,
233 : "/dev/urandom (or equivalent) not found");
234 : else
235 0 : PyErr_SetFromErrno(PyExc_OSError);
236 0 : return -1;
237 : }
238 :
239 : /* try to make the file descriptor non-inheritable, ignore errors */
240 3 : attr = fcntl(fd, F_GETFD);
241 3 : if (attr >= 0) {
242 3 : attr |= FD_CLOEXEC;
243 3 : (void)fcntl(fd, F_SETFD, attr);
244 : }
245 :
246 3 : if (urandom_cache.fd >= 0) {
247 : /* urandom_fd was initialized by another thread while we were
248 : not holding the GIL, keep it. */
249 0 : close(fd);
250 0 : fd = urandom_cache.fd;
251 : }
252 : else {
253 3 : if (fstat(fd, &st)) {
254 0 : PyErr_SetFromErrno(PyExc_OSError);
255 0 : close(fd);
256 0 : return -1;
257 : }
258 : else {
259 3 : urandom_cache.fd = fd;
260 3 : urandom_cache.st_dev = st.st_dev;
261 3 : urandom_cache.st_ino = st.st_ino;
262 : }
263 : }
264 : }
265 :
266 : Py_BEGIN_ALLOW_THREADS
267 : do {
268 : do {
269 3 : n = read(fd, buffer, (size_t)size);
270 3 : } while (n < 0 && errno == EINTR);
271 3 : if (n <= 0)
272 0 : break;
273 3 : buffer += n;
274 3 : size -= (Py_ssize_t)n;
275 3 : } while (0 < size);
276 : Py_END_ALLOW_THREADS
277 :
278 3 : if (n <= 0)
279 : {
280 : /* stop on error or if read(size) returned 0 */
281 0 : if (n < 0)
282 0 : PyErr_SetFromErrno(PyExc_OSError);
283 : else
284 0 : PyErr_Format(PyExc_RuntimeError,
285 : "Failed to read %zi bytes from /dev/urandom",
286 : size);
287 0 : return -1;
288 : }
289 3 : return 0;
290 : }
291 :
292 : static void
293 3 : dev_urandom_close(void)
294 : {
295 3 : if (urandom_cache.fd >= 0) {
296 3 : close(urandom_cache.fd);
297 3 : urandom_cache.fd = -1;
298 : }
299 3 : }
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
311 0 : lcg_urandom(unsigned int x0, unsigned char *buffer, size_t size)
312 : {
313 : size_t index;
314 : unsigned int x;
315 :
316 0 : x = x0;
317 0 : for (index=0; index < size; index++) {
318 0 : x *= 214013;
319 0 : x += 2531011;
320 : /* modulo 2 ^ (8 * sizeof(int)) */
321 0 : buffer[index] = (x >> 16) & 0xff;
322 : }
323 0 : }
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
331 3 : _PyOS_URandom(void *buffer, Py_ssize_t size)
332 : {
333 3 : if (size < 0) {
334 0 : PyErr_Format(PyExc_ValueError,
335 : "negative argument not allowed");
336 0 : return -1;
337 : }
338 3 : if (size == 0)
339 0 : 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 3 : return dev_urandom_python((char*)buffer, size);
350 : # endif
351 : #endif
352 : }
353 :
354 : void
355 6 : _PyRandom_Init(void)
356 : {
357 : char *env;
358 6 : void *secret = &_Py_HashSecret;
359 6 : Py_ssize_t secret_size = sizeof(_Py_HashSecret_t);
360 :
361 6 : if (_Py_HashSecret_Initialized)
362 3 : return;
363 3 : _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 3 : if (!Py_HashRandomizationFlag) {
371 : /* Disable the randomized hash: */
372 3 : memset(secret, 0, secret_size);
373 3 : return;
374 : }
375 :
376 : /*
377 : Hash randomization is enabled. Generate a per-process secret,
378 : using PYTHONHASHSEED if provided.
379 : */
380 :
381 0 : env = Py_GETENV("PYTHONHASHSEED");
382 0 : if (env && *env != '\0' && strcmp(env, "random") != 0) {
383 0 : char *endptr = env;
384 : unsigned long seed;
385 0 : seed = strtoul(env, &endptr, 10);
386 0 : if (*endptr != '\0'
387 0 : || seed > 4294967295UL
388 0 : || (errno == ERANGE && seed == ULONG_MAX))
389 : {
390 0 : Py_FatalError("PYTHONHASHSEED must be \"random\" or an integer "
391 : "in range [0; 4294967295]");
392 : }
393 0 : if (seed == 0) {
394 : /* disable the randomized hash */
395 0 : memset(secret, 0, secret_size);
396 : }
397 : else {
398 0 : 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 0 : dev_urandom_noraise(secret, secret_size);
410 : #endif
411 : }
412 : }
413 :
414 : void
415 3 : _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 3 : dev_urandom_close();
426 : #endif
427 3 : }
|