Add a klp-build script which makes use of a new "objtool klp" subcommand to generate livepatch modules using a source patch as input. The concept is similar to kpatch-build which has been a successful out-of-tree project for over a decade. It takes a source .patch as an input, builds kernels before and after, does a binary diff, and copies any changed functions into a new object file which is then linked into a livepatch module. By making use of existing objtool functionalities, and taking from lessons learned over the last decade of maintaining kpatch-build, the overall design is much simpler. In fact, it's a complete redesign and has been written from scratch (no copied code). Advantages over kpatch-build: - Runs on vmlinux.o, so it's compatible with late-linked features like IBT and LTO - Much simpler design: ~3k fewer LOC - Makes use of existing objtool CFG functionality to create checksums to trivially detect changed functions - Offset __LINE__ changes are no longer a problem thanks to the adjust-patch-lines script - In-tree means less cruft, easier maintenance, and a larger pool of potential maintainers To use, run the following from the kernel source root: scripts/livepatch/klp-build /path/to/my.patch If it succeeds, the patch module (livepatch.ko) will be created in the current directory. TODO: - specify module name on cmdline - handle edge cases like correlation of static locals - support other arches (currently x86-64 only) - support clang - performance optimization - automated testing Signed-off-by: Josh Poimboeuf <jpoimboe@xxxxxxxxxx> --- .gitignore | 3 + include/linux/livepatch.h | 25 +- include/linux/livepatch_ext.h | 83 ++ include/linux/livepatch_patch.h | 73 ++ kernel/livepatch/core.c | 4 +- scripts/livepatch/adjust-patch-lines | 181 ++++ scripts/livepatch/klp-build | 355 ++++++++ scripts/livepatch/module.c | 120 +++ scripts/module.lds.S | 9 +- tools/include/linux/livepatch_ext.h | 83 ++ tools/objtool/Build | 4 +- tools/objtool/Makefile | 33 +- tools/objtool/arch/x86/decode.c | 40 + tools/objtool/check.c | 29 +- tools/objtool/elf.c | 18 +- tools/objtool/include/objtool/arch.h | 1 + tools/objtool/include/objtool/builtin.h | 1 + tools/objtool/include/objtool/elf.h | 20 +- tools/objtool/include/objtool/klp.h | 25 + tools/objtool/include/objtool/objtool.h | 2 +- tools/objtool/klp-diff.c | 1112 +++++++++++++++++++++++ tools/objtool/klp-link.c | 122 +++ tools/objtool/klp.c | 57 ++ tools/objtool/objtool.c | 6 + tools/objtool/sync-check.sh | 1 + tools/objtool/weak.c | 7 + 26 files changed, 2361 insertions(+), 53 deletions(-) create mode 100644 include/linux/livepatch_ext.h create mode 100644 include/linux/livepatch_patch.h create mode 100755 scripts/livepatch/adjust-patch-lines create mode 100755 scripts/livepatch/klp-build create mode 100644 scripts/livepatch/module.c create mode 100644 tools/include/linux/livepatch_ext.h create mode 100644 tools/objtool/include/objtool/klp.h create mode 100644 tools/objtool/klp-diff.c create mode 100644 tools/objtool/klp-link.c create mode 100644 tools/objtool/klp.c diff --git a/.gitignore b/.gitignore index c59dc60ba62e..28bb70c9e808 100644 --- a/.gitignore +++ b/.gitignore @@ -171,3 +171,6 @@ sphinx_*/ # Rust analyzer configuration /rust-project.json + +# Livepatch module build directory +/klp-tmp diff --git a/include/linux/livepatch.h b/include/linux/livepatch.h index 51a258c24ff5..d54e4dfe320e 100644 --- a/include/linux/livepatch.h +++ b/include/linux/livepatch.h @@ -13,6 +13,7 @@ #include <linux/ftrace.h> #include <linux/completion.h> #include <linux/list.h> +#include <linux/livepatch_ext.h> #include <linux/livepatch_sched.h> #if IS_ENABLED(CONFIG_LIVEPATCH) @@ -77,30 +78,6 @@ struct klp_func { bool transition; }; -struct klp_object; - -/** - * struct klp_callbacks - pre/post live-(un)patch callback structure - * @pre_patch: executed before code patching - * @post_patch: executed after code patching - * @pre_unpatch: executed before code unpatching - * @post_unpatch: executed after code unpatching - * @post_unpatch_enabled: flag indicating if post-unpatch callback - * should run - * - * All callbacks are optional. Only the pre-patch callback, if provided, - * will be unconditionally executed. If the parent klp_object fails to - * patch for any reason, including a non-zero error status returned from - * the pre-patch callback, no further callbacks will be executed. - */ -struct klp_callbacks { - int (*pre_patch)(struct klp_object *obj); - void (*post_patch)(struct klp_object *obj); - void (*pre_unpatch)(struct klp_object *obj); - void (*post_unpatch)(struct klp_object *obj); - bool post_unpatch_enabled; -}; - /** * struct klp_object - kernel object structure for live patching * @name: module name (or NULL for vmlinux) diff --git a/include/linux/livepatch_ext.h b/include/linux/livepatch_ext.h new file mode 100644 index 000000000000..4b71e72952d5 --- /dev/null +++ b/include/linux/livepatch_ext.h @@ -0,0 +1,83 @@ +/* SPDX License-Identifier: GPL-2.0-or-later */ +/* + * External livepatch interfaces for patch creation tooling + * + * Copyright (C) 2024 Josh Poimboeuf <jpoimboe@xxxxxxxxxx> + */ + +#ifndef _LINUX_LIVEPATCH_EXT_H_ +#define _LINUX_LIVEPATCH_EXT_H_ + +#include <linux/types.h> + +#define KLP_RELOC_SEC_PREFIX ".klp.rela." +#define KLP_SYM_PREFIX ".klp.sym." + +#define KLP_CALLBACKS_SEC ".discard.klp_callbacks" + +#define __KLP_PRE_PATCH_PREFIX __klp_pre_patch_callback_ +#define __KLP_POST_PATCH_PREFIX __klp_post_patch_callback_ +#define __KLP_PRE_UNPATCH_PREFIX __klp_pre_unpatch_callback_ +#define __KLP_POST_UNPATCH_PREFIX __klp_post_unpatch_callback_ + +#define KLP_PRE_PATCH_PREFIX __stringify(__KLP_PRE_PATCH_PREFIX) +#define KLP_POST_PATCH_PREFIX __stringify(__KLP_POST_PATCH_PREFIX) +#define KLP_PRE_UNPATCH_PREFIX __stringify(__KLP_PRE_UNPATCH_PREFIX) +#define KLP_POST_UNPATCH_PREFIX __stringify(__KLP_POST_UNPATCH_PREFIX) + +struct klp_object; + +typedef int (*klp_pre_patch_t)(struct klp_object *obj); +typedef void (*klp_post_patch_t)(struct klp_object *obj); +typedef void (*klp_pre_unpatch_t)(struct klp_object *obj); +typedef void (*klp_post_unpatch_t)(struct klp_object *obj); + +/** + * struct klp_callbacks - pre/post live-(un)patch callback structure + * @pre_patch: executed before code patching + * @post_patch: executed after code patching + * @pre_unpatch: executed before code unpatching + * @post_unpatch: executed after code unpatching + * @post_unpatch_enabled: flag indicating if post-unpatch callback + * should run + * + * All callbacks are optional. Only the pre-patch callback, if provided, + * will be unconditionally executed. If the parent klp_object fails to + * patch for any reason, including a non-zero error status returned from + * the pre-patch callback, no further callbacks will be executed. + */ +struct klp_callbacks { + klp_pre_patch_t pre_patch; + klp_post_patch_t post_patch; + klp_pre_unpatch_t pre_unpatch; + klp_post_unpatch_t post_unpatch; + bool post_unpatch_enabled; +}; + +/* + * 'struct klp_{func,object}_ext' are compact "external" representations of + * 'struct klp_{func,object}'. They are used by objtool for livepatch + * generation. The structs are then read by the livepatch module and converted + * to the real structs before calling klp_enable_patch(). + * + * TODO make these the official API for klp_enable_patch(). That should + * simplify livepatch's interface as well as its data structure lifetime + * management. + * + * TODO possibly use struct_group_tagged() to declare these within the original + * structs. + */ +struct klp_func_ext { + const char *old_name; + void *new_func; + unsigned long sympos; +}; + +struct klp_object_ext { + const char *name; + struct klp_func_ext *funcs; + struct klp_callbacks callbacks; + unsigned int nr_funcs; +}; + +#endif /* _LINUX_LIVEPATCH_EXT_H_ */ diff --git a/include/linux/livepatch_patch.h b/include/linux/livepatch_patch.h new file mode 100644 index 000000000000..6f3b930cdc5a --- /dev/null +++ b/include/linux/livepatch_patch.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Helper macros for livepatch source patches + * + * Copyright (C) 2024 Josh Poimboeuf <jpoimboe@xxxxxxxxxx> + */ +#ifndef _LINUX_LIVEPATCH_PATCH_H_ +#define _LINUX_LIVEPATCH_PATCH_H_ + +#include <linux/livepatch.h> + +#ifdef MODULE +#define KLP_OBJNAME __KBUILD_MODNAME +#else +#define KLP_OBJNAME vmlinux +#endif + +#define KLP_PRE_PATCH_CALLBACK(func) \ + klp_pre_patch_t __used __section(KLP_CALLBACKS_SEC) \ + __PASTE(__KLP_PRE_PATCH_PREFIX, KLP_OBJNAME) = func + +#define KLP_POST_PATCH_CALLBACK(func) \ + klp_post_patch_t __used __section(KLP_CALLBACKS_SEC) \ + __PASTE(__KLP_POST_PATCH_PREFIX, KLP_OBJNAME) = func + +#define KLP_PRE_UNPATCH_CALLBACK(func) \ + klp_pre_unpatch_t __used __section(KLP_CALLBACKS_SEC) \ + __PASTE(__KLP_PRE_UNPATCH_PREFIX, KLP_OBJNAME) = func + +#define KLP_POST_UNPATCH_CALLBACK(func) \ + klp_post_unpatch_t __used __section(KLP_CALLBACKS_SEC) \ + __PASTE(__KLP_POST_UNPATCH_PREFIX, KLP_OBJNAME) = func + +#define KLP_SYSCALL_METADATA(sname) \ + static struct syscall_metadata __used \ + __section("__syscalls_metadata") \ + *__p_syscall_meta_##sname = NULL; \ + \ + static struct trace_event_call __used \ + __section("_ftrace_events") \ + *__event_enter_##sname = NULL + +#define KLP_SYSCALL_DEFINE1(name, ...) KLP_SYSCALL_DEFINEx(1, _##name, __VA_ARGS__) +#define KLP_SYSCALL_DEFINE2(name, ...) KLP_SYSCALL_DEFINEx(2, _##name, __VA_ARGS__) +#define KLP_SYSCALL_DEFINE3(name, ...) KLP_SYSCALL_DEFINEx(3, _##name, __VA_ARGS__) +#define KLP_SYSCALL_DEFINE4(name, ...) KLP_SYSCALL_DEFINEx(4, _##name, __VA_ARGS__) +#define KLP_SYSCALL_DEFINE5(name, ...) KLP_SYSCALL_DEFINEx(5, _##name, __VA_ARGS__) +#define KLP_SYSCALL_DEFINE6(name, ...) KLP_SYSCALL_DEFINEx(6, _##name, __VA_ARGS__) + +#define KLP_SYSCALL_DEFINEx(x, sname, ...) \ + KLP_SYSCALL_METADATA(sname); \ + __KLP_SYSCALL_DEFINEx(x, sname, __VA_ARGS__) + +#ifdef CONFIG_X86_64 + +// TODO move this to arch/x86/include/asm/syscall_wrapper.h and share code +#define __KLP_SYSCALL_DEFINEx(x, name, ...) \ + static long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)); \ + static inline long __klp_do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__));\ + __X64_SYS_STUBx(x, name, __VA_ARGS__) \ + __IA32_SYS_STUBx(x, name, __VA_ARGS__) \ + static long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)) \ + { \ + long ret = __klp_do_sys##name(__MAP(x,__SC_CAST,__VA_ARGS__));\ + __MAP(x,__SC_TEST,__VA_ARGS__); \ + __PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__)); \ + return ret; \ + } \ + static inline long __klp_do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)) + +#endif + +#endif /* _LINUX_LIVEPATCH_PATCH_H_ */ diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c index 76ffe29934d4..8ff917973254 100644 --- a/kernel/livepatch/core.c +++ b/kernel/livepatch/core.c @@ -226,7 +226,7 @@ static int klp_resolve_symbols(Elf_Shdr *sechdrs, const char *strtab, /* Format: .klp.sym.sym_objname.sym_name,sympos */ cnt = sscanf(strtab + sym->st_name, - ".klp.sym.%55[^.].%511[^,],%lu", + KLP_SYM_PREFIX "%55[^.].%511[^,],%lu", sym_objname, sym_name, &sympos); if (cnt != 3) { pr_err("symbol %s has an incorrectly formatted name\n", @@ -305,7 +305,7 @@ static int klp_write_section_relocs(struct module *pmod, Elf_Shdr *sechdrs, * See comment in klp_resolve_symbols() for an explanation * of the selected field width value. */ - cnt = sscanf(shstrtab + sec->sh_name, ".klp.rela.%55[^.]", + cnt = sscanf(shstrtab + sec->sh_name, KLP_RELOC_SEC_PREFIX "%55[^.]", sec_objname); if (cnt != 1) { pr_err("section %s has an incorrectly formatted name\n", diff --git a/scripts/livepatch/adjust-patch-lines b/scripts/livepatch/adjust-patch-lines new file mode 100755 index 000000000000..b29592a57ed3 --- /dev/null +++ b/scripts/livepatch/adjust-patch-lines @@ -0,0 +1,181 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (C) 2024 Josh Poimboeuf <jpoimboe@xxxxxxxxxx> +# +# Add #line statements to a patch so it doesn't affect __LINE__ usage + +SCRIPT="$(basename "$0")" + +set -o errexit +set -o errtrace +set -o pipefail + +warn() { + str="$1" + if [ -n "$str" ]; then + echo -e "$SCRIPT: error: $*" >&2 + else + echo -e "$SCRIPT: error" >&2 + + fi +} + +die() { + warn "$@" + if [ -n "$TMPFILE" ]; then + rm -f "$TMPFILE" + fi + exit 1 +} + +do_trap() { + die +} + +trap do_trap ERR + +__usage() { + echo "Usage: $SCRIPT [options] patch_file [output_file]" + echo "Add #line statements to a patch so it doesn't affect __LINE__ usage" + echo "for the rest of the file. This prevents false positive changes in the binary." + echo + echo "Options:" + echo " -h, --help Show this help message and exit" + echo + echo "Arguments:" + echo " patch_file Source .patch file" + echo " output_file Optional output file. If not provided 'patch_file' will be" + echo " edited in place." +} + +usage() { + __usage >&2 +} + +args=$(getopt -o "h" -l "help" -- "$@") +eval set -- "$args" + +while true; do + case "$1" in + -h | --help) + usage + exit 0 + ;; + --) + shift + break + ;; + *) + usage + exit 1 + ;; + esac +done + +if [ $# != 1 ] && [ $# != 2 ]; then + usage + die "unexpected # of args $#" +fi + +patch="$1" + +if [ ! -e "$patch" ]; then + die "missing file: $patch" +fi + +if [ $# = 2 ]; then + output="$2" +else + output="$patch" +fi + +TMPFILE="$(mktemp)" + +skip_file=false +in_hunk=false +needs_update=false +while IFS= read -r line; do + if [[ "$line" =~ ^---\ ]]; then + filename="${line#--- */}" + + if [[ ! "$filename" =~ \.[ch]$ ]]; then + skip_file=true + echo "$line" >> "$TMPFILE" + continue + fi + + case "$filename" in + *vmlinux.lds.h) + skip_file=true + echo "$line" >> "$TMPFILE" + continue + ;; + esac + + skip_file=false + in_hunk=false + needs_update=false + echo "$line" >> "$TMPFILE" + continue + fi + + if $skip_file; then + echo "$line" >> "$TMPFILE" + continue + fi + + if [[ "$line" =~ ^@@ ]]; then + + in_hunk=true + needs_update=false + + cur="${line#*-}" + cur="${cur%%,*}" + ((cur--)) + + last="${line#*,}" + last="${last%% *}" + last=$((cur + last)) + + echo "$line" >> "$TMPFILE" + continue + fi + + if $in_hunk; then + if [[ "$line" =~ ^\+ ]]; then + needs_update=true + echo "$line" >> "$TMPFILE" + continue + fi + + if [[ "$line" =~ ^- ]]; then + ((cur++)) + needs_update=true + echo "$line" >> "$TMPFILE" + continue + fi + + if $needs_update; then + ((cur++)) + needs_update=false + echo "+#line $cur" >> "$TMPFILE" + echo "$line" >> "$TMPFILE" + continue + fi + + ((cur++)) + echo "$line" >> "$TMPFILE" + + if [ $cur = $last ]; then + in_hunk=false + fi + + continue + fi + + echo "$line" >> "$TMPFILE" + +done < "$patch" + +mv -f "$TMPFILE" "$output" diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build new file mode 100755 index 000000000000..e16584a4b697 --- /dev/null +++ b/scripts/livepatch/klp-build @@ -0,0 +1,355 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (C) 2024 Josh Poimboeuf <jpoimboe@xxxxxxxxxx> + +set -o errexit +set -o errtrace +set -o pipefail + +SCRIPT="$(basename "$0")" +SCRIPTDIR="$(readlink -f "$(dirname "$(type -p "$0")")")" + +SRC="$(pwd)" +BUILD_DIR="$SRC" +TMP_DIR="$BUILD_DIR/klp-tmp" +ORIG_DIR="$TMP_DIR/orig" +PATCHED_DIR="$TMP_DIR/patched" +OUTPUT_DIR="$TMP_DIR/out" +TMP_TMP_DIR="$TMP_DIR/tmp" + +shopt -o xtrace | grep -q 'on' && XTRACE=1 + +status() { + echo -e "- $SCRIPT: $*" +} + +warn() { + str="$1" + if [[ -n "$str" ]]; then + echo "$SCRIPT: error: $*" >&2 + else + echo "$SCRIPT: error" >&2 + fi +} + +die() { + warn "$@" + revert_applied_patches "--recount" + [[ -z "$DEBUG" ]] && rm -rf "$TMP_DIR" + exit 1 +} + +__trap() { + die "line $1" +} + +trap '__trap ${LINENO}' ERR INT + +__usage() { + echo "Usage: $SCRIPT [options] patch_file(s)" + echo "Generate a livepatch module" + echo + echo "Options:" + echo " -h, --help Show this help message and exit" + echo + echo "Arguments:" + echo " patch_file(s) One or more .patch files" +} + +usage() { + __usage >&2 +} + +apply_patch() { + local patch="$1" + local extra_args="$2" + + [[ -f "$patch" ]] || die "$patch doesn't exist" + + ( cd "$SRC" && git apply --quiet --check $extra_args "$patch" ) || die "$patch failed to apply" + ( cd "$SRC" && git apply --quiet $extra_args "$patch" ) || die "$patch failed to apply" + APPLIED_PATCHES+=("$patch") +} + +revert_patch() { + local patch="$1" + local extra_args="$2" + + ( cd "$SRC" && git apply --reverse --quiet $extra_args "$patch" ) || die "$patch failed to apply" + APPLIED_PATCHES=("${APPLIED_PATCHES[@]/$patch}") +} + +revert_applied_patches() { + local patches=("${APPLIED_PATCHES[@]}") + local extra_args="$1" + + for (( i=${#patches[@]}-1 ; i>=0 ; i-- )) ; do + local patch="${patches[$i]}" + + # "deleted" entry can still exist as an empty string + [[ "$patch" ]] || continue + + revert_patch "${patches[$i]}" "$extra_args" + done + + APPLIED_PATCHES=() +} + +apply_patches() { + for patch in "${PATCHES[@]}"; do + apply_patch "$patch" + done +} + +validate_patches() { + apply_patches + revert_applied_patches +} + +refresh_patch() { + local patch="$1" + local tmp="$TMP_TMP_DIR" + + rm -rf "$tmp" + mkdir -p "$tmp" + + while read -r file; do + local dest + dest="$tmp/a/$(dirname "$file")" + mkdir -p "$dest" + cp -f "$SRC/$file" "$dest" + done < <(grep -E '^(--- |\+\+\+ )' "$patch" | sed -E 's/(--- a\/|\+\+\+ b\/)//' | sort | uniq) + + apply_patch "$patch" --recount + + while read -r file; do + local dest + dest="$tmp/b/$(dirname "$file")" + mkdir -p "$dest" + cp -f "$SRC/$file" "$dest" + done < <(grep -E '^(--- |\+\+\+ )' "$patch" | sed -E 's/(--- a\/|\+\+\+ b\/)//' | sort | uniq) + + revert_patch "$patch" --recount + + ( + cd "$tmp" + git diff --no-index --no-prefix a b > "$patch" || true + ) +} + +# Copy the patches to a temporary directory, fix their lines so as not to +# affect the __LINE__ macro for otherwise unchanged functions, and update +# $PATCHES to point to fixed patches. +copy_and_fix_patches() { + + idx=0001 + for patch in "${PATCHES[@]}"; do + cp -f "$patch" "$TMP_DIR/$idx-$(basename "$patch")" + idx=$(printf "%04d" $((10#$idx + 1))) + done + + PATCHES=() + idx=0001 + while true; do + patch="$(ls "$TMP_DIR"/"$idx"-*.patch 2> /dev/null || true)" + [[ -z "$patch" ]] && break + + refresh_patch "$patch" + "$SCRIPTDIR/adjust-patch-lines" "$patch" || die "adjust-patch-lines failed" + refresh_patch "$patch" + apply_patch "$patch" + + idx=$(printf "%04d" $((10#$idx + 1))) + done + + revert_applied_patches + + idx=0001 + while true; do + patch="$(ls "$TMP_DIR"/"$idx"-*.patch 2> /dev/null || true)" + [[ -z "$patch" ]] && break + + PATCHES+=("$patch") + + idx=$(printf "%04d" $((10#$idx + 1))) + done +} + +build_kernel() { + local options + + if [[ -n "$VERBOSE" ]]; then + options="V=1" + else + options="-s" + fi + + ( cd "$SRC" && make -j"$(nproc)" "$options" vmlinux modules ) || die "kernel build failed" +} + +copy_orig_objs() { + if [[ "$XTRACE" = 1 ]]; then + set +x + fi + + while read -r _file; do + local file="${_file/.ko/.o}" + local rel_file="${file#"$BUILD_DIR"/}" + local dest_dir="$ORIG_DIR/$(dirname "$rel_file")" + + # ignore any livepatch modules in pwd + if [[ "$_file" = *.ko ]] && [[ "$(dirname "$file")" -ef "$BUILD_DIR" ]]; then + continue + fi + + [[ -f "$file" ]] || die "can't find $file" + + mkdir -p "$dest_dir" + cp -f "$file" "$dest_dir" || die "cp -f $file $dest_dir failed" + done < <(find "$BUILD_DIR" -type f \( -name vmlinux.o -o -name "*.ko" \)) + + if [[ -n "$XTRACE" ]]; then + set -x + fi +} + +diff_objects() { + local timestamp="$1" + + while read -r _file; do + local file="${_file/.ko/.o}" + local rel_file="${file#"$BUILD_DIR"/}" + local orig="$ORIG_DIR/$rel_file" + local patched="$PATCHED_DIR/$rel_file" + local output="$OUTPUT_DIR/$rel_file" + + [[ -f "$file" ]] || die "can't find $file" + + mkdir -p "$(dirname "$patched")" + cp -f "$file" "$patched" + + mkdir -p "$(dirname "$output")" + + # status "diff: $rel_file" + + "$SRC/tools/objtool/objtool" klp diff "$orig" "$patched" "$output" || die "objtool klp diff failed" + done < <(find "$BUILD_DIR" -type f \( -name vmlinux.o -o -name "*.ko" \) -newer "$timestamp") + + local nr_objs="$(find "$OUTPUT_DIR" -type f \( -name vmlinux.o -o -name "*.ko" \) | wc -l)" + + if [[ "$nr_objs" = 0 ]]; then + die "no changes detected" + fi +} + +build_patch_module() { + local makefile="$OUTPUT_DIR/Kbuild" + local verbose + local replace + + cp -f "$SRC/scripts/livepatch/module.c" "$OUTPUT_DIR" + + echo "obj-m := $NAME.o" > "$makefile" + echo -n "$NAME-y := module.o" >> "$makefile" + + while read -r file; do + cp -f "$file" "$file"_shipped + echo -n " ${file#"$OUTPUT_DIR"/}" >> "$makefile" + done < <(find "$OUTPUT_DIR" -type f -name "*.o" ) + echo >> "$makefile" + + if [[ -n "$VERBOSE" ]]; then + verbose="V=1" + else + verbose="-s" + fi + + [[ $REPLACE == 1 ]] && replace="KCFLAGS=-DKLP_REPLACE" + + make -C . M="$OUTPUT_DIR" "$verbose" $replace || die "module build failed" + + cp -f "$OUTPUT_DIR/$NAME.ko" "$OUTPUT_DIR/$NAME.ko.prelink" + + "$SRC/tools/objtool/objtool" klp link "$OUTPUT_DIR/$NAME.ko" || die "objtool klp link failed" + + cp -f "$OUTPUT_DIR/$NAME.ko" . +} + +args=$(getopt -o dhn:v -l "debug,help,name:,verbose,noreplace" -- "$@") +eval set -- "$args" + +NAME="livepatch" +REPLACE=1 + +while true; do + case "$1" in + -d | --debug) + DEBUG=1 + ;; + -h | --help) + usage + exit 0 + ;; + -n | --name) + NAME="$2" + shift + ;; + --noreplace) + REPLACE=0 + ;; + -v | --verbose) + VERBOSE=1 + ;; + --) + shift + break + ;; + *) + usage + exit 1 + ;; + esac + shift +done + +if [[ $# -eq 0 ]]; then + usage + exit 1 +fi + +# not yet smart enough to handle anything other than in-tree builds from pwd +[[ "$PWD" -ef "$SCRIPTDIR/../.." ]] || die "please run from the kernel root directory" + +[[ -x "$SCRIPTDIR/adjust-patch-lines" ]] || die "can't find adjust-patch-lines script" + +validate_patches + +rm -rf "$TMP_DIR" +mkdir -p "$TMP_DIR" +APPLIED_PATCHES=() +PATCHES=("$@") + +# this updates ${PATCHES} to point to the modified versions +copy_and_fix_patches + +status "building original kernel" +build_kernel +copy_orig_objs + +touch "$TMP_DIR/timestamp" + +status "building patched kernel" +apply_patches +export KBUILD_MODPOST_WARN=1 +build_kernel +revert_applied_patches + +status "diffing objects" +diff_objects "$TMP_DIR/timestamp" + +status "building patch module" +build_patch_module + +status "success" +[[ -z "$DEBUG" ]] && rm -rf "$TMP_DIR" diff --git a/scripts/livepatch/module.c b/scripts/livepatch/module.c new file mode 100644 index 000000000000..101cabf6b2f1 --- /dev/null +++ b/scripts/livepatch/module.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Base module code for a livepatch kernel module + * + * Copyright (C) 2024 Josh Poimboeuf <jpoimboe@xxxxxxxxxx> + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/livepatch.h> + +// TODO livepatch could recognize these sections directly +// TODO use function checksums instead of sympos + +extern char __start_klp_objects, __stop_klp_objects; + +/* + * Create weak versions of the linker-created symbols to prevent modpost from + * warning about unresolved symbols. + */ +__weak char __start_klp_objects = 0; +__weak char __stop_klp_objects = 0; +struct klp_object_ext *__start_objs = (struct klp_object_ext *)&__start_klp_objects; +struct klp_object_ext *__stop_objs = (struct klp_object_ext *)&__stop_klp_objects; + +static struct klp_patch *patch; + +static int __init livepatch_mod_init(void) +{ + struct klp_object *objs; + unsigned int nr_objs; + int ret; + + nr_objs = __stop_objs - __start_objs; + + if (!__start_klp_objects || !nr_objs) { + pr_err("nothing to patch!\n"); + ret = -EINVAL; + goto err; + } + + patch = kzalloc(sizeof(*patch), GFP_KERNEL); + if (!patch) { + ret = -ENOMEM; + goto err; + } + + objs = kzalloc(sizeof(struct klp_object) * (nr_objs + 1), GFP_KERNEL); + if (!objs) { + ret = -ENOMEM; + goto err_free_patch; + } + + for (int i = 0; i < nr_objs; i++) { + struct klp_object_ext *obj_ext = __start_objs + i; + struct klp_func_ext *funcs_ext = obj_ext->funcs; + unsigned int nr_funcs = obj_ext->nr_funcs; + struct klp_func *funcs = objs[i].funcs; + struct klp_object *obj = objs + i; + + funcs = kzalloc(sizeof(struct klp_func) * (obj_ext->nr_funcs + 1), GFP_KERNEL); + if (!funcs) { + ret = -ENOMEM; + for (int j = 0; j < i; j++) + kfree(objs[i].funcs); + goto err_free_objs; + } + + for (int j = 0; j < nr_funcs; j++) { + funcs[j].old_name = funcs_ext[j].old_name; + funcs[j].new_func = funcs_ext[j].new_func; + funcs[j].old_sympos = funcs_ext[j].sympos; + } + + obj->name = obj_ext->name; + obj->funcs = funcs; + + memcpy(&obj->callbacks, &obj_ext->callbacks, sizeof(struct klp_callbacks)); + } + + patch->mod = THIS_MODULE; + patch->objs = objs; + + /* TODO patch->states */ + +#ifdef KLP_REPLACE + patch->replace = true; +#else + patch->replace = false; +#endif + + return klp_enable_patch(patch); + +err_free_objs: + kfree(objs); +err_free_patch: + kfree(patch); +err: + return ret; +} + +static void __exit livepatch_mod_exit(void) +{ + unsigned int nr_objs; + + nr_objs = __stop_objs - __start_objs; + + for (int i = 0; i < nr_objs; i++) + kfree(patch->objs[i].funcs); + + kfree(patch->objs); + kfree(patch); +} + +module_init(livepatch_mod_init); +module_exit(livepatch_mod_exit); +MODULE_LICENSE("GPL"); +MODULE_INFO(livepatch, "Y"); diff --git a/scripts/module.lds.S b/scripts/module.lds.S index 5cbae820bca0..aec4b9f0ec95 100644 --- a/scripts/module.lds.S +++ b/scripts/module.lds.S @@ -32,8 +32,15 @@ SECTIONS { __patchable_function_entries : { *(__patchable_function_entries) } + __klp_objects 0: ALIGN(8) { + __start_klp_objects = .; + KEEP(*(__klp_objects)) + __stop_klp_objects = .; + } + __klp_funcs 0: ALIGN(8) { KEEP(*(__klp_funcs)) } + #ifdef CONFIG_ARCH_USES_CFI_TRAPS - __kcfi_traps : { KEEP(*(.kcfi_traps)) } + __kcfi_traps : { KEEP(*(.kcfi_traps)) } #endif #if defined(CONFIG_LTO_CLANG) || defined(CONFIG_LIVEPATCH) diff --git a/tools/include/linux/livepatch_ext.h b/tools/include/linux/livepatch_ext.h new file mode 100644 index 000000000000..4b71e72952d5 --- /dev/null +++ b/tools/include/linux/livepatch_ext.h @@ -0,0 +1,83 @@ +/* SPDX License-Identifier: GPL-2.0-or-later */ +/* + * External livepatch interfaces for patch creation tooling + * + * Copyright (C) 2024 Josh Poimboeuf <jpoimboe@xxxxxxxxxx> + */ + +#ifndef _LINUX_LIVEPATCH_EXT_H_ +#define _LINUX_LIVEPATCH_EXT_H_ + +#include <linux/types.h> + +#define KLP_RELOC_SEC_PREFIX ".klp.rela." +#define KLP_SYM_PREFIX ".klp.sym." + +#define KLP_CALLBACKS_SEC ".discard.klp_callbacks" + +#define __KLP_PRE_PATCH_PREFIX __klp_pre_patch_callback_ +#define __KLP_POST_PATCH_PREFIX __klp_post_patch_callback_ +#define __KLP_PRE_UNPATCH_PREFIX __klp_pre_unpatch_callback_ +#define __KLP_POST_UNPATCH_PREFIX __klp_post_unpatch_callback_ + +#define KLP_PRE_PATCH_PREFIX __stringify(__KLP_PRE_PATCH_PREFIX) +#define KLP_POST_PATCH_PREFIX __stringify(__KLP_POST_PATCH_PREFIX) +#define KLP_PRE_UNPATCH_PREFIX __stringify(__KLP_PRE_UNPATCH_PREFIX) +#define KLP_POST_UNPATCH_PREFIX __stringify(__KLP_POST_UNPATCH_PREFIX) + +struct klp_object; + +typedef int (*klp_pre_patch_t)(struct klp_object *obj); +typedef void (*klp_post_patch_t)(struct klp_object *obj); +typedef void (*klp_pre_unpatch_t)(struct klp_object *obj); +typedef void (*klp_post_unpatch_t)(struct klp_object *obj); + +/** + * struct klp_callbacks - pre/post live-(un)patch callback structure + * @pre_patch: executed before code patching + * @post_patch: executed after code patching + * @pre_unpatch: executed before code unpatching + * @post_unpatch: executed after code unpatching + * @post_unpatch_enabled: flag indicating if post-unpatch callback + * should run + * + * All callbacks are optional. Only the pre-patch callback, if provided, + * will be unconditionally executed. If the parent klp_object fails to + * patch for any reason, including a non-zero error status returned from + * the pre-patch callback, no further callbacks will be executed. + */ +struct klp_callbacks { + klp_pre_patch_t pre_patch; + klp_post_patch_t post_patch; + klp_pre_unpatch_t pre_unpatch; + klp_post_unpatch_t post_unpatch; + bool post_unpatch_enabled; +}; + +/* + * 'struct klp_{func,object}_ext' are compact "external" representations of + * 'struct klp_{func,object}'. They are used by objtool for livepatch + * generation. The structs are then read by the livepatch module and converted + * to the real structs before calling klp_enable_patch(). + * + * TODO make these the official API for klp_enable_patch(). That should + * simplify livepatch's interface as well as its data structure lifetime + * management. + * + * TODO possibly use struct_group_tagged() to declare these within the original + * structs. + */ +struct klp_func_ext { + const char *old_name; + void *new_func; + unsigned long sympos; +}; + +struct klp_object_ext { + const char *name; + struct klp_func_ext *funcs; + struct klp_callbacks callbacks; + unsigned int nr_funcs; +}; + +#endif /* _LINUX_LIVEPATCH_EXT_H_ */ diff --git a/tools/objtool/Build b/tools/objtool/Build index a3cdf8af6635..f917e70f0fd0 100644 --- a/tools/objtool/Build +++ b/tools/objtool/Build @@ -8,8 +8,8 @@ objtool-y += builtin-check.o objtool-y += elf.o objtool-y += objtool.o -objtool-$(BUILD_ORC) += orc_gen.o -objtool-$(BUILD_ORC) += orc_dump.o +objtool-$(BUILD_ORC) += orc_gen.o orc_dump.o +objtool-$(BUILD_KLP) += klp.o klp-diff.o klp-link.o objtool-y += libstring.o objtool-y += libctype.o diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile index 6833804ca419..cdae220abca0 100644 --- a/tools/objtool/Makefile +++ b/tools/objtool/Makefile @@ -2,6 +2,25 @@ include ../scripts/Makefile.include include ../scripts/Makefile.arch +BUILD_ORC := n +BUILD_KLP := n + +ifeq ($(SRCARCH),x86) +BUILD_ORC := y +BUILD_KLP := y +endif + +ifeq ($(SRCARCH),loongarch) +BUILD_ORC := y +endif + +export BUILD_ORC BUILD_KLP + +ifeq ($(BUILD_KLP),y) +LIBXXHASH_FLAGS := $(shell $(HOSTPKG_CONFIG) libxxhash --cflags 2>/dev/null) +LIBXXHASH_LIBS := $(shell $(HOSTPKG_CONFIG) libxxhash --libs 2>/dev/null || echo -lxxhash) +endif + ifeq ($(srctree),) srctree := $(patsubst %/,%,$(dir $(CURDIR))) srctree := $(patsubst %/,%,$(dir $(srctree))) @@ -21,9 +40,6 @@ OBJTOOL_IN := $(OBJTOOL)-in.o LIBELF_FLAGS := $(shell $(HOSTPKG_CONFIG) libelf --cflags 2>/dev/null) LIBELF_LIBS := $(shell $(HOSTPKG_CONFIG) libelf --libs 2>/dev/null || echo -lelf) -LIBXXHASH_FLAGS := $(shell $(HOSTPKG_CONFIG) libxxhash --cflags 2>/dev/null) -LIBXXHASH_LIBS := $(shell $(HOSTPKG_CONFIG) libxxhash --libs 2>/dev/null || echo -lxxhash) - all: $(OBJTOOL) INCLUDES := -I$(srctree)/tools/include \ @@ -54,17 +70,6 @@ else Q = @ endif -BUILD_ORC := n - -ifeq ($(SRCARCH),x86) - BUILD_ORC := y -endif - -ifeq ($(SRCARCH),loongarch) - BUILD_ORC := y -endif - -export BUILD_ORC export srctree OUTPUT CFLAGS SRCARCH AWK include $(srctree)/tools/build/Makefile.include diff --git a/tools/objtool/arch/x86/decode.c b/tools/objtool/arch/x86/decode.c index 5468fd15f380..bb24e963f371 100644 --- a/tools/objtool/arch/x86/decode.c +++ b/tools/objtool/arch/x86/decode.c @@ -94,6 +94,46 @@ s64 arch_insn_adjusted_addend(struct instruction *insn, struct reloc *reloc) return phys_to_virt(addend); } +static void scan_for_insn(struct section *sec, unsigned long offset, + unsigned long *insn_off, unsigned int *insn_len) +{ + unsigned long o = 0; + struct insn insn; + + while (1) { + + insn_decode(&insn, sec->data->d_buf + o, sec_size(sec) - o, + INSN_MODE_64); + + if (o + insn.length > offset) { + *insn_off = o; + *insn_len = insn.length; + return; + } + + o += insn.length; + } +} + +u64 arch_adjusted_addend(struct reloc *reloc) +{ + unsigned int type = reloc_type(reloc); + s64 addend = reloc_addend(reloc); + unsigned long insn_off; + unsigned int insn_len; + + if (type == R_X86_64_PLT32) + return addend + 4; + + if (type != R_X86_64_PC32 || !is_text_section(reloc->sec->base)) + return addend; + + scan_for_insn(reloc->sec->base, reloc_offset(reloc), + &insn_off, &insn_len); + + return addend + insn_off + insn_len - reloc_offset(reloc); +} + unsigned long arch_jump_destination(struct instruction *insn) { return insn->offset + insn->len + insn->immediate; diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 0e9e485cd3b6..f55dec2932de 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -647,6 +647,20 @@ static void create_fake_symbol(struct objtool_file *file, const char *name_pfx, elf_create_symbol(file->elf, name, sec, STB_LOCAL, STT_OBJECT, offset, size); } +static bool is_livepatch_module(struct objtool_file *file) +{ + struct section *sec; + + if (!opts.module) + return false; + + sec = find_section_by_name(file->elf, ".modinfo"); + if (!sec) + return false; + + return memmem(sec->data->d_buf, sec_size(sec), "livepatch=Y", 12); +} + static void create_static_call_sections(struct objtool_file *file) { struct static_call_site *site; @@ -659,7 +673,14 @@ static void create_static_call_sections(struct objtool_file *file) sec = find_section_by_name(file->elf, ".static_call_sites"); if (sec) { INIT_LIST_HEAD(&file->static_call_list); - WARN("file already has .static_call_sites section, skipping"); + + /* + * Livepatch modules may have already extracted the static call + * site entries. + */ + if (!file->klp) + WARN("file already has .static_call_sites section, skipping"); + return; } @@ -696,7 +717,7 @@ static void create_static_call_sections(struct objtool_file *file) key_sym = find_symbol_by_name(file->elf, tmp); if (!key_sym) { - if (!opts.module) + if (!opts.module || file->klp) ERROR("static_call: can't find static_call_key symbol: %s", tmp); /* @@ -2406,6 +2427,8 @@ static void mark_rodata(struct objtool_file *file) static void decode_sections(struct objtool_file *file) { + file->klp = is_livepatch_module(file); + mark_rodata(file); init_pv_ops(file); @@ -4006,7 +4029,7 @@ static void add_prefix_symbol(struct objtool_file *file, struct symbol *func) continue; sym_pfx = elf_create_prefix_symbol(file->elf, func, opts.prefix); - if (!sym_pfx) + if (!sym_pfx && !file->klp) ERROR("duplicate prefix symbol for %s\n", func->name); break; diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c index 022873bf7064..7960921996bd 100644 --- a/tools/objtool/elf.c +++ b/tools/objtool/elf.c @@ -21,6 +21,7 @@ #include <linux/interval_tree_generic.h> #include <objtool/builtin.h> #include <objtool/elf.h> +#include <objtool/klp.h> #include <objtool/warn.h> #define ALIGN_UP(x, align_to) (((x) + ((align_to)-1)) & ~((align_to)-1)) @@ -456,6 +457,8 @@ static void elf_add_symbol(struct elf *elf, struct symbol *sym) else entry = &sym->sec->symbol_list; list_add(&sym->list, entry); + + list_add_tail(&sym->global_list, &elf->symbols); elf_hash_add(symbol, &sym->hash, sym->idx); elf_hash_add(symbol_name, &sym->name_hash, str_hash(sym->name)); @@ -505,6 +508,8 @@ static void read_symbols(struct elf *elf) elf->symbol_data = calloc(symbols_nr, sizeof(*sym)); ERROR_ON(!elf->symbol_data, "calloc"); + INIT_LIST_HEAD(&elf->symbols); + for (i = 0; i < symbols_nr; i++) { sym = &elf->symbol_data[i]; @@ -720,7 +725,7 @@ static void elf_update_symbol(struct elf *elf, struct section *symtab, static struct symbol *__elf_create_symbol(struct elf *elf, const char *name, struct section *sec, unsigned int bind, unsigned int type, unsigned long offset, - size_t size) + size_t size, bool klp) { struct section *symtab, *symtab_shndx; Elf32_Word first_non_local, new_idx; @@ -735,6 +740,9 @@ static struct symbol *__elf_create_symbol(struct elf *elf, const char *name, sym->sym.st_name = elf_add_string(elf, NULL, sym->name); } + if (klp) + sym->sym.st_shndx = SHN_LIVEPATCH; + sym->sec = sec ? : find_section_by_index(elf, 0); sym->sym.st_info = GELF_ST_INFO(bind, type); @@ -799,7 +807,7 @@ struct symbol *elf_create_symbol(struct elf *elf, const char *name, unsigned int type, unsigned long offset, size_t size) { - return __elf_create_symbol(elf, name, sec, bind, type, offset, size); + return __elf_create_symbol(elf, name, sec, bind, type, offset, size, false); } struct symbol *elf_create_section_symbol(struct elf *elf, struct section *sec) @@ -812,6 +820,12 @@ struct symbol *elf_create_section_symbol(struct elf *elf, struct section *sec) return sym; } +struct symbol *elf_create_klp_symbol(struct elf *elf, const char *name, + unsigned int bind, unsigned int type) +{ + return __elf_create_symbol(elf, name, NULL, bind, type, 0, 0, true); +} + struct symbol * elf_create_prefix_symbol(struct elf *elf, struct symbol *orig, size_t size) { diff --git a/tools/objtool/include/objtool/arch.h b/tools/objtool/include/objtool/arch.h index 14911fdfdc8f..d9f019ef89a7 100644 --- a/tools/objtool/include/objtool/arch.h +++ b/tools/objtool/include/objtool/arch.h @@ -82,6 +82,7 @@ bool arch_callee_saved_reg(unsigned char reg); unsigned long arch_jump_destination(struct instruction *insn); s64 arch_insn_adjusted_addend(struct instruction *insn, struct reloc *reloc); +u64 arch_adjusted_addend(struct reloc *reloc); const char *arch_nop_insn(int len); const char *arch_ret_insn(int len); diff --git a/tools/objtool/include/objtool/builtin.h b/tools/objtool/include/objtool/builtin.h index eab376169c1e..26bbf04afb24 100644 --- a/tools/objtool/include/objtool/builtin.h +++ b/tools/objtool/include/objtool/builtin.h @@ -46,5 +46,6 @@ extern struct opts opts; extern int cmd_parse_options(int argc, const char **argv, const char * const usage[]); extern int objtool_run(int argc, const char **argv); +extern int cmd_klp(int argc, const char **argv); #endif /* _BUILTIN_H */ diff --git a/tools/objtool/include/objtool/elf.h b/tools/objtool/include/objtool/elf.h index 1f14f33d279e..43839b3ac80f 100644 --- a/tools/objtool/include/objtool/elf.h +++ b/tools/objtool/include/objtool/elf.h @@ -16,6 +16,7 @@ #include <xxhash.h> #include <arch/elf.h> +#define SEC_NAME_LEN 512 #define SYM_NAME_LEN 512 #ifdef LIBELF_USE_DEPRECATED @@ -53,10 +54,12 @@ struct section { int idx; bool _changed, text, rodata, noinstr, init, truncate; struct reloc *relocs; + struct section *twin; }; struct symbol { struct list_head list; + struct list_head global_list; struct rb_node node; struct elf_hash_node hash; struct elf_hash_node name_hash; @@ -77,8 +80,11 @@ struct symbol { u8 warned : 1; u8 embedded_insn : 1; u8 local_label : 1; + u8 changed : 1; + u8 added : 1; struct list_head pv_target; struct reloc *relocs; + struct symbol *twin, *clone; XXH3_state_t *checksum_state; XXH64_hash_t checksum; @@ -99,6 +105,7 @@ struct elf { const char *name, *tmp_name; unsigned int num_files; struct list_head sections; + struct list_head symbols; unsigned long num_relocs; int symbol_bits; @@ -138,6 +145,8 @@ struct symbol *elf_create_symbol(struct elf *elf, const char *name, struct symbol *elf_create_section_symbol(struct elf *elf, struct section *sec); struct symbol *elf_create_prefix_symbol(struct elf *elf, struct symbol *orig, size_t size); +struct symbol *elf_create_klp_symbol(struct elf *elf, const char *name, + unsigned int bind, unsigned int type); struct reloc *elf_create_reloc(struct elf *elf, struct section *sec, unsigned long offset, struct symbol *sym, @@ -412,11 +421,14 @@ static inline void set_reloc_type(struct elf *elf, struct reloc *reloc, unsigned #define sec_for_each_sym_continue_reverse(sec, sym) \ list_for_each_entry_continue_reverse(sym, &sec->symbol_list, list) +#define sec_prev_sym(sec, sym) \ + sym->list.prev == &sec->symbol_list ? NULL : list_prev_entry(sym, list) + #define for_each_sym(elf, sym) \ - for (struct section *__sec, *__fake = (struct section *)1; \ - __fake; __fake = NULL) \ - for_each_sec(elf, __sec) \ - sec_for_each_sym(__sec, sym) + list_for_each_entry(sym, &elf->symbols, global_list) + +#define for_each_sym_continue(elf, sym) \ + list_for_each_entry_continue(sym, &elf->symbols, global_list) #define for_each_reloc(rsec, reloc) \ for (int __i = 0, __fake = 1; __fake; __fake = 0) \ diff --git a/tools/objtool/include/objtool/klp.h b/tools/objtool/include/objtool/klp.h new file mode 100644 index 000000000000..0df1dd273e1e --- /dev/null +++ b/tools/objtool/include/objtool/klp.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2024 Josh Poimboeuf <jpoimboe@xxxxxxxxxx> + */ +#ifndef _OBJTOOL_KLP_H +#define _OBJTOOL_KLP_H + +#define SHF_RELA_LIVEPATCH 0x00100000 +#define SHN_LIVEPATCH 0xff20 + +#define KLP_RELOCS_SEC ".klp.relocs" +#define KLP_OBJECTS_SEC "__klp_objects" +#define KLP_FUNCS_SEC "__klp_funcs" +#define KLP_STRINGS_SEC ".rodata.klp.str1.1" + +struct klp_reloc { + void *offset; + void *sym; + u32 type; +}; + +int cmd_klp_diff(int argc, const char **argv); +int cmd_klp_link(int argc, const char **argv); + +#endif /* _OBJTOOL_KLP_H */ diff --git a/tools/objtool/include/objtool/objtool.h b/tools/objtool/include/objtool/objtool.h index 3280abcce55e..f562b08bfbff 100644 --- a/tools/objtool/include/objtool/objtool.h +++ b/tools/objtool/include/objtool/objtool.h @@ -30,7 +30,7 @@ struct objtool_file { struct list_head mcount_loc_list; struct list_head endbr_list; struct list_head call_list; - bool ignore_unreachables, hints, rodata; + bool ignore_unreachables, hints, rodata, klp; unsigned int nr_endbr; unsigned int nr_endbr_int; diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c new file mode 100644 index 000000000000..76296e38f9ff --- /dev/null +++ b/tools/objtool/klp-diff.c @@ -0,0 +1,1112 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2024 Josh Poimboeuf <jpoimboe@xxxxxxxxxx> + */ +#include <libgen.h> +#include <stdio.h> +#include <objtool/objtool.h> +#include <objtool/warn.h> +#include <objtool/arch.h> +#include <objtool/klp.h> +#include <linux/livepatch_ext.h> +#include <linux/stringify.h> +#include <linux/string.h> +#include <linux/jhash.h> + +#define ALIGN_DOWN(x, align_to) ((x) & ~((align_to)-1)) +#define sizeof_field(TYPE, MEMBER) sizeof((((TYPE *)0)->MEMBER)) + +struct elfs { + struct elf *orig, *patched, *out; +}; + +struct export { + struct hlist_node hash; + char *mod, *sym; +}; + +static DEFINE_HASHTABLE(exports, 15); + +static inline u32 str_hash(const char *str) +{ + return jhash(str, strlen(str), 0); +} + +static void read_exports(void) +{ + const char *symvers = "Module.symvers"; + char line[1024]; + FILE *file; + + file = fopen(symvers, "r"); + ERROR_ON(!file, "can't open '%s', \"objtool diff\" should be run from the kernel tree", symvers); + + while (fgets(line, 1024, file)) { + char *sym, *mod, *exp; + struct export *export; + + sym = strchr(line, '\t'); + ERROR_ON(!sym, "malformed Module.symvers"); + + *sym++ = '\0'; + + mod = strchr(sym, '\t'); + ERROR_ON(!mod, "malformed Module.symvers"); + + *mod++ = '\0'; + + exp = strchr(mod, '\t'); + ERROR_ON(!exp, "malformed Module.symvers"); + + *exp++ = '\0'; + + ERROR_ON(*sym == '\0' || *mod == '\0', "malformed Module.symvers"); + + export = calloc(1, sizeof(*export)); + ERROR_ON(!export, "calloc"); + + export->mod = strdup(mod); + export->sym = strdup(sym); + hash_add(exports, &export->hash, str_hash(sym)); + } +} + +static int read_sym_checksums(struct elf *elf) +{ + struct section *sec; + + sec = find_section_by_name(elf, SYM_CHECKSUM_SEC); + if (!sec) + return 0; + + if (!sec->rsec) + ERROR("missing reloc section for " SYM_CHECKSUM_SEC); + + if (sec_size(sec) % sizeof(struct sym_checksum)) + ERROR("struct sym_checksum size mismatch"); + + for (int i = 0; i < sec_size(sec) / sizeof(struct sym_checksum); i++) { + struct sym_checksum *sym_checksum; + struct reloc *reloc; + struct symbol *sym; + + sym_checksum = (struct sym_checksum *)sec->data->d_buf + i; + + reloc = find_reloc_by_dest(elf, sec, i * sizeof(*sym_checksum)); + if (!reloc) + ERROR("can't find reloc for sym_checksum[%d]", i); + + sym = reloc->sym; + + if (is_section_symbol(sym)) + ERROR("not sure how to handle section %s", sym->name); + + if (is_function_symbol(sym)) + sym->checksum = sym_checksum->checksum; + } + + return 0; +} +static struct symbol *first_file_symbol(struct elf *elf) +{ + struct symbol *sym; + + for_each_sym(elf, sym) + if (is_file_symbol(sym)) + return sym; + + return NULL; +} + +static struct symbol *next_file_symbol(struct elf *elf, struct symbol *sym) +{ + for_each_sym_continue(elf, sym) + if (is_file_symbol(sym)) + return sym; + + return NULL; +} + +/* + * Certain static local variables should never be correlated. They will be + * used in place rather than referencing the originals. + */ +static bool is_uncorrelated_static_local(struct symbol *sym) +{ + static const char * const vars[] = { + "__key.", + "__warned.", + "__already_done.", + "__func__.", + "_rs.", + "CSWTCH.", + }; + + if (!is_object_symbol(sym) || !is_local_symbol(sym)) + return false; + + if (!strcmp(sym->sec->name, ".data.once")) + return true; + + for (int i = 0; i < ARRAY_SIZE(vars); i++) { + if (strstarts(sym->name, vars[i])) + return true; + } + + return false; +} + +static bool is_special_section(struct section *sec) +{ + static const char * const specials[] = { + ".altinstructions", + ".smp_locks", + "__bug_table", + "__ex_table", + "__jump_table", + "__mcount_loc", + /* + * .static_call_sites is generated by "objtool --static-call" + * which will run again later at livepatch module link time. + * So one might be forgiven for thinking it doesn't need to be + * extracted here. + * + * However, when run on modules, objtool blocks access to + * unexported static call keys. + * + * So extract it here to inherit the non-module preferential + * treatment. The later static call processing will be skipped + * when it sees this section already exists. + */ + ".static_call_sites", + }; + + static const char * const non_special_discards[] = { + ".discard.addressable", + SYM_CHECKSUM_SEC, + }; + + for (int i = 0; i < ARRAY_SIZE(specials); i++) + if (!strcmp(sec->name, specials[i])) + return true; + + /* Most .discard sections are special */ + for (int i = 0; i < ARRAY_SIZE(non_special_discards); i++) + if (!strcmp(sec->name, non_special_discards[i])) + return false; + return strstarts(sec->name, ".discard."); +} + +/* + * These sections are referenced by special sections but aren't considered + * special sections themselves. + */ +static bool is_special_section_aux(struct section *sec) +{ + static const char * const specials_aux[] = { + ".altinstr_replacement", + ".altinstr_aux", + }; + + for (int i = 0; i < ARRAY_SIZE(specials_aux); i++) + if (!strcmp(sec->name, specials_aux[i])) + return true; + + return false; +} + +/* + * These symbols should never be correlated, so their local patched versions + * are used instead of linking to the originals. + */ +static bool dont_correlate(struct symbol *sym) +{ + return is_file_symbol(sym) || + is_null_symbol(sym) || + is_section_symbol(sym) || + is_prefix_symbol(sym) || + is_uncorrelated_static_local(sym) || + is_string_section(sym->sec) || + is_special_section(sym->sec) || + is_special_section_aux(sym->sec) || + strstarts(sym->name, "__UNIQUE_ID") || + strstarts(sym->name, "__initcall__"); +} + +/* + * For each symbol in the original kernel, find its corresponding "twin" in the + * patched kernel. + */ +static void correlate_symbols(struct elf *elf1, struct elf *elf2) +{ + struct symbol *file1_sym, *file2_sym; + struct symbol *sym1, *sym2; + + /* Correlate locals */ + for (file1_sym = first_file_symbol(elf1), + file2_sym = first_file_symbol(elf2); ; + file1_sym = next_file_symbol(elf1, file1_sym), + file2_sym = next_file_symbol(elf2, file2_sym)) { + + if (!file1_sym && file2_sym) + ERROR("FILE symbol mismatch: NULL != %s", file2_sym->name); + + if (file1_sym && !file2_sym) + ERROR("FILE symbol mismatch: %s != NULL", file1_sym->name); + + if (!file1_sym) + break; + + if (strcmp(file1_sym->name, file2_sym->name)) + ERROR("FILE symbol mismatch: %s != %s",file1_sym->name, file2_sym->name); + + file1_sym->twin = file2_sym; + file2_sym->twin = file1_sym; + + sym1 = file1_sym; + + for_each_sym_continue(elf1, sym1) { + if (is_file_symbol(sym1) || !is_local_symbol(sym1)) + break; + + if (dont_correlate(sym1)) + continue; + + sym2 = file2_sym; + for_each_sym_continue(elf2, sym2) { + if (is_file_symbol(sym2) || !is_local_symbol(sym2)) + break; + + if (sym2->twin || dont_correlate(sym2)) + continue; + + if (strcmp(sym1->demangled_name, sym2->demangled_name)) + continue; + + sym1->twin = sym2; + sym2->twin = sym1; + break; + } + } + } + + /* Correlate globals */ + for_each_sym(elf1, sym1) { + if (sym1->bind == STB_LOCAL) + continue; + + sym2 = find_global_symbol_by_name(elf2, sym1->name); + + if (sym2 && !sym2->twin && !strcmp(sym1->name, sym2->name)) { + sym1->twin = sym2; + sym2->twin = sym1; + } + } + + for_each_sym(elf1, sym1) { + if (sym1->twin || dont_correlate(sym1)) + continue; + WARN("no correlation: %s", sym1->name); + } +} + +/* "sympos" is used by livepatch to disambiguate duplicate symbol names */ +static unsigned long find_sympos(struct elf *elf, struct symbol *sym) +{ + unsigned long sympos = 0, nr_matches = 0; + bool has_dup = false; + struct symbol *s; + + if (sym->bind != STB_LOCAL) + return 0; + + for_each_sym(elf, s) { + if (!strcmp(s->name, sym->name)) { + nr_matches++; + if (s == sym) + sympos = nr_matches; + else + has_dup = true; + } + } + + if (!sympos) + ERROR("can't find sympos for %s", sym->name); + + return has_dup ? sympos : 0; +} + +static void clone_sym_relocs(struct elfs *e, struct symbol *sym_patched); + +static struct symbol *__clone_symbol(struct elf *elf, struct symbol *sym_patched, + bool data_too) +{ + struct section *sec = NULL; + unsigned long offset = 0; + struct symbol *sym; + + if (data_too && sym_has_section(sym_patched)) { + struct section *sec_patched = sym_patched->sec; + + sec = find_section_by_name(elf, sec_patched->name); + if (!sec) + sec = elf_create_section(elf, sec_patched->name, 0, + sec_patched->sh.sh_entsize, + sec_patched->sh.sh_type, + sec_patched->sh.sh_addralign, + sec_patched->sh.sh_flags); + + if (is_string_section(sym_patched->sec)) { + sym = elf_create_section_symbol(elf, sec); + goto sym_created; + } + + if (!is_section_symbol(sym_patched)) + offset = sec_size(sec); + + if (sym_patched->len || is_section_symbol(sym_patched)) { + void *data = NULL; + size_t size; + + // bss doesn't have data + if (sym_patched->sec->data->d_buf) + data = sym_patched->sec->data->d_buf + sym_patched->offset; + + if (is_section_symbol(sym_patched)) + size = sec_size(sym_patched->sec); + else + size = sym_patched->len; + + elf_add_data(elf, sec, data, size); + } + } + + sym = elf_create_symbol(elf, sym_patched->name, sec, sym_patched->bind, + sym_patched->type, offset, sym_patched->len); + +sym_created: + sym_patched->clone = sym; + sym->clone = sym_patched; + + return sym; +} + +/* + * Copy a symbol to the output object, optionally including its data and + * relocations. + */ +static struct symbol *clone_symbol(struct elfs *e, struct symbol *sym_patched, + bool data_too) +{ + if (sym_patched->clone) + return sym_patched->clone; + + /* Clone prefix symbol */ + if (data_too && is_function_symbol(sym_patched) && sym_patched->offset) { + struct symbol *pfx_patched; + + pfx_patched = sec_prev_sym(sym_patched->sec, sym_patched); + + if (pfx_patched && is_prefix_symbol(pfx_patched)) + __clone_symbol(e->out, pfx_patched, true); + } + + __clone_symbol(e->out, sym_patched, data_too); + + if (data_too) + clone_sym_relocs(e, sym_patched); + + return sym_patched->clone; +} + +/* + * Copy all changed functions (and their dependencies) from the patched object + * to the output object. + */ +static void clone_changed_functions(struct elfs *e) +{ + struct symbol *sym_orig, *sym_patched; + + /* Find changed functions */ + for_each_sym(e->orig, sym_orig) { + if (!is_function_symbol(sym_orig) || is_prefix_symbol(sym_orig)) + continue; + + if (!sym_orig->twin) + continue; + + if (sym_orig->checksum != sym_orig->twin->checksum) { + printf("%s: changed: %s\n", Objname, sym_orig->name); + sym_orig->twin->changed = 1; + } + } + + /* Find added functions */ + for_each_sym(e->patched, sym_patched) { + if (!is_function_symbol(sym_patched) || is_prefix_symbol(sym_patched)) + continue; + + if (!sym_patched->twin) { + sym_patched->added = 1; + printf("%s: added: %s\n", Objname, sym_patched->name); + } + } + + /* Clone changed and added functions */ + for_each_sym(e->patched, sym_patched) { + if (sym_patched->changed || sym_patched->added) + clone_symbol(e, sym_patched, true); + } +} + +/* + * Determine whether a relocation should reference the section rather than the + * underlying symbol. + */ +static bool section_reference_needed(struct section *sec) +{ + /* + * String symbols are zero-length and uncorrelated. It's easier to + * deal with them as section symbols. + */ + if (is_string_section(sec)) + return true; + + /* + * .rodata has mostly anonymous data so there's no way to determine the + * length of a needed reference. just copy the whole section. + */ + if (!strcmp(sec->name, ".rodata")) + return true; + + /* UBSAN anonymous data */ + if (strstarts(sec->name, ".data..Lubsan")) + return true; + + return false; +} + +static bool is_reloc_allowed(struct reloc *reloc) +{ + return section_reference_needed(reloc->sym->sec) == + is_section_symbol(reloc->sym); +} + +static struct export *find_export(struct elf *elf, struct symbol *sym) +{ + struct export *export; + + hash_for_each_possible(exports, export, hash, str_hash(sym->name)) { + if (!strcmp(export->sym, sym->name)) + return export; + } + + return NULL; +} + +static const char *__find_modname(struct elfs *e) +{ + struct section *sec; + char *name; + + sec = find_section_by_name(e->orig, ".modinfo"); + if (!sec) + ERROR("missing .modinfo section"); + + name = memmem(sec->data, sec_size(sec), "\0name=", 6); + if (name) + return name + 6; + + name = strdup(e->orig->name); + ERROR_ON(!name, "strdup"); + + for (char *c = name; *c; c++) { + if (*c == '/') + name = c + 1; + else if (*c == '-') + *c = '_'; + else if (*c == '.') { + *c = '\0'; + break; + } + } + + return name; +} + +/* Get the object's module name as defined by the kernel (and klp_object) */ +static const char *find_modname(struct elfs *e) +{ + static const char *modname; + + if (modname) + return modname; + + modname = __find_modname(e); + return modname; +} + +static bool klp_reloc_needed(struct elf *elf_patched, struct reloc *reloc_patched) +{ + struct symbol *sym_patched = reloc_patched->sym; + struct export *export; + + /* no external symbol to reference */ + if (dont_correlate(sym_patched)) + return false; + + /* For cloned functions, a regular reloc will do. */ + if (sym_patched->changed || sym_patched->added) + return false; + + /* + * If exported by a module, it has to be a klp reloc. Thanks to the + * clusterfoot that is late module patching, the patch module is + * allowed to be loaded before any modules it depends on. + * + * If exported by vmlinux, a normal reloc will work. + */ + export = find_export(elf_patched, sym_patched); + if (export) + return strcmp(export->mod, "vmlinux"); + + if (!sym_patched->twin) { + /* + * Presumably the symbol and its reference were added by the + * patch. The symbol could be defined in this .o or in another + * .o in the patch module. + * + * This should be checked *after* the exports, for the case + * where the patch adds a reference to an exported symbol. + */ + return false; + } + + /* Unexported symbol which lives in the original vmlinux or module. */ + return true; +} + +static int convert_reloc_sym_to_secsym(struct elf *elf, struct reloc *reloc) +{ + struct symbol *sym = reloc->sym; + struct section *sec = sym->sec; + + if (!sec->sym) + elf_create_section_symbol(elf, sec); + + reloc->sym = sec->sym; + set_reloc_sym(elf, reloc, sym->idx); + set_reloc_addend(elf, reloc, sym->offset + reloc_addend(reloc)); + return 0; +} + +static int convert_reloc_secsym_to_sym(struct elf *elf, struct reloc *reloc) +{ + struct symbol *sym = reloc->sym; + struct section *sec = sym->sec; + + /* If the symbol has a dedicated section, it's easy to find */ + sym = find_symbol_by_offset(sec, 0); + if (sym && sym->len == sec_size(sec)) + goto found_sym; + + /* No dedicated section; find the symbol manually */ + sym = find_symbol_containing(sec, arch_adjusted_addend(reloc)); + if (!sym) { + /* + * This can happen for special section references to weak code + * whose symbol has been stripped by the linker. + */ + return -1; + } + +found_sym: + reloc->sym = sym; + set_reloc_sym(elf, reloc, sym->idx); + set_reloc_addend(elf, reloc, reloc_addend(reloc) - sym->offset); + return 0; +} + +/* + * Convert a relocation symbol reference to the needed format: either a section + * symbol or the underlying symbol itself. + */ +static int convert_reloc_sym(struct elf *elf, struct reloc *reloc) +{ + if (is_reloc_allowed(reloc)) + return 0; + + if (section_reference_needed(reloc->sym->sec)) + return convert_reloc_sym_to_secsym(elf, reloc); + else + return convert_reloc_secsym_to_sym(elf, reloc); +} + +/* + * Convert a regular relocation to a klp relocation (sort of). + */ +static void clone_reloc_klp(struct elfs *e, struct reloc *reloc_patched, + struct section *sec, unsigned long offset, + struct export *export) +{ + struct symbol *sym_patched = reloc_patched->sym; + s64 addend = reloc_addend(reloc_patched); + const char *sym_modname, *sym_orig_name; + static struct section *klp_relocs; + struct symbol *sym, *klp_sym; + unsigned long klp_reloc_off; + char sym_name[SYM_NAME_LEN]; + struct klp_reloc klp_reloc; + unsigned long sympos; + + if (!sym_patched->twin) + ERROR("unexpected klp reloc for new symbol %s", sym_patched->name); + + /* + * First, copy the original reloc and symbol as-is so objtool will be + * able to process the code correctly during module link time. This + * relocation will be disabled later by cmd_klp_link(). + */ + + sym = sym_patched->clone; + if (!sym) { + sym = elf_create_klp_symbol(e->out, sym_patched->name, + sym_patched->bind, + sym_patched->type); + sym_patched->clone = sym; + sym->clone = sym_patched; + } + + elf_create_reloc(e->out, sec, offset, sym, addend, reloc_type(reloc_patched)); + + /* + * Second, create the klp symbol. + */ + + if (export) { + sym_modname = export->mod; + sym_orig_name = export->sym; + sympos = 0; + } else { + sym_modname = find_modname(e); + sym_orig_name = sym_patched->twin->name; + sympos = find_sympos(e->orig, sym_patched->twin); + } + + /* symbol format: .klp.sym.modname.sym_name,sympos */ + snprintf(sym_name, SYM_NAME_LEN, KLP_SYM_PREFIX "%s.%s,%ld", + sym_modname, sym_orig_name, sympos); + + klp_sym = find_symbol_by_name(e->out, sym_name); + if (!klp_sym) + klp_sym = elf_create_klp_symbol(e->out, sym_name, + sym_patched->bind, + sym_patched->type); + + /* + * Third, create the .klp_relocs entry, which will be converted to an + * actual klp rela by cmd_klp_link(). + * + * This intermediate step is necessary to prevent corruption by the + * linker, which doesn't know how to properly handle two rela sections + * applying to the same base section. + */ + + if (!klp_relocs) + klp_relocs = elf_create_section(e->out, KLP_RELOCS_SEC, 0, + 0, SHT_PROGBITS, 8, SHF_ALLOC); + + klp_reloc_off = sec_size(klp_relocs); + memset(&klp_reloc, 0, sizeof(klp_reloc)); + + klp_reloc.type = reloc_type(reloc_patched); + elf_add_data(e->out, klp_relocs, &klp_reloc, sizeof(klp_reloc)); + + /* klp_reloc.offset */ + if (!sec->sym) + elf_create_section_symbol(e->out, sec); + elf_create_reloc(e->out, klp_relocs, + klp_reloc_off + offsetof(struct klp_reloc, offset), + sec->sym, offset, R_ABS64); + + /* klp_reloc.sym */ + elf_create_reloc(e->out, klp_relocs, + klp_reloc_off + offsetof(struct klp_reloc, sym), + klp_sym, addend, R_ABS64); + +} + +/* Copy a reloc and its symbol to the output object */ +static void clone_reloc(struct elfs *e, struct reloc *reloc_patched, + struct section *sec, unsigned long offset) +{ + struct symbol *sym_patched = reloc_patched->sym; + struct export *export = find_export(e->out, sym_patched); + s64 addend = reloc_addend(reloc_patched); + struct symbol *sym; + + if (!is_reloc_allowed(reloc_patched)) + ERROR_FUNC(reloc_patched->sec->base, reloc_offset(reloc_patched), + "missing symbol for reference to %s+0x%lx", + sym_patched->name, addend); + + if (klp_reloc_needed(e->patched, reloc_patched)) { + clone_reloc_klp(e, reloc_patched, sec, offset, export); + return; + } + + sym = clone_symbol(e, sym_patched, !export); + + if (is_string_section(sym_patched->sec)) + addend = elf_add_string(e->out, sym->sec, + reloc_patched->sym->sec->data->d_buf + addend); + + elf_create_reloc(e->out, sec, offset, sym, addend, reloc_type(reloc_patched)); +} + +/* Copy all relocs needed for a symbol's contents */ +static void clone_sym_relocs(struct elfs *e, struct symbol *sym_patched) +{ + struct section *rsec_patched = sym_patched->sec->rsec; + struct reloc *reloc_patched; + unsigned long start, end; + struct symbol *sym; + + sym = sym_patched->clone; + if (!sym) + ERROR("no clone for %s", sym_patched->name); + + if (!rsec_patched) + return; + + if (!is_section_symbol(sym_patched) && !sym_patched->len) + return; + + if (is_string_section(sym_patched->sec)) + return; + + if (is_section_symbol(sym_patched)) { + start = 0; + end = sec_size(sym_patched->sec); + } else { + start = sym_patched->offset; + end = start + sym_patched->len; + } + + for_each_reloc(rsec_patched, reloc_patched) { + unsigned long offset; + + if (reloc_offset(reloc_patched) < start || + reloc_offset(reloc_patched) >= end) + continue; + + convert_reloc_sym(e->patched, reloc_patched); + + offset = sym->offset + (reloc_offset(reloc_patched) - sym_patched->offset); + + clone_reloc(e, reloc_patched, sym->sec, offset); + } +} + +/* Keep a special section entry if it references an included function */ +static bool should_keep_special_sym(struct elf *elf, struct symbol *sym) +{ + struct reloc *reloc; + + if (is_section_symbol(sym)) + return false; + + for_each_reloc(sym->sec->rsec, reloc) { + unsigned long reloc_off = reloc_offset(reloc); + + if (convert_reloc_sym(elf, reloc)) + continue; + + if (reloc_off >= sym->offset && reloc_off < sym->offset + sym->len && + is_function_symbol(reloc->sym) && + (reloc->sym->changed || reloc->sym->added)) + return true; + } + + return false; +} + +static unsigned int reloc_size(struct reloc *reloc) +{ + switch (reloc_type(reloc)) { + + case R_X86_64_PC32: + case R_X86_64_32: + return 4; + + case R_X86_64_64: + case R_X86_64_PC64: + return 8; + + default: + ERROR("unknown reloc type"); + } +} + +static void clone_special_section(struct elfs *e, struct section *sec_patched) +{ + unsigned long off_patched, off_out; + struct reloc *reloc_patched; + struct symbol *sym_patched; + bool has_syms = false; + struct section *sec; + + /* + * Some special sections are composed of an array of structs, where + * each array entry has its own fake symbol denoting its location and + * size. Copy all entries (both data and relocs) referencing an + * included function. + */ + off_patched = 0; + sec_for_each_sym(sec_patched, sym_patched) { + if (!is_object_symbol(sym_patched)) + continue; + has_syms = true; + + if (off_patched != sym_patched->offset) + ERROR_FUNC(sec_patched, off_patched, "special section symbol gap"); + + off_patched = sym_patched->offset + sym_patched->len; + + if (!should_keep_special_sym(e->patched, sym_patched)) + continue; + + clone_symbol(e, sym_patched, true); + } + + if (has_syms) + return; + + /* + * Some special sections are just a simple array of pointers. Copy all + * relocs referencing included functions. + */ + off_patched = 0; + off_out = 0; + sec = NULL; + for_each_reloc(sec_patched->rsec, reloc_patched) { + unsigned int rel_size = reloc_size(reloc_patched); + + if (off_patched != reloc_offset(reloc_patched)) + ERROR("special section reloc gap - does it need fake symbols?"); + + off_patched += rel_size; + + if (convert_reloc_sym(e->patched, reloc_patched)) + continue; + + if (!reloc_patched->sym->changed && !reloc_patched->sym->added) + continue; + + if (!sec) { + sec = find_section_by_name(e->out, sec_patched->name); + if (sec) + ERROR("why does %s already exist?", sec_patched->name); + + sec = elf_create_section(e->out, sec_patched->name, 0, + sec_patched->sh.sh_entsize, + sec_patched->sh.sh_type, + sec_patched->sh.sh_addralign, + sec_patched->sh.sh_flags); + } + + elf_add_data(e->out, sec, NULL, rel_size); + clone_reloc(e, reloc_patched, sec, off_out); + off_out += rel_size; + } +} + +/* Extract only the needed bits from special sections */ +static void clone_special_sections(struct elfs *e) +{ + struct section *sec_patched; + + for_each_sec(e->patched, sec_patched) { + if (is_special_section(sec_patched)) + clone_special_section(e, sec_patched); + } +} + +/* + * Create __klp_objects and __klp_funcs sections which are intermediate + * sections provided as input to the patch module's init code (module.c) for + * building the 'struct klp_patch' needed for the livepatch API. + */ +static void create_klp_sections(struct elfs *e) +{ + size_t obj_size = sizeof(struct klp_object_ext); + size_t func_size = sizeof(struct klp_func_ext); + struct section *obj_sec, *funcs_sec, *str_sec; + struct symbol *funcs_sym, *str_sym, *sym; + const char *modname = find_modname(e); + char sym_name[SYM_NAME_LEN]; + unsigned int nr_funcs = 0; + void *obj_data; + s64 addend; + + obj_sec = elf_create_section_pair(e->out, KLP_OBJECTS_SEC, obj_size, 0, 0); + + funcs_sec = elf_create_section_pair(e->out, KLP_FUNCS_SEC, func_size, 0, 0); + funcs_sym = elf_create_section_symbol(e->out, funcs_sec); + + str_sec = elf_create_section(e->out, KLP_STRINGS_SEC, 0, 0, + SHT_PROGBITS, 1, + SHF_ALLOC | SHF_STRINGS | SHF_MERGE); + elf_add_string(e->out, str_sec, ""); + str_sym = elf_create_section_symbol(e->out, str_sec); + + /* allocate klp_object_ext */ + obj_data = elf_add_data(e->out, obj_sec, NULL, obj_size); + + /* klp_object_ext.name */ + if (strcmp(modname, "vmlinux")) { + addend = elf_add_string(e->out, str_sec, modname); + elf_create_reloc(e->out, obj_sec, + offsetof(struct klp_object_ext, name), + str_sym, addend, R_ABS64); + } + + /* klp_object_ext.funcs */ + elf_create_reloc(e->out, obj_sec, offsetof(struct klp_object_ext, funcs), + funcs_sym, 0, R_ABS64); + + for_each_sym(e->out, sym) { + unsigned long offset = nr_funcs * func_size; + unsigned long sympos; + void *func_data; + + if (!sym->clone || !sym->clone->changed || !is_function_symbol(sym)) + continue; + + /* allocate klp_func_ext */ + func_data = elf_add_data(e->out, funcs_sec, NULL, func_size); + + /* klp_func_ext.old_name */ + addend = elf_add_string(e->out, str_sec, sym->clone->twin->name); + elf_create_reloc(e->out, funcs_sec, + offset + offsetof(struct klp_func_ext, old_name), + str_sym, addend, R_ABS64); + + /* klp_func_ext.new_func */ + elf_create_reloc(e->out, funcs_sec, + offset + offsetof(struct klp_func_ext, new_func), + sym, 0, R_ABS64); + + /* klp_func_ext.sympos */ + BUILD_BUG_ON(sizeof(sympos) != sizeof_field(struct klp_func_ext, sympos)); + sympos = find_sympos(e->orig, sym->clone->twin); + memcpy(func_data + offsetof(struct klp_func_ext, sympos), &sympos, + sizeof_field(struct klp_func_ext, sympos)); + + nr_funcs++; + } + + /* klp_object_ext.nr_funcs */ + BUILD_BUG_ON(sizeof(nr_funcs) != sizeof_field(struct klp_object_ext, nr_funcs)); + memcpy(obj_data + offsetof(struct klp_object_ext, nr_funcs), &nr_funcs, + sizeof_field(struct klp_object_ext, nr_funcs)); + + /* + * Find callback pointers created by KLP_PRE_PATCH_CALLBACK() and + * friends, and add them to the klp object. + */ + + snprintf(sym_name, SYM_NAME_LEN, KLP_PRE_PATCH_PREFIX "%s", modname); + sym = find_symbol_by_name(e->out, sym_name); + if (sym) { + struct reloc *reloc; + + reloc = find_reloc_by_dest(e->out, sym->sec, sym->offset); + + elf_create_reloc(e->out, obj_sec, + offsetof(struct klp_object_ext, callbacks) + + offsetof(struct klp_callbacks, pre_patch), + reloc->sym, reloc_addend(reloc), R_ABS64); + } + + snprintf(sym_name, SYM_NAME_LEN, KLP_POST_PATCH_PREFIX "%s", modname); + sym = find_symbol_by_name(e->out, sym_name); + if (sym) { + struct reloc *reloc; + + reloc = find_reloc_by_dest(e->out, sym->sec, sym->offset); + + elf_create_reloc(e->out, obj_sec, + offsetof(struct klp_object_ext, callbacks) + + offsetof(struct klp_callbacks, post_patch), + reloc->sym, reloc_addend(reloc), R_ABS64); + } + + snprintf(sym_name, SYM_NAME_LEN, KLP_PRE_UNPATCH_PREFIX "%s", modname); + sym = find_symbol_by_name(e->out, sym_name); + if (sym) { + struct reloc *reloc; + + reloc = find_reloc_by_dest(e->out, sym->sec, sym->offset); + + elf_create_reloc(e->out, obj_sec, + offsetof(struct klp_object_ext, callbacks) + + offsetof(struct klp_callbacks, pre_unpatch), + reloc->sym, reloc_addend(reloc), R_ABS64); + } + + snprintf(sym_name, SYM_NAME_LEN, KLP_POST_UNPATCH_PREFIX "%s", modname); + sym = find_symbol_by_name(e->out, sym_name); + if (sym) { + struct reloc *reloc; + + reloc = find_reloc_by_dest(e->out, sym->sec, sym->offset); + + elf_create_reloc(e->out, obj_sec, + offsetof(struct klp_object_ext, callbacks) + + offsetof(struct klp_callbacks, post_unpatch), + reloc->sym, reloc_addend(reloc), R_ABS64); + } +} + +int cmd_klp_diff(int argc, const char **argv) +{ + struct elf *elf_orig, *elf_patched, *elf_out; + struct elfs e; + + argc--; + argv++; + + if (argc != 3) { + fprintf(stderr, "usage: objtool diff <in1.o> <in2.o> <out.o>\n"); + return -1; + } + + elf_orig = elf_open_read(argv[0], O_RDONLY); + elf_patched = elf_open_read(argv[1], O_RDONLY); + + Objname = basename(Objname); + + read_exports(); + + read_sym_checksums(elf_orig); + read_sym_checksums(elf_patched); + + correlate_symbols(elf_orig, elf_patched); + + elf_out = elf_create_file(&elf_orig->ehdr, argv[2]); + + e.orig = elf_orig; + e.patched = elf_patched; + e.out = elf_out; + + clone_changed_functions(&e); + + clone_special_sections(&e); + + create_klp_sections(&e); + + elf_write(elf_out); + return 0; +} + diff --git a/tools/objtool/klp-link.c b/tools/objtool/klp-link.c new file mode 100644 index 000000000000..0b42a79c8215 --- /dev/null +++ b/tools/objtool/klp-link.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2024 Josh Poimboeuf <jpoimboe@xxxxxxxxxx> + */ +#include <fcntl.h> +#include <gelf.h> +#include <objtool/objtool.h> +#include <objtool/warn.h> +#include <objtool/klp.h> +#include <linux/livepatch_ext.h> + +/* + * This runs on the livepatch module after all other linking has been done. It + * converts the intermediate __klp_relocs section into proper klp relocs to be + * processed by livepatch. This needs to run last to avoid linker wreckage. + * Linkers don't tend to handle the "two rela sections for a single base + * section" case very well. + */ +int cmd_klp_link(int argc, const char **argv) +{ + struct section *symtab, *klp_relocs; + struct elf *elf; + + argc--; + argv++; + + if (argc != 1) { + fprintf(stderr, "%d\n", argc); + fprintf(stderr, "usage: objtool link <file.ko>\n"); + return -1; + } + + elf = elf_open_read(argv[0], O_RDWR); + + klp_relocs = find_section_by_name(elf, KLP_RELOCS_SEC); + if (!klp_relocs) + return 0; + + symtab = find_section_by_name(elf, ".symtab"); + if (!symtab) + ERROR("missing .symtab"); + + for (int i = 0; i < sec_size(klp_relocs) / sizeof(struct klp_reloc); i++) { + struct klp_reloc *klp_reloc; + unsigned long klp_reloc_off; + struct section *sec, *tmp, *klp_rsec; + unsigned long offset; + struct reloc *reloc; + char sym_modname[64]; + char rsec_name[SEC_NAME_LEN]; + u64 addend; + struct symbol *sym, *klp_sym; + + klp_reloc_off = i * sizeof(*klp_reloc); + klp_reloc = klp_relocs->data->d_buf + klp_reloc_off; + + /* + * Read __klp_relocs entry: + */ + + /* klp_reloc.sec_offset */ + reloc = find_reloc_by_dest(elf, klp_relocs, + klp_reloc_off + offsetof(struct klp_reloc, offset)); + ERROR_ON(!reloc, "malformed " KLP_RELOCS_SEC " section"); + + sec = reloc->sym->sec; + offset = reloc_addend(reloc); + + /* klp_reloc.sym */ + reloc = find_reloc_by_dest(elf, klp_relocs, + klp_reloc_off + offsetof(struct klp_reloc, sym)); + ERROR_ON(!reloc, "malformed " KLP_RELOCS_SEC " section"); + + klp_sym = reloc->sym; + addend = reloc_addend(reloc); + + /* symbol format: .klp.sym.modname.sym_name,sympos */ + sscanf(klp_sym->name + strlen(KLP_SYM_PREFIX), "%55[^.]", sym_modname); + + /* + * Create klp reloc: + */ + + /* section format: .klp.rela.sec_objname.section_name */ + snprintf(rsec_name, SEC_NAME_LEN, KLP_RELOC_SEC_PREFIX "%s.%s", + sym_modname, sec->name); + klp_rsec = find_section_by_name(elf, rsec_name); + + if (!klp_rsec) { + klp_rsec = elf_create_section(elf, rsec_name, 0, + elf_rela_size(elf), + SHT_RELA, elf_addr_size(elf), + SHF_ALLOC | SHF_INFO_LINK | SHF_RELA_LIVEPATCH); + + klp_rsec->sh.sh_link = symtab->idx; + klp_rsec->sh.sh_info = sec->idx; + klp_rsec->base = sec; + } + + tmp = sec->rsec; + sec->rsec = klp_rsec; + elf_create_reloc(elf, sec, offset, klp_sym, addend, klp_reloc->type); + sec->rsec = tmp; + + klp_sym->sym.st_shndx = SHN_LIVEPATCH; + gelf_update_sym(symtab->data, klp_sym->idx, &klp_sym->sym); + + /* + * Disable original non-klp reloc by converting it to R_*_NONE: + */ + + reloc = find_reloc_by_dest(elf, sec, offset); + sym = reloc->sym; + sym->sym.st_shndx = SHN_LIVEPATCH; + set_reloc_type(elf, reloc, 0); + gelf_update_sym(symtab->data, sym->idx, &sym->sym); + } + + elf_write(elf); + + return 0; +} diff --git a/tools/objtool/klp.c b/tools/objtool/klp.c new file mode 100644 index 000000000000..fc871108060e --- /dev/null +++ b/tools/objtool/klp.c @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2024 Josh Poimboeuf <jpoimboe@xxxxxxxxxx> + */ + +#include <subcmd/parse-options.h> +#include <string.h> +#include <stdlib.h> +#include <objtool/builtin.h> +#include <objtool/objtool.h> +#include <objtool/klp.h> + +struct subcmd { + const char *name; + const char *description; + int (*fn)(int, const char **); +}; + +static struct subcmd subcmds[] = { + { "diff", "Generate binary diff of two object files", cmd_klp_diff, }, + { "link", "Finalize klp symbols/relocations after module linking", cmd_klp_link, }, +}; + +static void cmd_klp_usage(void) +{ + fprintf(stderr, "usage: objtool klp <subcommand> [<options>]\n\n"); + fprintf(stderr, "Subcommands:\n"); + + for (int i = 0; i < ARRAY_SIZE(subcmds); i++) { + struct subcmd *cmd = &subcmds[i]; + + fprintf(stderr," %s\t%s\n", cmd->name, cmd->description); + } + + exit(1); +} + +int cmd_klp(int argc, const char **argv) +{ + argc--; + argv++; + + if (!argc) + cmd_klp_usage(); + + if (argc) { + for (int i = 0; i < ARRAY_SIZE(subcmds); i++) { + struct subcmd *cmd = &subcmds[i]; + + if (!strcmp(cmd->name, argv[0])) + return cmd->fn(argc, argv); + } + } + + cmd_klp_usage(); + return 0; +} diff --git a/tools/objtool/objtool.c b/tools/objtool/objtool.c index 06f7e518b8a7..75ff32ab0368 100644 --- a/tools/objtool/objtool.c +++ b/tools/objtool/objtool.c @@ -122,5 +122,11 @@ int main(int argc, const char **argv) exec_cmd_init("objtool", UNUSED, UNUSED, UNUSED); pager_init(UNUSED); + if (argc > 1 && !strcmp(argv[1], "klp")) { + argc--; + argv++; + return cmd_klp(argc, argv); + } + return objtool_run(argc, argv); } diff --git a/tools/objtool/sync-check.sh b/tools/objtool/sync-check.sh index 81d120d05442..873ce93f9993 100755 --- a/tools/objtool/sync-check.sh +++ b/tools/objtool/sync-check.sh @@ -16,6 +16,7 @@ arch/x86/include/asm/orc_types.h arch/x86/include/asm/emulate_prefix.h arch/x86/lib/x86-opcode-map.txt arch/x86/tools/gen-insn-attr-x86.awk +include/linux/livepatch_ext.h include/linux/static_call_types.h " diff --git a/tools/objtool/weak.c b/tools/objtool/weak.c index 426fdf0b7548..7e1858885fd6 100644 --- a/tools/objtool/weak.c +++ b/tools/objtool/weak.c @@ -8,6 +8,8 @@ #include <stdbool.h> #include <errno.h> #include <objtool/objtool.h> +#include <objtool/arch.h> +#include <objtool/builtin.h> #define UNSUPPORTED(name) \ ({ \ @@ -24,3 +26,8 @@ int __weak orc_create(struct objtool_file *file) { UNSUPPORTED("ORC"); } + +int __weak cmd_klp(int argc, const char **argv) +{ + UNSUPPORTED("klp"); +} -- 2.45.2