On Thu, Sep 3, 2026 at 12:29 PM Ard Biesheuvel <[email protected]> wrote:
>
> Hello Jasper,
>
> This work looks very interesting, thanks for sticking with it.
> That said, I think the path to possible upstream inclusion is
> rather uncertain, given the intrusive nature of this work.

Hi Ard,
Thank you for your input!

While I expect a long way there, I envision a SPSLR that does not impose
any other compatibility constraints than RandStruct does. Just the same
layout-insensitivity that RandStruct has put to the test over the last decade.
So I wouldn't call SPSLR more intrusive, just scarier :)

> On Mon, 20 Jul 2026, at 21:11, York Jasper Niebuhr wrote:
> > Hello,
> > this is the third RFC version of Bootpatch-SLR. The previous RFCs were
> > sent to linux-hardening; this version is also sent to LKML for broader
> > review. Compared to RFC v2, it uses a new GAS feature instead of a
> > dedicated assembler inside the compiler plugin. Additionally, it
> > introduces the Sanemaker validation framework. This version rebases
> > BPSLR onto Linux 7.2-rc3 and resolves the boot failures that prevented
> > earlier rebases.
> >
> > RFC v2:
> >     https://lists.openwall.net/linux-hardening/2026/06/20/5
> >
> > Changes since RFC v2:
> >   - Implemented GAS fieldlabel feature to label immediate instruction
> >     operand bytes.
> >   - Removed pinpoint instruction pin assembler. Metadata now links
> >     directly against fieldlabels emitted by GAS.
> >   - Implemented Sanemaker validation tool and integrated it into the
> >     kernel.
> >   - Rebased Bootpatch-SLR onto Linux 7.2-rc3.
> >   - Excluded some RCU fields of task_struct from randomization to
> >     prevent boot crashes on new version.
> >
> > Bootpatch-SLR enables per-instance structure layout randomization for
> > distribution kernels. While GCC's RandStruct pass randomizes layouts at
> > compile-time, every machine running the same kernel image receives the
> > same layout. Bootpatch-SLR applies comparable randomization during boot.
> >
>
> I take it this means that DWARF debug data and BTF typeinfo are no longer
> usable on such kernels? If so, how does that impact BPF?

Yes, both DWARF and BTF are currently unusable on these kernels. But I think
that this can be changed. For example, the SPSLR runtime could patch the BTF
BLOB in memory to reflect the new structure layouts. Adjusting DWARF debug
info to dynamic layouts should not even require runtime patching. Instead, the
DW_AT_data_member_location DIEs can be based on DWARF expressions
rather than constants. For example, give a static variable
"struct_S_field_x_offset"
that holds the runtime offset of a field:

    DW_AT_data_member_location:
        DW_OP_addr        struct_S_field_x_offset
        DW_OP_deref_size  4

should naturally yield valid debug info at runtime.

> > A full architectural overview and additional resources are available at:
> >
> >     https://spslr.yjn-systems.com
> >
> > At this stage, tooling is only available for x86_64. It is based on
> > Linux 7.2-rc3. Bootpatch-SLR currently randomizes most of the
> > task_struct. A few fields are exempt from randomization because of
> > current implementation details (see v2 cover letter).
> >
> > Bootpatch-SLR requires a custom toolchain based on GCC 16.1.0 and GAS
> > 2.46.1. GCC is extended to provide access to COMPONENT_REFs that are
> > usually folded inside the parser.
>
> How does this impact codegen and optimizations in particular? I suppose
> keeping individual field offsets patchable results in missed optimization
> opportunities? E.g., GCC may combine adjacent struct member accesses.
>
> It would be good to get some numbers in terms of code size increase in
> general, as well as I-cache efficiency on some representative benchmarks.

The following numbers are all based on defconfig (+CONFIG_SPSLR) with
task_struct and cred randomized.

Enabling SPSLR grows the vmlinux binary from 54.9MB to 63.9MB. Of this
9MB increase, almost everything (rounded to one digit after the comma,
literally also 9MB) is the metadata emitted by pinpoint. Before you get a heart
attack now: 93% of that metadata is the string table containing the struct and
field names of the randomized targets, because that table is not deduplicated
between CUs. I should probably fix that :-) The remaining 7% will likely also
become smaller soon, because the metadata format currently uses exclusively
64 bit fields for everything and I want to get rid of all the CU-local stuff.

Regarding code size specifically: the combined size of .text and
.init.text grows
by about 0.2% when enabling SPSLR. For a total of 10487 instructions pins,
the code grows by 42798 bytes that are not attributed to one of the new source
files in kernel/spslr. That constitutes an average 4.08 bytes per instruction
patch site, which makes a lot of sense given that the vast majority of
patch sites
turn:

    48 89 ab 34 12 00 00   movq %rbp, 0x1234(%rbx)

with a total of 7 bytes into:

    48 c7 c0 34 12 00 00    mov $0x1234, %rax
    48 89 2c 03             movq %rbp, (%rbx,%rax)

with a total of 11 bytes. The remaining 0.08 bytes are then caused by chains
of multiple offsets that have to be added, or by the instrumentation of
displacements that used to fit into a single byte but get 4 byte patch-sites
anyways.

To measure the impact of this code size increase on l1i cache efficiency,
I ran lmbench's lat_proc fork in a minimal busybox userspace in qemu, all
pinned to the same CPU core, based on 3 different kernel states:

- baseline kernel without CONFIG_SPSLR set
- kernel with CONFIG_SPSLR set, but "nospslr" set on boot
- kernel with CONFIG_SPSLR set, and SPSLR actually applied on boot

The second one served mainly to avoid falsification of the results by the
different effects that CONFIG_RANDSTRUCT_PERFORMANCE aims to
avoid.

Across all 3 experiments, the run-to-run noise within each setup greatly
exceeded the discrepancy between the setups. That applies to lmbench's
raw latency results, as well as perf's statistics for cycles, instructions, l1i
cache misses, and the ratio between them. I came to the conclusion that
syscalls like fork simply do too much besides accessing some structs for
the dedicated patch sites to make a real difference. On the other hand, I
think that some kind of synthetic micro benchmark can not be reasonably
used to measure l1i cache efficiency, because its entire code just ends
up in the cache when it first runs and then remains there.

The really neat thing is: patch-sites do not need to stay in their own
instructions.
I designed the fieldlabel mechanism to allow something like:

    movq %rbp, fieldlabel(0x1234, .Lspslr_ipin_42, .Lspslr_ipin_width_42)(%rbx)

So there is nothing preventing the patch-sites from being inside the actual
memory operands. And if we are talking about access chains, the addition
of multiple offsets can be done at patch-time. The result can then also go
directly into the memory access operand. If everything goes according to
my plan, v4 will offer a significantly better foundation to build something like
this on.

> > The custom GAS provides the new
> > fieldlabel feature used to annotate encoded instruction patch sites.
> >
> > An example for the fieldlabel feature is:
> >
> >     movq $fieldlabel(8, .Lspslr_ipin_42, .Lspslr_ipin_width_42), %0
> >
> > This causes the custom GAS to put the .Lspslr_ipin_42 label directly
> > onto the bytes encoding the immediate value 8. It additionally defines
> > the .Lspslr_ipin_width_42 label to be the size of the immediate field.
> > The metadata emitted by the Pinpoint plugin directly references these
> > symbols to specify exact patch-site locations.
> >
>
> Does the fieldlabel feature have any other uses? Or is this only for
> SPSLR. Could these changes be extended to DWARF metadata generation too?

I am not aware of any debug info or other users that would benefit from knowing
where immediates or displacements are inside instructions.

> For example, it might be useful to be able to capture the SPSLR seed
> (assuming there is one), and feed it into a host tool that can generate
> a matching vmlinux.elf that you can load into the debugger.

At the moment, SPSLR does not have a seed. Though I suppose that would be
nice to have for reproducibility anyways. I will keep this in mind and implement
some way to extract a seed and perhaps a boot argument to set it, when I find
the time.

If the DWARF approach mentioned above turns out to not suffice, it
would probably
also be possible to use the seed to generate corresponding debug info.

I hope I was able to answer your questions to a satisfactory extent!

- Jasper

Reply via email to