summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--na/arch/riscv/uaccess/uaccess_memcpy.S117
-rw-r--r--na/include/lume/uaccess.h27
-rw-r--r--na/kernel/uaccess.cc67
3 files changed, 211 insertions, 0 deletions
diff --git a/na/arch/riscv/uaccess/uaccess_memcpy.S b/na/arch/riscv/uaccess/uaccess_memcpy.S
new file mode 100644
index 0000000..615680d
--- /dev/null
+++ b/na/arch/riscv/uaccess/uaccess_memcpy.S
@@ -0,0 +1,117 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#include <arch/extable.h>
+
+/*
+ * extern "C" int __uaccess_memcpy(void* dst, const void* src, uint64 len);
+ *
+ * a0: dst
+ * a1: src
+ * a2: len
+ * Returns: 0 on success, -EFAULT (-14) on page fault.
+ */
+.global __uaccess_memcpy
+.type __uaccess_memcpy, @function
+__uaccess_memcpy:
+ /* If len == 0, return 0 */
+ beqz a2, .Lmemcpy_done
+
+ /* Enable SUM (Supervisor User Memory) bit: sstatus.SUM is bit 18 */
+ li t0, (1 << 18)
+ csrs sstatus, t0
+
+.Lmemcpy_loop:
+ /* Load byte from src */
+1: lb t1, 0(a1)
+ /* Store byte to dst */
+2: sb t1, 0(a0)
+
+ addi a0, a0, 1
+ addi a1, a1, 1
+ addi a2, a2, -1
+ bnez a2, .Lmemcpy_loop
+
+.Lmemcpy_done:
+ /* Disable SUM */
+ li t0, (1 << 18)
+ csrc sstatus, t0
+ li a0, 0
+ ret
+
+.Lmemcpy_fault:
+ /* Fixup routine on fault */
+ /* Disable SUM */
+ li t0, (1 << 18)
+ csrc sstatus, t0
+ li a0, -14 /* -EFAULT */
+ ret
+
+/* Exception table entries */
+EXTABLE(1b, .Lmemcpy_fault)
+EXTABLE(2b, .Lmemcpy_fault)
+
+
+/*
+ * extern "C" int64 __uaccess_strncpy(char* dst, const char* src, uint64 max_len);
+ *
+ * a0: dst
+ * a1: src
+ * a2: max_len
+ * Returns: number of bytes copied (excluding null), or -EFAULT (-14) on page fault.
+ */
+.global __uaccess_strncpy
+.type __uaccess_strncpy, @function
+__uaccess_strncpy:
+ /* If max_len == 0, return 0 */
+ beqz a2, .Lstrncpy_done_zero
+
+ /* Enable SUM */
+ li t0, (1 << 18)
+ csrs sstatus, t0
+
+ /* t2 will store the length copied */
+ li t2, 0
+
+.Lstrncpy_loop:
+ /* Load byte from src */
+3: lb t1, 0(a1)
+ /* Store byte to dst */
+4: sb t1, 0(a0)
+
+ /* If it was the null terminator, we're done */
+ beqz t1, .Lstrncpy_done
+
+ addi a0, a0, 1
+ addi a1, a1, 1
+ addi t2, t2, 1
+ addi a2, a2, -1
+ bnez a2, .Lstrncpy_loop
+
+ /* Reached max_len without finding null */
+ /* We must null-terminate at max_len - 1 as per typical strncpy_from_user semantics */
+ addi a0, a0, -1
+ sb zero, 0(a0)
+ addi t2, t2, -1 /* length of string without the forced null terminator */
+
+.Lstrncpy_done:
+ /* Disable SUM */
+ li t0, (1 << 18)
+ csrc sstatus, t0
+ mv a0, t2
+ ret
+
+.Lstrncpy_done_zero:
+ li a0, 0
+ ret
+
+.Lstrncpy_fault:
+ /* Fixup routine on fault */
+ li t0, (1 << 18)
+ csrc sstatus, t0
+ li a0, -14 /* -EFAULT */
+ ret
+
+/* Exception table entries */
+EXTABLE(3b, .Lstrncpy_fault)
+EXTABLE(4b, .Lstrncpy_fault)
diff --git a/na/include/lume/uaccess.h b/na/include/lume/uaccess.h
new file mode 100644
index 0000000..5a9ed71
--- /dev/null
+++ b/na/include/lume/uaccess.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * User-space memory access — safe copy routines.
+ *
+ * Phase 2: Software page-table validation.
+ * Phase 3+: Exception Table (zero-cost happy path).
+ *
+ * Reference: docs/specs/syscall.md §8
+ */
+
+#include <lume/types.h>
+
+/* Copy len bytes from user address to kernel buffer.
+ * Returns 0 on success, -EFAULT if user address is invalid. */
+int copy_from_user(void* kernel_dst, uint64 user_src, uint64 len);
+
+/* Copy len bytes from kernel buffer to user address.
+ * Returns 0 on success, -EFAULT if user address is invalid. */
+int copy_to_user(uint64 user_dst, const void* kernel_src, uint64 len);
+
+/* Copy NUL-terminated string from user space.
+ * Returns string length (excluding NUL) or -EFAULT. */
+int64 strncpy_from_user(char* kernel_dst, uint64 user_src, uint64 max_len);
diff --git a/na/kernel/uaccess.cc b/na/kernel/uaccess.cc
new file mode 100644
index 0000000..4a5b963
--- /dev/null
+++ b/na/kernel/uaccess.cc
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+/*
+ * User-Kernel Memory Copy — Phase 3 (Exception Tables)
+ *
+ * Validates user pointers by checking they fall within user address space
+ * (below KERNEL_VA_BASE). Uses assembly-level safe copy functions that
+ * integrate with the kernel's Exception Table mechanism.
+ *
+ * Reference: docs/specs/syscall.md §8
+ */
+
+#include <lume/uaccess.h>
+#include <lume/task.h>
+#include <lume/errno.h>
+#include <lume/config.h>
+
+/* Kernel VA base — addresses at or above this are kernel space */
+inline constexpr uint64 kKernelVABase = 0xFFFFFFC000000000ULL;
+
+extern "C" int __uaccess_memcpy(void* dst, const void* src, uint64 len);
+extern "C" int64 __uaccess_strncpy(char* dst, const char* src, uint64 max_len);
+
+/* Validate that [addr, addr+len) is entirely in user space */
+static bool is_user_range(uint64 addr, uint64 len)
+{
+ if (addr >= kKernelVABase)
+ return false;
+ if (addr + len < addr) /* overflow */
+ return false;
+ if (addr + len > kKernelVABase)
+ return false;
+ return true;
+}
+
+int copy_from_user(void* kernel_dst, uint64 user_src, uint64 len)
+{
+ if (len == 0) return 0;
+ if (!is_user_range(user_src, len)) return -EFAULT;
+
+ return __uaccess_memcpy(kernel_dst, reinterpret_cast<const void*>(user_src), len);
+}
+
+int copy_to_user(uint64 user_dst, const void* kernel_src, uint64 len)
+{
+ if (len == 0) return 0;
+ if (!is_user_range(user_dst, len)) return -EFAULT;
+
+ return __uaccess_memcpy(reinterpret_cast<void*>(user_dst), kernel_src, len);
+}
+
+int64 strncpy_from_user(char* kernel_dst, uint64 user_src, uint64 max_len)
+{
+ if (!is_user_range(user_src, 1)) return -EFAULT;
+
+ /* We must also check that the string does not cross into kernel space.
+ This is safely handled by the assembly routine which will fault if it hits
+ an unmapped page. However, to strictly respect kKernelVABase, we should
+ restrict max_len to the distance to kKernelVABase. */
+ uint64 max_allowed = kKernelVABase - user_src;
+ if (max_len > max_allowed) {
+ max_len = max_allowed;
+ }
+
+ return __uaccess_strncpy(kernel_dst, reinterpret_cast<const char*>(user_src), max_len);
+}