diff --git a/kmod/core/core.c b/kmod/core/core.c index 8da6036..9f9ddd3 100644 --- a/kmod/core/core.c +++ b/kmod/core/core.c @@ -141,19 +141,33 @@ static struct kpatch_func *kpatch_get_prev_func(struct kpatch_func *f, return NULL; } +static inline int kpatch_compare_addresses(unsigned long stack_addr, + unsigned long func_addr, + unsigned long func_size) +{ + if (stack_addr >= func_addr && stack_addr < func_addr + func_size) { + /* TODO: use kallsyms to print symbol name */ + pr_err("activeness safety check failed for function at address 0x%lx\n", stack_addr); + return -EBUSY; + } + return 0; +} + static void kpatch_backtrace_address_verify(void *data, unsigned long address, int reliable) { struct kpatch_backtrace_args *args = data; struct kpatch_module *kpmod = args->kpmod; + struct kpatch_func *func; int i; if (args->ret) return; + /* check kpmod funcs */ for (i = 0; i < kpmod->num_funcs; i++) { unsigned long func_addr, func_size; - struct kpatch_func *func, *active_func; + struct kpatch_func *active_func; func = &kpmod->funcs[i]; active_func = kpatch_get_func(func->old_addr); @@ -167,11 +181,20 @@ static void kpatch_backtrace_address_verify(void *data, unsigned long address, func_size = active_func->new_size; } - if (address >= func_addr && address < func_addr + func_size) { - pr_err("activeness safety check failed for function at address 0x%lx\n", - func_addr); - args->ret = -EBUSY; + args->ret = kpatch_compare_addresses(address, func_addr, + func_size); + if (args->ret) return; + } + + /* in the replace case, need to check the func hash as well */ + hash_for_each_rcu(kpatch_func_hash, i, func, node) { + if (func->op == KPATCH_OP_UNPATCH) { + args->ret = kpatch_compare_addresses(address, + func->new_addr, + func->new_size); + if (args->ret) + return; } } } @@ -366,10 +389,11 @@ static void kpatch_remove_funcs_from_filter(struct kpatch_func *funcs, } } -int kpatch_register(struct kpatch_module *kpmod) +int kpatch_register(struct kpatch_module *kpmod, bool replace) { int ret, i; struct kpatch_func *funcs = kpmod->funcs; + struct kpatch_func *func; int num_funcs = kpmod->num_funcs; if (!kpmod->mod || !funcs || !num_funcs) @@ -385,9 +409,10 @@ int kpatch_register(struct kpatch_module *kpmod) } for (i = 0; i < num_funcs; i++) { - struct kpatch_func *func = &funcs[i]; + func = &funcs[i]; func->op = KPATCH_OP_PATCH; + func->kpmod = kpmod; /* * If any other modules have also patched this function, it @@ -417,6 +442,10 @@ int kpatch_register(struct kpatch_module *kpmod) } kpatch_num_registered++; + if (replace) + hash_for_each_rcu(kpatch_func_hash, i, func, node) + func->op = KPATCH_OP_UNPATCH; + /* memory barrier between func hash and state write */ smp_wmb(); @@ -428,6 +457,29 @@ int kpatch_register(struct kpatch_module *kpmod) */ ret = stop_machine(kpatch_apply_patch, kpmod, NULL); + /* + * For the replace case, remove any obsolete funcs from the hash and + * the ftrace filter, and disable the owning patch module so that it + * can be removed. + */ + if (!ret && replace) + hash_for_each_rcu(kpatch_func_hash, i, func, node) { + if (func->op != KPATCH_OP_UNPATCH) + continue; + hash_del_rcu(&func->node); + kpatch_remove_funcs_from_filter(func, 1); + if (func->kpmod->enabled) { + kpatch_num_registered--; + func->kpmod->enabled = false; + pr_notice("unloaded patch module \"%s\"\n", + func->kpmod->mod->name); + module_put(func->kpmod->mod); + } + } + + /* memory barrier between func hash and state write */ + smp_wmb(); + /* NMI handlers can return to normal now */ kpatch_state_idle(); @@ -458,6 +510,9 @@ int kpatch_register(struct kpatch_module *kpmod) return 0; err_unregister: + if (replace) + hash_for_each_rcu(kpatch_func_hash, i, func, node) + func->op = KPATCH_OP_NONE; if (kpatch_num_registered == 1) { int ret2 = unregister_ftrace_function(&kpatch_ftrace_ops); if (ret2) { @@ -550,6 +605,7 @@ static int kpatch_init(void) static void kpatch_exit(void) { + WARN_ON(kpatch_num_registered != 0); kobject_put(kpatch_patches_kobj); kobject_put(kpatch_root_kobj); } diff --git a/kmod/core/kpatch.h b/kmod/core/kpatch.h index a317ecd..121e383 100644 --- a/kmod/core/kpatch.h +++ b/kmod/core/kpatch.h @@ -41,6 +41,7 @@ struct kpatch_func { /* private */ struct hlist_node node; + struct kpatch_module *kpmod; enum kpatch_op op; }; @@ -54,7 +55,7 @@ struct kpatch_module { extern struct kobject *kpatch_patches_kobj; -extern int kpatch_register(struct kpatch_module *kpmod); +extern int kpatch_register(struct kpatch_module *kpmod, bool replace); extern int kpatch_unregister(struct kpatch_module *kpmod); #endif /* _KPATCH_H_ */ diff --git a/kmod/patch/kpatch-patch-hook.c b/kmod/patch/kpatch-patch-hook.c index 61c5921..3c085b3 100644 --- a/kmod/patch/kpatch-patch-hook.c +++ b/kmod/patch/kpatch-patch-hook.c @@ -25,6 +25,10 @@ #include "kpatch.h" #include "kpatch-patch.h" +static bool replace; +module_param(replace, bool, S_IRUGO); +MODULE_PARM_DESC(replace, "replace all previously loaded patch modules"); + extern char __kpatch_patches, __kpatch_patches_end; static struct kpatch_module kpmod; @@ -99,7 +103,7 @@ static int __init patch_init(void) if (ret) goto err_put; - ret = kpatch_register(&kpmod); + ret = kpatch_register(&kpmod, replace); if (ret) goto err_sysfs; diff --git a/kpatch/kpatch b/kpatch/kpatch index 9d77fd5..38e9a13 100755 --- a/kpatch/kpatch +++ b/kpatch/kpatch @@ -33,6 +33,8 @@ usage_cmd() { } usage () { + # ATTENTION ATTENTION ATTENTION ATTENTION ATTENTION ATTENTION + # When changing this, please also update the man page. Thanks! echo "usage: kpatch []" >&2 echo >&2 echo "Valid commands:" >&2 @@ -41,6 +43,7 @@ usage () { echo >&2 usage_cmd "load --all" "load all installed patch modules into the running kernel" usage_cmd "load " "load patch module into the running kernel" + usage_cmd "replace " "load patch module into the running kernel, replacing all other modules" usage_cmd "unload " "unload patch module from the running kernel" echo >&2 usage_cmd "info " "show information about a patch module" @@ -100,7 +103,7 @@ load_module () { /usr/sbin/insmod "$COREMOD" || die "failed to load core module" fi echo "loading patch module: $1" - /usr/sbin/insmod "$1" + /usr/sbin/insmod "$1" "$2" } unload_module () { @@ -113,14 +116,22 @@ unload_module () { ENABLED=/sys/kernel/kpatch/patches/"$PATCH"/enabled if [[ -e "$ENABLED" ]] && [[ $(cat "$ENABLED") -eq 1 ]]; then - echo "disabling patch module: $1" + echo "disabling patch module: $PATCH" echo 0 > $ENABLED || die "can't disable $PATCH" fi - echo "unloading patch module: $1" + echo "unloading patch module: $PATCH" /usr/sbin/rmmod "$(basename $1)" } +unload_disabled_modules() { + for module in /sys/kernel/kpatch/patches/*; do + if [[ $(cat $module/enabled) -eq 0 ]]; then + unload_module $module || die "failed to unload $module" + fi + done +} + echo_patch_name() { NAME="$(basename $1)" echo $NAME @@ -150,6 +161,14 @@ case "$1" in esac ;; +"replace") + [[ "$#" -ne 2 ]] && usage + PATCH="$2" + find_module "$PATCH" || die "can't find $PATCH" + load_module "$MODULE" replace=1 || die "failed to load patch $PATCH" + unload_disabled_modules || die "failed to unload old modules" + ;; + "unload") [[ "$#" -ne 2 ]] && usage unload_module "$2" || die "failed to unload patch $2" diff --git a/man/kpatch.1 b/man/kpatch.1 index fc0af62..98b9ed0 100644 --- a/man/kpatch.1 +++ b/man/kpatch.1 @@ -9,7 +9,7 @@ kpatch [] kpatch is a user script that manages installing, loading, and displaying information about kernel patch modules installed on the system. -.SH OPTIONS +.SH COMMANDS install install patch module to the initrd to be loaded at boot @@ -23,6 +23,9 @@ load --all load load patch module into the running kernel +replace + load patch module into the running kernel, replacing all other modules + unload unload patch module from the running kernel