mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v1] riscv: uaccess: Report partial progress when the word copy faults
@ 2026-10-27 12:52 Nickolai Zeldovich
  0 siblings, 0 replies; only message in thread
From: Nickolai Zeldovich @ 2026-10-27 12:52 UTC (permalink / raw)
  To: Paul Walmsley, Palmer Dabbelt, Albert Ou
  Cc: Alexandre Ghiti, Andy Chiu, linux-riscv, linux-kernel,
	Nickolai Zeldovich

write(2) from a buffer whose last 40 readable bytes are followed by an
unmapped page spins in generic_perform_write(), holding the inode lock,
until the task is killed, instead of returning 40; read(2) into such a
buffer fails with -EFAULT.

The unrolled word copy does eight loads before its eight stores, and
the shift copy loads one word ahead of its store, but their fixups
count everything from the first destination byte not stored as not
copied. So a fault in the first block reports that nothing was copied
although some bytes could be, which uaccess.h does not allow;
generic_perform_write() then retries forever, as
fault_in_iov_iter_readable() succeeds for the readable bytes.

Make those fixups finish the copy a byte at a time from the first byte
not stored; the byte copy's fixup then returns the exact count.

Reproduced on riscv64 under QEMU with write(2) to tmpfs and ramfs; the
patched kernel returns 40.

Fixes: 4b05b993900d ("riscv: uaccess: Return the number of bytes effectively not copied")
Signed-off-by: Nickolai Zeldovich <nickolai@csail.mit.edu>
---

Notes:
    This patch was prepared with the help of an AI tool.
    
    The scalar copy also runs when the vector copy is in use: for copies
    below riscv_v_usercopy_threshold and for the rest of a vector copy that
    faulted. Under QEMU -cpu max a 4096-byte write(2) from the buffer below
    hangs as well.
    
    Reproducer (riscv64, no privileges; the directory is on ramfs or tmpfs):
    
      gcc -O2 -o uaccess-progress uaccess-progress.c && ./uaccess-progress /tmp
    
    Before (mainline d135ab453a74, defconfig, QEMU -cpu max):
    "read:  -1 (expected 40)", then the write spins in the kernel until the
    alarm kills it ("Alarm clock"). After: "read:  40 (expected 40)" and
    "write: 40 (expected 40)".
    
      /*
       * read(2) and write(2) of 100 bytes from a buffer with 40 bytes before an
       * unmapped page: both must return 40.
       * Usage: uaccess-progress <dir on ramfs or tmpfs>
       */
      #include <fcntl.h>
      #include <stdio.h>
      #include <string.h>
      #include <sys/mman.h>
      #include <unistd.h>
    
      int main(int argc, char **argv)
      {
      	long pg = sysconf(_SC_PAGESIZE);
      	char path[256], *p;
      	ssize_t r;
      	int fd;
    
      	snprintf(path, sizeof(path), "%s/uaccess-progress", argc > 1 ? argv[1] : "/tmp");
      	p = mmap(NULL, 2 * pg, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
      	if (p == MAP_FAILED || munmap(p + pg, pg))
      		return 1;
      	memset(p, 'x', pg);
      	fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0600);
      	if (fd < 0 || pwrite(fd, p, pg, 0) != pg)
      		return 1;
    
      	r = pread(fd, p + pg - 40, 100, 0);
      	printf("read:  %zd (expected 40)\n", r);
      	fflush(stdout);
    
      	alarm(5);	/* the unpatched kernel never returns: "Alarm clock" */
      	r = pwrite(fd, p + pg - 40, 100, pg);
      	printf("write: %zd (expected 40)\n", r);
      	return 0;
      }

 arch/riscv/lib/uaccess.S | 29 ++++++++++++++++++++++-------
 1 file changed, 22 insertions(+), 7 deletions(-)

diff --git a/arch/riscv/lib/uaccess.S b/arch/riscv/lib/uaccess.S
index cf8586a937de8..99886ab86802e 100644
--- a/arch/riscv/lib/uaccess.S
+++ b/arch/riscv/lib/uaccess.S
@@ -88,9 +88,9 @@ SYM_FUNC_START(fallback_scalar_usercopy_sum_enabled)
 	beq	a0, t1, .Lskip_align_dst
 1:
 	/* a5 - one byte for copying data */
-	fixup lb      a5, 0(a1), 10f
+	fixup lb      a5, 0(a1), 11f
 	addi	a1, a1, 1	/* src */
-	fixup sb      a5, 0(a0), 10f
+	fixup sb      a5, 0(a0), 11f
 	addi	a0, a0, 1	/* dst */
 	bltu	a0, t1, 1b	/* t1 - start of aligned dst */
 
@@ -169,7 +169,7 @@ SYM_FUNC_START(fallback_scalar_usercopy_sum_enabled)
 	sub	t4, a5, t3
 
 	/* Load the first word to combine with second word */
-	fixup REG_L   a5, 0(a1), 10f
+	fixup REG_L   a5, 0(a1), 12f
 
 3:
 	/* Main shifting copy
@@ -181,11 +181,11 @@ SYM_FUNC_START(fallback_scalar_usercopy_sum_enabled)
 
 	/* At least one iteration will be executed */
 	srl	a4, a5, t3
-	fixup REG_L   a5, SZREG(a1), 10f
+	fixup REG_L   a5, SZREG(a1), 12f
 	addi	a1, a1, SZREG
 	sll	a2, a5, t4
 	or	a2, a2, a4
-	fixup REG_S   a2, 0(a0), 10f
+	fixup REG_S   a2, 0(a0), 13f
 	addi	a0, a0, SZREG
 	bltu	a0, t1, 3b
 
@@ -203,16 +203,31 @@ SYM_FUNC_START(fallback_scalar_usercopy_sum_enabled)
 	 */
 	bgeu	a0, t0, .Lout_copy_user  /* check if end of copy */
 4:
-	fixup lb      a5, 0(a1), 10f
+	fixup lb      a5, 0(a1), 11f
 	addi	a1, a1, 1	/* src */
-	fixup sb      a5, 0(a0), 10f
+	fixup sb      a5, 0(a0), 11f
 	addi	a0, a0, 1	/* dst */
 	bltu	a0, t0, 4b	/* t0 - end of dst */
 
 .Lout_copy_user:
 	li	a0, 0
 	ret
+
+	/*
+	 * A fault in the word or shift copy: redo the rest a byte at a time
+	 * from the first dst byte not stored, so that every byte before the
+	 * faulting one is copied and counted.
+	 */
+#if !defined(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS)
+13:	/* shift copy store: a1 is already one word ahead */
+	addi	a1, a1, -SZREG
+12:	/* shift copy load: the src of a0 is a1 + a3 */
+	add	a1, a1, a3
+#endif
 10:
+	mv	t0, t5
+	j	.Lbyte_copy_tail
+11:
 	sub a0, t5, a0
 	ret
 SYM_FUNC_END(fallback_scalar_usercopy_sum_enabled)

base-commit: d135ab453a7400a34930bdda1ef4bdc8acfac80e
-- 
2.53.0


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2026-10-27 12:52 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-27 12:52 [PATCH v1] riscv: uaccess: Report partial progress when the word copy faults Nickolai Zeldovich

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®