mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-03-06 19:50:36 +01:00
Heavily inspired by systemd ([1]).
We now also have nm_random_get_bytes{,_full}() and
nm_random_get_crypto_bytes(), like systemd's random_bytes()
and crypto_random_bytes(), respectively.
Differences:
- instead of systemd's random_bytes(), our nm_random_get_bytes_full()
also estimates whether the output is of high quality. The caller
may find that interesting. Due to that, we will first try to call
getrandom(GRND_NONBLOCK) before getrandom(GRND_INSECURE). That is
reversed from systemd's random_bytes(), because we want to find
out whether we can get good random numbers. In most cases, kernel
should have entropy already, and it makes no difference.
Otherwise, heavily rework the code. It should be easy to understand
and correct.
There is also a major bugfix here. Previously, if getrandom() failed
with ENOSYS and we fell back to /dev/urandom, we would assume that we
have high quality random numbers. That assumption is not warranted.
Now instead poll on /dev/random to find out.
[1] a268e7f402/src/basic/random-util.c (L81)
302 lines
7.6 KiB
C
302 lines
7.6 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-hash-utils.h"
|
|
|
|
#include <stdint.h>
|
|
|
|
#include "nm-shared-utils.h"
|
|
#include "nm-random-utils.h"
|
|
|
|
/*****************************************************************************/
|
|
|
|
#define HASH_KEY_SIZE 16u
|
|
#define HASH_KEY_SIZE_GUINT ((HASH_KEY_SIZE + sizeof(guint) - 1) / sizeof(guint))
|
|
|
|
G_STATIC_ASSERT(sizeof(guint) * HASH_KEY_SIZE_GUINT >= HASH_KEY_SIZE);
|
|
|
|
static const guint8 *volatile global_seed = NULL;
|
|
|
|
static const guint8 *
|
|
_get_hash_key_init(void)
|
|
{
|
|
/* the returned hash is aligned to guin64, hence, it is safe
|
|
* to use it as guint* or guint64* pointer. */
|
|
static union {
|
|
guint8 v8[HASH_KEY_SIZE];
|
|
guint _align_as_uint;
|
|
guint32 _align_as_uint32;
|
|
guint64 _align_as_uint64;
|
|
} g_arr;
|
|
const guint8 *g;
|
|
|
|
again:
|
|
g = g_atomic_pointer_get(&global_seed);
|
|
if (!G_UNLIKELY(g)) {
|
|
static gsize g_lock;
|
|
uint64_t h;
|
|
union {
|
|
guint vuint;
|
|
guint8 v8[HASH_KEY_SIZE];
|
|
guint8 _extra_entropy[3 * HASH_KEY_SIZE];
|
|
} 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
|
|
* necessary.
|
|
*
|
|
* Then, the first guint of the seed should have all the entropy that we could
|
|
* obtain in sizeof(t_arr). For that, siphash(t_arr) and xor the first guint
|
|
* with hash.
|
|
* The first guint is especially interesting for nm_hash_static() below that
|
|
* doesn't use siphash itself. */
|
|
h = c_siphash_hash(t_arr.v8, (const guint8 *) &t_arr, sizeof(t_arr));
|
|
if (sizeof(h) > sizeof(guint))
|
|
t_arr.vuint = t_arr.vuint ^ ((guint) (h & G_MAXUINT)) ^ ((guint) (h >> 32));
|
|
else
|
|
t_arr.vuint = t_arr.vuint ^ ((guint) (h & G_MAXUINT));
|
|
|
|
if (!g_once_init_enter(&g_lock)) {
|
|
/* lost a race. The random key is already initialized. */
|
|
goto again;
|
|
}
|
|
|
|
memcpy(g_arr.v8, t_arr.v8, HASH_KEY_SIZE);
|
|
g = g_arr.v8;
|
|
g_atomic_pointer_set(&global_seed, g);
|
|
g_once_init_leave(&g_lock, 1);
|
|
}
|
|
|
|
nm_assert(g == g_arr.v8);
|
|
return g;
|
|
}
|
|
|
|
#define _get_hash_key() \
|
|
({ \
|
|
const guint8 *_g; \
|
|
\
|
|
_g = g_atomic_pointer_get(&global_seed); \
|
|
if (G_UNLIKELY(!_g)) \
|
|
_g = _get_hash_key_init(); \
|
|
_g; \
|
|
})
|
|
|
|
guint
|
|
nm_hash_static(guint static_seed)
|
|
{
|
|
/* Note that we only xor the static_seed with the first guint of the key.
|
|
*
|
|
* We don't use siphash, which would mix the bits better with _get_hash_key().
|
|
* Note that nm_hash_static() isn't used to hash the static_seed. Instead, it
|
|
* is used to get a unique hash value in a static context. That means, every
|
|
* caller is responsible to choose a static_seed that is sufficiently
|
|
* distinct from all other callers. In other words, static_seed should be a
|
|
* unique constant with good entropy.
|
|
*
|
|
* Note that _get_hash_key_init() already xored the first guint of the
|
|
* key with the siphash of the entire static key. That means, even if
|
|
* we got bad randomness for the first guint, the first guint is also
|
|
* mixed with the randomness of the entire random key.
|
|
*
|
|
* Also, ensure that we don't return zero (like for nm_hash_complete()).
|
|
*/
|
|
return ((*((const guint *) _get_hash_key())) ^ static_seed) ?: 3679500967u;
|
|
}
|
|
|
|
void
|
|
nm_hash_siphash42_init(CSipHash *h, guint static_seed)
|
|
{
|
|
const guint8 *g;
|
|
union {
|
|
guint64 _align_as_uint64;
|
|
guint arr[HASH_KEY_SIZE_GUINT];
|
|
} seed;
|
|
|
|
nm_assert(h);
|
|
|
|
g = _get_hash_key();
|
|
memcpy(&seed, g, HASH_KEY_SIZE);
|
|
seed.arr[0] ^= static_seed;
|
|
c_siphash_init(h, (const guint8 *) &seed);
|
|
}
|
|
|
|
guint
|
|
nm_hash_str(const char *str)
|
|
{
|
|
NMHashState h;
|
|
|
|
if (!str)
|
|
return nm_hash_static(1867854211u);
|
|
nm_hash_init(&h, 1867854211u);
|
|
nm_hash_update_str(&h, str);
|
|
return nm_hash_complete(&h);
|
|
}
|
|
|
|
guint
|
|
nm_hash_ptr(gconstpointer ptr)
|
|
{
|
|
NMHashState h;
|
|
|
|
if (!ptr)
|
|
return nm_hash_static(2907677551u);
|
|
nm_hash_init(&h, 2907677551u);
|
|
nm_hash_update(&h, &ptr, sizeof(ptr));
|
|
return nm_hash_complete(&h);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
guint
|
|
nm_pstr_hash(gconstpointer p)
|
|
{
|
|
const char *const *s = p;
|
|
|
|
if (!s)
|
|
return nm_hash_static(101061439u);
|
|
return nm_hash_str(*s);
|
|
}
|
|
|
|
gboolean
|
|
nm_pstr_equal(gconstpointer a, gconstpointer b)
|
|
{
|
|
const char *const *s1 = a;
|
|
const char *const *s2 = b;
|
|
|
|
return (s1 == s2) || (s1 && s2 && nm_streq0(*s1, *s2));
|
|
}
|
|
|
|
guint
|
|
nm_pint_hash(gconstpointer p)
|
|
{
|
|
const int *s = p;
|
|
|
|
if (!s)
|
|
return nm_hash_static(298377461u);
|
|
return nm_hash_val(1208815757u, *s);
|
|
}
|
|
|
|
gboolean
|
|
nm_pint_equal(gconstpointer a, gconstpointer b)
|
|
{
|
|
const int *s1 = a;
|
|
const int *s2 = a;
|
|
|
|
return s1 == s2 || (s1 && s2 && *s1 == *s2);
|
|
}
|
|
|
|
guint
|
|
nm_pdirect_hash(gconstpointer p)
|
|
{
|
|
const void *const *s = p;
|
|
|
|
if (!s)
|
|
return nm_hash_static(1852748873u);
|
|
return nm_direct_hash(*s);
|
|
}
|
|
|
|
gboolean
|
|
nm_pdirect_equal(gconstpointer a, gconstpointer b)
|
|
{
|
|
const void *const *s1 = a;
|
|
const void *const *s2 = b;
|
|
|
|
return (s1 == s2) || (s1 && s2 && *s1 == *s2);
|
|
}
|
|
|
|
guint
|
|
nm_ppdirect_hash(gconstpointer p)
|
|
{
|
|
const void *const *const *s = p;
|
|
|
|
if (!s)
|
|
return nm_hash_static(396534869u);
|
|
if (!*s)
|
|
return nm_hash_static(1476102263u);
|
|
return nm_direct_hash(**s);
|
|
}
|
|
|
|
gboolean
|
|
nm_ppdirect_equal(gconstpointer a, gconstpointer b)
|
|
{
|
|
const void *const *const *s1 = a;
|
|
const void *const *const *s2 = b;
|
|
|
|
if (s1 == s2)
|
|
return TRUE;
|
|
if (!s1 || !s2)
|
|
return FALSE;
|
|
|
|
if (*s1 == *s2)
|
|
return TRUE;
|
|
if (!*s1 || !*s2)
|
|
return FALSE;
|
|
|
|
return **s1 == **s2;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
guint
|
|
nm_g_bytes_hash(gconstpointer p)
|
|
{
|
|
GBytes *ptr = (GBytes *) p;
|
|
gconstpointer arr;
|
|
gsize len;
|
|
|
|
arr = g_bytes_get_data(ptr, &len);
|
|
return nm_hash_mem(792701303u, arr, len);
|
|
}
|
|
|
|
guint
|
|
nm_pg_bytes_hash(gconstpointer p)
|
|
{
|
|
GBytes *const *ptr = p;
|
|
gconstpointer arr;
|
|
gsize len;
|
|
|
|
arr = g_bytes_get_data(*ptr, &len);
|
|
return nm_hash_mem(1470631313u, arr, len);
|
|
}
|
|
|
|
gboolean
|
|
nm_pg_bytes_equal(gconstpointer a, gconstpointer b)
|
|
{
|
|
GBytes *const *ptr_a = a;
|
|
GBytes *const *ptr_b = b;
|
|
|
|
return g_bytes_equal(*ptr_a, *ptr_b);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
guint64
|
|
nm_hash_obfuscate_ptr(guint static_seed, gconstpointer val)
|
|
{
|
|
NMHashState h;
|
|
|
|
if (NM_MORE_ASSERTS > 0) {
|
|
static int obfuscate_static = -1;
|
|
int obfuscate;
|
|
|
|
again:
|
|
obfuscate = g_atomic_int_get(&obfuscate_static);
|
|
if (G_UNLIKELY(obfuscate == -1)) {
|
|
obfuscate = _nm_utils_ascii_str_to_int64(g_getenv("NM_OBFUSCATE_PTR"), 10, 0, 1, 1);
|
|
if (!g_atomic_int_compare_and_exchange(&obfuscate_static, -1, obfuscate))
|
|
goto again;
|
|
}
|
|
|
|
if (!obfuscate)
|
|
return (uintptr_t) val;
|
|
}
|
|
|
|
nm_hash_init(&h, static_seed);
|
|
nm_hash_update_val(&h, val);
|
|
return nm_hash_complete_u64(&h);
|
|
}
|