There are scenarios where it may not be safe to disable a livepatch once it has been enabled. Add a 'sticky' bool to struct klp_patch so that livepatches can indicate this to the core. If set, the livepatch may not be directly disabled/unloaded. Signed-off-by: Joe Lawrence <joe.lawrence@xxxxxxxxxx> --- .../ABI/testing/sysfs-kernel-livepatch | 7 +++ Documentation/livepatch/sticky-patches.txt | 27 ++++++++ include/linux/livepatch.h | 2 + kernel/livepatch/core.c | 13 +++- lib/livepatch/Makefile | 2 + lib/livepatch/test_klp_sticky_0.c | 43 +++++++++++++ lib/livepatch/test_klp_sticky_1.c | 43 +++++++++++++ tools/testing/selftests/livepatch/Makefile | 1 + .../selftests/livepatch/test-sticky.sh | 61 +++++++++++++++++++ 9 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 Documentation/livepatch/sticky-patches.txt create mode 100644 lib/livepatch/test_klp_sticky_0.c create mode 100644 lib/livepatch/test_klp_sticky_1.c create mode 100755 tools/testing/selftests/livepatch/test-sticky.sh diff --git a/Documentation/ABI/testing/sysfs-kernel-livepatch b/Documentation/ABI/testing/sysfs-kernel-livepatch index bc4475062446..18282aaa5ca9 100644 --- a/Documentation/ABI/testing/sysfs-kernel-livepatch +++ b/Documentation/ABI/testing/sysfs-kernel-livepatch @@ -32,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>/sticky +Date: Feb 2019 +KernelVersion: 5.0.0 +Contact: live-patching@xxxxxxxxxxxxxxx +Description: + Is this livepatch not disable-able (bool). + What: /sys/kernel/livepatch/<patch>/version Date: Feb 2019 KernelVersion: 5.0.0 diff --git a/Documentation/livepatch/sticky-patches.txt b/Documentation/livepatch/sticky-patches.txt new file mode 100644 index 000000000000..b23430a6fcba --- /dev/null +++ b/Documentation/livepatch/sticky-patches.txt @@ -0,0 +1,27 @@ +============== +Sticky Patches +============== + +Livepatches may introduce new behavior or changes to in-memory data +structures such that it would be unsafe to disable the livepatch. Newer +versions of the livepatch that account for such changes may be safe to +supplement or replace the original patch. In these instances, the +original livepatch should set its klp_patch 'sticky' bool to true. + +If klp.patch.sticky is set, the livepatching core will: + + - Always load the livepatch, given it meets all the usual livepatch + requirements. + + - Allow the patch-enable transition to be reversed. + + - Once the patch has fully transitioned, however, it will no longer + have the ability to be directly disabled. + + - Subsequent livepatches may be loaded, regardless of whether they are + atomic-replace cumulative patches or not. + + - If a subsequent atomic-replace patch is loaded that doesn't set + klp.sticky, it may be disabled and unloaded. The net effect is that + the original sticky livepatch will be replaced and then its + replacement removed. diff --git a/include/linux/livepatch.h b/include/linux/livepatch.h index 70d5354e11a8..a33ce73015ba 100644 --- a/include/linux/livepatch.h +++ b/include/linux/livepatch.h @@ -151,6 +151,7 @@ struct klp_object { * @mod: reference to the live patch module * @objs: object entries for kernel objects to be patched * @version: patch version number + * @sticky: patch can be replaced, but not disabled * @replace: replace all actively used patches * @list: list node for global list of actively used patches * @kobj: kobject for sysfs resources @@ -166,6 +167,7 @@ struct klp_patch { struct module *mod; struct klp_object *objs; unsigned int version; + bool sticky; bool replace; /* internal */ diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c index cc120b5018e1..50bf39ec2a9d 100644 --- a/kernel/livepatch/core.c +++ b/kernel/livepatch/core.c @@ -380,7 +380,7 @@ static ssize_t enabled_store(struct kobject *kobj, struct kobj_attribute *attr, */ if (patch == klp_transition_patch) klp_reverse_transition(); - else if (!enabled) + else if (!enabled && !patch->sticky) ret = __klp_disable_patch(patch); else ret = -EINVAL; @@ -412,6 +412,15 @@ static ssize_t transition_show(struct kobject *kobj, patch == klp_transition_patch); } +static ssize_t sticky_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->sticky); +} + static ssize_t version_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { @@ -452,11 +461,13 @@ 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 sticky_kobj_attr = __ATTR_RO(sticky); 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, + &sticky_kobj_attr.attr, &version_kobj_attr.attr, &force_kobj_attr.attr, NULL diff --git a/lib/livepatch/Makefile b/lib/livepatch/Makefile index 6890dadd4d0e..7b5f9f17efea 100644 --- a/lib/livepatch/Makefile +++ b/lib/livepatch/Makefile @@ -9,6 +9,8 @@ obj-$(CONFIG_TEST_LIVEPATCH) += test_klp_atomic_replace.o \ test_klp_callbacks_mod.o \ test_klp_livepatch.o \ test_klp_shadow_vars.o \ + test_klp_sticky_0.o \ + test_klp_sticky_1.o \ test_klp_version_0.o \ test_klp_version_1.o \ test_klp_version_2.o \ diff --git a/lib/livepatch/test_klp_sticky_0.c b/lib/livepatch/test_klp_sticky_0.c new file mode 100644 index 000000000000..50164ad94207 --- /dev/null +++ b/lib/livepatch/test_klp_sticky_0.c @@ -0,0 +1,43 @@ +// 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 int replace; +module_param(replace, int, 0644); +MODULE_PARM_DESC(replace, "replace (default=0)"); + +static int klp_sticky; +module_param(klp_sticky, int, 0644); +MODULE_PARM_DESC(klp_sticky, "klp_sticky (default=0)"); + +static struct klp_object objs[] = { + { } +}; + +static struct klp_patch patch = { + .mod = THIS_MODULE, + .objs = objs, +}; + +static int test_klp_version_0_init(void) +{ + patch.replace = replace; + patch.sticky = klp_sticky; + 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: sticky patch #0"); diff --git a/lib/livepatch/test_klp_sticky_1.c b/lib/livepatch/test_klp_sticky_1.c new file mode 100644 index 000000000000..d6bfd47555b8 --- /dev/null +++ b/lib/livepatch/test_klp_sticky_1.c @@ -0,0 +1,43 @@ +// 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 int replace; +module_param(replace, int, 0644); +MODULE_PARM_DESC(replace, "replace (default=0)"); + +static int klp_sticky; +module_param(klp_sticky, int, 0644); +MODULE_PARM_DESC(klp_sticky, "klp_sticky (default=0)"); + +static struct klp_object objs[] = { + { } +}; + +static struct klp_patch patch = { + .mod = THIS_MODULE, + .objs = objs, +}; + +static int test_klp_version_0_init(void) +{ + patch.replace = replace; + patch.sticky = klp_sticky; + 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: sticky patch #1"); diff --git a/tools/testing/selftests/livepatch/Makefile b/tools/testing/selftests/livepatch/Makefile index d4fa03153dea..92a9292fc97c 100644 --- a/tools/testing/selftests/livepatch/Makefile +++ b/tools/testing/selftests/livepatch/Makefile @@ -4,6 +4,7 @@ TEST_GEN_PROGS := \ test-livepatch.sh \ test-callbacks.sh \ test-shadow-vars.sh \ + test-sticky.sh \ test-versions.sh include ../lib.mk diff --git a/tools/testing/selftests/livepatch/test-sticky.sh b/tools/testing/selftests/livepatch/test-sticky.sh new file mode 100755 index 000000000000..00d4282af492 --- /dev/null +++ b/tools/testing/selftests/livepatch/test-sticky.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2019 Joe Lawrence <joe.lawrence@xxxxxxxxxx> + +. $(dirname $0)/functions.sh + +MOD_LIVEPATCH_0=test_klp_sticky_0 +MOD_LIVEPATCH_1=test_klp_sticky_1 + +# disable_lp_fail(modname) - disable a livepatch, but expect to fail +# modname - module name to unload +function disable_lp_fail() +{ + local mod="$1"; shift + + log "% echo 0 > /sys/kernel/livepatch/$mod/enabled (expect failure)" + if echo 0 > /sys/kernel/livepatch/"$mod"/enabled 2>/dev/null; then + die "$mod unexpectedly disabled" + fi +} + + +set_dynamic_debug + + +# TEST: sticky livepatch cannot be disabled +# + +echo -n "TEST: sticky livepatch cannot be disabled ... " +dmesg -C + +load_lp $MOD_LIVEPATCH_0 replace=0 klp_sticky=1 +disable_lp_fail $MOD_LIVEPATCH_0 +load_lp $MOD_LIVEPATCH_1 replace=1 klp_sticky=0 +disable_lp $MOD_LIVEPATCH_1 +unload_lp $MOD_LIVEPATCH_1 +unload_lp $MOD_LIVEPATCH_0 + +check_result "% modprobe $MOD_LIVEPATCH_0 replace=0 klp_sticky=1 +livepatch: enabling patch '$MOD_LIVEPATCH_0' +livepatch: '$MOD_LIVEPATCH_0': initializing patching transition +livepatch: '$MOD_LIVEPATCH_0': starting patching transition +livepatch: '$MOD_LIVEPATCH_0': completing patching transition +livepatch: '$MOD_LIVEPATCH_0': patching complete +% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH_0/enabled (expect failure) +% modprobe $MOD_LIVEPATCH_1 replace=1 klp_sticky=0 +livepatch: enabling patch '$MOD_LIVEPATCH_1' +livepatch: '$MOD_LIVEPATCH_1': initializing patching transition +livepatch: '$MOD_LIVEPATCH_1': starting patching transition +livepatch: '$MOD_LIVEPATCH_1': completing patching transition +livepatch: '$MOD_LIVEPATCH_1': patching complete +% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH_1/enabled +livepatch: '$MOD_LIVEPATCH_1': initializing unpatching transition +livepatch: '$MOD_LIVEPATCH_1': starting unpatching transition +livepatch: '$MOD_LIVEPATCH_1': completing unpatching transition +livepatch: '$MOD_LIVEPATCH_1': unpatching complete +% rmmod $MOD_LIVEPATCH_1 +% rmmod $MOD_LIVEPATCH_0" + + +exit 0 -- 2.20.1