chiark / gitweb /
Prep v238: Uncomment now needed headers and unmask now needed functions in src/basic...
[elogind.git] / src / basic / random-util.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3   This file is part of systemd.
4
5   Copyright 2010 Lennart Poettering
6
7   systemd is free software; you can redistribute it and/or modify it
8   under the terms of the GNU Lesser General Public License as published by
9   the Free Software Foundation; either version 2.1 of the License, or
10   (at your option) any later version.
11
12   systemd is distributed in the hope that it will be useful, but
13   WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15   Lesser General Public License for more details.
16
17   You should have received a copy of the GNU Lesser General Public License
18   along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <elf.h>
22 #include <errno.h>
23 #include <fcntl.h>
24 //#include <linux/random.h>
25 #include <stdbool.h>
26 //#include <stdint.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <sys/time.h>
30
31 #if HAVE_SYS_AUXV_H
32 #  include <sys/auxv.h>
33 #endif
34
35 #if USE_SYS_RANDOM_H
36 #  include <sys/random.h>
37 #else
38 #  include <linux/random.h>
39 #endif
40
41 #include "fd-util.h"
42 #include "io-util.h"
43 #include "missing.h"
44 #include "random-util.h"
45 #include "time-util.h"
46
47 int acquire_random_bytes(void *p, size_t n, bool high_quality_required) {
48         static int have_syscall = -1;
49
50         _cleanup_close_ int fd = -1;
51         unsigned already_done = 0;
52         int r;
53
54         /* Gathers some randomness from the kernel. This call will never block. If
55          * high_quality_required, it will always return some data from the kernel,
56          * regardless of whether the random pool is fully initialized or not.
57          * Otherwise, it will return success if at least some random bytes were
58          * successfully acquired, and an error if the kernel has no entropy whatsover
59          * for us. */
60
61         /* Use the getrandom() syscall unless we know we don't have it. */
62         if (have_syscall != 0) {
63                 r = getrandom(p, n, GRND_NONBLOCK);
64                 if (r > 0) {
65                         have_syscall = true;
66                         if ((size_t) r == n)
67                                 return 0;
68                         if (!high_quality_required) {
69                                 /* Fill in the remaining bytes using pseudorandom values */
70                                 pseudorandom_bytes((uint8_t*) p + r, n - r);
71                                 return 0;
72                         }
73
74                         already_done = r;
75                 } else if (errno == ENOSYS)
76                           /* We lack the syscall, continue with reading from /dev/urandom. */
77                           have_syscall = false;
78                 else if (errno == EAGAIN) {
79                         /* The kernel has no entropy whatsoever. Let's remember to
80                          * use the syscall the next time again though.
81                          *
82                          * If high_quality_required is false, return an error so that
83                          * random_bytes() can produce some pseudorandom
84                          * bytes. Otherwise, fall back to /dev/urandom, which we know
85                          * is empty, but the kernel will produce some bytes for us on
86                          * a best-effort basis. */
87                         have_syscall = true;
88
89                         if (!high_quality_required)
90                                 return -ENODATA;
91                 } else
92                         return -errno;
93         }
94
95         fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY);
96         if (fd < 0)
97                 return errno == ENOENT ? -ENOSYS : -errno;
98
99         return loop_read_exact(fd, (uint8_t*) p + already_done, n - already_done, true);
100 }
101
102 void initialize_srand(void) {
103         static bool srand_called = false;
104         unsigned x;
105 #if HAVE_SYS_AUXV_H
106         void *auxv;
107 #endif
108
109         if (srand_called)
110                 return;
111
112 #if HAVE_SYS_AUXV_H
113         /* The kernel provides us with 16 bytes of entropy in auxv, so let's
114          * try to make use of that to seed the pseudo-random generator. It's
115          * better than nothing... */
116
117         auxv = (void*) getauxval(AT_RANDOM);
118         if (auxv) {
119                 assert_cc(sizeof(x) <= 16);
120                 memcpy(&x, auxv, sizeof(x));
121         } else
122 #endif
123                 x = 0;
124
125
126         x ^= (unsigned) now(CLOCK_REALTIME);
127         x ^= (unsigned) gettid();
128
129         srand(x);
130         srand_called = true;
131 }
132
133 /* INT_MAX gives us only 31 bits, so use 24 out of that. */
134 #if RAND_MAX >= INT_MAX
135 #  define RAND_STEP 3
136 #else
137 /* SHORT_INT_MAX or lower gives at most 15 bits, we just just 8 out of that. */
138 #  define RAND_STEP 1
139 #endif
140
141 void pseudorandom_bytes(void *p, size_t n) {
142         uint8_t *q;
143
144         initialize_srand();
145
146         for (q = p; q < (uint8_t*) p + n; q += RAND_STEP) {
147                 unsigned rr;
148
149                 rr = (unsigned) rand();
150
151 #if RAND_STEP >= 3
152                 if ((size_t) (q - (uint8_t*) p + 2) < n)
153                         q[2] = rr >> 16;
154 #endif
155 #if RAND_STEP >= 2
156                 if ((size_t) (q - (uint8_t*) p + 1) < n)
157                         q[1] = rr >> 8;
158 #endif
159                 q[0] = rr;
160         }
161 }
162
163 void random_bytes(void *p, size_t n) {
164         int r;
165
166         r = acquire_random_bytes(p, n, false);
167         if (r >= 0)
168                 return;
169
170         /* If some idiot made /dev/urandom unavailable to us, or the
171          * kernel has no entropy, use a PRNG instead. */
172         return pseudorandom_bytes(p, n);
173 }