]>
Commit | Line | Data |
---|---|---|
fe72459e | 1 | /* |
dde4b593 | 2 | * SPDX-License-Identifier: BSD-3-Clause |
0f23ee0c | 3 | * |
dde4b593 | 4 | * General purpose random utilities. Based on libuuid code. |
0f23ee0c | 5 | * |
dde4b593 KZ |
6 | * This code is free software; you can redistribute it and/or modify it under |
7 | * the terms of the Modified BSD License. The complete text of the license is | |
8 | * available in the Documentation/licenses/COPYING.BSD-3-Clause file. | |
fe72459e | 9 | */ |
fe72459e DB |
10 | #include <stdio.h> |
11 | #include <unistd.h> | |
12 | #include <fcntl.h> | |
13 | #include <stdlib.h> | |
14 | #include <string.h> | |
15 | #include <sys/time.h> | |
319563bf | 16 | #ifdef HAVE_SYS_SYSCALL_H |
fe72459e | 17 | #include <sys/syscall.h> |
811351ac | 18 | #endif |
0720d60c | 19 | #include "c.h" |
fe72459e | 20 | #include "randutils.h" |
0720d60c | 21 | #include "nls.h" |
fe72459e DB |
22 | |
23 | #ifdef HAVE_TLS | |
24 | #define THREAD_LOCAL static __thread | |
25 | #else | |
26 | #define THREAD_LOCAL static | |
27 | #endif | |
28 | ||
cc01c2dc | 29 | #ifdef HAVE_GETRANDOM |
b192dd69 | 30 | # include <sys/random.h> |
cc01c2dc | 31 | #elif defined (__linux__) |
cc01c2dc SK |
32 | # if !defined(SYS_getrandom) && defined(__NR_getrandom) |
33 | /* usable kernel-headers, but old glibc-headers */ | |
34 | # define SYS_getrandom __NR_getrandom | |
35 | # endif | |
36 | #endif | |
37 | ||
38 | #if !defined(HAVE_GETRANDOM) && defined(SYS_getrandom) | |
bd0f347f | 39 | /* libc without function, but we have syscall */ |
a9cf659e CC |
40 | #define GRND_NONBLOCK 0x01 |
41 | #define GRND_RANDOM 0x02 | |
cc01c2dc SK |
42 | static int getrandom(void *buf, size_t buflen, unsigned int flags) |
43 | { | |
44 | return (syscall(SYS_getrandom, buf, buflen, flags)); | |
45 | } | |
46 | # define HAVE_GETRANDOM | |
47 | #endif | |
48 | ||
fe72459e DB |
49 | #if defined(__linux__) && defined(__NR_gettid) && defined(HAVE_JRAND48) |
50 | #define DO_JRAND_MIX | |
51 | THREAD_LOCAL unsigned short ul_jrand_seed[3]; | |
52 | #endif | |
53 | ||
a55d646b KZ |
54 | int rand_get_number(int low_n, int high_n) |
55 | { | |
56 | return rand() % (high_n - low_n + 1) + low_n; | |
57 | } | |
58 | ||
cc01c2dc | 59 | static void crank_random(void) |
fe72459e | 60 | { |
cc01c2dc | 61 | int i; |
a55d646b | 62 | struct timeval tv; |
3534b2c7 | 63 | unsigned int n_pid, n_uid; |
fe72459e | 64 | |
87918040 | 65 | gettimeofday(&tv, NULL); |
3534b2c7 KZ |
66 | n_pid = getpid(); |
67 | n_uid = getuid(); | |
68 | srand((n_pid << 16) ^ n_uid ^ tv.tv_sec ^ tv.tv_usec); | |
fe72459e DB |
69 | |
70 | #ifdef DO_JRAND_MIX | |
71 | ul_jrand_seed[0] = getpid() ^ (tv.tv_sec & 0xFFFF); | |
72 | ul_jrand_seed[1] = getppid() ^ (tv.tv_usec & 0xFFFF); | |
73 | ul_jrand_seed[2] = (tv.tv_sec ^ tv.tv_usec) >> 16; | |
74 | #endif | |
75 | /* Crank the random number generator a few times */ | |
87918040 | 76 | gettimeofday(&tv, NULL); |
fe72459e DB |
77 | for (i = (tv.tv_sec ^ tv.tv_usec) & 0x1F; i > 0; i--) |
78 | rand(); | |
fe72459e DB |
79 | } |
80 | ||
cc01c2dc SK |
81 | int random_get_fd(void) |
82 | { | |
261888f1 | 83 | int fd; |
cc01c2dc SK |
84 | |
85 | fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC); | |
86 | if (fd == -1) | |
87 | fd = open("/dev/random", O_RDONLY | O_NONBLOCK | O_CLOEXEC); | |
cc01c2dc SK |
88 | crank_random(); |
89 | return fd; | |
90 | } | |
fe72459e DB |
91 | |
92 | /* | |
93 | * Generate a stream of random nbytes into buf. | |
94 | * Use /dev/urandom if possible, and if not, | |
95 | * use glibc pseudo-random functions. | |
96 | */ | |
edc1c90c KZ |
97 | #define UL_RAND_READ_ATTEMPTS 8 |
98 | #define UL_RAND_READ_DELAY 125000 /* microseconds */ | |
99 | ||
e4be3ee0 SN |
100 | /* |
101 | * Write @nbytes random bytes into @buf. | |
102 | * | |
103 | * Returns 0 for good quality of random bytes or 1 for weak quality. | |
104 | */ | |
105 | int ul_random_get_bytes(void *buf, size_t nbytes) | |
fe72459e | 106 | { |
cc01c2dc | 107 | unsigned char *cp = (unsigned char *)buf; |
5264aebb KZ |
108 | size_t i, n = nbytes; |
109 | int lose_counter = 0; | |
cc01c2dc SK |
110 | |
111 | #ifdef HAVE_GETRANDOM | |
5264aebb KZ |
112 | while (n > 0) { |
113 | int x; | |
114 | ||
115 | errno = 0; | |
a9cf659e | 116 | x = getrandom(cp, n, GRND_NONBLOCK); |
5264aebb KZ |
117 | if (x > 0) { /* success */ |
118 | n -= x; | |
119 | cp += x; | |
120 | lose_counter = 0; | |
e4be3ee0 | 121 | errno = 0; |
edc1c90c | 122 | } else if (errno == ENOSYS) { /* kernel without getrandom() */ |
a9cf659e | 123 | break; |
edc1c90c KZ |
124 | |
125 | } else if (errno == EAGAIN && lose_counter < UL_RAND_READ_ATTEMPTS) { | |
311e33af | 126 | xusleep(UL_RAND_READ_DELAY); /* no entropy, wait and try again */ |
edc1c90c KZ |
127 | lose_counter++; |
128 | } else | |
5264aebb | 129 | break; |
cc01c2dc | 130 | } |
5264aebb | 131 | |
a7df0f5f | 132 | if (errno == ENOSYS) |
5264aebb | 133 | #endif |
a7df0f5f | 134 | /* |
5264aebb KZ |
135 | * We've been built against headers that support getrandom, but the |
136 | * running kernel does not. Fallback to reading from /dev/{u,}random | |
137 | * as before | |
a7df0f5f | 138 | */ |
a7df0f5f | 139 | { |
a7df0f5f | 140 | int fd = random_get_fd(); |
a7df0f5f | 141 | |
51013d3e | 142 | lose_counter = 0; |
a7df0f5f CJHR |
143 | if (fd >= 0) { |
144 | while (n > 0) { | |
145 | ssize_t x = read(fd, cp, n); | |
146 | if (x <= 0) { | |
edc1c90c | 147 | if (lose_counter++ > UL_RAND_READ_ATTEMPTS) |
a7df0f5f | 148 | break; |
edc1c90c | 149 | xusleep(UL_RAND_READ_DELAY); |
a7df0f5f CJHR |
150 | continue; |
151 | } | |
152 | n -= x; | |
153 | cp += x; | |
154 | lose_counter = 0; | |
fe72459e | 155 | } |
fe72459e | 156 | |
a7df0f5f CJHR |
157 | close(fd); |
158 | } | |
fe72459e | 159 | } |
fe72459e DB |
160 | /* |
161 | * We do this all the time, but this is the only source of | |
162 | * randomness if /dev/random/urandom is out to lunch. | |
163 | */ | |
cc01c2dc | 164 | crank_random(); |
fe72459e DB |
165 | for (cp = buf, i = 0; i < nbytes; i++) |
166 | *cp++ ^= (rand() >> 7) & 0xFF; | |
167 | ||
168 | #ifdef DO_JRAND_MIX | |
169 | { | |
170 | unsigned short tmp_seed[3]; | |
171 | ||
172 | memcpy(tmp_seed, ul_jrand_seed, sizeof(tmp_seed)); | |
173 | ul_jrand_seed[2] = ul_jrand_seed[2] ^ syscall(__NR_gettid); | |
174 | for (cp = buf, i = 0; i < nbytes; i++) | |
175 | *cp++ ^= (jrand48(tmp_seed) >> 7) & 0xFF; | |
176 | memcpy(ul_jrand_seed, tmp_seed, | |
177 | sizeof(ul_jrand_seed)-sizeof(unsigned short)); | |
178 | } | |
179 | #endif | |
e4be3ee0 SN |
180 | |
181 | return n != 0; | |
fe72459e DB |
182 | } |
183 | ||
0720d60c SK |
184 | |
185 | /* | |
186 | * Tell source of randomness. | |
187 | */ | |
188 | const char *random_tell_source(void) | |
189 | { | |
cc01c2dc SK |
190 | #ifdef HAVE_GETRANDOM |
191 | return _("getrandom() function"); | |
192 | #else | |
0720d60c SK |
193 | size_t i; |
194 | static const char *random_sources[] = { | |
195 | "/dev/urandom", | |
196 | "/dev/random" | |
197 | }; | |
198 | ||
199 | for (i = 0; i < ARRAY_SIZE(random_sources); i++) { | |
200 | if (!access(random_sources[i], R_OK)) | |
201 | return random_sources[i]; | |
202 | } | |
cc01c2dc | 203 | #endif |
0720d60c SK |
204 | return _("libc pseudo-random functions"); |
205 | } | |
206 | ||
e8f7acb0 | 207 | #ifdef TEST_PROGRAM_RANDUTILS |
e282116f RM |
208 | #include <inttypes.h> |
209 | ||
5264aebb | 210 | int main(int argc, char *argv[]) |
fe72459e | 211 | { |
5264aebb KZ |
212 | size_t i, n; |
213 | int64_t *vp, v; | |
214 | char *buf; | |
215 | size_t bufsz; | |
216 | ||
217 | n = argc == 1 ? 16 : atoi(argv[1]); | |
fe72459e | 218 | |
5264aebb KZ |
219 | printf("Multiple random calls:\n"); |
220 | for (i = 0; i < n; i++) { | |
364de8f4 | 221 | ul_random_get_bytes(&v, sizeof(v)); |
e282116f | 222 | printf("#%02zu: %25"PRIu64"\n", i, v); |
5264aebb KZ |
223 | } |
224 | ||
225 | ||
226 | printf("One random call:\n"); | |
227 | bufsz = n * sizeof(*vp); | |
228 | buf = malloc(bufsz); | |
229 | if (!buf) | |
230 | err(EXIT_FAILURE, "failed to allocate buffer"); | |
231 | ||
364de8f4 | 232 | ul_random_get_bytes(buf, bufsz); |
5264aebb KZ |
233 | for (i = 0; i < n; i++) { |
234 | vp = (int64_t *) (buf + (i * sizeof(*vp))); | |
e282116f | 235 | printf("#%02zu: %25"PRIu64"\n", i, *vp); |
fe72459e DB |
236 | } |
237 | ||
238 | return EXIT_SUCCESS; | |
239 | } | |
e8f7acb0 | 240 | #endif /* TEST_PROGRAM_RANDUTILS */ |