From b0058988c7e57627ae64ace40377de6da4fa14b9 Mon Sep 17 00:00:00 2001 From: Jessica Yu Date: Thu, 4 Sep 2014 07:38:17 -0700 Subject: [PATCH 3/3] re-enable patch modules with checksum matching To: rhkernel-list@redhat.com In order to safely re-enable patch modules, add a special .kpatch.checksum section containing an md5sum of a patch module's contents. The contents of this section are exported to sysfs via patch_init and double checked when kpatch load finds that a module of the same name is already loaded. Conflicts: kpatch-build/kpatch-build --- kmod/core/core.c | 19 ++++++++++++------ kmod/patch/kpatch-patch-hook.c | 45 ++++++++++++++++++++++++++++-------------- kmod/patch/kpatch.lds | 1 + kpatch-build/kpatch-build | 5 ++++- kpatch/kpatch | 32 ++++++++++++++++++++++++++++++ 5 files changed, 80 insertions(+), 22 deletions(-) diff --git a/kmod/core/core.c b/kmod/core/core.c index 8c1bb37..bb624a9 100644 --- a/kmod/core/core.c +++ b/kmod/core/core.c @@ -811,12 +811,16 @@ int kpatch_register(struct kpatch_module *kpmod, bool replace) down(&kpatch_mutex); - kpmod->enabled = false; + if (kpmod->enabled) { + ret = -EINVAL; + goto err_up; + } + list_add_tail(&kpmod->list, &kpmod_list); if (!try_module_get(kpmod->mod)) { ret = -ENODEV; - goto err_up; + goto err_list; } list_for_each_entry(object, &kpmod->objects, list) { @@ -932,8 +936,9 @@ err_unlink: if (kpatch_object_linked(object)) kpatch_unlink_object(object); module_put(kpmod->mod); -err_up: +err_list: list_del(&kpmod->list); +err_up: up(&kpatch_mutex); return ret; } @@ -945,11 +950,13 @@ int kpatch_unregister(struct kpatch_module *kpmod) struct kpatch_func *func; int ret, force = 0; - if (!kpmod->enabled) - return -EINVAL; - down(&kpatch_mutex); + if (!kpmod->enabled) { + ret = -EINVAL; + goto out; + } + do_for_each_linked_func(kpmod, func) { func->op = KPATCH_OP_UNPATCH; if (func->force) diff --git a/kmod/patch/kpatch-patch-hook.c b/kmod/patch/kpatch-patch-hook.c index ea68878..1370223 100644 --- a/kmod/patch/kpatch-patch-hook.c +++ b/kmod/patch/kpatch-patch-hook.c @@ -35,6 +35,7 @@ extern struct kpatch_patch_dynrela __kpatch_dynrelas[], __kpatch_dynrelas_end[]; extern struct kpatch_patch_hook __kpatch_hooks_load[], __kpatch_hooks_load_end[]; extern struct kpatch_patch_hook __kpatch_hooks_unload[], __kpatch_hooks_unload_end[]; extern unsigned long __kpatch_force_funcs[], __kpatch_force_funcs_end[]; +extern char __kpatch_checksum[]; static struct kpatch_module kpmod; static struct kobject *patch_kobj; @@ -61,29 +62,43 @@ static ssize_t patch_enabled_store(struct kobject *kobj, int ret; unsigned long val; - /* only disabling is supported */ - if (!kpmod.enabled) - return -EINVAL; - ret = kstrtoul(buf, 10, &val); if (ret) return ret; val = !!val; - /* only disabling is supported */ if (val) - return -EINVAL; + ret = kpatch_register(&kpmod, replace); + else + ret = kpatch_unregister(&kpmod); - ret = kpatch_unregister(&kpmod); if (ret) return ret; return count; } +static ssize_t checksum_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", __kpatch_checksum); +} + static struct kobj_attribute patch_enabled_attr = __ATTR(enabled, 0644, patch_enabled_show, patch_enabled_store); +static struct kobj_attribute checksum_attr = + __ATTR(checksum, 0444, checksum_show, NULL); + +static struct attribute *kpatch_attrs[] = { + &patch_enabled_attr.attr, + &checksum_attr.attr, + NULL, +}; + +static struct attribute_group kpatch_attr_group = { + .attrs = kpatch_attrs, +}; static ssize_t func_old_addr_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) @@ -354,14 +369,10 @@ static int __init patch_init(void) if (!patch_kobj) return -ENOMEM; - ret = sysfs_create_file(patch_kobj, &patch_enabled_attr.attr); - if (ret) - goto err_patch; - functions_kobj = kobject_create_and_add("functions", patch_kobj); if (!functions_kobj) { ret = -ENOMEM; - goto err_sysfs; + goto err_patch; } kpmod.mod = THIS_MODULE; @@ -383,13 +394,17 @@ static int __init patch_init(void) if (ret) goto err_objects; + ret = sysfs_create_group(patch_kobj, &kpatch_attr_group); + if (ret) + goto err_sysfs; + return 0; +err_sysfs: + kpatch_unregister(&kpmod); err_objects: patch_free_objects(); kobject_put(functions_kobj); -err_sysfs: - sysfs_remove_file(patch_kobj, &patch_enabled_attr.attr); err_patch: kobject_put(patch_kobj); return ret; @@ -401,7 +416,7 @@ static void __exit patch_exit(void) patch_free_objects(); kobject_put(functions_kobj); - sysfs_remove_file(patch_kobj, &patch_enabled_attr.attr); + sysfs_remove_group(patch_kobj, &kpatch_attr_group); kobject_put(patch_kobj); } diff --git a/kmod/patch/kpatch.lds b/kmod/patch/kpatch.lds index 105e5e6..49ae8e7 100644 --- a/kmod/patch/kpatch.lds +++ b/kmod/patch/kpatch.lds @@ -2,6 +2,7 @@ __kpatch_funcs = ADDR(.kpatch.funcs); __kpatch_funcs_end = ADDR(.kpatch.funcs) + SIZEOF(.kpatch.funcs); __kpatch_dynrelas = ADDR(.kpatch.dynrelas); __kpatch_dynrelas_end = ADDR(.kpatch.dynrelas) + SIZEOF(.kpatch.dynrelas); +__kpatch_checksum = ADDR(.kpatch.checksum); SECTIONS { .kpatch.hooks.load : { diff --git a/kpatch-build/kpatch-build b/kpatch-build/kpatch-build index 91bf5c2..38466f4 100755 --- a/kpatch-build/kpatch-build +++ b/kpatch-build/kpatch-build @@ -343,7 +343,7 @@ else url="http://us.archive.ubuntu.com/ubuntu/pool/main/l/linux" extension="bz2" sublevel="SUBLEVEL = 0" - taroptions="xvjf" + troptions="xvjf" elif [[ $DISTRO = debian ]]; then @@ -478,6 +478,9 @@ cd "$SRCDIR" make prepare >> "$LOGFILE" 2>&1 || die cd "$TEMPDIR/output" ld -r -o ../patch/output.o $FILES >> "$LOGFILE" 2>&1 || die +md5sum ../patch/output.o | awk '{printf "%s\0", $1}' > checksum.tmp || die +objcopy --add-section .kpatch.checksum=checksum.tmp --set-section-flags .kpatch.checksum=alloc,load,contents,readonly ../patch/output.o || die +rm -f checksum.tmp cd "$TEMPDIR/patch" KPATCH_BUILD="$SRCDIR" KPATCH_NAME="$PATCHNAME" KBUILD_EXTRA_SYMBOLS="$SYMVERSFILE" make "O=$OBJDIR" >> "$LOGFILE" 2>&1 || die diff --git a/kpatch/kpatch b/kpatch/kpatch index 0b932a6..0e36dda 100755 --- a/kpatch/kpatch +++ b/kpatch/kpatch @@ -121,6 +121,21 @@ core_module_loaded () { grep -q "T kpatch_register" /proc/kallsyms } +get_module_name () { + echo $(readelf -p .gnu.linkonce.this_module $1 | grep '\[.*\]' | awk '{print $3}') +} + +verify_module_checksum () { + modname=$(get_module_name $1) + [[ -z $modname ]] && return 1 + + checksum=$(readelf -p .kpatch.checksum $1 | grep '\[.*\]' | awk '{print $3}') + [[ -z $checksum ]] && return 1 + + sysfs_checksum=$(cat /sys/kernel/kpatch/patches/${modname}/checksum) + [[ $checksum == $sysfs_checksum ]] || return 1 +} + load_module () { if ! core_module_loaded; then if modprobe -q kpatch; then @@ -131,6 +146,23 @@ load_module () { insmod "$COREMOD" || die "failed to load core module" fi fi + + modname=$(get_module_name $1) + moddir=/sys/kernel/kpatch/patches/$modname + if [[ -d $moddir ]] ; then + if [[ $(cat "${moddir}/enabled") -eq 0 ]]; then + if verify_module_checksum $1; then # same checksum + echo "module already loaded, re-enabling" + echo 1 > ${moddir}/enabled || die "failed to re-enable module $modname" + return + else + die "error: cannot re-enable patch module $modname, cannot verify checksum match" + fi + else + die "error: module named $modname already loaded and enabled" + fi + fi + echo "loading patch module: $1" insmod "$1" "$2" } -- 1.9.3