Commit 9b1f474d authored by Victor Stinner's avatar Victor Stinner

Add os.getrandom()

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

This change is part of the PEP 524.
parent 3580b033
...@@ -3931,21 +3931,44 @@ Higher-level operations on pathnames are defined in the :mod:`os.path` module. ...@@ -3931,21 +3931,44 @@ Higher-level operations on pathnames are defined in the :mod:`os.path` module.
.. versionadded:: 3.3 .. versionadded:: 3.3
.. _os-miscfunc:
Miscellaneous Functions Random numbers
----------------------- --------------
.. function:: getrandom(size, flags=0)
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(n) .. function:: urandom(size)
Return a string of *n* random bytes suitable for cryptographic use. Return a string of *size* random bytes suitable for cryptographic use.
This function returns random bytes from an OS-specific randomness source. The This function returns random bytes from an OS-specific randomness source. The
returned data should be unpredictable enough for cryptographic applications, returned data should be unpredictable enough for cryptographic applications,
though its exact quality depends on the OS implementation. 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). entropy pool is initialized (``getrandom()`` does not block).
On a Unix-like system this will query ``/dev/urandom``. On Windows, it On a Unix-like system this will query ``/dev/urandom``. On Windows, it
will use ``CryptGenRandom()``. If a randomness source is not found, will use ``CryptGenRandom()``. If a randomness source is not found,
...@@ -3955,11 +3978,29 @@ Miscellaneous Functions ...@@ -3955,11 +3978,29 @@ Miscellaneous Functions
provided by your platform, please see :class:`random.SystemRandom`. provided by your platform, please see :class:`random.SystemRandom`.
.. versionchanged:: 3.5.2 .. versionchanged:: 3.5.2
On Linux, if ``getrandom()`` blocks (the urandom entropy pool is not On Linux, if the ``getrandom()`` syscall blocks (the urandom entropy pool
initialized yet), fall back on reading ``/dev/urandom``. is not initialized yet), fall back on reading ``/dev/urandom``.
.. versionchanged:: 3.5 .. versionchanged:: 3.5
On Linux 3.17 and newer, the ``getrandom()`` syscall is now used On Linux 3.17 and newer, the ``getrandom()`` syscall is now used
when available. On OpenBSD 5.6 and newer, the C ``getentropy()`` when available. On OpenBSD 5.6 and newer, the C ``getentropy()``
function is now used. These functions avoid the usage of an internal file function is now used. These functions avoid the usage of an internal file
descriptor. 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
...@@ -484,6 +484,10 @@ iterator is neither exhausted nor explicitly closed a :exc:`ResourceWarning` ...@@ -484,6 +484,10 @@ iterator is neither exhausted nor explicitly closed a :exc:`ResourceWarning`
will be emitted in its destructor. will be emitted in its destructor.
(Contributed by Serhiy Storchaka in :issue:`25994`.) (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 pickle
------ ------
......
...@@ -1248,6 +1248,7 @@ class URandomTests(unittest.TestCase): ...@@ -1248,6 +1248,7 @@ class URandomTests(unittest.TestCase):
def test_urandom_value(self): def test_urandom_value(self):
data1 = os.urandom(16) data1 = os.urandom(16)
self.assertIsInstance(data1, bytes)
data2 = os.urandom(16) data2 = os.urandom(16)
self.assertNotEqual(data1, data2) self.assertNotEqual(data1, data2)
...@@ -1268,6 +1269,37 @@ class URandomTests(unittest.TestCase): ...@@ -1268,6 +1269,37 @@ class URandomTests(unittest.TestCase):
self.assertNotEqual(data1, data2) 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 # os.urandom() doesn't use a file descriptor when it is implemented with the
# getentropy() function, the getrandom() function or the getrandom() syscall # getentropy() function, the getrandom() function or the getrandom() syscall
OS_URANDOM_DONT_USE_FD = ( OS_URANDOM_DONT_USE_FD = (
......
...@@ -89,6 +89,9 @@ Core and Builtins ...@@ -89,6 +89,9 @@ Core and Builtins
Library 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 - Issue #27691: Fix ssl module's parsing of GEN_RID subject alternative name
fields in X.509 certs. fields in X.509 certs.
......
...@@ -5571,6 +5571,41 @@ exit: ...@@ -5571,6 +5571,41 @@ exit:
return return_value; 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 #ifndef OS_TTYNAME_METHODDEF
#define OS_TTYNAME_METHODDEF #define OS_TTYNAME_METHODDEF
#endif /* !defined(OS_TTYNAME_METHODDEF) */ #endif /* !defined(OS_TTYNAME_METHODDEF) */
...@@ -6042,4 +6077,8 @@ exit: ...@@ -6042,4 +6077,8 @@ exit:
#ifndef OS_SET_HANDLE_INHERITABLE_METHODDEF #ifndef OS_SET_HANDLE_INHERITABLE_METHODDEF
#define OS_SET_HANDLE_INHERITABLE_METHODDEF #define OS_SET_HANDLE_INHERITABLE_METHODDEF
#endif /* !defined(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]*/
...@@ -133,6 +133,13 @@ corresponding Unix manual entries for more information on calls."); ...@@ -133,6 +133,13 @@ corresponding Unix manual entries for more information on calls.");
#include <sys/sysctl.h> #include <sys/sysctl.h>
#endif #endif
#ifdef HAVE_LINUX_RANDOM_H
# include <linux/random.h>
#endif
#ifdef HAVE_GETRANDOM_SYSCALL
# include <sys/syscall.h>
#endif
#if defined(MS_WINDOWS) #if defined(MS_WINDOWS)
# define TERMSIZE_USE_CONIO # define TERMSIZE_USE_CONIO
#elif defined(HAVE_SYS_IOCTL_H) #elif defined(HAVE_SYS_IOCTL_H)
...@@ -12421,6 +12428,59 @@ os_fspath_impl(PyObject *module, PyObject *path) ...@@ -12421,6 +12428,59 @@ os_fspath_impl(PyObject *module, PyObject *path)
return PyOS_FSPath(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" #include "clinic/posixmodule.c.h"
/*[clinic input] /*[clinic input]
...@@ -12621,6 +12681,7 @@ static PyMethodDef posix_methods[] = { ...@@ -12621,6 +12681,7 @@ static PyMethodDef posix_methods[] = {
METH_VARARGS | METH_KEYWORDS, METH_VARARGS | METH_KEYWORDS,
posix_scandir__doc__}, posix_scandir__doc__},
OS_FSPATH_METHODDEF OS_FSPATH_METHODDEF
OS_GETRANDOM_METHODDEF
{NULL, NULL} /* Sentinel */ {NULL, NULL} /* Sentinel */
}; };
...@@ -13066,6 +13127,11 @@ all_ins(PyObject *m) ...@@ -13066,6 +13127,11 @@ all_ins(PyObject *m)
if (PyModule_AddIntMacro(m, RTLD_DEEPBIND)) return -1; if (PyModule_AddIntMacro(m, RTLD_DEEPBIND)) return -1;
#endif #endif
#ifdef HAVE_GETRANDOM_SYSCALL
if (PyModule_AddIntMacro(m, GRND_RANDOM)) return -1;
if (PyModule_AddIntMacro(m, GRND_NONBLOCK)) return -1;
#endif
return 0; return 0;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment