[go: up one dir, main page]

|
|
Subscribe / Log in / New account

Pitchforks for RDSEED

By Jonathan Corbet
February 8, 2024
The generation of random (or, at least, unpredictable) numbers is key to many security technologies. For this reason, the provision of random data as a CPU feature has drawn a lot of attention over the years. A proper hardware-based random-number generator can address the problems that make randomness hard to obtain in some systems, but only if the manufacturer can be trusted to not have compromised that generator in some way. A recent discussion has brought to light a different problem, though: what happens if a hardware random-number generator can be simply driven into exhaustion?

As background, it is worth looking at two related instructions provided on x86 systems:

  • RDSEED is a true random-number generator; it reads entropy collected from the environment and stores a random bit pattern into a CPU register. It is provided primarily for the seeding of pseudo-random number generators or other applications where the highest-quality randomness is needed.
  • RDRAND obtains a random pattern from a deterministic random-number generator built into the hardware and stores it into a register. The generator used by RDRAND is periodically reseeded from the source used by RDSEED.

RDRAND is recommended for most uses; it is faster and can significantly stretch the amount of entropy that is available to the system. Within the kernel, RDSEED is used to seed the kernel's random-number generator, but for little else. An important point to note is that neither of these instructions is privileged; they are equally available to user-space code. Also important is the fact that either instruction can fail if there is not sufficient entropy available. (See this page for lots of details on how these instructions work).

On the x86 architecture, the kernel has a pair of functions, rdseed_long() and rdrand_long(), that make use of the above instructions. Kirill Shutemov recently observed that, while rdrand_long() will retry ten times if RDRAND fails, rdseed_long() gives up immediately. He posted a patch adding a retry loop to rdseed_long(), and another to issue a warning if even ten tries were not enough to get a successful result. That is where the discussion began.

Can they fail (and do we care)?

Initially, there was some uncertainty over whether those instructions can fail at all. RDSEED is able to produce entropy at a high rate, and RDRAND multiplies that entropy considerably, so exhausting them seems unlikely. Jason Donenfeld, who maintains the kernel's random-number generator, worried that, if the CPU's random-number generator could be driven to failure, that denial-of-service problems could result. Dave Hansen, though, thought that such worries were misplaced; he spoke authoritatively:

Despite the SDM allowing it, we (software) need RDRAND/RDSEED failures to be exceedingly rare by design. If they're not, we're going to get our trusty torches and pitchforks and go after the folks who built the broken hardware.

Repeat after me:

Regular RDRAND/RDSEED failures only occur on broken hardware

It seems, though, that there may just be a use for those pitchforks. Donenfeld wrote a little program to stress the hardware by repeatedly executing RDRAND and RDSEED instructions; he observed that RDSEED failed nearly 30% of the time — with a single-threaded test. The hardware random-number generator, though, is shared between all CPUs in a socket, so a multi-threaded test can be expected to show worse results. Indeed, Daniel P. Berrangé reported that, with a multi-threaded test, RDSEED only succeeded 3% of the time. Nobody was able to demonstrate RDRAND failures but, as Berrangé pointed out later in the discussion, that situation could change with a larger number of threads. Elena Reshetova also acknowledged that it may be possible to force RDRAND to fail.

The clear outcome is that, with a determined effort, RDSEED can be made to fail and the reliability of RDRAND is not guaranteed. The logical next question is: how much of a concern is this? For most use cases, there is little to worry about. The kernel's random-number generator will continue to produce unpredictable output if the hardware random-number generator fails occasionally, even in the absence of other entropy sources. As Ted Ts'o pointed out, the kernel makes use of any other entropy sources it can find (such as the variation in interrupt timings) and is intended to be robust even if RDRAND were to turn out to be entirely compromised. Since most applications get their random data from the kernel, even an unreliable RDRAND should not be a concern.

There is, however, one noteworthy exception: the use case known as confidential computing (sometimes referred to as "CoCo"), which is intended to guarantee the security and confidentiality of virtual machines even if they are running on a compromised or hostile host computer. Techniques like secure enclaves and encrypted memory have been developed to protect virtual machines from a prying host; these technologies may, someday, work as intended, but they are absolutely dependent on the availability of trustworthy random data. If a "confidential" virtual machine can be launched with a known random seed, the game may be over before it starts.

Availability of entropy at boot time has long been a problem for Linux systems, so a number of mechanisms have been developed to seed the random-number generator as quickly as possible. These can include using random data injected by the bootloader and collecting entropy from the environment. A confidential-computing system, though, cannot trust inputs like that. The bootloader is under the host's control, but so is environmental entropy. As Reshetova explained, the host is able to control events like the expiration of timers and the delivery of interrupts. The only source of entropy that is not, at least theoretically, under the host's control is the hardware random-number generator. If that, too, is compromised, the entire confidential-computing dream falls apart.

What to do

That dream has been somewhat controversial in kernel circles from the beginning, and this revelation has not helped; at one point Donenfeld asked directly: "Is this CoCo VM stuff even real? Is protecting guests from hosts actually possible in the end?" Most of the ensuing discussion, though, was focused on what the appropriate response should be.

Outside of the confidential-computing use case, the consensus seems to be that little needs to be done. Adding a warning if RDSEED or RDRAND fail (as Shutemov proposed at the beginning of the discussion) is being considered, but even that is not clearly the right thing to do. Many systems run with panic_on_warn enabled; on such systems, a warning will cause a system crash. That would turn a random-number-generator failure into a denial-of-service problem. Even in this case, though, a failure during early boot seems worth a warning; if that happens, either the random-number-generator is simply broken, or there is clearly some sort of attack underway.

When the system is running in a confidential-computing mode, though, the situation is a bit more complicated. Among other things, that is not a mode that the kernel as a whole recognizes; Ts'o suggested adding a global flag for this purpose, since other parts of the kernel are eventually likely to need it as well. But, even with that in place, there are a number of alternatives to consider; Donenfeld spelled them out in detail. They come down to whether the kernel should warn (or panic) on failure, whether that should happen at any time or only during early boot, and whether this response should change in "confidential-computing mode".

The emerging consensus would seem to be that the first step is simply retrying a failing operation, as is already done for RDRAND. Even if a single RDRAND can be made to fail, doing so ten times in a row is a more difficult prospect. That said, one should remember that, as Reshetova pointed out, the host controls a guest's scheduling and could, in theory, interfere with a retry loop as well. If retries are forced to happen when the random-number generator is exhausted, they will never succeed.

At a minimum, a warning should be issued if these instructions fail during early boot, since that is a clear sign that something is going wrong. For the confidential-computing case, a randomness failure means that the system is unable to provide the guarantees upon which the whole edifice is built, so the system should simply refuse to run. That could be achieved with a panic, or by simply looping on RDRAND indefinitely, locking up the virtual machine if the instruction never succeeds.

Beyond that, there is not a whole lot that needs to be done.

There is one part of the discussion that is not visible, though: this concern seems to have created a scramble within the CPU vendors to characterize the extent of the problem and figure out what, if anything, needs to be done about it. Lest one think this is an Intel-specific issue, Berrangé reported the ability to force high failure rates on some AMD processors as well. The outcome of those discussions may be some combination of documentation, microcode updates, and design changes in future processors.

Meanwhile, the prospect of random-number-generator exhaustion need not be a big worry for most users; it seems unlikely that it can be used to threaten real-world systems. For the confidential-computing crowd, it is just another one of what is sure to be an unending list of potential threats that need to be mitigated. Fixes will be put into place, and we can all put our pitchforks away and go back to watching the argument over whether confidential computing is achievable in the first place.

Index entries for this article
KernelArchitectures/x86
KernelConfidential computing
KernelRandom numbers
SecurityLinux kernel/Random number generation


to post comments

Pitchforks for RDSEED

Posted Feb 8, 2024 18:27 UTC (Thu) by dullfire (guest, #111432) [Link] (6 responses)

> Many systems run with panic_on_warn enabled; on such systems, a warning will cause a system crash. That would turn a random-number-generator failure into a denial-of-service problem.

Sorry but this is a bad statement. The if the system is running with panic_on_warn then the system has explicitly been told to panic (effectively go offline) on a warning event. Which means that it can't be a "denial-of-service" when it is behaving exactly the way the admins[0] requested. Additionally, panic_on_warn turns any ability to generate warnings into a DoS by that definition.

Would you call an admin ssh-ing in running a "sudo reboot" a "denial of service". If so, that makes the term so broad as to be useless.

Further more: If this is occurring after a retry loop of 10 (which, if I did my math right, has less than a 0.001% chance of happening if a 'normal' RDSEED has a failure rate of 30%), then most likely the best case is someone is simply probing you system (or your host) for weaknesses. In the worst case, there's an actual attack in progress. Immediately an panic might be the correct response.

[0] Alternately a non-"admin" managed to control your kernel command line (or equivalent), but if that is the case, you have other, very different, and much worse problems.

panic_on_warn

Posted Feb 8, 2024 18:29 UTC (Thu) by corbet (editor, #1) [Link]

Yes, the system is behaving as configured in that setting. Still, developers need to be (and are) aware that issuing warnings can have that effect in a fairly wide swath of deployed systems and consider warnings carefully.

Pitchforks for RDSEED

Posted Feb 9, 2024 0:08 UTC (Fri) by Jannes (subscriber, #80396) [Link] (1 responses)

I understood this as an *unprivileged* process being able to bring down the entire machine. That's probably not the admin's intention.

A misbehaving app should just crash itself, not bring down the entire kernel and thereby DOSsing all other apps.

Pitchforks for RDSEED

Posted Feb 24, 2024 15:14 UTC (Sat) by DanilaBerezin (guest, #168271) [Link]

But the process isn't bringing down the machine, a failing `RDSEED` -- which is kernel functionality -- is. I think a warning is warranted in this case especially when the only known cause for this is a deliberate attack on the randomness subsystem. If crash on warn is enabled and this warning causes the system to crash, that would be the admins fault.

Pitchforks for RDSEED

Posted Feb 9, 2024 10:42 UTC (Fri) by taladar (subscriber, #68407) [Link] (1 responses)

I don't think you can calculate the probability of repeated failures in the retry loop like that. It is not as if they are independent events. If entropy is exhausted in one RDSEED instruction it will not be just as likely to be restored to a usable level if the next CPU instruction is another RDSEED as it would be for a random RDSEED occurring after many other instructions.

Pitchforks for RDSEED

Posted Feb 9, 2024 13:58 UTC (Fri) by dullfire (guest, #111432) [Link]

I was actually just thinking about that.

I think you MIGHT be able to maintain that probably format, if there's a change (possibly delays) you can make to make the next RDSEED mostly unrelated to the first. Also note that isn't not necessary to try accounting for things like another thread attempting to drain entropy (since that would be an attack, in which case a warning, or panic if panic_on_warn, is a perfectly sane response)

IF that's possible[0], then you just need to pick a loop count that makes the likelyhood of successive failures unreasonably small.

Although, honestly I think the sanest course of action would simply to dedicated hardware (that requires privilege to access to use) in the non-cloud case. In my humble opinion the whole notion of confidential cloud compute is intractable, so I have no proposed solutions for it .

[0] I think it should be, but have no proof.

Pitchforks for RDSEED

Posted Feb 10, 2024 15:26 UTC (Sat) by mathstuf (subscriber, #69389) [Link]

> If this is occurring after a retry loop of 10 (which, if I did my math right, has less than a 0.001% chance of happening if a 'normal' RDSEED has a failure rate of 30%)

That sounds like you're assuming events are independent. I feel like there's some level of dependence when one is right after another (i.e., failures will cluster).

Pitchforks for RDSEED

Posted Feb 8, 2024 19:24 UTC (Thu) by vstinner (subscriber, #42675) [Link]

> he observed that RDSEED failed nearly 30% of the time

"Result on my i7-11850H: RDRAND: 100.00%, RDSEED: 29.26%"

So RDRAND has a success rate of 100% (and 0% failure), and RDSEED has a success rate of 29.26% which means around 70% of failure, no?

Pitchforks for RDSEED

Posted Feb 8, 2024 19:24 UTC (Thu) by corsac (subscriber, #49696) [Link] (1 responses)

Seems that on my older system (i5-5200U) rdseed never fails

Pitchforks for RDSEED

Posted Feb 9, 2024 10:12 UTC (Fri) by adobriyan (subscriber, #30858) [Link]

5950X (microcode 0xa20120e) doesn't seem to fail rdseed.

Pitchforks for RDSEED

Posted Feb 9, 2024 9:44 UTC (Fri) by james (subscriber, #1325) [Link]

...the host controls a guest's scheduling and could, in theory, interfere with a retry loop as well. If retries are forced to happen when the random-number generator is exhausted, they will never succeed.
Or the host could just never let the guest run? Same effect?

Pitchforks for RDSEED

Posted Feb 9, 2024 11:37 UTC (Fri) by spacefrogg (subscriber, #119608) [Link] (12 responses)

I have worked in the field of hardware true random number generation and I can tell you with confidence: However you build a system that contains a TRNG, you will always find a significant amount of instances in which the TRNG will fail at some point.

Any expectation that the OS can just assume (or even assert) that it is an error that a TRNG fails at some point, is severely misguided.

I can even deliver a mathematical argument if you desire. Any functionality test of a TRNG must restrict itself to use a limited amount of TRNG output to draw its conclusion. Otherwise it would never finish. Any TRNG must eventually produce a sequence, by chance(!), that contradicts the functionality test. Otherwise it would not be completely random, because it would specifically avoid the test patterns. Conclusion: Every TRNG that does not fail from time to time is already failing.

Yes you can shift the probabilities etc. pp., but you cannot avoid the fundamental mechanics. By delivering billions of CPUs each running for thousands of hours, you will find those instances.

Pitchforks for RDSEED

Posted Feb 9, 2024 12:18 UTC (Fri) by zx2c4 (subscriber, #82519) [Link] (4 responses)

The issue isn't that it fails but that one trust domain can potentially force failures in another. If it would "eventually succeed", one could try again and again until success. But if a different domain can induce perpetual failure, then we've now got two problems. So solutions include making RDRAND keep generating stream output without fail, or imposing some sort of cross-domain fairness with regards to failures. The threads on LKML discuss this.

Pitchforks for RDSEED

Posted Feb 9, 2024 12:42 UTC (Fri) by spacefrogg (subscriber, #119608) [Link] (3 responses)

True, I was not arguing against that. It came across in the article that Dave Hansen suggested to consider failing TRNGs to signify broken hardware, which is not a proper way to look at the issue.

Cross-domain fairness seems to be hard to achieve when I think about it, especially because entropy is such a scarce resource. Access to the entropy source should be exclusive to the kernel(s). Applications should have to make due with derived PRNG values. But I am just thinking out loud...

Pitchforks for RDSEED

Posted Feb 9, 2024 12:59 UTC (Fri) by spacefrogg (subscriber, #119608) [Link] (2 responses)

Thinking about it further, entropy is like CPU time. Once consumed by a process, you cannot get it back (opposed to memory), but you have to let time pass. So, an entropy-access scheduler seems to be one solution. Processes would need to announce to the kernel that they want to be part of the list of entropy users and get a scheduled and limited amount of RDSEED calls. Obviously, this needs close monitoring of the systems entropy state, which might be hard to do and may end up to be as good as limiting calls to x/sec.

Pitchforks for RDSEED

Posted Feb 9, 2024 13:28 UTC (Fri) by zx2c4 (subscriber, #82519) [Link] (1 responses)

These are unprivileged CPU instructions. This isn't a kernel scheduler issue.

Pitchforks for RDSEED

Posted Feb 9, 2024 14:31 UTC (Fri) by spacefrogg (subscriber, #119608) [Link]

Then the CPUs are actually broken. You must guard your entropy state or its useless to you.

Daniel J. Bernstein has an excellent write-up about how you could potentially mis-use an entropy source to force your encryption scheme to leak your private key alongside your ciphertext while making it look perfectly fine. I just fail to find it right now. (It was concentrating on why encryption schemes should not mindlessly access entropy)

The argument is quite simple: You do know how the encryption scheme distributes bits. If you control the entropy bits and the encryption scheme, you can hide the private key in the entropy bits and make the ciphertext partially predictable, enough to recover the private key and thus the original message.

Pitchforks for RDSEED

Posted Feb 9, 2024 12:30 UTC (Fri) by smurf (subscriber, #17840) [Link] (2 responses)

Sure – but you can make this number of instances less significant than, well, any arbitrary probability you desire. Simply retry often enough so that it only happens once per century, assuming that a billion CPUs do nothing but call RDSEED.

Pitchforks for RDSEED

Posted Feb 9, 2024 12:47 UTC (Fri) by spacefrogg (subscriber, #119608) [Link]

I don't think you understand the relations. You cannot, be assured. (At least not by the way of waving your hands).

If you really must and invest a lot of money, the best you can currently achieve is the quality level (very high), speed (kbit/s), chip size (10mm^2) and usage intervals (<100 per day) of smartcards.

Pitchforks for RDSEED

Posted Feb 9, 2024 12:52 UTC (Fri) by Wol (subscriber, #4433) [Link]

And I think you need to study some statistics. There's a reason buses come in threes ...

Cheers,
Wol

Pitchforks for RDSEED

Posted Feb 9, 2024 13:43 UTC (Fri) by epa (subscriber, #39769) [Link] (1 responses)

Oh, I completely misunderstood the article in that case. By the instruction ‘failing’ I thought it meant generating some kind of fault or trap, and not giving any value back. If it appears to work, but the values are no longer random enough for practical use (they have become predictable) then that is a much harder kind of failure to detect and handle.

Instruction failure

Posted Feb 9, 2024 14:31 UTC (Fri) by corbet (editor, #1) [Link]

The RD* instructions do not fail silently; the CPU sets the carry flag to indicate whether they succeed or not.

Pitchforks for RDSEED

Posted Feb 9, 2024 14:01 UTC (Fri) by DemiMarie (subscriber, #164188) [Link]

The problem is that there is usually very little software can do to handle that failure.

Many random number generation APIs are infallible: they promise to always succeed. Always. The simplest way for software to handle this is to busy-spin until success. If the RNG is working, this succeeds with probability 1, and the likelihood of more retry loops being needed decreases exponentially with time.

I’d much rather have RDAND use a CSPRNG like Fortuna that never runs out of entropy once seeded, block the boot until the RNG is seeded, and reseed with entropy from the TRNG whenever possible. Fortuna is designed to be secure even if the new entropy is malicious, so one can feed raw output from the TRNG without any further conditioning. Entropy estimation is then only needed to determine when, and if, RDRAND needs to block. There would be no error returns for software to check: if the TRNG is not working for long enough, you get a machine check exception.

Pitchforks for RDSEED

Posted Feb 9, 2024 14:22 UTC (Fri) by khim (subscriber, #9252) [Link]

> I have worked in the field of hardware true random number generation and I can tell you with confidence: However you build a system that contains a TRNG, you will always find a significant amount of instances in which the TRNG will fail at some point.

Nope. Or, rather: technically yes, practically no.

You are forgetting one fundamental truth: there's non-zero probability (around 10⁻¹² or something like that) that anything at all would fail in a computer. Even simple jz test_passed may jump to a wrong place once-in-a-while.

> Conclusion: Every TRNG that does not fail from time to time is already failing.

Why should it fail significantly more often than once per 10¹² calls, though? That is the question and I don't see why that would be the case.

We ignore probabilities smaller than these (simply because we don't really, have much choice) and if we do that than making non-fallible (in a practical sense) TRNG should be possible.

Pitchforks for RDSEED

Posted Feb 9, 2024 16:54 UTC (Fri) by zx2c4 (subscriber, #82519) [Link]

This didn't make it in time for publication, but here's the latest patch on the matter: https://lore.kernel.org/all/20240209164946.4164052-1-Jaso...

Pitchforks for RDSEED

Posted Feb 13, 2024 4:49 UTC (Tue) by jcm (subscriber, #18262) [Link]

There are (shockingly) architectures beyond x86. I know! It's surprising isn't it ;) But anyway, this isn't a unique problem. However, on at least one other architecture we do have some platform implementation guidance around rate of reseed and consideration for denial of service from bad actors sharing a socket.


Copyright © 2024, Eklektix, Inc.
This article may be redistributed under the terms of the Creative Commons CC BY-SA 4.0 license
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds