summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/os.rst57
-rw-r--r--Doc/whatsnew/3.6.rst4
-rw-r--r--Lib/test/test_os.py32
-rw-r--r--Misc/NEWS3
-rw-r--r--Modules/clinic/posixmodule.c.h41
-rw-r--r--Modules/posixmodule.c66
6 files changed, 194 insertions, 9 deletions
diff --git a/Doc/library/os.rst b/Doc/library/os.rst
index b73cb25..69c559c 100644
--- a/Doc/library/os.rst
+++ b/Doc/library/os.rst
@@ -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:: 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
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
diff --git a/Doc/whatsnew/3.6.rst b/Doc/whatsnew/3.6.rst
index 270424e..683ea82 100644
--- a/Doc/whatsnew/3.6.rst
+++ b/Doc/whatsnew/3.6.rst
@@ -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
------
diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py
index d79a32f..5205672 100644
--- a/Lib/test/test_os.py
+++ b/Lib/test/test_os.py
@@ -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 = (
diff --git a/Misc/NEWS b/Misc/NEWS
index 34aa42e..c0c75c8 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -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.
diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h
index b99f180..e543db4 100644
--- a/Modules/clinic/posixmodule.c.h
+++ b/Modules/clinic/posixmodule.c.h
@@ -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]*/
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index beea9e0..2dc6d7b 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -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;
}