Message ID | 20221118172839.2653829-3-Jason@zx2c4.com (mailing list archive) |
---|---|
State | Not Applicable |
Delegated to: | Herbert Xu |
Headers | show |
Series | implement getrandom() in vDSO | expand |
Le 18/11/2022 à 18:28, Jason A. Donenfeld a écrit : > Provide a generic C vDSO getrandom() implementation, which operates on > an opaque state returned by vgetrandom_alloc() and produces random bytes > the same way as getrandom(). This has a the API signature: > > ssize_t vgetrandom(void *buffer, size_t len, unsigned int flags, void *opaque_state); > ... > diff --git a/lib/vdso/getrandom.c b/lib/vdso/getrandom.c > new file mode 100644 > index 000000000000..b253e9247706 > --- /dev/null > +++ b/lib/vdso/getrandom.c > @@ -0,0 +1,109 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright (C) 2022 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. > + */ > + > +#include <linux/kernel.h> > +#include <linux/atomic.h> > +#include <linux/fs.h> > +#include <vdso/datapage.h> > +#include <asm/vdso/getrandom.h> > +#include <asm/vdso/vsyscall.h> > +#include "getrandom.h" > + > +#undef memcpy > +#define memcpy(d,s,l) __builtin_memcpy(d,s,l) > +#undef memset > +#define memset(d,c,l) __builtin_memset(d,c,l) > + > +#define CHACHA_FOR_VDSO_INCLUDE > +#include "../crypto/chacha.c" > + > +static void memcpy_and_zero(void *dst, void *src, size_t len) > +{ > +#define CASCADE(type) \ > + while (len >= sizeof(type)) { \ > + *(type *)dst = *(type *)src; \ > + *(type *)src = 0; \ > + dst += sizeof(type); \ > + src += sizeof(type); \ > + len -= sizeof(type); \ > + } > +#ifdef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS > +#if BITS_PER_LONG == 64 > + CASCADE(u64); > +#endif > + CASCADE(u32); > + CASCADE(u16); > +#endif > + CASCADE(u8); > +#undef CASCADE > +} > + > +static __always_inline ssize_t > +__cvdso_getrandom(void *buffer, size_t len, unsigned int flags, void *opaque_state) > +{ > + struct vgetrandom_state *state = opaque_state; > + const struct vdso_rng_data *rng_info = __arch_get_vdso_rng_data(); In order to ease wiring up to powerpc, can it be done the same way as commit e876f0b69dc9 ("lib/vdso: Allow architectures to provide the vdso data pointer") > + u32 chacha_state[CHACHA_STATE_WORDS]; > + ssize_t ret = min_t(size_t, MAX_RW_COUNT, len); > + unsigned long current_generation; > + size_t batch_len; > + Thanks, Christophe
Hi Christophe, On Fri, Nov 18, 2022 at 07:34:26PM +0000, Christophe Leroy wrote: > > +static __always_inline ssize_t > > +__cvdso_getrandom(void *buffer, size_t len, unsigned int flags, void *opaque_state) > > +{ > > + struct vgetrandom_state *state = opaque_state; > > + const struct vdso_rng_data *rng_info = __arch_get_vdso_rng_data(); > > In order to ease wiring up to powerpc, can it be done the same way as > commit e876f0b69dc9 ("lib/vdso: Allow architectures to provide the vdso > data pointer") It is already. At least I think it is, unless I'm missing a subtle distinction? The call to __arch_get_vdso_rng_data() goes to arch-specific code, implemented in the 3/3 of this patch set for x86 inside of arch/x86/include/asm/vdso/getrandom.h. On powerpc, you'd make a powerpc-specific __arch_get_vdso_rng_data() instead of arch/powerpc/include/asm/vdso/getrandom.h. Or maybe I'm not reading that commit right? The commit message says something about __arch_get_vdso_rng_data() being problematic because of clobbering a register, but then the same commit still seems to call __arch_get_vdso_rng_data()? Is this one of those things where what you'd prefer is that I define an inline function, __cvdso_get_vdso_rng_data(), that by default calls __arch_get_vdso_rng_data(), but can be overridden on powerpc to do some other type of magic? But __arch_get_vdso_rng_data() is already an overiddable inline, so what would the difference be? Sorry if I'm a bit slow here to grok what's up. Jason
On Sat, Nov 19, 2022 at 12:55:02AM +0100, Jason A. Donenfeld wrote:
> Sorry if I'm a bit slow here to grok what's up.
Okay, I think I see it now. That's a complicated patch for a simple idea
because gettime is more complicated than getrandom. I suspect what you
want is something like the below, correct? Seems easy enough.
diff --git a/lib/vdso/getrandom.c b/lib/vdso/getrandom.c
index b253e9247706..b3282d7cfb53 100644
--- a/lib/vdso/getrandom.c
+++ b/lib/vdso/getrandom.c
@@ -41,10 +41,10 @@ static void memcpy_and_zero(void *dst, void *src, size_t len)
}
static __always_inline ssize_t
-__cvdso_getrandom(void *buffer, size_t len, unsigned int flags, void *opaque_state)
+__cvdso_getrandom_data(const struct vdso_rng_data *rng_info, void *buffer, size_t len,
+ unsigned int flags, void *opaque_state)
{
struct vgetrandom_state *state = opaque_state;
- const struct vdso_rng_data *rng_info = __arch_get_vdso_rng_data();
u32 chacha_state[CHACHA_STATE_WORDS];
ssize_t ret = min_t(size_t, MAX_RW_COUNT, len);
unsigned long current_generation;
@@ -107,3 +107,9 @@ __cvdso_getrandom(void *buffer, size_t len, unsigned int flags, void *opaque_sta
memzero_explicit(chacha_state, sizeof(chacha_state));
goto more_batch;
}
+
+static __always_inline ssize_t
+__cvdso_getrandom(void *buffer, size_t len, unsigned int flags, void *opaque_state)
+{
+ return __cvdso_getrandom_data(__arch_get_vdso_rng_data(), buffer, len, flags, opaque_state);
+}
Le 19/11/2022 à 01:04, Jason A. Donenfeld a écrit : > On Sat, Nov 19, 2022 at 12:55:02AM +0100, Jason A. Donenfeld wrote: >> Sorry if I'm a bit slow here to grok what's up. Maybe I should also have mentionned commit ce7d8056e38b ("powerpc/vdso: Prepare for switching VDSO to generic C implementation.") for you to have a complete picture. > > Okay, I think I see it now. That's a complicated patch for a simple idea > because gettime is more complicated than getrandom. I suspect what you > want is something like the below, correct? Seems easy enough. Yes it is exactly that, powerpc will then call __cvdso_getrandom_data() directly and pass rng_info pointer. > > diff --git a/lib/vdso/getrandom.c b/lib/vdso/getrandom.c > index b253e9247706..b3282d7cfb53 100644 > --- a/lib/vdso/getrandom.c > +++ b/lib/vdso/getrandom.c > @@ -41,10 +41,10 @@ static void memcpy_and_zero(void *dst, void *src, size_t len) > } > > static __always_inline ssize_t > -__cvdso_getrandom(void *buffer, size_t len, unsigned int flags, void *opaque_state) > +__cvdso_getrandom_data(const struct vdso_rng_data *rng_info, void *buffer, size_t len, > + unsigned int flags, void *opaque_state) > { > struct vgetrandom_state *state = opaque_state; > - const struct vdso_rng_data *rng_info = __arch_get_vdso_rng_data(); > u32 chacha_state[CHACHA_STATE_WORDS]; > ssize_t ret = min_t(size_t, MAX_RW_COUNT, len); > unsigned long current_generation; > @@ -107,3 +107,9 @@ __cvdso_getrandom(void *buffer, size_t len, unsigned int flags, void *opaque_sta > memzero_explicit(chacha_state, sizeof(chacha_state)); > goto more_batch; > } > + > +static __always_inline ssize_t > +__cvdso_getrandom(void *buffer, size_t len, unsigned int flags, void *opaque_state) > +{ > + return __cvdso_getrandom_data(__arch_get_vdso_rng_data(), buffer, len, flags, opaque_state); > +} >
On Sat, Nov 19, 2022 at 07:51:21AM +0000, Christophe Leroy wrote: > > > Le 19/11/2022 à 01:04, Jason A. Donenfeld a écrit : > > On Sat, Nov 19, 2022 at 12:55:02AM +0100, Jason A. Donenfeld wrote: > >> Sorry if I'm a bit slow here to grok what's up. > > Maybe I should also have mentionned commit ce7d8056e38b ("powerpc/vdso: > Prepare for switching VDSO to generic C implementation.") for you to > have a complete picture. > > > > > Okay, I think I see it now. That's a complicated patch for a simple idea > > because gettime is more complicated than getrandom. I suspect what you > > want is something like the below, correct? Seems easy enough. > > Yes it is exactly that, powerpc will then call __cvdso_getrandom_data() > directly and pass rng_info pointer. Okay, I'll do that and send out a v+1. Jason
On Sat, Nov 19, 2022 at 12:43:31PM +0100, Jason A. Donenfeld wrote: > On Sat, Nov 19, 2022 at 07:51:21AM +0000, Christophe Leroy wrote: > > > > > > Le 19/11/2022 à 01:04, Jason A. Donenfeld a écrit : > > > On Sat, Nov 19, 2022 at 12:55:02AM +0100, Jason A. Donenfeld wrote: > > >> Sorry if I'm a bit slow here to grok what's up. > > > > Maybe I should also have mentionned commit ce7d8056e38b ("powerpc/vdso: > > Prepare for switching VDSO to generic C implementation.") for you to > > have a complete picture. > > > > > > > > Okay, I think I see it now. That's a complicated patch for a simple idea > > > because gettime is more complicated than getrandom. I suspect what you > > > want is something like the below, correct? Seems easy enough. > > > > Yes it is exactly that, powerpc will then call __cvdso_getrandom_data() > > directly and pass rng_info pointer. > > Okay, I'll do that and send out a v+1. Alright, new series posted here, with those changes. Let me know if that is workable for you. Since a lot of the vDSO is tied up (currently) with timekeeping stuff, it's a bit tricky adding things to it like this. I've done the best I can to keep it clean and such, but if you want to play around with wiring up powerpc, that might prove to be an interesting exercise in shaking out inconsistencies. New series is here: https://lore.kernel.org/lkml/20221119120929.2963813-1-Jason@zx2c4.com/T Jason
diff --git a/MAINTAINERS b/MAINTAINERS index 843dd6a49538..e0aa33f54c57 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17287,6 +17287,7 @@ T: git https://git.kernel.org/pub/scm/linux/kernel/git/crng/random.git S: Maintained F: drivers/char/random.c F: drivers/virt/vmgenid.c +F: lib/vdso/getrandom.c F: lib/vdso/getrandom.h RAPIDIO SUBSYSTEM diff --git a/drivers/char/random.c b/drivers/char/random.c index b83481eb00a9..a62255d852f9 100644 --- a/drivers/char/random.c +++ b/drivers/char/random.c @@ -61,6 +61,7 @@ #include <asm/irq.h> #include <asm/irq_regs.h> #include <asm/io.h> +#include <vdso/datapage.h> #include "../../lib/vdso/getrandom.h" /********************************************************************* @@ -305,6 +306,8 @@ static void crng_reseed(struct work_struct *work) if (next_gen == ULONG_MAX) ++next_gen; WRITE_ONCE(base_crng.generation, next_gen); + if (IS_ENABLED(CONFIG_HAVE_VDSO_GETRANDOM)) + smp_store_release(&_vdso_rng_data.generation, next_gen + 1); if (!static_branch_likely(&crng_is_ready)) crng_init = CRNG_READY; spin_unlock_irqrestore(&base_crng.lock, flags); @@ -754,6 +757,8 @@ static void __cold _credit_init_bits(size_t bits) crng_reseed(NULL); /* Sets crng_init to CRNG_READY under base_crng.lock. */ if (static_key_initialized) execute_in_process_context(crng_set_ready, &set_ready); + if (IS_ENABLED(CONFIG_HAVE_VDSO_GETRANDOM)) + smp_store_release(&_vdso_rng_data.is_ready, true); wake_up_interruptible(&crng_init_wait); kill_fasync(&fasync, SIGIO, POLL_IN); pr_notice("crng init done\n"); diff --git a/include/vdso/datapage.h b/include/vdso/datapage.h index 73eb622e7663..cbacfd923a5c 100644 --- a/include/vdso/datapage.h +++ b/include/vdso/datapage.h @@ -109,6 +109,11 @@ struct vdso_data { struct arch_vdso_data arch_data; }; +struct vdso_rng_data { + unsigned long generation; + bool is_ready; +}; + /* * We use the hidden visibility to prevent the compiler from generating a GOT * relocation. Not only is going through a GOT useless (the entry couldn't and @@ -120,6 +125,7 @@ struct vdso_data { */ extern struct vdso_data _vdso_data[CS_BASES] __attribute__((visibility("hidden"))); extern struct vdso_data _timens_data[CS_BASES] __attribute__((visibility("hidden"))); +extern struct vdso_rng_data _vdso_rng_data __attribute__((visibility("hidden"))); /* * The generic vDSO implementation requires that gettimeofday.h diff --git a/lib/crypto/chacha.c b/lib/crypto/chacha.c index b748fd3d256e..944991bb36c7 100644 --- a/lib/crypto/chacha.c +++ b/lib/crypto/chacha.c @@ -17,8 +17,10 @@ static void chacha_permute(u32 *x, int nrounds) { int i; +#ifndef CHACHA_FOR_VDSO_INCLUDE /* whitelist the allowed round counts */ WARN_ON_ONCE(nrounds != 20 && nrounds != 12); +#endif for (i = 0; i < nrounds; i += 2) { x[0] += x[4]; x[12] = rol32(x[12] ^ x[0], 16); @@ -87,6 +89,7 @@ void chacha_block_generic(u32 *state, u8 *stream, int nrounds) state[12]++; } +#ifndef CHACHA_FOR_VDSO_INCLUDE EXPORT_SYMBOL(chacha_block_generic); /** @@ -112,3 +115,4 @@ void hchacha_block_generic(const u32 *state, u32 *stream, int nrounds) memcpy(&stream[4], &x[12], 16); } EXPORT_SYMBOL(hchacha_block_generic); +#endif diff --git a/lib/vdso/Kconfig b/lib/vdso/Kconfig index d883ac299508..c35fac664574 100644 --- a/lib/vdso/Kconfig +++ b/lib/vdso/Kconfig @@ -30,4 +30,9 @@ config GENERIC_VDSO_TIME_NS Selected by architectures which support time namespaces in the VDSO +config HAVE_VDSO_GETRANDOM + bool + help + Selected by architectures that support vDSO getrandom(). + endif diff --git a/lib/vdso/getrandom.c b/lib/vdso/getrandom.c new file mode 100644 index 000000000000..b253e9247706 --- /dev/null +++ b/lib/vdso/getrandom.c @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. + */ + +#include <linux/kernel.h> +#include <linux/atomic.h> +#include <linux/fs.h> +#include <vdso/datapage.h> +#include <asm/vdso/getrandom.h> +#include <asm/vdso/vsyscall.h> +#include "getrandom.h" + +#undef memcpy +#define memcpy(d,s,l) __builtin_memcpy(d,s,l) +#undef memset +#define memset(d,c,l) __builtin_memset(d,c,l) + +#define CHACHA_FOR_VDSO_INCLUDE +#include "../crypto/chacha.c" + +static void memcpy_and_zero(void *dst, void *src, size_t len) +{ +#define CASCADE(type) \ + while (len >= sizeof(type)) { \ + *(type *)dst = *(type *)src; \ + *(type *)src = 0; \ + dst += sizeof(type); \ + src += sizeof(type); \ + len -= sizeof(type); \ + } +#ifdef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS +#if BITS_PER_LONG == 64 + CASCADE(u64); +#endif + CASCADE(u32); + CASCADE(u16); +#endif + CASCADE(u8); +#undef CASCADE +} + +static __always_inline ssize_t +__cvdso_getrandom(void *buffer, size_t len, unsigned int flags, void *opaque_state) +{ + struct vgetrandom_state *state = opaque_state; + const struct vdso_rng_data *rng_info = __arch_get_vdso_rng_data(); + u32 chacha_state[CHACHA_STATE_WORDS]; + ssize_t ret = min_t(size_t, MAX_RW_COUNT, len); + unsigned long current_generation; + size_t batch_len; + + if (unlikely(!rng_info->is_ready)) + return getrandom_syscall(buffer, len, flags); + + if (unlikely(!len)) + return 0; + + if (unlikely(!READ_ONCE(state->not_forked))) + state->not_forked = true; + +retry_generation: + current_generation = READ_ONCE(rng_info->generation); + if (unlikely(state->generation != current_generation)) { + if (getrandom_syscall(state->key, sizeof(state->key), 0) != sizeof(state->key)) + return getrandom_syscall(buffer, len, flags); + state->generation = current_generation; + state->pos = sizeof(state->batch); + } + + len = ret; +more_batch: + batch_len = min_t(size_t, sizeof(state->batch) - state->pos, len); + if (batch_len) { + memcpy_and_zero(buffer, state->batch + state->pos, batch_len); + state->pos += batch_len; + buffer += batch_len; + len -= batch_len; + } + if (!len) { + if (unlikely(current_generation != READ_ONCE(rng_info->generation))) + goto retry_generation; + if (unlikely(!READ_ONCE(state->not_forked))) { + state->not_forked = true; + goto retry_generation; + } + return ret; + } + + chacha_init_consts(chacha_state); + memcpy(&chacha_state[4], state->key, CHACHA_KEY_SIZE); + memset(&chacha_state[12], 0, sizeof(u32) * 4); + + while (len >= CHACHA_BLOCK_SIZE) { + chacha20_block(chacha_state, buffer); + if (unlikely(chacha_state[12] == 0)) + ++chacha_state[13]; + buffer += CHACHA_BLOCK_SIZE; + len -= CHACHA_BLOCK_SIZE; + } + + chacha20_block(chacha_state, state->key_batch); + if (unlikely(chacha_state[12] == 0)) + ++chacha_state[13]; + chacha20_block(chacha_state, state->key_batch + CHACHA_BLOCK_SIZE); + state->pos = 0; + memzero_explicit(chacha_state, sizeof(chacha_state)); + goto more_batch; +}
Provide a generic C vDSO getrandom() implementation, which operates on an opaque state returned by vgetrandom_alloc() and produces random bytes the same way as getrandom(). This has a the API signature: 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 the opaque allocated state. Were all four arguments passed to the getrandom() syscall, nothing different would happen, and the functions would have the exact same behavior. The actual vDSO RNG algorithm implemented is the same one implemented by drivers/char/random.c, using the same fast-erasure techniques as that. Should the in-kernel implementation change, so too will the vDSO one. Initially, the state is keyless, and so the first call makes a getrandom() syscall to generate that key, and then uses it for subsequent calls. By keeping track of a generation counter, it knows when its key is invalidated and it should fetch a new one using the syscall. Later, more than just a generation counter might be used. Since MADV_WIPEONFORK is set on the opaque state, the key and related state is wiped during a fork(), so secrets don't roll over into new processes, and the same state doesn't accidentally generate the same random stream. The generation counter, as well, is always >0, so that the 0 counter is a useful indication of a fork() or otherwise uninitialized state. If the kernel RNG is not yet initialized, then the vDSO always calls the syscall, because that behavior cannot be emulated in userspace, but fortunately that state is short lived and only during early boot. If it has been initialized, then there is no need to inspect the `flags` argument, because the behavior does not change post-initialization regardless of the `flags` value. Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com> --- MAINTAINERS | 1 + drivers/char/random.c | 5 ++ include/vdso/datapage.h | 6 +++ lib/crypto/chacha.c | 4 ++ lib/vdso/Kconfig | 5 ++ lib/vdso/getrandom.c | 109 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 130 insertions(+) create mode 100644 lib/vdso/getrandom.c