[RFC PATCH v2 5/7] livepatch/klp-build: add basic out-of-tree module support
From: Joe Lawrence <joe.lawrence@redhat.com>
Date: 2026-08-26 19:50:19
Subsystem:
live patching, the rest · Maintainers:
Josh Poimboeuf, Jiri Kosina, Miroslav Benes, Petr Mladek, Linus Torvalds
klp-build is currently limited to patching in-tree kernel modules. Introduce a --oot-dir option to enable livepatch generation for basic out-of-tree (OOT) modules. This requires the associated kernel tree to be fully built (Module.symvers must exist). The OOT workflow is as follows: cd /path/to/built-kernel ./scripts/livepatch/klp-build --oot-dir /path/to/mymodule my-fix.patch With this option, klp-build performs two builds (original and patched) of the OOT module via `make M=...` instead of a full kernel rebuild. The resulting objects are then processed and diffed to produce the final livepatch .ko. Note that --oot-dir drives the build via `make M=`, assuming a simple Kbuild/Makefile layout. This is suited for small test modules and bug reproducers rather than full OOT drivers with complex build systems (which should use --orig-dir/--patched-dir instead). Signed-off-by: Joe Lawrence <joe.lawrence@redhat.com> --- scripts/livepatch/klp-build | 84 ++++++++++++++++++++++++++++--------- 1 file changed, 64 insertions(+), 20 deletions(-)
diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build
index b52a8489d9f6..b60f5a5da31e 100755
--- a/scripts/livepatch/klp-build
+++ b/scripts/livepatch/klp-build@@ -21,6 +21,7 @@ shopt -s lastpipe unset DEBUG_CLONE DIFF_CHECKSUM SKIP_CLEANUP VERBOSE XTRACE +OOT_DIR="" REPLACE=1 SHORT_CIRCUIT=0 JOBS="$(getconf _NPROCESSORS_ONLN)"
@@ -137,6 +138,7 @@ Options: Advanced Options: -d, --debug Show symbol/reloc cloning decisions + --oot-dir=<DIR> Out-of-tree module source directory -S, --short-circuit=STEP Start at build step (requires prior --keep-tmp) 1|orig Build original kernel (default) 2|patched Build patched kernel
@@ -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:,oot-dir:,output:,no-replace,verbose,debug,short-circuit:,keep-tmp" args=$(getopt --options "$short" --longoptions "$long" -- "$@") || { echo; usage; exit
@@ -202,6 +204,10 @@ process_args() { keep_tmp=1 shift ;; + --oot-dir) + OOT_DIR="$2" + shift 2 + ;; -S | --short-circuit) [[ ! -d "$TMP_DIR" ]] && die "--short-circuit requires preserved klp-tmp dir" keep_tmp=1
@@ -364,11 +370,21 @@ check_unsupported_patches() { get_patch_files "$patch" | mapfile -t files for file in "${files[@]}"; do + # In and out-of-tree paths to reject case "$file" in - lib/*|*/vdso/*|*/realmode/rm/*|*.S) + *.S) die "${patch}: unsupported patch to $file" ;; esac + + # In-tree paths to reject + if [[ -z "$OOT_DIR" ]]; then + case "$file" in + lib/*|*/vdso/*|*/realmode/rm/*) + die "${patch}: unsupported patch to $file" + ;; + esac + fi done done }
@@ -377,13 +393,14 @@ apply_patch() { local patch="$1" shift local extra_args=("$@") + local patch_target="${OOT_DIR:-$PWD}" local drift_regex="with fuzz|offset [0-9]+ line" local output local status [[ ! -f "$patch" ]] && die "$patch doesn't exist" status=0 - output=$(patch -p1 --dry-run --no-backup-if-mismatch -r /dev/null "${extra_args[@]}" < "$patch" 2>&1) || status=$? + output=$(patch -d "$patch_target" -p1 --dry-run --no-backup-if-mismatch -r /dev/null "${extra_args[@]}" < "$patch" 2>&1) || status=$? if [[ "$status" -ne 0 ]]; then echo "$output" >&2 die "$patch did not apply"
@@ -398,9 +415,10 @@ apply_patch() { revert_patch() { local patch="$1" + local patch_target="${OOT_DIR:-$PWD}" local tmp=() - patch -p1 -R --force --no-backup-if-mismatch -r /dev/null &> /dev/null < "$patch" || true + patch -d "$patch_target" -p1 -R --force --no-backup-if-mismatch -r /dev/null &> /dev/null < "$patch" || true for p in "${APPLIED_PATCHES[@]}"; do [[ "$p" == "$patch" ]] && continue
@@ -436,10 +454,17 @@ validate_patches() { } do_init() { - # We're not yet smart enough to handle anything other than in-tree - # builds in pwd. [[ ! "$PWD" -ef "$SCRIPT_DIR/../.." ]] && die "please run from the kernel root directory" + if [[ -n "$OOT_DIR" ]]; then + [[ -d "$OOT_DIR" ]] || die "module directory not found: $OOT_DIR" + OOT_DIR="$(realpath "$OOT_DIR")" + [[ -f "$OOT_DIR/Kbuild" || -f "$OOT_DIR/Makefile" ]] || + die "no Kbuild or Makefile in $OOT_DIR" + [[ -f "$PWD/Module.symvers" ]] || + die "kernel must be built first (no Module.symvers in $PWD)" + fi + if (( SHORT_CIRCUIT >= 2 )); then [[ -f "$ORIG_DIR/.complete" ]] || die "-S $SHORT_CIRCUIT requires completed $ORIG_DIR" fi
@@ -471,6 +496,7 @@ do_init() { refresh_patch() { local patch="$1" local tmpdir="$PATCH_TMP_DIR" + local patch_target="${OOT_DIR:-$PWD}" local input_files=() local output_files=()
@@ -483,11 +509,11 @@ refresh_patch() { get_patch_output_files "$patch" | mapfile -t output_files # Copy orig source files to 'a' - echo "${input_files[@]}" | xargs cp --parents --target-directory="$tmpdir/a" + ( cd "$patch_target" && echo "${input_files[@]}" | xargs cp --parents --target-directory="$tmpdir/a" ) # Copy patched source files to 'b' apply_patch "$patch" "--silent" - echo "${output_files[@]}" | xargs cp --parents --target-directory="$tmpdir/b" + ( cd "$patch_target" && echo "${output_files[@]}" | xargs cp --parents --target-directory="$tmpdir/b" ) revert_patch "$patch" # Diff 'a' and 'b' to make a clean patch
@@ -529,6 +555,7 @@ clean_kernel() { cmd=("make") cmd+=("--silent") cmd+=("-j$JOBS") + [[ -n "$OOT_DIR" ]] && cmd+=("M=$OOT_DIR") cmd+=("clean") "${cmd[@]}"
@@ -567,7 +594,11 @@ build_kernel() { fi cmd+=("-j$JOBS") cmd+=("KCFLAGS=-ffunction-sections -fdata-sections") - cmd+=("vmlinux") + if [[ -z "$OOT_DIR" ]]; then + cmd+=("vmlinux") + else + cmd+=("M=$OOT_DIR") + fi cmd+=("modules") "${cmd[@]}" \
@@ -579,13 +610,20 @@ build_kernel() { find_objects() { local opts=("$@") - # Find root-level vmlinux.o and non-root-level .ko files, - # excluding klp-tmp/ and hidden directories. - find "$PWD" -mindepth 1 \ - \( -path "$TMP_DIR" -o -name ".*" -o -regex "$PWD/[^/][^/]*\.ko" \) -prune -o \ - -type f "${opts[@]}" \ - \( -name "*.ko" -o -path "$PWD/vmlinux.o" \) \ - -printf '%P\n' + if [[ -z "$OOT_DIR" ]]; then + # In-tree: find root-level vmlinux.o and non-root-level .ko files, + # excluding klp-tmp/ and hidden directories. + find "$PWD" -mindepth 1 \ + \( -path "$TMP_DIR" -o -name ".*" -o -regex "$PWD/[^/][^/]*\.ko" \) -prune -o \ + -type f "${opts[@]}" \ + \( -name "*.ko" -o -path "$PWD/vmlinux.o" \) \ + -printf '%P\n' + else + # OOT: find .ko at any depth under the module dir + find "$OOT_DIR" -path "$OOT_DIR/.git" -prune -o \ + -type f "${opts[@]}" \ + -name "*.ko" -printf '%P\n' + fi } # Copy all .o archives to $ORIG_DIR
@@ -597,10 +635,11 @@ copy_orig_objects() { find_objects | mapfile -t files + local obj_root="${OOT_DIR:-$PWD}" xtrace_save "copying original objects" for _file in "${files[@]}"; do local rel_file="${_file/.ko/.o}" - local file="$PWD/$rel_file" + local file="$obj_root/$rel_file" local orig_file="$ORIG_DIR/$rel_file" local orig_dir="$(dirname "$orig_file")"
@@ -633,10 +672,11 @@ copy_patched_objects() { find_objects "${opts[@]}" | mapfile -t files + local obj_root="${OOT_DIR:-$PWD}" xtrace_save "copying changed objects" for _file in "${files[@]}"; do local rel_file="${_file/.ko/.o}" - local file="$PWD/$rel_file" + local file="$obj_root/$rel_file" local orig_file="$ORIG_DIR/$rel_file" local patched_file="$PATCHED_DIR/$rel_file" local patched_dir="$(dirname "$patched_file")"
@@ -717,6 +757,7 @@ diff_objects() { cmd+=("klp") cmd+=("diff") (( ${#opts[@]} > 0 )) && cmd+=("${opts[@]}") + [[ -n "$OOT_DIR" ]] && cmd+=("--symvers" "$PWD/Module.symvers") cmd+=("$orig_file") cmd+=("$patched_file") cmd+=("$out_file")
@@ -895,13 +936,16 @@ build_patch_module() { process_args "$@" do_init +BUILD_TARGET="kernel" +[[ -n "$OOT_DIR" ]] && BUILD_TARGET="module ${OOT_DIR##*/}" + if (( SHORT_CIRCUIT <= 2 )); then status "Validating patch(es)" validate_patches fi if (( SHORT_CIRCUIT <= 1 )); then - status "Building original kernel" + status "Building original $BUILD_TARGET" clean_kernel build_kernel "original" status "Copying original object files"
@@ -912,7 +956,7 @@ if (( SHORT_CIRCUIT <= 2 )); then status "Fixing patch(es)" fix_patches apply_patches "--silent" - status "Building patched kernel" + status "Building patched $BUILD_TARGET" build_kernel "patched" revert_patches status "Copying patched object files"
--
2.55.0