From nobody Tue Dec 16 07:09:08 2025 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 6EC21C4332F for ; Fri, 10 Nov 2023 17:42:19 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1344902AbjKJRmU (ORCPT ); Fri, 10 Nov 2023 12:42:20 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:46692 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S234882AbjKJRlt (ORCPT ); Fri, 10 Nov 2023 12:41:49 -0500 Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.220.29]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 89CC542C3C; Fri, 10 Nov 2023 09:05:06 -0800 (PST) Received: from relay2.suse.de (relay2.suse.de [149.44.160.134]) by smtp-out2.suse.de (Postfix) with ESMTP id 49B871F8BB; Fri, 10 Nov 2023 17:05:05 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1699635905; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=ojdUKEsAIrqTJ0c75S6e7/nz7HiBRgbaDZ3Fw43TrDk=; b=fLXFEINxO320OU0b8WGezlJG9FPN24JvmElIR1mJJ+X3NyVAVy7SbZHp8vlGMFQ9h20HHC GArKtx+0n1PQJl+3kVWIzfW6+ovagm/qyoK0hvnI4arWW/HKgSqvsTGLLYVc6Gtta0bcqc 8Ng6v+kTDfvGBDuSkdQ7gWck7BRRLFo= Received: from alley.suse.cz (pmladek.udp.ovpn2.prg.suse.de [10.100.201.202]) by relay2.suse.de (Postfix) with ESMTP id DD21D2C24F; Fri, 10 Nov 2023 17:05:04 +0000 (UTC) From: Petr Mladek To: Josh Poimboeuf , Miroslav Benes Cc: Joe Lawrence , Nicolai Stange , live-patching@vger.kernel.org, linux-kernel@vger.kernel.org, Petr Mladek Subject: [POC 1/7] livepatch: Add callbacks for introducing and removing states Date: Fri, 10 Nov 2023 18:04:22 +0100 Message-Id: <20231110170428.6664-2-pmladek@suse.com> X-Mailer: git-send-email 2.35.3 In-Reply-To: <20231110170428.6664-1-pmladek@suse.com> References: <20231110170428.6664-1-pmladek@suse.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" The basic livepatch functionality is to redirect problematic functions to a fixed or improved variants. In addition, there are two features helping with more problematic situations: + pre_patch(), post_patch(), pre_unpatch(), post_unpatch() callbacks might be called before and after the respective transitions. For example, post_patch() callback might enable some functionality at the end of the transition when the entire system is using the new code. + Shadow variables allow to add new items into structures or other data objects. The practice has shown that these features were hard to use with the atomic replace feature. The new livepatch usually just adds more fixes. But it might also remove problematic ones. Originally, any version of the livepatch was allowed to replace any older or newer version of the patch. It was not clear how to handle the extra features. The new patch did not know whether to run the callbacks or if the changes were already done by the current livepatch. Or if it has to revert some changes or free shadow variables whey they would not longer be supported. It was even more complicated because only the callbacks from the newly installed livepatch were called. It means that older livepatch might not be able to revert changes supported only by newer livepatches. The above problems were supposed to be solved by adding livepatch states. Each livepatch might define which states are supported. The states are versioned. The livepatch core checks if the newly installed livepatch is able to handle all states used by the currently installed livepatch. Though the practice has shown that the states API was not easy to use either. It was not well connected with the callbacks and shadow variables. The states are per-patch. The callbacks are per-object. The livepatch does not know about the supported shadow variables at all. As a first step, new per-state callbacks are introduced: + "setup" is called before the livepatch is applied but only when the state is new. It might be used to allocate some memory. Or it might check if the state change is safe on the running system. If it fails, the patch will not be enabled. + "enable" is called after the livepatch is applied but only when the state is new. It might be used to enable using some functionality provided by the livepatch after the entire system is livepatched. + "disable" is called before the livepatch is disabled or replaced. When replacing, the callback is called only when the new livepatch does not support the related state. And it uses the implementation from the to-be-replaced livepatch. The to-be-replaced livepatch needed the callback to allow disabling the livepatch anyway. The new livepatch does not need to know anything about the state. It might be used to disable using some functionality which will not longer be supported after the livepatch gets disabled. + "release" is called after the livepatch was disabled or replaced. There are the same rules for replacement as for "disable" callback. It might be used for freeing some memory or unused shadow variables. These callbacks are going to replace the existing ones. It would cause some changes: + The new callbacks are not called when a livepatched object is loaded or removed later. The practice shows that per-object callbacks are not worth supporting. In a rare case, when a per-object callback is needed. the livepatch might register a custom module notifier. + The new callbacks are called only when the state is introduced or removed. It does not handle the situation when the newly installed livepatch continues using an existing state. The practice shows that this is exactly what is needed. In the rare case when this is not enough, an extra takeover might be done in the module->init() callback. The per-state callbacks are called in similar code paths as the per-object ones. Especially, the ordering against the other operations is the same. Though, there are some obvious and less obvious changes: + The per-state callbacks are called for the entire patch instead of loaded object. It means that they called outside the for-each-object cycle. + The per-state callbacks are called when a state is introduced or obsoleted. Both variants might happen when the atomic replace is used. + In __klp_enable_patch(), the per-state callbacks are called before the smp_wmb() while the per-object ones are called later. The new location makes more sense. The setup of the state should be ready before processes start being transferred. The per-object callbacks were called after the barrier. They were using and already existing for-cycle. And nobody did mind about the ordering. Signed-off-by: Petr Mladek --- include/linux/livepatch.h | 28 ++++++++ kernel/livepatch/core.c | 19 +++++- kernel/livepatch/state.c | 118 ++++++++++++++++++++++++++++++++++ kernel/livepatch/state.h | 9 +++ kernel/livepatch/transition.c | 8 +++ 5 files changed, 180 insertions(+), 2 deletions(-) diff --git a/include/linux/livepatch.h b/include/linux/livepatch.h index 9b9b38e89563..c2a39e5f5b66 100644 --- a/include/linux/livepatch.h +++ b/include/linux/livepatch.h @@ -129,15 +129,43 @@ struct klp_object { bool patched; }; =20 +struct klp_patch; +struct klp_state; + +/** + * struct klp_state_callbacks - callbacks manipulating the state + * @setup: executed before code patching when the state is added + * @enable: executed after code patching when the state is added + * @disable: executed before code unpatching when the state is removed + * @release: executed after code unpatching when the state is removed + * @setup_succeeded: internal state used by a rollback on error + * + * All callbacks are optional. + * + * @setup callback returns 0 on success and an error code otherwise. + * Any error prevents enabling the livepatch. @disable() callbacks + * are then called to rollback @enable callbacks which has already + * succeeded before. + */ +struct klp_state_callbacks { + int (*setup)(struct klp_patch *patch, struct klp_state *state); + void (*enable)(struct klp_patch *patch, struct klp_state *state); + void (*disable)(struct klp_patch *patch, struct klp_state *state); + void (*release)(struct klp_patch *patch, struct klp_state *state); + bool setup_succeeded; +}; + /** * struct klp_state - state of the system modified by the livepatch * @id: system state identifier (non-zero) * @version: version of the change + * @callbacks: optional callbacks used when introducing or removing the st= ate * @data: custom data */ struct klp_state { unsigned long id; unsigned int version; + struct klp_state_callbacks callbacks; void *data; }; =20 diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c index 61328328c474..a4a3fe7907ad 100644 --- a/kernel/livepatch/core.c +++ b/kernel/livepatch/core.c @@ -975,6 +975,8 @@ static int __klp_disable_patch(struct klp_patch *patch) =20 klp_init_transition(patch, KLP_UNPATCHED); =20 + klp_disable_states(patch); + klp_for_each_object(patch, obj) if (obj->patched) klp_pre_unpatch_callback(obj); @@ -1010,6 +1012,13 @@ static int __klp_enable_patch(struct klp_patch *patc= h) =20 klp_init_transition(patch, KLP_PATCHED); =20 + ret =3D klp_setup_states(patch); + if (ret) + goto err; + + if (patch->replace) + klp_disable_obsolete_states(patch); + /* * Enforce the order of the func->transition writes in * klp_init_transition() and the ops->func_stack writes in @@ -1027,14 +1036,14 @@ static int __klp_enable_patch(struct klp_patch *pat= ch) if (ret) { pr_warn("pre-patch callback failed for object '%s'\n", klp_is_module(obj) ? obj->name : "vmlinux"); - goto err; + goto err_states; } =20 ret =3D klp_patch_object(obj); if (ret) { pr_warn("failed to patch object '%s'\n", klp_is_module(obj) ? obj->name : "vmlinux"); - goto err; + goto err_states; } } =20 @@ -1043,6 +1052,12 @@ static int __klp_enable_patch(struct klp_patch *patc= h) klp_try_complete_transition(); =20 return 0; + +err_states: + if (patch->replace) + klp_enable_obsolete_states(patch); + + klp_release_states(patch); err: pr_warn("failed to enable patch '%s'\n", patch->mod->name); =20 diff --git a/kernel/livepatch/state.c b/kernel/livepatch/state.c index 2565d039ade0..6693d808106b 100644 --- a/kernel/livepatch/state.c +++ b/kernel/livepatch/state.c @@ -117,3 +117,121 @@ bool klp_is_patch_compatible(struct klp_patch *patch) =20 return true; } + +bool is_state_in_other_patches(struct klp_patch *patch, struct klp_state *= state) +{ + struct klp_patch *old_patch; + struct klp_state *old_state; + + klp_for_each_patch(old_patch) { + if (old_patch =3D=3D patch) + continue; + + klp_for_each_state(old_patch, old_state) { + if (old_state->id =3D=3D state->id) + return true; + } + } + + return false; +} + +int klp_setup_states(struct klp_patch *patch) +{ + struct klp_state *state; + int err; + + klp_for_each_state(patch, state) { + if (!is_state_in_other_patches(patch, state) && + state->callbacks.setup) { + + err =3D state->callbacks.setup(patch, state); + if (err) + goto err; + } + + state->callbacks.setup_succeeded =3D true; + } + + return 0; + +err: + klp_release_states(patch); + return err; +} + +void klp_enable_states(struct klp_patch *patch) +{ + struct klp_state *state; + + klp_for_each_state(patch, state) { + if (is_state_in_other_patches(patch, state)) + continue; + + if (!state->callbacks.enable) + continue; + + state->callbacks.enable(patch, state); + } +} + +void klp_disable_states(struct klp_patch *patch) +{ + struct klp_state *state; + + klp_for_each_state(patch, state) { + if (is_state_in_other_patches(patch, state)) + continue; + + if (!state->callbacks.disable) + continue; + + state->callbacks.disable(patch, state); + } +} + +void klp_release_states(struct klp_patch *patch) +{ + struct klp_state *state; + + klp_for_each_state(patch, state) { + if (is_state_in_other_patches(patch, state)) + continue; + + if (!state->callbacks.release) + continue; + + if (state->callbacks.setup_succeeded) + state->callbacks.release(patch, state); + } +} + +void klp_enable_obsolete_states(struct klp_patch *patch) +{ + struct klp_patch *old_patch; + + klp_for_each_patch(old_patch) { + if (old_patch !=3D patch) + klp_enable_states(old_patch); + } +} + +void klp_disable_obsolete_states(struct klp_patch *patch) +{ + struct klp_patch *old_patch; + + klp_for_each_patch(old_patch) { + if (old_patch !=3D patch) + klp_disable_states(old_patch); + } +} + +void klp_release_obsolete_states(struct klp_patch *patch) +{ + struct klp_patch *old_patch; + + klp_for_each_patch(old_patch) { + if (old_patch !=3D patch) + klp_release_states(old_patch); + } +} diff --git a/kernel/livepatch/state.h b/kernel/livepatch/state.h index 49d9c16e8762..e9940e7f00dd 100644 --- a/kernel/livepatch/state.h +++ b/kernel/livepatch/state.h @@ -5,5 +5,14 @@ #include =20 bool klp_is_patch_compatible(struct klp_patch *patch); +int klp_setup_states(struct klp_patch *patch); +void klp_enable_states(struct klp_patch *patch); +void klp_disable_states(struct klp_patch *patch); +void klp_release_states(struct klp_patch *patch); + +void klp_enable_obsolete_states(struct klp_patch *patch); +void klp_disable_obsolete_states(struct klp_patch *patch); +void klp_release_obsolete_states(struct klp_patch *patch); + =20 #endif /* _LIVEPATCH_STATE_H */ diff --git a/kernel/livepatch/transition.c b/kernel/livepatch/transition.c index e54c3d60a904..cfa1ab10feb7 100644 --- a/kernel/livepatch/transition.c +++ b/kernel/livepatch/transition.c @@ -12,6 +12,7 @@ #include #include "core.h" #include "patch.h" +#include "state.h" #include "transition.h" =20 #define MAX_STACK_ENTRIES 100 @@ -101,6 +102,7 @@ static void klp_complete_transition(void) if (klp_transition_patch->replace && klp_target_state =3D=3D KLP_PATCHED)= { klp_unpatch_replaced_patches(klp_transition_patch); klp_discard_nops(klp_transition_patch); + klp_release_obsolete_states(klp_transition_patch); } =20 if (klp_target_state =3D=3D KLP_UNPATCHED) { @@ -140,6 +142,12 @@ static void klp_complete_transition(void) task->patch_state =3D KLP_UNDEFINED; } =20 + if (klp_target_state =3D=3D KLP_PATCHED) { + klp_enable_states(klp_transition_patch); + } else if (klp_target_state =3D=3D KLP_UNPATCHED) { + klp_release_states(klp_transition_patch); + } + klp_for_each_object(klp_transition_patch, obj) { if (!klp_is_object_loaded(obj)) continue; --=20 2.35.3 From nobody Tue Dec 16 07:09:08 2025 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id E06A5C4332F for ; Fri, 10 Nov 2023 17:43:07 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S235201AbjKJRnG (ORCPT ); Fri, 10 Nov 2023 12:43:06 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:46830 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S235226AbjKJRl6 (ORCPT ); Fri, 10 Nov 2023 12:41:58 -0500 Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.220.29]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id DBD5542C3E; Fri, 10 Nov 2023 09:05:17 -0800 (PST) Received: from relay2.suse.de (relay2.suse.de [149.44.160.134]) by smtp-out2.suse.de (Postfix) with ESMTP id 9F88B1F8BD; Fri, 10 Nov 2023 17:05:16 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1699635916; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=N53A1y0Jr8OxUW9POOJ+wwsplLhd7v++YACla5XuUwE=; b=b/aFKANMsgmPrkb8UudWyFf0Y37BgEKe6iILm4m9mY9pWjN1W50NpINtfQ20FahOVXyBG/ L+MynoEFybrOHRmN4toDBEfro+B5fsn6HKgMuAJ5I4z6k4n2WuVv+sXs6Pxubjd0iWIV6a jLQfUZeOaX6/1Z170mVGwcqAl6r0Pho= Received: from alley.suse.cz (pmladek.udp.ovpn2.prg.suse.de [10.100.201.202]) by relay2.suse.de (Postfix) with ESMTP id 471292C4C5; Fri, 10 Nov 2023 17:05:16 +0000 (UTC) From: Petr Mladek To: Josh Poimboeuf , Miroslav Benes Cc: Joe Lawrence , Nicolai Stange , live-patching@vger.kernel.org, linux-kernel@vger.kernel.org, Petr Mladek Subject: [POC 2/7] livepatch: Allow to handle lifetime of shadow variables using the livepatch state Date: Fri, 10 Nov 2023 18:04:23 +0100 Message-Id: <20231110170428.6664-3-pmladek@suse.com> X-Mailer: git-send-email 2.35.3 In-Reply-To: <20231110170428.6664-1-pmladek@suse.com> References: <20231110170428.6664-1-pmladek@suse.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" The handling of the lifetime of the shadow variables is not easy when the atomic replace is used. The new patch does not know if a shadow variable has already been used by the previous livepatch. Or if there is a shadow variable which will not longer be used. Shadow variables are almost always used together with callbacks. At least @post_unpatch callback is used to free not longer used shadow variables. And sometimes @post_patch and @pre_unpatch callbacks are used to enable or disable the use of the shadow variables. It is needed when the shadow variable can be used only when the entire system is able to handle them. All this gets even more complicated because the original callbacks are called only from the new livepatch when atomic replace is used. Newly created livepatches might be able to handle obsolete shadow variables so the upgrade would work. But older livepatches do not know anything about later introduced shadow variables. They might leak during downgrade. And they might contain outdated information when another upgrade would start using them again. All problems are better solved with the new callbacks associated with a livepatch state. They are called when the state is first introduced and when it gets obsolete. Also the callbacks are called from the patch where the state was supported. So that even downgrade might be safe. Let's make it official. A shadow variable might be associated with a livepatch state by setting the new "state.is_shadow" flag and using the same "id" in both struct klp_shadow and struct klp_state. The shadow variable will then have the same lifetime as the livepatch state. It allows to free obsolete shadow variables automatically without the need to add a callback. The generic callback will free the shadow variables using state->callbacks.shadow_dtor callback when provided. Signed-off-by: Petr Mladek --- include/linux/livepatch.h | 15 ++++++++++----- kernel/livepatch/state.c | 14 ++++++++++---- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/include/linux/livepatch.h b/include/linux/livepatch.h index c2a39e5f5b66..189ec7c6a89f 100644 --- a/include/linux/livepatch.h +++ b/include/linux/livepatch.h @@ -132,12 +132,18 @@ struct klp_object { struct klp_patch; struct klp_state; =20 +typedef int (*klp_shadow_ctor_t)(void *obj, + void *shadow_data, + void *ctor_data); +typedef void (*klp_shadow_dtor_t)(void *obj, void *shadow_data); + /** * struct klp_state_callbacks - callbacks manipulating the state * @setup: executed before code patching when the state is added * @enable: executed after code patching when the state is added * @disable: executed before code unpatching when the state is removed * @release: executed after code unpatching when the state is removed + * @shadow_dtor: destructor for the related shadow variable * @setup_succeeded: internal state used by a rollback on error * * All callbacks are optional. @@ -152,6 +158,7 @@ struct klp_state_callbacks { void (*enable)(struct klp_patch *patch, struct klp_state *state); void (*disable)(struct klp_patch *patch, struct klp_state *state); void (*release)(struct klp_patch *patch, struct klp_state *state); + klp_shadow_dtor_t shadow_dtor; bool setup_succeeded; }; =20 @@ -160,12 +167,15 @@ struct klp_state_callbacks { * @id: system state identifier (non-zero) * @version: version of the change * @callbacks: optional callbacks used when introducing or removing the st= ate + * @is_shadow: the state handles lifetime of a shadow variable + * with the same @id * @data: custom data */ struct klp_state { unsigned long id; unsigned int version; struct klp_state_callbacks callbacks; + bool is_shadow; void *data; }; =20 @@ -240,11 +250,6 @@ static inline bool klp_have_reliable_stack(void) IS_ENABLED(CONFIG_HAVE_RELIABLE_STACKTRACE); } =20 -typedef int (*klp_shadow_ctor_t)(void *obj, - void *shadow_data, - void *ctor_data); -typedef void (*klp_shadow_dtor_t)(void *obj, void *shadow_data); - void *klp_shadow_get(void *obj, unsigned long id); void *klp_shadow_alloc(void *obj, unsigned long id, size_t size, gfp_t gfp_flags, diff --git a/kernel/livepatch/state.c b/kernel/livepatch/state.c index 6693d808106b..4ec65afe3a43 100644 --- a/kernel/livepatch/state.c +++ b/kernel/livepatch/state.c @@ -198,11 +198,17 @@ void klp_release_states(struct klp_patch *patch) if (is_state_in_other_patches(patch, state)) continue; =20 - if (!state->callbacks.release) - continue; - - if (state->callbacks.setup_succeeded) + if (state->callbacks.release && state->callbacks.setup_succeeded) state->callbacks.release(patch, state); + + if (state->is_shadow) + klp_shadow_free_all(state->id, state->callbacks.shadow_dtor); + + /* + * The @release callback is supposed to restore the original + * state before the @setup callback was called. + */ + state->callbacks.setup_succeeded =3D 0; } } =20 --=20 2.35.3 From nobody Tue Dec 16 07:09:08 2025 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 46A69C4332F for ; Fri, 10 Nov 2023 17:42:05 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1344259AbjKJRmF (ORCPT ); Fri, 10 Nov 2023 12:42:05 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:46668 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S234916AbjKJRli (ORCPT ); Fri, 10 Nov 2023 12:41:38 -0500 Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.220.29]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 4A82442C3F; Fri, 10 Nov 2023 09:05:29 -0800 (PST) Received: from relay2.suse.de (relay2.suse.de [149.44.160.134]) by smtp-out2.suse.de (Postfix) with ESMTP id 0CAEA1F8BD; Fri, 10 Nov 2023 17:05:28 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1699635928; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=NtDSSLcV0byG9cvqCWu07Juf5CWHCIaEaSsBJ4qrCCs=; b=p00s/BciTlpwzMJpTTzS7kl5BUhFnhhbNVBw/IB99avja8qAt03zggWx589bLweml9t+Ig 7kzHuIdwzyiYGFrJDNbnEDYIkpqMbCUIgzqndfRMVozdKAjFTy9vVFJAANr960c/HY2K6x UFL+AJudn1jve4UVPVbPVUlozCC6jzo= Received: from alley.suse.cz (pmladek.udp.ovpn2.prg.suse.de [10.100.201.202]) by relay2.suse.de (Postfix) with ESMTP id 99A2F2C4C5; Fri, 10 Nov 2023 17:05:27 +0000 (UTC) From: Petr Mladek To: Josh Poimboeuf , Miroslav Benes Cc: Joe Lawrence , Nicolai Stange , live-patching@vger.kernel.org, linux-kernel@vger.kernel.org, Petr Mladek Subject: [POC 3/7] livepatch: Use per-state callbacks in state API tests Date: Fri, 10 Nov 2023 18:04:24 +0100 Message-Id: <20231110170428.6664-4-pmladek@suse.com> X-Mailer: git-send-email 2.35.3 In-Reply-To: <20231110170428.6664-1-pmladek@suse.com> References: <20231110170428.6664-1-pmladek@suse.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" Recent changes in the livepatch core have allowed to connect states, shadow variables, and callbacks. Use these new features in the state tests. Use the shadow variable API to store the original loglevel. It is better suited for this purpose than directly accessing the .data pointer in state klp_state. Another big advantage is that the shadow variable is preserved when the current patch is replaced by a new version. As a result, there is not need to copy the pointer. Finally, the lifetime of the shadow variable is connected with the lifetime of the state. It is freed automatically when it is not longer supported. This results into the following changes in the code: + Rename CONSOLE_LOGLEVEL_STATE -> CONSOLE_LOGLEVEL_FIX_ID because it will be used also the for shadow variable + Remove the extra code for module coming and going states because the new callback are per-state. + Remove callbacks needed to transfer the pointer between states. + Keep the versioning of the state to prevent downgrade. The problem is artificial because no callbacks are needed to transfer or free the shadow variable anymore. Signed-off-by: Petr Mladek --- lib/livepatch/test_klp_state.c | 113 +++++------ lib/livepatch/test_klp_state2.c | 190 +----------------- .../testing/selftests/livepatch/test-state.sh | 44 ++-- 3 files changed, 76 insertions(+), 271 deletions(-) diff --git a/lib/livepatch/test_klp_state.c b/lib/livepatch/test_klp_state.c index 57a4253acb01..b3d1ee48dfcc 100644 --- a/lib/livepatch/test_klp_state.c +++ b/lib/livepatch/test_klp_state.c @@ -9,108 +9,109 @@ #include #include =20 -#define CONSOLE_LOGLEVEL_STATE 1 -/* Version 1 does not support migration. */ -#define CONSOLE_LOGLEVEL_STATE_VERSION 1 +#define CONSOLE_LOGLEVEL_FIX_ID 1 =20 -static const char *const module_state[] =3D { - [MODULE_STATE_LIVE] =3D "[MODULE_STATE_LIVE] Normal state", - [MODULE_STATE_COMING] =3D "[MODULE_STATE_COMING] Full formed, running mod= ule_init", - [MODULE_STATE_GOING] =3D "[MODULE_STATE_GOING] Going away", - [MODULE_STATE_UNFORMED] =3D "[MODULE_STATE_UNFORMED] Still setting it up", -}; - -static void callback_info(const char *callback, struct klp_object *obj) -{ - if (obj->mod) - pr_info("%s: %s -> %s\n", callback, obj->mod->name, - module_state[obj->mod->state]); - else - pr_info("%s: vmlinux\n", callback); -} +/* + * Version of the state which defines compatibility of livepaches. + * The value is artificial. It set just for testing the compatibility + * checks. In reality, all versions are compatible because all + * the callbacks do nothing and the shadow variable clean up + * is done by the core. + */ +#ifndef CONSOLE_LOGLEVEL_FIX_VERSION +#define CONSOLE_LOGLEVEL_FIX_VERSION 1 +#endif =20 static struct klp_patch patch; =20 static int allocate_loglevel_state(void) { - struct klp_state *loglevel_state; + int *shadow_console_loglevel; =20 - loglevel_state =3D klp_get_state(&patch, CONSOLE_LOGLEVEL_STATE); - if (!loglevel_state) - return -EINVAL; + /* Make sure that the shadow variable does not exist yet. */ + shadow_console_loglevel =3D + klp_shadow_alloc(&console_loglevel, CONSOLE_LOGLEVEL_FIX_ID, + sizeof(*shadow_console_loglevel), GFP_KERNEL, + NULL, NULL); =20 - loglevel_state->data =3D kzalloc(sizeof(console_loglevel), GFP_KERNEL); - if (!loglevel_state->data) + if (!shadow_console_loglevel) { + pr_err("%s: failed to allocated shadow variable for storing original log= level\n", + __func__); return -ENOMEM; + } =20 pr_info("%s: allocating space to store console_loglevel\n", __func__); + return 0; } =20 static void fix_console_loglevel(void) { - struct klp_state *loglevel_state; + int *shadow_console_loglevel; =20 - loglevel_state =3D klp_get_state(&patch, CONSOLE_LOGLEVEL_STATE); - if (!loglevel_state) + shadow_console_loglevel =3D + (int *)klp_shadow_get(&console_loglevel, CONSOLE_LOGLEVEL_FIX_ID); + if (!shadow_console_loglevel) return; =20 pr_info("%s: fixing console_loglevel\n", __func__); - *(int *)loglevel_state->data =3D console_loglevel; + *shadow_console_loglevel =3D console_loglevel; console_loglevel =3D CONSOLE_LOGLEVEL_MOTORMOUTH; } =20 static void restore_console_loglevel(void) { - struct klp_state *loglevel_state; + int *shadow_console_loglevel; =20 - loglevel_state =3D klp_get_state(&patch, CONSOLE_LOGLEVEL_STATE); - if (!loglevel_state) + shadow_console_loglevel =3D + (int *)klp_shadow_get(&console_loglevel, CONSOLE_LOGLEVEL_FIX_ID); + if (!shadow_console_loglevel) return; =20 pr_info("%s: restoring console_loglevel\n", __func__); - console_loglevel =3D *(int *)loglevel_state->data; + console_loglevel =3D *shadow_console_loglevel; } =20 static void free_loglevel_state(void) { - struct klp_state *loglevel_state; + int *shadow_console_loglevel; =20 - loglevel_state =3D klp_get_state(&patch, CONSOLE_LOGLEVEL_STATE); - if (!loglevel_state) + shadow_console_loglevel =3D + (int *)klp_shadow_get(&console_loglevel, CONSOLE_LOGLEVEL_FIX_ID); + if (!shadow_console_loglevel) return; =20 pr_info("%s: freeing space for the stored console_loglevel\n", __func__); - kfree(loglevel_state->data); + klp_shadow_free(&console_loglevel, CONSOLE_LOGLEVEL_FIX_ID, NULL); } =20 -/* Executed on object patching (ie, patch enablement) */ -static int pre_patch_callback(struct klp_object *obj) +/* Executed before patching when the state is new. */ +static int setup_state_callback(struct klp_patch *patch, struct klp_state = *state) { - callback_info(__func__, obj); + pr_info("%s: state %lu\n", __func__, state->id); return allocate_loglevel_state(); } =20 -/* Executed on object unpatching (ie, patch disablement) */ -static void post_patch_callback(struct klp_object *obj) +/* Executed after patching when the state is new. */ +static void enable_state_callback(struct klp_patch *patch, struct klp_stat= e *state) { - callback_info(__func__, obj); + pr_info("%s: state %lu\n", __func__, state->id); fix_console_loglevel(); } =20 -/* Executed on object unpatching (ie, patch disablement) */ -static void pre_unpatch_callback(struct klp_object *obj) +/* Executed before unpatching when the state is obsoleted. */ +static void disable_state_callback(struct klp_patch *patch, struct klp_sta= te *state) { - callback_info(__func__, obj); + pr_info("%s: state %lu\n", __func__, state->id); restore_console_loglevel(); } =20 -/* Executed on object unpatching (ie, patch disablement) */ -static void post_unpatch_callback(struct klp_object *obj) +/* Executed after unpatching when the state is obsoleted. */ +static void release_state_callback(struct klp_patch *patch, struct klp_sta= te *state) { - callback_info(__func__, obj); + pr_info("%s: state %lu\n", __func__, state->id); free_loglevel_state(); } =20 @@ -122,19 +123,19 @@ static struct klp_object objs[] =3D { { .name =3D NULL, /* vmlinux */ .funcs =3D no_funcs, - .callbacks =3D { - .pre_patch =3D pre_patch_callback, - .post_patch =3D post_patch_callback, - .pre_unpatch =3D pre_unpatch_callback, - .post_unpatch =3D post_unpatch_callback, - }, }, { } }; =20 static struct klp_state states[] =3D { { - .id =3D CONSOLE_LOGLEVEL_STATE, - .version =3D CONSOLE_LOGLEVEL_STATE_VERSION, + .id =3D CONSOLE_LOGLEVEL_FIX_ID, + .version =3D CONSOLE_LOGLEVEL_FIX_VERSION, + .callbacks =3D { + .setup =3D setup_state_callback, + .enable =3D enable_state_callback, + .disable =3D disable_state_callback, + .release =3D release_state_callback, + }, }, { } }; =20 diff --git a/lib/livepatch/test_klp_state2.c b/lib/livepatch/test_klp_state= 2.c index c978ea4d5e67..128855764bf8 100644 --- a/lib/livepatch/test_klp_state2.c +++ b/lib/livepatch/test_klp_state2.c @@ -1,191 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2019 SUSE =20 -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#define CONSOLE_LOGLEVEL_FIX_VERSION 2 =20 -#include -#include -#include -#include -#include - -#define CONSOLE_LOGLEVEL_STATE 1 -/* Version 2 supports migration. */ -#define CONSOLE_LOGLEVEL_STATE_VERSION 2 - -static const char *const module_state[] =3D { - [MODULE_STATE_LIVE] =3D "[MODULE_STATE_LIVE] Normal state", - [MODULE_STATE_COMING] =3D "[MODULE_STATE_COMING] Full formed, running mod= ule_init", - [MODULE_STATE_GOING] =3D "[MODULE_STATE_GOING] Going away", - [MODULE_STATE_UNFORMED] =3D "[MODULE_STATE_UNFORMED] Still setting it up", -}; - -static void callback_info(const char *callback, struct klp_object *obj) -{ - if (obj->mod) - pr_info("%s: %s -> %s\n", callback, obj->mod->name, - module_state[obj->mod->state]); - else - pr_info("%s: vmlinux\n", callback); -} - -static struct klp_patch patch; - -static int allocate_loglevel_state(void) -{ - struct klp_state *loglevel_state, *prev_loglevel_state; - - prev_loglevel_state =3D klp_get_prev_state(CONSOLE_LOGLEVEL_STATE); - if (prev_loglevel_state) { - pr_info("%s: space to store console_loglevel already allocated\n", - __func__); - return 0; - } - - loglevel_state =3D klp_get_state(&patch, CONSOLE_LOGLEVEL_STATE); - if (!loglevel_state) - return -EINVAL; - - loglevel_state->data =3D kzalloc(sizeof(console_loglevel), GFP_KERNEL); - if (!loglevel_state->data) - return -ENOMEM; - - pr_info("%s: allocating space to store console_loglevel\n", - __func__); - return 0; -} - -static void fix_console_loglevel(void) -{ - struct klp_state *loglevel_state, *prev_loglevel_state; - - loglevel_state =3D klp_get_state(&patch, CONSOLE_LOGLEVEL_STATE); - if (!loglevel_state) - return; - - prev_loglevel_state =3D klp_get_prev_state(CONSOLE_LOGLEVEL_STATE); - if (prev_loglevel_state) { - pr_info("%s: taking over the console_loglevel change\n", - __func__); - loglevel_state->data =3D prev_loglevel_state->data; - return; - } - - pr_info("%s: fixing console_loglevel\n", __func__); - *(int *)loglevel_state->data =3D console_loglevel; - console_loglevel =3D CONSOLE_LOGLEVEL_MOTORMOUTH; -} - -static void restore_console_loglevel(void) -{ - struct klp_state *loglevel_state, *prev_loglevel_state; - - prev_loglevel_state =3D klp_get_prev_state(CONSOLE_LOGLEVEL_STATE); - if (prev_loglevel_state) { - pr_info("%s: passing the console_loglevel change back to the old livepat= ch\n", - __func__); - return; - } - - loglevel_state =3D klp_get_state(&patch, CONSOLE_LOGLEVEL_STATE); - if (!loglevel_state) - return; - - pr_info("%s: restoring console_loglevel\n", __func__); - console_loglevel =3D *(int *)loglevel_state->data; -} - -static void free_loglevel_state(void) -{ - struct klp_state *loglevel_state, *prev_loglevel_state; - - prev_loglevel_state =3D klp_get_prev_state(CONSOLE_LOGLEVEL_STATE); - if (prev_loglevel_state) { - pr_info("%s: keeping space to store console_loglevel\n", - __func__); - return; - } - - loglevel_state =3D klp_get_state(&patch, CONSOLE_LOGLEVEL_STATE); - if (!loglevel_state) - return; - - pr_info("%s: freeing space for the stored console_loglevel\n", - __func__); - kfree(loglevel_state->data); -} - -/* Executed on object patching (ie, patch enablement) */ -static int pre_patch_callback(struct klp_object *obj) -{ - callback_info(__func__, obj); - return allocate_loglevel_state(); -} - -/* Executed on object unpatching (ie, patch disablement) */ -static void post_patch_callback(struct klp_object *obj) -{ - callback_info(__func__, obj); - fix_console_loglevel(); -} - -/* Executed on object unpatching (ie, patch disablement) */ -static void pre_unpatch_callback(struct klp_object *obj) -{ - callback_info(__func__, obj); - restore_console_loglevel(); -} - -/* Executed on object unpatching (ie, patch disablement) */ -static void post_unpatch_callback(struct klp_object *obj) -{ - callback_info(__func__, obj); - free_loglevel_state(); -} - -static struct klp_func no_funcs[] =3D { - {} -}; - -static struct klp_object objs[] =3D { - { - .name =3D NULL, /* vmlinux */ - .funcs =3D no_funcs, - .callbacks =3D { - .pre_patch =3D pre_patch_callback, - .post_patch =3D post_patch_callback, - .pre_unpatch =3D pre_unpatch_callback, - .post_unpatch =3D post_unpatch_callback, - }, - }, { } -}; - -static struct klp_state states[] =3D { - { - .id =3D CONSOLE_LOGLEVEL_STATE, - .version =3D CONSOLE_LOGLEVEL_STATE_VERSION, - }, { } -}; - -static struct klp_patch patch =3D { - .mod =3D THIS_MODULE, - .objs =3D objs, - .states =3D states, - .replace =3D true, -}; - -static int test_klp_callbacks_demo_init(void) -{ - return klp_enable_patch(&patch); -} - -static void test_klp_callbacks_demo_exit(void) -{ -} - -module_init(test_klp_callbacks_demo_init); -module_exit(test_klp_callbacks_demo_exit); -MODULE_LICENSE("GPL"); -MODULE_INFO(livepatch, "Y"); -MODULE_AUTHOR("Petr Mladek "); -MODULE_DESCRIPTION("Livepatch test: system state modification"); +/* The console loglevel fix is the same in the next cumulative patch. */ +#include "test_klp_state.c" diff --git a/tools/testing/selftests/livepatch/test-state.sh b/tools/testin= g/selftests/livepatch/test-state.sh index 38656721c958..a3c933ea96fc 100755 --- a/tools/testing/selftests/livepatch/test-state.sh +++ b/tools/testing/selftests/livepatch/test-state.sh @@ -22,20 +22,20 @@ unload_lp $MOD_LIVEPATCH check_result "% modprobe $MOD_LIVEPATCH livepatch: enabling patch '$MOD_LIVEPATCH' livepatch: '$MOD_LIVEPATCH': initializing patching transition -$MOD_LIVEPATCH: pre_patch_callback: vmlinux +$MOD_LIVEPATCH: setup_state_callback: state 1 $MOD_LIVEPATCH: allocate_loglevel_state: allocating space to store console= _loglevel livepatch: '$MOD_LIVEPATCH': starting patching transition livepatch: '$MOD_LIVEPATCH': completing patching transition -$MOD_LIVEPATCH: post_patch_callback: vmlinux +$MOD_LIVEPATCH: enable_state_callback: state 1 $MOD_LIVEPATCH: fix_console_loglevel: fixing console_loglevel livepatch: '$MOD_LIVEPATCH': patching complete % echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled livepatch: '$MOD_LIVEPATCH': initializing unpatching transition -$MOD_LIVEPATCH: pre_unpatch_callback: vmlinux +$MOD_LIVEPATCH: disable_state_callback: state 1 $MOD_LIVEPATCH: restore_console_loglevel: restoring console_loglevel livepatch: '$MOD_LIVEPATCH': starting unpatching transition livepatch: '$MOD_LIVEPATCH': completing unpatching transition -$MOD_LIVEPATCH: post_unpatch_callback: vmlinux +$MOD_LIVEPATCH: release_state_callback: state 1 $MOD_LIVEPATCH: free_loglevel_state: freeing space for the stored console_= loglevel livepatch: '$MOD_LIVEPATCH': unpatching complete % rmmod $MOD_LIVEPATCH" @@ -54,31 +54,27 @@ unload_lp $MOD_LIVEPATCH2 check_result "% modprobe $MOD_LIVEPATCH livepatch: enabling patch '$MOD_LIVEPATCH' livepatch: '$MOD_LIVEPATCH': initializing patching transition -$MOD_LIVEPATCH: pre_patch_callback: vmlinux +$MOD_LIVEPATCH: setup_state_callback: state 1 $MOD_LIVEPATCH: allocate_loglevel_state: allocating space to store console= _loglevel livepatch: '$MOD_LIVEPATCH': starting patching transition livepatch: '$MOD_LIVEPATCH': completing patching transition -$MOD_LIVEPATCH: post_patch_callback: vmlinux +$MOD_LIVEPATCH: enable_state_callback: state 1 $MOD_LIVEPATCH: fix_console_loglevel: fixing console_loglevel livepatch: '$MOD_LIVEPATCH': patching complete % modprobe $MOD_LIVEPATCH2 livepatch: enabling patch '$MOD_LIVEPATCH2' livepatch: '$MOD_LIVEPATCH2': initializing patching transition -$MOD_LIVEPATCH2: pre_patch_callback: vmlinux -$MOD_LIVEPATCH2: allocate_loglevel_state: space to store console_loglevel = already allocated livepatch: '$MOD_LIVEPATCH2': starting patching transition livepatch: '$MOD_LIVEPATCH2': completing patching transition -$MOD_LIVEPATCH2: post_patch_callback: vmlinux -$MOD_LIVEPATCH2: fix_console_loglevel: taking over the console_loglevel ch= ange livepatch: '$MOD_LIVEPATCH2': patching complete % rmmod $MOD_LIVEPATCH % echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH2/enabled livepatch: '$MOD_LIVEPATCH2': initializing unpatching transition -$MOD_LIVEPATCH2: pre_unpatch_callback: vmlinux +$MOD_LIVEPATCH2: disable_state_callback: state 1 $MOD_LIVEPATCH2: restore_console_loglevel: restoring console_loglevel livepatch: '$MOD_LIVEPATCH2': starting unpatching transition livepatch: '$MOD_LIVEPATCH2': completing unpatching transition -$MOD_LIVEPATCH2: post_unpatch_callback: vmlinux +$MOD_LIVEPATCH2: release_state_callback: state 1 $MOD_LIVEPATCH2: free_loglevel_state: freeing space for the stored console= _loglevel livepatch: '$MOD_LIVEPATCH2': unpatching complete % rmmod $MOD_LIVEPATCH2" @@ -99,41 +95,33 @@ unload_lp $MOD_LIVEPATCH3 check_result "% modprobe $MOD_LIVEPATCH2 livepatch: enabling patch '$MOD_LIVEPATCH2' livepatch: '$MOD_LIVEPATCH2': initializing patching transition -$MOD_LIVEPATCH2: pre_patch_callback: vmlinux +$MOD_LIVEPATCH2: setup_state_callback: state 1 $MOD_LIVEPATCH2: allocate_loglevel_state: allocating space to store consol= e_loglevel livepatch: '$MOD_LIVEPATCH2': starting patching transition livepatch: '$MOD_LIVEPATCH2': completing patching transition -$MOD_LIVEPATCH2: post_patch_callback: vmlinux +$MOD_LIVEPATCH2: enable_state_callback: state 1 $MOD_LIVEPATCH2: fix_console_loglevel: fixing console_loglevel livepatch: '$MOD_LIVEPATCH2': patching complete % modprobe $MOD_LIVEPATCH3 livepatch: enabling patch '$MOD_LIVEPATCH3' livepatch: '$MOD_LIVEPATCH3': initializing patching transition -$MOD_LIVEPATCH3: pre_patch_callback: vmlinux -$MOD_LIVEPATCH3: allocate_loglevel_state: space to store console_loglevel = already allocated livepatch: '$MOD_LIVEPATCH3': starting patching transition livepatch: '$MOD_LIVEPATCH3': completing patching transition -$MOD_LIVEPATCH3: post_patch_callback: vmlinux -$MOD_LIVEPATCH3: fix_console_loglevel: taking over the console_loglevel ch= ange livepatch: '$MOD_LIVEPATCH3': patching complete % rmmod $MOD_LIVEPATCH2 % modprobe $MOD_LIVEPATCH2 livepatch: enabling patch '$MOD_LIVEPATCH2' livepatch: '$MOD_LIVEPATCH2': initializing patching transition -$MOD_LIVEPATCH2: pre_patch_callback: vmlinux -$MOD_LIVEPATCH2: allocate_loglevel_state: space to store console_loglevel = already allocated livepatch: '$MOD_LIVEPATCH2': starting patching transition livepatch: '$MOD_LIVEPATCH2': completing patching transition -$MOD_LIVEPATCH2: post_patch_callback: vmlinux -$MOD_LIVEPATCH2: fix_console_loglevel: taking over the console_loglevel ch= ange livepatch: '$MOD_LIVEPATCH2': patching complete % echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH2/enabled livepatch: '$MOD_LIVEPATCH2': initializing unpatching transition -$MOD_LIVEPATCH2: pre_unpatch_callback: vmlinux +$MOD_LIVEPATCH2: disable_state_callback: state 1 $MOD_LIVEPATCH2: restore_console_loglevel: restoring console_loglevel livepatch: '$MOD_LIVEPATCH2': starting unpatching transition livepatch: '$MOD_LIVEPATCH2': completing unpatching transition -$MOD_LIVEPATCH2: post_unpatch_callback: vmlinux +$MOD_LIVEPATCH2: release_state_callback: state 1 $MOD_LIVEPATCH2: free_loglevel_state: freeing space for the stored console= _loglevel livepatch: '$MOD_LIVEPATCH2': unpatching complete % rmmod $MOD_LIVEPATCH2 @@ -152,11 +140,11 @@ unload_lp $MOD_LIVEPATCH2 check_result "% modprobe $MOD_LIVEPATCH2 livepatch: enabling patch '$MOD_LIVEPATCH2' livepatch: '$MOD_LIVEPATCH2': initializing patching transition -$MOD_LIVEPATCH2: pre_patch_callback: vmlinux +$MOD_LIVEPATCH2: setup_state_callback: state 1 $MOD_LIVEPATCH2: allocate_loglevel_state: allocating space to store consol= e_loglevel livepatch: '$MOD_LIVEPATCH2': starting patching transition livepatch: '$MOD_LIVEPATCH2': completing patching transition -$MOD_LIVEPATCH2: post_patch_callback: vmlinux +$MOD_LIVEPATCH2: enable_state_callback: state 1 $MOD_LIVEPATCH2: fix_console_loglevel: fixing console_loglevel livepatch: '$MOD_LIVEPATCH2': patching complete % modprobe $MOD_LIVEPATCH @@ -164,11 +152,11 @@ livepatch: Livepatch patch ($MOD_LIVEPATCH) is not co= mpatible with the already i modprobe: ERROR: could not insert '$MOD_LIVEPATCH': Invalid argument % echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH2/enabled livepatch: '$MOD_LIVEPATCH2': initializing unpatching transition -$MOD_LIVEPATCH2: pre_unpatch_callback: vmlinux +$MOD_LIVEPATCH2: disable_state_callback: state 1 $MOD_LIVEPATCH2: restore_console_loglevel: restoring console_loglevel livepatch: '$MOD_LIVEPATCH2': starting unpatching transition livepatch: '$MOD_LIVEPATCH2': completing unpatching transition -$MOD_LIVEPATCH2: post_unpatch_callback: vmlinux +$MOD_LIVEPATCH2: release_state_callback: state 1 $MOD_LIVEPATCH2: free_loglevel_state: freeing space for the stored console= _loglevel livepatch: '$MOD_LIVEPATCH2': unpatching complete % rmmod $MOD_LIVEPATCH2" --=20 2.35.3 From nobody Tue Dec 16 07:09:08 2025 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id E3FF1C4167B for ; Fri, 10 Nov 2023 17:42:56 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1345691AbjKJRm4 (ORCPT ); Fri, 10 Nov 2023 12:42:56 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:46772 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S234985AbjKJRlz (ORCPT ); Fri, 10 Nov 2023 12:41:55 -0500 Received: from smtp-out1.suse.de (smtp-out1.suse.de [195.135.220.28]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id B997F431C6; Fri, 10 Nov 2023 09:05:40 -0800 (PST) Received: from relay2.suse.de (relay2.suse.de [149.44.160.134]) by smtp-out1.suse.de (Postfix) with ESMTP id 76A36219A8; Fri, 10 Nov 2023 17:05:39 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1699635939; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=NCeADohju+t4A5792an+kfuP3X1RplnDdJoa+c7b8Kw=; b=PDrSH6j6XXpWzPvje0K6HX8bDL/aDEJ7Qe83QB0uCPUJHLKm/fwpsmRjqyCV4Wqpq+1ySc nM0JWU3HXgLwIDCCDblmSfYtzGXpqcsvu8oPFgxvw45UlGUv8V27GU1KZ9fYfigI2NPtSJ V33tetz+dwj0Idx/9yt0Uj+RR9nFcFs= Received: from alley.suse.cz (pmladek.udp.ovpn2.prg.suse.de [10.100.201.202]) by relay2.suse.de (Postfix) with ESMTP id 1A5262C2A9; Fri, 10 Nov 2023 17:05:39 +0000 (UTC) From: Petr Mladek To: Josh Poimboeuf , Miroslav Benes Cc: Joe Lawrence , Nicolai Stange , live-patching@vger.kernel.org, linux-kernel@vger.kernel.org, Petr Mladek Subject: [POC 4/7] livepatch: Do not use callbacks when testing sysfs interface Date: Fri, 10 Nov 2023 18:04:25 +0100 Message-Id: <20231110170428.6664-5-pmladek@suse.com> X-Mailer: git-send-email 2.35.3 In-Reply-To: <20231110170428.6664-1-pmladek@suse.com> References: <20231110170428.6664-1-pmladek@suse.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" The per-object callbacks have been obsoleted by per-state callbacks. As a result, the callbacks test modules have been obsoleted by updated klp state tests. The callbacks test modules are re-used in the sysfs selftests. It would be possible to replace them by klp state test modules but the newly generated logs are hard to review because there is a lot of noise caused by the callbacks. Instead, introduce a simple test module in a "Hello World" style and corresponding livepatch. The expected log can be reviewed easily. The test module might be later extended to provide more functionality which might be used in more tests. It would allow to create tests focusing on some particular feature with an easier output. Signed-off-by: Petr Mladek --- lib/livepatch/Makefile | 2 + lib/livepatch/test_klp_speaker.c | 34 +++++++++++++ lib/livepatch/test_klp_speaker_livepatch.c | 50 +++++++++++++++++++ .../testing/selftests/livepatch/test-sysfs.sh | 48 ++++++++---------- 4 files changed, 106 insertions(+), 28 deletions(-) create mode 100644 lib/livepatch/test_klp_speaker.c create mode 100644 lib/livepatch/test_klp_speaker_livepatch.c diff --git a/lib/livepatch/Makefile b/lib/livepatch/Makefile index dcc912b3478f..a8a5f6597633 100644 --- a/lib/livepatch/Makefile +++ b/lib/livepatch/Makefile @@ -9,6 +9,8 @@ obj-$(CONFIG_TEST_LIVEPATCH) +=3D test_klp_atomic_replace.o= \ test_klp_callbacks_mod.o \ test_klp_livepatch.o \ test_klp_shadow_vars.o \ + test_klp_speaker.o \ + test_klp_speaker_livepatch.o \ test_klp_state.o \ test_klp_state2.o \ test_klp_state3.o diff --git a/lib/livepatch/test_klp_speaker.c b/lib/livepatch/test_klp_spea= ker.c new file mode 100644 index 000000000000..d2d31072639a --- /dev/null +++ b/lib/livepatch/test_klp_speaker.c @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2023 SUSE + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#ifndef _VER_NAME +#define _VER_NAME(name) name +#endif + +#include +#include + +noinline +void speaker_welcome(void) +{ + pr_info("%s: Hello, World!\n", __func__); +} + +static int test_klp_speaker_init(void) +{ + pr_info("%s\n", __func__); + + return 0; +} + +static void test_klp_speaker_exit(void) +{ + pr_info("%s\n", __func__); +} + +module_init(test_klp_speaker_init); +module_exit(test_klp_speaker_exit); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Livepatch test: test functions"); diff --git a/lib/livepatch/test_klp_speaker_livepatch.c b/lib/livepatch/tes= t_klp_speaker_livepatch.c new file mode 100644 index 000000000000..0317a4937b78 --- /dev/null +++ b/lib/livepatch/test_klp_speaker_livepatch.c @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-2.0 + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include + + +void livepatch_speaker_welcome(void) +{ + pr_info("%s: Ladies and gentleman, ...\n", __func__); +} + + +static struct klp_func test_klp_speaker_funcs[] =3D { + { + .old_name =3D "speaker_welcome", + .new_func =3D livepatch_speaker_welcome, + }, + { } +}; + +static struct klp_object objs[] =3D { + { + .name =3D "test_klp_speaker", + .funcs =3D test_klp_speaker_funcs, + }, + { } +}; + +static struct klp_patch patch =3D { + .mod =3D THIS_MODULE, + .objs =3D objs, +}; + +static int test_klp_speaker_livepatch_init(void) +{ + return klp_enable_patch(&patch); +} + +static void test_klp_speaker_livepatch_exit(void) +{ +} + +module_init(test_klp_speaker_livepatch_init); +module_exit(test_klp_speaker_livepatch_exit); +MODULE_LICENSE("GPL"); +MODULE_INFO(livepatch, "Y"); diff --git a/tools/testing/selftests/livepatch/test-sysfs.sh b/tools/testin= g/selftests/livepatch/test-sysfs.sh index 7f76f280189a..424b6af32c99 100755 --- a/tools/testing/selftests/livepatch/test-sysfs.sh +++ b/tools/testing/selftests/livepatch/test-sysfs.sh @@ -42,8 +42,8 @@ livepatch: '$MOD_LIVEPATCH': unpatching complete =20 start_test "sysfs test object/patched" =20 -MOD_LIVEPATCH=3Dtest_klp_callbacks_demo -MOD_TARGET=3Dtest_klp_callbacks_mod +MOD_LIVEPATCH=3Dtest_klp_speaker_livepatch +MOD_TARGET=3Dtest_klp_speaker load_lp $MOD_LIVEPATCH =20 # check the "patch" file changes as target module loads/unloads @@ -56,31 +56,23 @@ check_sysfs_value "$MOD_LIVEPATCH" "$MOD_TARGET/patche= d" "0" disable_lp $MOD_LIVEPATCH unload_lp $MOD_LIVEPATCH =20 -check_result "% modprobe test_klp_callbacks_demo -livepatch: enabling patch 'test_klp_callbacks_demo' -livepatch: 'test_klp_callbacks_demo': initializing patching transition -test_klp_callbacks_demo: pre_patch_callback: vmlinux -livepatch: 'test_klp_callbacks_demo': starting patching transition -livepatch: 'test_klp_callbacks_demo': completing patching transition -test_klp_callbacks_demo: post_patch_callback: vmlinux -livepatch: 'test_klp_callbacks_demo': patching complete -% modprobe test_klp_callbacks_mod -livepatch: applying patch 'test_klp_callbacks_demo' to loading module 'tes= t_klp_callbacks_mod' -test_klp_callbacks_demo: pre_patch_callback: test_klp_callbacks_mod -> [MO= DULE_STATE_COMING] Full formed, running module_init -test_klp_callbacks_demo: post_patch_callback: test_klp_callbacks_mod -> [M= ODULE_STATE_COMING] Full formed, running module_init -test_klp_callbacks_mod: test_klp_callbacks_mod_init -% rmmod test_klp_callbacks_mod -test_klp_callbacks_mod: test_klp_callbacks_mod_exit -test_klp_callbacks_demo: pre_unpatch_callback: test_klp_callbacks_mod -> [= MODULE_STATE_GOING] Going away -livepatch: reverting patch 'test_klp_callbacks_demo' on unloading module '= test_klp_callbacks_mod' -test_klp_callbacks_demo: post_unpatch_callback: test_klp_callbacks_mod -> = [MODULE_STATE_GOING] Going away -% echo 0 > /sys/kernel/livepatch/test_klp_callbacks_demo/enabled -livepatch: 'test_klp_callbacks_demo': initializing unpatching transition -test_klp_callbacks_demo: pre_unpatch_callback: vmlinux -livepatch: 'test_klp_callbacks_demo': starting unpatching transition -livepatch: 'test_klp_callbacks_demo': completing unpatching transition -test_klp_callbacks_demo: post_unpatch_callback: vmlinux -livepatch: 'test_klp_callbacks_demo': unpatching complete -% rmmod test_klp_callbacks_demo" +check_result "% modprobe $MOD_LIVEPATCH +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +livepatch: '$MOD_LIVEPATCH': starting patching transition +livepatch: '$MOD_LIVEPATCH': completing patching transition +livepatch: '$MOD_LIVEPATCH': patching complete +% modprobe $MOD_TARGET +livepatch: applying patch '$MOD_LIVEPATCH' to loading module '$MOD_TARGET' +$MOD_TARGET: ${MOD_TARGET}_init +% rmmod $MOD_TARGET +$MOD_TARGET: ${MOD_TARGET}_exit +livepatch: reverting patch '$MOD_LIVEPATCH' on unloading module '$MOD_TARG= ET' +% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled +livepatch: '$MOD_LIVEPATCH': initializing unpatching transition +livepatch: '$MOD_LIVEPATCH': starting unpatching transition +livepatch: '$MOD_LIVEPATCH': completing unpatching transition +livepatch: '$MOD_LIVEPATCH': unpatching complete +% rmmod $MOD_LIVEPATCH" =20 exit 0 --=20 2.35.3 From nobody Tue Dec 16 07:09:08 2025 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 80ED3C4332F for ; Fri, 10 Nov 2023 17:42:43 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S235045AbjKJRmo (ORCPT ); Fri, 10 Nov 2023 12:42:44 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:46472 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S234896AbjKJRly (ORCPT ); Fri, 10 Nov 2023 12:41:54 -0500 Received: from smtp-out2.suse.de (smtp-out2.suse.de [IPv6:2001:67c:2178:6::1d]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 99BD8431C4; Fri, 10 Nov 2023 09:05:53 -0800 (PST) Received: from relay2.suse.de (relay2.suse.de [149.44.160.134]) by smtp-out2.suse.de (Postfix) with ESMTP id 851891F8BD; Fri, 10 Nov 2023 17:05:51 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1699635951; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=NklZN6fzMlsGpMkP/ig8LeO2pM2n3MLcogCKsVFNavQ=; b=aZ+6ilVbrGm4mcmrlji8QszOhAyZojpL4nl/76v1owYcLHKU12Qjc2dU5lF8rrpsroZwnJ 8N4ve6KkwEZdRljZzIRuFL3S99pRXrgPrJpKnRvPOn2+TxIm9I/wEfKNUNJ3T7Oh3PVu9v +kssCrY/NJ73+CUiXYcC88cjwitLnXw= Received: from alley.suse.cz (pmladek.udp.ovpn2.prg.suse.de [10.100.201.202]) by relay2.suse.de (Postfix) with ESMTP id 7EE462D35B; Fri, 10 Nov 2023 17:05:50 +0000 (UTC) From: Petr Mladek To: Josh Poimboeuf , Miroslav Benes Cc: Joe Lawrence , Nicolai Stange , live-patching@vger.kernel.org, linux-kernel@vger.kernel.org, Petr Mladek Subject: [POC 5/7] livepatch: Convert klp module callbacks tests into livepatch module tests Date: Fri, 10 Nov 2023 18:04:26 +0100 Message-Id: <20231110170428.6664-6-pmladek@suse.com> X-Mailer: git-send-email 2.35.3 In-Reply-To: <20231110170428.6664-1-pmladek@suse.com> References: <20231110170428.6664-1-pmladek@suse.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" The original livepatch callbacks has been obsoleted by the livepatch state callbacks. The important difference is that the original callbacks were associated with the livepatched object. The state callbacks are associated with the livepatch states which are associated with the livepatch. As a result, some per-object callbacks functionality could not be preserved with the new per-state callbacks. Namely, it is not possible to change a state when a livepatched module is loaded or unloaded. This usecase it considered rare and not worth the complexity. Instead, the callback self-tests are transformed into testing the new state callbacks. Some tests do not use the callbacks any longer. But they still test some basic scenarios which is not tested by other tests. Many features are added to the test_klp_speaker module: - The parameter "welcome" allows to call the livepatched function speaker_welcome. - The parameter "waiting_welcome" allows to a trigger a workqueue work which might busy wait in a livepatched function and block the transition. It requires storing pointer to speaker_wait_and_welcome() so that the original one could be called from the livepatch. Alternative solution would be to livepatch speaker_wait_and_welcome() but it would require passing pointer to the completion structure from the livepatched module. Another tricky part is the initialization of the work structure. It must be initialized in the module init() callback so that it can be flushed in the exit() callback even when it was not used. But it must be initialized also in waiting_welcome_set() because the module parameter callbacks are proceed before calling the module init() callback. - The same sources are used to build two target modules. The livepatched functions have versioned names. It makes it easier to distinguish messages printed by this functions and the livepatched variants. It is solved by using macro for definition of the functions. Many features are added also into the livepatch module: - The parameter "add_applause" allows to add "[APPLAUSE " string into the message printed by speaker_welcome(). The string is stored in a shadow variable which is: - associated with a state. - allocated and initialized to "[] " in setup() state callback. - set to "[APPLAUSE ]" in enable() state callback. - set back to "[] " in disable() state callback. - freed in release() state callback. Allows testing state callbacks. It even allows to distinguish the state after setup() and enable() callbacks. - The parameter "setup_ret" allow to force a negative return value to the callback allocation the shadow variable. - The parameter "noreplace" allows to disable patch.replace parameter and install the livepatch in parallel with others. - The same sources are used for building two livepatches. It allows writing tests with more livepatches. The livepatched functions are versioned. Both livepatches can livepatch both speaker test modules. But they use the same shadow variable for storing the "add_applause" feature. It allows testing passing the states and shadow variables with atomic replace. But obviously, "add_applause" parameter could no be used for both livepatches when they are loaded in parallel using the "noreplace" parameter. Finally, remove the obsolete and unused "callback" test modules and livepatches. TODO: - Debug why "waiting_welcome" does not block the transition. The livepatched function is not on the stack from unknown reasons. See the comments in test-module.h for more details. - Better organize the tests. test-module.sh combines tests of various aspects which might better be suited somewhere else. As a first step, test-callbacks.sh has been renamed to test-modules.sh. But there still might be a better name. - Split this huge patch. Add the various features and tests in more steps. Signed-off-by: Petr Mladek --- lib/livepatch/Makefile | 6 +- lib/livepatch/test_klp_callbacks_busy.c | 70 --- lib/livepatch/test_klp_callbacks_demo.c | 121 ---- lib/livepatch/test_klp_callbacks_demo2.c | 93 --- lib/livepatch/test_klp_callbacks_mod.c | 24 - lib/livepatch/test_klp_speaker.c | 153 ++++- lib/livepatch/test_klp_speaker_livepatch.c | 209 ++++++- tools/testing/selftests/livepatch/Makefile | 2 +- .../testing/selftests/livepatch/functions.sh | 29 + .../selftests/livepatch/test-callbacks.sh | 553 ------------------ .../selftests/livepatch/test-modules.sh | 539 +++++++++++++++++ 11 files changed, 929 insertions(+), 870 deletions(-) delete mode 100644 lib/livepatch/test_klp_callbacks_busy.c delete mode 100644 lib/livepatch/test_klp_callbacks_demo.c delete mode 100644 lib/livepatch/test_klp_callbacks_demo2.c delete mode 100644 lib/livepatch/test_klp_callbacks_mod.c delete mode 100755 tools/testing/selftests/livepatch/test-callbacks.sh create mode 100755 tools/testing/selftests/livepatch/test-modules.sh diff --git a/lib/livepatch/Makefile b/lib/livepatch/Makefile index a8a5f6597633..e85e0c0f3875 100644 --- a/lib/livepatch/Makefile +++ b/lib/livepatch/Makefile @@ -3,14 +3,12 @@ # Makefile for livepatch test code. =20 obj-$(CONFIG_TEST_LIVEPATCH) +=3D test_klp_atomic_replace.o \ - test_klp_callbacks_demo.o \ - test_klp_callbacks_demo2.o \ - test_klp_callbacks_busy.o \ - test_klp_callbacks_mod.o \ test_klp_livepatch.o \ test_klp_shadow_vars.o \ test_klp_speaker.o \ + test_klp_speaker2.o \ test_klp_speaker_livepatch.o \ + test_klp_speaker_livepatch2.o \ test_klp_state.o \ test_klp_state2.o \ test_klp_state3.o diff --git a/lib/livepatch/test_klp_callbacks_busy.c b/lib/livepatch/test_k= lp_callbacks_busy.c deleted file mode 100644 index 133929e0ce8f..000000000000 --- a/lib/livepatch/test_klp_callbacks_busy.c +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -// Copyright (C) 2018 Joe Lawrence - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include - -/* load/run-time control from sysfs writer */ -static bool block_transition; -module_param(block_transition, bool, 0644); -MODULE_PARM_DESC(block_transition, "block_transition (default=3Dfalse)"); - -static void busymod_work_func(struct work_struct *work); -static DECLARE_WORK(work, busymod_work_func); -static DECLARE_COMPLETION(busymod_work_started); - -static void busymod_work_func(struct work_struct *work) -{ - pr_info("%s enter\n", __func__); - complete(&busymod_work_started); - - while (READ_ONCE(block_transition)) { - /* - * Busy-wait until the sysfs writer has acknowledged a - * blocked transition and clears the flag. - */ - msleep(20); - } - - pr_info("%s exit\n", __func__); -} - -static int test_klp_callbacks_busy_init(void) -{ - pr_info("%s\n", __func__); - schedule_work(&work); - - /* - * To synchronize kernel messages, hold the init function from - * exiting until the work function's entry message has printed. - */ - wait_for_completion(&busymod_work_started); - - if (!block_transition) { - /* - * Serialize output: print all messages from the work - * function before returning from init(). - */ - flush_work(&work); - } - - return 0; -} - -static void test_klp_callbacks_busy_exit(void) -{ - WRITE_ONCE(block_transition, false); - flush_work(&work); - pr_info("%s\n", __func__); -} - -module_init(test_klp_callbacks_busy_init); -module_exit(test_klp_callbacks_busy_exit); -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Joe Lawrence "); -MODULE_DESCRIPTION("Livepatch test: busy target module"); diff --git a/lib/livepatch/test_klp_callbacks_demo.c b/lib/livepatch/test_k= lp_callbacks_demo.c deleted file mode 100644 index 3fd8fe1cd1cc..000000000000 --- a/lib/livepatch/test_klp_callbacks_demo.c +++ /dev/null @@ -1,121 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -// Copyright (C) 2018 Joe Lawrence - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include - -static int pre_patch_ret; -module_param(pre_patch_ret, int, 0644); -MODULE_PARM_DESC(pre_patch_ret, "pre_patch_ret (default=3D0)"); - -static const char *const module_state[] =3D { - [MODULE_STATE_LIVE] =3D "[MODULE_STATE_LIVE] Normal state", - [MODULE_STATE_COMING] =3D "[MODULE_STATE_COMING] Full formed, running mod= ule_init", - [MODULE_STATE_GOING] =3D "[MODULE_STATE_GOING] Going away", - [MODULE_STATE_UNFORMED] =3D "[MODULE_STATE_UNFORMED] Still setting it up", -}; - -static void callback_info(const char *callback, struct klp_object *obj) -{ - if (obj->mod) - pr_info("%s: %s -> %s\n", callback, obj->mod->name, - module_state[obj->mod->state]); - else - pr_info("%s: vmlinux\n", callback); -} - -/* Executed on object patching (ie, patch enablement) */ -static int pre_patch_callback(struct klp_object *obj) -{ - callback_info(__func__, obj); - return pre_patch_ret; -} - -/* Executed on object unpatching (ie, patch disablement) */ -static void post_patch_callback(struct klp_object *obj) -{ - callback_info(__func__, obj); -} - -/* Executed on object unpatching (ie, patch disablement) */ -static void pre_unpatch_callback(struct klp_object *obj) -{ - callback_info(__func__, obj); -} - -/* Executed on object unpatching (ie, patch disablement) */ -static void post_unpatch_callback(struct klp_object *obj) -{ - callback_info(__func__, obj); -} - -static void patched_work_func(struct work_struct *work) -{ - pr_info("%s\n", __func__); -} - -static struct klp_func no_funcs[] =3D { - {} -}; - -static struct klp_func busymod_funcs[] =3D { - { - .old_name =3D "busymod_work_func", - .new_func =3D patched_work_func, - }, {} -}; - -static struct klp_object objs[] =3D { - { - .name =3D NULL, /* vmlinux */ - .funcs =3D no_funcs, - .callbacks =3D { - .pre_patch =3D pre_patch_callback, - .post_patch =3D post_patch_callback, - .pre_unpatch =3D pre_unpatch_callback, - .post_unpatch =3D post_unpatch_callback, - }, - }, { - .name =3D "test_klp_callbacks_mod", - .funcs =3D no_funcs, - .callbacks =3D { - .pre_patch =3D pre_patch_callback, - .post_patch =3D post_patch_callback, - .pre_unpatch =3D pre_unpatch_callback, - .post_unpatch =3D post_unpatch_callback, - }, - }, { - .name =3D "test_klp_callbacks_busy", - .funcs =3D busymod_funcs, - .callbacks =3D { - .pre_patch =3D pre_patch_callback, - .post_patch =3D post_patch_callback, - .pre_unpatch =3D pre_unpatch_callback, - .post_unpatch =3D post_unpatch_callback, - }, - }, { } -}; - -static struct klp_patch patch =3D { - .mod =3D THIS_MODULE, - .objs =3D objs, -}; - -static int test_klp_callbacks_demo_init(void) -{ - return klp_enable_patch(&patch); -} - -static void test_klp_callbacks_demo_exit(void) -{ -} - -module_init(test_klp_callbacks_demo_init); -module_exit(test_klp_callbacks_demo_exit); -MODULE_LICENSE("GPL"); -MODULE_INFO(livepatch, "Y"); -MODULE_AUTHOR("Joe Lawrence "); -MODULE_DESCRIPTION("Livepatch test: livepatch demo"); diff --git a/lib/livepatch/test_klp_callbacks_demo2.c b/lib/livepatch/test_= klp_callbacks_demo2.c deleted file mode 100644 index 5417573e80af..000000000000 --- a/lib/livepatch/test_klp_callbacks_demo2.c +++ /dev/null @@ -1,93 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -// Copyright (C) 2018 Joe Lawrence - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include - -static int replace; -module_param(replace, int, 0644); -MODULE_PARM_DESC(replace, "replace (default=3D0)"); - -static const char *const module_state[] =3D { - [MODULE_STATE_LIVE] =3D "[MODULE_STATE_LIVE] Normal state", - [MODULE_STATE_COMING] =3D "[MODULE_STATE_COMING] Full formed, running mod= ule_init", - [MODULE_STATE_GOING] =3D "[MODULE_STATE_GOING] Going away", - [MODULE_STATE_UNFORMED] =3D "[MODULE_STATE_UNFORMED] Still setting it up", -}; - -static void callback_info(const char *callback, struct klp_object *obj) -{ - if (obj->mod) - pr_info("%s: %s -> %s\n", callback, obj->mod->name, - module_state[obj->mod->state]); - else - pr_info("%s: vmlinux\n", callback); -} - -/* Executed on object patching (ie, patch enablement) */ -static int pre_patch_callback(struct klp_object *obj) -{ - callback_info(__func__, obj); - return 0; -} - -/* Executed on object unpatching (ie, patch disablement) */ -static void post_patch_callback(struct klp_object *obj) -{ - callback_info(__func__, obj); -} - -/* Executed on object unpatching (ie, patch disablement) */ -static void pre_unpatch_callback(struct klp_object *obj) -{ - callback_info(__func__, obj); -} - -/* Executed on object unpatching (ie, patch disablement) */ -static void post_unpatch_callback(struct klp_object *obj) -{ - callback_info(__func__, obj); -} - -static struct klp_func no_funcs[] =3D { - { } -}; - -static struct klp_object objs[] =3D { - { - .name =3D NULL, /* vmlinux */ - .funcs =3D no_funcs, - .callbacks =3D { - .pre_patch =3D pre_patch_callback, - .post_patch =3D post_patch_callback, - .pre_unpatch =3D pre_unpatch_callback, - .post_unpatch =3D post_unpatch_callback, - }, - }, { } -}; - -static struct klp_patch patch =3D { - .mod =3D THIS_MODULE, - .objs =3D objs, - /* set .replace in the init function below for demo purposes */ -}; - -static int test_klp_callbacks_demo2_init(void) -{ - patch.replace =3D replace; - return klp_enable_patch(&patch); -} - -static void test_klp_callbacks_demo2_exit(void) -{ -} - -module_init(test_klp_callbacks_demo2_init); -module_exit(test_klp_callbacks_demo2_exit); -MODULE_LICENSE("GPL"); -MODULE_INFO(livepatch, "Y"); -MODULE_AUTHOR("Joe Lawrence "); -MODULE_DESCRIPTION("Livepatch test: livepatch demo2"); diff --git a/lib/livepatch/test_klp_callbacks_mod.c b/lib/livepatch/test_kl= p_callbacks_mod.c deleted file mode 100644 index 8fbe645b1c2c..000000000000 --- a/lib/livepatch/test_klp_callbacks_mod.c +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -// Copyright (C) 2018 Joe Lawrence - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include - -static int test_klp_callbacks_mod_init(void) -{ - pr_info("%s\n", __func__); - return 0; -} - -static void test_klp_callbacks_mod_exit(void) -{ - pr_info("%s\n", __func__); -} - -module_init(test_klp_callbacks_mod_init); -module_exit(test_klp_callbacks_mod_exit); -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Joe Lawrence "); -MODULE_DESCRIPTION("Livepatch test: target module"); diff --git a/lib/livepatch/test_klp_speaker.c b/lib/livepatch/test_klp_spea= ker.c index d2d31072639a..d8e42267f5cd 100644 --- a/lib/livepatch/test_klp_speaker.c +++ b/lib/livepatch/test_klp_speaker.c @@ -9,23 +9,174 @@ =20 #include #include +#include +#include + +#include "test_klp_speaker.h" =20 noinline -void speaker_welcome(void) +void _VER_NAME(speaker_welcome)(void) { pr_info("%s: Hello, World!\n", __func__); } =20 +static int welcome_get(char *buffer, const struct kernel_param *kp) +{ + _VER_NAME(speaker_welcome)(); + + return 0; +} + +static const struct kernel_param_ops welcome_ops =3D { + .get =3D welcome_get, +}; + +module_param_cb(welcome, &welcome_ops, NULL, 0400); +MODULE_PARM_DESC(welcome, "Print speaker's welcome message into the kernel= log when reading the value."); + +noinline +void speaker_wait_and_welcome(struct speaker *speaker) +{ + pr_info("%s: Speaker started waiting.\n", __func__); + complete(&speaker->started_waiting); + speaker->started_waiting_param =3D true; + + while (READ_ONCE(speaker->is_waiting)) { + /* + * Busy-wait until the sysfs writer has acknowledged a + * blocked transition and clears the flag. + */ + msleep(20); + } + + speaker->welcome(); +} + +noinline +void _VER_NAME(call_speaker)(struct speaker *speaker) +{ + pr_info("%s: Calling speaker.\n", __func__); + speaker->wait_and_welcome(speaker); +} + +static struct speaker test_klp_speaker =3D { + .call =3D _VER_NAME(call_speaker), + .welcome =3D _VER_NAME(speaker_welcome), + .wait_and_welcome =3D speaker_wait_and_welcome, +}; + +static void speaker_func(struct work_struct *work) +{ + struct speaker *speaker =3D container_of(work, struct speaker, work); + + speaker->call(speaker); +} + +/* + * The work must be initialized when "waiting_welcome" parameter is proceed + * during the module load. Which is done before calling the module init + * callback. + * + * Also it must be initialized also when the parameter was not used because + * the work must be flushed in the module exit callback. + */ +static void speaker_work_init(struct speaker *speaker) +{ + static bool speaker_work_initialized; + + if (speaker_work_initialized) + return; + + INIT_WORK(&speaker->work, speaker_func); + speaker_work_initialized =3D true; +} + +static int waiting_welcome_get(char *buffer, const struct kernel_param *kp) +{ + if (test_klp_speaker.is_waiting) + pr_info("Speaker is waiting.\n"); + else + pr_info("Speaker is not waiting.\n"); + + return 0; +} + +static int waiting_welcome_set(const char *val, const struct kernel_param = *kp) +{ + bool wait; + int ret; + + ret =3D kstrtobool(val, &wait); + if (ret) + return ret; + + if (wait) { + if (test_klp_speaker.is_waiting) { + pr_err("%s: Speaker is already waiting.\n", __func__); + return -EBUSY; + } + + test_klp_speaker.started_waiting_param =3D false; + init_completion(&test_klp_speaker.started_waiting); + speaker_work_init(&test_klp_speaker); + + WRITE_ONCE(test_klp_speaker.is_waiting, true); + schedule_work(&test_klp_speaker.work); + + /* + * To synchronize kernel messages, hold this callback from + * exiting until the work function's entry message has printed. + */ + wait_for_completion(&test_klp_speaker.started_waiting); + } else { + if (!test_klp_speaker.is_waiting) { + pr_err("%s: Speaker has not been waiting.\n", __func__); + return -EINVAL; + } + + WRITE_ONCE(test_klp_speaker.is_waiting, false); + flush_work(&test_klp_speaker.work); + } + + return 0; +} + +static const struct kernel_param_ops waiting_welcome_ops =3D { + .set =3D waiting_welcome_set, + .get =3D waiting_welcome_get, +}; + +module_param_cb(waiting_welcome, &waiting_welcome_ops, NULL, 0600); +MODULE_PARM_DESC(waiting_welcome, "Speaker will start waiting when set and= will say welcome message when cleared."); + +static int started_waiting_get(char *buffer, const struct kernel_param *kp) +{ + return sysfs_emit(buffer, test_klp_speaker.started_waiting_param ? "1" : = "0"); +} + +static const struct kernel_param_ops started_waiting_ops =3D { + .get =3D started_waiting_get, +}; + +module_param_cb(started_waiting, &started_waiting_ops, NULL, 0400); +MODULE_PARM_DESC(started_waiting, "Read only parameter to check whether th= e asynchronously started speaker already started waiting."); + static int test_klp_speaker_init(void) { pr_info("%s\n", __func__); =20 + speaker_work_init(&test_klp_speaker); + return 0; } =20 static void test_klp_speaker_exit(void) { pr_info("%s\n", __func__); + + /* Make sure that wait_funtion() is not running. */ + WRITE_ONCE(test_klp_speaker.is_waiting, false); + flush_work(&test_klp_speaker.work); } =20 module_init(test_klp_speaker_init); diff --git a/lib/livepatch/test_klp_speaker_livepatch.c b/lib/livepatch/tes= t_klp_speaker_livepatch.c index 0317a4937b78..54bd32ee24ea 100644 --- a/lib/livepatch/test_klp_speaker_livepatch.c +++ b/lib/livepatch/test_klp_speaker_livepatch.c @@ -7,18 +7,182 @@ #include #include =20 +#include "test_klp_speaker.h" =20 -void livepatch_speaker_welcome(void) -{ - pr_info("%s: Ladies and gentleman, ...\n", __func__); +#define APPLAUSE_ID 1 +#define APPLAUSE_SIZE 64 + +/* associate the shadow variable with NULL address */; +void *shadow_object =3D NULL; + +/* load/run-time control from sysfs writer */ +static bool add_applause; +module_param(add_applause, bool, 0600); +MODULE_PARM_DESC(add_applause, "Use shadow variable to add applause (defau= lt=3Dfalse)"); + +/* load/run-time control from sysfs writer */ +static int setup_ret; +module_param(setup_ret, int, 0644); +MODULE_PARM_DESC(setup_ret, "Allow to force failure for the setup callback= (default=3D0)"); + +/* load/run-time control from sysfs writer */ +static bool noreplace; +module_param(noreplace, bool, 0600); +MODULE_PARM_DESC(noreplace, "Allow to install the livepatch together with = other livepatches. (default=3Dfalse)"); + +#define LIVEPATCH_SPEAKER_WELCOME_FN(fn_name) \ +noinline \ +void fn_name(void) \ +{ \ + const char *applause; \ + \ + applause =3D (char *)klp_shadow_get(shadow_object, APPLAUSE_ID); \ + \ + if (!applause) \ + applause =3D ""; \ + \ + pr_info("%s: %sLadies and gentleman, ...\n", __func__, applause); \ } =20 +LIVEPATCH_SPEAKER_WELCOME_FN(livepatch_speaker_welcome) +LIVEPATCH_SPEAKER_WELCOME_FN(livepatch_speaker_welcome2) + +static int allocate_applause(void) +{ + char *applause; + + /* + * Attach the shadow variable to some well known address it stays + * even when the livepatch gets replaced with a newer version. + * + * Make sure that the shadow variable does not exist yet. + */ + applause =3D (char *)klp_shadow_alloc(shadow_object, APPLAUSE_ID, + APPLAUSE_SIZE, GFP_KERNEL, + NULL, NULL); + + if (!applause) { + pr_err("%s: failed to allocated shadow variable for storing an applause = description\n", + __func__); + return -ENOMEM; + } + + /* + * Fill the shadow target with an empty brackets before all processes + * get livepatched. + */ + strscpy(applause, "[] ", APPLAUSE_SIZE); + + return 0; +} + +static void set_applause(void) +{ + char *applause; + + applause =3D (char *)klp_shadow_get(shadow_object, APPLAUSE_ID); + if (!applause) { + pr_err("%s: failed to get shadow variable with the applause description:= %d\n", + __func__, APPLAUSE_ID); + return; + } + + strscpy(applause, "[APPLAUSE] ", APPLAUSE_SIZE); +} + +static void unset_applause(void) +{ + char *applause; + + applause =3D (char *)klp_shadow_get(shadow_object, APPLAUSE_ID); + if (!applause) { + pr_err("%s: failed to get shadow variable with the applause description:= %d\n", + __func__, APPLAUSE_ID); + return; + } + + applause[0] =3D '\0'; +} + +static void free_applause(void) +{ + char *applause; + + applause =3D (char *)klp_shadow_get(shadow_object, APPLAUSE_ID); + if (!applause) { + pr_err("%s: failed to get shadow variable with the applause description:= %d\n", + __func__, APPLAUSE_ID); + return; + } + + klp_shadow_free(shadow_object, APPLAUSE_ID, NULL); +} + +/* Executed before patching when the state is new. */ +static int setup_applause_callback(struct klp_patch *patch, struct klp_sta= te *state) +{ + pr_info("%s: state %lu\n", __func__, state->id); + + if (setup_ret) { + pr_err("%s: forcing err: %pe\n", __func__, ERR_PTR(setup_ret)); + return setup_ret; + } + + return allocate_applause(); +} + +/* Executed after patching when the state is new. */ +static void enable_applause_callback(struct klp_patch *patch, struct klp_s= tate *state) +{ + pr_info("%s: state %lu\n", __func__, state->id); + set_applause(); +} + +/* Executed before unpatching when the state is obsoleted. */ +static void disable_applause_callback(struct klp_patch *patch, struct klp_= state *state) +{ + pr_info("%s: state %lu\n", __func__, state->id); + unset_applause(); +} + +/* Executed after unpatching when the state is obsoleted. */ +static void release_applause_callback(struct klp_patch *patch, struct klp_= state *state) +{ + pr_info("%s: state %lu\n", __func__, state->id); + free_applause(); +} + +#define LIVEPATCH_CALL_SPEAKER_FN(fn_name) \ +void fn_name(struct speaker *speaker) \ +{ \ + pr_info("%s: Calling speaker (fixed).\n", __func__); \ + speaker->wait_and_welcome(speaker); \ +} + +LIVEPATCH_CALL_SPEAKER_FN(livepatch_call_speaker) +LIVEPATCH_CALL_SPEAKER_FN(livepatch_call_speaker2) =20 static struct klp_func test_klp_speaker_funcs[] =3D { { .old_name =3D "speaker_welcome", .new_func =3D livepatch_speaker_welcome, }, + { + .old_name =3D "call_speaker", + .new_func =3D livepatch_call_speaker, + }, + { } +}; + +static struct klp_func test_klp_speaker2_funcs[] =3D { + { + .old_name =3D "speaker_welcome2", + .new_func =3D livepatch_speaker_welcome2, + }, + { + .old_name =3D "call_speaker2", + .new_func =3D livepatch_call_speaker2, + }, { } }; =20 @@ -27,16 +191,55 @@ static struct klp_object objs[] =3D { .name =3D "test_klp_speaker", .funcs =3D test_klp_speaker_funcs, }, + { + .name =3D "test_klp_speaker2", + .funcs =3D test_klp_speaker2_funcs, + }, { } }; =20 +static struct klp_state states[] =3D { + { + .id =3D APPLAUSE_ID, + .is_shadow =3D true, + .callbacks =3D { + .setup =3D setup_applause_callback, + .enable =3D enable_applause_callback, + .disable =3D disable_applause_callback, + .release =3D release_applause_callback, + }, + }, + { } +}; + +/* + * Use the atomic replace by default so that the APPLAUSE state + * is correctly transferred when another version of the speaker + * livepatch gets loaded. + * + * Can be overridden by "noreplace=3D1" parameter. But it can't + * be used together with the "add_applause=3D1" parameter when + * another speaker livepatch is already loaded with + * the "add_applause=3D1" parameter. + */ static struct klp_patch patch =3D { .mod =3D THIS_MODULE, .objs =3D objs, + .replace =3D true, }; =20 static int test_klp_speaker_livepatch_init(void) { + if (add_applause) + patch.states =3D states; + + if (noreplace) { + if (add_applause) + pr_warn("The speaker livepatch can't be loaded when both \"add_applause= \" and \"noreplace\" are used and another speaker livepatch is already load= ed with \"add_aplause\"\n"); + + patch.replace =3D false; + } + return klp_enable_patch(&patch); } =20 diff --git a/tools/testing/selftests/livepatch/Makefile b/tools/testing/sel= ftests/livepatch/Makefile index 02fadc9d55e0..18c52b9e8f46 100644 --- a/tools/testing/selftests/livepatch/Makefile +++ b/tools/testing/selftests/livepatch/Makefile @@ -3,7 +3,7 @@ TEST_PROGS_EXTENDED :=3D functions.sh TEST_PROGS :=3D \ test-livepatch.sh \ - test-callbacks.sh \ + test-modules.sh \ test-shadow-vars.sh \ test-state.sh \ test-ftrace.sh \ diff --git a/tools/testing/selftests/livepatch/functions.sh b/tools/testing= /selftests/livepatch/functions.sh index c8416c54b463..a962119f053e 100644 --- a/tools/testing/selftests/livepatch/functions.sh +++ b/tools/testing/selftests/livepatch/functions.sh @@ -277,6 +277,20 @@ function set_pre_patch_ret { die "failed to set pre_patch_ret parameter for $mod module" } =20 +# read_module_param(modname, param) +# modname - module name which provides the given parameter +# param - parameter name to be read +function read_module_param { + local mod=3D"$1"; shift + local param=3D"$1" + + log "% cat /sys/module/$mod/parameters/$param" + ret=3D$(cat /sys/module/$mod/parameters/$param 2>&1) + if [[ "$ret" !=3D "" ]]; then + die "$ret" + fi +} + function start_test { local test=3D"$1" =20 @@ -336,9 +350,24 @@ function check_sysfs_value() { local rel_path=3D"$1"; shift local expected_value=3D"$1"; shift =20 +# echo "mod=3D$mod" +# echo "rel_path=3D$rel_path" +# echo "expected_value=3D$expected_value" local path=3D"$KLP_SYSFS_DIR/$mod/$rel_path" local value=3D`cat $path` if test "$value" !=3D "$expected_value" ; then die "Unexpected value in $path: $expected_value vs. $value" fi } + +# check_object_patched(livepatch_module, objname, expected_value) +# livepatch_module - livepatch module creating the sysfs interface +# objname - livepatched object to be checked +# expected_value - expected value read from the file +function check_object_patched() { + local livepatch_module=3D"$1"; shift + local objname=3D"$1"; shift + local expected_value=3D"$1"; shift + + check_sysfs_value "$livepatch_module" "$objname/patched" "$expected_value" +} diff --git a/tools/testing/selftests/livepatch/test-callbacks.sh b/tools/te= sting/selftests/livepatch/test-callbacks.sh deleted file mode 100755 index 90b26dbb2626..000000000000 --- a/tools/testing/selftests/livepatch/test-callbacks.sh +++ /dev/null @@ -1,553 +0,0 @@ -#!/bin/bash -# SPDX-License-Identifier: GPL-2.0 -# Copyright (C) 2018 Joe Lawrence - -. $(dirname $0)/functions.sh - -MOD_LIVEPATCH=3Dtest_klp_callbacks_demo -MOD_LIVEPATCH2=3Dtest_klp_callbacks_demo2 -MOD_TARGET=3Dtest_klp_callbacks_mod -MOD_TARGET_BUSY=3Dtest_klp_callbacks_busy - -setup_config - - -# Test a combination of loading a kernel module and a livepatch that -# patches a function in the first module. Load the target module -# before the livepatch module. Unload them in the same order. -# -# - On livepatch enable, before the livepatch transition starts, -# pre-patch callbacks are executed for vmlinux and $MOD_TARGET (those -# klp_objects currently loaded). After klp_objects are patched -# according to the klp_patch, their post-patch callbacks run and the -# transition completes. -# -# - Similarly, on livepatch disable, pre-patch callbacks run before the -# unpatching transition starts. klp_objects are reverted, post-patch -# callbacks execute and the transition completes. - -start_test "target module before livepatch" - -load_mod $MOD_TARGET -load_lp $MOD_LIVEPATCH -disable_lp $MOD_LIVEPATCH -unload_lp $MOD_LIVEPATCH -unload_mod $MOD_TARGET - -check_result "% modprobe $MOD_TARGET -$MOD_TARGET: ${MOD_TARGET}_init -% modprobe $MOD_LIVEPATCH -livepatch: enabling patch '$MOD_LIVEPATCH' -livepatch: '$MOD_LIVEPATCH': initializing patching transition -$MOD_LIVEPATCH: pre_patch_callback: vmlinux -$MOD_LIVEPATCH: pre_patch_callback: $MOD_TARGET -> [MODULE_STATE_LIVE] Nor= mal state -livepatch: '$MOD_LIVEPATCH': starting patching transition -livepatch: '$MOD_LIVEPATCH': completing patching transition -$MOD_LIVEPATCH: post_patch_callback: vmlinux -$MOD_LIVEPATCH: post_patch_callback: $MOD_TARGET -> [MODULE_STATE_LIVE] No= rmal state -livepatch: '$MOD_LIVEPATCH': patching complete -% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled -livepatch: '$MOD_LIVEPATCH': initializing unpatching transition -$MOD_LIVEPATCH: pre_unpatch_callback: vmlinux -$MOD_LIVEPATCH: pre_unpatch_callback: $MOD_TARGET -> [MODULE_STATE_LIVE] N= ormal state -livepatch: '$MOD_LIVEPATCH': starting unpatching transition -livepatch: '$MOD_LIVEPATCH': completing unpatching transition -$MOD_LIVEPATCH: post_unpatch_callback: vmlinux -$MOD_LIVEPATCH: post_unpatch_callback: $MOD_TARGET -> [MODULE_STATE_LIVE] = Normal state -livepatch: '$MOD_LIVEPATCH': unpatching complete -% rmmod $MOD_LIVEPATCH -% rmmod $MOD_TARGET -$MOD_TARGET: ${MOD_TARGET}_exit" - - -# This test is similar to the previous test, but (un)load the livepatch -# module before the target kernel module. This tests the livepatch -# core's module_coming handler. -# -# - On livepatch enable, only pre/post-patch callbacks are executed for -# currently loaded klp_objects, in this case, vmlinux. -# -# - When a targeted module is subsequently loaded, only its -# pre/post-patch callbacks are executed. -# -# - On livepatch disable, all currently loaded klp_objects' (vmlinux and -# $MOD_TARGET) pre/post-unpatch callbacks are executed. - -start_test "module_coming notifier" - -load_lp $MOD_LIVEPATCH -load_mod $MOD_TARGET -disable_lp $MOD_LIVEPATCH -unload_lp $MOD_LIVEPATCH -unload_mod $MOD_TARGET - -check_result "% modprobe $MOD_LIVEPATCH -livepatch: enabling patch '$MOD_LIVEPATCH' -livepatch: '$MOD_LIVEPATCH': initializing patching transition -$MOD_LIVEPATCH: pre_patch_callback: vmlinux -livepatch: '$MOD_LIVEPATCH': starting patching transition -livepatch: '$MOD_LIVEPATCH': completing patching transition -$MOD_LIVEPATCH: post_patch_callback: vmlinux -livepatch: '$MOD_LIVEPATCH': patching complete -% modprobe $MOD_TARGET -livepatch: applying patch '$MOD_LIVEPATCH' to loading module '$MOD_TARGET' -$MOD_LIVEPATCH: pre_patch_callback: $MOD_TARGET -> [MODULE_STATE_COMING] F= ull formed, running module_init -$MOD_LIVEPATCH: post_patch_callback: $MOD_TARGET -> [MODULE_STATE_COMING] = Full formed, running module_init -$MOD_TARGET: ${MOD_TARGET}_init -% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled -livepatch: '$MOD_LIVEPATCH': initializing unpatching transition -$MOD_LIVEPATCH: pre_unpatch_callback: vmlinux -$MOD_LIVEPATCH: pre_unpatch_callback: $MOD_TARGET -> [MODULE_STATE_LIVE] N= ormal state -livepatch: '$MOD_LIVEPATCH': starting unpatching transition -livepatch: '$MOD_LIVEPATCH': completing unpatching transition -$MOD_LIVEPATCH: post_unpatch_callback: vmlinux -$MOD_LIVEPATCH: post_unpatch_callback: $MOD_TARGET -> [MODULE_STATE_LIVE] = Normal state -livepatch: '$MOD_LIVEPATCH': unpatching complete -% rmmod $MOD_LIVEPATCH -% rmmod $MOD_TARGET -$MOD_TARGET: ${MOD_TARGET}_exit" - - -# Test loading the livepatch after a targeted kernel module, then unload -# the kernel module before disabling the livepatch. This tests the -# livepatch core's module_going handler. -# -# - First load a target module, then the livepatch. -# -# - When a target module is unloaded, the livepatch is only reverted -# from that klp_object ($MOD_TARGET). As such, only its pre and -# post-unpatch callbacks are executed when this occurs. -# -# - When the livepatch is disabled, pre and post-unpatch callbacks are -# run for the remaining klp_object, vmlinux. - -start_test "module_going notifier" - -load_mod $MOD_TARGET -load_lp $MOD_LIVEPATCH -unload_mod $MOD_TARGET -disable_lp $MOD_LIVEPATCH -unload_lp $MOD_LIVEPATCH - -check_result "% modprobe $MOD_TARGET -$MOD_TARGET: ${MOD_TARGET}_init -% modprobe $MOD_LIVEPATCH -livepatch: enabling patch '$MOD_LIVEPATCH' -livepatch: '$MOD_LIVEPATCH': initializing patching transition -$MOD_LIVEPATCH: pre_patch_callback: vmlinux -$MOD_LIVEPATCH: pre_patch_callback: $MOD_TARGET -> [MODULE_STATE_LIVE] Nor= mal state -livepatch: '$MOD_LIVEPATCH': starting patching transition -livepatch: '$MOD_LIVEPATCH': completing patching transition -$MOD_LIVEPATCH: post_patch_callback: vmlinux -$MOD_LIVEPATCH: post_patch_callback: $MOD_TARGET -> [MODULE_STATE_LIVE] No= rmal state -livepatch: '$MOD_LIVEPATCH': patching complete -% rmmod $MOD_TARGET -$MOD_TARGET: ${MOD_TARGET}_exit -$MOD_LIVEPATCH: pre_unpatch_callback: $MOD_TARGET -> [MODULE_STATE_GOING] = Going away -livepatch: reverting patch '$MOD_LIVEPATCH' on unloading module '$MOD_TARG= ET' -$MOD_LIVEPATCH: post_unpatch_callback: $MOD_TARGET -> [MODULE_STATE_GOING]= Going away -% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled -livepatch: '$MOD_LIVEPATCH': initializing unpatching transition -$MOD_LIVEPATCH: pre_unpatch_callback: vmlinux -livepatch: '$MOD_LIVEPATCH': starting unpatching transition -livepatch: '$MOD_LIVEPATCH': completing unpatching transition -$MOD_LIVEPATCH: post_unpatch_callback: vmlinux -livepatch: '$MOD_LIVEPATCH': unpatching complete -% rmmod $MOD_LIVEPATCH" - - -# This test is similar to the previous test, however the livepatch is -# loaded first. This tests the livepatch core's module_coming and -# module_going handlers. -# -# - First load the livepatch. -# -# - When a targeted kernel module is subsequently loaded, only its -# pre/post-patch callbacks are executed. -# -# - When the target module is unloaded, the livepatch is only reverted -# from the $MOD_TARGET klp_object. As such, only pre and -# post-unpatch callbacks are executed when this occurs. - -start_test "module_coming and module_going notifiers" - -load_lp $MOD_LIVEPATCH -load_mod $MOD_TARGET -unload_mod $MOD_TARGET -disable_lp $MOD_LIVEPATCH -unload_lp $MOD_LIVEPATCH - -check_result "% modprobe $MOD_LIVEPATCH -livepatch: enabling patch '$MOD_LIVEPATCH' -livepatch: '$MOD_LIVEPATCH': initializing patching transition -$MOD_LIVEPATCH: pre_patch_callback: vmlinux -livepatch: '$MOD_LIVEPATCH': starting patching transition -livepatch: '$MOD_LIVEPATCH': completing patching transition -$MOD_LIVEPATCH: post_patch_callback: vmlinux -livepatch: '$MOD_LIVEPATCH': patching complete -% modprobe $MOD_TARGET -livepatch: applying patch '$MOD_LIVEPATCH' to loading module '$MOD_TARGET' -$MOD_LIVEPATCH: pre_patch_callback: $MOD_TARGET -> [MODULE_STATE_COMING] F= ull formed, running module_init -$MOD_LIVEPATCH: post_patch_callback: $MOD_TARGET -> [MODULE_STATE_COMING] = Full formed, running module_init -$MOD_TARGET: ${MOD_TARGET}_init -% rmmod $MOD_TARGET -$MOD_TARGET: ${MOD_TARGET}_exit -$MOD_LIVEPATCH: pre_unpatch_callback: $MOD_TARGET -> [MODULE_STATE_GOING] = Going away -livepatch: reverting patch '$MOD_LIVEPATCH' on unloading module '$MOD_TARG= ET' -$MOD_LIVEPATCH: post_unpatch_callback: $MOD_TARGET -> [MODULE_STATE_GOING]= Going away -% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled -livepatch: '$MOD_LIVEPATCH': initializing unpatching transition -$MOD_LIVEPATCH: pre_unpatch_callback: vmlinux -livepatch: '$MOD_LIVEPATCH': starting unpatching transition -livepatch: '$MOD_LIVEPATCH': completing unpatching transition -$MOD_LIVEPATCH: post_unpatch_callback: vmlinux -livepatch: '$MOD_LIVEPATCH': unpatching complete -% rmmod $MOD_LIVEPATCH" - - -# A simple test of loading a livepatch without one of its patch target -# klp_objects ever loaded ($MOD_TARGET). -# -# - Load the livepatch. -# -# - As expected, only pre/post-(un)patch handlers are executed for -# vmlinux. - -start_test "target module not present" - -load_lp $MOD_LIVEPATCH -disable_lp $MOD_LIVEPATCH -unload_lp $MOD_LIVEPATCH - -check_result "% modprobe $MOD_LIVEPATCH -livepatch: enabling patch '$MOD_LIVEPATCH' -livepatch: '$MOD_LIVEPATCH': initializing patching transition -$MOD_LIVEPATCH: pre_patch_callback: vmlinux -livepatch: '$MOD_LIVEPATCH': starting patching transition -livepatch: '$MOD_LIVEPATCH': completing patching transition -$MOD_LIVEPATCH: post_patch_callback: vmlinux -livepatch: '$MOD_LIVEPATCH': patching complete -% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled -livepatch: '$MOD_LIVEPATCH': initializing unpatching transition -$MOD_LIVEPATCH: pre_unpatch_callback: vmlinux -livepatch: '$MOD_LIVEPATCH': starting unpatching transition -livepatch: '$MOD_LIVEPATCH': completing unpatching transition -$MOD_LIVEPATCH: post_unpatch_callback: vmlinux -livepatch: '$MOD_LIVEPATCH': unpatching complete -% rmmod $MOD_LIVEPATCH" - - -# Test a scenario where a vmlinux pre-patch callback returns a non-zero -# status (ie, failure). -# -# - First load a target module. -# -# - Load the livepatch module, setting its 'pre_patch_ret' value to -19 -# (-ENODEV). When its vmlinux pre-patch callback executes, this -# status code will propagate back to the module-loading subsystem. -# The result is that the insmod command refuses to load the livepatch -# module. - -start_test "pre-patch callback -ENODEV" - -load_mod $MOD_TARGET -load_failing_mod $MOD_LIVEPATCH pre_patch_ret=3D-19 -unload_mod $MOD_TARGET - -check_result "% modprobe $MOD_TARGET -$MOD_TARGET: ${MOD_TARGET}_init -% modprobe $MOD_LIVEPATCH pre_patch_ret=3D-19 -livepatch: enabling patch '$MOD_LIVEPATCH' -livepatch: '$MOD_LIVEPATCH': initializing patching transition -test_klp_callbacks_demo: pre_patch_callback: vmlinux -livepatch: pre-patch callback failed for object 'vmlinux' -livepatch: failed to enable patch '$MOD_LIVEPATCH' -livepatch: '$MOD_LIVEPATCH': canceling patching transition, going to unpat= ch -livepatch: '$MOD_LIVEPATCH': completing unpatching transition -livepatch: '$MOD_LIVEPATCH': unpatching complete -modprobe: ERROR: could not insert '$MOD_LIVEPATCH': No such device -% rmmod $MOD_TARGET -$MOD_TARGET: ${MOD_TARGET}_exit" - - -# Similar to the previous test, setup a livepatch such that its vmlinux -# pre-patch callback returns success. However, when a targeted kernel -# module is later loaded, have the livepatch return a failing status -# code. -# -# - Load the livepatch, vmlinux pre-patch callback succeeds. -# -# - Set a trap so subsequent pre-patch callbacks to this livepatch will -# return -ENODEV. -# -# - The livepatch pre-patch callback for subsequently loaded target -# modules will return failure, so the module loader refuses to load -# the kernel module. No post-patch or pre/post-unpatch callbacks are -# executed for this klp_object. -# -# - Pre/post-unpatch callbacks are run for the vmlinux klp_object. - -start_test "module_coming + pre-patch callback -ENODEV" - -load_lp $MOD_LIVEPATCH -set_pre_patch_ret $MOD_LIVEPATCH -19 -load_failing_mod $MOD_TARGET -disable_lp $MOD_LIVEPATCH -unload_lp $MOD_LIVEPATCH - -check_result "% modprobe $MOD_LIVEPATCH -livepatch: enabling patch '$MOD_LIVEPATCH' -livepatch: '$MOD_LIVEPATCH': initializing patching transition -$MOD_LIVEPATCH: pre_patch_callback: vmlinux -livepatch: '$MOD_LIVEPATCH': starting patching transition -livepatch: '$MOD_LIVEPATCH': completing patching transition -$MOD_LIVEPATCH: post_patch_callback: vmlinux -livepatch: '$MOD_LIVEPATCH': patching complete -% echo -19 > /sys/module/$MOD_LIVEPATCH/parameters/pre_patch_ret -% modprobe $MOD_TARGET -livepatch: applying patch '$MOD_LIVEPATCH' to loading module '$MOD_TARGET' -$MOD_LIVEPATCH: pre_patch_callback: $MOD_TARGET -> [MODULE_STATE_COMING] F= ull formed, running module_init -livepatch: pre-patch callback failed for object '$MOD_TARGET' -livepatch: patch '$MOD_LIVEPATCH' failed for module '$MOD_TARGET', refusin= g to load module '$MOD_TARGET' -modprobe: ERROR: could not insert '$MOD_TARGET': No such device -% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled -livepatch: '$MOD_LIVEPATCH': initializing unpatching transition -$MOD_LIVEPATCH: pre_unpatch_callback: vmlinux -livepatch: '$MOD_LIVEPATCH': starting unpatching transition -livepatch: '$MOD_LIVEPATCH': completing unpatching transition -$MOD_LIVEPATCH: post_unpatch_callback: vmlinux -livepatch: '$MOD_LIVEPATCH': unpatching complete -% rmmod $MOD_LIVEPATCH" - - -# Test loading multiple targeted kernel modules. This test-case is -# mainly for comparing with the next test-case. -# -# - Load a target "busy" kernel module which kicks off a worker function -# that immediately exits. -# -# - Proceed with loading the livepatch and another ordinary target -# module. Post-patch callbacks are executed and the transition -# completes quickly. - -start_test "multiple target modules" - -load_mod $MOD_TARGET_BUSY block_transition=3DN -load_lp $MOD_LIVEPATCH -load_mod $MOD_TARGET -unload_mod $MOD_TARGET -disable_lp $MOD_LIVEPATCH -unload_lp $MOD_LIVEPATCH -unload_mod $MOD_TARGET_BUSY - -check_result "% modprobe $MOD_TARGET_BUSY block_transition=3DN -$MOD_TARGET_BUSY: ${MOD_TARGET_BUSY}_init -$MOD_TARGET_BUSY: busymod_work_func enter -$MOD_TARGET_BUSY: busymod_work_func exit -% modprobe $MOD_LIVEPATCH -livepatch: enabling patch '$MOD_LIVEPATCH' -livepatch: '$MOD_LIVEPATCH': initializing patching transition -$MOD_LIVEPATCH: pre_patch_callback: vmlinux -$MOD_LIVEPATCH: pre_patch_callback: $MOD_TARGET_BUSY -> [MODULE_STATE_LIVE= ] Normal state -livepatch: '$MOD_LIVEPATCH': starting patching transition -livepatch: '$MOD_LIVEPATCH': completing patching transition -$MOD_LIVEPATCH: post_patch_callback: vmlinux -$MOD_LIVEPATCH: post_patch_callback: $MOD_TARGET_BUSY -> [MODULE_STATE_LIV= E] Normal state -livepatch: '$MOD_LIVEPATCH': patching complete -% modprobe $MOD_TARGET -livepatch: applying patch '$MOD_LIVEPATCH' to loading module '$MOD_TARGET' -$MOD_LIVEPATCH: pre_patch_callback: $MOD_TARGET -> [MODULE_STATE_COMING] F= ull formed, running module_init -$MOD_LIVEPATCH: post_patch_callback: $MOD_TARGET -> [MODULE_STATE_COMING] = Full formed, running module_init -$MOD_TARGET: ${MOD_TARGET}_init -% rmmod $MOD_TARGET -$MOD_TARGET: ${MOD_TARGET}_exit -$MOD_LIVEPATCH: pre_unpatch_callback: $MOD_TARGET -> [MODULE_STATE_GOING] = Going away -livepatch: reverting patch '$MOD_LIVEPATCH' on unloading module '$MOD_TARG= ET' -$MOD_LIVEPATCH: post_unpatch_callback: $MOD_TARGET -> [MODULE_STATE_GOING]= Going away -% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled -livepatch: '$MOD_LIVEPATCH': initializing unpatching transition -$MOD_LIVEPATCH: pre_unpatch_callback: vmlinux -$MOD_LIVEPATCH: pre_unpatch_callback: $MOD_TARGET_BUSY -> [MODULE_STATE_LI= VE] Normal state -livepatch: '$MOD_LIVEPATCH': starting unpatching transition -livepatch: '$MOD_LIVEPATCH': completing unpatching transition -$MOD_LIVEPATCH: post_unpatch_callback: vmlinux -$MOD_LIVEPATCH: post_unpatch_callback: $MOD_TARGET_BUSY -> [MODULE_STATE_L= IVE] Normal state -livepatch: '$MOD_LIVEPATCH': unpatching complete -% rmmod $MOD_LIVEPATCH -% rmmod $MOD_TARGET_BUSY -$MOD_TARGET_BUSY: ${MOD_TARGET_BUSY}_exit" - - -# A similar test as the previous one, but force the "busy" kernel module -# to block the livepatch transition. -# -# The livepatching core will refuse to patch a task that is currently -# executing a to-be-patched function -- the consistency model stalls the -# current patch transition until this safety-check is met. Test a -# scenario where one of a livepatch's target klp_objects sits on such a -# function for a long time. Meanwhile, load and unload other target -# kernel modules while the livepatch transition is in progress. -# -# - Load the "busy" kernel module, this time make its work function loop -# -# - Meanwhile, the livepatch is loaded. Notice that the patch -# transition does not complete as the targeted "busy" module is -# sitting on a to-be-patched function. -# -# - Load a second target module (this one is an ordinary idle kernel -# module). Note that *no* post-patch callbacks will be executed while -# the livepatch is still in transition. -# -# - Request an unload of the simple kernel module. The patch is still -# transitioning, so its pre-unpatch callbacks are skipped. -# -# - Finally the livepatch is disabled. Since none of the patch's -# klp_object's post-patch callbacks executed, the remaining -# klp_object's pre-unpatch callbacks are skipped. - -start_test "busy target module" - -load_mod $MOD_TARGET_BUSY block_transition=3DY -load_lp_nowait $MOD_LIVEPATCH - -# Wait until the livepatch reports in-transition state, i.e. that it's -# stalled on $MOD_TARGET_BUSY::busymod_work_func() -loop_until 'grep -q '^1$' /sys/kernel/livepatch/$MOD_LIVEPATCH/transition'= || - die "failed to stall transition" - -load_mod $MOD_TARGET -unload_mod $MOD_TARGET -disable_lp $MOD_LIVEPATCH -unload_lp $MOD_LIVEPATCH -unload_mod $MOD_TARGET_BUSY - -check_result "% modprobe $MOD_TARGET_BUSY block_transition=3DY -$MOD_TARGET_BUSY: ${MOD_TARGET_BUSY}_init -$MOD_TARGET_BUSY: busymod_work_func enter -% modprobe $MOD_LIVEPATCH -livepatch: enabling patch '$MOD_LIVEPATCH' -livepatch: '$MOD_LIVEPATCH': initializing patching transition -$MOD_LIVEPATCH: pre_patch_callback: vmlinux -$MOD_LIVEPATCH: pre_patch_callback: $MOD_TARGET_BUSY -> [MODULE_STATE_LIVE= ] Normal state -livepatch: '$MOD_LIVEPATCH': starting patching transition -% modprobe $MOD_TARGET -livepatch: applying patch '$MOD_LIVEPATCH' to loading module '$MOD_TARGET' -$MOD_LIVEPATCH: pre_patch_callback: $MOD_TARGET -> [MODULE_STATE_COMING] F= ull formed, running module_init -$MOD_TARGET: ${MOD_TARGET}_init -% rmmod $MOD_TARGET -$MOD_TARGET: ${MOD_TARGET}_exit -livepatch: reverting patch '$MOD_LIVEPATCH' on unloading module '$MOD_TARG= ET' -$MOD_LIVEPATCH: post_unpatch_callback: $MOD_TARGET -> [MODULE_STATE_GOING]= Going away -% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled -livepatch: '$MOD_LIVEPATCH': reversing transition from patching to unpatch= ing -livepatch: '$MOD_LIVEPATCH': starting unpatching transition -livepatch: '$MOD_LIVEPATCH': completing unpatching transition -$MOD_LIVEPATCH: post_unpatch_callback: vmlinux -$MOD_LIVEPATCH: post_unpatch_callback: $MOD_TARGET_BUSY -> [MODULE_STATE_L= IVE] Normal state -livepatch: '$MOD_LIVEPATCH': unpatching complete -% rmmod $MOD_LIVEPATCH -% rmmod $MOD_TARGET_BUSY -$MOD_TARGET_BUSY: busymod_work_func exit -$MOD_TARGET_BUSY: ${MOD_TARGET_BUSY}_exit" - - -# Test loading multiple livepatches. This test-case is mainly for compari= ng -# with the next test-case. -# -# - Load and unload two livepatches, pre and post (un)patch callbacks -# execute as each patch progresses through its (un)patching -# transition. - -start_test "multiple livepatches" - -load_lp $MOD_LIVEPATCH -load_lp $MOD_LIVEPATCH2 -disable_lp $MOD_LIVEPATCH2 -disable_lp $MOD_LIVEPATCH -unload_lp $MOD_LIVEPATCH2 -unload_lp $MOD_LIVEPATCH - -check_result "% modprobe $MOD_LIVEPATCH -livepatch: enabling patch '$MOD_LIVEPATCH' -livepatch: '$MOD_LIVEPATCH': initializing patching transition -$MOD_LIVEPATCH: pre_patch_callback: vmlinux -livepatch: '$MOD_LIVEPATCH': starting patching transition -livepatch: '$MOD_LIVEPATCH': completing patching transition -$MOD_LIVEPATCH: post_patch_callback: vmlinux -livepatch: '$MOD_LIVEPATCH': patching complete -% modprobe $MOD_LIVEPATCH2 -livepatch: enabling patch '$MOD_LIVEPATCH2' -livepatch: '$MOD_LIVEPATCH2': initializing patching transition -$MOD_LIVEPATCH2: pre_patch_callback: vmlinux -livepatch: '$MOD_LIVEPATCH2': starting patching transition -livepatch: '$MOD_LIVEPATCH2': completing patching transition -$MOD_LIVEPATCH2: post_patch_callback: vmlinux -livepatch: '$MOD_LIVEPATCH2': patching complete -% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH2/enabled -livepatch: '$MOD_LIVEPATCH2': initializing unpatching transition -$MOD_LIVEPATCH2: pre_unpatch_callback: vmlinux -livepatch: '$MOD_LIVEPATCH2': starting unpatching transition -livepatch: '$MOD_LIVEPATCH2': completing unpatching transition -$MOD_LIVEPATCH2: post_unpatch_callback: vmlinux -livepatch: '$MOD_LIVEPATCH2': unpatching complete -% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled -livepatch: '$MOD_LIVEPATCH': initializing unpatching transition -$MOD_LIVEPATCH: pre_unpatch_callback: vmlinux -livepatch: '$MOD_LIVEPATCH': starting unpatching transition -livepatch: '$MOD_LIVEPATCH': completing unpatching transition -$MOD_LIVEPATCH: post_unpatch_callback: vmlinux -livepatch: '$MOD_LIVEPATCH': unpatching complete -% rmmod $MOD_LIVEPATCH2 -% rmmod $MOD_LIVEPATCH" - - -# Load multiple livepatches, but the second as an 'atomic-replace' -# patch. When the latter loads, the original livepatch should be -# disabled and *none* of its pre/post-unpatch callbacks executed. On -# the other hand, when the atomic-replace livepatch is disabled, its -# pre/post-unpatch callbacks *should* be executed. -# -# - Load and unload two livepatches, the second of which has its -# .replace flag set true. -# -# - Pre and post patch callbacks are executed for both livepatches. -# -# - Once the atomic replace module is loaded, only its pre and post -# unpatch callbacks are executed. - -start_test "atomic replace" - -load_lp $MOD_LIVEPATCH -load_lp $MOD_LIVEPATCH2 replace=3D1 -disable_lp $MOD_LIVEPATCH2 -unload_lp $MOD_LIVEPATCH2 -unload_lp $MOD_LIVEPATCH - -check_result "% modprobe $MOD_LIVEPATCH -livepatch: enabling patch '$MOD_LIVEPATCH' -livepatch: '$MOD_LIVEPATCH': initializing patching transition -$MOD_LIVEPATCH: pre_patch_callback: vmlinux -livepatch: '$MOD_LIVEPATCH': starting patching transition -livepatch: '$MOD_LIVEPATCH': completing patching transition -$MOD_LIVEPATCH: post_patch_callback: vmlinux -livepatch: '$MOD_LIVEPATCH': patching complete -% modprobe $MOD_LIVEPATCH2 replace=3D1 -livepatch: enabling patch '$MOD_LIVEPATCH2' -livepatch: '$MOD_LIVEPATCH2': initializing patching transition -$MOD_LIVEPATCH2: pre_patch_callback: vmlinux -livepatch: '$MOD_LIVEPATCH2': starting patching transition -livepatch: '$MOD_LIVEPATCH2': completing patching transition -$MOD_LIVEPATCH2: post_patch_callback: vmlinux -livepatch: '$MOD_LIVEPATCH2': patching complete -% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH2/enabled -livepatch: '$MOD_LIVEPATCH2': initializing unpatching transition -$MOD_LIVEPATCH2: pre_unpatch_callback: vmlinux -livepatch: '$MOD_LIVEPATCH2': starting unpatching transition -livepatch: '$MOD_LIVEPATCH2': completing unpatching transition -$MOD_LIVEPATCH2: post_unpatch_callback: vmlinux -livepatch: '$MOD_LIVEPATCH2': unpatching complete -% rmmod $MOD_LIVEPATCH2 -% rmmod $MOD_LIVEPATCH" - - -exit 0 diff --git a/tools/testing/selftests/livepatch/test-modules.sh b/tools/test= ing/selftests/livepatch/test-modules.sh new file mode 100755 index 000000000000..1f7b502db173 --- /dev/null +++ b/tools/testing/selftests/livepatch/test-modules.sh @@ -0,0 +1,539 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2018 Joe Lawrence + +. $(dirname $0)/functions.sh + +MOD_LIVEPATCH=3Dtest_klp_speaker_livepatch +MOD_LIVEPATCH2=3Dtest_klp_speaker_livepatch2 +MOD_TARGET=3Dtest_klp_speaker +MOD_TARGET2=3Dtest_klp_speaker2 + +setup_config + +# Test basic livepatch enable/disable functionality when livepatching +# modules. +# +# Load the target module before the livepatch module. Unload them +# in the reverse order. +# +# The expected state is checked by reading "welcome" parameter +# of the target module. The livepatched variant should be printed +# when both the target and livepatch modules are loaded. + +start_test "module enable/disable livepatch" + +load_mod $MOD_TARGET +read_module_param $MOD_TARGET welcome + +load_lp $MOD_LIVEPATCH +read_module_param $MOD_TARGET welcome + +disable_lp $MOD_LIVEPATCH +unload_lp $MOD_LIVEPATCH +read_module_param $MOD_TARGET welcome + +unload_mod $MOD_TARGET + +check_result "% modprobe $MOD_TARGET +$MOD_TARGET: ${MOD_TARGET}_init +% cat /sys/module/$MOD_TARGET/parameters/welcome +$MOD_TARGET: speaker_welcome: Hello, World! +% modprobe $MOD_LIVEPATCH +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +livepatch: '$MOD_LIVEPATCH': starting patching transition +livepatch: '$MOD_LIVEPATCH': completing patching transition +livepatch: '$MOD_LIVEPATCH': patching complete +% cat /sys/module/$MOD_TARGET/parameters/welcome +$MOD_LIVEPATCH: livepatch_speaker_welcome: Ladies and gentleman, ... +% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled +livepatch: '$MOD_LIVEPATCH': initializing unpatching transition +livepatch: '$MOD_LIVEPATCH': starting unpatching transition +livepatch: '$MOD_LIVEPATCH': completing unpatching transition +livepatch: '$MOD_LIVEPATCH': unpatching complete +% rmmod $MOD_LIVEPATCH +% cat /sys/module/$MOD_TARGET/parameters/welcome +$MOD_TARGET: speaker_welcome: Hello, World! +% rmmod $MOD_TARGET +$MOD_TARGET: ${MOD_TARGET}_exit" + + +# Test the module coming hook in the module loader. +# +# Load the livepatch before the target module. Unload them in +# the same order. +# +# The livepatch hook in the module loader should print a message +# about applying the livepatch to the target module. +# +# The expected state is checked by reading "welcome" parameter +# of the target module. The livepatched variant should be printed +# when both the target and livepatch modules are loaded. + +start_test "module coming hook" + +load_lp $MOD_LIVEPATCH +load_mod $MOD_TARGET +read_module_param $MOD_TARGET welcome + +disable_lp $MOD_LIVEPATCH +read_module_param $MOD_TARGET welcome + +unload_lp $MOD_LIVEPATCH +unload_mod $MOD_TARGET + +check_result "% modprobe $MOD_LIVEPATCH +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +livepatch: '$MOD_LIVEPATCH': starting patching transition +livepatch: '$MOD_LIVEPATCH': completing patching transition +livepatch: '$MOD_LIVEPATCH': patching complete +% modprobe $MOD_TARGET +livepatch: applying patch '$MOD_LIVEPATCH' to loading module '$MOD_TARGET' +$MOD_TARGET: ${MOD_TARGET}_init +% cat /sys/module/$MOD_TARGET/parameters/welcome +$MOD_LIVEPATCH: livepatch_speaker_welcome: Ladies and gentleman, ... +% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled +livepatch: '$MOD_LIVEPATCH': initializing unpatching transition +livepatch: '$MOD_LIVEPATCH': starting unpatching transition +livepatch: '$MOD_LIVEPATCH': completing unpatching transition +livepatch: '$MOD_LIVEPATCH': unpatching complete +% cat /sys/module/$MOD_TARGET/parameters/welcome +$MOD_TARGET: speaker_welcome: Hello, World! +% rmmod $MOD_LIVEPATCH +% rmmod $MOD_TARGET +$MOD_TARGET: ${MOD_TARGET}_exit" + + +# Test the module going hook in the module loader. +# +# The livepatch hook in the module loader should print a message +# about reverting the livepatch to the target module. +# +# The expected state is checked by reading "welcome" parameter +# of the target module. The livepatched variant should be printed +# when both the target and livepatch modules are loaded. + +start_test "module going hook" + +load_mod $MOD_TARGET +read_module_param $MOD_TARGET welcome + +load_lp $MOD_LIVEPATCH +read_module_param $MOD_TARGET welcome +check_object_patched $MOD_LIVEPATCH $MOD_TARGET "1" + +unload_mod $MOD_TARGET +check_object_patched $MOD_LIVEPATCH $MOD_TARGET "0" + +disable_lp $MOD_LIVEPATCH +unload_lp $MOD_LIVEPATCH + +check_result "% modprobe $MOD_TARGET +$MOD_TARGET: ${MOD_TARGET}_init +% cat /sys/module/$MOD_TARGET/parameters/welcome +$MOD_TARGET: speaker_welcome: Hello, World! +% modprobe $MOD_LIVEPATCH +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +livepatch: '$MOD_LIVEPATCH': starting patching transition +livepatch: '$MOD_LIVEPATCH': completing patching transition +livepatch: '$MOD_LIVEPATCH': patching complete +% cat /sys/module/$MOD_TARGET/parameters/welcome +$MOD_LIVEPATCH: livepatch_speaker_welcome: Ladies and gentleman, ... +% rmmod $MOD_TARGET +$MOD_TARGET: ${MOD_TARGET}_exit +livepatch: reverting patch '$MOD_LIVEPATCH' on unloading module '$MOD_TARG= ET' +% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled +livepatch: '$MOD_LIVEPATCH': initializing unpatching transition +livepatch: '$MOD_LIVEPATCH': starting unpatching transition +livepatch: '$MOD_LIVEPATCH': completing unpatching transition +livepatch: '$MOD_LIVEPATCH': unpatching complete +% rmmod $MOD_LIVEPATCH" + +# Test the module coming and going hooks in the module loader. +# +# Load the livepatch before the target module. Unload them in the reverse = order. +# +# Both livepatch hooks in the module loader should print a message +# about applying resp. reverting the livepatch to the target module. +# +# The expected state is checked by reading "welcome" parameter +# of the target module. The livepatched variant should be printed +# when both the target and livepatch modules are loaded. + +start_test "module coming and going hooks" + +load_lp $MOD_LIVEPATCH +load_mod $MOD_TARGET +read_module_param $MOD_TARGET welcome + +unload_mod $MOD_TARGET +disable_lp $MOD_LIVEPATCH +unload_lp $MOD_LIVEPATCH + +check_result "% modprobe $MOD_LIVEPATCH +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +livepatch: '$MOD_LIVEPATCH': starting patching transition +livepatch: '$MOD_LIVEPATCH': completing patching transition +livepatch: '$MOD_LIVEPATCH': patching complete +% modprobe $MOD_TARGET +livepatch: applying patch '$MOD_LIVEPATCH' to loading module '$MOD_TARGET' +$MOD_TARGET: ${MOD_TARGET}_init +% cat /sys/module/$MOD_TARGET/parameters/welcome +$MOD_LIVEPATCH: livepatch_speaker_welcome: Ladies and gentleman, ... +% rmmod $MOD_TARGET +$MOD_TARGET: ${MOD_TARGET}_exit +livepatch: reverting patch '$MOD_LIVEPATCH' on unloading module '$MOD_TARG= ET' +% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled +livepatch: '$MOD_LIVEPATCH': initializing unpatching transition +livepatch: '$MOD_LIVEPATCH': starting unpatching transition +livepatch: '$MOD_LIVEPATCH': completing unpatching transition +livepatch: '$MOD_LIVEPATCH': unpatching complete +% rmmod $MOD_LIVEPATCH" + + +# Use shadow variables, state, and callbacks to add "[APPLAUSE] " +# into the message printed by "welcome" parameter. + +start_test "livepatch state callbacks" + +load_mod $MOD_TARGET +read_module_param $MOD_TARGET welcome + +load_lp $MOD_LIVEPATCH add_applause=3D1 +read_module_param $MOD_TARGET welcome + +disable_lp $MOD_LIVEPATCH +unload_lp $MOD_LIVEPATCH +read_module_param $MOD_TARGET welcome + +unload_mod $MOD_TARGET + +check_result "% modprobe $MOD_TARGET +$MOD_TARGET: ${MOD_TARGET}_init +% cat /sys/module/$MOD_TARGET/parameters/welcome +$MOD_TARGET: speaker_welcome: Hello, World! +% modprobe $MOD_LIVEPATCH add_applause=3D1 +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +$MOD_LIVEPATCH: setup_applause_callback: state 1 +livepatch: '$MOD_LIVEPATCH': starting patching transition +livepatch: '$MOD_LIVEPATCH': completing patching transition +$MOD_LIVEPATCH: enable_applause_callback: state 1 +livepatch: '$MOD_LIVEPATCH': patching complete +% cat /sys/module/$MOD_TARGET/parameters/welcome +$MOD_LIVEPATCH: livepatch_speaker_welcome: [APPLAUSE] Ladies and gentleman= , ... +% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled +livepatch: '$MOD_LIVEPATCH': initializing unpatching transition +$MOD_LIVEPATCH: disable_applause_callback: state 1 +livepatch: '$MOD_LIVEPATCH': starting unpatching transition +livepatch: '$MOD_LIVEPATCH': completing unpatching transition +$MOD_LIVEPATCH: release_applause_callback: state 1 +livepatch: '$MOD_LIVEPATCH': unpatching complete +% rmmod $MOD_LIVEPATCH +% cat /sys/module/$MOD_TARGET/parameters/welcome +$MOD_TARGET: speaker_welcome: Hello, World! +% rmmod $MOD_TARGET +$MOD_TARGET: ${MOD_TARGET}_exit" + +# Use shadow variables, state, and callbacks to add "[APPLAUSE] " +# into the message printed by "welcome" parameter. +# +# BUT make the "setup" callback fail. +# +# The livepatch should not get loaded. The test module should +# should stay unpatched which is checked by reading the "welcome" +# parameter. + +start_test "failing livepatch setup callback with -ENODEV" + +load_mod $MOD_TARGET +read_module_param $MOD_TARGET welcome + +load_failing_mod $MOD_LIVEPATCH add_applause=3D1 setup_ret=3D-19 +read_module_param $MOD_TARGET welcome + +unload_mod $MOD_TARGET + +check_result "% modprobe $MOD_TARGET +$MOD_TARGET: ${MOD_TARGET}_init +% cat /sys/module/$MOD_TARGET/parameters/welcome +$MOD_TARGET: speaker_welcome: Hello, World! +% modprobe $MOD_LIVEPATCH add_applause=3D1 setup_ret=3D-19 +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +$MOD_LIVEPATCH: setup_applause_callback: state 1 +$MOD_LIVEPATCH: setup_applause_callback: forcing err: -ENODEV +livepatch: failed to enable patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': canceling patching transition, going to unpat= ch +livepatch: '$MOD_LIVEPATCH': completing unpatching transition +livepatch: '$MOD_LIVEPATCH': unpatching complete +modprobe: ERROR: could not insert '$MOD_LIVEPATCH': No such device +% cat /sys/module/$MOD_TARGET/parameters/welcome +$MOD_TARGET: speaker_welcome: Hello, World! +% rmmod $MOD_TARGET +$MOD_TARGET: ${MOD_TARGET}_exit" + +# Test loading multiple targeted kernel modules. This test-case is +# mainly for comparing with the next test-case. +# +# The livepatch gets loaded between two target modules. It adds +# a livepatch state, callbacks, and shadow variable which would +# add "[APPLAUSE] " into the message printed when reading +# the "welcome" parameters of the two target modules. +# +# All four state callbacks should get called. And the message +# "[APPLAUSE] Ladies and gentleman, ..." should be printed when +# reading the "welcome" parameter while the livepatch is enabled. + +start_test "multiple target modules" + +load_mod $MOD_TARGET +read_module_param $MOD_TARGET welcome + +load_lp $MOD_LIVEPATCH add_applause=3D1 +read_module_param $MOD_TARGET welcome + +load_mod $MOD_TARGET2 +read_module_param $MOD_TARGET2 welcome + +unload_mod $MOD_TARGET2 +disable_lp $MOD_LIVEPATCH +read_module_param $MOD_TARGET welcome + +unload_lp $MOD_LIVEPATCH +unload_mod $MOD_TARGET + +check_result "% modprobe $MOD_TARGET +$MOD_TARGET: ${MOD_TARGET}_init +% cat /sys/module/$MOD_TARGET/parameters/welcome +$MOD_TARGET: speaker_welcome: Hello, World! +% modprobe $MOD_LIVEPATCH add_applause=3D1 +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +$MOD_LIVEPATCH: setup_applause_callback: state 1 +livepatch: '$MOD_LIVEPATCH': starting patching transition +livepatch: '$MOD_LIVEPATCH': completing patching transition +$MOD_LIVEPATCH: enable_applause_callback: state 1 +livepatch: '$MOD_LIVEPATCH': patching complete +% cat /sys/module/$MOD_TARGET/parameters/welcome +$MOD_LIVEPATCH: livepatch_speaker_welcome: [APPLAUSE] Ladies and gentleman= , ... +% modprobe $MOD_TARGET2 +livepatch: applying patch '$MOD_LIVEPATCH' to loading module '$MOD_TARGET2' +$MOD_TARGET2: ${MOD_TARGET}_init +% cat /sys/module/$MOD_TARGET2/parameters/welcome +$MOD_LIVEPATCH: livepatch_speaker_welcome2: [APPLAUSE] Ladies and gentlema= n, ... +% rmmod $MOD_TARGET2 +$MOD_TARGET2: ${MOD_TARGET}_exit +livepatch: reverting patch '$MOD_LIVEPATCH' on unloading module '$MOD_TARG= ET2' +% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled +livepatch: '$MOD_LIVEPATCH': initializing unpatching transition +$MOD_LIVEPATCH: disable_applause_callback: state 1 +livepatch: '$MOD_LIVEPATCH': starting unpatching transition +livepatch: '$MOD_LIVEPATCH': completing unpatching transition +$MOD_LIVEPATCH: release_applause_callback: state 1 +livepatch: '$MOD_LIVEPATCH': unpatching complete +% cat /sys/module/$MOD_TARGET/parameters/welcome +$MOD_TARGET: speaker_welcome: Hello, World! +% rmmod $MOD_LIVEPATCH +% rmmod $MOD_TARGET +$MOD_TARGET: ${MOD_TARGET}_exit" + +# Test loading multiple livepatches. This test-case is mainly for compari= ng +# with the next test-case. +# +# The patching and unpatching transition should be done for both livepatch= es. + +start_test "multiple livepatches in parallel" + +load_lp $MOD_LIVEPATCH +load_lp $MOD_LIVEPATCH2 noreplace=3D1 +disable_lp $MOD_LIVEPATCH2 +disable_lp $MOD_LIVEPATCH +unload_lp $MOD_LIVEPATCH2 +unload_lp $MOD_LIVEPATCH + +check_result "% modprobe $MOD_LIVEPATCH +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +livepatch: '$MOD_LIVEPATCH': starting patching transition +livepatch: '$MOD_LIVEPATCH': completing patching transition +livepatch: '$MOD_LIVEPATCH': patching complete +% modprobe $MOD_LIVEPATCH2 noreplace=3D1 +livepatch: enabling patch '$MOD_LIVEPATCH2' +livepatch: '$MOD_LIVEPATCH2': initializing patching transition +livepatch: '$MOD_LIVEPATCH2': starting patching transition +livepatch: '$MOD_LIVEPATCH2': completing patching transition +livepatch: '$MOD_LIVEPATCH2': patching complete +% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH2/enabled +livepatch: '$MOD_LIVEPATCH2': initializing unpatching transition +livepatch: '$MOD_LIVEPATCH2': starting unpatching transition +livepatch: '$MOD_LIVEPATCH2': completing unpatching transition +livepatch: '$MOD_LIVEPATCH2': unpatching complete +% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled +livepatch: '$MOD_LIVEPATCH': initializing unpatching transition +livepatch: '$MOD_LIVEPATCH': starting unpatching transition +livepatch: '$MOD_LIVEPATCH': completing unpatching transition +livepatch: '$MOD_LIVEPATCH': unpatching complete +% rmmod $MOD_LIVEPATCH2 +% rmmod $MOD_LIVEPATCH" + + +# Load multiple livepatches, but the second as an 'atomic-replace' +# patch. +# +# The 2nd livepatch will replace the 1st one. As a result, the 1s patch +# can be removed wihtout the unpatch transition. + +start_test "atomic replace" + +load_lp $MOD_LIVEPATCH +load_lp $MOD_LIVEPATCH2 +disable_lp $MOD_LIVEPATCH2 +unload_lp $MOD_LIVEPATCH2 +unload_lp $MOD_LIVEPATCH + +check_result "% modprobe $MOD_LIVEPATCH +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +livepatch: '$MOD_LIVEPATCH': starting patching transition +livepatch: '$MOD_LIVEPATCH': completing patching transition +livepatch: '$MOD_LIVEPATCH': patching complete +% modprobe $MOD_LIVEPATCH2 +livepatch: enabling patch '$MOD_LIVEPATCH2' +livepatch: '$MOD_LIVEPATCH2': initializing patching transition +livepatch: '$MOD_LIVEPATCH2': starting patching transition +livepatch: '$MOD_LIVEPATCH2': completing patching transition +livepatch: '$MOD_LIVEPATCH2': patching complete +% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH2/enabled +livepatch: '$MOD_LIVEPATCH2': initializing unpatching transition +livepatch: '$MOD_LIVEPATCH2': starting unpatching transition +livepatch: '$MOD_LIVEPATCH2': completing unpatching transition +livepatch: '$MOD_LIVEPATCH2': unpatching complete +% rmmod $MOD_LIVEPATCH2 +% rmmod $MOD_LIVEPATCH" + +exit 0 + +# FIXME: +# +# The test below does not work and I do not know why. +# +# It seems that the livepatched function call_speaker() +# is not on the stack of the workqueue worker which +# is processing the speaker work. +# +# Even though, there is a worker which is waiting in +# speaker_wait_and_welcome(). But the stack looks like: +# +# [<0>] msleep+0x36/0x40 +# [<0>] speaker_wait_and_welcome+0x40/0x60 [test_klp_speaker] +# [<0>] process_scheduled_works+0x2b4/0x530 +# [<0>] worker_thread+0x174/0x340 +# [<0>] kthread+0x100/0x130 +# [<0>] ret_from_fork+0x2d/0x50 +# [<0>] ret_from_fork_asm+0x1b/0x30 +# +# Note that I have got the same stack by adding show_stack() +# directly into speaker_wait_and_welcome() function. +# +# Also note that the speaker must be waiting when the livepatch +# gets loaded. The speaker work is queued when +# the "waiting_speaker=3D1" parameter is proceed. And +# waiting_welcome_set() waits until the worker started waiting. +# +# And the function is supposed to be on the stack. It is called +# via the speaker->call() callback. And it is marked as noinline. +# +# Note that "call_speaker() did not appear on the stack even +# when I tried to call it directly from speaker_func(). +# +# BTW: speaker_func() is not on the stack either. And it can't +# be inlined because it is callback for the workqueue work. +# +###################################################################### +# +# A similar test as the previous one, but force the "busy" kernel module +# to block the livepatch transition. +# +# The livepatching core will refuse to patch a task that is currently +# executing a to-be-patched function -- the consistency model stalls the +# current patch transition until this safety-check is met. Test a +# scenario where one of a livepatch's target klp_objects sits on such a +# function for a long time. Meanwhile, load and unload other target +# kernel modules while the livepatch transition is in progress. +# +# Note: +# +# - The started patching transion never finishes. Only "setup" +# callback is called. +# +# - When reading the "welcome" parameter, the livepatched message +# is printed because it is a new process. But [APPLAUSE] is not +# printed because the "enable" callback has not been called. +# +# - When the livepatch gets disabled, the current transiton gets +# reverted instead of starting a new disable transition. Only +# the "remove" callback is called. +start_test "busy target module" + +load_mod $MOD_TARGET waiting_welcome=3D1 +# Wait until the asynchronous speaker started waiting. +loop_until 'grep -q '^1$' /sys/module/$MOD_TARGET/parameters/started_waiti= ng' || + die "failed to stall transition" +read_module_param $MOD_TARGET welcome + +load_lp_nowait $MOD_LIVEPATCH add_applause=3D1 +# Wait until the livepatch reports in-transition state, i.e. that it's +# stalled because of the process with the waiting speaker +loop_until 'grep -q '^1$' /sys/kernel/livepatch/$MOD_LIVEPATCH/transition'= || + die "failed to stall transition" +read_module_param $MOD_TARGET welcome + +load_mod $MOD_TARGET2 +read_module_param $MOD_TARGET2 welcome + +unload_mod $MOD_TARGET2 +disable_lp $MOD_LIVEPATCH +read_module_param $MOD_TARGET welcome + +unload_lp $MOD_LIVEPATCH +unload_mod $MOD_TARGET + +check_result "% modprobe $MOD_TARGET waiting_welcome=3D1 +$MOD_TARGET: call_speaker: Calling speaker. +$MOD_TARGET: speaker_wait_and_welcome: Speaker started waiting. +$MOD_TARGET: ${MOD_TARGET}_init +% cat /sys/module/$MOD_TARGET/parameters/welcome +$MOD_TARGET: speaker_welcome: Hello, World! +% modprobe $MOD_LIVEPATCH add_applause=3D1 +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +$MOD_LIVEPATCH: setup_applause_callback: state 1 +livepatch: '$MOD_LIVEPATCH': starting patching transition +% cat /sys/module/$MOD_TARGET/parameters/welcome +$MOD_LIVEPATCH: livepatch_speaker_welcome: [] Ladies and gentleman, ... +% modprobe $MOD_TARGET2 +livepatch: applying patch '$MOD_LIVEPATCH' to loading module '$MOD_TARGET2' +$MOD_TARGET2: ${MOD_TARGET}_init +% cat /sys/module/$MOD_TARGET2/parameters/welcome +$MOD_LIVEPATCH: livepatch_speaker_welcome2: [] Ladies and gentleman, ... +% rmmod $MOD_TARGET2 +$MOD_TARGET2: ${MOD_TARGET}_exit +livepatch: reverting patch '$MOD_LIVEPATCH' on unloading module '$MOD_TARG= ET2' +% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled +livepatch: '$MOD_LIVEPATCH': reversing transition from patching to unpatch= ing +livepatch: '$MOD_LIVEPATCH': starting unpatching transition +livepatch: '$MOD_LIVEPATCH': completing unpatching transition +$MOD_LIVEPATCH: release_applause_callback: state 1 +livepatch: '$MOD_LIVEPATCH': unpatching complete +% cat /sys/module/$MOD_TARGET/parameters/welcome +$MOD_TARGET: speaker_welcome: Hello, World! +% rmmod $MOD_LIVEPATCH +% rmmod $MOD_TARGET +$MOD_TARGET: ${MOD_TARGET}_exit +$MOD_TARGET: speaker_welcome: Hello, World!" + --=20 2.35.3 From nobody Tue Dec 16 07:09:08 2025 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 691F0C4332F for ; Fri, 10 Nov 2023 17:42:09 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1344535AbjKJRmI (ORCPT ); Fri, 10 Nov 2023 12:42:08 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:46440 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229761AbjKJRli (ORCPT ); Fri, 10 Nov 2023 12:41:38 -0500 Received: from smtp-out1.suse.de (smtp-out1.suse.de [IPv6:2001:67c:2178:6::1c]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 47FBE431C5; Fri, 10 Nov 2023 09:06:04 -0800 (PST) Received: from relay2.suse.de (relay2.suse.de [149.44.160.134]) by smtp-out1.suse.de (Postfix) with ESMTP id EBDF1219B2; Fri, 10 Nov 2023 17:06:02 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1699635962; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=F3XpT26i+blZpynYH3Yln/cBgtmR5OHvdHE7ZoPL7YU=; b=lN0j7vPd3dCFf9osuPXOqBOvN6CWwOxCOIsEK+CEMuls7Cw90Vg5fUCJU9PSoodU1KgXQH ymaHs6HgJFvIuH9t9iUjqWB3rcFCRDBvVfMkXwBODuXQxbPOoAgX+PI9uoiXCwvJaG3haz ckGetlS8jcA8uBd5Xcs0f2dRW1e4IdM= Received: from alley.suse.cz (pmladek.udp.ovpn2.prg.suse.de [10.100.201.202]) by relay2.suse.de (Postfix) with ESMTP id 817EC2C188; Fri, 10 Nov 2023 17:06:02 +0000 (UTC) From: Petr Mladek To: Josh Poimboeuf , Miroslav Benes Cc: Joe Lawrence , Nicolai Stange , live-patching@vger.kernel.org, linux-kernel@vger.kernel.org, Petr Mladek Subject: [POC 6/7] livepatch: Remove the obsolete per-object callbacks Date: Fri, 10 Nov 2023 18:04:27 +0100 Message-Id: <20231110170428.6664-7-pmladek@suse.com> X-Mailer: git-send-email 2.35.3 In-Reply-To: <20231110170428.6664-1-pmladek@suse.com> References: <20231110170428.6664-1-pmladek@suse.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" All selftests have been migrated to the new per-state callbacks. And the obsoleted per-object callbacks can be safely removed. FIXME: This patch is removing the sample module and documentation without any replacement. They obviously have to be converted for the state-callbacks. It has been postponed until the approach has been approved in the POC stage. Signed-off-by: Petr Mladek --- Documentation/livepatch/callbacks.rst | 133 ------------ Documentation/livepatch/index.rst | 1 - include/linux/livepatch.h | 25 --- kernel/livepatch/core.c | 29 --- kernel/livepatch/core.h | 33 --- kernel/livepatch/transition.c | 9 - samples/livepatch/Makefile | 3 - .../livepatch/livepatch-callbacks-busymod.c | 60 ------ samples/livepatch/livepatch-callbacks-demo.c | 196 ------------------ samples/livepatch/livepatch-callbacks-mod.c | 41 ---- 10 files changed, 530 deletions(-) delete mode 100644 Documentation/livepatch/callbacks.rst delete mode 100644 samples/livepatch/livepatch-callbacks-busymod.c delete mode 100644 samples/livepatch/livepatch-callbacks-demo.c delete mode 100644 samples/livepatch/livepatch-callbacks-mod.c diff --git a/Documentation/livepatch/callbacks.rst b/Documentation/livepatc= h/callbacks.rst deleted file mode 100644 index 470944aa8658..000000000000 --- a/Documentation/livepatch/callbacks.rst +++ /dev/null @@ -1,133 +0,0 @@ -=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D -(Un)patching Callbacks -=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D - -Livepatch (un)patch-callbacks provide a mechanism for livepatch modules -to execute callback functions when a kernel object is (un)patched. They -can be considered a **power feature** that **extends livepatching abilitie= s** -to include: - - - Safe updates to global data - - - "Patches" to init and probe functions - - - Patching otherwise unpatchable code (i.e. assembly) - -In most cases, (un)patch callbacks will need to be used in conjunction -with memory barriers and kernel synchronization primitives, like -mutexes/spinlocks, or even stop_machine(), to avoid concurrency issues. - -1. Motivation -=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D - -Callbacks differ from existing kernel facilities: - - - Module init/exit code doesn't run when disabling and re-enabling a - patch. - - - A module notifier can't stop a to-be-patched module from loading. - -Callbacks are part of the klp_object structure and their implementation -is specific to that klp_object. Other livepatch objects may or may not -be patched, irrespective of the target klp_object's current state. - -2. Callback types -=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D - -Callbacks can be registered for the following livepatch actions: - - * Pre-patch - - before a klp_object is patched - - * Post-patch - - after a klp_object has been patched and is active - across all tasks - - * Pre-unpatch - - before a klp_object is unpatched (ie, patched code is - active), used to clean up post-patch callback - resources - - * Post-unpatch - - after a klp_object has been patched, all code has - been restored and no tasks are running patched code, - used to cleanup pre-patch callback resources - -3. How it works -=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D - -Each callback is optional, omitting one does not preclude specifying any -other. However, the livepatching core executes the handlers in -symmetry: pre-patch callbacks have a post-unpatch counterpart and -post-patch callbacks have a pre-unpatch counterpart. An unpatch -callback will only be executed if its corresponding patch callback was -executed. Typical use cases pair a patch handler that acquires and -configures resources with an unpatch handler tears down and releases -those same resources. - -A callback is only executed if its host klp_object is loaded. For -in-kernel vmlinux targets, this means that callbacks will always execute -when a livepatch is enabled/disabled. For patch target kernel modules, -callbacks will only execute if the target module is loaded. When a -module target is (un)loaded, its callbacks will execute only if the -livepatch module is enabled. - -The pre-patch callback, if specified, is expected to return a status -code (0 for success, -ERRNO on error). An error status code indicates -to the livepatching core that patching of the current klp_object is not -safe and to stop the current patching request. (When no pre-patch -callback is provided, the transition is assumed to be safe.) If a -pre-patch callback returns failure, the kernel's module loader will: - - - Refuse to load a livepatch, if the livepatch is loaded after - targeted code. - - or: - - - Refuse to load a module, if the livepatch was already successfully - loaded. - -No post-patch, pre-unpatch, or post-unpatch callbacks will be executed -for a given klp_object if the object failed to patch, due to a failed -pre_patch callback or for any other reason. - -If a patch transition is reversed, no pre-unpatch handlers will be run -(this follows the previously mentioned symmetry -- pre-unpatch callbacks -will only occur if their corresponding post-patch callback executed). - -If the object did successfully patch, but the patch transition never -started for some reason (e.g., if another object failed to patch), -only the post-unpatch callback will be called. - -4. Use cases -=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D - -Sample livepatch modules demonstrating the callback API can be found in -samples/livepatch/ directory. These samples were modified for use in -kselftests and can be found in the lib/livepatch directory. - -Global data update ------------------- - -A pre-patch callback can be useful to update a global variable. For -example, 75ff39ccc1bd ("tcp: make challenge acks less predictable") -changes a global sysctl, as well as patches the tcp_send_challenge_ack() -function. - -In this case, if we're being super paranoid, it might make sense to -patch the data *after* patching is complete with a post-patch callback, -so that tcp_send_challenge_ack() could first be changed to read -sysctl_tcp_challenge_ack_limit with READ_ONCE. - -__init and probe function patches support ------------------------------------------ - -Although __init and probe functions are not directly livepatch-able, it -may be possible to implement similar updates via pre/post-patch -callbacks. - -The commit ``48900cb6af42 ("virtio-net: drop NETIF_F_FRAGLIST")`` change t= he way that -virtnet_probe() initialized its driver's net_device features. A -pre/post-patch callback could iterate over all such devices, making a -similar change to their hw_features value. (Client functions of the -value may need to be updated accordingly.) diff --git a/Documentation/livepatch/index.rst b/Documentation/livepatch/in= dex.rst index cebf1c71d4a5..997b5ddf4779 100644 --- a/Documentation/livepatch/index.rst +++ b/Documentation/livepatch/index.rst @@ -8,7 +8,6 @@ Kernel Livepatching :maxdepth: 1 =20 livepatch - callbacks cumulative-patches module-elf-format shadow-vars diff --git a/include/linux/livepatch.h b/include/linux/livepatch.h index 189ec7c6a89f..3807c7bb0281 100644 --- a/include/linux/livepatch.h +++ b/include/linux/livepatch.h @@ -77,30 +77,6 @@ struct klp_func { bool transition; }; =20 -struct klp_object; - -/** - * struct klp_callbacks - pre/post live-(un)patch callback structure - * @pre_patch: executed before code patching - * @post_patch: executed after code patching - * @pre_unpatch: executed before code unpatching - * @post_unpatch: executed after code unpatching - * @post_unpatch_enabled: flag indicating if post-unpatch callback - * should run - * - * All callbacks are optional. Only the pre-patch callback, if provided, - * will be unconditionally executed. If the parent klp_object fails to - * patch for any reason, including a non-zero error status returned from - * the pre-patch callback, no further callbacks will be executed. - */ -struct klp_callbacks { - int (*pre_patch)(struct klp_object *obj); - void (*post_patch)(struct klp_object *obj); - void (*pre_unpatch)(struct klp_object *obj); - void (*post_unpatch)(struct klp_object *obj); - bool post_unpatch_enabled; -}; - /** * struct klp_object - kernel object structure for live patching * @name: module name (or NULL for vmlinux) @@ -118,7 +94,6 @@ struct klp_object { /* external */ const char *name; struct klp_func *funcs; - struct klp_callbacks callbacks; =20 /* internal */ struct kobject kobj; diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c index a4a3fe7907ad..d982365777f1 100644 --- a/kernel/livepatch/core.c +++ b/kernel/livepatch/core.c @@ -965,8 +965,6 @@ static int klp_init_patch(struct klp_patch *patch) =20 static int __klp_disable_patch(struct klp_patch *patch) { - struct klp_object *obj; - if (WARN_ON(!patch->enabled)) return -EINVAL; =20 @@ -977,10 +975,6 @@ static int __klp_disable_patch(struct klp_patch *patch) =20 klp_disable_states(patch); =20 - klp_for_each_object(patch, obj) - if (obj->patched) - klp_pre_unpatch_callback(obj); - /* * Enforce the order of the func->transition writes in * klp_init_transition() and the TIF_PATCH_PENDING writes in @@ -1032,13 +1026,6 @@ static int __klp_enable_patch(struct klp_patch *patc= h) if (!klp_is_object_loaded(obj)) continue; =20 - ret =3D klp_pre_patch_callback(obj); - if (ret) { - pr_warn("pre-patch callback failed for object '%s'\n", - klp_is_module(obj) ? obj->name : "vmlinux"); - goto err_states; - } - ret =3D klp_patch_object(obj); if (ret) { pr_warn("failed to patch object '%s'\n", @@ -1214,14 +1201,10 @@ static void klp_cleanup_module_patches_limited(stru= ct module *mod, if (!klp_is_module(obj) || strcmp(obj->name, mod->name)) continue; =20 - if (patch !=3D klp_transition_patch) - klp_pre_unpatch_callback(obj); - pr_notice("reverting patch '%s' on unloading module '%s'\n", patch->mod->name, obj->mod->name); klp_unpatch_object(obj); =20 - klp_post_unpatch_callback(obj); klp_clear_object_relocs(patch, obj); klp_free_object_loaded(obj); break; @@ -1268,25 +1251,13 @@ int klp_module_coming(struct module *mod) pr_notice("applying patch '%s' to loading module '%s'\n", patch->mod->name, obj->mod->name); =20 - ret =3D klp_pre_patch_callback(obj); - if (ret) { - pr_warn("pre-patch callback failed for object '%s'\n", - obj->name); - goto err; - } - ret =3D klp_patch_object(obj); if (ret) { pr_warn("failed to apply patch '%s' to module '%s' (%d)\n", patch->mod->name, obj->mod->name, ret); - - klp_post_unpatch_callback(obj); goto err; } =20 - if (patch !=3D klp_transition_patch) - klp_post_patch_callback(obj); - break; } } diff --git a/kernel/livepatch/core.h b/kernel/livepatch/core.h index 38209c7361b6..02b8364f6779 100644 --- a/kernel/livepatch/core.h +++ b/kernel/livepatch/core.h @@ -23,37 +23,4 @@ static inline bool klp_is_object_loaded(struct klp_objec= t *obj) return !obj->name || obj->mod; } =20 -static inline int klp_pre_patch_callback(struct klp_object *obj) -{ - int ret =3D 0; - - if (obj->callbacks.pre_patch) - ret =3D (*obj->callbacks.pre_patch)(obj); - - obj->callbacks.post_unpatch_enabled =3D !ret; - - return ret; -} - -static inline void klp_post_patch_callback(struct klp_object *obj) -{ - if (obj->callbacks.post_patch) - (*obj->callbacks.post_patch)(obj); -} - -static inline void klp_pre_unpatch_callback(struct klp_object *obj) -{ - if (obj->callbacks.pre_unpatch) - (*obj->callbacks.pre_unpatch)(obj); -} - -static inline void klp_post_unpatch_callback(struct klp_object *obj) -{ - if (obj->callbacks.post_unpatch_enabled && - obj->callbacks.post_unpatch) - (*obj->callbacks.post_unpatch)(obj); - - obj->callbacks.post_unpatch_enabled =3D false; -} - #endif /* _LIVEPATCH_CORE_H */ diff --git a/kernel/livepatch/transition.c b/kernel/livepatch/transition.c index cfa1ab10feb7..1826e08a31dd 100644 --- a/kernel/livepatch/transition.c +++ b/kernel/livepatch/transition.c @@ -148,15 +148,6 @@ static void klp_complete_transition(void) klp_release_states(klp_transition_patch); } =20 - klp_for_each_object(klp_transition_patch, obj) { - if (!klp_is_object_loaded(obj)) - continue; - if (klp_target_state =3D=3D KLP_PATCHED) - klp_post_patch_callback(obj); - else if (klp_target_state =3D=3D KLP_UNPATCHED) - klp_post_unpatch_callback(obj); - } - pr_notice("'%s': %s complete\n", klp_transition_patch->mod->name, klp_target_state =3D=3D KLP_PATCHED ? "patching" : "unpatching"); =20 diff --git a/samples/livepatch/Makefile b/samples/livepatch/Makefile index 9f853eeb6140..5ad205c61a67 100644 --- a/samples/livepatch/Makefile +++ b/samples/livepatch/Makefile @@ -3,6 +3,3 @@ obj-$(CONFIG_SAMPLE_LIVEPATCH) +=3D livepatch-sample.o obj-$(CONFIG_SAMPLE_LIVEPATCH) +=3D livepatch-shadow-mod.o obj-$(CONFIG_SAMPLE_LIVEPATCH) +=3D livepatch-shadow-fix1.o obj-$(CONFIG_SAMPLE_LIVEPATCH) +=3D livepatch-shadow-fix2.o -obj-$(CONFIG_SAMPLE_LIVEPATCH) +=3D livepatch-callbacks-demo.o -obj-$(CONFIG_SAMPLE_LIVEPATCH) +=3D livepatch-callbacks-mod.o -obj-$(CONFIG_SAMPLE_LIVEPATCH) +=3D livepatch-callbacks-busymod.o diff --git a/samples/livepatch/livepatch-callbacks-busymod.c b/samples/live= patch/livepatch-callbacks-busymod.c deleted file mode 100644 index 378e2d40271a..000000000000 --- a/samples/livepatch/livepatch-callbacks-busymod.c +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2017 Joe Lawrence - */ - -/* - * livepatch-callbacks-busymod.c - (un)patching callbacks demo support mod= ule - * - * - * Purpose - * ------- - * - * Simple module to demonstrate livepatch (un)patching callbacks. - * - * - * Usage - * ----- - * - * This module is not intended to be standalone. See the "Usage" - * section of livepatch-callbacks-mod.c. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include - -static int sleep_secs; -module_param(sleep_secs, int, 0644); -MODULE_PARM_DESC(sleep_secs, "sleep_secs (default=3D0)"); - -static void busymod_work_func(struct work_struct *work); -static DECLARE_DELAYED_WORK(work, busymod_work_func); - -static void busymod_work_func(struct work_struct *work) -{ - pr_info("%s, sleeping %d seconds ...\n", __func__, sleep_secs); - msleep(sleep_secs * 1000); - pr_info("%s exit\n", __func__); -} - -static int livepatch_callbacks_mod_init(void) -{ - pr_info("%s\n", __func__); - schedule_delayed_work(&work, - msecs_to_jiffies(1000 * 0)); - return 0; -} - -static void livepatch_callbacks_mod_exit(void) -{ - cancel_delayed_work_sync(&work); - pr_info("%s\n", __func__); -} - -module_init(livepatch_callbacks_mod_init); -module_exit(livepatch_callbacks_mod_exit); -MODULE_LICENSE("GPL"); diff --git a/samples/livepatch/livepatch-callbacks-demo.c b/samples/livepat= ch/livepatch-callbacks-demo.c deleted file mode 100644 index 11c3f4357812..000000000000 --- a/samples/livepatch/livepatch-callbacks-demo.c +++ /dev/null @@ -1,196 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2017 Joe Lawrence - */ - -/* - * livepatch-callbacks-demo.c - (un)patching callbacks livepatch demo - * - * - * Purpose - * ------- - * - * Demonstration of registering livepatch (un)patching callbacks. - * - * - * Usage - * ----- - * - * Step 1 - load the simple module - * - * insmod samples/livepatch/livepatch-callbacks-mod.ko - * - * - * Step 2 - load the demonstration livepatch (with callbacks) - * - * insmod samples/livepatch/livepatch-callbacks-demo.ko - * - * - * Step 3 - cleanup - * - * echo 0 > /sys/kernel/livepatch/livepatch_callbacks_demo/enabled - * rmmod livepatch_callbacks_demo - * rmmod livepatch_callbacks_mod - * - * Watch dmesg output to see livepatch enablement, callback execution - * and patching operations for both vmlinux and module targets. - * - * NOTE: swap the insmod order of livepatch-callbacks-mod.ko and - * livepatch-callbacks-demo.ko to observe what happens when a - * target module is loaded after a livepatch with callbacks. - * - * NOTE: 'pre_patch_ret' is a module parameter that sets the pre-patch - * callback return status. Try setting up a non-zero status - * such as -19 (-ENODEV): - * - * # Load demo livepatch, vmlinux is patched - * insmod samples/livepatch/livepatch-callbacks-demo.ko - * - * # Setup next pre-patch callback to return -ENODEV - * echo -19 > /sys/module/livepatch_callbacks_demo/parameters/pre_pa= tch_ret - * - * # Module loader refuses to load the target module - * insmod samples/livepatch/livepatch-callbacks-mod.ko - * insmod: ERROR: could not insert module samples/livepatch/livepatc= h-callbacks-mod.ko: No such device - * - * NOTE: There is a second target module, - * livepatch-callbacks-busymod.ko, available for experimenting - * with livepatch (un)patch callbacks. This module contains - * a 'sleep_secs' parameter that parks the module on one of the - * functions that the livepatch demo module wants to patch. - * Modifying this value and tweaking the order of module loads can - * effectively demonstrate stalled patch transitions: - * - * # Load a target module, let it park on 'busymod_work_func' for - * # thirty seconds - * insmod samples/livepatch/livepatch-callbacks-busymod.ko sleep_sec= s=3D30 - * - * # Meanwhile load the livepatch - * insmod samples/livepatch/livepatch-callbacks-demo.ko - * - * # ... then load and unload another target module while the - * # transition is in progress - * insmod samples/livepatch/livepatch-callbacks-mod.ko - * rmmod samples/livepatch/livepatch-callbacks-mod.ko - * - * # Finally cleanup - * echo 0 > /sys/kernel/livepatch/livepatch_callbacks_demo/enabled - * rmmod samples/livepatch/livepatch-callbacks-demo.ko - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include - -static int pre_patch_ret; -module_param(pre_patch_ret, int, 0644); -MODULE_PARM_DESC(pre_patch_ret, "pre_patch_ret (default=3D0)"); - -static const char *const module_state[] =3D { - [MODULE_STATE_LIVE] =3D "[MODULE_STATE_LIVE] Normal state", - [MODULE_STATE_COMING] =3D "[MODULE_STATE_COMING] Full formed, running mod= ule_init", - [MODULE_STATE_GOING] =3D "[MODULE_STATE_GOING] Going away", - [MODULE_STATE_UNFORMED] =3D "[MODULE_STATE_UNFORMED] Still setting it up", -}; - -static void callback_info(const char *callback, struct klp_object *obj) -{ - if (obj->mod) - pr_info("%s: %s -> %s\n", callback, obj->mod->name, - module_state[obj->mod->state]); - else - pr_info("%s: vmlinux\n", callback); -} - -/* Executed on object patching (ie, patch enablement) */ -static int pre_patch_callback(struct klp_object *obj) -{ - callback_info(__func__, obj); - return pre_patch_ret; -} - -/* Executed on object unpatching (ie, patch disablement) */ -static void post_patch_callback(struct klp_object *obj) -{ - callback_info(__func__, obj); -} - -/* Executed on object unpatching (ie, patch disablement) */ -static void pre_unpatch_callback(struct klp_object *obj) -{ - callback_info(__func__, obj); -} - -/* Executed on object unpatching (ie, patch disablement) */ -static void post_unpatch_callback(struct klp_object *obj) -{ - callback_info(__func__, obj); -} - -static void patched_work_func(struct work_struct *work) -{ - pr_info("%s\n", __func__); -} - -static struct klp_func no_funcs[] =3D { - { } -}; - -static struct klp_func busymod_funcs[] =3D { - { - .old_name =3D "busymod_work_func", - .new_func =3D patched_work_func, - }, { } -}; - -static struct klp_object objs[] =3D { - { - .name =3D NULL, /* vmlinux */ - .funcs =3D no_funcs, - .callbacks =3D { - .pre_patch =3D pre_patch_callback, - .post_patch =3D post_patch_callback, - .pre_unpatch =3D pre_unpatch_callback, - .post_unpatch =3D post_unpatch_callback, - }, - }, { - .name =3D "livepatch_callbacks_mod", - .funcs =3D no_funcs, - .callbacks =3D { - .pre_patch =3D pre_patch_callback, - .post_patch =3D post_patch_callback, - .pre_unpatch =3D pre_unpatch_callback, - .post_unpatch =3D post_unpatch_callback, - }, - }, { - .name =3D "livepatch_callbacks_busymod", - .funcs =3D busymod_funcs, - .callbacks =3D { - .pre_patch =3D pre_patch_callback, - .post_patch =3D post_patch_callback, - .pre_unpatch =3D pre_unpatch_callback, - .post_unpatch =3D post_unpatch_callback, - }, - }, { } -}; - -static struct klp_patch patch =3D { - .mod =3D THIS_MODULE, - .objs =3D objs, -}; - -static int livepatch_callbacks_demo_init(void) -{ - return klp_enable_patch(&patch); -} - -static void livepatch_callbacks_demo_exit(void) -{ -} - -module_init(livepatch_callbacks_demo_init); -module_exit(livepatch_callbacks_demo_exit); -MODULE_LICENSE("GPL"); -MODULE_INFO(livepatch, "Y"); diff --git a/samples/livepatch/livepatch-callbacks-mod.c b/samples/livepatc= h/livepatch-callbacks-mod.c deleted file mode 100644 index 2a074f422a51..000000000000 --- a/samples/livepatch/livepatch-callbacks-mod.c +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2017 Joe Lawrence - */ - -/* - * livepatch-callbacks-mod.c - (un)patching callbacks demo support module - * - * - * Purpose - * ------- - * - * Simple module to demonstrate livepatch (un)patching callbacks. - * - * - * Usage - * ----- - * - * This module is not intended to be standalone. See the "Usage" - * section of livepatch-callbacks-demo.c. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include - -static int livepatch_callbacks_mod_init(void) -{ - pr_info("%s\n", __func__); - return 0; -} - -static void livepatch_callbacks_mod_exit(void) -{ - pr_info("%s\n", __func__); -} - -module_init(livepatch_callbacks_mod_init); -module_exit(livepatch_callbacks_mod_exit); -MODULE_LICENSE("GPL"); --=20 2.35.3 From nobody Tue Dec 16 07:09:08 2025 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 4E52AC4332F for ; Fri, 10 Nov 2023 17:43:00 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1345805AbjKJRnB (ORCPT ); Fri, 10 Nov 2023 12:43:01 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:34418 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S234969AbjKJRl4 (ORCPT ); Fri, 10 Nov 2023 12:41:56 -0500 Received: from smtp-out1.suse.de (smtp-out1.suse.de [195.135.220.28]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id B12C4431C8; Fri, 10 Nov 2023 09:06:15 -0800 (PST) Received: from relay2.suse.de (relay2.suse.de [149.44.160.134]) by smtp-out1.suse.de (Postfix) with ESMTP id 585DA219A8; Fri, 10 Nov 2023 17:06:14 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1699635974; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=giYunGuYrxM57Pa0IasakG1uBcbk7GI9phg0VBSv0W0=; b=fBvf7d41y32jA5uc4u/PcT+qkob4AqRSjqcNB8fa0TIPyczwhPZ27qpLUBPAgNSswTVt+b JLRGtWp6l4Ci3pqnNSCQuPJD3+0nHSZPcvZSeOiv0vRxV5e5AnMnC2j3qtbUfWVkAI2SsQ /+J5xvP/vRNacRwnNux7lwS7pcS87UU= Received: from alley.suse.cz (pmladek.udp.ovpn2.prg.suse.de [10.100.201.202]) by relay2.suse.de (Postfix) with ESMTP id EB6342CA2E; Fri, 10 Nov 2023 17:06:13 +0000 (UTC) From: Petr Mladek To: Josh Poimboeuf , Miroslav Benes Cc: Joe Lawrence , Nicolai Stange , live-patching@vger.kernel.org, linux-kernel@vger.kernel.org, Petr Mladek Subject: [POC 7/7] livepatching: Remove per-state version Date: Fri, 10 Nov 2023 18:04:28 +0100 Message-Id: <20231110170428.6664-8-pmladek@suse.com> X-Mailer: git-send-email 2.35.3 In-Reply-To: <20231110170428.6664-1-pmladek@suse.com> References: <20231110170428.6664-1-pmladek@suse.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" The livepatch state API was added to help with maintaining: + changes done by livepatch callbasks + lifetime of shadow variables The original API was hard to use. Both objectives are better handled by the new per-state callbacks. They are called when the state is introduced or removed. There is also support for automatically freeing obsolete shadow variables. The new callbacks changed the view of compatibility. The livepatch can be replaced to any older one as long the current livepatch is able to disable the obsolete state. As a result, the new patch does not need to support the currently used states. The current patch will be able to disable them. The remaining question is what to do with the per-state version. It was supposed to allow doing more modifications on an existing state. The experience shows that it is not needed in practice. Well, it still might make sense to prevent downgrade when the state could not be disabled easily or when the author does not want to deal with it. Replace the per-state version with per-state block_disable flag. It allows to handle several scenarios: + prevent disabling the livepatch when it does not support a particular state disablement. + prevent replacing the livepatch when the state is not supported by the new patch and the current patch does not support a particular state disablement. + allow to replace the livepatch with a new one which would support the particular state disablement. Signed-off-by: Petr Mladek --- include/linux/livepatch.h | 7 +- kernel/livepatch/core.c | 17 ++- kernel/livepatch/state.c | 27 +++-- kernel/livepatch/state.h | 1 + lib/livepatch/test_klp_state.c | 101 +++++++++++++----- lib/livepatch/test_klp_state2.c | 2 - lib/livepatch/test_klp_state3.c | 2 +- .../testing/selftests/livepatch/functions.sh | 17 +++ .../testing/selftests/livepatch/test-state.sh | 40 ++++--- 9 files changed, 158 insertions(+), 56 deletions(-) diff --git a/include/linux/livepatch.h b/include/linux/livepatch.h index 3807c7bb0281..c7c2c013b380 100644 --- a/include/linux/livepatch.h +++ b/include/linux/livepatch.h @@ -140,16 +140,16 @@ struct klp_state_callbacks { /** * struct klp_state - state of the system modified by the livepatch * @id: system state identifier (non-zero) - * @version: version of the change * @callbacks: optional callbacks used when introducing or removing the st= ate + * @block_disable: the state disablement is not supported * @is_shadow: the state handles lifetime of a shadow variable * with the same @id * @data: custom data */ struct klp_state { unsigned long id; - unsigned int version; struct klp_state_callbacks callbacks; + bool block_disable; bool is_shadow; void *data; }; @@ -159,7 +159,9 @@ struct klp_state { * @mod: reference to the live patch module * @objs: object entries for kernel objects to be patched * @states: system states that can get modified + * version: livepatch version (optional) * @replace: replace all actively used patches + * * @list: list node for global list of actively used patches * @kobj: kobject for sysfs resources * @obj_list: dynamic list of the object entries @@ -173,6 +175,7 @@ struct klp_patch { struct module *mod; struct klp_object *objs; struct klp_state *states; + unsigned int version; bool replace; =20 /* internal */ diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c index d982365777f1..81fbe0778742 100644 --- a/kernel/livepatch/core.c +++ b/kernel/livepatch/core.c @@ -373,6 +373,13 @@ static ssize_t enabled_store(struct kobject *kobj, str= uct kobj_attribute *attr, goto out; } =20 + if (patch->enabled && klp_patch_disable_blocked(patch)) { + pr_err("The livepatch '%s' does not support disable\n", + patch->mod->name); + ret =3D -EINVAL; + goto out; + } + /* * Allow to reverse a pending transition in both ways. It might be * necessary to complete the transition without forcing and breaking @@ -1097,10 +1104,10 @@ int klp_enable_patch(struct klp_patch *patch) =20 if (!klp_is_patch_compatible(patch)) { pr_err("Livepatch patch (%s) is not compatible with the already installe= d livepatches.\n", - patch->mod->name); + patch->mod->name); mutex_unlock(&klp_mutex); return -EINVAL; - } + } =20 if (!try_module_get(patch->mod)) { mutex_unlock(&klp_mutex); @@ -1111,17 +1118,17 @@ int klp_enable_patch(struct klp_patch *patch) =20 ret =3D klp_init_patch(patch); if (ret) - goto err; + goto unlock_free; =20 ret =3D __klp_enable_patch(patch); if (ret) - goto err; + goto unlock_free; =20 mutex_unlock(&klp_mutex); =20 return 0; =20 -err: +unlock_free: klp_free_patch_start(patch); =20 mutex_unlock(&klp_mutex); diff --git a/kernel/livepatch/state.c b/kernel/livepatch/state.c index 4ec65afe3a43..0647cc688f38 100644 --- a/kernel/livepatch/state.c +++ b/kernel/livepatch/state.c @@ -91,23 +91,26 @@ static bool klp_is_state_compatible(struct klp_patch *p= atch, =20 state =3D klp_get_state(patch, old_state->id); =20 - /* A cumulative livepatch must handle all already modified states. */ - if (!state) - return !patch->replace; + if (!state && old_state->block_disable) + return false; =20 - return state->version >=3D old_state->version; + return true; } =20 /* * Check that the new livepatch will not break the existing system states. - * Cumulative patches must handle all already modified states. - * Non-cumulative patches can touch already modified states. + * The patch could replace existing patches only when the obsolete + * states can be disabled. */ bool klp_is_patch_compatible(struct klp_patch *patch) { struct klp_patch *old_patch; struct klp_state *old_state; =20 + /* Non-cumulative patches are always compatible. */ + if (!patch->replace) + return true; + klp_for_each_patch(old_patch) { klp_for_each_state(old_patch, old_state) { if (!klp_is_state_compatible(patch, old_state)) @@ -118,6 +121,18 @@ bool klp_is_patch_compatible(struct klp_patch *patch) return true; } =20 +bool klp_patch_disable_blocked(struct klp_patch *patch) +{ + struct klp_state *state; + + klp_for_each_state(patch, state) { + if (state->block_disable) + return true; + } + + return false; +} + bool is_state_in_other_patches(struct klp_patch *patch, struct klp_state *= state) { struct klp_patch *old_patch; diff --git a/kernel/livepatch/state.h b/kernel/livepatch/state.h index e9940e7f00dd..74cd9d8f63c5 100644 --- a/kernel/livepatch/state.h +++ b/kernel/livepatch/state.h @@ -5,6 +5,7 @@ #include =20 bool klp_is_patch_compatible(struct klp_patch *patch); +bool klp_patch_disable_blocked(struct klp_patch *patch); int klp_setup_states(struct klp_patch *patch); void klp_enable_states(struct klp_patch *patch); void klp_disable_states(struct klp_patch *patch); diff --git a/lib/livepatch/test_klp_state.c b/lib/livepatch/test_klp_state.c index b3d1ee48dfcc..ec9bf4f37b26 100644 --- a/lib/livepatch/test_klp_state.c +++ b/lib/livepatch/test_klp_state.c @@ -11,17 +11,6 @@ =20 #define CONSOLE_LOGLEVEL_FIX_ID 1 =20 -/* - * Version of the state which defines compatibility of livepaches. - * The value is artificial. It set just for testing the compatibility - * checks. In reality, all versions are compatible because all - * the callbacks do nothing and the shadow variable clean up - * is done by the core. - */ -#ifndef CONSOLE_LOGLEVEL_FIX_VERSION -#define CONSOLE_LOGLEVEL_FIX_VERSION 1 -#endif - static struct klp_patch patch; =20 static int allocate_loglevel_state(void) @@ -115,6 +104,80 @@ static void release_state_callback(struct klp_patch *p= atch, struct klp_state *st free_loglevel_state(); } =20 +static struct klp_state states[] =3D { + { + .id =3D CONSOLE_LOGLEVEL_FIX_ID, + .callbacks =3D { + .setup =3D setup_state_callback, + .enable =3D enable_state_callback, + .disable =3D disable_state_callback, + .release =3D release_state_callback, + }, + }, { } +}; + +static int block_state_disable_get(char *buffer, const struct kernel_param= *kp) +{ + pr_info("%s: Disable transition is %s by state: %lu\n", + __func__, + states[0].block_disable ? "not supported" : "supported", + states[0].id); + + return 0; +} + +static int block_state_disable_set(const char *val, const struct kernel_pa= ram *kp) +{ + bool block; + int ret; + + ret =3D kstrtobool(val, &block); + if (ret) + return ret; + + states[0].block_disable =3D block; + + return 0; +} + +static const struct kernel_param_ops block_state_disable_ops =3D { + .get =3D block_state_disable_get, + .set =3D block_state_disable_set, +}; + +module_param_cb(block_state_disable, &block_state_disable_ops, NULL, 0600); +MODULE_PARM_DESC(block_state_disable, "Set to 1 to pretend that the state = does not support disable operation (default =3D 0)."); + +bool no_state; + +static int no_state_get(char *buffer, const struct kernel_param *kp) +{ + return sysfs_emit("%s", no_state ? "1" : "0"); +} + +static int no_state_set(const char *val, const struct kernel_param *kp) +{ + bool no; + int ret; + + ret =3D kstrtobool(val, &no); + if (ret) + return ret; + + no_state =3D no; + + return 0; +} + +static const struct kernel_param_ops no_state_ops =3D { + .get =3D no_state_get, + .set =3D no_state_set, +}; + +module_param_cb(no_state, &no_state_ops, NULL, 0400); +MODULE_PARM_DESC(no_state, "Set to 1 when the livepatch should not support= the state. (default =3D 0)."); + + static struct klp_func no_funcs[] =3D { {} }; @@ -126,19 +189,6 @@ static struct klp_object objs[] =3D { }, { } }; =20 -static struct klp_state states[] =3D { - { - .id =3D CONSOLE_LOGLEVEL_FIX_ID, - .version =3D CONSOLE_LOGLEVEL_FIX_VERSION, - .callbacks =3D { - .setup =3D setup_state_callback, - .enable =3D enable_state_callback, - .disable =3D disable_state_callback, - .release =3D release_state_callback, - }, - }, { } -}; - static struct klp_patch patch =3D { .mod =3D THIS_MODULE, .objs =3D objs, @@ -148,6 +198,9 @@ static struct klp_patch patch =3D { =20 static int test_klp_callbacks_demo_init(void) { + if (no_state) + patch.states =3D NULL; + return klp_enable_patch(&patch); } =20 diff --git a/lib/livepatch/test_klp_state2.c b/lib/livepatch/test_klp_state= 2.c index 128855764bf8..b8fe1ca2d802 100644 --- a/lib/livepatch/test_klp_state2.c +++ b/lib/livepatch/test_klp_state2.c @@ -1,7 +1,5 @@ // SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2019 SUSE =20 -#define CONSOLE_LOGLEVEL_FIX_VERSION 2 - /* The console loglevel fix is the same in the next cumulative patch. */ #include "test_klp_state.c" diff --git a/lib/livepatch/test_klp_state3.c b/lib/livepatch/test_klp_state= 3.c index 9226579d10c5..b8fe1ca2d802 100644 --- a/lib/livepatch/test_klp_state3.c +++ b/lib/livepatch/test_klp_state3.c @@ -2,4 +2,4 @@ // Copyright (C) 2019 SUSE =20 /* The console loglevel fix is the same in the next cumulative patch. */ -#include "test_klp_state2.c" +#include "test_klp_state.c" diff --git a/tools/testing/selftests/livepatch/functions.sh b/tools/testing= /selftests/livepatch/functions.sh index a962119f053e..5e2569bff126 100644 --- a/tools/testing/selftests/livepatch/functions.sh +++ b/tools/testing/selftests/livepatch/functions.sh @@ -277,6 +277,23 @@ function set_pre_patch_ret { die "failed to set pre_patch_ret parameter for $mod module" } =20 +# set_module_param(modname, param, val) +# modname - module name to set +# param - name of the parameter to set +# val - value to set +function set_module_param { + local mod=3D"$1"; shift + local param=3D"$1"; shift + local val=3D"$1" + + log "% echo $val > /sys/module/$mod/parameters/$param" + echo "$val" > "/sys/module/$mod/parameters/$param" + + # Wait for sysfs value to hold ... + loop_until '[[ $(cat "/sys/module/$mod/parameters/$param") =3D=3D "$val" = ]]' || + die "failed to set parameter $param for $mod module to the value $val" +} + # read_module_param(modname, param) # modname - module name which provides the given parameter # param - parameter name to be read diff --git a/tools/testing/selftests/livepatch/test-state.sh b/tools/testin= g/selftests/livepatch/test-state.sh index a3c933ea96fc..72387b5775c6 100755 --- a/tools/testing/selftests/livepatch/test-state.sh +++ b/tools/testing/selftests/livepatch/test-state.sh @@ -132,12 +132,14 @@ livepatch: '$MOD_LIVEPATCH2': unpatching complete =20 start_test "incompatible cumulative livepatches" =20 -load_lp $MOD_LIVEPATCH2 -load_failing_mod $MOD_LIVEPATCH -disable_lp $MOD_LIVEPATCH2 -unload_lp $MOD_LIVEPATCH2 +load_lp $MOD_LIVEPATCH2 block_state_disable=3D1 +load_failing_mod $MOD_LIVEPATCH no_state=3D1 +# load the livepatch again with default features (state and disable suppor= ted) +load_lp $MOD_LIVEPATCH +disable_lp $MOD_LIVEPATCH +unload_lp $MOD_LIVEPATCH =20 -check_result "% modprobe $MOD_LIVEPATCH2 +check_result "% modprobe $MOD_LIVEPATCH2 block_state_disable=3D1 livepatch: enabling patch '$MOD_LIVEPATCH2' livepatch: '$MOD_LIVEPATCH2': initializing patching transition $MOD_LIVEPATCH2: setup_state_callback: state 1 @@ -147,18 +149,24 @@ livepatch: '$MOD_LIVEPATCH2': completing patching tra= nsition $MOD_LIVEPATCH2: enable_state_callback: state 1 $MOD_LIVEPATCH2: fix_console_loglevel: fixing console_loglevel livepatch: '$MOD_LIVEPATCH2': patching complete -% modprobe $MOD_LIVEPATCH +% modprobe $MOD_LIVEPATCH no_state=3D1 livepatch: Livepatch patch ($MOD_LIVEPATCH) is not compatible with the alr= eady installed livepatches. modprobe: ERROR: could not insert '$MOD_LIVEPATCH': Invalid argument -% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH2/enabled -livepatch: '$MOD_LIVEPATCH2': initializing unpatching transition -$MOD_LIVEPATCH2: disable_state_callback: state 1 -$MOD_LIVEPATCH2: restore_console_loglevel: restoring console_loglevel -livepatch: '$MOD_LIVEPATCH2': starting unpatching transition -livepatch: '$MOD_LIVEPATCH2': completing unpatching transition -$MOD_LIVEPATCH2: release_state_callback: state 1 -$MOD_LIVEPATCH2: free_loglevel_state: freeing space for the stored console= _loglevel -livepatch: '$MOD_LIVEPATCH2': unpatching complete -% rmmod $MOD_LIVEPATCH2" +% modprobe $MOD_LIVEPATCH +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +livepatch: '$MOD_LIVEPATCH': starting patching transition +livepatch: '$MOD_LIVEPATCH': completing patching transition +livepatch: '$MOD_LIVEPATCH': patching complete +% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled +livepatch: '$MOD_LIVEPATCH': initializing unpatching transition +$MOD_LIVEPATCH: disable_state_callback: state 1 +$MOD_LIVEPATCH: restore_console_loglevel: restoring console_loglevel +livepatch: '$MOD_LIVEPATCH': starting unpatching transition +livepatch: '$MOD_LIVEPATCH': completing unpatching transition +$MOD_LIVEPATCH: release_state_callback: state 1 +$MOD_LIVEPATCH: free_loglevel_state: freeing space for the stored console_= loglevel +livepatch: '$MOD_LIVEPATCH': unpatching complete +% rmmod $MOD_LIVEPATCH" =20 exit 0 --=20 2.35.3