Skip to content

Commit 4ad10a5

Browse files
committed
random: introduce generic vDSO getrandom() implementation
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 the following API signature: ssize_t vgetrandom(void *buffer, size_t len, unsigned int flags, void *opaque_state, size_t opaque_len); The return value and the first three arguments are the same as ordinary getrandom(), while the last two arguments are a pointer to the opaque allocated state and its size. Were all five 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. It requires an implementation of ChaCha20 that does not use any stack, in order to maintain forward secrecy if a multi-threaded program forks (though this does not account for a similar issue with SA_SIGINFO copying registers to the stack), so this is left as an architecture-specific fill-in. Stack-less ChaCha20 is an easy algorithm to implement on a variety of architectures, so this shouldn't be too onerous. 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. Since the opaque state passed to it is mutated, vDSO getrandom() is not reentrant, when used with the same opaque state, which libc should be mindful of. The function works over an opaque per-thread state of a particular size, which must be marked VM_WIPEONFORK, VM_DONTDUMP, VM_NORESERVE, and VM_DROPPABLE for proper operation. Over time, the nuances of these allocations may change or grow or even differ based on architectural features. The opaque state passed to vDSO getrandom() must be allocated using the mmap_flags and mmap_prot parameters provided by the vgetrandom_opaque_params struct, which also contains the size of each state. That struct can be obtained with a call to vgetrandom(NULL, 0, 0, &params, ~0UL). Then, libc can call mmap(2) and slice up the returned array into a state per each thread, while ensuring that no single state straddles a page boundary. 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. vDSO getrandom() provides the ability for userspace to generate random bytes quickly and safely, and is intended to be integrated into libc's thread management. As an illustrative example, the introduced code in the vdso_test_getrandom self test later in this series might be used to do the same outside of libc. In a libc the various pthread-isms are expected to be elided into libc internals. Reviewed-by: Thomas Gleixner <[email protected]> Signed-off-by: Jason A. Donenfeld <[email protected]>
1 parent 9651fce commit 4ad10a5

File tree

7 files changed

+347
-1
lines changed

7 files changed

+347
-1
lines changed

MAINTAINERS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18745,6 +18745,8 @@ T: git https://git.kernel.org/pub/scm/linux/kernel/git/crng/random.git
1874518745
F: Documentation/devicetree/bindings/rng/microsoft,vmgenid.yaml
1874618746
F: drivers/char/random.c
1874718747
F: drivers/virt/vmgenid.c
18748+
F: include/vdso/getrandom.h
18749+
F: lib/vdso/getrandom.c
1874818750

1874918751
RAPIDIO SUBSYSTEM
1875018752
M: Matt Porter <[email protected]>

drivers/char/random.c

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
22
/*
3-
* Copyright (C) 2017-2022 Jason A. Donenfeld <[email protected]>. All Rights Reserved.
3+
* Copyright (C) 2017-2024 Jason A. Donenfeld <[email protected]>. All Rights Reserved.
44
* Copyright Matt Mackall <[email protected]>, 2003, 2004, 2005
55
* Copyright Theodore Ts'o, 1994, 1995, 1996, 1997, 1998, 1999. All rights reserved.
66
*
@@ -56,6 +56,10 @@
5656
#include <linux/sched/isolation.h>
5757
#include <crypto/chacha.h>
5858
#include <crypto/blake2s.h>
59+
#ifdef CONFIG_VDSO_GETRANDOM
60+
#include <vdso/getrandom.h>
61+
#include <vdso/datapage.h>
62+
#endif
5963
#include <asm/archrandom.h>
6064
#include <asm/processor.h>
6165
#include <asm/irq.h>
@@ -271,6 +275,15 @@ static void crng_reseed(struct work_struct *work)
271275
if (next_gen == ULONG_MAX)
272276
++next_gen;
273277
WRITE_ONCE(base_crng.generation, next_gen);
278+
#ifdef CONFIG_VDSO_GETRANDOM
279+
/* base_crng.generation's invalid value is ULONG_MAX, while
280+
* _vdso_rng_data.generation's invalid value is 0, so add one to the
281+
* former to arrive at the latter. Use smp_store_release so that this
282+
* is ordered with the write above to base_crng.generation. Pairs with
283+
* the smp_rmb() before the syscall in the vDSO code.
284+
*/
285+
smp_store_release(&_vdso_rng_data.generation, next_gen + 1);
286+
#endif
274287
if (!static_branch_likely(&crng_is_ready))
275288
crng_init = CRNG_READY;
276289
spin_unlock_irqrestore(&base_crng.lock, flags);
@@ -721,6 +734,9 @@ static void __cold _credit_init_bits(size_t bits)
721734
if (static_key_initialized && system_unbound_wq)
722735
queue_work(system_unbound_wq, &set_ready);
723736
atomic_notifier_call_chain(&random_ready_notifier, 0, NULL);
737+
#ifdef CONFIG_VDSO_GETRANDOM
738+
WRITE_ONCE(_vdso_rng_data.is_ready, true);
739+
#endif
724740
wake_up_interruptible(&crng_init_wait);
725741
kill_fasync(&fasync, SIGIO, POLL_IN);
726742
pr_notice("crng init done\n");

include/uapi/linux/random.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,19 @@ struct rand_pool_info {
5555
#define GRND_RANDOM 0x0002
5656
#define GRND_INSECURE 0x0004
5757

58+
/**
59+
* struct vgetrandom_opaque_params - arguments for allocating memory for vgetrandom
60+
*
61+
* @size_per_opaque_state: Size of each state that is to be passed to vgetrandom().
62+
* @mmap_prot: Value of the prot argument in mmap(2).
63+
* @mmap_flags: Value of the flags argument in mmap(2).
64+
* @reserved: Reserved for future use.
65+
*/
66+
struct vgetrandom_opaque_params {
67+
__u32 size_of_opaque_state;
68+
__u32 mmap_prot;
69+
__u32 mmap_flags;
70+
__u32 reserved[13];
71+
};
72+
5873
#endif /* _UAPI_LINUX_RANDOM_H */

include/vdso/datapage.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,16 @@ struct vdso_data {
113113
struct arch_vdso_data arch_data;
114114
};
115115

116+
/**
117+
* struct vdso_rng_data - vdso RNG state information
118+
* @generation: counter representing the number of RNG reseeds
119+
* @is_ready: boolean signaling whether the RNG is initialized
120+
*/
121+
struct vdso_rng_data {
122+
u64 generation;
123+
u8 is_ready;
124+
};
125+
116126
/*
117127
* We use the hidden visibility to prevent the compiler from generating a GOT
118128
* relocation. Not only is going through a GOT useless (the entry couldn't and
@@ -124,6 +134,7 @@ struct vdso_data {
124134
*/
125135
extern struct vdso_data _vdso_data[CS_BASES] __attribute__((visibility("hidden")));
126136
extern struct vdso_data _timens_data[CS_BASES] __attribute__((visibility("hidden")));
137+
extern struct vdso_rng_data _vdso_rng_data __attribute__((visibility("hidden")));
127138

128139
/**
129140
* union vdso_data_store - Generic vDSO data page

include/vdso/getrandom.h

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/* SPDX-License-Identifier: GPL-2.0 */
2+
/*
3+
* Copyright (C) 2022-2024 Jason A. Donenfeld <[email protected]>. All Rights Reserved.
4+
*/
5+
6+
#ifndef _VDSO_GETRANDOM_H
7+
#define _VDSO_GETRANDOM_H
8+
9+
#include <linux/types.h>
10+
11+
#define CHACHA_KEY_SIZE 32
12+
#define CHACHA_BLOCK_SIZE 64
13+
14+
/**
15+
* struct vgetrandom_state - State used by vDSO getrandom().
16+
*
17+
* @batch: One and a half ChaCha20 blocks of buffered RNG output.
18+
*
19+
* @key: Key to be used for generating next batch.
20+
*
21+
* @batch_key: Union of the prior two members, which is exactly two full
22+
* ChaCha20 blocks in size, so that @batch and @key can be filled
23+
* together.
24+
*
25+
* @generation: Snapshot of @rng_info->generation in the vDSO data page at
26+
* the time @key was generated.
27+
*
28+
* @pos: Offset into @batch of the next available random byte.
29+
*
30+
* @in_use: Reentrancy guard for reusing a state within the same thread
31+
* due to signal handlers.
32+
*/
33+
struct vgetrandom_state {
34+
union {
35+
struct {
36+
u8 batch[CHACHA_BLOCK_SIZE * 3 / 2];
37+
u32 key[CHACHA_KEY_SIZE / sizeof(u32)];
38+
};
39+
u8 batch_key[CHACHA_BLOCK_SIZE * 2];
40+
};
41+
u64 generation;
42+
u8 pos;
43+
bool in_use;
44+
};
45+
46+
#endif /* _VDSO_GETRANDOM_H */

lib/vdso/Kconfig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,8 @@ config GENERIC_VDSO_OVERFLOW_PROTECT
3838
in the hotpath.
3939

4040
endif
41+
42+
config VDSO_GETRANDOM
43+
bool
44+
help
45+
Selected by architectures that support vDSO getrandom().

0 commit comments

Comments
 (0)