Blob Blame History Raw
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;
 }