Skip to content

Commit e66987e

Browse files
committed
os.urandom() now blocks on Linux
Issue #27776: The os.urandom() function does now block on Linux 3.17 and newer until the system urandom entropy pool is initialized to increase the security. This change is part of the PEP 524.
1 parent e256acc commit e66987e

File tree

8 files changed

+132
-61
lines changed

8 files changed

+132
-61
lines changed

Doc/library/os.rst

+21-8
Original file line numberDiff line numberDiff line change
@@ -3968,14 +3968,27 @@ Random numbers
39683968
returned data should be unpredictable enough for cryptographic applications,
39693969
though its exact quality depends on the OS implementation.
39703970

3971-
On Linux, the ``getrandom()`` syscall is used if available and the urandom
3972-
entropy pool is initialized (``getrandom()`` does not block).
3973-
On a Unix-like system this will query ``/dev/urandom``. On Windows, it
3974-
will use ``CryptGenRandom()``. If a randomness source is not found,
3975-
:exc:`NotImplementedError` will be raised.
3976-
3977-
For an easy-to-use interface to the random number generator
3978-
provided by your platform, please see :class:`random.SystemRandom`.
3971+
On Linux, if the ``getrandom()`` syscall is available, it is used in
3972+
blocking mode: block until the system urandom entropy pool is initialized
3973+
(128 bits of entropy are collected by the kernel). See the :pep:`524` for
3974+
the rationale. On Linux, the :func:`getrandom` function can be used to get
3975+
random bytes in non-blocking mode (using the :data:`GRND_NONBLOCK` flag) or
3976+
to poll until the system urandom entropy pool is initialized.
3977+
3978+
On a Unix-like system, random bytes are read from the ``/dev/urandom``
3979+
device. If the ``/dev/urandom`` device is not available or not readable, the
3980+
:exc:`NotImplementedError` exception is raised.
3981+
3982+
On Windows, it will use ``CryptGenRandom()``.
3983+
3984+
.. seealso::
3985+
The :mod:`secrets` module provides higher level functions. For an
3986+
easy-to-use interface to the random number generator provided by your
3987+
platform, please see :class:`random.SystemRandom`.
3988+
3989+
.. versionchanged:: 3.6.0
3990+
On Linux, ``getrandom()`` is now used in blocking mode to increase the
3991+
security.
39793992

39803993
.. versionchanged:: 3.5.2
39813994
On Linux, if the ``getrandom()`` syscall blocks (the urandom entropy pool

Doc/whatsnew/3.6.rst

+12
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ Standard library improvements:
7070

7171
* PEP 519: :ref:`Adding a file system path protocol <pep-519>`
7272

73+
Security improvements:
74+
75+
* On Linux, :func:`os.urandom` now blocks until the system urandom entropy pool
76+
is initialized to increase the security. See the :pep:`524` for the
77+
rationale.
78+
7379
Windows improvements:
7480

7581
* The ``py.exe`` launcher, when used interactively, no longer prefers
@@ -345,6 +351,9 @@ New Modules
345351
Improved Modules
346352
================
347353

354+
On Linux, :func:`os.urandom` now blocks until the system urandom entropy pool
355+
is initialized to increase the security. See the :pep:`524` for the rationale.
356+
348357

349358
asyncio
350359
-------
@@ -913,6 +922,9 @@ Changes in 'python' Command Behavior
913922
Changes in the Python API
914923
-------------------------
915924

925+
* On Linux, :func:`os.urandom` now blocks until the system urandom entropy pool
926+
is initialized to increase the security.
927+
916928
* When :meth:`importlib.abc.Loader.exec_module` is defined,
917929
:meth:`importlib.abc.Loader.create_module` must also be defined.
918930

Include/pylifecycle.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,8 @@ PyAPI_FUNC(PyOS_sighandler_t) PyOS_getsig(int);
117117
PyAPI_FUNC(PyOS_sighandler_t) PyOS_setsig(int, PyOS_sighandler_t);
118118

119119
/* Random */
120-
PyAPI_FUNC(int) _PyOS_URandom (void *buffer, Py_ssize_t size);
120+
PyAPI_FUNC(int) _PyOS_URandom(void *buffer, Py_ssize_t size);
121+
PyAPI_FUNC(int) _PyOS_URandomNonblock(void *buffer, Py_ssize_t size);
121122

122123
#ifdef __cplusplus
123124
}

Lib/random.py

-9
Original file line numberDiff line numberDiff line change
@@ -105,15 +105,6 @@ def seed(self, a=None, version=2):
105105
106106
"""
107107

108-
if a is None:
109-
try:
110-
# Seed with enough bytes to span the 19937 bit
111-
# state space for the Mersenne Twister
112-
a = int.from_bytes(_urandom(2500), 'big')
113-
except NotImplementedError:
114-
import time
115-
a = int(time.time() * 256) # use fractional seconds
116-
117108
if version == 1 and isinstance(a, (str, bytes)):
118109
x = ord(a[0]) << 7 if a else 0
119110
for c in a:

Misc/NEWS

+4
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ Core and Builtins
8989
Library
9090
-------
9191

92+
- Issue #27776: The :func:`os.urandom` function does now block on Linux 3.17
93+
and newer until the system urandom entropy pool is initialized to increase
94+
the security. This change is part of the :pep:`524`.
95+
9296
- Issue #27778: Expose the Linux ``getrandom()`` syscall as a new
9397
:func:`os.getrandom` function. This change is part of the :pep:`524`.
9498

Modules/_randommodule.c

+46-10
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ init_genrand(RandomObject *self, uint32_t s)
165165
/* initialize by an array with array-length */
166166
/* init_key is the array for initializing keys */
167167
/* key_length is its length */
168-
static PyObject *
168+
static void
169169
init_by_array(RandomObject *self, uint32_t init_key[], size_t key_length)
170170
{
171171
size_t i, j, k; /* was signed in the original code. RDH 12/16/2002 */
@@ -190,15 +190,44 @@ init_by_array(RandomObject *self, uint32_t init_key[], size_t key_length)
190190
}
191191

192192
mt[0] = 0x80000000U; /* MSB is 1; assuring non-zero initial array */
193-
Py_INCREF(Py_None);
194-
return Py_None;
195193
}
196194

197195
/*
198196
* The rest is Python-specific code, neither part of, nor derived from, the
199197
* Twister download.
200198
*/
201199

200+
static int
201+
random_seed_urandom(RandomObject *self)
202+
{
203+
PY_UINT32_T key[N];
204+
205+
if (_PyOS_URandomNonblock(key, sizeof(key)) < 0) {
206+
return -1;
207+
}
208+
init_by_array(self, key, Py_ARRAY_LENGTH(key));
209+
return 0;
210+
}
211+
212+
static void
213+
random_seed_time_pid(RandomObject *self)
214+
{
215+
_PyTime_t now;
216+
uint32_t key[5];
217+
218+
now = _PyTime_GetSystemClock();
219+
key[0] = (PY_UINT32_T)(now & 0xffffffffU);
220+
key[1] = (PY_UINT32_T)(now >> 32);
221+
222+
key[2] = (PY_UINT32_T)getpid();
223+
224+
now = _PyTime_GetMonotonicClock();
225+
key[3] = (PY_UINT32_T)(now & 0xffffffffU);
226+
key[4] = (PY_UINT32_T)(now >> 32);
227+
228+
init_by_array(self, key, Py_ARRAY_LENGTH(key));
229+
}
230+
202231
static PyObject *
203232
random_seed(RandomObject *self, PyObject *args)
204233
{
@@ -212,14 +241,17 @@ random_seed(RandomObject *self, PyObject *args)
212241
if (!PyArg_UnpackTuple(args, "seed", 0, 1, &arg))
213242
return NULL;
214243

215-
if (arg == NULL || arg == Py_None) {
216-
time_t now;
244+
if (arg == NULL || arg == Py_None) {
245+
if (random_seed_urandom(self) >= 0) {
246+
PyErr_Clear();
217247

218-
time(&now);
219-
init_genrand(self, (uint32_t)now);
220-
Py_INCREF(Py_None);
221-
return Py_None;
248+
/* Reading system entropy failed, fall back on the worst entropy:
249+
use the current time and process identifier. */
250+
random_seed_time_pid(self);
251+
}
252+
Py_RETURN_NONE;
222253
}
254+
223255
/* This algorithm relies on the number being unsigned.
224256
* So: if the arg is a PyLong, use its absolute value.
225257
* Otherwise use its hash value, cast to unsigned.
@@ -269,7 +301,11 @@ random_seed(RandomObject *self, PyObject *args)
269301
}
270302
}
271303
#endif
272-
result = init_by_array(self, key, keyused);
304+
init_by_array(self, key, keyused);
305+
306+
Py_INCREF(Py_None);
307+
result = Py_None;
308+
273309
Done:
274310
Py_XDECREF(n);
275311
PyMem_Free(key);

Modules/posixmodule.c

+1-2
Original file line numberDiff line numberDiff line change
@@ -11168,8 +11168,7 @@ os_urandom_impl(PyObject *module, Py_ssize_t size)
1116811168
if (bytes == NULL)
1116911169
return NULL;
1117011170

11171-
result = _PyOS_URandom(PyBytes_AS_STRING(bytes),
11172-
PyBytes_GET_SIZE(bytes));
11171+
result = _PyOS_URandom(PyBytes_AS_STRING(bytes), PyBytes_GET_SIZE(bytes));
1117311172
if (result == -1) {
1117411173
Py_DECREF(bytes);
1117511174
return NULL;

Python/random.c

+46-31
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ win32_urandom(unsigned char *buffer, Py_ssize_t size, int raise)
7777
}
7878

7979
/* Issue #25003: Don't use getentropy() on Solaris (available since
80-
Solaris 11.3), it is blocking whereas os.urandom() should not block. */
80+
* Solaris 11.3), it is blocking whereas os.urandom() should not block. */
8181
#elif defined(HAVE_GETENTROPY) && !defined(sun)
8282
#define PY_GETENTROPY 1
8383

@@ -121,31 +121,28 @@ py_getentropy(char *buffer, Py_ssize_t size, int raise)
121121

122122
/* Call getrandom()
123123
- Return 1 on success
124-
- Return 0 if getrandom() syscall is not available (fails with ENOSYS).
124+
- Return 0 if getrandom() syscall is not available (fails with ENOSYS)
125+
or if getrandom(GRND_NONBLOCK) fails with EAGAIN (blocking=0 and system
126+
urandom not initialized yet) and raise=0.
125127
- Raise an exception (if raise is non-zero) and return -1 on error:
126128
getrandom() failed with EINTR and the Python signal handler raised an
127129
exception, or getrandom() failed with a different error. */
128130
static int
129-
py_getrandom(void *buffer, Py_ssize_t size, int raise)
131+
py_getrandom(void *buffer, Py_ssize_t size, int blocking, int raise)
130132
{
131-
/* Is getrandom() supported by the running kernel?
132-
Need Linux kernel 3.17 or newer, or Solaris 11.3 or newer */
133+
/* Is getrandom() supported by the running kernel? Set to 0 if getrandom()
134+
fails with ENOSYS. Need Linux kernel 3.17 or newer, or Solaris 11.3
135+
or newer */
133136
static int getrandom_works = 1;
134-
135-
/* getrandom() on Linux will block if called before the kernel has
136-
initialized the urandom entropy pool. This will cause Python
137-
to hang on startup if called very early in the boot process -
138-
see https://bugs.python.org/issue26839. To avoid this, use the
139-
GRND_NONBLOCK flag. */
140-
const int flags = GRND_NONBLOCK;
141-
137+
int flags;
142138
char *dest;
143139
long n;
144140

145141
if (!getrandom_works) {
146142
return 0;
147143
}
148144

145+
flags = blocking ? 0 : GRND_NONBLOCK;
149146
dest = buffer;
150147
while (0 < size) {
151148
#ifdef sun
@@ -185,15 +182,12 @@ py_getrandom(void *buffer, Py_ssize_t size, int raise)
185182
getrandom_works = 0;
186183
return 0;
187184
}
188-
if (errno == EAGAIN) {
189-
/* If we failed with EAGAIN, the entropy pool was
190-
uninitialized. In this case, we return failure to fall
191-
back to reading from /dev/urandom.
192-
193-
Note: In this case the data read will not be random so
194-
should not be used for cryptographic purposes. Retaining
195-
the existing semantics for practical purposes. */
196-
getrandom_works = 0;
185+
186+
/* getrandom(GRND_NONBLOCK) fails with EAGAIN if the system urandom
187+
is not initialiazed yet. For _PyRandom_Init(), we ignore their
188+
error and fall back on reading /dev/urandom which never blocks,
189+
even if the system urandom is not initialized yet. */
190+
if (errno == EAGAIN && !raise && !blocking) {
197191
return 0;
198192
}
199193

@@ -228,13 +222,13 @@ static struct {
228222
} urandom_cache = { -1 };
229223

230224

231-
/* Read 'size' random bytes from getrandom(). Fall back on reading from
225+
/* Read 'size' random bytes from py_getrandom(). Fall back on reading from
232226
/dev/urandom if getrandom() is not available.
233227
234228
Return 0 on success. Raise an exception (if raise is non-zero) and return -1
235229
on error. */
236230
static int
237-
dev_urandom(char *buffer, Py_ssize_t size, int raise)
231+
dev_urandom(char *buffer, Py_ssize_t size, int blocking, int raise)
238232
{
239233
int fd;
240234
Py_ssize_t n;
@@ -245,7 +239,7 @@ dev_urandom(char *buffer, Py_ssize_t size, int raise)
245239
assert(size > 0);
246240

247241
#ifdef PY_GETRANDOM
248-
res = py_getrandom(buffer, size, raise);
242+
res = py_getrandom(buffer, size, blocking, raise);
249243
if (res < 0) {
250244
return -1;
251245
}
@@ -381,7 +375,7 @@ lcg_urandom(unsigned int x0, unsigned char *buffer, size_t size)
381375
syscall)
382376
- Don't release the GIL to call syscalls. */
383377
static int
384-
pyurandom(void *buffer, Py_ssize_t size, int raise)
378+
pyurandom(void *buffer, Py_ssize_t size, int blocking, int raise)
385379
{
386380
if (size < 0) {
387381
if (raise) {
@@ -400,19 +394,37 @@ pyurandom(void *buffer, Py_ssize_t size, int raise)
400394
#elif defined(PY_GETENTROPY)
401395
return py_getentropy(buffer, size, raise);
402396
#else
403-
return dev_urandom(buffer, size, raise);
397+
return dev_urandom(buffer, size, blocking, raise);
404398
#endif
405399
}
406400

407401
/* Fill buffer with size pseudo-random bytes from the operating system random
408402
number generator (RNG). It is suitable for most cryptographic purposes
409403
except long living private keys for asymmetric encryption.
410404
411-
Return 0 on success, raise an exception and return -1 on error. */
405+
On Linux 3.17 and newer, the getrandom() syscall is used in blocking mode:
406+
block until the system urandom entropy pool is initialized (128 bits are
407+
collected by the kernel).
408+
409+
Return 0 on success. Raise an exception and return -1 on error. */
412410
int
413411
_PyOS_URandom(void *buffer, Py_ssize_t size)
414412
{
415-
return pyurandom(buffer, size, 1);
413+
return pyurandom(buffer, size, 1, 1);
414+
}
415+
416+
/* Fill buffer with size pseudo-random bytes from the operating system random
417+
number generator (RNG). It is not suitable for cryptographic purpose.
418+
419+
On Linux 3.17 and newer (when getrandom() syscall is used), if the system
420+
urandom is not initialized yet, the function returns "weak" entropy read
421+
from /dev/urandom.
422+
423+
Return 0 on success. Raise an exception and return -1 on error. */
424+
int
425+
_PyOS_URandomNonblock(void *buffer, Py_ssize_t size)
426+
{
427+
return pyurandom(buffer, size, 0, 1);
416428
}
417429

418430
void
@@ -456,8 +468,11 @@ _PyRandom_Init(void)
456468
int res;
457469

458470
/* _PyRandom_Init() is called very early in the Python initialization
459-
and so exceptions cannot be used (use raise=0). */
460-
res = pyurandom(secret, secret_size, 0);
471+
and so exceptions cannot be used (use raise=0).
472+
473+
_PyRandom_Init() must not block Python initialization: call
474+
pyurandom() is non-blocking mode (blocking=0): see the PEP 524. */
475+
res = pyurandom(secret, secret_size, 0, 0);
461476
if (res < 0) {
462477
Py_FatalError("failed to get random numbers to initialize Python");
463478
}

0 commit comments

Comments
 (0)