diff --git a/Include/pythonrun.h b/Include/pythonrun.h --- a/Include/pythonrun.h +++ b/Include/pythonrun.h @@ -221,6 +221,7 @@ PyAPI_FUNC(void) _PyGC_DumpShutdownStats PyAPI_FUNC(void) _PyGC_Fini(void); PyAPI_FUNC(void) PySlice_Fini(void); PyAPI_FUNC(void) _PyType_Fini(void); +PyAPI_FUNC(void) _PyRandom_Fini(void); PyAPI_DATA(PyThreadState *) _Py_Finalizing; #endif 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 @@ -1017,17 +1017,24 @@ class URandomTests(unittest.TestCase): @unittest.skipUnless(resource, "test requires the resource module") def test_urandom_failure(self): - soft_limit, hard_limit = resource.getrlimit(resource.RLIMIT_NOFILE) - resource.setrlimit(resource.RLIMIT_NOFILE, (1, hard_limit)) - try: - with self.assertRaises(OSError) as cm: + # Spawn a new process to circumvent fd caching in Python/random.c + code = """if 1: + import errno + import os + import resource + + soft_limit, hard_limit = resource.getrlimit(resource.RLIMIT_NOFILE) + resource.setrlimit(resource.RLIMIT_NOFILE, (1, hard_limit)) + try: os.urandom(16) - self.assertEqual(cm.exception.errno, errno.EMFILE) - finally: - # We restore the old limit as soon as possible. If doing it - # using addCleanup(), code running in between would fail - # creating any file descriptor. - resource.setrlimit(resource.RLIMIT_NOFILE, (soft_limit, hard_limit)) + except OSError as e: + assert e.errno == errno.EMFILE, e.errno + else: + raise AssertionError("OSError not raised") + finally: + resource.setrlimit(resource.RLIMIT_NOFILE, (soft_limit, hard_limit)) + """ + assert_python_ok('-c', code) @contextlib.contextmanager diff --git a/Python/pythonrun.c b/Python/pythonrun.c --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -625,6 +625,7 @@ Py_Finalize(void) PyDict_Fini(); PySlice_Fini(); _PyGC_Fini(); + _PyRandom_Fini(); /* Cleanup Unicode implementation */ _PyUnicode_Fini(); diff --git a/Python/random.c b/Python/random.c --- a/Python/random.c +++ b/Python/random.c @@ -90,6 +90,7 @@ vms_urandom(unsigned char *buffer, Py_ss #if !defined(MS_WINDOWS) && !defined(__VMS) +static int urandom_fd = -1; /* Read size bytes from /dev/urandom into buffer. Call Py_FatalError() on error. */ @@ -133,18 +134,34 @@ dev_urandom_python(char *buffer, Py_ssiz if (size <= 0) return 0; - Py_BEGIN_ALLOW_THREADS - fd = open("/dev/urandom", O_RDONLY); - Py_END_ALLOW_THREADS - if (fd < 0) - { - if (errno == ENOENT || errno == ENXIO || - errno == ENODEV || errno == EACCES) - PyErr_SetString(PyExc_NotImplementedError, - "/dev/urandom (or equivalent) not found"); + if (urandom_fd >= 0) + fd = urandom_fd; + else { + Py_BEGIN_ALLOW_THREADS +#ifdef O_CLOEXEC + fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC); +#else + fd = open("/dev/urandom", O_RDONLY); +#endif + Py_END_ALLOW_THREADS + if (fd < 0) + { + if (errno == ENOENT || errno == ENXIO || + errno == ENODEV || errno == EACCES) + PyErr_SetString(PyExc_NotImplementedError, + "/dev/urandom (or equivalent) not found"); + else + PyErr_SetFromErrno(PyExc_OSError); + return -1; + } + if (urandom_fd >= 0) { + /* urandom_fd was initialized by another thread while we were + not holding the GIL, keep it. */ + close(fd); + fd = urandom_fd; + } else - PyErr_SetFromErrno(PyExc_OSError); - return -1; + urandom_fd = fd; } Py_BEGIN_ALLOW_THREADS @@ -162,18 +179,33 @@ dev_urandom_python(char *buffer, Py_ssiz if (n <= 0) { /* stop on error or if read(size) returned 0 */ - if (n < 0) + if (n < 0) { + if (errno == EBADF || errno == EINVAL) { + /* fd has become somewhat invalid, close it */ + close(fd); + if (fd == urandom_fd) + urandom_fd = -1; + } PyErr_SetFromErrno(PyExc_OSError); + } else PyErr_Format(PyExc_RuntimeError, "Failed to read %zi bytes from /dev/urandom", size); - close(fd); return -1; } - close(fd); return 0; } + +static void +dev_urandom_close(void) +{ + if (urandom_fd >= 0) { + close(urandom_fd); + urandom_fd = -1; + } +} + #endif /* !defined(MS_WINDOWS) && !defined(__VMS) */ /* Fill buffer with pseudo-random bytes generated by a linear congruent @@ -271,3 +303,11 @@ void #endif } } + +void +_PyRandom_Fini(void) +{ +#if !defined(MS_WINDOWS) && !defined(__VMS) + dev_urandom_close(); +#endif +}