mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-05-21 20:08:10 +02:00
The (allow-none) annotation is deprecated since a long time now, it is better to use (nullable) and/or (optional) which clarifies what it means with the (out) annotation. https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/1551
501 lines
14 KiB
C
501 lines
14 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
/*
|
|
* Copyright (C) 2017 Red Hat, Inc.
|
|
*/
|
|
|
|
#include "libnm-glib-aux/nm-default-glib-i18n-lib.h"
|
|
|
|
#include "nm-random-utils.h"
|
|
|
|
#include <fcntl.h>
|
|
#include <sys/auxv.h>
|
|
#include <sys/syscall.h>
|
|
#include <poll.h>
|
|
|
|
#if USE_SYS_RANDOM_H
|
|
#include <sys/random.h>
|
|
#else
|
|
#include <linux/random.h>
|
|
#endif
|
|
|
|
#include "nm-shared-utils.h"
|
|
#include "nm-time-utils.h"
|
|
|
|
/*****************************************************************************/
|
|
|
|
#if !defined(SYS_getrandom) && defined(__NR_getrandom)
|
|
#define SYS_getrandom __NR_getrandom
|
|
#endif
|
|
|
|
#ifndef GRND_NONBLOCK
|
|
#define GRND_NONBLOCK 0x01
|
|
#endif
|
|
|
|
#ifndef GRND_INSECURE
|
|
#define GRND_INSECURE 0x04
|
|
#endif
|
|
|
|
#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
|
|
}
|
|
#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;
|
|
gint64 now_bootime;
|
|
gint64 now_real;
|
|
pid_t pid;
|
|
pid_t ppid;
|
|
pid_t tid;
|
|
guint32 grand[16];
|
|
guint8 auxval[16];
|
|
guint8 getrandom_buf[20];
|
|
} BadRandSeed;
|
|
|
|
typedef struct _nm_packed {
|
|
guint64 counter;
|
|
union {
|
|
guint8 full[NM_UTILS_CHECKSUM_LENGTH_SHA256];
|
|
struct {
|
|
guint8 half_1[NM_UTILS_CHECKSUM_LENGTH_SHA256 / 2];
|
|
guint8 half_2[NM_UTILS_CHECKSUM_LENGTH_SHA256 / 2];
|
|
};
|
|
} sha_digest;
|
|
union {
|
|
guint8 u8[NM_UTILS_CHECKSUM_LENGTH_SHA256 / 2];
|
|
guint32 u32[((NM_UTILS_CHECKSUM_LENGTH_SHA256 / 2) + 3) / 4];
|
|
} rand_vals;
|
|
guint8 rand_vals_getrandom[16];
|
|
gint64 rand_vals_timestamp;
|
|
} BadRandState;
|
|
|
|
static void
|
|
_bad_random_init_seed(BadRandSeed *seed)
|
|
{
|
|
const guint8 *p_at_random;
|
|
int seed_idx;
|
|
GRand *rand;
|
|
|
|
/* g_rand_new() reads /dev/urandom too, but we already know that
|
|
* /dev/urandom fails to give us good randomness (which is why
|
|
* we hit the "bad random" code path). So this may not be as
|
|
* good as we wish, but let's hope that it it does something smart
|
|
* to give some extra entropy... */
|
|
rand = g_rand_new();
|
|
|
|
/* Get some seed material from a GRand. */
|
|
for (seed_idx = 0; seed_idx < (int) G_N_ELEMENTS(seed->grand); seed_idx++)
|
|
seed->grand[seed_idx] = g_rand_int(rand);
|
|
|
|
/* Add an address from the heap and stack, maybe ASLR helps a bit? */
|
|
seed->heap_ptr = (uintptr_t) ((gpointer) rand);
|
|
seed->stack_ptr = (uintptr_t) ((gpointer) &rand);
|
|
|
|
g_rand_free(rand);
|
|
|
|
/* Add the per-process, random number. */
|
|
p_at_random = ((gpointer) getauxval(AT_RANDOM));
|
|
if (p_at_random) {
|
|
G_STATIC_ASSERT(sizeof(seed->auxval) == 16);
|
|
memcpy(&seed->auxval, p_at_random, 16);
|
|
}
|
|
|
|
_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();
|
|
seed->pid = getpid();
|
|
seed->ppid = getppid();
|
|
seed->tid = nm_utils_gettid();
|
|
}
|
|
|
|
static void
|
|
_bad_random_bytes(guint8 *buf, gsize n)
|
|
{
|
|
nm_auto_free_checksum GChecksum *sum = g_checksum_new(G_CHECKSUM_SHA256);
|
|
|
|
nm_assert(n > 0);
|
|
|
|
/* We are in the fallback code path, where getrandom() (and /dev/urandom) failed
|
|
* to give us good randomness. Try our best.
|
|
*
|
|
* Our ability to get entropy for the CPRNG is very limited and thus the overall
|
|
* result will be bad randomness.
|
|
*
|
|
* Once we have some seed material, we combine GRand (which is not a cryptographically
|
|
* secure PRNG) with some iterative sha256 hashing. It would be nice if we had
|
|
* easy access to chacha20, but it's probably more cumbersome to fork those
|
|
* implementations than hack a bad CPRNG by using sha256 hashing. After all, this
|
|
* is fallback code to get *some* bad randomness. And with the inability to get a good
|
|
* seed, any CPRNG can only give us bad randomness. */
|
|
|
|
{
|
|
static BadRandState gl_state;
|
|
static GRand *gl_rand;
|
|
static GMutex gl_mutex;
|
|
NM_G_MUTEX_LOCKED(&gl_mutex);
|
|
|
|
if (G_UNLIKELY(!gl_rand)) {
|
|
union {
|
|
BadRandSeed d_seed;
|
|
guint32 d_u32[(sizeof(BadRandSeed) + 3) / 4];
|
|
} data = {
|
|
.d_u32 = {0},
|
|
};
|
|
|
|
_bad_random_init_seed(&data.d_seed);
|
|
|
|
gl_rand = g_rand_new_with_seed_array(data.d_u32, G_N_ELEMENTS(data.d_u32));
|
|
|
|
g_checksum_update(sum, (const guchar *) &data, sizeof(data));
|
|
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;
|
|
|
|
gl_state.counter++;
|
|
for (i = 0; i < G_N_ELEMENTS(gl_state.rand_vals.u32); i++)
|
|
gl_state.rand_vals.u32[i] = g_rand_int(gl_rand);
|
|
g_checksum_reset(sum);
|
|
g_checksum_update(sum, (const guchar *) &gl_state, sizeof(gl_state));
|
|
nm_utils_checksum_get_digest(sum, gl_state.sha_digest.full);
|
|
|
|
/* gl_state.sha_digest.full and gl_state.rand_vals contain now our
|
|
* bad random values, but they are also the state for the next iteration.
|
|
* We must not directly expose that state to the caller, so XOR the values.
|
|
*
|
|
* That means, per iteration we can generate 16 bytes of bad randomness. That
|
|
* is suitable to initialize a random UUID. */
|
|
for (i = 0; i < (int) (NM_UTILS_CHECKSUM_LENGTH_SHA256 / 2); i++) {
|
|
nm_assert(n > 0);
|
|
buf[0] = gl_state.sha_digest.half_1[i] ^ gl_state.sha_digest.half_2[i]
|
|
^ gl_state.rand_vals.u8[i];
|
|
buf++;
|
|
n--;
|
|
if (n == 0)
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
* nm_random_get_bytes_full:
|
|
* @p: the buffer to fill
|
|
* @n: the number of bytes to write to @p.
|
|
* @out_high_quality: (out) (optional): whether the returned
|
|
* random bytes are of high quality.
|
|
*
|
|
* - 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().
|
|
*/
|
|
void
|
|
nm_random_get_bytes_full(void *p, size_t n, gboolean *out_high_quality)
|
|
{
|
|
int fd;
|
|
int r;
|
|
gboolean has_high_quality;
|
|
ssize_t l;
|
|
|
|
if (n == 0) {
|
|
NM_SET_OUT(out_high_quality, TRUE);
|
|
return;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
again_open:
|
|
fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOCTTY);
|
|
if (fd < 0) {
|
|
if (errno == EINTR)
|
|
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;
|
|
}
|
|
}
|
|
|
|
/* we failed to fill the bytes reading from /dev/urandom.
|
|
* Fill the bits using our fallback approach (which obviously
|
|
* cannot give high quality random).
|
|
*/
|
|
_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);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
guint64
|
|
nm_random_u64_range_full(guint64 begin, guint64 end, gboolean crypto_bytes)
|
|
{
|
|
gboolean bad_crypto_bytes = FALSE;
|
|
guint64 remainder;
|
|
guint64 maxvalue;
|
|
guint64 x;
|
|
guint64 m;
|
|
|
|
/* Returns a random #guint64 equally distributed in the range [@begin..@end-1].
|
|
*
|
|
* The function always set errno. It either sets it to zero or to EAGAIN
|
|
* (if crypto_bytes were requested but not obtained). In any case, the function
|
|
* will always return a random number in the requested range (worst case, it's
|
|
* not crypto_bytes despite being requested). Check errno if you care. */
|
|
|
|
if (begin >= end) {
|
|
/* systemd's random_u64_range(0) is an alias for nm_random_u64().
|
|
* Not for us. It's a caller error to request an element from an empty range. */
|
|
return nm_assert_unreachable_val(begin);
|
|
}
|
|
|
|
m = end - begin;
|
|
|
|
if (m == 1) {
|
|
x = 0;
|
|
goto out;
|
|
}
|
|
|
|
remainder = G_MAXUINT64 % m;
|
|
maxvalue = G_MAXUINT64 - remainder;
|
|
|
|
do
|
|
if (crypto_bytes) {
|
|
if (nm_random_get_crypto_bytes(&x, sizeof(x)) < 0) {
|
|
/* Cannot get good crypto numbers. We will try our best, but fail
|
|
* and set errno below. */
|
|
crypto_bytes = FALSE;
|
|
bad_crypto_bytes = TRUE;
|
|
continue;
|
|
}
|
|
} else
|
|
nm_random_get_bytes(&x, sizeof(x));
|
|
while (x >= maxvalue);
|
|
|
|
out:
|
|
errno = bad_crypto_bytes ? EAGAIN : 0;
|
|
return begin + (x % m);
|
|
}
|