Add a simple livepatch versioning policy to the livepatch core. For those klp_patches with non-zero version, only load those with higher versions than the livepatch core's latest-version. Livepatches may opt out of the policy altogether (i.e. patches always load and do not update latest-version) by specifying klp_patch.version=0. Signed-off-by: Joe Lawrence <joe.lawrence@xxxxxxxxxx> --- .../ABI/testing/sysfs-kernel-livepatch | 14 ++ Documentation/livepatch/versioning.txt | 31 ++++ include/linux/livepatch.h | 2 + kernel/livepatch/core.c | 55 ++++++ kernel/livepatch/core.h | 1 + kernel/livepatch/transition.c | 6 + lib/livepatch/Makefile | 6 +- lib/livepatch/test_klp_version_0.c | 34 ++++ lib/livepatch/test_klp_version_1.c | 34 ++++ lib/livepatch/test_klp_version_2.c | 34 ++++ lib/livepatch/test_klp_version_3.c | 34 ++++ tools/testing/selftests/livepatch/Makefile | 3 +- .../selftests/livepatch/test-versions.sh | 172 ++++++++++++++++++ 13 files changed, 424 insertions(+), 2 deletions(-) create mode 100644 Documentation/livepatch/versioning.txt create mode 100644 lib/livepatch/test_klp_version_0.c create mode 100644 lib/livepatch/test_klp_version_1.c create mode 100644 lib/livepatch/test_klp_version_2.c create mode 100644 lib/livepatch/test_klp_version_3.c create mode 100755 tools/testing/selftests/livepatch/test-versions.sh diff --git a/Documentation/ABI/testing/sysfs-kernel-livepatch b/Documentation/ABI/testing/sysfs-kernel-livepatch index 85db352f68f9..bc4475062446 100644 --- a/Documentation/ABI/testing/sysfs-kernel-livepatch +++ b/Documentation/ABI/testing/sysfs-kernel-livepatch @@ -8,6 +8,13 @@ Description: The /sys/kernel/livepatch directory contains subdirectories for each loaded live patch module. +What: /sys/kernel/livepatch/latest_version +Date: Feb 2019 +KernelVersion: 5.0.0 +Contact: live-patching@xxxxxxxxxxxxxxx +Description: + The highest version of any currently enabled patch. + What: /sys/kernel/livepatch/<patch> Date: Nov 2014 KernelVersion: 3.19.0 @@ -25,6 +32,13 @@ Description: code is currently applied. Writing 0 will disable the patch while writing 1 will re-enable the patch. +What: /sys/kernel/livepatch/<patch>/version +Date: Feb 2019 +KernelVersion: 5.0.0 +Contact: live-patching@xxxxxxxxxxxxxxx +Description: + Patch version number (unsigned). + What: /sys/kernel/livepatch/<patch>/transition Date: Feb 2017 KernelVersion: 4.12.0 diff --git a/Documentation/livepatch/versioning.txt b/Documentation/livepatch/versioning.txt new file mode 100644 index 000000000000..de94fa6e37f7 --- /dev/null +++ b/Documentation/livepatch/versioning.txt @@ -0,0 +1,31 @@ +==================== +Livepatch versioning +==================== + +Livepatches can alter the system state in ways that require awareness from +subsequent livepatches. For example, livepatch FOO may change the semantic of +data structure X or function Y. If a second livepatch BAR is subsequently +loaded and replaces or relies on the functionality in livepatch FOO, it will +need to consider these semantic changes to operate safely. Livepatch +versioning provides a means to facilitate this. + +The klp_patch structure contains a 'version' value that may be used to resolve +compatibility concerns between livepatch modules. The livepatching core +enforces a simple policy: + + - The livepatch core keeps a 'latest-version' value, equal to the highest + version from the set of livepatches that are currently enabled. It is + recalculated when livepatches are enabled and disabled. + + - On klp_patch_enable(), the patch version is inspected and must be greater + than the livepatch core's latest-version: if it is not, the livepatch + module will fail to load and the latest-version remains unchanged. + + - Livepatches with no version, i.e. klp_patch.version=0, are exempt from + this policy and may always be loaded, regardless of the latest-version. + The latest-version remains unchanged when such patches are loaded. + +Returning to the earlier example, the klp_patch.version value may be used by +livepatch FOO to indicate special consideration will be required from future +livepatches. In turn, livepatch BAR's higher version value indicates that it +does indeed succeed livepatch FOO and that it is safe to load. diff --git a/include/linux/livepatch.h b/include/linux/livepatch.h index 53551f470722..70d5354e11a8 100644 --- a/include/linux/livepatch.h +++ b/include/linux/livepatch.h @@ -150,6 +150,7 @@ struct klp_object { * struct klp_patch - patch structure for live patching * @mod: reference to the live patch module * @objs: object entries for kernel objects to be patched + * @version: patch version number * @replace: replace all actively used patches * @list: list node for global list of actively used patches * @kobj: kobject for sysfs resources @@ -164,6 +165,7 @@ struct klp_patch { /* external */ struct module *mod; struct klp_object *objs; + unsigned int version; bool replace; /* internal */ diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c index fe1993399823..cc120b5018e1 100644 --- a/kernel/livepatch/core.c +++ b/kernel/livepatch/core.c @@ -54,6 +54,11 @@ LIST_HEAD(klp_patches); static struct kobject *klp_root_kobj; +/* + * The latest klp_patch.version currently loaded + */ +unsigned int latest_version; + static bool klp_is_module(struct klp_object *obj) { return obj->name; @@ -310,13 +315,39 @@ static int klp_write_object_relocations(struct module *pmod, * Sysfs Interface * * /sys/kernel/livepatch + * /sys/kernel/livepatch/latest_version * /sys/kernel/livepatch/<patch> * /sys/kernel/livepatch/<patch>/enabled * /sys/kernel/livepatch/<patch>/transition * /sys/kernel/livepatch/<patch>/force + * /sys/kernel/livepatch/<patch>/version * /sys/kernel/livepatch/<patch>/<object> * /sys/kernel/livepatch/<patch>/<object>/<function,sympos> */ + +static ssize_t latest_version_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int ret; + + mutex_lock(&klp_mutex); + ret = snprintf(buf, PAGE_SIZE-1, "%d\n", latest_version); + mutex_unlock(&klp_mutex); + + return ret; +} + +static struct kobj_attribute latest_version_kobj_attr = + __ATTR_RO(latest_version); +static struct attribute *klp_sysfs_attrs[] = { + &latest_version_kobj_attr.attr, + NULL, +}; + +static struct attribute_group attr_group = { + .attrs = klp_sysfs_attrs, +}; + static int __klp_disable_patch(struct klp_patch *patch); static ssize_t enabled_store(struct kobject *kobj, struct kobj_attribute *attr, @@ -381,6 +412,15 @@ static ssize_t transition_show(struct kobject *kobj, patch == klp_transition_patch); } +static ssize_t version_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct klp_patch *patch; + + patch = container_of(kobj, struct klp_patch, kobj); + return snprintf(buf, PAGE_SIZE-1, "%d\n", patch->version); +} + static ssize_t force_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { @@ -412,10 +452,12 @@ static ssize_t force_store(struct kobject *kobj, struct kobj_attribute *attr, static struct kobj_attribute enabled_kobj_attr = __ATTR_RW(enabled); static struct kobj_attribute transition_kobj_attr = __ATTR_RO(transition); +static struct kobj_attribute version_kobj_attr = __ATTR_RO(version); static struct kobj_attribute force_kobj_attr = __ATTR_WO(force); static struct attribute *klp_patch_attrs[] = { &enabled_kobj_attr.attr, &transition_kobj_attr.attr, + &version_kobj_attr.attr, &force_kobj_attr.attr, NULL }; @@ -1010,6 +1052,13 @@ int klp_enable_patch(struct klp_patch *patch) mutex_lock(&klp_mutex); + if (patch->version && patch->version <= latest_version) { + pr_err("Livepatch klp_patch.version=%u <= latest_version=%u, not loading\n", + patch->version, latest_version); + mutex_unlock(&klp_mutex); + return -EINVAL; + } + ret = klp_init_patch_early(patch); if (ret) { mutex_unlock(&klp_mutex); @@ -1246,6 +1295,12 @@ static int __init klp_init(void) if (!klp_root_kobj) return -ENOMEM; + ret = sysfs_create_group(klp_root_kobj, &attr_group); + if (ret) { + kobject_put(klp_root_kobj); + return -ENOMEM; + } + return 0; } diff --git a/kernel/livepatch/core.h b/kernel/livepatch/core.h index e6200f38701f..306bd9240c24 100644 --- a/kernel/livepatch/core.h +++ b/kernel/livepatch/core.h @@ -6,6 +6,7 @@ extern struct mutex klp_mutex; extern struct list_head klp_patches; +extern unsigned int latest_version; void klp_free_patch_start(struct klp_patch *patch); void klp_discard_replaced_patches(struct klp_patch *new_patch); diff --git a/kernel/livepatch/transition.c b/kernel/livepatch/transition.c index 183b2086ba03..99aa0d554da6 100644 --- a/kernel/livepatch/transition.c +++ b/kernel/livepatch/transition.c @@ -80,6 +80,7 @@ static void klp_synchronize_transition(void) */ static void klp_complete_transition(void) { + struct klp_patch *patch; struct klp_object *obj; struct klp_func *func; struct task_struct *g, *task; @@ -143,6 +144,11 @@ static void klp_complete_transition(void) pr_notice("'%s': %s complete\n", klp_transition_patch->mod->name, klp_target_state == KLP_PATCHED ? "patching" : "unpatching"); + latest_version = 0; + list_for_each_entry(patch, &klp_patches, list) + if (patch->enabled && patch->version > latest_version) + latest_version = patch->version; + klp_target_state = KLP_UNDEFINED; klp_transition_patch = NULL; } diff --git a/lib/livepatch/Makefile b/lib/livepatch/Makefile index 26900ddaef82..6890dadd4d0e 100644 --- a/lib/livepatch/Makefile +++ b/lib/livepatch/Makefile @@ -8,7 +8,11 @@ obj-$(CONFIG_TEST_LIVEPATCH) += test_klp_atomic_replace.o \ test_klp_callbacks_busy.o \ test_klp_callbacks_mod.o \ test_klp_livepatch.o \ - test_klp_shadow_vars.o + test_klp_shadow_vars.o \ + test_klp_version_0.o \ + test_klp_version_1.o \ + test_klp_version_2.o \ + test_klp_version_3.o # Target modules to be livepatched require CC_FLAGS_FTRACE CFLAGS_test_klp_callbacks_busy.o += $(CC_FLAGS_FTRACE) diff --git a/lib/livepatch/test_klp_version_0.c b/lib/livepatch/test_klp_version_0.c new file mode 100644 index 000000000000..5beb72df1539 --- /dev/null +++ b/lib/livepatch/test_klp_version_0.c @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2019 Joe Lawrence <joe.lawrence@xxxxxxxxxx> + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/livepatch.h> + +static struct klp_object objs[] = { + { } +}; + +static struct klp_patch patch = { + .mod = THIS_MODULE, + .objs = objs, + .version = 0, +}; + +static int test_klp_version_0_init(void) +{ + return klp_enable_patch(&patch); +} + +static void test_klp_version_0_exit(void) +{ +} + +module_init(test_klp_version_0_init); +module_exit(test_klp_version_0_exit); +MODULE_LICENSE("GPL"); +MODULE_INFO(livepatch, "Y"); +MODULE_AUTHOR("Joe Lawrence <joe.lawrence@xxxxxxxxxx>"); +MODULE_DESCRIPTION("Livepatch test: patch versioning, v0"); diff --git a/lib/livepatch/test_klp_version_1.c b/lib/livepatch/test_klp_version_1.c new file mode 100644 index 000000000000..311fd8381b42 --- /dev/null +++ b/lib/livepatch/test_klp_version_1.c @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2019 Joe Lawrence <joe.lawrence@xxxxxxxxxx> + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/livepatch.h> + +static struct klp_object objs[] = { + { } +}; + +static struct klp_patch patch = { + .mod = THIS_MODULE, + .objs = objs, + .version = 1, +}; + +static int test_klp_version_1_init(void) +{ + return klp_enable_patch(&patch); +} + +static void test_klp_version_1_exit(void) +{ +} + +module_init(test_klp_version_1_init); +module_exit(test_klp_version_1_exit); +MODULE_LICENSE("GPL"); +MODULE_INFO(livepatch, "Y"); +MODULE_AUTHOR("Joe Lawrence <joe.lawrence@xxxxxxxxxx>"); +MODULE_DESCRIPTION("Livepatch test: patch versioning, v1"); diff --git a/lib/livepatch/test_klp_version_2.c b/lib/livepatch/test_klp_version_2.c new file mode 100644 index 000000000000..d004f00be705 --- /dev/null +++ b/lib/livepatch/test_klp_version_2.c @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2019 Joe Lawrence <joe.lawrence@xxxxxxxxxx> + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/livepatch.h> + +static struct klp_object objs[] = { + { } +}; + +static struct klp_patch patch = { + .mod = THIS_MODULE, + .objs = objs, + .version = 2, +}; + +static int test_klp_version_2_init(void) +{ + return klp_enable_patch(&patch); +} + +static void test_klp_version_2_exit(void) +{ +} + +module_init(test_klp_version_2_init); +module_exit(test_klp_version_2_exit); +MODULE_LICENSE("GPL"); +MODULE_INFO(livepatch, "Y"); +MODULE_AUTHOR("Joe Lawrence <joe.lawrence@xxxxxxxxxx>"); +MODULE_DESCRIPTION("Livepatch test: patch versioning, v2"); diff --git a/lib/livepatch/test_klp_version_3.c b/lib/livepatch/test_klp_version_3.c new file mode 100644 index 000000000000..b773489d0c24 --- /dev/null +++ b/lib/livepatch/test_klp_version_3.c @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2019 Joe Lawrence <joe.lawrence@xxxxxxxxxx> + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/livepatch.h> + +static struct klp_object objs[] = { + { } +}; + +static struct klp_patch patch = { + .mod = THIS_MODULE, + .objs = objs, + .version = 3, +}; + +static int test_klp_version_3_init(void) +{ + return klp_enable_patch(&patch); +} + +static void test_klp_version_3_exit(void) +{ +} + +module_init(test_klp_version_3_init); +module_exit(test_klp_version_3_exit); +MODULE_LICENSE("GPL"); +MODULE_INFO(livepatch, "Y"); +MODULE_AUTHOR("Joe Lawrence <joe.lawrence@xxxxxxxxxx>"); +MODULE_DESCRIPTION("Livepatch test: patch versioning, v3"); diff --git a/tools/testing/selftests/livepatch/Makefile b/tools/testing/selftests/livepatch/Makefile index af4aee79bebb..d4fa03153dea 100644 --- a/tools/testing/selftests/livepatch/Makefile +++ b/tools/testing/selftests/livepatch/Makefile @@ -3,6 +3,7 @@ TEST_GEN_PROGS := \ test-livepatch.sh \ test-callbacks.sh \ - test-shadow-vars.sh + test-shadow-vars.sh \ + test-versions.sh include ../lib.mk diff --git a/tools/testing/selftests/livepatch/test-versions.sh b/tools/testing/selftests/livepatch/test-versions.sh new file mode 100755 index 000000000000..f8f6636b1874 --- /dev/null +++ b/tools/testing/selftests/livepatch/test-versions.sh @@ -0,0 +1,172 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2019 Joe Lawrence <joe.lawrence@xxxxxxxxxx> + +. $(dirname $0)/functions.sh + +MOD_VERSION_0=test_klp_version_0 +MOD_VERSION_1=test_klp_version_1 +MOD_VERSION_2=test_klp_version_2 +MOD_VERSION_3=test_klp_version_3 + +set_dynamic_debug + +function latest_version_to_dmesg() { + echo "livepatch: latest_version=$(cat /sys/kernel/livepatch/latest_version)" > /dev/kmsg 2> /dev/null +} + +# TEST: increasing livepatch version numbers +# + +echo -n "TEST: increasing livepatch version numbers ... " +dmesg -C + +load_lp $MOD_VERSION_1 +latest_version_to_dmesg + +load_lp $MOD_VERSION_2 +latest_version_to_dmesg + +load_lp $MOD_VERSION_3 +latest_version_to_dmesg + +disable_lp $MOD_VERSION_3 +latest_version_to_dmesg +unload_lp $MOD_VERSION_3 + +disable_lp $MOD_VERSION_2 +latest_version_to_dmesg +unload_lp $MOD_VERSION_2 + +disable_lp $MOD_VERSION_1 +latest_version_to_dmesg +unload_lp $MOD_VERSION_1 + +check_result "% modprobe $MOD_VERSION_1 +livepatch: enabling patch '$MOD_VERSION_1' +livepatch: '$MOD_VERSION_1': initializing patching transition +livepatch: '$MOD_VERSION_1': starting patching transition +livepatch: '$MOD_VERSION_1': completing patching transition +livepatch: '$MOD_VERSION_1': patching complete +livepatch: latest_version=1 +% modprobe $MOD_VERSION_2 +livepatch: enabling patch '$MOD_VERSION_2' +livepatch: '$MOD_VERSION_2': initializing patching transition +livepatch: '$MOD_VERSION_2': starting patching transition +livepatch: '$MOD_VERSION_2': completing patching transition +livepatch: '$MOD_VERSION_2': patching complete +livepatch: latest_version=2 +% modprobe $MOD_VERSION_3 +livepatch: enabling patch '$MOD_VERSION_3' +livepatch: '$MOD_VERSION_3': initializing patching transition +livepatch: '$MOD_VERSION_3': starting patching transition +livepatch: '$MOD_VERSION_3': completing patching transition +livepatch: '$MOD_VERSION_3': patching complete +livepatch: latest_version=3 +% echo 0 > /sys/kernel/livepatch/$MOD_VERSION_3/enabled +livepatch: '$MOD_VERSION_3': initializing unpatching transition +livepatch: '$MOD_VERSION_3': starting unpatching transition +livepatch: '$MOD_VERSION_3': completing unpatching transition +livepatch: '$MOD_VERSION_3': unpatching complete +livepatch: latest_version=2 +% rmmod $MOD_VERSION_3 +% echo 0 > /sys/kernel/livepatch/$MOD_VERSION_2/enabled +livepatch: '$MOD_VERSION_2': initializing unpatching transition +livepatch: '$MOD_VERSION_2': starting unpatching transition +livepatch: '$MOD_VERSION_2': completing unpatching transition +livepatch: '$MOD_VERSION_2': unpatching complete +livepatch: latest_version=1 +% rmmod $MOD_VERSION_2 +% echo 0 > /sys/kernel/livepatch/$MOD_VERSION_1/enabled +livepatch: '$MOD_VERSION_1': initializing unpatching transition +livepatch: '$MOD_VERSION_1': starting unpatching transition +livepatch: '$MOD_VERSION_1': completing unpatching transition +livepatch: '$MOD_VERSION_1': unpatching complete +livepatch: latest_version=0 +% rmmod $MOD_VERSION_1" + + +# TEST: verify version 0 can be loaded anytime +# + +echo -n "TEST: verify version 0 can be loaded anytime ... " +dmesg -C + +load_lp $MOD_VERSION_2 +latest_version_to_dmesg + +load_lp $MOD_VERSION_0 +latest_version_to_dmesg + +disable_lp $MOD_VERSION_0 +latest_version_to_dmesg +unload_lp $MOD_VERSION_0 + +disable_lp $MOD_VERSION_2 +latest_version_to_dmesg +unload_lp $MOD_VERSION_2 + +check_result "% modprobe $MOD_VERSION_2 +livepatch: enabling patch '$MOD_VERSION_2' +livepatch: '$MOD_VERSION_2': initializing patching transition +livepatch: '$MOD_VERSION_2': starting patching transition +livepatch: '$MOD_VERSION_2': completing patching transition +livepatch: '$MOD_VERSION_2': patching complete +livepatch: latest_version=2 +% modprobe $MOD_VERSION_0 +livepatch: enabling patch '$MOD_VERSION_0' +livepatch: '$MOD_VERSION_0': initializing patching transition +livepatch: '$MOD_VERSION_0': starting patching transition +livepatch: '$MOD_VERSION_0': completing patching transition +livepatch: '$MOD_VERSION_0': patching complete +livepatch: latest_version=2 +% echo 0 > /sys/kernel/livepatch/$MOD_VERSION_0/enabled +livepatch: '$MOD_VERSION_0': initializing unpatching transition +livepatch: '$MOD_VERSION_0': starting unpatching transition +livepatch: '$MOD_VERSION_0': completing unpatching transition +livepatch: '$MOD_VERSION_0': unpatching complete +livepatch: latest_version=2 +% rmmod $MOD_VERSION_0 +% echo 0 > /sys/kernel/livepatch/$MOD_VERSION_2/enabled +livepatch: '$MOD_VERSION_2': initializing unpatching transition +livepatch: '$MOD_VERSION_2': starting unpatching transition +livepatch: '$MOD_VERSION_2': completing unpatching transition +livepatch: '$MOD_VERSION_2': unpatching complete +livepatch: latest_version=0 +% rmmod $MOD_VERSION_2" + + +# TEST: verify lower versions can't be loaded +# + +echo -n "TEST: verify lower versions can't be loaded ... " +dmesg -C + +load_lp $MOD_VERSION_2 +latest_version_to_dmesg + +load_failing_mod $MOD_VERSION_1 +latest_version_to_dmesg + +disable_lp $MOD_VERSION_2 +latest_version_to_dmesg +unload_lp $MOD_VERSION_2 + +check_result "% modprobe $MOD_VERSION_2 +livepatch: enabling patch '$MOD_VERSION_2' +livepatch: '$MOD_VERSION_2': initializing patching transition +livepatch: '$MOD_VERSION_2': starting patching transition +livepatch: '$MOD_VERSION_2': completing patching transition +livepatch: '$MOD_VERSION_2': patching complete +livepatch: latest_version=2 +% modprobe $MOD_VERSION_1 +livepatch: Livepatch klp_patch.version=1 <= latest_version=2, not loading +modprobe: ERROR: could not insert '$MOD_VERSION_1': Invalid argument +livepatch: latest_version=2 +% echo 0 > /sys/kernel/livepatch/$MOD_VERSION_2/enabled +livepatch: '$MOD_VERSION_2': initializing unpatching transition +livepatch: '$MOD_VERSION_2': starting unpatching transition +livepatch: '$MOD_VERSION_2': completing unpatching transition +livepatch: '$MOD_VERSION_2': unpatching complete +livepatch: latest_version=0 +% rmmod $MOD_VERSION_2" -- 2.20.1