NetworkManager/src/libnm-glib-aux/nm-hash-utils.c
Thomas Haller d20343c9d0
glib-aux: rework random number utils
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)
2022-08-05 19:29:34 +02:00

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);
}