diff --git a/arch/riscv/include/asm/csr.h b/arch/riscv/include/asm/csr.h index d432928202efd3..b897fdf7805c1a 100644 --- a/arch/riscv/include/asm/csr.h +++ b/arch/riscv/include/asm/csr.h @@ -331,6 +331,13 @@ #define CSR_STVAL2 0x14b #define CSR_SATP 0x180 +/* + * Page-fault encoding in stval2/mtval2. Note this is an enum, not a bitfield. + */ +#define TVAL2_PF_RISCV 0 /* RISC-V page fault only */ +#define TVAL2_PF_CHERI 1 /* CHERI PTE fault only */ +#define TVAL2_PF_RISCV_CHERI 2 /* both */ + #define CSR_STIMECMP 0x14D #define CSR_STIMECMPH 0x15D diff --git a/arch/riscv/include/asm/user_ptr.h b/arch/riscv/include/asm/user_ptr.h index f3e17385faad9a..d35a8a0ce658bc 100644 --- a/arch/riscv/include/asm/user_ptr.h +++ b/arch/riscv/include/asm/user_ptr.h @@ -18,6 +18,13 @@ user_ptr_perms_t arch_user_ptr_owning_perms_from_prot(int prot, unsigned long vm if ((prot & PROT_READ) && (vm_flags & VM_READ_CAPS)) perms |= CHERI_PERM_MUTABLE_LOAD; + if (prot & PROT_NO_CAP) { + if (prot & PROT_READ) + perms |= CHERI_PERMS_LOAD_CAP; + if (prot & PROT_WRITE) + perms |= CHERI_PERMS_STORE_CAP; + } + if (prot & PROT_EXEC) { if (cheri_perms_get(regs->epc) & CHERI_PERM_SYSTEM_REGS) perms |= CHERI_PERM_SYSTEM_REGS; diff --git a/arch/riscv/mm/fault.c b/arch/riscv/mm/fault.c index 04ed6f8acae4fd..6c3a17f80c605d 100644 --- a/arch/riscv/mm/fault.c +++ b/arch/riscv/mm/fault.c @@ -246,29 +246,33 @@ static inline void vmalloc_fault(struct pt_regs *regs, int code, unsigned long a local_flush_tlb_page(addr); } -static inline bool access_error(unsigned long cause, struct vm_area_struct *vma) +/* + * Returns 0 if the access is permitted, otherwise the SIGSEGV si_code to + * deliver. + */ +static inline int access_error(unsigned long cause, struct vm_area_struct *vma, + bool cheri_pte_fault) { switch (cause) { case EXC_INST_PAGE_FAULT: - if (!(vma->vm_flags & VM_EXEC)) { - return true; - } + if (!(vma->vm_flags & VM_EXEC)) + return SEGV_ACCERR; break; case EXC_LOAD_PAGE_FAULT: /* Write implies read */ - if (!(vma->vm_flags & (VM_READ | VM_WRITE))) { - return true; - } + if (!(vma->vm_flags & (VM_READ | VM_WRITE))) + return SEGV_ACCERR; break; case EXC_STORE_PAGE_FAULT: - if (!(vma->vm_flags & VM_WRITE)) { - return true; - } + if (!(vma->vm_flags & VM_WRITE)) + return SEGV_ACCERR; + if (cheri_pte_fault && !(vma->vm_flags & VM_WRITE_CAPS)) + return SEGV_STORETAG; break; default: panic("%s: unhandled cause %lu", __func__, cause); } - return false; + return 0; } /* @@ -283,11 +287,22 @@ void handle_page_fault(struct pt_regs *regs) unsigned long addr, cause; unsigned int flags = FAULT_FLAG_DEFAULT; int code = SEGV_MAPERR; + int access_code; vm_fault_t fault; + bool cheri_pte_fault = false; cause = regs->cause; addr = regs->badaddr; +#ifdef CONFIG_RISCV_CHERI + if (cause == EXC_STORE_PAGE_FAULT || cause == EXC_LOAD_PAGE_FAULT) { + unsigned long t2 = csr_read(CSR_TVAL2); + + cheri_pte_fault = (t2 == TVAL2_PF_CHERI || + t2 == TVAL2_PF_RISCV_CHERI); + } +#endif + tsk = current; mm = tsk->mm; @@ -351,11 +366,12 @@ void handle_page_fault(struct pt_regs *regs) if (!vma) goto lock_mmap; - if (unlikely(access_error(cause, vma))) { + access_code = access_error(cause, vma, cheri_pte_fault); + if (unlikely(access_code)) { vma_end_read(vma); count_vm_vma_lock_event(VMA_LOCK_SUCCESS); tsk->thread.bad_cause = cause; - bad_area_nosemaphore(regs, SEGV_ACCERR, addr); + bad_area_nosemaphore(regs, access_code, addr); return; } @@ -390,11 +406,10 @@ void handle_page_fault(struct pt_regs *regs) * Ok, we have a good vm_area for this memory access, so * we can handle it. */ - code = SEGV_ACCERR; - - if (unlikely(access_error(cause, vma))) { + access_code = access_error(cause, vma, cheri_pte_fault); + if (unlikely(access_code)) { tsk->thread.bad_cause = cause; - bad_area(regs, mm, code, addr); + bad_area(regs, mm, access_code, addr); return; } diff --git a/include/uapi/asm-generic/mman-common.h b/include/uapi/asm-generic/mman-common.h index 3a9062b7acc96f..13c31c8d69b3e3 100644 --- a/include/uapi/asm-generic/mman-common.h +++ b/include/uapi/asm-generic/mman-common.h @@ -19,6 +19,8 @@ /* PCuABI mapping and capability permissions */ #define PROT_CAP_INVOKE 0x2000 /* mmap flag: provide CInvoke capability permission */ +#define PROT_CAP 0x4000 /* region may hold valid capability tags (tag stores preserved) */ +#define PROT_NO_CAP 0x8000 /* region may not hold valid capability tags (tag store fails with a signal) */ #define _PROT_MAX_SHIFT 16 #define PROT_MAX(prot) ((prot) << _PROT_MAX_SHIFT) diff --git a/include/uapi/asm-generic/siginfo.h b/include/uapi/asm-generic/siginfo.h index fa58d189ec9b57..564ceac7cc4c15 100644 --- a/include/uapi/asm-generic/siginfo.h +++ b/include/uapi/asm-generic/siginfo.h @@ -244,7 +244,8 @@ typedef struct siginfo { #define SEGV_CAPBOUNDSERR 12 /* Capability bounds fault */ #define SEGV_CAPPERMERR 13 /* Capability permission fault */ #define SEGV_CAPACCESSERR 14 /* Capability access fault */ -#define NSIGSEGV 14 +#define SEGV_STORETAG 15 /* Capability tag store fault */ +#define NSIGSEGV 15 /* * SIGBUS si_codes diff --git a/mm/mmap.c b/mm/mmap.c index 9cc9faad57c942..3390488f26512d 100644 --- a/mm/mmap.c +++ b/mm/mmap.c @@ -404,9 +404,26 @@ unsigned long do_mmap(struct file *file, unsigned long addr, * to. we assume access permissions have been handled by the open * of the memory object, so we don't do any here. */ + if (IS_ENABLED(CONFIG_CHERI_PURECAP_UABI)) { + if ((prot & PROT_CAP) && (prot & PROT_NO_CAP)) + return -EINVAL; + /* + * PROT_CAP is not supported with file-backed MAP_SHARED mapping + */ + if ((prot & PROT_CAP) && file && (flags & MAP_SHARED)) + return -EINVAL; + } + vm_flags |= calc_vm_prot_bits(prot, pkey) | calc_vm_flag_bits(file, flags) | mm->def_flags | VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC; + if (IS_ENABLED(CONFIG_CHERI_PURECAP_UABI)) { + if (prot & PROT_CAP) + vm_flags |= VM_READ_CAPS | VM_WRITE_CAPS; + else if (prot & PROT_NO_CAP) + vm_flags &= ~(VM_READ_CAPS | VM_WRITE_CAPS); + } + /* Obtain the address to map to. we verify (or select) it and ensure * that it represents a valid section of the address space. */ diff --git a/tools/testing/selftests/riscv/Makefile b/tools/testing/selftests/riscv/Makefile index 5671b4405a1294..07c132ab8af9db 100644 --- a/tools/testing/selftests/riscv/Makefile +++ b/tools/testing/selftests/riscv/Makefile @@ -5,7 +5,7 @@ ARCH ?= $(shell uname -m 2>/dev/null || echo not) ifneq (,$(filter $(ARCH),riscv)) -RISCV_SUBTARGETS ?= abi hwprobe mm sigreturn vector cfi +RISCV_SUBTARGETS ?= abi hwprobe mm sigreturn vector cfi cheri else RISCV_SUBTARGETS := endif diff --git a/tools/testing/selftests/riscv/cheri/Makefile b/tools/testing/selftests/riscv/cheri/Makefile new file mode 100644 index 00000000000000..de56b854ab5984 --- /dev/null +++ b/tools/testing/selftests/riscv/cheri/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 + +CFLAGS += -std=gnu99 -I. + +TEST_GEN_PROGS := mmap_prot_cap + +include ../../lib.mk + +$(OUTPUT)/mmap_prot_cap: mmap_prot_cap.c + $(CC) -o$@ $(CFLAGS) $(LDFLAGS) $^ diff --git a/tools/testing/selftests/riscv/cheri/mmap_prot_cap.c b/tools/testing/selftests/riscv/cheri/mmap_prot_cap.c new file mode 100644 index 00000000000000..632733af704262 --- /dev/null +++ b/tools/testing/selftests/riscv/cheri/mmap_prot_cap.c @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2026 BayLibre +/* + * Tests for PROT_CAP and PROT_NO_CAP mmap flags on RISCV CHERI. + * + * PROT_CAP enables capability tag preservation for a mapping. + * PROT_NO_CAP explicitly disables capability tags: per the Zcheripte + * spec, a capability store to a PROT_NO_CAP page raises a CapStore + * exception (delivered as SIGSEGV), and capability loads have their + * tags cleared. + * + * The #ifdef __CHERI__ tests verify actual tag behaviour and require a + * CHERI-capable compiler. The other tests verify syscall-level error + * handling and compile with any standard C compiler. + */ + +#include +#include +#include +#include +#include +#include + +#include "../../kselftest_harness.h" + +#ifdef __CHERI__ +#include +#endif + +/* 64 KiB — large enough for any typical page size */ +#define MMAP_SIZE (1UL << 16) + +#ifndef PROT_CAP +#define PROT_CAP 0x4000 +#endif +#ifndef PROT_NO_CAP +#define PROT_NO_CAP 0x8000 +#endif +#ifndef SEGV_STORETAG +#define SEGV_STORETAG 15 +#endif + +/* --- Syscall validation tests (always compiled) --- */ + +TEST(test_prot_cap_anon_private) +{ + void *ptr; + + ptr = mmap(NULL, MMAP_SIZE, PROT_READ | PROT_WRITE | PROT_CAP, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(MAP_FAILED, ptr); + EXPECT_EQ(0, munmap(ptr, MMAP_SIZE)); +} + +TEST(test_prot_no_cap_anon_private) +{ + void *ptr; + + ptr = mmap(NULL, MMAP_SIZE, PROT_READ | PROT_WRITE | PROT_NO_CAP, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(MAP_FAILED, ptr); + EXPECT_EQ(0, munmap(ptr, MMAP_SIZE)); +} + +/* PROT_CAP and PROT_NO_CAP are mutually exclusive */ +TEST(test_prot_cap_no_cap_einval) +{ + void *ptr; + + ptr = mmap(NULL, MMAP_SIZE, + PROT_READ | PROT_WRITE | PROT_CAP | PROT_NO_CAP, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ASSERT_EQ(MAP_FAILED, ptr); + ASSERT_EQ(EINVAL, errno); +} + +/* PROT_CAP on a file-backed MAP_SHARED mapping is rejected because ordinary + * filesystems drop capability tags on writeback. */ +TEST(test_prot_cap_file_shared_einval) +{ + char tmppath[] = "/tmp/mmap_prot_cap_XXXXXX"; + void *ptr; + int fd; + + fd = mkstemp(tmppath); + ASSERT_GE(fd, 0); + unlink(tmppath); + ASSERT_EQ(0, ftruncate(fd, MMAP_SIZE)); + + ptr = mmap(NULL, MMAP_SIZE, PROT_READ | PROT_WRITE | PROT_CAP, + MAP_SHARED, fd, 0); + ASSERT_EQ(MAP_FAILED, ptr); + ASSERT_EQ(EINVAL, errno); + + close(fd); +} + +/* PROT_CAP on anonymous MAP_SHARED is allowed (no filesystem writeback) */ +TEST(test_prot_cap_anon_shared) +{ + void *ptr; + + ptr = mmap(NULL, MMAP_SIZE, PROT_READ | PROT_WRITE | PROT_CAP, + MAP_SHARED | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(MAP_FAILED, ptr); + EXPECT_EQ(0, munmap(ptr, MMAP_SIZE)); +} + +/* PROT_CAP is mmap-time only; mprotect() must reject it */ +TEST(test_prot_cap_mprotect_einval) +{ + void *ptr; + + ptr = mmap(NULL, MMAP_SIZE, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(MAP_FAILED, ptr); + + EXPECT_EQ(-1, mprotect(ptr, MMAP_SIZE, PROT_READ | PROT_CAP)); + EXPECT_EQ(EINVAL, errno); + + munmap(ptr, MMAP_SIZE); +} + +/* PROT_NO_CAP is mmap-time only; mprotect() must reject it */ +TEST(test_prot_no_cap_mprotect_einval) +{ + void *ptr; + + ptr = mmap(NULL, MMAP_SIZE, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(MAP_FAILED, ptr); + + EXPECT_EQ(-1, mprotect(ptr, MMAP_SIZE, PROT_READ | PROT_NO_CAP)); + EXPECT_EQ(EINVAL, errno); + + munmap(ptr, MMAP_SIZE); +} + +/* --- Capability tag behaviour tests (require CHERI compiler) --- */ + +#ifdef __CHERI__ + +/* Storing a capability into a PROT_CAP region preserves the tag */ +TEST(test_prot_cap_preserves_tags) +{ + int anchor = 0; + void **region; + + region = mmap(NULL, MMAP_SIZE, PROT_READ | PROT_WRITE | PROT_CAP, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(MAP_FAILED, region); + + *region = &anchor; + EXPECT_EQ(1, cheri_tag_get(*region)) { + TH_LOG("capability tag was not preserved in PROT_CAP region"); + } + + munmap(region, MMAP_SIZE); +} + +static void segv_storetag_handler(int sig, siginfo_t *info, void *uctx) +{ + if (info->si_code == SEGV_STORETAG) + _exit(0); + _exit(3); /* wrong si_code */ +} + +/* + * Storing a capability into a PROT_NO_CAP region raises a CapStore exception + * delivered as SIGSEGV with si_code == SEGV_STORETAG. Per the Zcheripte spec, + * PTE.CW=0 causes a tagged capability store to trap rather than silently + * stripping the tag. A forked child installs a SA_SIGINFO handler to assert + * the si_code; the parent inspects the child's exit status. + * + * Child exit codes: + * 0 — handler saw SEGV_STORETAG (success) + * 2 — mmap failed + * 3 — handler saw a different si_code + * 4 — sc instruction did not trap + * + * The test also verifies that ordinary (non-capability) data access to a + * PROT_NO_CAP region still works — the restriction applies only to + * capability-tagged operations. + */ +TEST(test_prot_no_cap_store_traps) +{ + int status; + pid_t pid; + + pid = fork(); + ASSERT_GE(pid, 0); + + if (pid == 0) { + struct sigaction sa = { + .sa_sigaction = segv_storetag_handler, + .sa_flags = SA_SIGINFO, + }; + int anchor = 0; + void **region; + + sigemptyset(&sa.sa_mask); + sigaction(SIGSEGV, &sa, NULL); + + region = mmap(NULL, MMAP_SIZE, + PROT_READ | PROT_WRITE | PROT_NO_CAP, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (region == MAP_FAILED) + _exit(2); + + asm volatile("sc %0, (%1)" + : + : "C" (&anchor), "C" (region) + : "memory"); + _exit(4); /* sc did not trap */ + } + + ASSERT_EQ(pid, waitpid(pid, &status, 0)); + ASSERT_TRUE(WIFEXITED(status)) { + TH_LOG("expected clean exit from handler, child killed by signal %d", + WIFSIGNALED(status) ? WTERMSIG(status) : -1); + } + ASSERT_EQ(0, WEXITSTATUS(status)) { + TH_LOG("child exit %d (2=mmap fail, 3=wrong si_code, 4=sc did not trap)", + WEXITSTATUS(status)); + } + + { + int *region; + + region = mmap(NULL, MMAP_SIZE, + PROT_READ | PROT_WRITE | PROT_NO_CAP, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(MAP_FAILED, region); + + *region = 42; + EXPECT_EQ(42, *region); + + munmap(region, MMAP_SIZE); + } +} + +/* On RISCV CHERI the default for anonymous/private mappings implicitly + * enables capability tag support (VM_READ_CAPS | VM_WRITE_CAPS), so no + * explicit PROT_CAP is needed. */ +TEST(test_default_anon_preserves_tags) +{ + int anchor = 0; + void **region; + + region = mmap(NULL, MMAP_SIZE, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(MAP_FAILED, region); + + *region = &anchor; + EXPECT_EQ(1, cheri_tag_get(*region)) { + TH_LOG("default anonymous mapping did not preserve capability tag"); + } + + munmap(region, MMAP_SIZE); +} + +#endif /* __CHERI__ */ + +TEST_HARNESS_MAIN \ No newline at end of file