hwrng: core - Fix page fault dead lock on mmap-ed hwrng

There is a dead-lock in the hwrng device read path.  This triggers
when the user reads from /dev/hwrng into memory also mmap-ed from
/dev/hwrng.  The resulting page fault triggers a recursive read
which then dead-locks.

Fix this by using a stack buffer when calling copy_to_user.

Reported-by: Edward Adam Davis <eadavis@qq.com>
Reported-by: syzbot+c52ab18308964d248092@syzkaller.appspotmail.com
Fixes: 9996508b33 ("hwrng: core - Replace u32 in driver API with byte array")
Cc: <stable@vger.kernel.org>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
This commit is contained in:
Herbert Xu 2023-12-02 09:01:54 +08:00
parent 01d70a4bbf
commit 78aafb3884
1 changed files with 22 additions and 14 deletions

View File

@ -23,10 +23,13 @@
#include <linux/sched.h> #include <linux/sched.h>
#include <linux/sched/signal.h> #include <linux/sched/signal.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/string.h>
#include <linux/uaccess.h> #include <linux/uaccess.h>
#define RNG_MODULE_NAME "hw_random" #define RNG_MODULE_NAME "hw_random"
#define RNG_BUFFER_SIZE (SMP_CACHE_BYTES < 32 ? 32 : SMP_CACHE_BYTES)
static struct hwrng *current_rng; static struct hwrng *current_rng;
/* the current rng has been explicitly chosen by user via sysfs */ /* the current rng has been explicitly chosen by user via sysfs */
static int cur_rng_set_by_user; static int cur_rng_set_by_user;
@ -58,7 +61,7 @@ static inline int rng_get_data(struct hwrng *rng, u8 *buffer, size_t size,
static size_t rng_buffer_size(void) static size_t rng_buffer_size(void)
{ {
return SMP_CACHE_BYTES < 32 ? 32 : SMP_CACHE_BYTES; return RNG_BUFFER_SIZE;
} }
static void add_early_randomness(struct hwrng *rng) static void add_early_randomness(struct hwrng *rng)
@ -209,6 +212,7 @@ static inline int rng_get_data(struct hwrng *rng, u8 *buffer, size_t size,
static ssize_t rng_dev_read(struct file *filp, char __user *buf, static ssize_t rng_dev_read(struct file *filp, char __user *buf,
size_t size, loff_t *offp) size_t size, loff_t *offp)
{ {
u8 buffer[RNG_BUFFER_SIZE];
ssize_t ret = 0; ssize_t ret = 0;
int err = 0; int err = 0;
int bytes_read, len; int bytes_read, len;
@ -236,34 +240,37 @@ static ssize_t rng_dev_read(struct file *filp, char __user *buf,
if (bytes_read < 0) { if (bytes_read < 0) {
err = bytes_read; err = bytes_read;
goto out_unlock_reading; goto out_unlock_reading;
} } else if (bytes_read == 0 &&
data_avail = bytes_read; (filp->f_flags & O_NONBLOCK)) {
}
if (!data_avail) {
if (filp->f_flags & O_NONBLOCK) {
err = -EAGAIN; err = -EAGAIN;
goto out_unlock_reading; goto out_unlock_reading;
} }
} else {
len = data_avail; data_avail = bytes_read;
}
len = data_avail;
if (len) {
if (len > size) if (len > size)
len = size; len = size;
data_avail -= len; data_avail -= len;
if (copy_to_user(buf + ret, rng_buffer + data_avail, memcpy(buffer, rng_buffer + data_avail, len);
len)) { }
mutex_unlock(&reading_mutex);
put_rng(rng);
if (len) {
if (copy_to_user(buf + ret, buffer, len)) {
err = -EFAULT; err = -EFAULT;
goto out_unlock_reading; goto out;
} }
size -= len; size -= len;
ret += len; ret += len;
} }
mutex_unlock(&reading_mutex);
put_rng(rng);
if (need_resched()) if (need_resched())
schedule_timeout_interruptible(1); schedule_timeout_interruptible(1);
@ -274,6 +281,7 @@ static ssize_t rng_dev_read(struct file *filp, char __user *buf,
} }
} }
out: out:
memzero_explicit(buffer, sizeof(buffer));
return ret ? : err; return ret ? : err;
out_unlock_reading: out_unlock_reading: