Re: [PATCH v14 0/7] implement getrandom() in vDSO

Mathieu Desnoyers
2023-01-11T22:23:43Z

On 01-Jan-2023 05:29:03 PM, Jason A. Donenfeld wrote:
[...]
> Two statements:
>
> 1) Userspace wants faster cryptographically secure random numbers of
> arbitrary size, big or small.
>
> 2) Userspace is currently unable to safely roll its own RNG with the
> same security profile as getrandom().
>
[...]
> API-wise, the vDSO gains this function:
>
> ssize_t vgetrandom(void *buffer, size_t len, unsigned int flags, void *opaque_state);
>
> The return value and the first 3 arguments are the same as ordinary
> getrandom(), while the last argument is a pointer to some state
> allocated with vgetrandom_alloc(), explained below. Were all four
> arguments passed to the getrandom syscall, nothing different would
> happen, and the functions would have the exact same behavior.
>
> Then, we introduce a new syscall:
>
> void *vgetrandom_alloc(unsigned int *num, unsigned int *size_per_each,
> unsigned long addr, unsigned int flags);
>
> This takes a hinted number of opaque states in `num`, and returns a
> pointer to an array of opaque states, the number actually allocated back
> in `num`, and the size in bytes of each one in `size_per_each`, enabling
> a libc to slice up the returned array into a state per each thread. (The
> `flags` and `addr` arguments, as well as the `*size_per_each` input
> value, are reserved for the future and are forced to be zero for now.)
>
> Libc is expected to allocate a chunk of these on first use, and then
> dole them out to threads as they're created, allocating more when
> needed. The returned address of the first state may be passed to
> munmap(2) with a length of `num * size_per_each`, in order to deallocate
> the memory.
>
> We very intentionally do *not* leave state allocation up to the caller
> of vgetrandom, but provide vgetrandom_alloc for that allocation. There
> are too many weird things that can go wrong, and it's important that
> vDSO does not provide too generic of a mechanism. It's not going to
> store its state in just any old memory address. It'll do it only in ones
> it allocates.

[...]

Have you considered extending rseq(2) per-thread "struct rseq" with an
additional "prng_seed" pointer field, which would point to a per-thread
memory area accessible both from userspace (at address
__builtin_thread_pointer() + __rseq_offset) and from kernel's
return-to-userspace rseq notification code (which can handle page
faults) ?

This way, the kernel can update its content when returning to userspace
if an update is needed since the last update.

Would that be sufficient as prng seed for your security requirements ?

Implementation-wise, the semantic of the prng_seed could be entirely
internal to a vgetrandom vDSO implementation, but the allocation of the
memory holding this seed would be the responsibility of libc.

libc could query the size required by the kernel for this prng seed with
a new getauxval(3) entry, e.g. AT_RSEQ_PRNG_SIZE. By doing so, libc
would only allocate as much memory as needed by the kernel vDSO
implementation.

This would remove the need for any kind of vgetrandom_alloc system call
and all its associated complexity.

Thoughts ?

Thanks,

Mathieu

--
Mathieu Desnoyers
EfficiOS Inc.