Unverified Commit bf0c71dd authored by Thomas Haller's avatar Thomas Haller
Browse files

glib-aux: merge branch 'th/random-utils'

!1323

(cherry picked from commit cf141f3e)
parents 8e748944 b4bc5e62
Pipeline #657047 passed with stages
in 27 minutes and 53 seconds
......@@ -1286,7 +1286,7 @@ out_fail:
duid,
duid_error);
 
nm_utils_random_bytes(&uuid, sizeof(uuid));
nm_random_get_bytes(&uuid, sizeof(uuid));
duid_out = nm_utils_generate_duid_uuid(&uuid);
}
 
......@@ -2022,7 +2022,7 @@ out_fail:
fail_reason);
client_id_buf = g_malloc(1 + 15);
client_id_buf[0] = 0;
nm_utils_random_bytes(&client_id_buf[1], 15);
nm_random_get_bytes(&client_id_buf[1], 15);
result = g_bytes_new_take(client_id_buf, 1 + 15);
 
out_good:
......
......@@ -306,7 +306,7 @@ iwd_agent_export(GDBusConnection *connection, gpointer user_data, char **agent_p
unsigned int rnd;
guint id;
nm_utils_random_bytes(&rnd, sizeof(rnd));
nm_random_get_bytes(&rnd, sizeof(rnd));
nm_sprintf_buf(path, "/agent/%u", rnd);
......
......@@ -2815,7 +2815,10 @@ _host_id_read(guint8 **out_host_id, gsize *out_host_id_len)
int base64_save = 0;
gsize len;
success = nm_utils_random_bytes(rnd_buf, sizeof(rnd_buf));
if (nm_random_get_crypto_bytes(rnd_buf, sizeof(rnd_buf)) < 0)
nm_random_get_bytes_full(rnd_buf, sizeof(rnd_buf), &success);
else
success = TRUE;
/* Our key is really binary data. But since we anyway generate a random seed
* (with 32 random bytes), don't write it in binary, but instead create
......@@ -3313,7 +3316,7 @@ nm_utils_stable_id_random(void)
{
char buf[15];
nm_utils_random_bytes(buf, sizeof(buf));
nm_random_get_bytes(buf, sizeof(buf));
return g_base64_encode((guchar *) buf, sizeof(buf));
}
......@@ -3684,7 +3687,7 @@ nm_utils_hw_addr_gen_random_eth(const char *current_mac_address,
{
struct ether_addr bin_addr;
nm_utils_random_bytes(&bin_addr, ETH_ALEN);
nm_random_get_bytes(&bin_addr, ETH_ALEN);
_hw_addr_eth_complete(&bin_addr, current_mac_address, generate_mac_address_mask);
return nm_utils_hwaddr_ntoa(&bin_addr, ETH_ALEN);
}
......
......@@ -45,7 +45,7 @@ again:
guint8 _extra_entropy[3 * HASH_KEY_SIZE];
} t_arr;
nm_utils_random_bytes(&t_arr, sizeof(t_arr));
nm_random_get_bytes(&t_arr, sizeof(t_arr));
/* We only initialize one random hash key. So we can spend some effort
* of getting this right. For one, we collect more random bytes than
......
......@@ -10,6 +10,7 @@
#include <fcntl.h>
#include <sys/auxv.h>
#include <sys/syscall.h>
#include <poll.h>
#if USE_SYS_RANDOM_H
#include <sys/random.h>
......@@ -34,18 +35,121 @@
#define GRND_INSECURE 0x04
#endif
#if !HAVE_GETRANDOM && defined(SYS_getrandom)
static int
#if !HAVE_GETRANDOM
static ssize_t
getrandom(void *buf, size_t buflen, unsigned flags)
{
#if defined(SYS_getrandom)
return syscall(SYS_getrandom, buf, buflen, flags);
#else
errno = ENOSYS;
return -1;
#endif
}
#undef HAVE_GETRANDOM
#define HAVE_GETRANDOM 1
#endif
/*****************************************************************************/
static ssize_t
_getrandom(void *buf, size_t buflen, unsigned flags)
{
static int have_getrandom = TRUE;
ssize_t l;
int errsv;
nm_assert(buflen > 0);
/* This calls getrandom() and either returns the positive
* success or an negative errno. ENOSYS means getrandom()
* call is not supported. That result is cached and we don't retry. */
if (!have_getrandom)
return -ENOSYS;
l = getrandom(buf, buflen, flags);
if (l > 0)
return l;
if (l == 0)
return -EIO;
errsv = errno;
if (errsv == ENOSYS)
have_getrandom = FALSE;
return -errsv;
}
static ssize_t
_getrandom_insecure(void *buf, size_t buflen)
{
static int have_grnd_insecure = TRUE;
ssize_t l;
/* GRND_INSECURE was added recently. We catch EINVAL
* if kernel does not support the flag (and cache it). */
if (!have_grnd_insecure)
return -EINVAL;
l = _getrandom(buf, buflen, GRND_INSECURE);
if (l == -EINVAL)
have_grnd_insecure = FALSE;
return l;
}
static ssize_t
_getrandom_best_effort(void *buf, size_t buflen)
{
ssize_t l;
/* To get best-effort bytes, we would use GRND_INSECURE (and we try that
* first). However, not all kernel versions support that, so we fallback
* to GRND_NONBLOCK.
*
* Granted, this is called from a fallback path where we have no entropy
* already, it's unlikely that GRND_NONBLOCK would succeed. Still... */
l = _getrandom_insecure(buf, buflen);
if (l != -EINVAL)
return l;
return _getrandom(buf, buflen, GRND_NONBLOCK);
}
static int
_random_check_entropy(gboolean block)
{
static gboolean seen_high_quality = FALSE;
nm_auto_close int fd = -1;
int r;
/* We come here because getrandom() gave ENOSYS. We will fallback to /dev/urandom,
* but the caller wants to know whether we have high quality numbers. Poll
* /dev/random to find out. */
if (seen_high_quality) {
/* We cache the positive result. Once kernel has entropy, we will get
* good random numbers. */
return 1;
}
fd = open("/dev/random", O_RDONLY | O_CLOEXEC | O_NOCTTY);
if (fd < 0)
return -errno;
r = nm_utils_fd_wait_for_event(fd, POLLIN, block ? -1 : 0);
if (r <= 0) {
nm_assert(r < 0 || !block);
return r;
}
nm_assert(r == 1);
seen_high_quality = TRUE;
return 1;
}
/*****************************************************************************/
typedef struct _nm_packed {
uintptr_t heap_ptr;
uintptr_t stack_ptr;
......@@ -72,7 +176,8 @@ typedef struct _nm_packed {
guint8 u8[NM_UTILS_CHECKSUM_LENGTH_SHA256 / 2];
guint32 u32[((NM_UTILS_CHECKSUM_LENGTH_SHA256 / 2) + 3) / 4];
} rand_vals;
GRand *rand;
guint8 rand_vals_getrandom[16];
gint64 rand_vals_timestamp;
} BadRandState;
static void
......@@ -106,18 +211,7 @@ _bad_random_init_seed(BadRandSeed *seed)
memcpy(&seed->auxval, p_at_random, 16);
}
#if HAVE_GETRANDOM
{
ssize_t r;
/* This is likely to fail, because we already failed a moment earlier. Still, give
* it a try. */
r = getrandom(seed->getrandom_buf,
sizeof(seed->getrandom_buf),
GRND_INSECURE | GRND_NONBLOCK);
(void) r;
}
#endif
_getrandom_best_effort(seed->getrandom_buf, sizeof(seed->getrandom_buf));
seed->now_bootime = nm_utils_clock_gettime_nsec(CLOCK_BOOTTIME);
seed->now_real = g_get_real_time();
......@@ -168,6 +262,10 @@ _bad_random_bytes(guint8 *buf, gsize n)
nm_utils_checksum_get_digest(sum, gl_state.sha_digest.full);
}
_getrandom_best_effort(gl_state.rand_vals_getrandom, sizeof(gl_state.rand_vals_getrandom));
gl_state.rand_vals_timestamp = nm_utils_clock_gettime_nsec(CLOCK_BOOTTIME);
while (TRUE) {
int i;
......@@ -197,103 +295,155 @@ _bad_random_bytes(guint8 *buf, gsize n)
}
}
/*****************************************************************************/
/**
* nm_utils_random_bytes:
* nm_random_get_bytes_full:
* @p: the buffer to fill
* @n: the number of bytes to write to @p.
* @out_high_quality: (allow-none) (out): whether the returned
* random bytes are of high quality.
*
* Uses getrandom() or reads /dev/urandom to fill the buffer
* with random data. If all fails, as last fallback it uses
* GRand to fill the buffer with pseudo random numbers.
* The function always succeeds in writing some random numbers
* to the buffer. The return value of FALSE indicates that the
* obtained bytes are probably not of good randomness.
*
* Returns: whether the written bytes are good. If you
* don't require good randomness, you can ignore the return
* value.
*
* Note that if calling getrandom() fails because there is not enough
* entropy (at early boot), the function will read /dev/urandom.
* Which of course, still has low entropy, and cause kernel to log
* a warning.
* - will never block
* - will always produce some numbers, but they may not
* be of high quality.
* - Whether they are of high quality, you can know via @out_high_quality.
* - will always try hard to produce high quality numbers, and on success
* they are as good as nm_random_get_crypto_bytes().
*/
gboolean
nm_utils_random_bytes(void *p, size_t n)
void
nm_random_get_bytes_full(void *p, size_t n, gboolean *out_high_quality)
{
int fd;
int r;
gboolean has_high_quality = TRUE;
guint8 *buf = p;
gboolean has_high_quality;
ssize_t l;
g_return_val_if_fail(p, FALSE);
g_return_val_if_fail(n > 0, FALSE);
if (n == 0) {
NM_SET_OUT(out_high_quality, TRUE);
return;
}
#if HAVE_GETRANDOM
{
static gboolean have_syscall = TRUE;
if (have_syscall) {
ssize_t r2;
int errsv;
r2 = getrandom(buf, n, GRND_NONBLOCK);
if (r2 >= 0) {
if ((size_t) r2 == n)
return TRUE;
/* no or partial read. There is not enough entropy.
* Fill the rest reading with the fallback code and remember
* that some bits are not high quality. */
nm_assert((size_t) r2 < n);
buf += r2;
n -= r2;
/* At this point, we don't want to read /dev/urandom, because
* the entropy pool is low (early boot?), and asking for more
* entropy causes kernel messages to be logged.
*
* Note that we fall back to _bad_random_bytes(), which (among others) seeds
* itself with g_rand_new(). That also will read /dev/urandom, but as
* we do that only once, we don't care. But in general, we are here in
* a situation where we want to avoid reading /dev/urandom too much. */
goto out_bad_random;
}
errsv = errno;
if (errsv == ENOSYS) {
/* no support for getrandom(). We don't know whether
* we /dev/urandom will give us good quality. Assume yes. */
have_syscall = FALSE;
} else if (errsv == EAGAIN) {
/* No entropy. We avoid reading /dev/urandom. */
goto out_bad_random;
} else {
/* Unknown error, likely no entropy. We'll read /dev/urandom below, but we don't
* have high-quality randomness. */
has_high_quality = FALSE;
g_return_if_fail(p);
again_getrandom:
l = _getrandom(p, n, GRND_NONBLOCK);
if (l > 0) {
if ((size_t) l == n) {
NM_SET_OUT(out_high_quality, TRUE);
return;
}
p = ((uint8_t *) p) + l;
n -= l;
goto again_getrandom;
}
/* getrandom() failed. Fallback to read /dev/urandom. */
if (l == -ENOSYS) {
/* no support for getrandom(). */
if (out_high_quality) {
/* The caller wants to know whether we have high quality. Poll /dev/random
* to find out. */
has_high_quality = (_random_check_entropy(FALSE) > 0);
} else {
/* The value doesn't matter in this case. It will be unused. */
has_high_quality = FALSE;
}
} else {
/* Any other failure of getrandom() means we don't have high quality. */
has_high_quality = FALSE;
if (l == -EAGAIN) {
/* getrandom(GRND_NONBLOCK) failed because lack of entropy. Retry with GRND_INSECURE. */
for (;;) {
l = _getrandom_insecure(p, n);
if (l > 0) {
if ((size_t) l == n) {
NM_SET_OUT(out_high_quality, FALSE);
return;
}
p = ((uint8_t *) p) + l;
n -= l;
continue;
}
/* Any error. Fallback to /dev/urandom. */
break;
}
}
}
#endif
fd_open:
again_open:
fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOCTTY);
if (fd < 0) {
if (errno == EINTR)
goto fd_open;
goto out_bad_random;
goto again_open;
} else {
r = nm_utils_fd_read_loop_exact(fd, p, n, TRUE);
nm_close(fd);
if (r >= 0) {
NM_SET_OUT(out_high_quality, has_high_quality);
return;
}
}
r = nm_utils_fd_read_loop_exact(fd, buf, n, TRUE);
nm_close(fd);
if (r >= 0)
return has_high_quality;
out_bad_random:
/* we failed to fill the bytes reading from /dev/urandom.
* Fill the bits using our pseudo random numbers.
*
* We don't have good quality.
* Fill the bits using our fallback approach (which obviously
* cannot give high quality random).
*/
_bad_random_bytes(buf, n);
return FALSE;
_bad_random_bytes(p, n);
NM_SET_OUT(out_high_quality, FALSE);
}
/*****************************************************************************/
/**
* nm_random_get_crypto_bytes:
* @p: the buffer to fill
* @n: the number of bytes to fill
*
* - can fail (in which case a negative number is returned
* and the output buffer is undefined).
* - will block trying to get high quality random numbers.
*/
int
nm_random_get_crypto_bytes(void *p, size_t n)
{
nm_auto_close int fd = -1;
ssize_t l;
int r;
if (n == 0)
return 0;
nm_assert(p);
again_getrandom:
l = _getrandom(p, n, 0);
if (l > 0) {
if ((size_t) l == n)
return 0;
p = (uint8_t *) p + l;
n -= l;
goto again_getrandom;
}
if (l != -ENOSYS) {
/* We got a failure, but getrandom seems to be working in principle. We
* won't get good numbers. Fail. */
return l;
}
/* getrandom() failed with ENOSYS. Fallback to reading /dev/urandom. */
r = _random_check_entropy(TRUE);
if (r < 0)
return r;
if (r == 0)
return nm_assert_unreachable_val(-EIO);
fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOCTTY);
if (fd < 0)
return -errno;
return nm_utils_fd_read_loop_exact(fd, p, n, FALSE);
}
......@@ -6,6 +6,14 @@
#ifndef __NM_RANDOM_UTILS_H__
#define __NM_RANDOM_UTILS_H__
gboolean nm_utils_random_bytes(void *p, size_t n);
void nm_random_get_bytes_full(void *p, size_t n, gboolean *out_high_quality);
static inline void
nm_random_get_bytes(void *p, size_t n)
{
nm_random_get_bytes_full(p, n, NULL);
}
int nm_random_get_crypto_bytes(void *p, size_t n);
#endif /* __NM_RANDOM_UTILS_H__ */
......@@ -3347,6 +3347,8 @@ nm_utils_fd_wait_for_event(int fd, int event, gint64 timeout_nsec)
struct timespec ts, *pts;
int r;
nm_assert(fd >= 0);
if (timeout_nsec < 0)
pts = NULL;
else {
......@@ -3360,6 +3362,13 @@ nm_utils_fd_wait_for_event(int fd, int event, gint64 timeout_nsec)
return -NM_ERRNO_NATIVE(errno);
if (r == 0)
return 0;
nm_assert(r == 1);
nm_assert(pollfd.revents > 0);
if (pollfd.revents & POLLNVAL)
return nm_assert_unreachable_val(-EBADF);
return pollfd.revents;
}
......
......@@ -115,12 +115,12 @@ nm_uuid_generate_random(NMUuid *out_uuid)
/* See also, systemd's id128_make_v4_uuid() */
/* nm_utils_random_bytes() is supposed to try hard to give good
/* nm_random_get_bytes() is supposed to try hard to give good
* randomness. If it fails, it still makes an effort to fill
* random data into the buffer. There is not much we can do about
* that case, except making sure that it does not happen in the
* first place. */
nm_utils_random_bytes(out_uuid, sizeof(*out_uuid));
nm_random_get_bytes(out_uuid, sizeof(*out_uuid));
/* Set the four most significant bits (bits 12 through 15) of the
* time_hi_and_version field to the 4-bit version number from
......
......@@ -93,7 +93,7 @@ test_nmhash(void)
{
int rnd;
nm_utils_random_bytes(&rnd, sizeof(rnd));
nm_random_get_bytes(&rnd, sizeof(rnd));
g_assert(nm_hash_val(555, 4) != 0);
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment