mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 05:31:20 +00:00 
			
		
		
		
	Issue #29157: Prefer getrandom() over getentropy()
* dev_urandom() now calls py_getentropy(). Prepare the fallback to support getentropy() failure and falls back on reading from /dev/urandom. * Simplify dev_urandom(). pyurandom() is now responsible to call getentropy() or getrandom(). Enhance also dev_urandom() and pyurandom() documentation. * getrandom() is now preferred over getentropy(). The glibc 2.24 now implements getentropy() on Linux using the getrandom() syscall. But getentropy() doesn't support non-blocking mode. Since getrandom() is tried first, it's not more needed to explicitly exclude getentropy() on Solaris. Replace: "if defined(HAVE_GETENTROPY) && !defined(sun)" with "if defined(HAVE_GETENTROPY)" * Enhance py_getrandom() documentation. py_getentropy() now supports ENOSYS, EPERM & EINTR
This commit is contained in:
		
							parent
							
								
									84b6fb0eea
								
							
						
					
					
						commit
						ff558f5aba
					
				
					 1 changed files with 187 additions and 87 deletions
				
			
		
							
								
								
									
										274
									
								
								Python/random.c
									
										
									
									
									
								
							
							
						
						
									
										274
									
								
								Python/random.c
									
										
									
									
									
								
							|  | @ -77,57 +77,23 @@ win32_urandom(unsigned char *buffer, Py_ssize_t size, int raise) | ||||||
|     return 0; |     return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* Issue #25003: Don't use getentropy() on Solaris (available since
 | #else /* !MS_WINDOWS */ | ||||||
|  * 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 |  | ||||||
| 
 | 
 | ||||||
| #if defined(HAVE_GETRANDOM) || defined(HAVE_GETRANDOM_SYSCALL) | #if defined(HAVE_GETRANDOM) || defined(HAVE_GETRANDOM_SYSCALL) | ||||||
| #define PY_GETRANDOM 1 | #define PY_GETRANDOM 1 | ||||||
| 
 | 
 | ||||||
| /* Call getrandom()
 | /* Call getrandom() to get random bytes:
 | ||||||
|  | 
 | ||||||
|    - Return 1 on success |    - Return 1 on success | ||||||
|    - Return 0 if getrandom() syscall is not available (failed with ENOSYS or |    - Return 0 if getrandom() is not available (failed with ENOSYS or EPERM), | ||||||
|      EPERM) or if getrandom(GRND_NONBLOCK) failed with EAGAIN (system urandom |      or if getrandom(GRND_NONBLOCK) failed with EAGAIN (system urandom not | ||||||
|      not initialized yet) and raise=0. |      initialized yet) and raise=0. | ||||||
|    - Raise an exception (if raise is non-zero) and return -1 on error: |    - Raise an exception (if raise is non-zero) and return -1 on error: | ||||||
|      getrandom() failed with EINTR and the Python signal handler raised an |      if getrandom() failed with EINTR, raise is non-zero and the Python signal | ||||||
|      exception, or getrandom() failed with a different error. */ |      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 | static int | ||||||
| py_getrandom(void *buffer, Py_ssize_t size, int blocking, int raise) | 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) { |     while (0 < size) { | ||||||
| #ifdef sun | #ifdef sun | ||||||
|         /* Issue #26735: On Solaris, getrandom() is limited to returning up
 |         /* 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); |         n = Py_MIN(size, 1024); | ||||||
| #else | #else | ||||||
|         n = Py_MIN(size, LONG_MAX); |         n = Py_MIN(size, LONG_MAX); | ||||||
|  | @ -179,18 +146,19 @@ py_getrandom(void *buffer, Py_ssize_t size, int blocking, int raise) | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
|         if (n < 0) { |         if (n < 0) { | ||||||
|             /* ENOSYS: getrandom() syscall not supported by the kernel (but
 |             /* ENOSYS: the syscall is not supported by the kernel.
 | ||||||
|              * maybe supported by the host which built Python). EPERM: |                EPERM: the syscall is blocked by a security policy (ex: SECCOMP) | ||||||
|              * getrandom() syscall blocked by SECCOMP or something else. */ |                or something else. */ | ||||||
|             if (errno == ENOSYS || errno == EPERM) { |             if (errno == ENOSYS || errno == EPERM) { | ||||||
|                 getrandom_works = 0; |                 getrandom_works = 0; | ||||||
|                 return 0; |                 return 0; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             /* getrandom(GRND_NONBLOCK) fails with EAGAIN if the system urandom
 |             /* 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, |                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) { |             if (errno == EAGAIN && !raise && !blocking) { | ||||||
|                 return 0; |                 return 0; | ||||||
|             } |             } | ||||||
|  | @ -217,7 +185,80 @@ py_getrandom(void *buffer, Py_ssize_t size, int blocking, int raise) | ||||||
|     } |     } | ||||||
|     return 1; |     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 { | static struct { | ||||||
|     int fd; |     int fd; | ||||||
|  | @ -225,35 +266,38 @@ static struct { | ||||||
|     ino_t st_ino; |     ino_t st_ino; | ||||||
| } urandom_cache = { -1 }; | } urandom_cache = { -1 }; | ||||||
| 
 | 
 | ||||||
|  | /* Read random bytes from the /dev/urandom device:
 | ||||||
| 
 | 
 | ||||||
| /* Read 'size' random bytes from py_getrandom(). Fall back on reading from
 |    - Return 0 on success | ||||||
|    /dev/urandom if getrandom() is not available. |    - Raise an exception (if raise is non-zero) and return -1 on error | ||||||
| 
 | 
 | ||||||
|    Return 0 on success. Raise an exception (if raise is non-zero) and return -1 |    Possible causes of errors: | ||||||
|    on error. */ | 
 | ||||||
|  |    - 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() is retried if it failed with EINTR: interrupted by a signal. | ||||||
|  | 
 | ||||||
|  |    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 | 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; |     int fd; | ||||||
|     Py_ssize_t n; |     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) { |     if (raise) { | ||||||
|         struct _Py_stat_struct st; |         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); |             fd = _Py_open("/dev/urandom", O_RDONLY); | ||||||
|             if (fd < 0) { |             if (fd < 0) { | ||||||
|                 if (errno == ENOENT || errno == ENXIO || |                 if (errno == ENOENT || errno == ENXIO || | ||||||
|                     errno == ENODEV || errno == EACCES) |                     errno == ENODEV || errno == EACCES) { | ||||||
|                     PyErr_SetString(PyExc_NotImplementedError, |                     PyErr_SetString(PyExc_NotImplementedError, | ||||||
|                                     "/dev/urandom (or equivalent) not found"); |                                     "/dev/urandom (or equivalent) not found"); | ||||||
|  |                 } | ||||||
|                 /* otherwise, keep the OSError exception raised by _Py_open() */ |                 /* otherwise, keep the OSError exception raised by _Py_open() */ | ||||||
|                 return -1; |                 return -1; | ||||||
|             } |             } | ||||||
|  | @ -349,8 +394,8 @@ dev_urandom_close(void) | ||||||
|         urandom_cache.fd = -1; |         urandom_cache.fd = -1; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | #endif /* !MS_WINDOWS */ | ||||||
| 
 | 
 | ||||||
| #endif |  | ||||||
| 
 | 
 | ||||||
| /* Fill buffer with pseudo-random bytes generated by a linear congruent
 | /* Fill buffer with pseudo-random bytes generated by a linear congruent
 | ||||||
|    generator (LCG): |    generator (LCG): | ||||||
|  | @ -373,14 +418,56 @@ lcg_urandom(unsigned int x0, unsigned char *buffer, size_t size) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* If raise is zero:
 | /* Read random bytes:
 | ||||||
|    - Don't raise exceptions on error | 
 | ||||||
|    - Don't call PyErr_CheckSignals() on EINTR (retry directly the interrupted |    - Return 0 on success | ||||||
|      syscall) |    - Raise an exception (if raise is non-zero) and return -1 on error | ||||||
|    - Don't release the GIL to call syscalls. */ | 
 | ||||||
|  |    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 | static int | ||||||
| pyurandom(void *buffer, Py_ssize_t size, int blocking, int raise) | 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 (size < 0) { | ||||||
|         if (raise) { |         if (raise) { | ||||||
|             PyErr_Format(PyExc_ValueError, |             PyErr_Format(PyExc_ValueError, | ||||||
|  | @ -395,10 +482,25 @@ pyurandom(void *buffer, Py_ssize_t size, int blocking, int raise) | ||||||
| 
 | 
 | ||||||
| #ifdef MS_WINDOWS | #ifdef MS_WINDOWS | ||||||
|     return win32_urandom((unsigned char *)buffer, size, raise); |     return win32_urandom((unsigned char *)buffer, size, raise); | ||||||
| #elif defined(PY_GETENTROPY) |  | ||||||
|     return py_getentropy(buffer, size, raise); |  | ||||||
| #else | #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 | #endif | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -491,8 +593,6 @@ _PyRandom_Fini(void) | ||||||
|         CryptReleaseContext(hCryptProv, 0); |         CryptReleaseContext(hCryptProv, 0); | ||||||
|         hCryptProv = 0; |         hCryptProv = 0; | ||||||
|     } |     } | ||||||
| #elif defined(PY_GETENTROPY) |  | ||||||
|     /* nothing to clean */ |  | ||||||
| #else | #else | ||||||
|     dev_urandom_close(); |     dev_urandom_close(); | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Victor Stinner
						Victor Stinner