• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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