Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(264837)

Unified Diff: Python/random.c

Issue 18756: os.urandom() fails under high load
Patch Set: Created 5 years, 12 months ago
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View side-by-side diff with in-line comments
Download patch
« Lib/test/test_cmd_line.py ('K') | « Python/pythonrun.c ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
--- a/Python/random.c
+++ b/Python/random.c
@@ -90,20 +90,20 @@ vms_urandom(unsigned char *buffer, Py_ss
#if !defined(MS_WINDOWS) && !defined(__VMS)
+static int urandom_fd = -1;
+static int urandom_errno = 0;
/* Read size bytes from /dev/urandom into buffer.
Call Py_FatalError() on error. */
static void
dev_urandom_noraise(char *buffer, Py_ssize_t size)
{
- int fd;
+ int fd = urandom_fd;
Py_ssize_t n;
- assert (0 < size);
-
- fd = open("/dev/urandom", O_RDONLY);
if (fd < 0)
Py_FatalError("Failed to open /dev/urandom");
+ assert (0 < size);
while (0 < size)
{
@@ -119,7 +119,6 @@ dev_urandom_noraise(char *buffer, Py_ssi
buffer += n;
size -= (Py_ssize_t)n;
}
- close(fd);
}
/* Read size bytes from /dev/urandom into buffer.
@@ -127,17 +126,15 @@ dev_urandom_noraise(char *buffer, Py_ssi
static int
dev_urandom_python(char *buffer, Py_ssize_t size)
{
- int fd;
+ int fd = urandom_fd;
Py_ssize_t n;
if (size <= 0)
return 0;
- Py_BEGIN_ALLOW_THREADS
- fd = open("/dev/urandom", O_RDONLY);
- Py_END_ALLOW_THREADS
- if (fd < 0)
- {
+ if (fd < 0) {
+ assert(urandom_errno != 0);
+ errno = urandom_errno;
if (errno == ENOENT || errno == ENXIO ||
errno == ENODEV || errno == EACCES)
PyErr_SetString(PyExc_NotImplementedError,
@@ -162,18 +159,76 @@ 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) {
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_init(void)
+{
+ assert(urandom_fd < 0);
+#ifdef O_CLOEXEC
+ urandom_fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC);
+#else
+ urandom_fd = open("/dev/urandom", O_RDONLY);
+#endif
+ if (urandom_fd < 0)
+ goto error;
+ else {
Benjamin Peterson 2013/08/24 21:54:05 Kill this else.
+ urandom_errno = 0;
+ if (urandom_fd <= 2) {
+ /* Ok, Python was launched with at least a missing standard
+ stream, and our random fd took its place. Unfortunately,
+ now other parts of the interpreter (as well as, possibly,
+ some user code) will mistake the random fd for a proper
+ standard stream. So we'll try to reallocate the fd to
+ something else. */
Charles-François Natali 2013/08/23 23:07:39 Holy crap :-) But AFAICT this issue isn't specifi
AntoinePitrou 2013/08/23 23:14:41 Hash randomization must be initialized before ever
+ int i = 0, dups[3], new_fd;
+ errno = 0;
+ while (1) {
+ new_fd = dup(urandom_fd);
+ if (new_fd < 0 || new_fd > 2)
+ break;
+ /* On the third dup() we *must* get either an error or
+ something greater than 2. */
+ assert(i < 2);
+ dups[i++] = new_fd;
+ }
+ assert(i < 3);
+ close(urandom_fd);
+ while (--i >= 0)
+ close(dups[i]);
+ if (new_fd < 0)
+ goto error;
+ urandom_fd = new_fd;
+ }
+ }
+ return;
+error:
+ /* If something failed, we don't report an error here, as it must still
+ be possible to launch Python if PYTHONHASHSEED is set to some fixed
+ value. Therefore, just record the error status for later. */
+ urandom_fd = -1;
+ urandom_errno = errno;
+}
+
+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
@@ -230,10 +285,17 @@ void
void *secret = &_Py_HashSecret;
Py_ssize_t secret_size = sizeof(_Py_HashSecret_t);
+ /* _PyRandom_Init() can be called twice (first by Python's main(),
+ second by Py_Initialize()). */
+
if (_Py_HashSecret_Initialized)
return;
_Py_HashSecret_Initialized = 1;
+#if !defined(MS_WINDOWS) && !defined(__VMS)
+ dev_urandom_init();
+#endif
+
/*
Hash randomization is enabled. Generate a per-process secret,
using PYTHONHASHSEED if provided.
@@ -271,3 +333,11 @@ void
#endif
}
}
+
+void
+_PyRandom_Fini(void)
+{
+#if !defined(MS_WINDOWS) && !defined(__VMS)
+ dev_urandom_close();
+#endif
+}
« Lib/test/test_cmd_line.py ('K') | « Python/pythonrun.c ('k') | no next file » | no next file with comments »

RSS Feeds Recent Issues | This issue
This is Rietveld 894c83f36cb7+