kpatch replace

Allow the user to atomically replace all existing modules with a new
"kpatch replace" command.  This provides a safe way to do atomic
upgrades for cumulative patch module updates.
This commit is contained in:
Josh Poimboeuf 2014-04-28 16:18:29 -05:00
parent 354b399be5
commit 65810a47d0
5 changed files with 96 additions and 13 deletions

View File

@ -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);
}

View File

@ -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_ */

View File

@ -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;

View File

@ -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 <command> [<args>]" >&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 <module>" "load patch module into the running kernel"
usage_cmd "replace <module>" "load patch module into the running kernel, replacing all other modules"
usage_cmd "unload <module>" "unload patch module from the running kernel"
echo >&2
usage_cmd "info <module>" "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"

View File

@ -9,7 +9,7 @@ kpatch <command> [<args>]
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 <module>
install patch module to the initrd to be loaded at boot
@ -23,6 +23,9 @@ load --all
load <module>
load patch module into the running kernel
replace <module>
load patch module into the running kernel, replacing all other modules
unload <module>
unload patch module from the running kernel