Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752683AbdGLOKZ (ORCPT ); Wed, 12 Jul 2017 10:10:25 -0400 Received: from mx1.redhat.com ([209.132.183.28]:49982 "EHLO mx1.redhat.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752579AbdGLOKX (ORCPT ); Wed, 12 Jul 2017 10:10:23 -0400 DMARC-Filter: OpenDMARC Filter v1.3.2 mx1.redhat.com E04307CE16 Authentication-Results: ext-mx04.extmail.prod.ext.phx2.redhat.com; dmarc=none (p=none dis=none) header.from=redhat.com Authentication-Results: ext-mx04.extmail.prod.ext.phx2.redhat.com; spf=pass smtp.mailfrom=joe.lawrence@redhat.com DKIM-Filter: OpenDKIM Filter v2.11.0 mx1.redhat.com E04307CE16 From: Joe Lawrence To: live-patching@vger.kernel.org, linux-kernel@vger.kernel.org Cc: Josh Poimboeuf , Jessica Yu , Jiri Kosina , Miroslav Benes , Petr Mladek , Chris J Arges Subject: [PATCH] livepatch: add (un)patch hooks Date: Wed, 12 Jul 2017 10:10:00 -0400 Message-Id: <1499868600-10176-2-git-send-email-joe.lawrence@redhat.com> In-Reply-To: <1499868600-10176-1-git-send-email-joe.lawrence@redhat.com> References: <1499868600-10176-1-git-send-email-joe.lawrence@redhat.com> X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.28]); Wed, 12 Jul 2017 14:10:23 +0000 (UTC) Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 13679 Lines: 459 When the livepatch core executes klp_(un)patch_object, call out to a livepatch-module specified array of callback hooks. These hooks provide a notification mechanism for livepatch modules when klp_objects are (un)patching. This may be most interesting when another kernel module is a klp_object target and the livepatch module needs to execute code after the target is loaded, but before its module_init code is run. The patch-hook executes right before patching objects and the unpatch-hook executes right after unpatching objects. Signed-off-by: Joe Lawrence --- Documentation/livepatch/hooks.txt | 98 +++++++++++++++++++++++++ include/linux/livepatch.h | 32 ++++++++ kernel/livepatch/core.c | 5 -- kernel/livepatch/patch.c | 35 +++++++++ samples/livepatch/Makefile | 2 + samples/livepatch/livepatch-hooks-demo.c | 122 +++++++++++++++++++++++++++++++ samples/livepatch/livepatch-hooks-mod.c | 38 ++++++++++ 7 files changed, 327 insertions(+), 5 deletions(-) create mode 100644 Documentation/livepatch/hooks.txt create mode 100644 samples/livepatch/livepatch-hooks-demo.c create mode 100644 samples/livepatch/livepatch-hooks-mod.c diff --git a/Documentation/livepatch/hooks.txt b/Documentation/livepatch/hooks.txt new file mode 100644 index 000000000000..ef18101a3b90 --- /dev/null +++ b/Documentation/livepatch/hooks.txt @@ -0,0 +1,98 @@ +(Un)patching Hooks +================== + +Livepatching (un)patch-hooks provide a mechanism to register and execute +a set of callback functions when the kernel's livepatching core performs +an (un)patching operation on a given kernel object. + +The hooks are provided and registered by a livepatch module as part of +klp_objects that make up its klp_patch structure. Both patch and +unpatch-hook function signatures accept a pointer to a klp_object +argument and return an integer status, ie: + + static int patch_hook(struct klp_object *obj) + { + /* ... */ + } + static int unpatch_hook(struct klp_object *obj) + { + /* ... */ + } + + static struct klp_hook patch_hooks[] = { + { + .hook = patch_hook, + }, { } + }; + static struct klp_hook unpatch_hooks[] = { + { + .hook = unpatch_hook, + }, { } + }; + + static struct klp_object objs[] = { + { + /* ... */ + .patch_hooks = patch_hooks, + .unpatch_hooks = unpatch_hooks, + }, { } + }; + + static struct klp_patch patch = { + .mod = THIS_MODULE, + .objs = objs, + }; + +If a hook returns non-zero status, the livepatching core will log a +hook failure warning message. + +Multiple (un)patch-hooks may be registered per klp_object. Each hook +will execute regardless of any previously executed hook's non-zero +return status. + +Hooks are optional. The livepatching core will not execute any +callbacks for an empty klp_hook.hook array or a NULL klp_hook.hook +value. + + +For module targets +------------------ + +In the case of kernel module objects, patch-hooks provide a livepatch +module opportunity to defer execution until a target module is loaded. +Similarly, unpatch-hooks only call back into a livepatch module after a +target module has itself cleaned up. In these cases, the order of +execution looks like: + + load kernel module + execute all patch_hooks[] for this kernel object + livepatch kernel object + execute module_init function + + ... + + unload kernel module + execute module_exit function + livepatch restore kernel object + execute all unpatch_hooks[] for this kernel object + +On the other hand, if a target kernel module is already present when a +livepatch is loading, then the corresponding patch hook(s) will execute +as soon as the livepatching kernel core enables the livepatch. + +It may be useful for hooks to inspect the module state of the klp_object +it is passed (i.e. obj->mod->state). Patch hooks can expect to see +modules in MODULE_STATE_LIVE and MODULE_STATE_COMING states. Unpatch +hooks can expect modules in MODULE_STATE_LIVE and MODULE_STATE_GOING +states. + + +For vmlinux target +------------------ + +As the kernel is always loaded, patch-hooks for vmlinux will execute as +soon as the livepatch core enables the livepatch. Patch-hooks will also +run if the livepatch is disabled and then re-enabled. + +Unpatch-hooks for vmlinux will only execute when the livepatch is +disabled. diff --git a/include/linux/livepatch.h b/include/linux/livepatch.h index 194991ef9347..d95050386ac7 100644 --- a/include/linux/livepatch.h +++ b/include/linux/livepatch.h @@ -87,10 +87,23 @@ struct klp_func { bool transition; }; +struct klp_object; + +/** + * struct klp_hook - hook structure for live patching + * @hook: function to be executed on hook + * + */ +struct klp_hook { + int (*hook)(struct klp_object *obj); +}; + /** * struct klp_object - kernel object structure for live patching * @name: module name (or NULL for vmlinux) * @funcs: function entries for functions to be patched in the object + * @patch_hooks: functions to be executed on patching + * @unpatch_hooks: functions to be executed on unpatching * @kobj: kobject for sysfs resources * @mod: kernel module associated with the patched object * (NULL for vmlinux) @@ -100,6 +113,8 @@ struct klp_object { /* external */ const char *name; struct klp_func *funcs; + struct klp_hook *patch_hooks; + struct klp_hook *unpatch_hooks; /* internal */ struct kobject kobj; @@ -108,6 +123,17 @@ struct klp_object { }; /** + * klp_is_module() - is klp_object a module? + * @obj: klp_object pointer + * + * Return: true if klp_object is a loadable module + */ +static inline bool klp_is_module(struct klp_object *obj) +{ + return obj->name; +} + +/** * struct klp_patch - patch structure for live patching * @mod: reference to the live patch module * @objs: object entries for kernel objects to be patched @@ -138,6 +164,12 @@ struct klp_patch { func->old_name || func->new_func || func->old_sympos; \ func++) +#define klp_for_each_patch_hook(obj, hook) \ + for (hook = obj->patch_hooks; hook && hook->hook; hook++) + +#define klp_for_each_unpatch_hook(obj, hook) \ + for (hook = obj->unpatch_hooks; hook && hook->hook; hook++) + int klp_register_patch(struct klp_patch *); int klp_unregister_patch(struct klp_patch *); int klp_enable_patch(struct klp_patch *); diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c index b9628e43c78f..ff3685470057 100644 --- a/kernel/livepatch/core.c +++ b/kernel/livepatch/core.c @@ -49,11 +49,6 @@ static struct kobject *klp_root_kobj; -static bool klp_is_module(struct klp_object *obj) -{ - return obj->name; -} - static bool klp_is_object_loaded(struct klp_object *obj) { return !obj->name || obj->mod; diff --git a/kernel/livepatch/patch.c b/kernel/livepatch/patch.c index 52c4e907c14b..c8084a18ddb7 100644 --- a/kernel/livepatch/patch.c +++ b/kernel/livepatch/patch.c @@ -235,25 +235,60 @@ static int klp_patch_func(struct klp_func *func) return ret; } +/** + * klp_run_hook - execute a given klp_hook callback + * @hook: callback hook + * @obj: kernel object that has been hooked + * + * Return: return value from hook, or 0 if none is currently associated + */ +static int klp_run_hook(struct klp_hook *hook, struct klp_object *obj) +{ + if (hook && hook->hook) + return (*hook->hook)(obj); + + return 0; +} + void klp_unpatch_object(struct klp_object *obj) { struct klp_func *func; + struct klp_hook *hook; + int ret; klp_for_each_func(obj, func) if (func->patched) klp_unpatch_func(func); obj->patched = false; + + klp_for_each_unpatch_hook(obj, hook) { + ret = klp_run_hook(hook, obj); + if (ret) { + pr_warn("unpatch hook '%p' failed for object '%s'\n", + hook, klp_is_module(obj) ? obj->name : "vmlinux"); + } + } + } int klp_patch_object(struct klp_object *obj) { struct klp_func *func; + struct klp_hook *hook; int ret; if (WARN_ON(obj->patched)) return -EINVAL; + klp_for_each_patch_hook(obj, hook) { + ret = klp_run_hook(hook, obj); + if (ret) { + pr_warn("patch hook '%p' failed for object '%s'\n", + hook, klp_is_module(obj) ? obj->name : "vmlinux"); + } + } + klp_for_each_func(obj, func) { ret = klp_patch_func(func); if (ret) { diff --git a/samples/livepatch/Makefile b/samples/livepatch/Makefile index 10319d7ea0b1..2568a56ba8f3 100644 --- a/samples/livepatch/Makefile +++ b/samples/livepatch/Makefile @@ -1 +1,3 @@ obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch-sample.o +obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch-hooks-demo.o +obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch-hooks-mod.o diff --git a/samples/livepatch/livepatch-hooks-mod.c b/samples/livepatch/livepatch-hooks-mod.c new file mode 100644 index 000000000000..f4ec09a5fc53 --- /dev/null +++ b/samples/livepatch/livepatch-hooks-mod.c @@ -0,0 +1,38 @@ +/* + * livepatch-hooks-mod.c - (un)patching hooks demo support module + * + * Copyright (C) 2017 Joe Lawrence + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include + +static int livepatch_hooks_mod_init(void) +{ + pr_info("%s\n", __func__); + return 0; +} + +static void livepatch_hooks_mod_exit(void) +{ + pr_info("%s\n", __func__); +} + +module_init(livepatch_hooks_mod_init); +module_exit(livepatch_hooks_mod_exit); +MODULE_LICENSE("GPL"); diff --git a/samples/livepatch/livepatch-hooks-demo.c b/samples/livepatch/livepatch-hooks-demo.c new file mode 100644 index 000000000000..672a749a0549 --- /dev/null +++ b/samples/livepatch/livepatch-hooks-demo.c @@ -0,0 +1,122 @@ +/* + * livepatch-hooks-demo.c - (un)patching hooks livepatch demo + * + * Copyright (C) 2017 Joe Lawrence + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include + +const char *module_state[] = { + [MODULE_STATE_LIVE] = "[MODULE_STATE_LIVE] Normal state", + [MODULE_STATE_COMING] = "[MODULE_STATE_COMING] Full formed, running module_init", + [MODULE_STATE_GOING] = "[MODULE_STATE_GOING] Going away", + [MODULE_STATE_UNFORMED] = "[MODULE_STATE_UNFORMED] Still setting it up", +}; + +static void hook_info(const char *hook, struct klp_object *obj) +{ + if (klp_is_module(obj)) + pr_info("%s: %s\n", hook, module_state[obj->mod->state]); + else + pr_info("%s: vmlinux\n", hook); +} + +/* Executed on object patching (ie, patch enablement) */ +static int patch_hook(struct klp_object *obj) +{ + hook_info(__func__, obj); + return 0; +} + +/* Executed on object unpatching (ie, patch disablement) */ +static int unpatch_hook(struct klp_object *obj) +{ + hook_info(__func__, obj); + return 0; +} + +static struct klp_func funcs[] = { + { } +}; + +static struct klp_hook patch_hooks[] = { + { + .hook = patch_hook, + }, { } +}; +static struct klp_hook unpatch_hooks[] = { + { + .hook = unpatch_hook, + }, { } +}; + +static struct klp_object objs[] = { + { + .name = "livepatch_hooks_mod", + .funcs = funcs, + .patch_hooks = patch_hooks, + .unpatch_hooks = unpatch_hooks, + }, { } +}; + +static struct klp_patch patch = { + .mod = THIS_MODULE, + .objs = objs, +}; + +static int livepatch_hooks_demo_init(void) +{ + int ret; + + if (!klp_have_reliable_stack() && !patch.immediate) { + /* + * WARNING: Be very careful when using 'patch.immediate' in + * your patches. It's ok to use it for simple patches like + * this, but for more complex patches which change function + * semantics, locking semantics, or data structures, it may not + * be safe. Use of this option will also prevent removal of + * the patch. + * + * See Documentation/livepatch/livepatch.txt for more details. + */ + patch.immediate = true; + pr_notice("The consistency model isn't supported for your architecture. Bypassing safety mechanisms and applying the patch immediately.\n"); + } + + ret = klp_register_patch(&patch); + if (ret) + return ret; + ret = klp_enable_patch(&patch); + if (ret) { + WARN_ON(klp_unregister_patch(&patch)); + return ret; + } + return 0; +} + +static void livepatch_hooks_demo_exit(void) +{ + WARN_ON(klp_unregister_patch(&patch)); +} + +module_init(livepatch_hooks_demo_init); +module_exit(livepatch_hooks_demo_exit); +MODULE_LICENSE("GPL"); +MODULE_INFO(livepatch, "Y"); -- 1.8.3.1