From b0058988c7e57627ae64ace40377de6da4fa14b9 Mon Sep 17 00:00:00 2001
From: Jessica Yu <jyu@cowsay.org>
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