mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-01-06 02:40:17 +01:00
glib-aux: merge branch 'th/random-utils'
https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/1323
This commit is contained in:
commit
cf141f3e7a
9 changed files with 270 additions and 121 deletions
|
|
@ -1290,7 +1290,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);
|
||||
}
|
||||
|
||||
|
|
@ -2073,7 +2073,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);
|
||||
|
||||
|
|
|
|||
|
|
@ -2814,7 +2814,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
|
||||
|
|
@ -3315,7 +3318,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));
|
||||
}
|
||||
|
||||
|
|
@ -3686,7 +3689,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,15 +35,118 @@
|
|||
#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);
|
||||
}
|
||||
#undef HAVE_GETRANDOM
|
||||
#define HAVE_GETRANDOM 1
|
||||
#else
|
||||
errno = ENOSYS;
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
#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;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
|
|
@ -72,11 +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;
|
||||
#if HAVE_GETRANDOM
|
||||
guint8 rand_vals_getrandom[16];
|
||||
#endif
|
||||
gint16 rand_vals_timestamp;
|
||||
GRand *rand;
|
||||
gint64 rand_vals_timestamp;
|
||||
} BadRandState;
|
||||
|
||||
static void
|
||||
|
|
@ -110,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();
|
||||
|
|
@ -172,18 +262,7 @@ _bad_random_bytes(guint8 *buf, gsize n)
|
|||
nm_utils_checksum_get_digest(sum, gl_state.sha_digest.full);
|
||||
}
|
||||
|
||||
#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(gl_state.rand_vals_getrandom,
|
||||
sizeof(gl_state.rand_vals_getrandom),
|
||||
GRND_INSECURE | GRND_NONBLOCK);
|
||||
(void) r;
|
||||
}
|
||||
#endif
|
||||
_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);
|
||||
|
||||
|
|
@ -216,105 +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;
|
||||
|
||||
if (n == 0)
|
||||
return TRUE;
|
||||
if (n == 0) {
|
||||
NM_SET_OUT(out_high_quality, TRUE);
|
||||
return;
|
||||
}
|
||||
|
||||
g_return_val_if_fail(p, FALSE);
|
||||
g_return_if_fail(p);
|
||||
|
||||
#if HAVE_GETRANDOM
|
||||
{
|
||||
static gboolean have_syscall = TRUE;
|
||||
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;
|
||||
}
|
||||
|
||||
if (have_syscall) {
|
||||
ssize_t r2;
|
||||
int errsv;
|
||||
/* getrandom() failed. Fallback to read /dev/urandom. */
|
||||
|
||||
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;
|
||||
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__ */
|
||||
|
|
|
|||
|
|
@ -3383,6 +3383,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 {
|
||||
|
|
@ -3396,6 +3398,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
|
||||
|
|
|
|||
|
|
@ -94,7 +94,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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue