hwrng: core - Fix page fault dead lock on mmap-ed hwrng
authorHerbert Xu <herbert@gondor.apana.org.au>
Sat, 2 Dec 2023 01:01:54 +0000 (09:01 +0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 1 Feb 2024 00:18:50 +0000 (16:18 -0800)
commit 78aafb3884f6bc6636efcc1760c891c8500b9922 upstream.

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: 9996508b3353 ("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>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/char/hw_random/core.c

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