• 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_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