Commit b01a453c authored by Thomas Haller's avatar Thomas Haller

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.
parent 93ea7a59
......@@ -26,6 +26,13 @@
#include <errno.h>
#include <arpa/inet.h>
#include <poll.h>
#include <fcntl.h>
#if USE_SYS_RANDOM_H
#include <sys/random.h>
#else
#include <linux/random.h>
#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;
}
......@@ -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__ */
......@@ -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);
}
......
......@@ -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);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment