-
Notifications
You must be signed in to change notification settings - Fork 13.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Tracking Issue for secure random data generation in std
#130703
Comments
Disclaimer: I am one of the I think it's important for It may be worth to add the following methods to
It's also not clear whether it's allowed to overwrite the default |
Would you consider rust-lang/libs-team#159 to be a better solution? That one used the |
No, I don't think it's an appropriate solution. Firstly, it relies on For the last point I guess we could define a separate |
I don't think this needs to be a blocker. IMO a lot of |
Would it be better to make this explicit with generics? Like |
I (mostly) disagree. CSPRNGs should almost never fail. When they do, users are almost never not qualified to diagnose the problem. For example: golang/go#66821 A compromise is something like this: trait RandomSource {
type Error;
fn fill_bytes(...) {
self.try_fill_bytes(...).unwrap();
}
fn try_fill_bytes(...) -> Result<..., Self::Error>
} This allows most CSPRNGs to use |
@ericlagergren As for design of fallible RNG traits, see the new |
This trait (and the topic of random value generation) should be removed from this discussion entirely in my opinion, focussing only on "secure random data generation" as in the title. Why: because (1) provision of secure random data is an important topic by itself (with many users only wanting a byte slice and with methods like Disclaimer: I am one of the |
Overriding the default source in an application that already has one from linking If overriding the
This wouldn't be a problem if the entire ecosystem could agree to always delegate this problem to on one specific crate (version) with appropriate hooks, like Edit: almost forgot that even |
There is a number of reasons to allow overriding:
Yes. How about following the Either way, overriding is probably can be left for later. I think we both agree that we need a way to expose "system" entropy source in
I agree that ideally we need a unified approach for this kind of problem. I made a similar proposal once upon a time. But I think it fits fine? Targets with
I believe that having
Well, it has happened, sort of. The problem is that
Can we add yet another sysroot crate for |
The RFC (and the competing ones I've looked at) only supports a default implementation in the crate that "declares" the externally-implementable thing. If that crate isn't
I was specifically talking about
Not to point any fingers but a counter example that's fresh on my mind because I looked at its code recently is foldhash. As another example,
Possibly, but people may object to a proliferation of sysroot crates so let's hope there's a better solution. |
Yes, it's not as if RFC is a technical specification which must be followed word-by-word. There is a number of cases where the original RFC vision has somewhat changed during implementation stages. If anything, I would say it's an oversight/deficiency of the RFC to not cover cases like this.
If a crate aims to minimize its number of dependencies as far as possible even at the cost of code quality and security, it obviously will not depend on |
As I said, I have no intention of pointing fingers at any crates. They have to navigate tricky trade-offs and complexities due to Rust's standard library (as a whole, not just |
The Random trait seems weakly motivated in terms of coupling it to RandomSource, as its design seems like it will be a much more hotly contested space, and it is (mostly) unrelated to RandomSource. |
Note that @dhardy (maintainer of rand) wrote some criticism of this at rust-lang/libs-team#393 (comment) |
Could let random_array: [u64; 100] = random(); which is a lot more convenient than using the |
I think it would be useful to have a data type like |
I disagree that it is useful in testing implementations of traits like |
As far as I know there’s no plan to make the |
It seems like one might regret adding For more complicated types (e.g., It seems better to leave this out rather than leave it in this poorly specified state. Just stabilize Although... even |
I really don't think so. The current interface works perfectly fine, it's just slightly suboptimal in some use cases (fewer than in the context of general byte-centric I/O). Providing only a So I think it's pretty clear that the method for filling a |
Perhaps something like this would work: // Users don't need to concern themselves with this type.
pub strut OutBuf([MaybeUninit<u8>]);
impl OutBuf {
// various methods to construct it and write to it, no method to read from it
}
// Users don't need to worry about implementing this, just use it
pub unsafe trait AsOutBuf {
type Init: ?Sized;
fn as_out_buf(&mut self) -> &mut OutBuf;
// Only allowed if the entire buffer was overwritten.
unsafe fn assume_init(&mut self) -> &mut Self::Init;
}
impl AsOutBuf for [u8] { type Init = [u8]; ... }
impl AsOutBuf for [MaybeUninit<u8>] { type Init = [u8]; ... }
impl<const N: usize> AsOutBuf for [u8; N] { type Init = [u8; N]; ... }
impl<const N: usize> AsOutBuf for [MaybeUninit<u8>; N] { type Init = [u8; N]; ... }
// Users can pass in any byte slice, byte array or their uninitialized counterparts
// The entire value is guaranteed to get overwritten, so if using the returned value is undesirable due to borrowing issues the users can simply use the original value safely, if it was an initialized type to begin with or they may just soundly call `assume_init` on it.
pub fn fill_random_bytes<T: AsOutBuf>(buf: &mut T) -> &mut T::Init { ... } This way it already supports all reasonable cases and people are not forced into using uninitialized API. They can write |
That helps the simple call sites remain simple, but it has several downsides:
|
No, I have not.
This is really not a new problem. It might be instructive to look at what Go does, for example. |
I dunno what's happening here, but usually when conversations about "how do I get random numbers?" veers into being super large with lots of diverse opinions, insanity reigns supreme and implementations quickly fly off the rails. So, seeing that Rust is kinda important to get right, I wanted to nip this tendency in the bud. With that out of the way, and reading some comments above to the tune of, "oh no, but I heard this one interface was rumored to be somewhere somehow broken!", I'd like to suggest that you just stick to each OS's normal interface and not try to do anything fancy or weird. Implement it in the vanilla way, and then if there are bugs because of environments you didn't foresee, you can investigate that on a case-by-case basis and see what needs to change, if anything at all. I wrote my suggestion for Linux (and other OSes) above. Just do that. If it doesn't work someplace, you won't be any different from most other runtimes and environments, and you can deal with the issue then. As far as I can tell from watching this play out over a long period of time, the pitfall to watch out for is using a loop and checking for |
what do we do on systems where there's intentionally no secure random source available, e.g. wasip2 with only |
panic? Or refuse to compile? If the platform doesn't provide it, it's not like that's a real runtime error a programmer needs to care about. |
I think there should be a |
Sounds like an utter disaster waiting to happen. Please don't do that. |
First, let me say that I am explicitly trying to avoid wading into the argument over whether anyone, anywhere should be using |
why? it seems like the best option to me, since you know at compile-time if you're trying to use something that doesn't exist on your target, rather than finding out at runtime when code unexpectedly panics, or returns errors... having a |
IIRC using such flags isn't currently something that is done in other parts of the standard library (except atomics, but they also depend on the ISA rather than the OS). I do, however, find it worth noting that we will still have to handle edge cases where RNG is for whatever reason not available (be it by panicking, aborting, or something third), thus rendering the |
I think they mean "std::random only exists if the config is enabled, and it's only enabled for certain platforms" not "std::random is only a CSPRNG if you enable the config." |
yes, or at least whatever functionality requires a secure random implementation; trait definitions (just the interface) and insecure randomness as a separate interface can still be enabled.
#ifdef SECURE_RANDOM_EXISTS
void fill_with_secure_random_bytes(void *buf, size_t size) { ... }
#endif |
The standard library should just provide two interfaces here:
In other words, the Regarding error handling, clearly compile-time errors are superior, but are they sufficient? E.g. Linux's |
It can but there's no point having a function available if it's guaranteed to fail. Just as we don't have Windows API on Linux made to always fail even though on Windows it can sometimes fail. |
I don’t know if “has a source of entropy” makes a lot of sense as a static property of compiler targets. As discussed in earlier comments, it would be very useful if the “give me some entropy” API was available in |
The policy in |
I consider that compile-time category - as opposed to runtime. It's definitely still better than runtime.
I don't think it's that simple. If you don't use conditional compilation you're relying on either dead code elimination or assuming nobody needs to use the library without randomness. (IOW everyone is able to provide an impl.) Further, there are cryptographic algorithms that can make use of additional entropy to make side channel attacks harder/less likely but don't strictly require it. I co-maintain one such thing. It'd make a lot of sense for them to conditionally enable this protection depending on availability of RNG. (Though being able to emit a warning if it's off would be nice.) I think what would make more sense is rather than link-time error provide the
I co-maintain such library. Exactly, we learned by someone filing an issue - somebody who didn't need
I haven't seen this happen anywhere outside wasm. And it's a horrible approach. Things suddenly panicking which is annoying to debug, annoying to communicate downstream, annoying to work around. Funnily, this exactly happened in the above mentioned library exactly with RNG and exactly with optional hardening. A feature was added there to workaround it but it really wasn't a great solution. Libraries need to know when some API they need is definitely unavailable. Sometimes because they can workaround things, sometimes because they can disable part of the functionality and still be useful and sometimes just to communicate it to the consumers properly. |
On the subject of error handling:
The only platforms where random data generation can reasonably fail are WASI and UEFI – and in the case of WASI, this is unlikely because the host platform is probably one of the above platforms, thus ensuring that the request succeeds. UEFI is the only environment where there may truly legitimate reasons for random number generation failure, as it lacks a way to block on the availability of sufficient entropy. On all other platforms, requests will either always succeed (potentially after blocking) or fail for unrelated reasons (e.g. restrictions imposed by In conclusion and after reading the above discussion, I think // in std::io
fn read_random(bytes: &mut [u8]); and call it a day. Everything else involves trade-offs between complexity, stability and security that conflict with the stability guarantees of the standard library. And quoting from
Providing only a |
Hermit (processes, pipes), SGX (filesystem, processes, UDP, pipes), SOLID (command line arguments, processes, pipes), TEEOS (nearly everything), UEFI (threads, pipes, other things are WIP), WASI (processes, pipes), Xous (filesystem, environment variables, pipes), ZKVM (nearly everything). I agree that this isn't ideal, but this is not the issue for that discussion. |
In my experience with reversing UEFI implementations, most of the time UEFI is just calling rdrand/rndr, which generally doesn't fail.
I agree. And then compile time error if the platform doesn't implement it. |
Except for some known cases where RDRAND always returns This issue is about secure random data generation, so if some platform happens to have known bugs in its random source, this cannot be ignored. I think it also cannot be handled as a compile-time error, thus run-time error handling is a must: either return an error code or panic. |
Ahh right the CPU bugs that get ironed out through microcode updates. If your hardware is broken, all bets are off. Just panic() and update your CPU microcode. This isn't something the programmer should need to account for. There are lots of CPU bugs that lead to all sorts of crazy behavior. This isn't Rust's responsibility. Anyway, fortunately this isn't an issue on real OSes with a proper multisource RNG and such. |
wasi's randomness interfaces are specifically designed such that some wasi implementations simply don't have the secure random functions (if you try to use them it will fail to run your wasm with a link error), because the whole wasm engine is intentionally running in a deterministic-only mode, even if the host is unix or windows where secure randomness is available. rust |
That should probably be a separate target anyway. Even if std doesn’t assume it can import all of the randomness related WASI interface, third party crates like |
As noted above, panicking or returning an error on unsupported functionality in The WASI/WASM case is yet another argument why we need to introduce an "insecure" randomness source. We can mandate that this source may use PRNG seeded with a fixed seed on platforms which do not provide proper system randomness. |
This seems okay for general use but I think it could actually be very dangerous. If I'm given an uninitialized buffer: let mut buf = [MaybeUninit::<u8>::uninit(); 100]; I might be tempted to convert it to a let slice = unsafe {
std::slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, buf.len())
};
read_random(slice); Unfortunately, calling I would recommend changing the signature to: fn read_random(bytes: &mut [MaybeUninit<u8>]); and also adding a guarantee that if this function succeeds, all the bytes in the slice will have been initialized (i.e. calling EDIT: upon further discussion this might be a good use case for BorrowedBuf? |
Requiring every caller to write a bit of I don’t find your new argument very convincing: casting an uninitialized slice to |
What if we make the
// core::marker
// Possibly as a language item:
pub /* unsafe */ trait FromBytes { } // Or with some other name...
// std::io
pub fn read_random<T: FromBytes>(buf: &mut T); We could then add an extra promise that the buffer referenced by (edit: I would personally prefer the name |
To be fair, it seems like many people here are overtly hostile to the "common uses", e.g. see above
But since every low-level OS API that I'm familiar with takes in a pointer and a length, it might be wise to have a lower-level function giving you direct access to it and avoiding any overhead or abstraction like: pub unsafe fn read_bytes_raw(dst: *mut u8, len: usize); and then also have higher-level APIs covering plenty of common stuff like generating numbers or vectors which don't require using
It seems like your proposed |
I would argue that it is different in that my proposal would randomise the raw representation of the buffer – regardless of how this effects its effective value (whereas the current |
Well, |
Feature gate:
#![feature(random)]
This is a tracking issue for secure random data generation support in
std
.Central to this feature are the
Random
andRandomSource
traits insidecore::random
. TheRandom
trait defines a method to create a new random value of the implementing type from random bytes generated by aRandomSource
.std
also exposes the platform's secure random number generator via theDefaultRandomSource
type which can be conveniently access via therandom::random
function.Public API
Steps / History
random
feature (alternative version) #129201Unresolved Questions
gen_bytes
andDefaultRng
, the implementation PR usesfill_bytes
andDefaultRandomSource
(see arguments progen_bytes
and profill_bytes
)Footnotes
https://std-dev-guide.rust-lang.org/feature-lifecycle/stabilization.html ↩
The text was updated successfully, but these errors were encountered: