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 ,

Reply via email to