[Python-checkins] cpython: Add os.getrandom()

victor.stinner python-checkins at python.org
Tue Sep 6 19:20:05 EDT 2016


https://hg.python.org/cpython/rev/27267d2fb091
changeset:   103168:27267d2fb091
user:        Victor Stinner <victor.stinner at gmail.com>
date:        Tue Sep 06 16:18:52 2016 -0700
summary:
  Add os.getrandom()

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

This change is part of the PEP 524.

files:
  Doc/library/os.rst             |  65 +++++++++++++++++----
  Doc/whatsnew/3.6.rst           |   4 +
  Lib/test/test_os.py            |  32 ++++++++++
  Misc/NEWS                      |   3 +
  Modules/clinic/posixmodule.c.h |  41 +++++++++++++-
  Modules/posixmodule.c          |  66 ++++++++++++++++++++++
  6 files changed, 198 insertions(+), 13 deletions(-)


diff --git a/Doc/library/os.rst b/Doc/library/os.rst
--- a/Doc/library/os.rst
+++ b/Doc/library/os.rst
@@ -3931,21 +3931,44 @@
 
    .. versionadded:: 3.3
 
-.. _os-miscfunc:
-
-Miscellaneous Functions
------------------------
-
-
-.. function:: urandom(n)
-
-   Return a string of *n* random bytes suitable for cryptographic use.
+
+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(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 @@
    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
--- a/Doc/whatsnew/3.6.rst
+++ b/Doc/whatsnew/3.6.rst
@@ -484,6 +484,10 @@
 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
--- a/Lib/test/test_os.py
+++ b/Lib/test/test_os.py
@@ -1248,6 +1248,7 @@
 
     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 @@
         self.assertNotEqual(data1, data2)
 
 
+ at 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
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -89,6 +89,9 @@
 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
--- a/Modules/clinic/posixmodule.c.h
+++ b/Modules/clinic/posixmodule.c.h
@@ -5571,6 +5571,41 @@
     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 @@
 #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
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -133,6 +133,13 @@
 #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 @@
     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 @@
                         METH_VARARGS | METH_KEYWORDS,
                         posix_scandir__doc__},
     OS_FSPATH_METHODDEF
+    OS_GETRANDOM_METHODDEF
     {NULL,              NULL}            /* Sentinel */
 };
 
@@ -13066,6 +13127,11 @@
     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;
 }
 

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list