From: Herbert Xu <herbert.xu@xxxxxxxxxx> This patch introduces a hook mechanism to drivers/char/random to allow the reads on /dev/*random as well as getrandom(2) to be overridden by an external RNG. This will be used to override drivers/char/random with a FIPS RNG in a subsequent patch. Signed-off-by: Herbert Xu <herbert.xu@xxxxxxxxxx> Signed-off-by: Prarit Bhargava <prarit@xxxxxxxxxx> [6.1: Handle some context in getrandom() and random.h, and convert the extrng_*_fops to use random_write_iter().] Signed-off-by: Samuel Mendoza-Jonas <samjonas@xxxxxxxxxx> [6.12: Further resolve surrounding conflicts in getrandom() and random.h.] Cc: stable@xxxxxxxxxxxxxxx Signed-off-by: Elena Avila <ellavila@xxxxxxxxxx> --- drivers/char/random.c | 114 +++++++++++++++++++++++++++++++++++++++++ include/linux/random.h | 7 +++ 2 files changed, 121 insertions(+) diff --git a/drivers/char/random.c b/drivers/char/random.c index 23ee76bbb4aa..032c5ffa814c 100644 --- a/drivers/char/random.c +++ b/drivers/char/random.c @@ -54,6 +54,7 @@ #include <linux/suspend.h> #include <linux/siphash.h> #include <linux/sched/isolation.h> +#include <linux/rcupdate.h> #include <crypto/chacha.h> #include <crypto/blake2s.h> #ifdef CONFIG_VDSO_GETRANDOM @@ -330,6 +331,11 @@ static void crng_fast_key_erasure(u8 key[CHACHA_KEY_SIZE], memzero_explicit(first_block, sizeof(first_block)); } +/* + * Hook for external RNG. + */ +static const struct random_extrng __rcu *extrng; + /* * This function returns a ChaCha state that you may use for generating * random data. It also returns up to 32 bytes on its own of random data @@ -610,6 +616,9 @@ int __cold random_prepare_cpu(unsigned int cpu) #endif +static const struct file_operations extrng_random_fops; +static const struct file_operations extrng_urandom_fops; + /********************************************************************** * * Entropy accumulation and extraction routines. @@ -980,6 +989,19 @@ void __init add_bootloader_randomness(const void *buf, size_t len) credit_init_bits(len * 8); } +void random_register_extrng(const struct random_extrng *rng) +{ + rcu_assign_pointer(extrng, rng); +} +EXPORT_SYMBOL_GPL(random_register_extrng); + +void random_unregister_extrng(void) +{ + RCU_INIT_POINTER(extrng, NULL); + synchronize_rcu(); +} +EXPORT_SYMBOL_GPL(random_unregister_extrng); + #if IS_ENABLED(CONFIG_VMGENID) static BLOCKING_NOTIFIER_HEAD(vmfork_chain); @@ -1387,6 +1409,7 @@ static void __cold try_to_generate_entropy(void) SYSCALL_DEFINE3(getrandom, char __user *, ubuf, size_t, len, unsigned int, flags) { + const struct random_extrng *rng; struct iov_iter iter; int ret; @@ -1400,6 +1423,18 @@ SYSCALL_DEFINE3(getrandom, char __user *, ubuf, size_t, len, unsigned int, flags if ((flags & (GRND_INSECURE | GRND_RANDOM)) == (GRND_INSECURE | GRND_RANDOM)) return -EINVAL; + rcu_read_lock(); + rng = rcu_dereference(extrng); + if (rng && !try_module_get(rng->owner)) + rng = NULL; + rcu_read_unlock(); + + if (rng) { + ret = rng->extrng_read(ubuf, len); + module_put(rng->owner); + return ret; + } + if (!crng_ready() && !(flags & GRND_INSECURE)) { if (flags & GRND_NONBLOCK) return -EAGAIN; @@ -1420,6 +1455,13 @@ static __poll_t random_poll(struct file *file, poll_table *wait) return crng_ready() ? EPOLLIN | EPOLLRDNORM : EPOLLOUT | EPOLLWRNORM; } +static __poll_t +extrng_poll(struct file *file, poll_table * wait) +{ + /* extrng pool is always full, always read, no writes */ + return EPOLLIN | EPOLLRDNORM; +} + static ssize_t write_pool_user(struct iov_iter *iter) { u8 block[BLAKE2S_BLOCK_SIZE]; @@ -1560,9 +1602,60 @@ static int random_fasync(int fd, struct file *filp, int on) return fasync_helper(fd, filp, on, &fasync); } +static int random_open(struct inode *inode, struct file *filp) +{ + const struct random_extrng *rng; + + rcu_read_lock(); + rng = rcu_dereference(extrng); + if (rng && !try_module_get(rng->owner)) + rng = NULL; + rcu_read_unlock(); + + if (!rng) + return 0; + + filp->f_op = &extrng_random_fops; + filp->private_data = rng->owner; + + return 0; +} + +static int urandom_open(struct inode *inode, struct file *filp) +{ + const struct random_extrng *rng; + + rcu_read_lock(); + rng = rcu_dereference(extrng); + if (rng && !try_module_get(rng->owner)) + rng = NULL; + rcu_read_unlock(); + + if (!rng) + return 0; + + filp->f_op = &extrng_urandom_fops; + filp->private_data = rng->owner; + + return 0; +} + +static int extrng_release(struct inode *inode, struct file *filp) +{ + module_put(filp->private_data); + return 0; +} + +static ssize_t +extrng_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos) +{ + return rcu_dereference_raw(extrng)->extrng_read(buf, nbytes); +} + const struct file_operations random_fops = { .read_iter = random_read_iter, .write_iter = random_write_iter, + .open = random_open, .poll = random_poll, .unlocked_ioctl = random_ioctl, .compat_ioctl = compat_ptr_ioctl, @@ -1575,6 +1668,7 @@ const struct file_operations random_fops = { const struct file_operations urandom_fops = { .read_iter = urandom_read_iter, .write_iter = random_write_iter, + .open = urandom_open, .unlocked_ioctl = random_ioctl, .compat_ioctl = compat_ptr_ioctl, .fasync = random_fasync, @@ -1583,6 +1677,26 @@ const struct file_operations urandom_fops = { .splice_write = iter_file_splice_write, }; +static const struct file_operations extrng_random_fops = { + .open = random_open, + .read = extrng_read, + .write_iter = random_write_iter, + .poll = extrng_poll, + .unlocked_ioctl = random_ioctl, + .fasync = random_fasync, + .llseek = noop_llseek, + .release = extrng_release, +}; + +static const struct file_operations extrng_urandom_fops = { + .open = urandom_open, + .read = extrng_read, + .write_iter = random_write_iter, + .unlocked_ioctl = random_ioctl, + .fasync = random_fasync, + .llseek = noop_llseek, + .release = extrng_release, +}; /******************************************************************** * diff --git a/include/linux/random.h b/include/linux/random.h index b0a940af4fff..6cfb75741ade 100644 --- a/include/linux/random.h +++ b/include/linux/random.h @@ -9,6 +9,11 @@ #include <uapi/linux/random.h> +struct random_extrng { + ssize_t (*extrng_read)(void __user *buf, size_t buflen); + struct module *owner; +}; + struct notifier_block; void add_device_randomness(const void *buf, size_t len); @@ -120,6 +125,8 @@ void __init random_init(void); bool rng_is_initialized(void); int wait_for_random_bytes(void); int execute_with_initialized_rng(struct notifier_block *nb); +void random_register_extrng(const struct random_extrng *rng); +void random_unregister_extrng(void); /* Calls wait_for_random_bytes() and then calls get_random_bytes(buf, nbytes). * Returns the result of the call to wait_for_random_bytes. */ -- 2.47.1