Blob Blame History Raw
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