From 82dcc72343079429fa771169c653fe3ea8e8bcad Mon Sep 17 00:00:00 2001 From: Dmitry Malkin Date: Mon, 28 Sep 2026 20:32:08 +0200 Subject: [PATCH] kpatch-build: allow setting patch module version metadata Add --module-version to let callers identify a generated patch module using standard MODULE_VERSION metadata. Write it to the temporary module glue, where it survives diff extraction, and verify the final module's version. Leave builds without the option unchanged. Validate the value before using it in a C string. Document the option and add CLI regression tests to make check without requiring a kernel build or root access. Install gawk in CI for kpatch-build's existing dependency. The version is a caller-supplied identifier, not livepatch activation state or a symbol version CRC. No source repository or CI integration is assumed. Signed-off-by: Dmitry Malkin --- .github/workflows/unit.yml | 2 +- Makefile | 3 ++- kpatch-build/kpatch-build | 21 +++++++++++++++++- man/kpatch-build.1 | 10 +++++++++ test/module-version-test.sh | 44 +++++++++++++++++++++++++++++++++++++ 5 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 test/module-version-test.sh diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 234515f15..85f9a07f5 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -13,7 +13,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: dependencies - run: sudo apt-get install -y libelf-dev linux-headers-$(uname -r) shellcheck elfutils + run: sudo apt-get install -y libelf-dev linux-headers-$(uname -r) shellcheck elfutils gawk - name: make run: make - name: submodule update diff --git a/Makefile b/Makefile index 37ab1e892..73cc37cda 100644 --- a/Makefile +++ b/Makefile @@ -71,7 +71,8 @@ check: shellcheck test/difftree.sh test/integration/kpatch-test \ test/integration/lib.sh test/integration/rebase-patches \ test/integration/test-vagrant \ - test/integration/vm-integration-run + test/integration/vm-integration-run test/module-version-test.sh + bash test/module-version-test.sh help: @echo "kpatch Makefile" diff --git a/kpatch-build/kpatch-build b/kpatch-build/kpatch-build index 67871a7ba..3285ffc1e 100755 --- a/kpatch-build/kpatch-build +++ b/kpatch-build/kpatch-build @@ -59,6 +59,7 @@ declare -a PATCH_LIST APPLIED_PATCHES=0 OOT_MODULE= KLP_REPLACE=1 +MODVERSION="" GCC="${CROSS_COMPILE:-}gcc" CLANG="${CROSS_COMPILE:-}clang" @@ -785,6 +786,7 @@ usage() { echo " -j, --jobs Specify the number of make jobs" >&2 echo " -t, --target Specify custom kernel build targets" >&2 echo " -n, --name Specify the name of the kpatch module" >&2 + echo " --module-version Set the patch module's version metadata" >&2 echo " -o, --output Specify output folder" >&2 echo " -d, --debug Enable 'xtrace' and keep scratch files" >&2 echo " in /tmp" >&2 @@ -803,7 +805,7 @@ if ! command -v gawk &> /dev/null; then die "gawk not installed" fi -options="$(getopt -o ha:r:s:c:v:j:t:n:o:dR -l "help,archversion:,sourcerpm:,sourcedir:,config:,vmlinux:,jobs:,target:,name:,output:,oot-module:,oot-module-src:,debug,skip-gcc-check,skip-compiler-check,skip-cleanup,non-replace,version" -- "$@")" || die "getopt failed" +options="$(getopt -o ha:r:s:c:v:j:t:n:o:dR -l "help,archversion:,sourcerpm:,sourcedir:,config:,vmlinux:,jobs:,target:,name:,module-version:,output:,oot-module:,oot-module-src:,debug,skip-gcc-check,skip-compiler-check,skip-cleanup,non-replace,version" -- "$@")" || die "getopt failed" eval set -- "$options" @@ -850,6 +852,13 @@ while [[ $# -gt 0 ]]; do MODNAME="$(module_name_string "$2")" shift ;; + --module-version) + # Restrict the value to a safe C string and a single modinfo line. + [[ "$2" =~ ^[a-zA-Z0-9][a-zA-Z0-9._+-]{0,63}$ ]] || + die "Invalid module version: expected 1-64 ASCII characters, starting with a letter or digit, followed by letters, digits, '.', '_', '+' or '-'" + MODVERSION="$2" + shift + ;; -o|--output) [[ ! -d "$2" ]] && die "output dir '$2' not found" BASE="$(readlink -f "$2")" @@ -1293,6 +1302,10 @@ remove_patches # interfere with cleanup later, so ensure the $TEMPDIR is read/write. cp -LR "$DATADIR/patch" "$TEMPDIR" || die chmod -R u+rw "$TEMPDIR" || die +if [[ -n "$MODVERSION" ]]; then + printf '\nMODULE_VERSION("%s");\n' "$MODVERSION" >> "$TEMPDIR/patch/patch-hook.c" || + die "Failed to add module version" +fi declare -a ARCH_MAKEVARS if [[ "$ARCH" = "ppc64le" ]]; then @@ -1585,6 +1598,12 @@ if [[ "$USE_KLP" -eq 1 ]]; then [[ "$rc" -ne 0 ]] && die "create-klp-module: exited with return code: $rc" fi +if [[ -n "$MODVERSION" ]]; then + actual_version=$(modinfo -F version "$TEMPDIR/patch/$MODNAME.ko") || + die "Failed to read module version" + [[ "$actual_version" = "$MODVERSION" ]] || die "Module version mismatch" +fi + if [[ -n "$CONFIG_MODVERSIONS" ]]; then # Check that final module does not reference symbols with different version # than the target kernel diff --git a/man/kpatch-build.1 b/man/kpatch-build.1 index f0e4d7d6b..e82c01088 100644 --- a/man/kpatch-build.1 +++ b/man/kpatch-build.1 @@ -40,6 +40,16 @@ effect. -n|--name Specify the name of the kpatch module +--module-version + Set MODULE_VERSION metadata in the generated patch module. + The value must contain 1-64 ASCII characters: an initial letter or digit, + followed by letters, digits, '.', '_', '+' or '-'. + Read it with 'modinfo -F version ' or, after loading, from + /sys/module//version. This is a caller-supplied identifier, + not an indication of livepatch activation or symbol version compatibility. + If omitted, no version metadata is added. Unlike --version, this option + does not print the version of the kpatch-build tool. + -o|--output Specify output folder diff --git a/test/module-version-test.sh b/test/module-version-test.sh new file mode 100644 index 000000000..8a5aeb4b4 --- /dev/null +++ b/test/module-version-test.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# Fast CLI regression tests: no kernel build, Docker, or root access required. +set -euo pipefail +export LC_ALL=C + +BUILD="$(dirname "$(readlink -f "$0")")/../kpatch-build/kpatch-build" +TMP=$(mktemp -d) +trap 'rm -rf "$TMP"' EXIT + +fail() { + echo "FAIL: $*" >&2 + exit 1 +} + +command -v gawk >/dev/null || fail "gawk is required by kpatch-build" + +# --help exits after option parsing, before source preparation or compilation. +max=$(printf '%064d' 0) +for version in "0" "1.2.3-rc1+test_2" "0123456789abcdef0123456789abcdef01234567" "$max"; do + "$BUILD" --module-version "$version" --help >"$TMP/out" 2>"$TMP/err" || + fail "valid version rejected: $version" +done + +for version in "" "${max}0" "-1" "a b" 'a"b' 'a\b' $'a\nb' $'a\rb' \ + "a/b" "a;exit" "\$(false)" $'\xc3\xa9'; do + if "$BUILD" --module-version "$version" --help >"$TMP/out" 2>"$TMP/err"; then + fail "invalid version accepted: $version" + fi + grep -q "Invalid module version" "$TMP/err" || + fail "unexpected failure for invalid version: $version" +done + +if "$BUILD" --module-version >"$TMP/out" 2>"$TMP/err"; then + fail "missing argument accepted" +fi +grep -q "requires an argument" "$TMP/err" || fail "missing-argument diagnostic not found" + +# The new option must not conflict with the existing tool-version option. +"$BUILD" --version >"$TMP/out" 2>"$TMP/err" || fail "--version failed" +grep -q "Version :" "$TMP/out" || fail "tool version missing" +"$BUILD" --help >"$TMP/out" 2>"$TMP/err" || fail "--help failed" +grep -q -- "--module-version" "$TMP/err" || fail "option missing from help" + +echo "module-version CLI tests: PASS"