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"