Skip to content
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

ThreadRng / EntropyRng improvements #579

Closed
wants to merge 2 commits into from

Conversation

dhardy
Copy link
Member

@dhardy dhardy commented Aug 2, 2018

Next step: allow custom entropy source. Unfortunately the EntropySource trait is still not good enough for run-time injection because the size & alignment of instances is unknown (it would be nice if this could be enforced somehow).

@dhardy
Copy link
Member Author

dhardy commented Aug 4, 2018

Updated: I added a CustomEntropySource trait and set_custom_entropy function.

There are a number of things I don't like about this. The code is untidy. We can't actually unimplement Send directly in stable Rust; this isn't required but could be useful. And I used std::sync functionality which isn't available in core; I guess we should use the spin crate instead.

I'm also unsure whether we want EntropyRng to behave like this (do necessary init and create a small data structure each time called) — it could instead behave more like thread_rng except create a single static instance which can only be accessed with locks each time.

@dhardy dhardy added the E-question Participation: opinions wanted label Aug 10, 2018
@dhardy
Copy link
Member Author

dhardy commented Aug 10, 2018

I would appreciate some feedback on this, in particular the design using a set_custom_entropy function. This has some advantages and disadvantages:

  • no extra crates or extern(C) code needed
  • memory-safe design (anything involving global mutables is potentially unsafe, but I think this design avoids all issues so long as any pointer passed by the user really does have 'static and isn't invalidated by a second call to the function)
  • relatively simple to use
  • any code may set a custom entropy source; this is also a potential security risk, though not a big one since (a) users have to trust library code anyway and (b) this entropy source is only used if the OS source is unavailable

Alternatives with static linkage are (1) extern(C) functions implemented by the user

  • must resolve to a single instance at compile time
  • not memory safe to use; implementation requires unsafe and pointer casts

or (2) an external crate

  • must resolve to a single instance at compile time
  • complexity of using an extra crate and patching the Cargo.toml; however if users want to put the custom entropy source in a separate crate anyway then this is probably not any more complex than other solutions

So perhaps using an external crate is the best option?

/// Is this source available?
///
/// The default implementation returns `true`.
fn is_available() -> bool { true }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we even provide a default implementation? I feel like this should always be implemented explicitly.

@@ -48,6 +54,7 @@ use rngs;
#[derive(Debug)]
pub struct EntropyRng {
source: Source,
_dummy: Rc<()> // enforce !Send
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably cleaner to use core::marker::PhantomData here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PhantomData<Rc<()>>?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, probably. I'm not sure that Rc<()> is a zero-sized type.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to not rely on Rc just for !Send, raw pointers are not send: https://play.rust-lang.org/?gist=e350c6d98704bae32e0feb593687bf62&version=stable&mode=debug&edition=2015

@vks
Copy link
Collaborator

vks commented Aug 28, 2018

Do we have a use case for set_custom_entropy? The only thing I can think of is some weird no_std platforms. Hardware RNGs come to mind as well, although they should probably be used by the OS directly, not by userspace.

The other two commits look fine to me.

@dhardy
Copy link
Member Author

dhardy commented Aug 28, 2018

Yes, this is for no_std platforms; this is also to allow us to make thread_rng available everywhere (but with potential run-time failure). No, I don't have an immediate use-case, though given that embedded implies no OS, it's basically required that the user provide an entropy source.

@vks
Copy link
Collaborator

vks commented Aug 28, 2018

Maybe we should get some input from the embedded community to make sure we are addressing their needs?

cc @japaric

@japaric
Copy link

japaric commented Aug 28, 2018

I don't have the bandwidth to look into this right now but I'll tell the other WG members to look into this during our next meeting (which is in 5 minutes)

@therealprof
Copy link

Can someone sum up what the changes are vs using rand::SeedableRng and rand::ChaChaRng seeded by a hardware RNG?

@dhardy
Copy link
Member Author

dhardy commented Aug 28, 2018

The main point is that thread_rng is currently only available with the std lib, but we wish to change that.

Specifically, this PR is about making EntropyRng able to function without the standard entropy sources (via set_custom_entropy function; as noted above this is one of three possible options).

There are still some other things to do to make thread_rng available with no_std, but we have a rough plan for achieving that (it involves replacing thread_local, probably with mutexes).

@therealprof
Copy link

@dhardy That's a noble goal but let me rephrase my question: With SeedableRng working today on no_std which advantages does EntropyRng bring on top and and what is going to happen to SeedableRng?

@therealprof
Copy link

Hm, I just had a closer look at the proposal. The set_custom_entropy() function does not seem very embedded friendly because it assumes that there's a way to jump into the CustomEntropySource impl at will which is not the case. In embedded there're really only two possible (and totally independent from a setup perspective) places of execution:

  1. The main loop
  2. Interrupt/Exception handlers

All information between those two must be exchanged using RefCells and Mutexes (or via 'statics), so in order to use the CustomEntropySource from an interrupt handler (which is usually what you would want to do from a performance perspective), one would have to find a way to access it from inside the interrupt handler. The alternative would be async polling from the main loop but conceptually you don't want to do too much of such janitor work from the main loop (if any at all, it's quite usual to just use the main loop for setup and do all work interrupt based).

In my opinion it would be more useful (and idiomatic) if the RNG conceptually worked more like a channel in that you can split the RNG into a entropy source part and a (cloneable?) RNG part which can be used on the spot or sent somewhere else for later use and even shared.

@dhardy
Copy link
Member Author

dhardy commented Aug 29, 2018

With SeedableRng working today on no_std which advantages does EntropyRng bring on top and and what is going to happen to SeedableRng?

EntropyRng can be used via SeedableRng, but that is only really useful in that it allows code to be portable across embedded (and std) targets. This is what thread_rng uses:

        let mut entropy_source = EntropyRng::new();
        let r = Hc128Core::from_rng(&mut entropy_source).unwrap_or_else(|err| panic!(...));

There are no changes planned for SeedableRng (except possibly requiring from_rng to be portable with regards to Endianness, but that's unrelated).


Yes, fill (and init if used) probably needs to use a mutex to protect access to the underlying source — which potentially allows dead-locking if interrupted while still locked (i.e. interrupts must be disabled while the entropy source is being used to fill some buffer or register).

Do you mean the main loop and interrupt handlers cannot share code? Or that global memory cannot be used to host the implementation? As shown by the CustomNoSource implementation, a static address using no actual memory can be used. set_custom_entropy only needs to be called once per process (not per thread). I don't see the issue here.


When it comes to thread_rng, then (unless some alternative to thread_local is available) it must use mutexes to protect access to the implementation — though this means that each call to next_u32 / next_u64 / fill_bytes / try_fill_bytes must lock the mutex and disable interrupts — which will be bad for performance. So whether this is really useful I don't know, except that it allows more libs to be portable across std and embedded platforms.

@therealprof
Copy link

Do you mean the main loop and interrupt handlers cannot share code?

Well, only via functions called from all sides.

Or that global memory cannot be used to host the implementation?

Sharing via memory is only possible using synchronisation primitives (e.g. RefCell and Mutex) or static mut (unsafe). I'm a bit doubtful that this will play nicely with set_custom_entropy. One of the programs where I currently use rand (although it's only seeded once during initialisation) looks like this:

static RNG: Mutex<RefCell<Option<rand::ChaChaRng>>> = Mutex::new(RefCell::new(None));
...
fn main() -> ! {
...
            /* Use hardware RNG to initialise PRNG */
            let mut rng = rng::Rng::new(p.RNG);

            let mut seed: [u8; 32] = [0; 32];

            /* Read 4 bytes of data from hardware RNG */
            rng.read(&mut seed).ok();

            let rng = rand::ChaChaRng::from_seed(seed);
            *RNG.borrow(cs).borrow_mut() = Some(rng);
...
}

interrupt!(RTC0, userng);
fn userng() {
...
    if let &mut Some(ref mut rng) = RNG.borrow(cs).borrow_mut().deref_mut() {
            use rand::Rng;
            let val = rng.gen::<u32>();
...
     }
}

In a nutshell: It's much easier to share something with an interrupt handler that was obtained from a call during initialisation than it is to share something that should ultimately be handled outside of main.

set_custom_entropy only needs to be called once per process (not per thread). I don't see the issue here.

There're no threads in no_std world. Threads share all of their state, interrupt handlers none.

When it comes to thread_rng, then (unless some alternative to thread_local is available) it must use mutexes to protect access to the implementation — though this means that each call to next_u32 / next_u64 / fill_bytes / try_fill_bytes must lock the mutex and disable interrupts — which will be bad for performance.

In a single_threaded world it's not as bad as it sounds. ;)

Coming back to my example above: What we'd really like to have here is separation of concerns; some MCUs have really nice TRNGs which can be set up and trigger an interrupt when they have a new nice true random value for us, so an interrupt handler should be able to seed that into the software RNG. On the other hand you might need random values in one or more other interrupt handlers so those should be able to fetch values when they need them. So the best approach would either be: Take an RNG, stuff it in a Mutex<RefCell<Option<>>> and allow any interrupt handler to grab it (in a critical section) either seed or draw and hand it back. Or alternatively split the RNG into a seeder and one or several reader pieces, just like a channel to allow for a less coupled usage.

@dhardy
Copy link
Member Author

dhardy commented Aug 30, 2018

some MCUs have really nice TRNGs which can be set up and trigger an interrupt when they have a new nice true random value for us

Using entropy when it becomes available rather than when your program/device starts is a challenge... the same one that has led to questionable behaviour in Linux in the past (producing output from /dev/urandom before sufficient entropy is available).

What is p.RNG in your main function above? Is that an entropy source you can rely on at start-up (as appears in your code, though you say 4 bytes and pull 32), or does one not really have unique entropy available on some platforms until a TRNG triggers an interrupt? Since we're being general here, I suppose the latter.

In that case, there are really only three directions:

  • initialise your PRNG with whatever entropy is available and inject more when available — i.e. "best effort" but with big security issues
  • make the PRNG block or error until it has enough entropy — with long boot time and/or risk of deadlock
  • use the same "solution" as Linux and save some entropy in persistent storage for use on start-up

Have you thoughts on this? I don't like the idea of being forced into a "best effort" compromise with potential security issues, but I don't know that the other options are viable in general (any kind of persistent storage would have to be handled outside of this lib).


That said, my idea for making thread_rng usable in no_std is similar to your RNG above, except with on-demand initialisation via a complex callback. But your example points out a simpler alternative: remove the on-demand initialisation (for no_std) but allow the user to directly initialise thread_rng.

In a single_threaded world it's not as bad as it sounds. ;)

If we use something like the spin crate for mutexes, is there some way these can be dropped when single threaded? Or is the overhead likely to be insignificant anyway?

@TeXitoi
Copy link

TeXitoi commented Aug 30, 2018

The spin crate might not be appropriate in some cases. For example, if you are on an interrupt driven paradigm, you can deadlock with the spin crate, but have better alternative as, for example, https://docs.rs/cortex-m/0.5.6/cortex_m/interrupt/struct.Mutex.html

See for example https://docs.rs/shared-bus/0.1.2/shared_bus/

@dhardy
Copy link
Member Author

dhardy commented Aug 30, 2018

Great, so there isn't even a good broadly-applicable synchronisation library available? Rand is supposed to be a general-purpose lib, not something targetting ARM Cortex M.

@TeXitoi
Copy link

TeXitoi commented Aug 30, 2018

shared bus propose an approach to this problem: an interface of a mutex that can be implemented by the user, with an implementation using std's mutex. The embedded dev can then use the spin crate or any custom mutex he want.

@therealprof
Copy link

What is p.RNG in your main function above?

A hardware RNG peripheral.

or does one not really have unique entropy available on some platforms until a TRNG triggers an interrupt

Depends. In this case you can poll the RNG if it has entropy (well, in some cases they're random numbers and not raw entropy) available and it will be somewhat quick, in the milliseconds range. In other cases you might not be so lucky.

Have you thoughts on this? I don't like the idea of being forced into a "best effort" compromise with potential security issues, but I don't know that the other options are viable in general (any kind of persistent storage would have to be handled outside of this lib).

Yeah, best effort is horrible. But OTOH shouldn't there be a way to re-seed entropy at a later point? I also don't think blocking is a good idea but you really want to let the user know: "Hey, I can't give you random values because: not enough entropy, you might want seed some", e.g. by returning a Result.

but allow the user to directly initialise thread_rng

Sounds great to me, but certainly want the option to re-seed later. That would also allow for re-seeding on demand (not sure the RNG will deplete the entropy pool or just continue chugging along) instead of periodically, e.g. when the RNG hands out an Err(OutOfEntropy).

@therealprof
Copy link

therealprof commented Aug 30, 2018

Great, so there isn't even a good broadly-applicable synchronisation library available? Rand is supposed to be a general-purpose lib, not something targetting ARM Cortex M.

Make the user specify the synchronisation implementation to use. ;)

@dhardy
Copy link
Member Author

dhardy commented Aug 30, 2018

Yeah, best effort is horrible. But OTOH shouldn't there be a way to re-seed entropy at a later point?

That's what I mean by best-effort. Output low-entropy (potentially guessable) "random" numbers now, but allow some type of entropy injection in order to improve security. But really if we go this route, we want some API to allow users the choice between "best effort available now" and "wait until it's secure".

shared bus propose an approach to this problem: an interface of a mutex that can be implemented by the user, with an implementation using std's mutex

Are you talking about Parking Lot's lock_api? This looks more like a building block for mutexes than something another lib can use. Or is there some other crate with user-replaceable mutexes?

Make the user specify the sychronisation implementation to use. ;)

Yes, but how?

@dhardy
Copy link
Member Author

dhardy commented Aug 30, 2018

That would also allow for re-seeding on demand (not sure the RNG will deplete the entropy pool or just continue chugging along)

PRNGs don't deplete the entropy pool (theoretically they have finite length, but the cycle length is huge). However, if they are initialised with zero entropy or very little entropy then an observer can potentially guess the next output. The trick to making them secure is to use sufficient entropy to start with (> ~100 bits). It's also important that "reseeding" or "entropy injection" only actually uses this once enough is available for whatever security criterion is chosen (e.g. 100 or 256 bits); since if the PRNG is directly adjusted each time a little entropy is available then "entropy exhaustion" attacks are still possible. But it's possible the TRNG implementation will do the necessary accumulation.

@therealprof
Copy link

That's what I mean by best-effort. Output low-entropy (potentially guessable) "random" numbers now, but allow some type of entropy injection in order to improve security. But really if we go this route, we want some API to allow users the choice between "best effort available now" and "wait until it's secure".

That's not what I meant, though. 😉

I would want the combination:

  • Don't hand out random values if not entropy is available
  • Don't block
  • Let the user know random numbers are not available due to missing entropy
  • Provide a way to add entropy

Yes, but how?

Check out https://crates.io/crates/shared-bus, I think it explains it nicely.

@dhardy
Copy link
Member Author

dhardy commented Aug 30, 2018

shared-bus works by letting the user inject the mutex implementation via the type system:

let manager = shared_bus::BusManager::<std::sync::Mutex<_>, _>::new(i2c);

This is not compatible with thread_rng however.

What might be best is a new crate, portable-mutex, which can enable an implementation via feature flags (as done in shared-bus), or can be replaced by a user implementation.

@therealprof
Copy link

PRNGs don't deplete the entropy pool (theoretically they have finite length, but the cycle length is huge). However, if they are initialised with zero entropy or very little entropy then an observer can potentially guess the next output. The trick to making them secure is to use sufficient entropy to start with (> ~100 bits). It's also important that "reseeding" or "entropy injection" only actually uses this once enough is available for whatever security criterion is chosen (e.g. 100 or 256 bits); since if the PRNG is directly adjusted each time a little entropy is available then "entropy exhaustion" attacks are still possible. But it's possible the TRNG implementation will do the necessary accumulation.

I see. For me it would be fine to just ignore additional entropy once enough has been supplied. I would still find it beneficial for initialisation purposes if I could try to use and if the PRNG deems that not enough entropy is available it'll just return an Err() and the application could supply more entropy via a seed function, rinse and repeat until sufficient entropy is available. The nice thing here is that I could nicely outsource the seeing to an interrupt handler and once enough entropy is available it would simply turn itself off while the user(s) of the random values could independently try to get values and decide themselves what to do if it doesn't work yet (e.g. retry and sleep).

@vks
Copy link
Collaborator

vks commented Aug 30, 2018

For me it would be fine to just ignore additional entropy once enough has been supplied.

This is actually what djb recommends (because additional entropy does not really improve security but might reduce it), so I agree this is probably the best approach.

@dhardy
Copy link
Member Author

dhardy commented Aug 30, 2018

@vks what do you think of the idea of making a portable-mutex crate which is basically a wrapper around std::sync::Mutex or Cortex-M barries or spin mutexes depending on feature flags? I was hoping something suitable already exists, but I don't see an existing option (except shared-bus, which doesn't advertise itself as providing this feature but appears to). I don't know whether there are any plans to make std::sync::Mutex available on (some) no_std platforms; but even if so it will likely be a while coming.

@burdges
Copy link
Contributor

burdges commented Sep 27, 2018

Are the unreachable!() bits in CustomNoSource really unreachable given it gets used as a trait object? I'm way outside the loop here but I'd think they should be panic!()s instead.

I kinda dislike exposing anything like set_custom_entropy on std platforms since afaik any crate could call it and the output gets used for cryptography. We could hide set_custom_entropy behind an rand_override feature that flows down from another crate, right? Are we worried that it must flow down from the top application crate, or else not cargo gives us two rand crates with one improperly configured? We could put set_custom_entropy in another crate, but doing so gets messy because rust lacks any visibility between pub and crate, right?

@alexcrichton
Copy link
Contributor

@dhardy sorry I'm absolutely overloaded right now and so I don't have time to work through the design here. Sorry about that :(

@dhardy
Copy link
Member Author

dhardy commented Sep 27, 2018

No that's fine Alex.

@newpavlov
Copy link
Member

Can't we make a mutable static which will hold a pointer to function which will be source of entropy? For example for modern linux it will be simply getrandom(2) syscall and for embedded platforms it will be a user-defined function which will access accumulated entropy pool or hardware entropy source (you will have to use unsafe to redefine the pointer, but I think it should be fine). And rand will provide (maybe in a separate crate) a convenience wrapper DefaultRng which will implement RngCore and will use the pointer under the hood. I am not well versed with such constructs, so I am not sure if it's implementable in manner which I've described.

@newpavlov
Copy link
Member

newpavlov commented Sep 28, 2018

So here is a small prototype which seems to work:

rand project crate (but probably not rand itself):

// in the real crate if `std` is enabled use syscalls and panicking function otherwise
// function must fill the whole buffer or return an error
pub fn default_entropy(buf: &mut [u8]) -> Result<(), u8> {
    buf.iter_mut().for_each(|v| *v = 1);
    Ok(())
}

pub static mut ENTROPY_SOURCE: fn(&mut [u8]) -> Result<(), u8> = default_entropy;

// we probably want to keep it as thin as possible
pub struct DefaultRng;

// in real crate use RngCore+CryptoRng instead
impl DefaultRng {
    pub fn new() -> Self { DefaultRng }
    pub fn try_fill_bytes(&mut self, buf: &mut [u8]) -> Result<(), Error> {
        unsafe { ENTROPY_SOURCE(buf) }
    }
}

Crate library:

extern crate rand;

use rand::DefaultRng;

pub fn foo() -> u32 {
    let mut buf = [0u8; 32];
    let mut rng = DefaultRng::new();
    rng.try_fill_bytes(&mut buf).unwrap();
    let mut c = 0u32;
    for v in buf.iter() { c += *v as u32; }
    c
}

App crate:

extern crate crate_lib;
extern crate rand;

// this function can use HW source or accumulated entropy pool
pub fn custom_entropy(buf: &mut [u8]) -> Result<(), Error> {
    buf.iter_mut().for_each(|v| *v = 2);
    Ok(())
}

fn main() {
    unsafe {
        rand::ENTROPY_SOURCE = custom_entropy;
    }
    println!("{}", crate_lib::foo());
}

App crate prints 64 instead of 32 as expected. Of course ENTROPY_SOURCE must not be overwritten by library crates outside of tests. We don't have any way to enforce it, so we'll have to rely on convention. Also calling entropy source must be thread/fork safe if target has such capabilities, and source must be cryptographically secure.

Alternatively we may want to split ENTROPY_SOURCE to two separate statics with different guarantees: CRYPTO_ENTROPY_SOURCE and ENTROPY_SOURCE. They will be used by DefaultCryptoRng and DefaultRng accordingly. The former will implement CryptoRng and the latter will not.

We also could make entropy source signature a bit closer to getrandom(2), e.g.: fn(&mut [u8]) -> Result<usize, Error>. This way we will remove requirement to fully fill buffer, which could prevent some nasty pitfalls (read all available entropy, but because it's not enough you'll discard it, and on next retry this process will be repeated, so you'll essentially waste entropy), but it will add some complexity on higher levels.

@dhardy
Copy link
Member Author

dhardy commented Sep 28, 2018

@newpavlov isn't that approach similar to the one implemented in this PR, except that it's slightly lower level (function pointer instead of trait object pointer)? Functionally I don't see much difference, and @burdges points still stand.

As @burdges says, it may make sense to hide this behind a feature flag, or only when not using std. But this similar to what I proposed above.

@newpavlov
Copy link
Member

newpavlov commented Sep 28, 2018

Yes, it's similar, but IMHO much simpler, you are always guaranteed to have "default" entropy source (but without std the default one will always panic). Additional machinery in this PR feels redundant to me. Plus we don't have Mutex on no_std targets. (well, I guess we could use spin crate, but IIRC it requires nightly) If entropy source have to access some data I think it can be handled without problems inside function.

I think we could omit set_custom_entropy altogether and expose static directly. It's a good idea to make this static public only if non-default feature is enabled, but I think for testing purposes we should allow overwriting it even if std is enabled.

And yes, I think this "default RNG" should be defined outside of rand.

Contrary to your proposal I don't think that we should provide anything for no_std except always panicking stub, and let embedded developers sort it out by themselves. I think that we even could remove JitterRng from default entropy setup.

@dhardy
Copy link
Member Author

dhardy commented Sep 28, 2018

You are aware that even reading static mut variables is unsafe, right? The reason being that if a read is simultaneous with a write there are no guarantees on what value is read (it can be anything). Possibly AtomicPtr can be used to get safe reads without a mutex, though I'm not sure if they are easily used for function pointers.

Contrary to your proposal I don't think that we should provide anything for no_std except always panicking stub, and let embedded developers sort it out by themselves. I think that we even could remove JitterRng from default entropy setup.

An "unavailable" stub for no_std sounds fine to me; users can implement a hardware RNG or import an "entropy pool" crate to implement this. But we should keep JitterRng in std; it was actually added because some DLL linkage failures occasionally caused OsRng to fail in Firefox.

@burdges
Copy link
Contributor

burdges commented Sep 28, 2018

I think Mutex exists only on std because it builds on native mutexes in std::sys_common::mutex which themselves only exit on std, and also must be boxed since they cannot be moved. We could avoid the box using static but not afaik the native mutex itself. I'd avoid even implementing this outside std myself but doing so requires a crate that implements mutex itself using atomics.

If we need set_custom_entropy then maybe a special rand_entropy or rand_override crate that depends on RngCore and from which Rng reexports safer bits but not set_custom_entropy. I'd give set_custom_entropy a scarier name too like override_secure_random_number_generator.

Afaik, there is never any reason to call set_custom_entropy twice but also no actual security benefit in forbidding multiple calls, but if you want to avoid Mutex then you could simplify your atomics using this requirement.

Why distinguish between EntropySource and CustomEntropySource?

@newpavlov
Copy link
Member

newpavlov commented Sep 28, 2018

You are aware that even reading static mut variables is unsafe, right?

Yes, this is why I've used unsafe { ENTROPY_SOURCE(buf) } in my snippet. I think it's fine to shift responsibility for preventing simultaneous read and writes to the writing part. In other words, when overwriting entropy source users must be sure, that default RNG is not used anywhere else, otherwise it will be a violation of unsafe contract. For embedded platforms it should not be a problem, as this will be done at initialization before main code execution.

It's possible to create AtomicPtr<fn(..)>, but AFAIK you'll have to transmute raw function pointer to be able to call this function. Here is an example. But it requires #[cfg(target_has_atomic = "ptr")], which not all embedded platform will have. I guess it should be possible to write something like this:

pub type EntropySource = fn(&mut [u8]) -> Result<usize, Error>;

#[cfg(target_has_atomic = "ptr")]
static ENTROPY_SOURCE: AtomicPtr<EntropySource> =
    AtomicPtr::new(default_entropy as *mut EntropySource);
#[cfg(not(target_has_atomic = "ptr"))]
static mut ENTROPY_SOURCE: EntropySource = default_entropy;

#[cfg(target_has_atomic = "ptr")]
pub unsafe fn set_custom_entropy(source: EntropySource) {
    // Not sure if `Release` is enough, to be safe we could use `SeqCst` instead
    ENTROPY_SOURCE.store(source as *mut EntropySource, Ordering::Release);
}

#[cfg(not(target_has_atomic = "ptr"))]
pub unsafe fn set_custom_entropy(source: EntropySource) {
    ENTROPY_SOURCE = source;
}

I guess we could remove unsafe on set_custom_entropy in assumption that if target does not have atomic pointers simultaneous read/write can not happen. Though I am not sure if this assumption is true.

But we should keep JitterRng in std; it was actually added because some DLL linkage failures occasionally caused OsRng to fail in Firefox.

Can you provide a link with the problem description? I think at the very least it should be disabled for cryptographic entropy source. And if it will be possible to redefine entropy source with std, then Firefox will be able to use custom entropy source with jitter fallback.

@burdges
Copy link
Contributor

burdges commented Sep 28, 2018

I'd assume rand employs ENTROPY_SOURCE in thread_rng(), so the interface must be thread safe.

Also, we can poison ENTROPY_SOURCE since we hold the lock during OsRng, which possibly panics. I think propagating the panic by doing nothing works well, but any atomics solution must implement poisoning too, or else require aborts.

Is there a discussion of the receiver type &self for EntropySoruce::fill anywhere? We could still have the vtable without receivers, right?

trait EntropySource {
    const fn name() -> &'static str;
    fn is_available() -> bool { false }
    fn init() -> Result<(), Error>;
    fn fill(&mut [u8]) -> Result<usize, Error>;
}

Is it okay that EntropySource::init defaults to unreachable!()?

@dhardy
Copy link
Member Author

dhardy commented Sep 29, 2018

Afaik, there is never any reason to call set_custom_entropy twice but also no actual security benefit in forbidding multiple calls, but if you want to avoid Mutex then you could simplify your atomics using this requirement.

Thanks for the suggestion @burdges; sounds sensible. Yes, the interface must be thread-safe.

Is there a discussion of the receiver type &self for EntropySoruce::fill anywhere? We could still have the vtable without receivers, right?

Right, this is an abuse of trait objects instead of function pointers (the idea being that the user sets a trait-object, which can then provide all the above functions). For our purposes &self is not needed — except I don't believe Rust allows use of vtables without a self pointer? I figured it doesn't do much harm though and might be useful to someone.


Can you provide a link with the problem description?

See 180 ("fixed" by #181, which was superseded by #196).

JitterRng is based on CPU-Jitter-NPTRNG which is supposed to be secure and checks against obvious weaknesses — but I grant you that it's impossible to be sure. It is only ever used if OsRng fails and all of JitterRng's internal quality checks pass, and it reduces the likelihood of users resorting to weak alternatives like #181, so I believe it does more good than harm.

@therealprof
Copy link

Right, this is an abuse of trait objects instead of function pointers (the idea being that the user sets a trait-object, which can then provide all the above functions). For our purposes &self is not needed — except I don't believe Rust allows use of vtables without a self pointer? I figured it doesn't do much harm though and might be useful to someone.

As long as there's awareness that in no_std there can be multiple indepent execution contexts which are not threads (and hence don't have a common initialisation point to exchange data), making some schemes are very hard to implement, we should be fine.

@newpavlov
Copy link
Member

newpavlov commented Sep 29, 2018

See 180 ("fixed" by #181, which was superseded by #196).

So it's about weak_rng, which is a higher level concept than entropy source, I was under impression that you wanted to incorporate this fallback into the default entropy source for enabled std. I believe we should not do this.

I am a bit hesitant about using trait objects, please keep in mind, that entropy source should be usable on embedded platforms without allocations, atomics and mutexes. Simple function is easy to understand and reason about in the presence of interrupts, while IIRC it's currently impossible to create trait objects from raw pointers on stable.

What is the motivation behind name and is_available methods? Do you expect that application will test list of entropy sources and will select the first working one? In my understanding the usual use-cases will be:

  • Use default OS RNG.
  • Use PRNG for reproducible tests.
  • Use hardware specific construct on embedded platforms.

In all three cases we don't need those methods. For entropy source users they are not needed as well. I see why we would need init method (/dev/random comes first to mind), but because it does not return Self, I don't see much difference with a simple function which will check source initialization inside itself.

@dhardy
Copy link
Member Author

dhardy commented Sep 30, 2018

I was under impression that you wanted to incorporate this fallback into the default entropy source for enabled std

This was done a long time ago (0.4).

I am a bit hesitant about using trait objects, please keep in mind, that entropy source should be usable on embedded platforms without allocations, atomics and mutexes.

Trait objects are part of the language. Atomics are part of the core lib, essential with multiple CPU (core)s, and should be trivial with a single CPU. The current design doesn't need allocations and wouldn't need real mutexes except that I took the lazy option here (it's only a proof of concept).

What is the motivation behind name and is_available methods?

name is mostly documentation and not essential. is_available is used in the existing logic to short-cut where the generator is not available. It could potentially be factored out but fits the current design nicely.

@vks
Copy link
Collaborator

vks commented Apr 9, 2019

@dhardy What is the status of this? It seems like some of the changes here rather belong to the getrandom crate.

@dhardy
Copy link
Member Author

dhardy commented Apr 9, 2019

That's a complicated question. IIRC there are several topics of discussion:

  • possible integration of getrandom into std
  • how an external crate may override the dummy implementation on unsupported platforms (embedded source compatibility getrandom#4)
  • development of some kind of entropy pool crate for use on embedded platforms where entropy is provided intermittently
  • some stuff regarding thread_rng which was already resolved elsewhere

So yes, at least some of this now belongs in the getrandom crate...

@vks
Copy link
Collaborator

vks commented Apr 9, 2019

Ok, then let's close this and move the discussion to rust-random/getrandom#4.

@vks vks closed this Apr 9, 2019
@dhardy
Copy link
Member Author

dhardy commented Apr 9, 2019

No, lets not. There are several points in here not yet tracked elsewhere. I'll re-read the conversation when I can find the time.

@dhardy
Copy link
Member Author

dhardy commented Jun 3, 2019

Okay, I read through and added a few notes to getrandom#4. Some discussion points may be worth referencing if anyone wishes to pursue no_std support for getrandom or thread_rng, but currently I see little interest.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
E-question Participation: opinions wanted
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants