summaryrefslogtreecommitdiff
path: root/Python/random.c
diff options
context:
space:
mode:
Diffstat (limited to 'Python/random.c')
-rw-r--r--Python/random.c70
1 files changed, 43 insertions, 27 deletions
diff --git a/Python/random.c b/Python/random.c
index 31f61d03b5..c97d5e7100 100644
--- a/Python/random.c
+++ b/Python/random.c
@@ -87,7 +87,7 @@ win32_urandom(unsigned char *buffer, Py_ssize_t size, int raise)
- Return 1 on success
- 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).
+ initialized yet) and raise=0.
- Raise an exception (if raise is non-zero) and return -1 on 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
@@ -95,19 +95,13 @@ win32_urandom(unsigned char *buffer, Py_ssize_t size, int raise)
getrandom() is retried if it failed with EINTR: interrupted by a signal. */
static int
-py_getrandom(void *buffer, Py_ssize_t size, int raise)
+py_getrandom(void *buffer, Py_ssize_t size, int blocking, int raise)
{
/* Is getrandom() supported by the running kernel? Set to 0 if getrandom()
failed with ENOSYS or EPERM. Need Linux kernel 3.17 or newer, or Solaris
11.3 or newer */
static int getrandom_works = 1;
-
- /* getrandom() on Linux will block if called before the kernel has
- * initialized the urandom entropy pool. This will cause Python
- * to hang on startup if called very early in the boot process -
- * see https://bugs.python.org/issue26839. To avoid this, use the
- * GRND_NONBLOCK flag. */
- const int flags = GRND_NONBLOCK;
+ int flags;
char *dest;
long n;
@@ -115,6 +109,7 @@ py_getrandom(void *buffer, Py_ssize_t size, int raise)
return 0;
}
+ flags = blocking ? 0 : GRND_NONBLOCK;
dest = buffer;
while (0 < size) {
#ifdef sun
@@ -159,15 +154,12 @@ py_getrandom(void *buffer, Py_ssize_t size, int raise)
return 0;
}
- if (errno == EAGAIN) {
- /* getrandom(GRND_NONBLOCK) fails with EAGAIN if the system
- urandom is not initialiazed yet. In this case, fall back on
- reading from /dev/urandom.
-
- Note: In this case the data read will not be random so
- should not be used for cryptographic purposes. Retaining
- the existing semantics for practical purposes. */
- getrandom_works = 0;
+ /* getrandom(GRND_NONBLOCK) fails with EAGAIN if the system urandom
+ 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:
+ see the PEP 524. */
+ if (errno == EAGAIN && !raise && !blocking) {
return 0;
}
@@ -442,13 +434,16 @@ lcg_urandom(unsigned int x0, unsigned char *buffer, size_t size)
is not available or does not work.
Prefer getrandom() over getentropy() because getrandom() supports blocking
- and non-blocking mode and Python requires non-blocking RNG at startup to
- initialize its hash secret: see the PEP 524.
+ 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
@@ -467,7 +462,7 @@ lcg_urandom(unsigned int x0, unsigned char *buffer, size_t size)
- Don't release the GIL to call functions.
*/
static int
-pyurandom(void *buffer, Py_ssize_t size, int raise)
+pyurandom(void *buffer, Py_ssize_t size, int blocking, int raise)
{
#if defined(PY_GETRANDOM) || defined(PY_GETENTROPY)
int res;
@@ -491,7 +486,7 @@ pyurandom(void *buffer, Py_ssize_t size, int raise)
#if defined(PY_GETRANDOM) || defined(PY_GETENTROPY)
#ifdef PY_GETRANDOM
- res = py_getrandom(buffer, size, raise);
+ res = py_getrandom(buffer, size, blocking, raise);
#else
res = py_getentropy(buffer, size, raise);
#endif
@@ -502,7 +497,7 @@ pyurandom(void *buffer, Py_ssize_t size, int raise)
return 0;
}
/* getrandom() or getentropy() function is not available: failed with
- ENOSYS, EPERM or EAGAIN. Fall back on reading from /dev/urandom. */
+ ENOSYS or EPERM. Fall back on reading from /dev/urandom. */
#endif
return dev_urandom(buffer, size, raise);
@@ -513,11 +508,29 @@ pyurandom(void *buffer, Py_ssize_t size, int raise)
number generator (RNG). It is suitable for most cryptographic purposes
except long living private keys for asymmetric encryption.
+ On Linux 3.17 and newer, the getrandom() syscall is used in blocking mode:
+ block until the system urandom entropy pool is initialized (128 bits are
+ collected by the kernel).
+
Return 0 on success. Raise an exception and return -1 on error. */
int
_PyOS_URandom(void *buffer, Py_ssize_t size)
{
- return pyurandom(buffer, size, 1);
+ return pyurandom(buffer, size, 1, 1);
+}
+
+/* Fill buffer with size pseudo-random bytes from the operating system random
+ number generator (RNG). It is not suitable for cryptographic purpose.
+
+ On Linux 3.17 and newer (when getrandom() syscall is used), if the system
+ urandom is not initialized yet, the function returns "weak" entropy read
+ from /dev/urandom.
+
+ Return 0 on success. Raise an exception and return -1 on error. */
+int
+_PyOS_URandomNonblock(void *buffer, Py_ssize_t size)
+{
+ return pyurandom(buffer, size, 0, 1);
}
void
@@ -526,7 +539,7 @@ _PyRandom_Init(void)
char *env;
unsigned char *secret = (unsigned char *)&_Py_HashSecret.uc;
Py_ssize_t secret_size = sizeof(_Py_HashSecret_t);
- assert(secret_size == sizeof(_Py_HashSecret.uc));
+ Py_BUILD_ASSERT(sizeof(_Py_HashSecret_t) == sizeof(_Py_HashSecret.uc));
if (_Py_HashSecret_Initialized)
return;
@@ -561,8 +574,11 @@ _PyRandom_Init(void)
int res;
/* _PyRandom_Init() is called very early in the Python initialization
- and so exceptions cannot be used (use raise=0). */
- res = pyurandom(secret, secret_size, 0);
+ and so exceptions cannot be used (use raise=0).
+
+ _PyRandom_Init() must not block Python initialization: call
+ pyurandom() is non-blocking mode (blocking=0): see the PEP 524. */
+ res = pyurandom(secret, secret_size, 0, 0);
if (res < 0) {
Py_FatalError("failed to get random numbers to initialize Python");
}