Issue #27776: Cleanup random.c

* Add pyurandom() helper function to factorize the code
* don't call Py_FatalError() in helper functions, but only in _PyRandom_Init()
  if pyurandom() failed, to uniformize the code
This commit is contained in:
Victor Stinner 2016-08-16 15:23:58 +02:00
parent c35a32fe85
commit 4bad3b622e

View file

@ -39,10 +39,9 @@ win32_urandom_init(int raise)
return 0; return 0;
error: error:
if (raise) if (raise) {
PyErr_SetFromWindowsErr(0); PyErr_SetFromWindowsErr(0);
else }
Py_FatalError("Failed to initialize Windows random API (CryptoGen)");
return -1; return -1;
} }
@ -55,8 +54,9 @@ win32_urandom(unsigned char *buffer, Py_ssize_t size, int raise)
if (hCryptProv == 0) if (hCryptProv == 0)
{ {
if (win32_urandom_init(raise) == -1) if (win32_urandom_init(raise) == -1) {
return -1; return -1;
}
} }
while (size > 0) while (size > 0)
@ -65,11 +65,9 @@ win32_urandom(unsigned char *buffer, Py_ssize_t size, int raise)
if (!CryptGenRandom(hCryptProv, (DWORD)chunk, buffer)) if (!CryptGenRandom(hCryptProv, (DWORD)chunk, buffer))
{ {
/* CryptGenRandom() failed */ /* CryptGenRandom() failed */
if (raise) if (raise) {
PyErr_SetFromWindowsErr(0); PyErr_SetFromWindowsErr(0);
else }
Py_FatalError("Failed to initialized the randomized hash "
"secret using CryptoGen)");
return -1; return -1;
} }
buffer += chunk; buffer += chunk;
@ -86,29 +84,28 @@ win32_urandom(unsigned char *buffer, Py_ssize_t size, int raise)
/* Fill buffer with size pseudo-random bytes generated by getentropy(). /* Fill buffer with size pseudo-random bytes generated by getentropy().
Return 0 on success, or raise an exception and return -1 on error. Return 0 on success, or raise an exception and return -1 on error.
If fatal is nonzero, call Py_FatalError() instead of raising an exception If raise is zero, don't raise an exception on error. */
on error. */
static int static int
py_getentropy(unsigned char *buffer, Py_ssize_t size, int fatal) py_getentropy(char *buffer, Py_ssize_t size, int raise)
{ {
while (size > 0) { while (size > 0) {
Py_ssize_t len = Py_MIN(size, 256); Py_ssize_t len = Py_MIN(size, 256);
int res; int res;
if (!fatal) { if (raise) {
Py_BEGIN_ALLOW_THREADS Py_BEGIN_ALLOW_THREADS
res = getentropy(buffer, len); res = getentropy(buffer, len);
Py_END_ALLOW_THREADS Py_END_ALLOW_THREADS
if (res < 0) {
PyErr_SetFromErrno(PyExc_OSError);
return -1;
}
} }
else { else {
res = getentropy(buffer, len); res = getentropy(buffer, len);
if (res < 0) }
Py_FatalError("getentropy() failed");
if (res < 0) {
if (raise) {
PyErr_SetFromErrno(PyExc_OSError);
}
return -1;
} }
buffer += len; buffer += len;
@ -195,18 +192,15 @@ py_getrandom(void *buffer, Py_ssize_t size, int raise)
if (errno == EINTR) { if (errno == EINTR) {
if (PyErr_CheckSignals()) { if (PyErr_CheckSignals()) {
if (!raise)
Py_FatalError("getrandom() interrupted by a signal");
return -1; return -1;
} }
/* retry getrandom() */ /* retry getrandom() */
continue; continue;
} }
if (raise) if (raise) {
PyErr_SetFromErrno(PyExc_OSError); PyErr_SetFromErrno(PyExc_OSError);
else }
Py_FatalError("getrandom() failed");
return -1; return -1;
} }
@ -225,9 +219,9 @@ static struct {
/* Read size bytes from /dev/urandom into buffer. /* Read size bytes from /dev/urandom into buffer.
Call Py_FatalError() on error. */ Return 0 success, or return -1 on error. */
static void static int
dev_urandom_noraise(unsigned char *buffer, Py_ssize_t size) dev_urandom_noraise(char *buffer, Py_ssize_t size)
{ {
int fd; int fd;
Py_ssize_t n; Py_ssize_t n;
@ -235,31 +229,35 @@ dev_urandom_noraise(unsigned char *buffer, Py_ssize_t size)
assert (0 < size); assert (0 < size);
#ifdef PY_GETRANDOM #ifdef PY_GETRANDOM
if (py_getrandom(buffer, size, 0) == 1) if (py_getrandom(buffer, size, 0) == 1) {
return; return 0;
}
/* getrandom() is not supported by the running kernel, fall back /* getrandom() is not supported by the running kernel, fall back
* on reading /dev/urandom */ * on reading /dev/urandom */
#endif #endif
fd = _Py_open_noraise("/dev/urandom", O_RDONLY); fd = _Py_open_noraise("/dev/urandom", O_RDONLY);
if (fd < 0) if (fd < 0) {
Py_FatalError("Failed to open /dev/urandom"); return -1;
}
while (0 < size) while (0 < size)
{ {
do { do {
n = read(fd, buffer, (size_t)size); n = read(fd, buffer, (size_t)size);
} while (n < 0 && errno == EINTR); } while (n < 0 && errno == EINTR);
if (n <= 0)
{ if (n <= 0) {
/* stop on error or if read(size) returned 0 */ /* stop on error or if read(size) returned 0 */
Py_FatalError("Failed to read bytes from /dev/urandom"); return -1;
break;
} }
buffer += n; buffer += n;
size -= n; size -= n;
} }
close(fd); close(fd);
return 0;
} }
/* Read size bytes from /dev/urandom into buffer. /* Read size bytes from /dev/urandom into buffer.
@ -379,6 +377,40 @@ lcg_urandom(unsigned int x0, unsigned char *buffer, size_t size)
} }
} }
/* If raise is zero:
* - Don't raise exceptions on error
* - Don't call PyErr_CheckSignals() on EINTR (retry directly the interrupted
* syscall)
* - Don't release the GIL to call syscalls. */
static int
pyurandom(void *buffer, Py_ssize_t size, int raise)
{
if (size < 0) {
if (raise) {
PyErr_Format(PyExc_ValueError,
"negative argument not allowed");
}
return -1;
}
if (size == 0) {
return 0;
}
#ifdef MS_WINDOWS
return win32_urandom((unsigned char *)buffer, size, raise);
#elif defined(PY_GETENTROPY)
return py_getentropy(buffer, size, raise);
#else
if (raise) {
return dev_urandom_python(buffer, size);
}
else {
return dev_urandom_noraise(buffer, size);
}
#endif
}
/* Fill buffer with size pseudo-random bytes from the operating system random /* Fill buffer with size pseudo-random bytes from the operating system random
number generator (RNG). It is suitable for most cryptographic purposes number generator (RNG). It is suitable for most cryptographic purposes
except long living private keys for asymmetric encryption. except long living private keys for asymmetric encryption.
@ -387,21 +419,7 @@ lcg_urandom(unsigned int x0, unsigned char *buffer, size_t size)
int int
_PyOS_URandom(void *buffer, Py_ssize_t size) _PyOS_URandom(void *buffer, Py_ssize_t size)
{ {
if (size < 0) { return pyurandom(buffer, size, 1);
PyErr_Format(PyExc_ValueError,
"negative argument not allowed");
return -1;
}
if (size == 0)
return 0;
#ifdef MS_WINDOWS
return win32_urandom((unsigned char *)buffer, size, 1);
#elif defined(PY_GETENTROPY)
return py_getentropy(buffer, size, 0);
#else
return dev_urandom_python((char*)buffer, size);
#endif
} }
void void
@ -442,13 +460,14 @@ _PyRandom_Init(void)
} }
} }
else { else {
#ifdef MS_WINDOWS int res;
(void)win32_urandom(secret, secret_size, 0);
#elif defined(PY_GETENTROPY) /* _PyRandom_Init() is called very early in the Python initialization
(void)py_getentropy(secret, secret_size, 1); * and so exceptions cannot be used. */
#else res = pyurandom(secret, secret_size, 0);
dev_urandom_noraise(secret, secret_size); if (res < 0) {
#endif Py_FatalError("failed to get random numbers to initialize Python");
}
} }
} }