From b01a453ca29850cb70e519c2e254226a05c44ed2 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Thu, 12 Oct 2017 15:43:21 +0200 Subject: [PATCH] core: add nm_utils_random_bytes() and use getrandom() Add a new function nm_utils_random_bytes(). This function now preferably uses getrandom() syscall if it is available. As fallback, it always tries to fill the buffer from /dev/urandom. If it cannot, as last fallback it uses GRand, which cannot fail. Hence, the function always sets some (pseudo) random bytes. It also returns FALSE if the obtained bytes are possibly not good randomness. --- shared/nm-utils/nm-shared-utils.c | 137 ++++++++++++++++++++++++++++++ shared/nm-utils/nm-shared-utils.h | 4 + src/nm-core-utils.c | 36 +------- src/nm-core-utils.h | 2 - 4 files changed, 145 insertions(+), 34 deletions(-) diff --git a/shared/nm-utils/nm-shared-utils.c b/shared/nm-utils/nm-shared-utils.c index ce45f9140..d2c057c48 100644 --- a/shared/nm-utils/nm-shared-utils.c +++ b/shared/nm-utils/nm-shared-utils.c @@ -26,6 +26,13 @@ #include #include #include +#include + +#if USE_SYS_RANDOM_H +#include +#else +#include +#endif /*****************************************************************************/ @@ -1089,3 +1096,133 @@ nm_utils_fd_read_loop_exact (int fd, void *buf, size_t nbytes, bool do_poll) return 0; } +/*****************************************************************************/ + +/** + * nm_utils_random_bytes: + * @p: the buffer to fill + * @n: the number of bytes to write to @p. + * + * 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 + * entroy (at early boot), the function will read /dev/urandom. + * Which of course, still has low entropy, and cause kernel to log + * a warning. + */ +gboolean +nm_utils_random_bytes (void *p, size_t n) +{ + int fd; + int r; + gboolean has_high_quality = TRUE; + gboolean urandom_success; + guint8 *buf = p; + gboolean avoid_urandom = FALSE; + + g_return_val_if_fail (p, FALSE); + g_return_val_if_fail (n > 0, FALSE); + +#if HAVE_GETRANDOM + { + static gboolean have_syscall = TRUE; + + if (have_syscall) { + r = getrandom (buf, n, GRND_NONBLOCK); + if (r > 0) { + if ((size_t) r == n) + return TRUE; + + /* no or partial read. There is not enough entropy. + * Fill the rest reading from urandom, and remember that + * some bits are not hight quality. */ + nm_assert (r < n); + buf += r; + n -= r; + has_high_quality = FALSE; + + /* 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. + * + * We use our fallback via GRand. Note that g_rand_new() also + * tries to seed itself with data from /dev/urandom, but since + * we reuse the instance, it shouldn't matter. */ + avoid_urandom = TRUE; + } else { + if (errno == ENOSYS) { + /* no support for getrandom(). We don't know whether + * we urandom will give us good quality. Assume yes. */ + have_syscall = FALSE; + } else { + /* unknown error. We'll read urandom below, but we don't have + * high-quality randomness. */ + has_high_quality = FALSE; + } + } + } + } +#endif + + urandom_success = FALSE; + if (!avoid_urandom) { +fd_open: + fd = open ("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOCTTY); + if (fd < 0) { + r = errno; + if (r == EINTR) + goto fd_open; + } else { + r = nm_utils_fd_read_loop_exact (fd, buf, n, TRUE); + close (fd); + if (r >= 0) + urandom_success = TRUE; + } + } + + if (!urandom_success) { + static _nm_thread_local GRand *rand = NULL; + gsize i; + int j; + + /* we failed to fill the bytes reading from urandom. + * Fill the bits using GRand pseudo random numbers. + * + * We don't have good quality. + */ + has_high_quality = FALSE; + + if (G_UNLIKELY (!rand)) + rand = g_rand_new (); + + nm_assert (n > 0); + i = 0; + for (;;) { + const union { + guint32 v32; + guint8 v8[4]; + } v = { + .v32 = g_rand_int (rand), + }; + + for (j = 0; j < 4; ) { + buf[i++] = v.v8[j++]; + if (i >= n) + goto done; + } + } +done: + ; + } + + return has_high_quality; +} diff --git a/shared/nm-utils/nm-shared-utils.h b/shared/nm-utils/nm-shared-utils.h index 96aab5341..e7cfec081 100644 --- a/shared/nm-utils/nm-shared-utils.h +++ b/shared/nm-utils/nm-shared-utils.h @@ -406,4 +406,8 @@ int nm_utils_fd_read_loop_exact (int fd, void *buf, size_t nbytes, bool do_poll) /*****************************************************************************/ +gboolean nm_utils_random_bytes (void *p, size_t n); + +/*****************************************************************************/ + #endif /* __NM_SHARED_UTILS_H__ */ diff --git a/src/nm-core-utils.c b/src/nm-core-utils.c index 65687482d..42bd47c1b 100644 --- a/src/nm-core-utils.c +++ b/src/nm-core-utils.c @@ -2938,30 +2938,6 @@ nm_utils_file_get_contents (int dirfd, /*****************************************************************************/ -/* taken from systemd's dev_urandom(). */ -int -nm_utils_read_urandom (void *p, size_t nbytes) -{ - int fd = -1; - int r; - -again: - fd = open ("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOCTTY); - if (fd < 0) { - r = errno; - if (r == EINTR) - goto again; - return r == ENOENT ? -ENOSYS : -r; - } - - r = nm_utils_fd_read_loop_exact (fd, p, nbytes, TRUE); - close (fd); - - return r; -} - -/*****************************************************************************/ - guint8 * nm_utils_secret_key_read (gsize *out_key_len, GError **error) { @@ -2980,7 +2956,6 @@ nm_utils_secret_key_read (gsize *out_key_len, GError **error) key_len = 0; } } else { - int r; mode_t key_mask; /* RFC7217 mandates the key SHOULD be at least 128 bits. @@ -2988,10 +2963,9 @@ nm_utils_secret_key_read (gsize *out_key_len, GError **error) key_len = 32; secret_key = g_malloc (key_len); - r = nm_utils_read_urandom (secret_key, key_len); - if (r < 0) { + if (!nm_utils_random_bytes (secret_key, key_len)) { g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, - "Can't read /dev/urandom: %s", strerror (-r)); + "Can't get random data to generate secret key"); key_len = 0; goto out; } @@ -3245,8 +3219,7 @@ nm_utils_stable_id_random (void) { char buf[15]; - if (nm_utils_read_urandom (buf, sizeof (buf)) < 0) - g_return_val_if_reached (nm_utils_uuid_generate ()); + nm_utils_random_bytes (buf, sizeof (buf)); return g_base64_encode ((guchar *) buf, sizeof (buf)); } @@ -3630,8 +3603,7 @@ nm_utils_hw_addr_gen_random_eth (const char *current_mac_address, { struct ether_addr bin_addr; - if (nm_utils_read_urandom (&bin_addr, ETH_ALEN) < 0) - return NULL; + nm_utils_random_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); } diff --git a/src/nm-core-utils.h b/src/nm-core-utils.h index 4dff0176c..9d35e2f62 100644 --- a/src/nm-core-utils.h +++ b/src/nm-core-utils.h @@ -272,8 +272,6 @@ gboolean nm_utils_file_set_contents (const gchar *filename, mode_t mode, GError **error); -int nm_utils_read_urandom (void *p, size_t n); - char *nm_utils_machine_id_read (void); gboolean nm_utils_machine_id_parse (const char *id_str, /*uuid_t*/ guchar *out_uuid);