* [PATCH] klp-build: Reuse the target kernel randstruct seed
@ 2026-10-20 21:23 Song Liu
2026-10-20 22:45 ` sashiko-bot
0 siblings, 1 reply; 2+ messages in thread
From: Song Liu @ 2026-10-20 21:23 UTC (permalink / raw)
To: live-patching, linux-kselftest
Cc: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, shuah,
linux-kernel, Song Liu
CONFIG_RANDSTRUCT randomizes struct layouts based on a seed which kbuild
generates from /dev/urandom. A livepatch has to observe the same layouts
as the kernel it is loaded into, but klp-build runs 'make clean', after
which kbuild generates a new seed, and the resulting module can't be
loaded:
module livepatch_foo: .gnu.linkonce.this_module
section size must match the kernel's built struct module size at run time
struct module had grown 64 bytes of padding relative to the running
kernel (0x540 vs 0x500). Every other randomized struct the patch touches
would be read at the wrong offsets too, so this is not just a loader nit.
Reuse the seed of the kernel being patched, taken from --randstruct-seed
or from scripts/basic/randstruct.seed in the tree, and refuse to build
when CONFIG_RANDSTRUCT is on and no valid seed is available. A wrong
module is worse than no module.
Pin scripts/gen-randstruct-seed.sh rather than the generated seed file:
clean_kernel() removes scripts/basic/.randstruct.seed.cmd, so kbuild's
if_changed finds no saved command and regenerates the seed no matter what
the file already holds. The generator is stashed, so it is restored on
exit like scripts/setlocalversion.
Read the seed before set_kernelversion(), but pin the generator after it.
set_kernelversion() runs 'make listnewconfig' and 'make syncconfig', which
build scripts/basic as well, so after a 'make clean' they would replace
the seed before the default seed file is read. But if the generator is
already modified when 'make kernelrelease' runs, setlocalversion appends
"-dirty" to the kernel release and the module's vermagic no longer
matches the target kernel.
The Clang and GCC plugin implementations both derive their layouts only
from scripts/basic/randstruct.seed, so pinning the seed covers
CONFIG_GCC_PLUGIN_RANDSTRUCT too. Drop the check that rejected it. The
plugin requires a 64 character seed, which is what gen-randstruct-seed.sh
writes, so validate exactly that format.
An explicit option is needed rather than deriving the path from
uname -r, because the build host is usually not running the kernel being
patched.
Signed-off-by: Song Liu <song@kernel.org>
---
scripts/livepatch/klp-build | 61 ++++++++++++++++++++++++++++++++++---
1 file changed, 56 insertions(+), 5 deletions(-)
diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build
index b52a8489d9f6..fd336f685a84 100755
--- a/scripts/livepatch/klp-build
+++ b/scripts/livepatch/klp-build
@@ -19,7 +19,7 @@ set -o nounset
# This helps keep execution in pipes so pipefail+ERR trap can catch errors.
shopt -s lastpipe
-unset DEBUG_CLONE DIFF_CHECKSUM SKIP_CLEANUP VERBOSE XTRACE
+unset DEBUG_CLONE DIFF_CHECKSUM RANDSTRUCT_SEED RANDSTRUCT_SEED_FILE SKIP_CLEANUP VERBOSE XTRACE
REPLACE=1
SHORT_CIRCUIT=0
@@ -133,6 +133,8 @@ Options:
-j, --jobs=<jobs> Build jobs to run simultaneously [default: $JOBS]
-o, --output=<file.ko> Output file [default: livepatch-<patch-name>.ko]
--no-replace Disable livepatch atomic replace
+ --randstruct-seed=<file> Randstruct seed of the target kernel
+ [default: scripts/basic/randstruct.seed]
-v, --verbose Pass V=1 to kernel/module builds
Advanced Options:
@@ -160,7 +162,7 @@ process_args() {
local patch
short="hfj:o:vdS:T"
- long="help,show-first-changed,jobs:,output:,no-replace,verbose,debug,short-circuit:,keep-tmp"
+ long="help,show-first-changed,jobs:,output:,no-replace,randstruct-seed:,verbose,debug,short-circuit:,keep-tmp"
args=$(getopt --options "$short" --longoptions "$long" -- "$@") || {
echo; usage; exit
@@ -193,6 +195,11 @@ process_args() {
REPLACE=0
shift
;;
+ --randstruct-seed)
+ [[ -r "$2" ]] || die "can't read randstruct seed file '$2'"
+ RANDSTRUCT_SEED_FILE="$2"
+ shift 2
+ ;;
-v | --verbose)
VERBOSE=1
shift
@@ -268,9 +275,6 @@ validate_config() {
[[ -v CONFIG_GCC_PLUGIN_LATENT_ENTROPY ]] && \
die "kernel option 'CONFIG_GCC_PLUGIN_LATENT_ENTROPY' not supported"
- [[ -v CONFIG_GCC_PLUGIN_RANDSTRUCT ]] && \
- die "kernel option 'CONFIG_GCC_PLUGIN_RANDSTRUCT' not supported"
-
[[ -v CONFIG_LD_DEAD_CODE_DATA_ELIMINATION ]] && \
die "kernel option 'CONFIG_LD_DEAD_CODE_DATA_ELIMINATION' not supported"
@@ -328,6 +332,51 @@ set_kernelversion() {
sed -i "2i echo $kernelrelease; exit 0" scripts/setlocalversion
}
+# CONFIG_RANDSTRUCT randomizes struct layouts based on a seed which kbuild
+# generates from /dev/urandom. A livepatch has to observe the same layouts as
+# the kernel it gets loaded into, so reuse that kernel's seed. Otherwise the
+# module loader rejects the module ("...this_module section size must match
+# the kernel's built struct module size at run time"), and any randomized
+# struct the patch touches would be accessed at the wrong offsets.
+#
+# This must run before set_kernelversion(), whose 'make syncconfig' also
+# builds scripts/basic. After a 'make clean' that would replace the seed
+# before the default seed file gets read.
+read_randstruct_seed() {
+ local seed_file
+
+ [[ -v CONFIG_RANDSTRUCT ]] || return 0
+
+ if [[ -v RANDSTRUCT_SEED_FILE ]]; then
+ seed_file="$RANDSTRUCT_SEED_FILE"
+ else
+ seed_file="$PWD/scripts/basic/randstruct.seed"
+ [[ -r "$seed_file" ]] || \
+ die "CONFIG_RANDSTRUCT is enabled but there's no $seed_file; pass --randstruct-seed with the seed used to build the kernel"
+ fi
+
+ RANDSTRUCT_SEED="$(< "$seed_file")"
+ [[ "$RANDSTRUCT_SEED" =~ ^[0-9a-f]{64}$ ]] || \
+ die "malformed randstruct seed in $seed_file"
+}
+
+# Pin the generator rather than the seed file: clean_kernel() removes
+# scripts/basic/.randstruct.seed.cmd, so if_changed sees no saved command and
+# regenerates the seed no matter what the file already holds.
+#
+# This must run after set_kernelversion(), otherwise setlocalversion sees the
+# modified generator and appends "-dirty" to the kernel release.
+set_randstruct_seed() {
+ local gen="$PWD/scripts/gen-randstruct-seed.sh"
+
+ [[ -v RANDSTRUCT_SEED ]] || return 0
+
+ stash_file "$gen"
+ sed -i "s|^SEED=.*|SEED=$RANDSTRUCT_SEED|" "$gen"
+ command grep -q "^SEED=$RANDSTRUCT_SEED$" "$gen" || \
+ die "failed to pin randstruct seed"
+}
+
get_patch_input_files() {
local patch="$1"
@@ -464,7 +513,9 @@ do_init() {
validate_config
set_module_name
+ read_randstruct_seed
set_kernelversion
+ set_randstruct_seed
}
# Refresh the patch hunk headers, specifically the line numbers and counts.
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 2+ messages in thread* Re: [PATCH] klp-build: Reuse the target kernel randstruct seed
2026-10-20 21:23 [PATCH] klp-build: Reuse the target kernel randstruct seed Song Liu
@ 2026-10-20 22:45 ` sashiko-bot
0 siblings, 0 replies; 2+ messages in thread
From: sashiko-bot @ 2026-10-20 22:45 UTC (permalink / raw)
To: Song Liu, live-patching
Cc: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, shuah
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] klp-build: set_kernelversion() clobbers randstruct.seed before pinning
- [Medium] klp-build: set_randstruct_seed() mtime update forces full rebuild on -S 2
--
commit 606c1a8e7c05ba3363ccb4c4faa0e0f6111aaeda
Author: Song Liu <song@kernel.org>
klp-build: Reuse the target kernel randstruct seed
This commit reuses the target kernel's randstruct seed in klp-build by
reading scripts/basic/randstruct.seed (or a file passed via
--randstruct-seed) before set_kernelversion() and pinning
scripts/gen-randstruct-seed.sh afterwards so that livepatch modules are
built with matching randomized struct layouts.
> diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build
> index b52a8489d9f67..fd336f685a841 100755
> --- a/scripts/livepatch/klp-build
> +++ b/scripts/livepatch/klp-build
[ ... ]
> @@ -328,6 +332,51 @@ set_kernelversion() {
> sed -i "2i echo $kernelrelease; exit 0" scripts/setlocalversion
> }
>
> +# CONFIG_RANDSTRUCT randomizes struct layouts based on a seed which kbuild
> +# generates from /dev/urandom. A livepatch has to observe the same layouts as
> +# the kernel it gets loaded into, so reuse that kernel's seed. Otherwise the
> +# module loader rejects the module ("...this_module section size must match
> +# the kernel's built struct module size at run time"), and any randomized
> +# struct the patch touches would be accessed at the wrong offsets.
> +#
> +# This must run before set_kernelversion(), whose 'make syncconfig' also
> +# builds scripts/basic. After a 'make clean' that would replace the seed
> +# before the default seed file gets read.
> +read_randstruct_seed() {
> + local seed_file
> +
> + [[ -v CONFIG_RANDSTRUCT ]] || return 0
> +
> + if [[ -v RANDSTRUCT_SEED_FILE ]]; then
> + seed_file="$RANDSTRUCT_SEED_FILE"
> + else
> + seed_file="$PWD/scripts/basic/randstruct.seed"
> + [[ -r "$seed_file" ]] || \
> + die "CONFIG_RANDSTRUCT is enabled but there's no $seed_file; pass --randstruct-seed with the seed used to build the kernel"
> + fi
> +
> + RANDSTRUCT_SEED="$(< "$seed_file")"
> + [[ "$RANDSTRUCT_SEED" =~ ^[0-9a-f]{64}$ ]] || \
> + die "malformed randstruct seed in $seed_file"
> +}
> +
> +# Pin the generator rather than the seed file: clean_kernel() removes
> +# scripts/basic/.randstruct.seed.cmd, so if_changed sees no saved command and
> +# regenerates the seed no matter what the file already holds.
> +#
> +# This must run after set_kernelversion(), otherwise setlocalversion sees the
> +# modified generator and appends "-dirty" to the kernel release.
> +set_randstruct_seed() {
> + local gen="$PWD/scripts/gen-randstruct-seed.sh"
> +
> + [[ -v RANDSTRUCT_SEED ]] || return 0
> +
> + stash_file "$gen"
> + sed -i "s|^SEED=.*|SEED=$RANDSTRUCT_SEED|" "$gen"
> + command grep -q "^SEED=$RANDSTRUCT_SEED$" "$gen" || \
> + die "failed to pin randstruct seed"
> +}
[Severity: Medium]
When running klp-build with --short-circuit=2 (-S 2 / -S patched) after an
initial -T run has built 1-orig and restored scripts/gen-randstruct-seed.sh
on exit via cleanup() -> restore_files(), set_randstruct_seed() runs sed -i
on scripts/gen-randstruct-seed.sh and updates its mtime to be newer than
scripts/basic/randstruct.seed from the 1-orig build.
In scripts/basic/Makefile:
cmd_create_randstruct_seed = \
$(CONFIG_SHELL) $(gen-randstruct-seed) \
$@ $(objtree)/include/generated/randstruct_hash.h
$(obj)/randstruct.seed: $(gen-randstruct-seed) FORCE
$(call if_changed,create_randstruct_seed)
Because $(gen-randstruct-seed) has a newer mtime than $(obj)/randstruct.seed,
if_changed re-executes scripts/gen-randstruct-seed.sh and rewrites
include/generated/randstruct_hash.h (as well as triggering a rebuild of
randomize_layout_seed.h, randomize_layout_plugin.so, and
include/generated/gcc-plugins.h when CONFIG_GCC_PLUGIN_RANDSTRUCT is
enabled).
Since include/linux/compiler-version.h is force-included in every translation
unit:
#ifdef RANDSTRUCT
#include <generated/randstruct_hash.h>
#endif
wouldn't touching include/generated/randstruct_hash.h cause build_kernel
"patched" under -S 2 to recompile the entire kernel tree instead of only the
patched files?
> +
> get_patch_input_files() {
> local patch="$1"
>
> @@ -464,7 +513,9 @@ do_init() {
>
> validate_config
> set_module_name
> + read_randstruct_seed
> set_kernelversion
> + set_randstruct_seed
> }
[Severity: Medium]
In do_init(), read_randstruct_seed() reads the seed before
set_kernelversion(), while set_randstruct_seed() pins
scripts/gen-randstruct-seed.sh only after set_kernelversion() finishes:
set_kernelversion() {
...
if [[ -n "$(make -s listnewconfig 2>/dev/null)" ]]; then
die ".config mismatch, check your .config or run 'make olddefconfig'"
fi
make syncconfig &>/dev/null || die "make syncconfig failed"
Both "make -s listnewconfig" and "make syncconfig" match the top-level
Makefile rule:
%config: outputmakefile scripts_basic FORCE
$(Q)$(MAKE) $(build)=scripts/kconfig $@
When scripts/basic/.randstruct.seed.cmd is missing (for instance after "make
clean") or when scripts/gen-randstruct-seed.sh has a newer mtime than
scripts/basic/randstruct.seed (such as after a previous run where
stash_file() copied scripts/gen-randstruct-seed.sh with "cp -f" without
preserving timestamps and restore_files() moved it back on exit), building
scripts_basic in set_kernelversion() runs the unpinned
scripts/gen-randstruct-seed.sh. That overwrites scripts/basic/randstruct.seed
and include/generated/randstruct_hash.h with a new random seed from
/dev/urandom.
Because set_randstruct_seed() only edits scripts/gen-randstruct-seed.sh
without stashing or restoring scripts/basic/randstruct.seed and
include/generated/randstruct_hash.h, can this overwrite cause two issues?
First, if klp-build exits before build_kernel() runs (for example, when
listnewconfig or syncconfig fails in set_kernelversion(), or when
validate_patches() fails before build_kernel()), cleanup() restores
scripts/gen-randstruct-seed.sh while leaving scripts/basic/randstruct.seed on
disk overwritten with the random seed. Subsequent runs relying on the default
scripts/basic/randstruct.seed will then silently read that corrupted seed.
Second, when invoked with --short-circuit (-S 3, -S 4, or -S 5),
clean_kernel() and build_kernel() do not run, and build_patch_module() runs
an external module build ("make --directory=. M=$KMOD_DIR") which does not
build scripts_basic. Because the pinned scripts/gen-randstruct-seed.sh never
runs (and even under -S 2, if_changed can skip running it if
set_kernelversion() and set_randstruct_seed() execute within the same
filesystem timestamp tick), wouldn't the livepatch module be compiled using
the wrong seed left in scripts/basic/randstruct.seed and
include/generated/randstruct_hash.h?
>
> # Refresh the patch hunk headers, specifically the line numbers and counts.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261020212341.706367-1-song@kernel.org?part=1
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-10-20 22:45 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-20 21:23 [PATCH] klp-build: Reuse the target kernel randstruct seed Song Liu
2026-10-20 22:45 ` sashiko-bot
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.