summaryrefslogtreecommitdiff
path: root/shared/nm-glib-aux/nm-random-utils.c
blob: 83d620dcaa5d980aa3ed72ec94af7a775b75d0e8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// SPDX-License-Identifier: LGPL-2.1+
/*
 * Copyright (C) 2017 Red Hat, Inc.
 */

#include "nm-default.h"

#include "nm-random-utils.h"

#include <fcntl.h>

#if USE_SYS_RANDOM_H
    #include <sys/random.h>
#else
    #include <linux/random.h>
#endif

#include "nm-shared-utils.h"

/*****************************************************************************/

/**
 * 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
 * entropy (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 high 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);
            nm_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;
}