Add os.getrandom()

Issue #27778: Expose the Linux getrandom() syscall as a new os.getrandom()
function.

This change is part of the PEP 524.
This commit is contained in:
Victor Stinner 2016-09-06 16:18:52 -07:00
parent 3580b03352
commit 9b1f474df6
6 changed files with 194 additions and 9 deletions

View File

@ -3931,21 +3931,44 @@ Higher-level operations on pathnames are defined in the :mod:`os.path` module.
.. versionadded:: 3.3
.. _os-miscfunc:
Miscellaneous Functions
-----------------------
Random numbers
--------------
.. function:: urandom(n)
.. function:: getrandom(size, flags=0)
Return a string of *n* random bytes suitable for cryptographic use.
Get up to *size* random bytes. The function can return less bytes than
requested.
These bytes can be used to seed user-space random number generators or for
cryptographic purposes.
``getrandom()`` relies on entropy gathered from device drivers and other
sources of environmental noise. Unnecessarily reading large quantities of
data will have a negative impact on other users of the ``/dev/random`` and
``/dev/urandom`` devices.
The flags argument is a bit mask that can contain zero or more of the
following values ORed together: :py:data:`os.GRND_RANDOM` and
:py:data:`GRND_NONBLOCK`.
See also the `Linux getrandom() manual page
<http://man7.org/linux/man-pages/man2/getrandom.2.html>`_.
Availability: Linux 3.17 and newer.
.. versionadded:: 3.6
.. function:: urandom(size)
Return a string of *size* random bytes suitable for cryptographic use.
This function returns random bytes from an OS-specific randomness source. The
returned data should be unpredictable enough for cryptographic applications,
though its exact quality depends on the OS implementation.
On Linux, ``getrandom()`` syscall is used if available and the urandom
On Linux, the ``getrandom()`` syscall is used if available and the urandom
entropy pool is initialized (``getrandom()`` does not block).
On a Unix-like system this will query ``/dev/urandom``. On Windows, it
will use ``CryptGenRandom()``. If a randomness source is not found,
@ -3955,11 +3978,29 @@ Miscellaneous Functions
provided by your platform, please see :class:`random.SystemRandom`.
.. versionchanged:: 3.5.2
On Linux, if ``getrandom()`` blocks (the urandom entropy pool is not
initialized yet), fall back on reading ``/dev/urandom``.
On Linux, if the ``getrandom()`` syscall blocks (the urandom entropy pool
is not initialized yet), fall back on reading ``/dev/urandom``.
.. versionchanged:: 3.5
On Linux 3.17 and newer, the ``getrandom()`` syscall is now used
when available. On OpenBSD 5.6 and newer, the C ``getentropy()``
function is now used. These functions avoid the usage of an internal file
descriptor.
.. data:: GRND_NONBLOCK
By default, when reading from ``/dev/random``, :func:`getrandom` blocks if
no random bytes are available, and when reading from ``/dev/urandom``, it blocks
if the entropy pool has not yet been initialized.
If the :py:data:`GRND_NONBLOCK` flag is set, then :func:`getrandom` does not
block in these cases, but instead immediately raises :exc:`BlockingIOError`.
.. versionadded:: 3.6
.. data:: GRND_RANDOM
If this bit is set, then random bytes are drawn from the
``/dev/random`` pool instead of the ``/dev/urandom`` pool.
.. versionadded:: 3.6

View File

@ -484,6 +484,10 @@ iterator is neither exhausted nor explicitly closed a :exc:`ResourceWarning`
will be emitted in its destructor.
(Contributed by Serhiy Storchaka in :issue:`25994`.)
The Linux ``getrandom()`` syscall (get random bytes) is now exposed as the new
:func:`os.getrandom` function.
(Contributed by Victor Stinner, part of the :pep:`524`)
pickle
------

View File

@ -1248,6 +1248,7 @@ class URandomTests(unittest.TestCase):
def test_urandom_value(self):
data1 = os.urandom(16)
self.assertIsInstance(data1, bytes)
data2 = os.urandom(16)
self.assertNotEqual(data1, data2)
@ -1268,6 +1269,37 @@ class URandomTests(unittest.TestCase):
self.assertNotEqual(data1, data2)
@unittest.skipUnless(hasattr(os, 'getrandom'), 'need os.getrandom()')
class GetRandomTests(unittest.TestCase):
def test_getrandom_type(self):
data = os.getrandom(16)
self.assertIsInstance(data, bytes)
self.assertEqual(len(data), 16)
def test_getrandom0(self):
empty = os.getrandom(0)
self.assertEqual(empty, b'')
def test_getrandom_random(self):
self.assertTrue(hasattr(os, 'GRND_RANDOM'))
# Don't test os.getrandom(1, os.GRND_RANDOM) to not consume the rare
# resource /dev/random
def test_getrandom_nonblock(self):
# The call must not fail. Check also that the flag exists
try:
os.getrandom(1, os.GRND_NONBLOCK)
except BlockingIOError:
# System urandom is not initialized yet
pass
def test_getrandom_value(self):
data1 = os.getrandom(16)
data2 = os.getrandom(16)
self.assertNotEqual(data1, data2)
# os.urandom() doesn't use a file descriptor when it is implemented with the
# getentropy() function, the getrandom() function or the getrandom() syscall
OS_URANDOM_DONT_USE_FD = (

View File

@ -89,6 +89,9 @@ Core and Builtins
Library
-------
- Issue #27778: Expose the Linux ``getrandom()`` syscall as a new
:func:`os.getrandom` function. This change is part of the :pep:`524`.
- Issue #27691: Fix ssl module's parsing of GEN_RID subject alternative name
fields in X.509 certs.

View File

@ -5571,6 +5571,41 @@ exit:
return return_value;
}
#if defined(HAVE_GETRANDOM_SYSCALL)
PyDoc_STRVAR(os_getrandom__doc__,
"getrandom($module, /, size, flags=0)\n"
"--\n"
"\n"
"Obtain a series of random bytes.");
#define OS_GETRANDOM_METHODDEF \
{"getrandom", (PyCFunction)os_getrandom, METH_VARARGS|METH_KEYWORDS, os_getrandom__doc__},
static PyObject *
os_getrandom_impl(PyObject *module, Py_ssize_t size, int flags);
static PyObject *
os_getrandom(PyObject *module, PyObject *args, PyObject *kwargs)
{
PyObject *return_value = NULL;
static const char * const _keywords[] = {"size", "flags", NULL};
static _PyArg_Parser _parser = {"n|i:getrandom", _keywords, 0};
Py_ssize_t size;
int flags = 0;
if (!_PyArg_ParseTupleAndKeywordsFast(args, kwargs, &_parser,
&size, &flags)) {
goto exit;
}
return_value = os_getrandom_impl(module, size, flags);
exit:
return return_value;
}
#endif /* defined(HAVE_GETRANDOM_SYSCALL) */
#ifndef OS_TTYNAME_METHODDEF
#define OS_TTYNAME_METHODDEF
#endif /* !defined(OS_TTYNAME_METHODDEF) */
@ -6042,4 +6077,8 @@ exit:
#ifndef OS_SET_HANDLE_INHERITABLE_METHODDEF
#define OS_SET_HANDLE_INHERITABLE_METHODDEF
#endif /* !defined(OS_SET_HANDLE_INHERITABLE_METHODDEF) */
/*[clinic end generated code: output=677ce794fb126161 input=a9049054013a1b77]*/
#ifndef OS_GETRANDOM_METHODDEF
#define OS_GETRANDOM_METHODDEF
#endif /* !defined(OS_GETRANDOM_METHODDEF) */
/*[clinic end generated code: output=fce51c7d432662c2 input=a9049054013a1b77]*/

View File

@ -133,6 +133,13 @@ corresponding Unix manual entries for more information on calls.");
#include <sys/sysctl.h>
#endif
#ifdef HAVE_LINUX_RANDOM_H
# include <linux/random.h>
#endif
#ifdef HAVE_GETRANDOM_SYSCALL
# include <sys/syscall.h>
#endif
#if defined(MS_WINDOWS)
# define TERMSIZE_USE_CONIO
#elif defined(HAVE_SYS_IOCTL_H)
@ -12421,6 +12428,59 @@ os_fspath_impl(PyObject *module, PyObject *path)
return PyOS_FSPath(path);
}
#ifdef HAVE_GETRANDOM_SYSCALL
/*[clinic input]
os.getrandom
size: Py_ssize_t
flags: int=0
Obtain a series of random bytes.
[clinic start generated code]*/
static PyObject *
os_getrandom_impl(PyObject *module, Py_ssize_t size, int flags)
/*[clinic end generated code: output=b3a618196a61409c input=59bafac39c594947]*/
{
char *buffer;
Py_ssize_t n;
PyObject *bytes;
if (size < 0) {
errno = EINVAL;
return posix_error();
}
buffer = PyMem_Malloc(size);
if (buffer == NULL) {
PyErr_NoMemory();
return NULL;
}
while (1) {
n = syscall(SYS_getrandom, buffer, size, flags);
if (n < 0 && errno == EINTR) {
if (PyErr_CheckSignals() < 0) {
return NULL;
}
continue;
}
break;
}
if (n < 0) {
PyMem_Free(buffer);
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
}
bytes = PyBytes_FromStringAndSize(buffer, n);
PyMem_Free(buffer);
return bytes;
}
#endif /* HAVE_GETRANDOM_SYSCALL */
#include "clinic/posixmodule.c.h"
/*[clinic input]
@ -12621,6 +12681,7 @@ static PyMethodDef posix_methods[] = {
METH_VARARGS | METH_KEYWORDS,
posix_scandir__doc__},
OS_FSPATH_METHODDEF
OS_GETRANDOM_METHODDEF
{NULL, NULL} /* Sentinel */
};
@ -13066,6 +13127,11 @@ all_ins(PyObject *m)
if (PyModule_AddIntMacro(m, RTLD_DEEPBIND)) return -1;
#endif
#ifdef HAVE_GETRANDOM_SYSCALL
if (PyModule_AddIntMacro(m, GRND_RANDOM)) return -1;
if (PyModule_AddIntMacro(m, GRND_NONBLOCK)) return -1;
#endif
return 0;
}