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.
This commit is contained in:
@@ -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);
|
||||
|
||||
|
Reference in New Issue
Block a user