Skip to content

Commit ffa047a

Browse files
zx2c4bluca
authored andcommitted
random-util: remove RDRAND usage
/dev/urandom is seeded with RDRAND. Calling genuine_random_bytes(..., ..., 0) will use /dev/urandom as a last resort. Hence, we gain nothing here by having our own RDRAND wrapper, because /dev/urandom already is based on RDRAND output, even before /dev/urandom has fully initialized. Furthermore, RDRAND is not actually fast! And on each successive generation of new x86 CPUs, from both AMD and Intel, it just gets slower. This commit simplifies things by just using /dev/urandom in cases where we before might use RDRAND, since /dev/urandom will always have RDRAND mixed in as part of it. And above where I say "/dev/urandom", what I actually mean is GRND_INSECURE, which is the same thing but won't generate warnings in dmesg.
1 parent e28770e commit ffa047a

File tree

9 files changed

+29
-227
lines changed

9 files changed

+29
-227
lines changed

NEWS

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,20 @@ CHANGES WITH 251:
9595
handling, and improving compatibility with home directories intended
9696
to be portable like the ones managed by systemd-homed.
9797

98+
* All kernels supported by systemd mix RDRAND (or similar) into the
99+
entropy pool at early boot. This means that on those systems, even
100+
if /dev/urandom is not yet initialized, it still returns bytes that
101+
that are at least as high quality as RDRAND. For that reason, we no
102+
longer have reason to invoke RDRAND from systemd itself, which has
103+
historically been a source of bugs. Furthermore, kernels ≥5.6 provide
104+
the getrandom(GRND_INSECURE) interface for returning random bytes
105+
before the entropy pool is initialized without warning into kmsg,
106+
which is what we attempt to use if available. By removing systemd's
107+
direct usage of RDRAND, x86 systems ≥Broadwell that are running an
108+
older kernel may experience kmsg warnings that were not seen with
109+
250. For newer kernels, non-x86 systems, or older x86 systems,
110+
there should be no visible changes.
111+
98112
CHANGES WITH 250:
99113

100114
* Support for encrypted and authenticated credentials has been added.

docs/ENVIRONMENT.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,6 @@ All tools:
9797
systems built with libxcrypt and is ignored on systems using glibc's
9898
original, internal `crypt()` implementation.)
9999

100-
* `$SYSTEMD_RDRAND=0` — if set, the RDRAND instruction will never be used,
101-
even if the CPU supports it.
102-
103100
* `$SYSTEMD_SECCOMP=0` — if set, seccomp filters will not be enforced, even if
104101
support for it is compiled in and available in the kernel.
105102

docs/PORTING_TO_NEW_ARCHITECTURES.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,6 @@ architecture.
5353
support booting into OS trees that have an empty root directory with only
5454
`/usr/` mounted in.
5555

56-
7. If your architecture has a CPU opcode similar to x86' RDRAND consider adding
57-
native support for it to `src/basic/random-util.c`'s `rdrand()` function.
58-
59-
8. If your architecture supports VM virtualization and provides CPU opcodes
56+
7. If your architecture supports VM virtualization and provides CPU opcodes
6057
similar to x86' CPUID consider adding native support for detecting VMs this
6158
way to `src/basic/virt.c`.

docs/RANDOM_SEEDS.md

Lines changed: 7 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -144,33 +144,11 @@ acquired.
144144
## Keeping `systemd'`s Demand on the Kernel Entropy Pool Minimal
145145

146146
Since most of systemd's own use of random numbers do not require
147-
cryptographic-grade RNGs, it tries to avoid reading entropy from the kernel
148-
entropy pool if possible. If it succeeds this has the benefit that there's no
149-
need to delay the early boot process until entropy is available, and noisy
150-
kernel log messages about early reading from `/dev/urandom` are avoided
151-
too. Specifically:
152-
153-
1. When generating [Type 4
154-
UUIDs](https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_\(random\)),
155-
systemd tries to use Intel's and AMD's RDRAND CPU opcode directly, if
156-
available. While some doubt the quality and trustworthiness of the entropy
157-
provided by these opcodes, they should be good enough for generating UUIDs,
158-
if not key material (though, as mentioned, today's big distributions opted
159-
to trust it for that too, now, see above — but we are not going to make that
160-
decision for you, and for anything key material related will only use the
161-
kernel's entropy pool). If RDRAND is not available or doesn't work, it will
162-
use synchronous `getrandom()` as fallback, and `/dev/urandom` on old kernels
163-
where that system call doesn't exist yet. This means on non-Intel/AMD
164-
systems UUID generation will block on kernel entropy initialization.
165-
166-
2. For seeding hash tables, and all the other similar purposes systemd first
167-
tries RDRAND, and if that's not available will try to use asynchronous
168-
`getrandom()` (if the kernel doesn't support this system call,
169-
`/dev/urandom` is used). This may fail too in case the pool is not
170-
initialized yet, in which case it will fall back to glibc's internal rand()
171-
calls, i.e. weak pseudo-random numbers. This should make sure we use good
172-
random bytes if we can, but neither delay boot nor trigger noisy kernel log
173-
messages during early boot for these use-cases.
147+
cryptographic-grade RNGs, it tries to avoid blocking reads to the kernel's RNG,
148+
opting instead for using `getrandom(GRND_INSECURE)`. After the pool is
149+
initialized, this is identical to `getrandom(0)`, returning cryptographically
150+
secure random numbers, but before it's initialized it has the nice effect of
151+
not blocking system boot.
174152

175153
## `systemd`'s Support for Filling the Kernel Entropy Pool
176154

@@ -280,10 +258,8 @@ early-boot entropy in most cases. Specifically:
280258
hosting provider if they don't. For VMs used in testing environments,
281259
`systemd.random_seed=` may be used as an alternative to a virtualized RNG.
282260

283-
3. On Intel/AMD systems systemd's own reliance on the kernel entropy pool is
284-
minimal (as RDRAND is used on those for UUID generation). This only works if
285-
the CPU has RDRAND of course, which most physical CPUs do (but I hear many
286-
virtualized CPUs do not. Pity.)
261+
3. In general, systemd's own reliance on the kernel entropy pool is minimal
262+
(due to the use of `GRND_INSECURE`).
287263

288264
4. In all other cases, `systemd-random-seed.service` will help a bit, but — as
289265
mentioned — is too late to help with early boot.

src/basic/random-util.c

Lines changed: 4 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -35,137 +35,11 @@
3535

3636
static bool srand_called = false;
3737

38-
int rdrand(unsigned long *ret) {
39-
40-
/* So, you are a "security researcher", and you wonder why we bother with using raw RDRAND here,
41-
* instead of sticking to /dev/urandom or getrandom()?
42-
*
43-
* Here's why: early boot. On Linux, during early boot the random pool that backs /dev/urandom and
44-
* getrandom() is generally not initialized yet. It is very common that initialization of the random
45-
* pool takes a longer time (up to many minutes), in particular on embedded devices that have no
46-
* explicit hardware random generator, as well as in virtualized environments such as major cloud
47-
* installations that do not provide virtio-rng or a similar mechanism.
48-
*
49-
* In such an environment using getrandom() synchronously means we'd block the entire system boot-up
50-
* until the pool is initialized, i.e. *very* long. Using getrandom() asynchronously (GRND_NONBLOCK)
51-
* would mean acquiring randomness during early boot would simply fail. Using /dev/urandom would mean
52-
* generating many kmsg log messages about our use of it before the random pool is properly
53-
* initialized. Neither of these outcomes is desirable.
54-
*
55-
* Thus, for very specific purposes we use RDRAND instead of either of these three options. RDRAND
56-
* provides us quickly and relatively reliably with random values, without having to delay boot,
57-
* without triggering warning messages in kmsg.
58-
*
59-
* Note that we use RDRAND only under very specific circumstances, when the requirements on the
60-
* quality of the returned entropy permit it. Specifically, here are some cases where we *do* use
61-
* RDRAND:
62-
*
63-
* • UUID generation: UUIDs are supposed to be universally unique but are not cryptographic
64-
* key material. The quality and trust level of RDRAND should hence be OK: UUIDs should be
65-
* generated in a way that is reliably unique, but they do not require ultimate trust into
66-
* the entropy generator. systemd generates a number of UUIDs during early boot, including
67-
* 'invocation IDs' for every unit spawned that identify the specific invocation of the
68-
* service globally, and a number of others. Other alternatives for generating these UUIDs
69-
* have been considered, but don't really work: for example, hashing uuids from a local
70-
* system identifier combined with a counter falls flat because during early boot disk
71-
* storage is not yet available (think: initrd) and thus a system-specific ID cannot be
72-
* stored or retrieved yet.
73-
*
74-
* • Hash table seed generation: systemd uses many hash tables internally. Hash tables are
75-
* generally assumed to have O(1) access complexity, but can deteriorate to prohibitive
76-
* O(n) access complexity if an attacker manages to trigger a large number of hash
77-
* collisions. Thus, systemd (as any software employing hash tables should) uses seeded
78-
* hash functions for its hash tables, with a seed generated randomly. The hash tables
79-
* systemd employs watch the fill level closely and reseed if necessary. This allows use of
80-
* a low quality RNG initially, as long as it improves should a hash table be under attack:
81-
* the attacker after all needs to trigger many collisions to exploit it for the purpose
82-
* of DoS, but if doing so improves the seed the attack surface is reduced as the attack
83-
* takes place.
84-
*
85-
* Some cases where we do NOT use RDRAND are:
86-
*
87-
* • Generation of cryptographic key material 🔑
88-
*
89-
* • Generation of cryptographic salt values 🧂
90-
*
91-
* This function returns:
92-
*
93-
* -EOPNOTSUPP → RDRAND is not available on this system 😔
94-
* -EAGAIN → The operation failed this time, but is likely to work if you try again a few
95-
* times ♻
96-
* -EUCLEAN → We got some random value, but it looked strange, so we refused using it.
97-
* This failure might or might not be temporary. 😕
98-
*/
99-
100-
#if defined(__i386__) || defined(__x86_64__)
101-
static int have_rdrand = -1;
102-
unsigned long v;
103-
uint8_t success;
104-
105-
if (have_rdrand < 0) {
106-
uint32_t eax, ebx, ecx, edx;
107-
108-
/* Check if RDRAND is supported by the CPU */
109-
if (__get_cpuid(1, &eax, &ebx, &ecx, &edx) == 0) {
110-
have_rdrand = false;
111-
return -EOPNOTSUPP;
112-
}
113-
114-
/* Compat with old gcc where bit_RDRND didn't exist yet */
115-
#ifndef bit_RDRND
116-
#define bit_RDRND (1U << 30)
117-
#endif
118-
119-
have_rdrand = !!(ecx & bit_RDRND);
120-
121-
if (have_rdrand > 0) {
122-
/* Allow disabling use of RDRAND with SYSTEMD_RDRAND=0
123-
If it is unset getenv_bool_secure will return a negative value. */
124-
if (getenv_bool_secure("SYSTEMD_RDRAND") == 0) {
125-
have_rdrand = false;
126-
return -EOPNOTSUPP;
127-
}
128-
}
129-
}
130-
131-
if (have_rdrand == 0)
132-
return -EOPNOTSUPP;
133-
134-
asm volatile("rdrand %0;"
135-
"setc %1"
136-
: "=r" (v),
137-
"=qm" (success));
138-
msan_unpoison(&success, sizeof(success));
139-
if (!success)
140-
return -EAGAIN;
141-
142-
/* Apparently on some AMD CPUs RDRAND will sometimes (after a suspend/resume cycle?) report success
143-
* via the carry flag but nonetheless return the same fixed value -1 in all cases. This appears to be
144-
* a bad bug in the CPU or firmware. Let's deal with that and work-around this by explicitly checking
145-
* for this special value (and also 0, just to be sure) and filtering it out. This is a work-around
146-
* only however and something AMD really should fix properly. The Linux kernel should probably work
147-
* around this issue by turning off RDRAND altogether on those CPUs. See:
148-
* https://github.com/systemd/systemd/issues/11810 */
149-
if (v == 0 || v == ULONG_MAX)
150-
return log_debug_errno(SYNTHETIC_ERRNO(EUCLEAN),
151-
"RDRAND returned suspicious value %lx, assuming bad hardware RNG, not using value.", v);
152-
153-
*ret = v;
154-
return 0;
155-
#else
156-
return -EOPNOTSUPP;
157-
#endif
158-
}
159-
16038
int genuine_random_bytes(void *p, size_t n, RandomFlags flags) {
16139
static int have_syscall = -1;
16240
_cleanup_close_ int fd = -1;
16341

164-
if (FLAGS_SET(flags, RANDOM_BLOCK | RANDOM_ALLOW_RDRAND))
165-
return -EINVAL;
166-
167-
/* Gathers some high-quality randomness from the kernel (or potentially mid-quality randomness from
168-
* the CPU if the RANDOM_ALLOW_RDRAND flag is set). This call won't block, unless the RANDOM_BLOCK
42+
/* Gathers some high-quality randomness from the kernel. This call won't block, unless the RANDOM_BLOCK
16943
* flag is set. If it doesn't block, it will still always return some data from the kernel, regardless
17044
* of whether the random pool is fully initialized or not. When creating cryptographic key material you
17145
* should always use RANDOM_BLOCK. */
@@ -212,34 +86,6 @@ int genuine_random_bytes(void *p, size_t n, RandomFlags flags) {
21286
}
21387
}
21488

215-
if (FLAGS_SET(flags, RANDOM_ALLOW_RDRAND)) {
216-
/* Try x86-64' RDRAND intrinsic if we have it. We only use it if high quality randomness is
217-
* not required, as we don't trust it (who does?). Note that we only do a single iteration of
218-
* RDRAND here, even though the Intel docs suggest calling this in a tight loop of 10
219-
* invocations or so. That's because we don't really care about the quality here. We
220-
* generally prefer using RDRAND if the caller allows us to, since this way we won't upset
221-
* the kernel's random subsystem by accessing it before the pool is initialized (after all it
222-
* will kmsg log about every attempt to do so). */
223-
for (;;) {
224-
unsigned long u;
225-
size_t m;
226-
227-
if (rdrand(&u) < 0) {
228-
/* OK, this didn't work, let's go with /dev/urandom instead */
229-
break;
230-
}
231-
232-
m = MIN(sizeof(u), n);
233-
memcpy(p, &u, m);
234-
235-
p = (uint8_t*) p + m;
236-
n -= m;
237-
238-
if (n == 0)
239-
return 0; /* Yay, success! */
240-
}
241-
}
242-
24389
fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY);
24490
if (fd < 0)
24591
return errno == ENOENT ? -ENOSYS : -errno;
@@ -257,8 +103,6 @@ void initialize_srand(void) {
257103
#if HAVE_SYS_AUXV_H
258104
const void *auxv;
259105
#endif
260-
unsigned long k;
261-
262106
if (srand_called)
263107
return;
264108

@@ -283,9 +127,6 @@ void initialize_srand(void) {
283127
x ^= (unsigned) now(CLOCK_REALTIME);
284128
x ^= (unsigned) gettid();
285129

286-
if (rdrand(&k) >= 0)
287-
x ^= (unsigned) k;
288-
289130
srand(x);
290131
srand_called = true;
291132

@@ -339,8 +180,8 @@ void random_bytes(void *p, size_t n) {
339180
*
340181
* What this function will do:
341182
*
342-
* • This function will preferably use the CPU's RDRAND operation, if it is available, in
343-
* order to return "mid-quality" random values cheaply.
183+
* • Use getrandom(GRND_INSECURE) or /dev/urandom, to return high-quality random values if
184+
* they are cheaply available, or less high-quality random values if they are not.
344185
*
345186
* • This function will return pseudo-random data, generated via libc rand() if nothing
346187
* better is available.
@@ -363,7 +204,7 @@ void random_bytes(void *p, size_t n) {
363204
* This function is hence not useful for generating UUIDs or cryptographic key material.
364205
*/
365206

366-
if (genuine_random_bytes(p, n, RANDOM_ALLOW_RDRAND) >= 0)
207+
if (genuine_random_bytes(p, n, 0) >= 0)
367208
return;
368209

369210
/* If for some reason some user made /dev/urandom unavailable to us, or the kernel has no entropy, use a PRNG instead. */

src/basic/random-util.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
typedef enum RandomFlags {
99
RANDOM_BLOCK = 1 << 0, /* Rather block than return crap randomness (only if the kernel supports that) */
10-
RANDOM_ALLOW_RDRAND = 1 << 1, /* Allow usage of the CPU RNG */
1110
} RandomFlags;
1211

1312
int genuine_random_bytes(void *p, size_t n, RandomFlags flags); /* returns "genuine" randomness, optionally filled up with pseudo random, if not enough is available */
@@ -28,8 +27,6 @@ static inline uint32_t random_u32(void) {
2827
return u;
2928
}
3029

31-
int rdrand(unsigned long *ret);
32-
3330
/* Some limits on the pool sizes when we deal with the kernel random pool */
3431
#define RANDOM_POOL_SIZE_MIN 512U
3532
#define RANDOM_POOL_SIZE_MAX (10U*1024U*1024U)

src/libsystemd/sd-id128/sd-id128.c

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -276,9 +276,7 @@ _public_ int sd_id128_randomize(sd_id128_t *ret) {
276276

277277
assert_return(ret, -EINVAL);
278278

279-
/* We allow usage if x86-64 RDRAND here. It might not be trusted enough for keeping secrets, but it should be
280-
* fine for UUIDS. */
281-
r = genuine_random_bytes(&t, sizeof t, RANDOM_ALLOW_RDRAND);
279+
r = genuine_random_bytes(&t, sizeof(t), 0);
282280
if (r < 0)
283281
return r;
284282

src/test/test-random-util.c

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ static void test_genuine_random_bytes_one(RandomFlags flags) {
2626
TEST(genuine_random_bytes) {
2727
test_genuine_random_bytes_one(0);
2828
test_genuine_random_bytes_one(RANDOM_BLOCK);
29-
test_genuine_random_bytes_one(RANDOM_ALLOW_RDRAND);
3029
}
3130

3231
TEST(pseudo_random_bytes) {
@@ -41,22 +40,6 @@ TEST(pseudo_random_bytes) {
4140
}
4241
}
4342

44-
TEST(rdrand) {
45-
int r;
46-
47-
for (unsigned i = 0; i < 10; i++) {
48-
unsigned long x = 0;
49-
50-
r = rdrand(&x);
51-
if (r < 0) {
52-
log_error_errno(r, "RDRAND failed: %m");
53-
return;
54-
}
55-
56-
printf("%lx\n", x);
57-
}
58-
}
59-
6043
#define TOTAL 100000
6144

6245
static void test_random_u64_range_one(unsigned mod) {

src/udev/net/link-config.c

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -622,10 +622,9 @@ static int link_generate_new_hw_addr(Link *link, struct hw_addr_data *ret) {
622622

623623
if (link->config->mac_address_policy == MAC_ADDRESS_POLICY_RANDOM)
624624
/* We require genuine randomness here, since we want to make sure we won't collide with other
625-
* systems booting up at the very same time. We do allow RDRAND however, since this is not
626-
* cryptographic key material. */
625+
* systems booting up at the very same time. */
627626
for (;;) {
628-
r = genuine_random_bytes(p, len, RANDOM_ALLOW_RDRAND);
627+
r = genuine_random_bytes(p, len, 0);
629628
if (r < 0)
630629
return log_link_warning_errno(link, r, "Failed to acquire random data to generate MAC address: %m");
631630

0 commit comments

Comments
 (0)