From: "Jason A. Donenfeld" <Jason@zx2c4.com>
Date: Thu, 7 Apr 2022 21:23:08 +0200
Subject: random: allow partial reads if later user copies fail
Patch-mainline: v5.18-rc3
Git-commit: 5209aed5137880fa229746cb521f715e55596460
References: bsc#1204911
Rather than failing entirely if a copy_to_user() fails at some point,
instead we should return a partial read for the amount that succeeded
prior, unless none succeeded at all, in which case we return -EFAULT as
before.
This makes it consistent with other reader interfaces. For example, the
following snippet for /dev/zero outputs "4" followed by "1":
int fd;
void *x = mmap(NULL, 4096, PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
assert(x != MAP_FAILED);
fd = open("/dev/zero", O_RDONLY);
assert(fd >= 0);
printf("%zd\n", read(fd, x, 4));
printf("%zd\n", read(fd, x + 4095, 4));
close(fd);
This brings that same standard behavior to the various RNG reader
interfaces.
While we're at it, we can streamline the loop logic a little bit.
Suggested-by: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Jann Horn <jannh@google.com>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
[nstange@suse.de: adapted context diff to backport,
added early nbytes == 0 check to account for missing small request logic]
Acked-by: Nicolai Stange <nstange@suse.de>
---
drivers/char/random.c | 22 ++++++++++++++--------
1 file changed, 14 insertions(+), 8 deletions(-)
--- a/drivers/char/random.c
+++ b/drivers/char/random.c
@@ -1084,23 +1084,29 @@ static void crng_backtrack_protect(__u8
static ssize_t extract_crng_user(void __user *buf, size_t nbytes)
{
- ssize_t ret = 0, i = CHACHA_BLOCK_SIZE;
+ size_t i, left, ret = 0;
__u8 tmp[CHACHA_BLOCK_SIZE] __aligned(4);
- while (nbytes) {
+ if (!nbytes)
+ return 0;
+
+ for (;;) {
extract_crng(tmp);
- i = min_t(int, nbytes, CHACHA_BLOCK_SIZE);
- if (copy_to_user(buf, tmp, i)) {
- ret = -EFAULT;
+ i = min_t(size_t, nbytes, CHACHA_BLOCK_SIZE);
+ left = copy_to_user(buf, tmp, i);
+ if (left) {
+ ret += i - left;
break;
}
- nbytes -= i;
buf += i;
ret += i;
+ nbytes -= i;
+ if (!nbytes)
+ break;
BUILD_BUG_ON(PAGE_SIZE % CHACHA_BLOCK_SIZE != 0);
- if (!(ret % PAGE_SIZE) && nbytes) {
+ if (ret % PAGE_SIZE == 0) {
if (signal_pending(current))
break;
cond_resched();
@@ -1111,7 +1117,7 @@ static ssize_t extract_crng_user(void __
/* Wipe data just written to memory */
memzero_explicit(tmp, sizeof(tmp));
- return ret;
+ return ret ? ret : -EFAULT;
}