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:
parent
3580b03352
commit
9b1f474df6
|
@ -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
|
||||
|
|
|
@ -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
|
||||
------
|
||||
|
|
|
@ -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 = (
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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]*/
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue