mirror of https://github.com/dynup/kpatch
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:
parent
354b399be5
commit
65810a47d0
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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_ */
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue