Skip to content

Commit 57057a6

Browse files
committed
PEP 466: backport persistent urandom fd (closes #21305)
Patch from Alex Gaynor.
1 parent 0062d1e commit 57057a6

File tree

4 files changed

+83
-16
lines changed

4 files changed

+83
-16
lines changed

Include/pythonrun.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ PyAPI_FUNC(void) PyInt_Fini(void);
145145
PyAPI_FUNC(void) PyFloat_Fini(void);
146146
PyAPI_FUNC(void) PyOS_FiniInterrupts(void);
147147
PyAPI_FUNC(void) PyByteArray_Fini(void);
148+
PyAPI_FUNC(void) _PyRandom_Fini(void);
148149

149150
PyAPI_DATA(PyThreadState *) _Py_Finalizing;
150151

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ Core and Builtins
1919
Library
2020
-------
2121

22+
- Issue #21305: os.urandom now caches a fd to /dev/urandom. This is a PEP 466
23+
backport from Python 3.
24+
2225
- Issue #21307: As part of PEP 466, backport hashlib.algorithms_guaranteed and
2326
hashlib.algorithms_available.
2427

Python/pythonrun.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,7 @@ Py_Finalize(void)
536536
PyInt_Fini();
537537
PyFloat_Fini();
538538
PyDict_Fini();
539+
_PyRandom_Fini();
539540

540541
#ifdef Py_USING_UNICODE
541542
/* Cleanup Unicode implementation */

Python/random.c

Lines changed: 78 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,16 @@ vms_urandom(unsigned char *buffer, Py_ssize_t size, int raise)
118118

119119
#if !defined(MS_WINDOWS) && !defined(__VMS)
120120

121+
static struct {
122+
int fd;
123+
dev_t st_dev;
124+
ino_t st_ino;
125+
} urandom_cache = { -1 };
126+
121127
/* Read size bytes from /dev/urandom into buffer.
122128
Call Py_FatalError() on error. */
123129
static void
124-
dev_urandom_noraise(char *buffer, Py_ssize_t size)
130+
dev_urandom_noraise(unsigned char *buffer, Py_ssize_t size)
125131
{
126132
int fd;
127133
Py_ssize_t n;
@@ -156,22 +162,56 @@ dev_urandom_python(char *buffer, Py_ssize_t size)
156162
{
157163
int fd;
158164
Py_ssize_t n;
165+
struct stat st;
159166

160167
if (size <= 0)
161168
return 0;
162169

163-
Py_BEGIN_ALLOW_THREADS
164-
fd = open("/dev/urandom", O_RDONLY);
165-
Py_END_ALLOW_THREADS
166-
if (fd < 0)
167-
{
168-
if (errno == ENOENT || errno == ENXIO ||
169-
errno == ENODEV || errno == EACCES)
170-
PyErr_SetString(PyExc_NotImplementedError,
171-
"/dev/urandom (or equivalent) not found");
172-
else
173-
PyErr_SetFromErrno(PyExc_OSError);
174-
return -1;
170+
if (urandom_cache.fd >= 0) {
171+
/* Does the fd point to the same thing as before? (issue #21207) */
172+
if (fstat(urandom_cache.fd, &st)
173+
|| st.st_dev != urandom_cache.st_dev
174+
|| st.st_ino != urandom_cache.st_ino) {
175+
/* Something changed: forget the cached fd (but don't close it,
176+
since it probably points to something important for some
177+
third-party code). */
178+
urandom_cache.fd = -1;
179+
}
180+
}
181+
if (urandom_cache.fd >= 0)
182+
fd = urandom_cache.fd;
183+
else {
184+
Py_BEGIN_ALLOW_THREADS
185+
fd = open("/dev/urandom", O_RDONLY);
186+
Py_END_ALLOW_THREADS
187+
if (fd < 0)
188+
{
189+
if (errno == ENOENT || errno == ENXIO ||
190+
errno == ENODEV || errno == EACCES)
191+
PyErr_SetString(PyExc_NotImplementedError,
192+
"/dev/urandom (or equivalent) not found");
193+
else
194+
PyErr_SetFromErrno(PyExc_OSError);
195+
return -1;
196+
}
197+
if (urandom_cache.fd >= 0) {
198+
/* urandom_fd was initialized by another thread while we were
199+
not holding the GIL, keep it. */
200+
close(fd);
201+
fd = urandom_cache.fd;
202+
}
203+
else {
204+
if (fstat(fd, &st)) {
205+
PyErr_SetFromErrno(PyExc_OSError);
206+
close(fd);
207+
return -1;
208+
}
209+
else {
210+
urandom_cache.fd = fd;
211+
urandom_cache.st_dev = st.st_dev;
212+
urandom_cache.st_ino = st.st_ino;
213+
}
214+
}
175215
}
176216

177217
Py_BEGIN_ALLOW_THREADS
@@ -195,12 +235,21 @@ dev_urandom_python(char *buffer, Py_ssize_t size)
195235
PyErr_Format(PyExc_RuntimeError,
196236
"Failed to read %zi bytes from /dev/urandom",
197237
size);
198-
close(fd);
199238
return -1;
200239
}
201-
close(fd);
202240
return 0;
203241
}
242+
243+
static void
244+
dev_urandom_close(void)
245+
{
246+
if (urandom_cache.fd >= 0) {
247+
close(urandom_cache.fd);
248+
urandom_cache.fd = -1;
249+
}
250+
}
251+
252+
204253
#endif /* !defined(MS_WINDOWS) && !defined(__VMS) */
205254

206255
/* Fill buffer with pseudo-random bytes generated by a linear congruent
@@ -305,8 +354,21 @@ _PyRandom_Init(void)
305354
# ifdef __VMS
306355
vms_urandom((unsigned char *)secret, secret_size, 0);
307356
# else
308-
dev_urandom_noraise((char*)secret, secret_size);
357+
dev_urandom_noraise((unsigned char*)secret, secret_size);
309358
# endif
310359
#endif
311360
}
312361
}
362+
363+
void
364+
_PyRandom_Fini(void)
365+
{
366+
#ifdef MS_WINDOWS
367+
if (hCryptProv) {
368+
CryptReleaseContext(hCryptProv, 0);
369+
hCryptProv = 0;
370+
}
371+
#else
372+
dev_urandom_close();
373+
#endif
374+
}

0 commit comments

Comments
 (0)