[RFC v4 0/3] Selfpatch-SLR: Randomizing Linux Kernel Structure Layouts at Boot
From: York Jasper Niebuhr
Date: Sat Oct 24 2026 - 23:16:03 EST
Hi there,
I am pleased to present the fourth iteration of Selfpatch-SLR (formerly
also called Bootpatch-SLR). Built on an entirely new GCC patch series,
and a few new concepts, this version achieves boot-time randomization of
almost the entire structure set that RandStruct targets. This series is
now based on v7.3-rc7.
RFC v3:
https://lists.openwall.net/linux-hardening/2026/07/20/38
Changes since RFC v3:
- Based on new GCC deferred constants
- Added refresh pins (rpins) to update static data leaves that depend
on randomized field offsets
- Drastically simplified metadata format
- Switched to fisher-yates shuffle with greedy padding-hole-filling
- Fixed build issues pointed out by Kees Cook
- Expanded set of target structures to reflect RandStruct targets
At this point, this series only applies to x86-64 and requires a custom
toolchain based on GCC 16.1.0 and GAS 2.46.1. The corresponding patches
are available at:
https://github.com/YJN-Systems/Selfpatch-SLR/tree/rfc-v4/toolchain/gcc_16_1_0
https://github.com/YJN-Systems/Selfpatch-SLR/tree/rfc-v4/toolchain/gas_2_46_1
With these, a defconfig+CONFIG_SPSLR kernel:
- successfully randomizes a total of 400 structure layouts (65
explicitly marked, the rest are pure-ops)
- patches 64438 instruction immediates that depend on the resulting
field offsets
- reorders 4521 static instances of target structures to reflect the
new layouts
- and updates 495 static data leaves that depend on randomized field
offsets (e.g. self-referencing list heads in the init_task)
at BOOT!
The behavior can be disabled using the 'nospslr' boot param. A seed for
randomization can be specified with 'spslr_seed=<up to 64 hex digits>'.
In QEMU, using kvm on an i9-13900H, SPSLR adds roughly 3ms to the boot
procedure, and no meaningful steady-state runtime overhead. While the
current instrumentation separates instruction patch sites into dedicated
gadgets, I am working on a follow-up patch that folds these into memory
operand displacements, bringing code size down to roughly what it is
without SPSLR transformations.
This RFC patch series is organized as follows:
* Patch 1 adds the Pinpoint GCC plugin.
* Patch 2 adds the Selfpatch runtime.
* Patch 3 makes minor adjustments to the explicit SPSLR target set,
compared to RandStruct.
Where the first 3 RFC versions used a custom GCC parser hook in the C
frontend to catch COMPONENT_REFs before field offset dependencies could
be folded into constants, v4 uses a new 'deferred constant' mechanism.
This mechanism preserves all dependencies on randomized field offsets
(e.g. when usually eliminated by fold_offsetof) through the C frontend.
The Pinpoint plugin, mostly running after the frontend finished parsing,
essentially sees
DEFERRED_CST_EXPR<IFN_DEFERRED_CST_FIELD_OFFSET<struct S, x>>
instead of
42
Additionally, deferred constants are allowed in static initializers,
meaning that the frontend will no longer throw an error due to non-const
initializers when they depend on deferred field offsets. Thus, code like
static size_t OFF = offsetof(struct task_struct, core_cookie);
survives until pinpoint's main instrumentation pass can generate a
refresh pin for this data point. While the deferred initializer is
replaced with its preliminary compile-time value (for the sake of
producing valid data in the ELF binary) Selfpatch substitutes the
runtime offsets into copies of the initializer's leaf expressions and
reapplies the initializers at runtime.
The new metadata format no longer uses per-CU data or runtime mappings
of CU-local target IDs to global targets. Instead, each subject (kernel
or module) now has exactly one array of target struct descriptors, one
of instruction pins, one of data pins, and one of refresh pins. The pins
refer to targets through an additional array of target references.
Selfpatch redirects a module's references to the kernel's target array
to allow efficient lookup.
I replaced the old whatever-that-was-shuffle with fisher-yates. To make
sure no random layout exceeds the original struct size, the shuffle is
followed by a greedy pull-back operation that fills a padding hole with
the first field that fits. While the old randomizer needed roughly half
a second to shuffle only the task_struct, this new mechanism is
practically instant.
As usual, I appreciate any feedback on this!
Thanks,
Jasper
York Jasper Niebuhr (3):
Selfpatch-SLR pinpoint plugin
Selfpatch-SLR selfpatch runtime
Selfpatch-SLR target annotations
.../admin-guide/kernel-parameters.txt | 9 +
arch/x86/boot/startup/Makefile | 4 +
arch/x86/entry/vdso/common/Makefile.include | 2 +-
arch/x86/include/asm/processor.h | 2 +-
arch/x86/kernel/vmlinux.lds.S | 42 +
fs/proc/internal.h | 7 +-
include/linux/compiler_types.h | 31 +-
include/linux/cred.h | 7 +-
include/linux/dax.h | 9 +-
include/linux/module.h | 18 +-
include/linux/sched.h | 21 +-
include/linux/spslr.h | 72 +
include/linux/time_namespace.h | 10 +-
include/linux/uts_namespace.h | 8 +-
include/net/neighbour.h | 10 +-
init/main.c | 39 +
ipc/sem.c | 6 +-
kernel/Makefile | 2 +
kernel/module/internal.h | 6 +
kernel/module/main.c | 194 ++-
kernel/module/strict_rwx.c | 23 +
kernel/spslr/Makefile | 5 +
kernel/spslr/pinpoint.h | 62 +
kernel/spslr/spslr.c | 590 +++++++
kernel/spslr/spslr_env.c | 147 ++
kernel/spslr/spslr_env.h | 41 +
kernel/spslr/spslr_randomizer.c | 264 +++
kernel/spslr/spslr_randomizer.h | 19 +
lib/tests/randstruct_kunit.c | 6 +-
scripts/Makefile.gcc-plugins | 11 +
scripts/gcc-plugins/Makefile | 15 +
.../gcc-plugins/pinpoint/deferred_pin_pass.c | 1437 +++++++++++++++++
scripts/gcc-plugins/pinpoint/dpin_registry.c | 212 +++
scripts/gcc-plugins/pinpoint/dpin_registry.h | 25 +
scripts/gcc-plugins/pinpoint/ipin_registry.c | 363 +++++
scripts/gcc-plugins/pinpoint/ipin_registry.h | 58 +
scripts/gcc-plugins/pinpoint/layout_hash.c | 71 +
scripts/gcc-plugins/pinpoint/layout_hash.h | 15 +
scripts/gcc-plugins/pinpoint/on_finish_type.c | 32 +
scripts/gcc-plugins/pinpoint/on_finish_unit.c | 221 +++
.../pinpoint/on_register_attributes.c | 61 +
scripts/gcc-plugins/pinpoint/on_start_unit.c | 17 +
scripts/gcc-plugins/pinpoint/passes.h | 21 +
scripts/gcc-plugins/pinpoint/pinpoint.c | 75 +
scripts/gcc-plugins/pinpoint/pinpoint.h | 78 +
scripts/gcc-plugins/pinpoint/rpin_registry.c | 45 +
scripts/gcc-plugins/pinpoint/rpin_registry.h | 22 +
.../pinpoint/rtl_ipin_survival_scan_pass.c | 47 +
scripts/gcc-plugins/pinpoint/serialize.c | 287 ++++
scripts/gcc-plugins/pinpoint/serialize.h | 114 ++
.../gcc-plugins/pinpoint/target_registry.c | 541 +++++++
.../gcc-plugins/pinpoint/target_registry.h | 59 +
scripts/module.lds.S | 43 +
security/Kconfig.hardening | 9 +-
security/selinux/include/objsec.h | 2 +-
security/selinux/ss/hashtab.h | 2 +-
56 files changed, 5504 insertions(+), 35 deletions(-)
create mode 100644 include/linux/spslr.h
create mode 100644 kernel/spslr/Makefile
create mode 100644 kernel/spslr/pinpoint.h
create mode 100644 kernel/spslr/spslr.c
create mode 100644 kernel/spslr/spslr_env.c
create mode 100644 kernel/spslr/spslr_env.h
create mode 100644 kernel/spslr/spslr_randomizer.c
create mode 100644 kernel/spslr/spslr_randomizer.h
create mode 100644 scripts/gcc-plugins/pinpoint/deferred_pin_pass.c
create mode 100644 scripts/gcc-plugins/pinpoint/dpin_registry.c
create mode 100644 scripts/gcc-plugins/pinpoint/dpin_registry.h
create mode 100644 scripts/gcc-plugins/pinpoint/ipin_registry.c
create mode 100644 scripts/gcc-plugins/pinpoint/ipin_registry.h
create mode 100644 scripts/gcc-plugins/pinpoint/layout_hash.c
create mode 100644 scripts/gcc-plugins/pinpoint/layout_hash.h
create mode 100644 scripts/gcc-plugins/pinpoint/on_finish_type.c
create mode 100644 scripts/gcc-plugins/pinpoint/on_finish_unit.c
create mode 100644 scripts/gcc-plugins/pinpoint/on_register_attributes.c
create mode 100644 scripts/gcc-plugins/pinpoint/on_start_unit.c
create mode 100644 scripts/gcc-plugins/pinpoint/passes.h
create mode 100644 scripts/gcc-plugins/pinpoint/pinpoint.c
create mode 100644 scripts/gcc-plugins/pinpoint/pinpoint.h
create mode 100644 scripts/gcc-plugins/pinpoint/rpin_registry.c
create mode 100644 scripts/gcc-plugins/pinpoint/rpin_registry.h
create mode 100644 scripts/gcc-plugins/pinpoint/rtl_ipin_survival_scan_pass.c
create mode 100644 scripts/gcc-plugins/pinpoint/serialize.c
create mode 100644 scripts/gcc-plugins/pinpoint/serialize.h
create mode 100644 scripts/gcc-plugins/pinpoint/target_registry.c
create mode 100644 scripts/gcc-plugins/pinpoint/target_registry.h
--
2.43.0