All of lore.kernel.org
 help / color / mirror / Atom feed
* [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

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.