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