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

Side by Side Diff: Python/random.c

Issue 18756: os.urandom() fails under high load
Patch Set: Created 5 years, 10 months 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:
View unified diff | Download patch
« Lib/test/test_cmd_line.py ('K') | « Python/pythonrun.c ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 72 matching lines...) Expand 10 before | Expand all | Expand 10 after
83 "secret using RAND_pseudo_bytes"); 83 "secret using RAND_pseudo_bytes");
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;
94 static int urandom_errno = 0;
93 95
94 /* Read size bytes from /dev/urandom into buffer. 96 /* Read size bytes from /dev/urandom into buffer.
95 Call Py_FatalError() on error. */ 97 Call Py_FatalError() on error. */
96 static void 98 static void
97 dev_urandom_noraise(char *buffer, Py_ssize_t size) 99 dev_urandom_noraise(char *buffer, Py_ssize_t size)
98 { 100 {
99 int fd; 101 int fd = urandom_fd;
100 Py_ssize_t n; 102 Py_ssize_t n;
101 103
102 assert (0 < size);
103
104 fd = open("/dev/urandom", O_RDONLY);
105 if (fd < 0) 104 if (fd < 0)
106 Py_FatalError("Failed to open /dev/urandom"); 105 Py_FatalError("Failed to open /dev/urandom");
106 assert (0 < size);
107 107
108 while (0 < size) 108 while (0 < size)
109 { 109 {
110 do { 110 do {
111 n = read(fd, buffer, (size_t)size); 111 n = read(fd, buffer, (size_t)size);
112 } while (n < 0 && errno == EINTR); 112 } while (n < 0 && errno == EINTR);
113 if (n <= 0) 113 if (n <= 0)
114 { 114 {
115 /* stop on error or if read(size) returned 0 */ 115 /* stop on error or if read(size) returned 0 */
116 Py_FatalError("Failed to read bytes from /dev/urandom"); 116 Py_FatalError("Failed to read bytes from /dev/urandom");
117 break; 117 break;
118 } 118 }
119 buffer += n; 119 buffer += n;
120 size -= (Py_ssize_t)n; 120 size -= (Py_ssize_t)n;
121 } 121 }
122 close(fd);
123 } 122 }
124 123
125 /* Read size bytes from /dev/urandom into buffer. 124 /* Read size bytes from /dev/urandom into buffer.
126 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. */
127 static int 126 static int
128 dev_urandom_python(char *buffer, Py_ssize_t size) 127 dev_urandom_python(char *buffer, Py_ssize_t size)
129 { 128 {
130 int fd; 129 int fd = urandom_fd;
131 Py_ssize_t n; 130 Py_ssize_t n;
132 131
133 if (size <= 0) 132 if (size <= 0)
134 return 0; 133 return 0;
135 134
136 Py_BEGIN_ALLOW_THREADS 135 if (fd < 0) {
137 fd = open("/dev/urandom", O_RDONLY); 136 assert(urandom_errno != 0);
138 Py_END_ALLOW_THREADS 137 errno = urandom_errno;
139 if (fd < 0)
140 {
141 if (errno == ENOENT || errno == ENXIO || 138 if (errno == ENOENT || errno == ENXIO ||
142 errno == ENODEV || errno == EACCES) 139 errno == ENODEV || errno == EACCES)
143 PyErr_SetString(PyExc_NotImplementedError, 140 PyErr_SetString(PyExc_NotImplementedError,
144 "/dev/urandom (or equivalent) not found"); 141 "/dev/urandom (or equivalent) not found");
145 else 142 else
146 PyErr_SetFromErrno(PyExc_OSError); 143 PyErr_SetFromErrno(PyExc_OSError);
147 return -1; 144 return -1;
148 } 145 }
149 146
150 Py_BEGIN_ALLOW_THREADS 147 Py_BEGIN_ALLOW_THREADS
151 do { 148 do {
152 do { 149 do {
153 n = read(fd, buffer, (size_t)size); 150 n = read(fd, buffer, (size_t)size);
154 } while (n < 0 && errno == EINTR); 151 } while (n < 0 && errno == EINTR);
155 if (n <= 0) 152 if (n <= 0)
156 break; 153 break;
157 buffer += n; 154 buffer += n;
158 size -= (Py_ssize_t)n; 155 size -= (Py_ssize_t)n;
159 } while (0 < size); 156 } while (0 < size);
160 Py_END_ALLOW_THREADS 157 Py_END_ALLOW_THREADS
161 158
162 if (n <= 0) 159 if (n <= 0)
163 { 160 {
164 /* stop on error or if read(size) returned 0 */ 161 /* stop on error or if read(size) returned 0 */
165 if (n < 0) 162 if (n < 0) {
166 PyErr_SetFromErrno(PyExc_OSError); 163 PyErr_SetFromErrno(PyExc_OSError);
164 }
167 else 165 else
168 PyErr_Format(PyExc_RuntimeError, 166 PyErr_Format(PyExc_RuntimeError,
169 "Failed to read %zi bytes from /dev/urandom", 167 "Failed to read %zi bytes from /dev/urandom",
170 size); 168 size);
171 close(fd);
172 return -1; 169 return -1;
173 } 170 }
174 close(fd);
175 return 0; 171 return 0;
176 } 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;
221 }
222
223 static void
224 dev_urandom_close(void)
225 {
226 if (urandom_fd >= 0) {
227 close(urandom_fd);
228 urandom_fd = -1;
229 }
230 }
231
177 #endif /* !defined(MS_WINDOWS) && !defined(__VMS) */ 232 #endif /* !defined(MS_WINDOWS) && !defined(__VMS) */
178 233
179 /* Fill buffer with pseudo-random bytes generated by a linear congruent 234 /* Fill buffer with pseudo-random bytes generated by a linear congruent
180 generator (LCG): 235 generator (LCG):
181 236
182 x(n+1) = (x(n) * 214013 + 2531011) % 2^32 237 x(n+1) = (x(n) * 214013 + 2531011) % 2^32
183 238
184 Use bits 23..16 of x(n) to generate a byte. */ 239 Use bits 23..16 of x(n) to generate a byte. */
185 static void 240 static void
186 lcg_urandom(unsigned int x0, unsigned char *buffer, size_t size) 241 lcg_urandom(unsigned int x0, unsigned char *buffer, size_t size)
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
222 # endif 277 # endif
223 #endif 278 #endif
224 } 279 }
225 280
226 void 281 void
227 _PyRandom_Init(void) 282 _PyRandom_Init(void)
228 { 283 {
229 char *env; 284 char *env;
230 void *secret = &_Py_HashSecret; 285 void *secret = &_Py_HashSecret;
231 Py_ssize_t secret_size = sizeof(_Py_HashSecret_t); 286 Py_ssize_t secret_size = sizeof(_Py_HashSecret_t);
287
288 /* _PyRandom_Init() can be called twice (first by Python's main(),
289 second by Py_Initialize()). */
232 290
233 if (_Py_HashSecret_Initialized) 291 if (_Py_HashSecret_Initialized)
234 return; 292 return;
235 _Py_HashSecret_Initialized = 1; 293 _Py_HashSecret_Initialized = 1;
236 294
295 #if !defined(MS_WINDOWS) && !defined(__VMS)
296 dev_urandom_init();
297 #endif
298
237 /* 299 /*
238 Hash randomization is enabled. Generate a per-process secret, 300 Hash randomization is enabled. Generate a per-process secret,
239 using PYTHONHASHSEED if provided. 301 using PYTHONHASHSEED if provided.
240 */ 302 */
241 303
242 env = Py_GETENV("PYTHONHASHSEED"); 304 env = Py_GETENV("PYTHONHASHSEED");
243 if (env && *env != '\0' && strcmp(env, "random") != 0) { 305 if (env && *env != '\0' && strcmp(env, "random") != 0) {
244 char *endptr = env; 306 char *endptr = env;
245 unsigned long seed; 307 unsigned long seed;
246 seed = strtoul(env, &endptr, 10); 308 seed = strtoul(env, &endptr, 10);
(...skipping 17 matching lines...) Expand all
264 (void)win32_urandom((unsigned char *)secret, secret_size, 0); 326 (void)win32_urandom((unsigned char *)secret, secret_size, 0);
265 #else /* #ifdef MS_WINDOWS */ 327 #else /* #ifdef MS_WINDOWS */
266 # ifdef __VMS 328 # ifdef __VMS
267 vms_urandom((unsigned char *)secret, secret_size, 0); 329 vms_urandom((unsigned char *)secret, secret_size, 0);
268 # else 330 # else
269 dev_urandom_noraise((char*)secret, secret_size); 331 dev_urandom_noraise((char*)secret, secret_size);
270 # endif 332 # endif
271 #endif 333 #endif
272 } 334 }
273 } 335 }
336
337 void
338 _PyRandom_Fini(void)
339 {
340 #if !defined(MS_WINDOWS) && !defined(__VMS)
341 dev_urandom_close();
342 #endif
343 }
OLDNEW
« 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+