rust-random / getrandom

A small cross-platform library for retrieving random data from (operating) system source
Apache License 2.0
275 stars 180 forks source link

generators: Expose specific generators #495

Closed mrkajetanp closed 4 weeks ago

mrkajetanp commented 2 months ago

Currently users of getrandom cannot choose which specific generator they want to use if multiple are available on their system. As the preferred generator may vary on a case-by-case basis, it is worth exposing an interface that enables doing so.

Without affecting the preexisting functionality, this commit adds a new "generators" module with a macro that can be used to generate function wrappers with the same signature as getrandom() for each specific generator.

The generated functions can be used analogously to getrandom(), as follows:

getrandom(&mut buf).unwrap(); // <-- unchanged, default getrandom usage
generators::use_file(&mut buf).unwrap();
generators::linux_android_with_fallback(&mut buf).unwrap();

Due to the way conditional compilation of modules is currently handled in the crate, this change required a rework of lib.rs in order to split up selecting which modules to compile based on the target and selecting which module to use as the preferred or default one.

In order to achieve that, a new module cfg_module.rs was added to contain a macro cfg_if_module which takes a module name and a block of code, then resolves to a cfg_if! containing the given block conditional on the targets that the module can be compiled for. This way the targets for each module can be configured once within the macro and then reused across the crate as shown in the snippet below. This approach avoids having to keep several sets of long target config attributes in sync in different parts of the crate and makes the code more maintainable.

cfg_if_module!(linux_android_with_fallback, {
    mod linux_android_with_fallback;
});

This change does not intend to modify the default backend selection logic. This split also makes it possible to avoid having to repeat the same module declarations for modules that are dependencies of more than one backend (especially mod util_libc).

As an example, an aarch64 Linux platform with the RNDR register available could use the use_file, linux_android, linux_android_with_fallback, rndr or rndr_with_fallback backends. All of them will be compiled and accessible through "mod generators", but only one of them will be selected and compiled as "mod imp" to be the crate default.


As an added explanation, the list of target cfgs in the big macro is not the same list as the target cfgs in lib.rs that determine which file ends up as mod imp. The cfg_if_module macro is intended to have target cfgs for any target for which the module can be compiled. The ones in lib.rs have target cfgs for which the module is "preferred", i.e. one is meant to describe technical compatibility and the other a crate policy choice. Consequently, most likely the specific target cfgs in the macro will end up being adjusted, especially for the more esoteric platforms I'm not familiar with, because I mostly copied them over from the policy configs and just added the things I knew for sure would be available (e.g. file reads on /dev/random on macos). But that's both easy to do and wouldn't break any functionality already in the crate anyway.

newpavlov commented 2 months ago

I am not sure if we want to do this. The main goal of this crate is to provide the "default" entropy source. Ideally, we want for this crate to be superseded by std.

If you want to expose an RNG source the rand project provides the way using traits defined in rand_core. For example, see the rdrand crate.

mrkajetanp commented 2 months ago

I am not sure if we want to do this. The main goal of this crate is to provide the "default" entropy source. Ideally, we want for this crate to be superseded by std.

Well the thinking is that the 'default' can be use-case specific. In the comments for the other PR you did mention wanting to have an opt-in mechanism of some kind, this could be a way to do it.

If you want to expose an RNG source the rand project provides the way using traits defined in rand_core. For example, see the rdrand crate.

That's an option sure, but that sounds like quite a lot of duplication for very little benefit. The users would need to pull in several different crates reinventing the wheel and reimplementing the same functionality that's already implemented in this crate just not exposed.

Imo the mail goal being the "default" entropy source makes perfect sense, but at the same time the crate making that choice only at compile time and never being able to change it after seems a bit too restrictive? This way we could have the default functionality be unchanged but also have an opt-in mechanism for users who want it.

newpavlov commented 2 months ago

Imo the mail goal being the "default" entropy source makes perfect sense, but at the same time the crate making that choice only at compile time and never being able to change it after seems a bit too restrictive?

Allowing runtime changes of the default entropy source is a potential security hole. It may be worth to introduce opt-in compile-time changes, but certainly not runtime.

This way we could have the default functionality be unchanged but also have an opt-in mechanism for users who want it.

Users rarely use getrandom directly. Most users use it through crates like rand. RNDR support is relatively small, so it can live perfectly well in a separate crate, even if later we will duplicate it into getrandom in some form. Also note that sophisticated users always can use a patched version of getrandom for their builds.

mrkajetanp commented 2 months ago

Allowing runtime changes of the default entropy source is a potential security hole

For sure, I'm not suggesting we allow changing the default at runtime. Just that we allow selecting which backend to use at runtime depending on the use case. For most cases the default is fine, for some other cases users might want to access a specific backend that might not be the default which is currently not possible.

If the goal is for this to eventually live in std something like this will have to be done regardless because you can't expect people to have to recompile std just to access a different generator backend.

Users rarely use getrandom directly. Most users use it through crates like rand.

Yes that's the goal here. My intention is to have a special generator in rand which rand users could use to generate numbers from a hardware RNG directly. Rand uses this crate for interacting with the hardware, which is why I'm proposing the hardware interface here first because as it stands I can't request a specific backend from rand because there is no interface. This rand generator when used by the end user on the inside would then be calling getrandom::generators::hardware() which would then return a number from whichever hardware generator backend is available on the platform.

mrkajetanp commented 1 month ago

Ironed out the build issues on misc targets as well, apologies for the delay. It is completely opt-in so users who just call getrandom::getrandom() will not see any differences due to these changes.

mrkajetanp commented 1 month ago

If you're not interested in accessors for specific generators being available in this way, would you like me to just close this PR then? No point keeping it open if it's not going to move forward anyway.

newpavlov commented 4 weeks ago

Closing this as discussed above. I will open a separate issue for this feature instead.