@ -77,57 +77,23 @@ win32_urandom(unsigned char *buffer, Py_ssize_t size, int raise)
return 0 ;
}
/* Issue #25003: Don't use getentropy() on Solaris (available since
* Solaris 11.3 ) , it is blocking whereas os . urandom ( ) should not block . */
# elif defined(HAVE_GETENTROPY) && !defined(sun)
# define PY_GETENTROPY 1
/* Fill buffer with size pseudo-random bytes generated by getentropy().
Return 0 on success , or raise an exception and return - 1 on error .
If raise is zero , don ' t raise an exception on error . */
static int
py_getentropy ( char * buffer , Py_ssize_t size , int raise )
{
while ( size > 0 ) {
Py_ssize_t len = Py_MIN ( size , 256 ) ;
int res ;
if ( raise ) {
Py_BEGIN_ALLOW_THREADS
res = getentropy ( buffer , len ) ;
Py_END_ALLOW_THREADS
}
else {
res = getentropy ( buffer , len ) ;
}
if ( res < 0 ) {
if ( raise ) {
PyErr_SetFromErrno ( PyExc_OSError ) ;
}
return - 1 ;
}
buffer + = len ;
size - = len ;
}
return 0 ;
}
# else
# else /* !MS_WINDOWS */
# if defined(HAVE_GETRANDOM) || defined(HAVE_GETRANDOM_SYSCALL)
# define PY_GETRANDOM 1
/* Call getrandom()
/* Call getrandom() to get random bytes:
- Return 1 on success
- Return 0 if getrandom ( ) syscall is not available ( failed with ENOSYS or
EPERM ) or if getrandom ( GRND_NONBLOCK ) failed with EAGAIN ( system urandom
not initialized yet ) and raise = 0.
- Return 0 if getrandom ( ) is not available ( failed with ENOSYS or EPERM ) ,
or if getrandom ( GRND_NONBLOCK ) failed with EAGAIN ( system urandom not
initialized yet ) and raise = 0.
- Raise an exception ( if raise is non - zero ) and return - 1 on error :
getrandom ( ) failed with EINTR and the Python signal handler raised an
exception , or getrandom ( ) failed with a different error . */
if getrandom ( ) failed with EINTR , raise is non - zero and the Python signal
handler raised an exception , or if getrandom ( ) failed with a different
error .
getrandom ( ) is retried if it failed with EINTR : interrupted by a signal . */
static int
py_getrandom ( void * buffer , Py_ssize_t size , int blocking , int raise )
{
@ -148,7 +114,8 @@ py_getrandom(void *buffer, Py_ssize_t size, int blocking, int raise)
while ( 0 < size ) {
# ifdef sun
/* Issue #26735: On Solaris, getrandom() is limited to returning up
to 1024 bytes */
to 1024 bytes . Call it multiple times if more bytes are
requested . */
n = Py_MIN ( size , 1024 ) ;
# else
n = Py_MIN ( size , LONG_MAX ) ;
@ -179,18 +146,19 @@ py_getrandom(void *buffer, Py_ssize_t size, int blocking, int raise)
# endif
if ( n < 0 ) {
/* ENOSYS: getrandom() syscall not supported by the kernel (but
* maybe supported by the host which built Python ) . EPERM :
* getrandom ( ) syscall blocked by SECCOMP or something else . */
/* ENOSYS: the syscall is not supported by the kernel.
EPERM : the syscall is blocked by a security policy ( ex : SECCOMP )
or something else . */
if ( errno = = ENOSYS | | errno = = EPERM ) {
getrandom_works = 0 ;
return 0 ;
}
/* getrandom(GRND_NONBLOCK) fails with EAGAIN if the system urandom
is not initialiazed yet . For _PyRandom_Init ( ) , we ignore their
is not initialiazed yet . For _PyRandom_Init ( ) , we ignore the
error and fall back on reading / dev / urandom which never blocks ,
even if the system urandom is not initialized yet . */
even if the system urandom is not initialized yet :
see the PEP 524. */
if ( errno = = EAGAIN & & ! raise & & ! blocking ) {
return 0 ;
}
@ -217,7 +185,80 @@ py_getrandom(void *buffer, Py_ssize_t size, int blocking, int raise)
}
return 1 ;
}
# endif
# elif defined(HAVE_GETENTROPY)
# define PY_GETENTROPY 1
/* Fill buffer with size pseudo-random bytes generated by getentropy():
- Return 1 on success
- Return 0 if getentropy ( ) syscall is not available ( failed with ENOSYS or
EPERM ) .
- Raise an exception ( if raise is non - zero ) and return - 1 on error :
if getentropy ( ) failed with EINTR , raise is non - zero and the Python signal
handler raised an exception , or if getentropy ( ) failed with a different
error .
getentropy ( ) is retried if it failed with EINTR : interrupted by a signal . */
static int
py_getentropy ( char * buffer , Py_ssize_t size , int raise )
{
/* Is getentropy() supported by the running kernel? Set to 0 if
getentropy ( ) failed with ENOSYS or EPERM . */
static int getentropy_works = 1 ;
if ( ! getentropy_works ) {
return 0 ;
}
while ( size > 0 ) {
/* getentropy() is limited to returning up to 256 bytes. Call it
multiple times if more bytes are requested . */
Py_ssize_t len = Py_MIN ( size , 256 ) ;
int res ;
if ( raise ) {
Py_BEGIN_ALLOW_THREADS
res = getentropy ( buffer , len ) ;
Py_END_ALLOW_THREADS
}
else {
res = getentropy ( buffer , len ) ;
}
if ( res < 0 ) {
/* ENOSYS: the syscall is not supported by the running kernel.
EPERM : the syscall is blocked by a security policy ( ex : SECCOMP )
or something else . */
if ( errno = = ENOSYS | | errno = = EPERM ) {
getentropy_works = 0 ;
return 0 ;
}
if ( errno = = EINTR ) {
if ( raise ) {
if ( PyErr_CheckSignals ( ) ) {
return - 1 ;
}
}
/* retry getentropy() if it was interrupted by a signal */
continue ;
}
if ( raise ) {
PyErr_SetFromErrno ( PyExc_OSError ) ;
}
return - 1 ;
}
buffer + = len ;
size - = len ;
}
return 1 ;
}
# endif /* defined(HAVE_GETENTROPY) && !defined(sun) */
static struct {
int fd ;
@ -225,35 +266,38 @@ static struct {
ino_t st_ino ;
} urandom_cache = { - 1 } ;
/* Read random bytes from the /dev/urandom device:
- Return 0 on success
- Raise an exception ( if raise is non - zero ) and return - 1 on error
Possible causes of errors :
- open ( ) failed with ENOENT , ENXIO , ENODEV , EACCES : the / dev / urandom device
was not found . For example , it was removed manually or not exposed in a
chroot or container .
- open ( ) failed with a different error
- fstat ( ) failed
- read ( ) failed or returned 0
/* Read 'size' random bytes from py_getrandom(). Fall back on reading from
/ dev / urandom if getrandom ( ) is not available .
read ( ) is retried if it failed with EINTR : interrupted by a signal .
Return 0 on success . Raise an exception ( if raise is non - zero ) and return - 1
on error . */
The file descriptor of the device is kept open between calls to avoid using
many file descriptors when run in parallel from multiple threads :
see the issue # 18756.
st_dev and st_ino fields of the file descriptor ( from fstat ( ) ) are cached to
check if the file descriptor was replaced by a different file ( which is
likely a bug in the application ) : see the issue # 21207.
If the file descriptor was closed or replaced , open a new file descriptor
but don ' t close the old file descriptor : it probably points to something
important for some third - party code . */
static int
dev_urandom ( char * buffer , Py_ssize_t size , int blocking , int raise )
dev_urandom ( char * buffer , Py_ssize_t size , int raise )
{
int fd ;
Py_ssize_t n ;
# ifdef PY_GETRANDOM
int res ;
# endif
assert ( size > 0 ) ;
# ifdef PY_GETRANDOM
res = py_getrandom ( buffer , size , blocking , raise ) ;
if ( res < 0 ) {
return - 1 ;
}
if ( res = = 1 ) {
return 0 ;
}
/* getrandom() failed with ENOSYS or EPERM,
fall back on reading / dev / urandom */
# endif
if ( raise ) {
struct _Py_stat_struct st ;
@ -275,9 +319,10 @@ dev_urandom(char *buffer, Py_ssize_t size, int blocking, int raise)
fd = _Py_open ( " /dev/urandom " , O_RDONLY ) ;
if ( fd < 0 ) {
if ( errno = = ENOENT | | errno = = ENXIO | |
errno = = ENODEV | | errno = = EACCES )
errno = = ENODEV | | errno = = EACCES ) {
PyErr_SetString ( PyExc_NotImplementedError ,
" /dev/urandom (or equivalent) not found " ) ;
}
/* otherwise, keep the OSError exception raised by _Py_open() */
return - 1 ;
}
@ -349,8 +394,8 @@ dev_urandom_close(void)
urandom_cache . fd = - 1 ;
}
}
# endif /* !MS_WINDOWS */
# endif
/* Fill buffer with pseudo-random bytes generated by a linear congruent
generator ( LCG ) :
@ -373,14 +418,56 @@ lcg_urandom(unsigned int x0, unsigned char *buffer, size_t size)
}
}
/* If raise is zero:
- Don ' t raise exceptions on error
- Don ' t call PyErr_CheckSignals ( ) on EINTR ( retry directly the interrupted
syscall )
- Don ' t release the GIL to call syscalls . */
/* Read random bytes:
- Return 0 on success
- Raise an exception ( if raise is non - zero ) and return - 1 on error
Used sources of entropy ordered by preference , preferred source first :
- CryptGenRandom ( ) on Windows
- getrandom ( ) function ( ex : Linux and Solaris ) : call py_getrandom ( )
- getentropy ( ) function ( ex : OpenBSD ) : call py_getentropy ( )
- / dev / urandom device
Read from the / dev / urandom device if getrandom ( ) or getentropy ( ) function
is not available or does not work .
Prefer getrandom ( ) over getentropy ( ) because getrandom ( ) supports blocking
and non - blocking mode : see the PEP 524. Python requires non - blocking RNG at
startup to initialize its hash secret , but os . urandom ( ) must block until the
system urandom is initialized ( at least on Linux 3.17 and newer ) .
Prefer getrandom ( ) and getentropy ( ) over reading directly / dev / urandom
because these functions don ' t need file descriptors and so avoid ENFILE or
EMFILE errors ( too many open files ) : see the issue # 18756.
Only the getrandom ( ) function supports non - blocking mode .
Only use RNG running in the kernel . They are more secure because it is
harder to get the internal state of a RNG running in the kernel land than a
RNG running in the user land . The kernel has a direct access to the hardware
and has access to hardware RNG , they are used as entropy sources .
Note : the OpenSSL RAND_pseudo_bytes ( ) function does not automatically reseed
its RNG on fork ( ) , two child processes ( with the same pid ) generate the same
random numbers : see issue # 18747. Kernel RNGs don ' t have this issue ,
they have access to good quality entropy sources .
If raise is zero :
- Don ' t raise an exception on error
- Don ' t call the Python signal handler ( don ' t call PyErr_CheckSignals ( ) ) if
a function fails with EINTR : retry directly the interrupted function
- Don ' t release the GIL to call functions .
*/
static int
pyurandom ( void * buffer , Py_ssize_t size , int blocking , int raise )
{
# if defined(PY_GETRANDOM) || defined(PY_GETENTROPY)
int res ;
# endif
if ( size < 0 ) {
if ( raise ) {
PyErr_Format ( PyExc_ValueError ,
@ -395,10 +482,25 @@ pyurandom(void *buffer, Py_ssize_t size, int blocking, int raise)
# ifdef MS_WINDOWS
return win32_urandom ( ( unsigned char * ) buffer , size , raise ) ;
# elif defined(PY_GETENTROPY)
return py_getentropy ( buffer , size , raise ) ;
# else
return dev_urandom ( buffer , size , blocking , raise ) ;
# if defined(PY_GETRANDOM) || defined(PY_GETENTROPY)
# ifdef PY_GETRANDOM
res = py_getrandom ( buffer , size , blocking , raise ) ;
# else
res = py_getentropy ( buffer , size , raise ) ;
# endif
if ( res < 0 ) {
return - 1 ;
}
if ( res = = 1 ) {
return 0 ;
}
/* getrandom() or getentropy() function is not available: failed with
ENOSYS or EPERM . Fall back on reading from / dev / urandom . */
# endif
return dev_urandom ( buffer , size , raise ) ;
# endif
}
@ -491,8 +593,6 @@ _PyRandom_Fini(void)
CryptReleaseContext ( hCryptProv , 0 ) ;
hCryptProv = 0 ;
}
# elif defined(PY_GETENTROPY)
/* nothing to clean */
# else
dev_urandom_close ( ) ;
# endif