beachdead wrote: > Are there any plans to implement any of the suggested mitigations > from this?
We can't speak for the developers, but we read the four patches against -current. Safe unlinking is the one to take. It turns the TAILQ_REMOVE write Big Fish used into a panic, and GENERIC sets DIAGNOSTIC, so release kernels would get it. It needs hot-path benchmarks. Allocation-order randomization comes next, after measuring. It gives malloc(9) at most 4 bits per allocation, which spraying 16 objects defeats, and it allocates page-sized buckets 16 pages at a time. The malloc base patch adds about 12 bits once per boot and changes the same uvm_addr.c function as the allocation-order patch, so only one of the two can go in. The PTE slot patch adds 7 bits once per boot, on amd64 only; moving the page tables out of the recursive mapping, as pmap.c's comments plan, would do more. Below is other hardening that -current lacks on amd64 and arm64. Diffs for twelve items follow. Kernel - Restrict kern.malloc and kern.pool statistics to root. Big Fish groomed the heap with the malloc counts. (diff 04) - Build kernels with -ftrivial-auto-var-init=zero. (diff 09) - Add kern.allowsysvipc to switch SysV IPC off. SysV gave both 2026 local roots. (diff 06) - Randomize the arm64 kernel stack offset per fork, as amd64 does (diff 10), and on both per system call. - Move the shm attach map and struct ip_moptions out of the shared malloc(9) buckets into dedicated pools, and use refcnt(9) for the shm attach count. - Randomize the kernel base on both; the amd64 recursive PTE slot is fixed at 255. - Use kcfi for forward edges. IBT and BTI accept any function entry. - Ship array-bounds checks in release kernels, and add a KASAN runtime for fuzzing. - Build kernel objects with -ffunction-sections and relink with lld --shuffle-sections, so each boot reorders functions inside every object. RETGUARD - One cookie per C function. The compiler hashes names modulo 4000, so about 45% of libc functions and 78% of core kernel functions share a cookie with another. (diff 01) - Sign return addresses with PAC in the arm64 kernel, using a kernel key swapped on every user/kernel transition. (diff 03) - Give RETGUARD failures a distinct trap and a log line. arm64 uses brk #1, the __builtin_trap encoding. (diff 02) - Add RETGUARD to riscv64 and to mips64 assembly. Userland - Refuse PROT_EXEC mappings of files on noexec mounts; mmap never checks noexec. (diff 05) - Refuse PROT_EXEC mappings of files open for writing, refuse to open files mapped PROT_EXEC for writing, and keep mprotect from adding PROT_EXEC to file mappings. (diff 12) - Delay exec of a binary that keeps crashing, as segvguard does on NetBSD and HardenedBSD. - Check relink kits against a hash list before relinking at boot. (diff 08) - Let -fstack-clash-protection through the clang driver, where it has no effect on OpenBSD now. (diff 07) - Record each pinsyscalls call site in place of the -1 wildcard. - Port -fret-clean to arm64. Hardware - CET shadow stacks on amd64, where only IBT is on. - MTE on arm64 for omalloc, malloc(9) and pool(9). The kernel has no MTE support. A 2026 study measured 2-10% overhead in synchronous mode on AmpereOne and Apple M5. - E0PD on arm64. (diff 11) - LASS on amd64, on Xeon 6 and Lunar Lake or later. The diffs are against -current from 2026-10-24 and apply from /usr/src with patch -p0, one at a time or the whole message at once. They apply cleanly, the changed C passes clang -fsyntax-only for amd64 and arm64, and the arm64 assembly assembles; they still need building and booting on hardware. https://calif.io/research/big-fish https://arxiv.org/html/2601.11786v1 (MTE overhead) https://phoronix.com/news/Intel-LASS-Linux-7.1 Diff 01 gives each C function a separate RETGUARD cookie. Index: gnu/llvm/llvm/lib/CodeGen/ReturnProtectorPass.cpp --- gnu/llvm/llvm/lib/CodeGen/ReturnProtectorPass.cpp +++ gnu/llvm/llvm/lib/CodeGen/ReturnProtectorPass.cpp @@ -34,7 +34,9 @@ namespace { // Create a symbol for the cookie Module *M = F.getParent(); std::hash<std::string> hasher; - std::string hash = std::to_string(hasher((M->getName() + F.getName()).str()) % 4000); + // One cookie per function: a bucketed cookie lets one leaked + // value defeat the check in every function sharing the bucket. + std::string hash = std::to_string(hasher((M->getName() + F.getName()).str())); std::string cookiename = "__retguard_" + hash; Type *cookietype = PointerType::getUnqual(M->getContext()); GlobalVariable *cookie = dyn_cast_or_null<GlobalVariable>( Diff 02 gives arm64 RETGUARD failures a distinct trap and a log line. Index: gnu/llvm/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp --- gnu/llvm/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp +++ gnu/llvm/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp @@ -3608,7 +3608,9 @@ void AArch64AsmPrinter::emitInstruction( EmitToStreamer(*OutStreamer, MCInstBuilder(AArch64::CBZX) .addReg(MI->getOperand(0).getReg()) .addExpr(MCSymbolRefExpr::create(RGSuccSym, OutContext))); - EmitToStreamer(*OutStreamer, MCInstBuilder(AArch64::BRK).addImm(1)); + // 0x5247 ('RG') marks a RETGUARD failure, so the kernel can tell it + // apart from __builtin_trap (brk #1) and debugger breakpoints. + EmitToStreamer(*OutStreamer, MCInstBuilder(AArch64::BRK).addImm(0x5247)); OutStreamer->emitLabel(RGSuccSym); return; } Index: sys/arch/arm64/arm64/trap.c --- sys/arch/arm64/arm64/trap.c +++ sys/arch/arm64/arm64/trap.c @@ -34,6 +34,7 @@ #include <sys/ptrace.h> #include <sys/syscall.h> #include <sys/signalvar.h> +#include <sys/syslog.h> #include <sys/user.h> #include <uvm/uvm_extern.h> @@ -341,6 +342,10 @@ do_el1h_sync(struct trapframe *frame) kdata_abort(frame, esr, far, 0); break; case EXCP_BRK: + if ((esr & ISS_BRK_COMMENT_MASK) == ISS_BRK_RETGUARD) + panic("retguard check failed at 0x%lx", + frame->tf_elr); + /* FALLTHROUGH */ case EXCP_WATCHPT_EL1: case EXCP_SOFTSTP_EL1: #ifdef DDB @@ -434,6 +439,12 @@ do_el0_sync(struct trapframe *frame) break; case EXCP_BRK: sv.sival_ptr = (void *)frame->tf_elr; + if ((esr & ISS_BRK_COMMENT_MASK) == ISS_BRK_RETGUARD) { + log(LOG_ERR, "%s[%d]: retguard check failed at 0x%lx\n", + p->p_p->ps_comm, p->p_p->ps_pid, frame->tf_elr); + uprintf("%s[%d]: retguard check failed\n", + p->p_p->ps_comm, p->p_p->ps_pid); + } trapsignal(p, SIGTRAP, esr, TRAP_BRKPT, sv); break; case EXCP_SOFTSTP_EL0: Index: sys/arch/arm64/include/armreg.h --- sys/arch/arm64/include/armreg.h +++ sys/arch/arm64/include/armreg.h @@ -205,6 +205,7 @@ #define ISS_MSR_OP0_MASK (0x3 << ISS_MSR_OP0_SHIFT) #define ISS_MSR_OP0(x) (((x) & ISS_MSR_OP0_MASK) >> ISS_MSR_OP0_SHIFT) #define ISS_BRK_COMMENT_MASK 0xffff +#define ISS_BRK_RETGUARD 0x5247 /* brk immediate of a RETGUARD check */ #define ESR_ELx_IL (0x01 << 25) #define ESR_ELx_EC_SHIFT 26 #define ESR_ELx_EC_MASK (0x3f << 26) Index: sys/arch/arm64/include/asm.h --- sys/arch/arm64/include/asm.h +++ sys/arch/arm64/include/asm.h @@ -94,7 +94,7 @@ RETGUARD_LOAD_RANDOM(x, x9); \ subs reg, reg, x9; \ cbz reg, 66f; \ - brk #0x1; \ + brk #0x5247; \ 66: # define RETGUARD_PUSH(reg) \ Diff 03 adds PAC return signing to the arm64 kernel. Index: sys/arch/arm64/arm64/db_trace.c --- sys/arch/arm64/arm64/db_trace.c +++ sys/arch/arm64/arm64/db_trace.c @@ -49,6 +49,19 @@ db_regs_t ddb_regs; #define INKERNEL(va) (((vaddr_t)(va)) & (1ULL << 63)) +/* + * Saved return addresses may carry a pointer authentication code in + * their upper bits. Bit 55 still selects the kernel or user half, so + * rebuild the canonical address from it. + */ +vaddr_t +db_strip_pac(vaddr_t va) +{ + if (va & (1ULL << 55)) + return (va | VM_MIN_KERNEL_ADDRESS); + return (va & ~VM_MIN_KERNEL_ADDRESS); +} + void db_stack_trace_print(db_expr_t addr, int have_addr, db_expr_t count, char *modif, int (*pr)(const char *, ...)) @@ -84,15 +97,15 @@ db_stack_trace_print(db_expr_t addr, int } else if (trace_thread) { db_read_bytes(p->p_addr->u_pcb.pcb_sp, sizeof(sf), &sf); frame = sf.sf_x29; - lr = sf.sf_lr; + lr = db_strip_pac(sf.sf_lr); } else { frame = (vaddr_t)db_get_value(addr, 8, 0); - lr = (vaddr_t)db_get_value(addr + 8, 8, 0); + lr = db_strip_pac((vaddr_t)db_get_value(addr + 8, 8, 0)); } while (count-- && frame != 0) { lastlr = lr; - lr = (vaddr_t)db_get_value(frame + 8, 8, 0); + lr = db_strip_pac((vaddr_t)db_get_value(frame + 8, 8, 0)); if (INKERNEL(frame)) { sym = db_search_symbol(lastlr, DB_STGY_ANY, &offset); @@ -174,7 +187,7 @@ stacktrace_save_at(struct stacktrace *st while (st->st_count < STACKTRACE_MAX) { if (skip == 0) - st->st_pc[st->st_count++] = frame->f_lr; + st->st_pc[st->st_count++] = db_strip_pac(frame->f_lr); else skip--; @@ -185,7 +198,7 @@ stacktrace_save_at(struct stacktrace *st break; if (frame >= limit) break; - if (!INKERNEL(frame->f_lr)) + if (!INKERNEL(db_strip_pac(frame->f_lr))) break; } } Index: sys/arch/arm64/arm64/exception.S --- sys/arch/arm64/arm64/exception.S +++ sys/arch/arm64/arm64/exception.S @@ -27,6 +27,7 @@ #include <machine/asm.h> #include <machine/armreg.h> +#include <machine/pac.h> #include "assym.h" @@ -63,6 +64,10 @@ stp fp, x10, [sp, #(TF_SIZE)] mrs x18, tpidr_el1 add fp, sp, #(TF_SIZE) +.if \el == 0 + /* x0-x2 are saved; switch to the kernel IA key before any C code. */ + PAC_KERNEL_KEY +.endif .endm .macro restore_registers el @@ -73,6 +78,10 @@ * handler. For EL0 exceptions, do_ast already did this. */ .endif +.if \el == 0 + /* No C code runs from here to eret; x0-x2 are reloaded below. */ + PAC_USER_KEY +.endif ldp x18, lr, [sp, #(TF_SP)] ldp x10, x11, [sp, #(TF_ELR)] .if \el == 0 Index: sys/arch/arm64/arm64/genassym.cf --- sys/arch/arm64/arm64/genassym.cf +++ sys/arch/arm64/arm64/genassym.cf @@ -50,6 +50,7 @@ member ci_mpidr member ci_next member ci_trampoline_vectors member ci_ttbr1 +member ci_user_apiakey member ci_self struct proc Index: sys/arch/arm64/arm64/locore.S --- sys/arch/arm64/arm64/locore.S +++ sys/arch/arm64/arm64/locore.S @@ -33,6 +33,7 @@ #include <machine/armreg.h> #include <machine/codepatch.h> #include <machine/hypervisor.h> +#include <machine/pac.h> #include <machine/param.h> #define VIRT_BITS 39 @@ -451,6 +452,8 @@ cpu_hatch_secondary: ldr x1, [x0, #CI_EL1_STKEND] mov sp, x1 + PAC_KERNEL_ENABLE + adr x1, .Lcpu_init_secondary ldr x1, [x1] blr x1 @@ -510,6 +513,8 @@ cpu_park: ldr x1, [x0, #CI_EL1_STKEND] mov sp, x1 + PAC_KERNEL_ENABLE + bl hibernate_dcache_flush_machdep mrs x0, tpidr_el1 @@ -544,6 +549,8 @@ cpu_hatch_primary: ldr x1, [x0, #CI_EL1_STKEND] mov sp, x1 + PAC_KERNEL_ENABLE + /* Restore registers. */ adr x1, .Lcpu_init_primary ldr x1, [x1] Index: sys/arch/arm64/arm64/locore0.S --- sys/arch/arm64/arm64/locore0.S +++ sys/arch/arm64/arm64/locore0.S @@ -30,6 +30,7 @@ #include "assym.h" #include <machine/asm.h> #include <machine/armreg.h> +#include <machine/pac.h> #include <machine/param.h> #include <machine/pte.h> @@ -165,6 +166,8 @@ virtdone: /* trace back starts here */ mov fp, #0 + /* Sign kernel return addresses from the first C function on */ + PAC_KERNEL_ENABLE /* Branch to C code */ bl initarm bl main Index: sys/arch/arm64/arm64/machdep.c --- sys/arch/arm64/arm64/machdep.c +++ sys/arch/arm64/arm64/machdep.c @@ -44,12 +44,20 @@ #include <machine/bootconfig.h> #include <machine/bus.h> #include <machine/fpu.h> +#include <machine/pac.h> #include <machine/db_machdep.h> #include <ddb/db_extern.h> #include <dev/efi/efi.h> +/* + * Kernel IA key for return-address signing, filled by the boot loader + * before the kernel runs; see <machine/pac.h>. + */ +uint64_t kern_apiakey[2] __attribute__((section(".openbsd.randomdata"))); +int kern_pac_enabled; /* set by PAC_KERNEL_ENABLE */ + #include "softraid.h" #if NSOFTRAID > 0 #include <dev/softraidvar.h> Index: sys/arch/arm64/arm64/pmap.c --- sys/arch/arm64/arm64/pmap.c +++ sys/arch/arm64/arm64/pmap.c @@ -24,6 +24,7 @@ #include <uvm/uvm.h> #include <machine/cpufunc.h> +#include <machine/pac.h> #include <machine/pmap.h> #include <machine/db_machdep.h> @@ -2367,10 +2368,18 @@ pmap_setpauthkeys(struct pmap *pm) if (ID_AA64ISAR1_APA(cpu_id_aa64isar1) >= ID_AA64ISAR1_APA_PAC || ID_AA64ISAR1_API(cpu_id_aa64isar1) >= ID_AA64ISAR1_API_PAC || ID_AA64ISAR2_APA3(cpu_id_aa64isar2) >= ID_AA64ISAR2_APA3_PAC) { - __asm volatile ("msr apiakeylo_el1, %0" - :: "r"(pm->pm_apiakey[0])); - __asm volatile ("msr apiakeyhi_el1, %0" - :: "r"(pm->pm_apiakey[1])); + /* + * With kernel PAC the IA key register holds the kernel key + * while in EL1; the EL0 return path installs this one. + */ + curcpu()->ci_user_apiakey[0] = pm->pm_apiakey[0]; + curcpu()->ci_user_apiakey[1] = pm->pm_apiakey[1]; + if (kern_pac_enabled == 0) { + __asm volatile ("msr apiakeylo_el1, %0" + :: "r"(pm->pm_apiakey[0])); + __asm volatile ("msr apiakeyhi_el1, %0" + :: "r"(pm->pm_apiakey[1])); + } __asm volatile ("msr apdakeylo_el1, %0" :: "r"(pm->pm_apdakey[0])); __asm volatile ("msr apdakeyhi_el1, %0" Index: sys/arch/arm64/conf/Makefile.arm64 --- sys/arch/arm64/conf/Makefile.arm64 +++ sys/arch/arm64/conf/Makefile.arm64 @@ -95,7 +95,7 @@ SORTR= cat COPTIMIZE?= -Oz CMACHFLAGS+= -mbranch-protection=none .else -CMACHFLAGS+= -mbranch-protection=bti +CMACHFLAGS+= -mbranch-protection=standard .endif DEBUG?= -g Index: sys/arch/arm64/include/cpu.h --- sys/arch/arm64/include/cpu.h +++ sys/arch/arm64/include/cpu.h @@ -156,6 +156,7 @@ struct cpu_info { uint64_t ci_ttbr1; vaddr_t ci_el1_stkend; + uint64_t ci_user_apiakey[2]; /* IA key for EL0 */ uint32_t ci_psci_idle_latency; uint32_t ci_psci_idle_param; Index: sys/arch/arm64/include/pac.h --- sys/arch/arm64/include/pac.h +++ sys/arch/arm64/include/pac.h @@ -0,0 +1,108 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2026 fowlbert <[email protected]> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _MACHINE_PAC_H_ +#define _MACHINE_PAC_H_ + +/* + * Kernel return-address signing. + * + * The kernel signs return addresses with the IA key. Each process has + * its own IA key (pm_apiakey), so the kernel keeps a boot-wide IA key in + * kern_apiakey and swaps keys on every transition: + * + * EL0 -> EL1 PAC_KERNEL_KEY loads kern_apiakey before any C code. + * EL1 -> EL0 PAC_USER_KEY loads ci_user_apiakey after the last C code. + * + * Without the swap a process could use pacia in EL0 as a signing oracle + * for kernel return addresses. One boot-wide key, not one per CPU, lets + * a thread migrate between CPUs with signed frames on its stack. + * + * SCTLR_EL1.EnIA must be set before the first signed C function runs on + * a CPU and never toggled under a signed frame, so PAC_KERNEL_ENABLE runs + * in assembly on every path into C: boot, secondary hatch, hibernate and + * suspend resume. + */ + +#ifdef _LOCORE + +/* Turn on IA signing for this CPU if it has address authentication. */ +#define PAC_KERNEL_ENABLE \ + .arch_extension pauth; \ + mrs x9, id_aa64isar1_el1; \ + ubfx x10, x9, #ID_AA64ISAR1_APA_SHIFT, #4; \ + ubfx x11, x9, #ID_AA64ISAR1_API_SHIFT, #4; \ + orr x10, x10, x11; \ + mrs x9, id_aa64isar2_el1; \ + ubfx x11, x9, #ID_AA64ISAR2_APA3_SHIFT, #4; \ + orr x10, x10, x11; \ + cbz x10, 199f; \ + adrp x9, kern_apiakey; \ + add x9, x9, :lo12:kern_apiakey; \ + ldp x10, x11, [x9]; \ + msr apiakeylo_el1, x10; \ + msr apiakeyhi_el1, x11; \ + mrs x9, sctlr_el1; \ + orr x9, x9, #SCTLR_EnIA; \ + msr sctlr_el1, x9; \ + isb; \ + adrp x9, kern_pac_enabled; \ + mov w10, #1; \ + str w10, [x9, :lo12:kern_pac_enabled]; \ +199: + +/* EL0 -> EL1: install the kernel IA key. Clobbers x0-x2. */ +#define PAC_KERNEL_KEY \ + .arch_extension pauth; \ + adrp x0, kern_pac_enabled; \ + ldr w0, [x0, :lo12:kern_pac_enabled]; \ + cbz w0, 198f; \ + adrp x0, kern_apiakey; \ + add x0, x0, :lo12:kern_apiakey; \ + ldp x1, x2, [x0]; \ + msr apiakeylo_el1, x1; \ + msr apiakeyhi_el1, x2; \ + isb; \ +198: + +/* + * EL1 -> EL0: install the current process's IA key. Clobbers x0-x2. + * Interrupts must be off and no signed function may run until eret, + * which synchronizes the key write. + */ +#define PAC_USER_KEY \ + .arch_extension pauth; \ + adrp x0, kern_pac_enabled; \ + ldr w0, [x0, :lo12:kern_pac_enabled]; \ + cbz w0, 197f; \ + mrs x0, tpidr_el1; \ + mov x1, #CI_USER_APIAKEY; \ + add x0, x0, x1; \ + ldp x1, x2, [x0]; \ + msr apiakeylo_el1, x1; \ + msr apiakeyhi_el1, x2; \ +197: + +#else /* !_LOCORE */ + +extern uint64_t kern_apiakey[2]; +extern int kern_pac_enabled; + +#endif /* _LOCORE */ + +#endif /* _MACHINE_PAC_H_ */ Diff 04 limits kern.malloc and kern.pool statistics to root. Index: sys/kern/kern_malloc.c --- sys/kern/kern_malloc.c +++ sys/kern/kern_malloc.c @@ -636,6 +636,7 @@ sysctl_malloc(int *name, u_int namelen, #ifdef KMEMSTATS struct kmemstats km; #endif + int error; if (namelen != 2 && name[0] != KERN_MALLOC_BUCKETS && name[0] != KERN_MALLOC_KMEMNAMES) @@ -646,6 +647,9 @@ sysctl_malloc(int *name, u_int namelen, return (sysctl_rdstring(oldp, oldlenp, newp, buckstring)); case KERN_MALLOC_BUCKET: + /* Bucket counts let an attacker groom the kernel heap. */ + if ((error = suser(p)) != 0) + return (error); mtx_enter(&malloc_mtx); memcpy(&kb, &bucket[BUCKETINDX(name[1])], sizeof(kb)); mtx_leave(&malloc_mtx); @@ -653,6 +657,8 @@ sysctl_malloc(int *name, u_int namelen, return (sysctl_rdstruct(oldp, oldlenp, newp, &kb, sizeof(kb))); case KERN_MALLOC_KMEMSTATS: #ifdef KMEMSTATS + if ((error = suser(p)) != 0) + return (error); if ((name[1] < 0) || (name[1] >= M_LAST)) return (EINVAL); mtx_enter(&malloc_mtx); Index: sys/kern/subr_pool.c --- sys/kern/subr_pool.c +++ sys/kern/subr_pool.c @@ -1481,10 +1481,14 @@ sysctl_dopool(int *name, u_int namelen, return (ENOTDIR); return (sysctl_rdint(oldp, oldlenp, NULL, pool_count)); - case KERN_POOL_NAME: case KERN_POOL_POOL: case KERN_POOL_CACHE: case KERN_POOL_CACHE_CPUS: + /* Per-pool counts let an attacker groom the kernel heap. */ + if (suser(curproc) != 0) + return (EPERM); + break; + case KERN_POOL_NAME: break; default: return (EOPNOTSUPP); Diff 05 refuses PROT_EXEC mappings from noexec mounts. Index: sys/uvm/uvm_mmap.c --- sys/uvm/uvm_mmap.c +++ sys/uvm/uvm_mmap.c @@ -348,7 +348,20 @@ sys_mmap(struct proc *p, void *v, regist } /* now check protection */ - maxprot = PROT_EXEC; + maxprot = PROT_NONE; + + /* + * Files on noexec filesystems cannot be mapped executable. + * Leaving PROT_EXEC out of maxprot stops mprotect(2) from + * adding it later. + */ + if (vp->v_mount == NULL || + (vp->v_mount->mnt_flag & MNT_NOEXEC) == 0) + maxprot |= PROT_EXEC; + else if (prot & PROT_EXEC) { + error = EACCES; + goto out; + } /* check read access */ if (fp->f_flag & FREAD) Diff 06 adds kern.allowsysvipc. Index: lib/libc/sys/sysctl.2 --- lib/libc/sys/sysctl.2 +++ lib/libc/sys/sysctl.2 @@ -480,6 +480,7 @@ information. .It Sy "Second level name" Ta Sy "Type" Ta Sy "Changeable" .It Dv KERN_ALLOWDT Ta "integer" Ta "yes" .It Dv KERN_ALLOWKMEM Ta "integer" Ta "yes" +.It Dv KERN_ALLOWSYSVIPC Ta "integer" Ta "yes" .It Dv KERN_ARGMAX Ta "integer" Ta "no" .It Dv KERN_AUDIO Ta "node" Ta "yes" .It Dv KERN_BOOTTIME Ta "struct timeval" Ta "no" @@ -570,6 +571,27 @@ and When running with a .Xr securelevel 7 greater than 0, +this variable may not be changed. +.It Dv KERN_ALLOWSYSVIPC Pq Va kern.allowsysvipc +Allow userland processes to use System V IPC. +When set to 0, +.Xr msgctl 2 , +.Xr msgget 2 , +.Xr msgrcv 2 , +.Xr msgsnd 2 , +.Xr semctl 2 , +.Xr semget 2 , +.Xr semop 2 , +.Xr shmat 2 , +.Xr shmctl 2 +and +.Xr shmget 2 +fail with +.Er ENOSYS . +The default is 1. +When running with a +.Xr securelevel 7 +greater than 0, this variable may not be changed. .It Dv KERN_ARGMAX Pq Va kern.argmax The maximum number of bytes allowed among the arguments to Index: sys/kern/kern_sysctl.c --- sys/kern/kern_sysctl.c +++ sys/kern/kern_sysctl.c @@ -605,6 +605,11 @@ kern_sysctl(int *name, u_int namelen, vo case KERN_ALLOWKMEM: return (sysctl_securelevel_int(oldp, oldlenp, newp, newlen, &allowkmem)); +#if defined(SYSVMSG) || defined(SYSVSEM) || defined(SYSVSHM) + case KERN_ALLOWSYSVIPC: + return (sysctl_securelevel_int(oldp, oldlenp, newp, newlen, + &allowsysvipc)); +#endif case KERN_NUMVNODES: /* XXX numvnodes is a long */ return (sysctl_rdint(oldp, oldlenp, newp, numvnodes)); case KERN_BOOTTIME: { Index: sys/kern/sysv_ipc.c --- sys/kern/sysv_ipc.c +++ sys/kern/sysv_ipc.c @@ -37,6 +37,13 @@ #include <sys/vnode.h> /* + * kern.allowsysvipc: 0 makes every System V IPC system call except + * shmdt(2) fail with ENOSYS. Like kern.allowkmem it can change only at + * securelevel 0 or below, so it is set from sysctl.conf(5) at boot. + */ +int allowsysvipc = 1; /* [a] */ + +/* * Check for ipc permission */ Index: sys/kern/sysv_msg.c --- sys/kern/sysv_msg.c +++ sys/kern/sysv_msg.c @@ -106,6 +106,9 @@ sys_msgctl(struct proc *p, void *v, regi int cmd = SCARG(uap, cmd); int error; + if (atomic_load_int(&allowsysvipc) == 0) + return (ENOSYS); + if ((que = que_lookup(msqid)) == NULL) return (EINVAL); @@ -192,6 +195,9 @@ sys_msgget(struct proc *p, void *v, regi int msgflg = SCARG(uap, msgflg); int error = 0; + if (atomic_load_int(&allowsysvipc) == 0) + return (ENOSYS); + again: if (key != IPC_PRIVATE) { que = que_key_lookup(key); @@ -242,6 +248,9 @@ sys_msgsnd(struct proc *p, void *v, regi size_t msgsz = SCARG(uap, msgsz); int error; + if (atomic_load_int(&allowsysvipc) == 0) + return (ENOSYS); + if ((que = que_lookup(SCARG(uap, msqid))) == NULL) return (EINVAL); @@ -319,6 +328,9 @@ sys_msgrcv(struct proc *p, void *v, regi long msgtyp = SCARG(uap, msgtyp); int error; + if (atomic_load_int(&allowsysvipc) == 0) + return (ENOSYS); + if ((que = que_lookup(SCARG(uap, msqid))) == NULL) return (EINVAL); Index: sys/kern/sysv_sem.c --- sys/kern/sysv_sem.c +++ sys/kern/sysv_sem.c @@ -260,6 +260,9 @@ sys___semctl(struct proc *p, void *v, re unsigned short *semval = NULL, nsems; int i, ix, error; + if (atomic_load_int(&allowsysvipc) == 0) + return (ENOSYS); + switch (cmd) { case IPC_SET: case IPC_STAT: @@ -448,6 +451,9 @@ sys_semget(struct proc *p, void *v, regi struct semid_ds_kern *semaptr, *semaptr_new = NULL; struct ucred *cred = p->p_ucred; + if (atomic_load_int(&allowsysvipc) == 0) + return (ENOSYS); + DPRINTF(("semget(0x%x, %d, 0%o)\n", key, nsems, semflg)); /* @@ -566,6 +572,9 @@ sys_semop(struct proc *p, void *v, regis size_t i, j; int do_wakeup, do_undos, error; + if (atomic_load_int(&allowsysvipc) == 0) + return (ENOSYS); + DPRINTF(("call to semop(%d, %p, %lu)\n", semid, SCARG(uap, sops), (u_long)nsops)); Index: sys/kern/sysv_shm.c --- sys/kern/sysv_shm.c +++ sys/kern/sysv_shm.c @@ -226,6 +226,9 @@ sys_shmat(struct proc *p, void *v, regis vm_prot_t prot; vsize_t size; + if (atomic_load_int(&allowsysvipc) == 0) + return (ENOSYS); + shmmap_h = (struct shmmap_head *)p->p_vmspace->vm_shm; if (shmmap_h == NULL) { struct shmmap_head *shmmap_h_probe; @@ -336,6 +339,9 @@ sys_shmctl(struct proc *p, void *v, regi struct shmid_ds shmbuf; int error; + if (atomic_load_int(&allowsysvipc) == 0) + return (ENOSYS); + if (cmd == IPC_SET) { error = copyin(buf, &shmbuf, sizeof(shmbuf)); if (error) @@ -514,6 +520,9 @@ sys_shmget(struct proc *p, void *v, regi } */ *uap = v; int segnum, mode, error; + if (atomic_load_int(&allowsysvipc) == 0) + return (ENOSYS); + mode = SCARG(uap, shmflg) & ACCESSPERMS; if (SCARG(uap, key) != IPC_PRIVATE) { Index: sys/sys/ipc.h --- sys/sys/ipc.h +++ sys/sys/ipc.h @@ -86,6 +86,8 @@ struct ucred; int ipcperm(struct ucred *, struct ipc_perm *, int); +extern int allowsysvipc; /* kern.allowsysvipc */ + #else /* !_KERNEL */ __BEGIN_DECLS Index: sys/sys/sysctl.h --- sys/sys/sysctl.h +++ sys/sys/sysctl.h @@ -193,7 +193,8 @@ struct ctlname { #define KERN_VIDEO 89 /* struct: video properties */ #define KERN_CLOCKINTR 90 /* node: clockintr */ #define KERN_AUTOCONF_SERIAL 91 /* int: kernel device tree state serial */ -#define KERN_MAXID 92 /* number of valid kern ids */ +#define KERN_ALLOWSYSVIPC 92 /* int: allow System V IPC */ +#define KERN_MAXID 93 /* number of valid kern ids */ #define CTL_KERN_NAMES { \ { 0, 0 }, \ @@ -288,6 +289,7 @@ struct ctlname { { "video", CTLTYPE_STRUCT }, \ { "clockintr", CTLTYPE_NODE }, \ { "autoconf_serial", CTLTYPE_INT }, \ + { "allowsysvipc", CTLTYPE_INT }, \ } /* Diff 07 passes -fstack-clash-protection through the clang driver. Index: gnu/llvm/clang/lib/Driver/ToolChains/Clang.cpp --- gnu/llvm/clang/lib/Driver/ToolChains/Clang.cpp +++ gnu/llvm/clang/lib/Driver/ToolChains/Clang.cpp @@ -3608,7 +3608,7 @@ static void RenderSCPOptions(const ToolC const llvm::Triple &EffectiveTriple = TC.getEffectiveTriple(); if (!EffectiveTriple.isOSFreeBSD() && !EffectiveTriple.isOSLinux() && - !EffectiveTriple.isOSFuchsia()) + !EffectiveTriple.isOSFuchsia() && !EffectiveTriple.isOSOpenBSD()) return; if (!EffectiveTriple.isX86() && !EffectiveTriple.isSystemZ() && Diff 08 checks relink kits against /var/db/relink.SHA256. Index: etc/rc --- etc/rc +++ etc/rc @@ -176,10 +176,17 @@ make_keys() { # Re-link libraries, placing the objects in a random order. reorder_libs() { local _error=false _dkdev _liba _libas _mp _ro_list _tmpdir - local _relink=/usr/share/relink + local _relink=/usr/share/relink _sums=/var/db/relink.SHA256 [[ $library_aslr == NO ]] && return + # Do not relink ld.so, libc, libcrypto or the program kits from + # objects that differ from the recorded checksums. + if [[ -f $_sums ]] && ! sha256 -q -C $_sums >/dev/null 2>&1; then + echo 'reordering: relink kit checksum mismatch (skipped).' + return + fi + # Skip if /usr/lib, /usr/libexec or /usr/share/relink are on nfs mounted # filesystems, otherwise record which ones are mounted read-only. for _dkdev in $(df /usr/{lib,libexec} $_relink | Index: libexec/reorder_kernel/reorder_kernel.sh --- libexec/reorder_kernel/reorder_kernel.sh +++ libexec/reorder_kernel/reorder_kernel.sh @@ -26,6 +26,7 @@ KERNEL_DIR=/usr/share/relink/kernel LOGFILE=$KERNEL_DIR/$KERNEL/relink.log PROGNAME=${0##*/} SHA256=/var/db/kernel.SHA256 +RELINK_SHA256=/var/db/relink.SHA256 # Silently skip if on a NFS mounted filesystem. df -t nonfs $KERNEL_DIR >/dev/null 2>&1 @@ -62,6 +63,16 @@ sha256 -h $SHA256 /bsd __EOF # Trigger ERR trap false +fi + +if [[ -f $RELINK_SHA256 ]] && ! sha256 -q -C $RELINK_SHA256; then + cat <<__EOF + +The relink kit does not match $RELINK_SHA256, therefore a randomly +linked kernel (KARL) is not being built. +__EOF + # Trigger ERR trap + false fi cd $KERNEL_DIR/$KERNEL Diff 09 zero-initializes kernel locals. Index: sys/arch/amd64/conf/Makefile.amd64 --- sys/arch/amd64/conf/Makefile.amd64 +++ sys/arch/amd64/conf/Makefile.amd64 @@ -83,6 +83,7 @@ CWARNFLAGS= -Werror -Wall -Wimplicit-fun CMACHFLAGS= -mcmodel=kernel -mno-red-zone -mno-sse2 -mno-sse -mno-3dnow \ -mno-mmx -msoft-float -fno-omit-frame-pointer CMACHFLAGS+= -ffreestanding ${NOPIE_FLAGS} +CMACHFLAGS+= -ftrivial-auto-var-init=zero SORTR= sort -R .if ${IDENT:M-DNO_PROPOLICE} CMACHFLAGS+= -fno-stack-protector Index: sys/arch/arm64/conf/Makefile.arm64 --- sys/arch/arm64/conf/Makefile.arm64 +++ sys/arch/arm64/conf/Makefile.arm64 @@ -86,6 +86,7 @@ CMACHFLAGS= -march=armv8-a+nofp+nosimd+r -ffixed-x18 CMACHFLAGS+= -ffreestanding ${NOPIE_FLAGS} CMACHFLAGS+= -moutline-atomics +CMACHFLAGS+= -ftrivial-auto-var-init=zero SORTR= sort -R .if ${IDENT:M-DNO_PROPOLICE} CMACHFLAGS+= -fno-stack-protector Diff 10 randomizes the arm64 kernel stack offset per fork. Index: sys/arch/arm64/arm64/vm_machdep.c --- sys/arch/arm64/arm64/vm_machdep.c +++ sys/arch/arm64/arm64/vm_machdep.c @@ -91,10 +91,15 @@ cpu_fork(struct proc *p1, struct proc *p pmap_activate(p2); + /* + * Place the trapframe, and so the whole kernel stack, at a random + * 16-byte aligned offset within the top page, as amd64 does. + */ tf = (struct trapframe *)((u_long)p2->p_addr + USPACE - sizeof(struct trapframe) - - 0x10); + - 0x10 + - (arc4random() & PAGE_MASK & ~_STACKALIGNBYTES)); tf = (struct trapframe *)STACKALIGN(tf); pcb->pcb_tf = tf; Diff 11 turns on E0PD on arm64. Index: sys/arch/arm64/include/armreg.h --- sys/arch/arm64/include/armreg.h +++ sys/arch/arm64/include/armreg.h @@ -646,6 +646,11 @@ #define ID_AA64MMFR2_IDS(x) ((x) & ID_AA64MMFR2_IDS_MASK) #define ID_AA64MMFR2_IDS_NONE (0x0ULL << ID_AA64MMFR2_IDS_SHIFT) #define ID_AA64MMFR2_IDS_IMPL (0x1ULL << ID_AA64MMFR2_IDS_SHIFT) +#define ID_AA64MMFR2_E0PD_SHIFT 60 +#define ID_AA64MMFR2_E0PD_MASK (0xfULL << ID_AA64MMFR2_E0PD_SHIFT) +#define ID_AA64MMFR2_E0PD(x) ((x) & ID_AA64MMFR2_E0PD_MASK) +#define ID_AA64MMFR2_E0PD_NONE (0x0ULL << ID_AA64MMFR2_E0PD_SHIFT) +#define ID_AA64MMFR2_E0PD_IMPL (0x1ULL << ID_AA64MMFR2_E0PD_SHIFT) /* ID_AA64PFR0_EL1 */ #define ID_AA64PFR0_MASK 0xff0fffffffffffffULL @@ -905,6 +910,8 @@ #define PSR_N 0x80000000 /* TCR_EL1 - Translation Control Register */ +#define TCR_E0PD1 (1UL << 56) /* EL0 access to TTBR1 range faults */ +#define TCR_E0PD0 (1UL << 55) #define TCR_AS (1UL << 36) #define TCR_IPS_SHIFT 32 Index: sys/arch/arm64/arm64/cpu.c --- sys/arch/arm64/arm64/cpu.c +++ sys/arch/arm64/arm64/cpu.c @@ -1209,6 +1209,11 @@ cpu_identify(struct cpu_info *ci) sep = ","; } + if (ID_AA64MMFR2_E0PD(id) >= ID_AA64MMFR2_E0PD_IMPL) { + printf("%sE0PD", sep); + sep = ","; + } + /* * ID_AA64PFR0 */ @@ -1806,6 +1811,13 @@ cpu_init(void) tcr &= ~TCR_T0SZ(0x3f); tcr |= TCR_T0SZ(64 - USER_SPACE_BITS); tcr |= TCR_A1; + /* + * With E0PD, EL0 accesses to the kernel (TTBR1) half fault in + * constant time, without a table walk that timing could expose. + */ + if (ID_AA64MMFR2_E0PD(READ_SPECIALREG(id_aa64mmfr2_el1)) >= + ID_AA64MMFR2_E0PD_IMPL) + tcr |= TCR_E0PD1; WRITE_SPECIALREG(tcr_el1, tcr); cpu_tlb_flush(); Diff 12 treats files mapped PROT_EXEC like running program text. Copying over a mapped library fails with ETXTBSY, and a writable MAP_SHARED mapping made earlier still reaches the file. Index: sys/uvm/uvm_mmap.c --- sys/uvm/uvm_mmap.c +++ sys/uvm/uvm_mmap.c @@ -384,6 +384,22 @@ sys_mmap(struct proc *p, void *v, regist /* MAP_PRIVATE mappings can always write to */ maxprot |= PROT_WRITE; } + + /* + * Treat a file mapped executable like the text of a running + * program. It cannot be mapped while open for writing, and + * vn_writechk() refuses to open it for writing while mapped. + * PROT_EXEC must be asked for here, so that mprotect(2) + * cannot add it to a mapping that skipped these checks. + */ + if (vp->v_type == VREG) { + if ((prot & PROT_EXEC) == 0) + maxprot &= ~PROT_EXEC; + else if (vp->v_writecount != 0) { + error = ETXTBSY; + goto out; + } + } if ((flags & __MAP_NOFAULT) != 0 || ((flags & MAP_PRIVATE) != 0 && (prot & PROT_WRITE) != 0)) { limit = lim_cur(RLIMIT_DATA); @@ -395,6 +411,8 @@ sys_mmap(struct proc *p, void *v, regist } error = uvm_mmapfile(&p->p_vmspace->vm_map, &addr, size, prot, maxprot, flags, vp, pos, lim_cur(RLIMIT_MEMLOCK), p); + if (error == 0 && vp->v_type == VREG && (prot & PROT_EXEC)) + vn_marktext(vp); FRELE(fp, p); KERNEL_UNLOCK(); } else { /* MAP_ANON case */ Index: lib/libc/sys/mmap.2 --- lib/libc/sys/mmap.2 +++ lib/libc/sys/mmap.2 @@ -307,6 +307,11 @@ and .Fa len parameters specify a region which contains at least one page marked immutable. +.It Bq Er ETXTBSY +.Dv PROT_EXEC +was specified and +.Fa fd +refers to a regular file that is open for writing. .El .Sh SEE ALSO .Xr madvise 2 ,

