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

Delta Between Two Patch Sets: Python/random.c

Issue 18756: os.urandom() fails under high load
Left Patch Set: Created 6 years, 2 months ago
Right Patch Set: Created 6 years, 1 month ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« Lib/test/test_cmd_line.py ('K') | « Python/pythonrun.c ('k') | no next file » | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
1 #include "Python.h" 1 #include "Python.h"
2 #ifdef MS_WINDOWS 2 #ifdef MS_WINDOWS
3 #include <windows.h> 3 #include <windows.h>
4 #else 4 #else
5 #include <fcntl.h> 5 #include <fcntl.h>
6 #endif 6 #endif
7 7
8 #ifdef Py_DEBUG 8 #ifdef Py_DEBUG
9 int _Py_HashSecret_Initialized = 0; 9 int _Py_HashSecret_Initialized = 0;
10 #else 10 #else
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after
84 } 84 }
85 return -1; 85 return -1;
86 } 86 }
87 return 0; 87 return 0;
88 } 88 }
89 #endif /* __VMS */ 89 #endif /* __VMS */
90 90
91 91
92 #if !defined(MS_WINDOWS) && !defined(__VMS) 92 #if !defined(MS_WINDOWS) && !defined(__VMS)
93 static int urandom_fd = -1; 93 static int urandom_fd = -1;
94 static int urandom_errno = 0;
94 95
95 /* Read size bytes from /dev/urandom into buffer. 96 /* Read size bytes from /dev/urandom into buffer.
96 Call Py_FatalError() on error. */ 97 Call Py_FatalError() on error. */
97 static void 98 static void
98 dev_urandom_noraise(char *buffer, Py_ssize_t size) 99 dev_urandom_noraise(char *buffer, Py_ssize_t size)
99 { 100 {
100 int fd; 101 int fd = urandom_fd;
101 Py_ssize_t n; 102 Py_ssize_t n;
102 103
103 assert (0 < size);
104
105 fd = open("/dev/urandom", O_RDONLY);
106 if (fd < 0) 104 if (fd < 0)
107 Py_FatalError("Failed to open /dev/urandom"); 105 Py_FatalError("Failed to open /dev/urandom");
106 assert (0 < size);
108 107
109 while (0 < size) 108 while (0 < size)
110 { 109 {
111 do { 110 do {
112 n = read(fd, buffer, (size_t)size); 111 n = read(fd, buffer, (size_t)size);
113 } while (n < 0 && errno == EINTR); 112 } while (n < 0 && errno == EINTR);
114 if (n <= 0) 113 if (n <= 0)
115 { 114 {
116 /* stop on error or if read(size) returned 0 */ 115 /* stop on error or if read(size) returned 0 */
117 Py_FatalError("Failed to read bytes from /dev/urandom"); 116 Py_FatalError("Failed to read bytes from /dev/urandom");
118 break; 117 break;
119 } 118 }
120 buffer += n; 119 buffer += n;
121 size -= (Py_ssize_t)n; 120 size -= (Py_ssize_t)n;
122 } 121 }
123 close(fd);
124 } 122 }
125 123
126 /* Read size bytes from /dev/urandom into buffer. 124 /* Read size bytes from /dev/urandom into buffer.
127 Return 0 on success, raise an exception and return -1 on error. */ 125 Return 0 on success, raise an exception and return -1 on error. */
128 static int 126 static int
129 dev_urandom_python(char *buffer, Py_ssize_t size) 127 dev_urandom_python(char *buffer, Py_ssize_t size)
130 { 128 {
131 int fd; 129 int fd = urandom_fd;
132 Py_ssize_t n; 130 Py_ssize_t n;
133 131
134 if (size <= 0) 132 if (size <= 0)
135 return 0; 133 return 0;
136 134
137 if (urandom_fd >= 0) 135 if (fd < 0) {
138 fd = urandom_fd; 136 assert(urandom_errno != 0);
139 else { 137 errno = urandom_errno;
140 Py_BEGIN_ALLOW_THREADS 138 if (errno == ENOENT || errno == ENXIO ||
141 #ifdef O_CLOEXEC 139 errno == ENODEV || errno == EACCES)
142 fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC); 140 PyErr_SetString(PyExc_NotImplementedError,
143 #else 141 "/dev/urandom (or equivalent) not found");
144 fd = open("/dev/urandom", O_RDONLY);
145 #endif
146 Py_END_ALLOW_THREADS
147 if (fd < 0)
148 {
149 if (errno == ENOENT || errno == ENXIO ||
150 errno == ENODEV || errno == EACCES)
151 PyErr_SetString(PyExc_NotImplementedError,
152 "/dev/urandom (or equivalent) not found");
153 else
154 PyErr_SetFromErrno(PyExc_OSError);
155 return -1;
156 }
157 if (urandom_fd >= 0) {
158 /* urandom_fd was initialized by another thread while we were
159 not holding the GIL, keep it. */
160 close(fd);
161 fd = urandom_fd;
162 }
163 else 142 else
164 urandom_fd = fd; 143 PyErr_SetFromErrno(PyExc_OSError);
144 return -1;
165 } 145 }
166 146
167 Py_BEGIN_ALLOW_THREADS 147 Py_BEGIN_ALLOW_THREADS
168 do { 148 do {
169 do { 149 do {
170 n = read(fd, buffer, (size_t)size); 150 n = read(fd, buffer, (size_t)size);
171 } while (n < 0 && errno == EINTR); 151 } while (n < 0 && errno == EINTR);
172 if (n <= 0) 152 if (n <= 0)
173 break; 153 break;
174 buffer += n; 154 buffer += n;
175 size -= (Py_ssize_t)n; 155 size -= (Py_ssize_t)n;
176 } while (0 < size); 156 } while (0 < size);
177 Py_END_ALLOW_THREADS 157 Py_END_ALLOW_THREADS
178 158
179 if (n <= 0) 159 if (n <= 0)
180 { 160 {
181 /* stop on error or if read(size) returned 0 */ 161 /* stop on error or if read(size) returned 0 */
182 if (n < 0) { 162 if (n < 0) {
183 if (errno == EBADF || errno == EINVAL) {
184 /* fd has become somewhat invalid, close it */
185 close(fd);
186 if (fd == urandom_fd)
187 urandom_fd = -1;
188 }
189 PyErr_SetFromErrno(PyExc_OSError); 163 PyErr_SetFromErrno(PyExc_OSError);
190 } 164 }
191 else 165 else
192 PyErr_Format(PyExc_RuntimeError, 166 PyErr_Format(PyExc_RuntimeError,
193 "Failed to read %zi bytes from /dev/urandom", 167 "Failed to read %zi bytes from /dev/urandom",
194 size); 168 size);
195 return -1; 169 return -1;
196 } 170 }
197 return 0; 171 return 0;
172 }
173
174 static void
175 dev_urandom_init(void)
176 {
177 assert(urandom_fd < 0);
178 #ifdef O_CLOEXEC
179 urandom_fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC);
180 #else
181 urandom_fd = open("/dev/urandom", O_RDONLY);
182 #endif
183 if (urandom_fd < 0)
184 goto error;
185 else {
Benjamin Peterson 2013/08/24 21:54:05 Kill this else.
186 urandom_errno = 0;
187 if (urandom_fd <= 2) {
188 /* Ok, Python was launched with at least a missing standard
189 stream, and our random fd took its place. Unfortunately,
190 now other parts of the interpreter (as well as, possibly,
191 some user code) will mistake the random fd for a proper
192 standard stream. So we'll try to reallocate the fd to
193 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
194 int i = 0, dups[3], new_fd;
195 errno = 0;
196 while (1) {
197 new_fd = dup(urandom_fd);
198 if (new_fd < 0 || new_fd > 2)
199 break;
200 /* On the third dup() we *must* get either an error or
201 something greater than 2. */
202 assert(i < 2);
203 dups[i++] = new_fd;
204 }
205 assert(i < 3);
206 close(urandom_fd);
207 while (--i >= 0)
208 close(dups[i]);
209 if (new_fd < 0)
210 goto error;
211 urandom_fd = new_fd;
212 }
213 }
214 return;
215 error:
216 /* If something failed, we don't report an error here, as it must still
217 be possible to launch Python if PYTHONHASHSEED is set to some fixed
218 value. Therefore, just record the error status for later. */
219 urandom_fd = -1;
220 urandom_errno = errno;
198 } 221 }
199 222
200 static void 223 static void
201 dev_urandom_close(void) 224 dev_urandom_close(void)
202 { 225 {
203 if (urandom_fd >= 0) { 226 if (urandom_fd >= 0) {
204 close(urandom_fd); 227 close(urandom_fd);
205 urandom_fd = -1; 228 urandom_fd = -1;
206 } 229 }
207 } 230 }
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
255 #endif 278 #endif
256 } 279 }
257 280
258 void 281 void
259 _PyRandom_Init(void) 282 _PyRandom_Init(void)
260 { 283 {
261 char *env; 284 char *env;
262 void *secret = &_Py_HashSecret; 285 void *secret = &_Py_HashSecret;
263 Py_ssize_t secret_size = sizeof(_Py_HashSecret_t); 286 Py_ssize_t secret_size = sizeof(_Py_HashSecret_t);
264 287
288 /* _PyRandom_Init() can be called twice (first by Python's main(),
289 second by Py_Initialize()). */
290
265 if (_Py_HashSecret_Initialized) 291 if (_Py_HashSecret_Initialized)
266 return; 292 return;
267 _Py_HashSecret_Initialized = 1; 293 _Py_HashSecret_Initialized = 1;
294
295 #if !defined(MS_WINDOWS) && !defined(__VMS)
296 dev_urandom_init();
297 #endif
268 298
269 /* 299 /*
270 Hash randomization is enabled. Generate a per-process secret, 300 Hash randomization is enabled. Generate a per-process secret,
271 using PYTHONHASHSEED if provided. 301 using PYTHONHASHSEED if provided.
272 */ 302 */
273 303
274 env = Py_GETENV("PYTHONHASHSEED"); 304 env = Py_GETENV("PYTHONHASHSEED");
275 if (env && *env != '\0' && strcmp(env, "random") != 0) { 305 if (env && *env != '\0' && strcmp(env, "random") != 0) {
276 char *endptr = env; 306 char *endptr = env;
277 unsigned long seed; 307 unsigned long seed;
(...skipping 26 matching lines...) Expand all
304 } 334 }
305 } 335 }
306 336
307 void 337 void
308 _PyRandom_Fini(void) 338 _PyRandom_Fini(void)
309 { 339 {
310 #if !defined(MS_WINDOWS) && !defined(__VMS) 340 #if !defined(MS_WINDOWS) && !defined(__VMS)
311 dev_urandom_close(); 341 dev_urandom_close();
312 #endif 342 #endif
313 } 343 }
LEFTRIGHT

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