From nobody Mon Dec 15 21:31:33 2025 Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.223.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 76B691EBFE8; Wed, 15 Jan 2025 08:24:54 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.135.223.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929496; cv=none; b=AUNzzJNe0LfMFqT+kdMfZZTkcu7sjKgR8ZP1o9oIFxCbJ2zEbbOSc5M/hzzxGwGjCkYmmSoawBwpToyZl67NPCzrY6U7UUXbXfLuVfZlsS+CVhGNyrsR2ReSquIQrnQ1HNcZyTAqPdiRTt0lf1OBmH4PBWFAPKrA0fCpaxMOF0Y= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929496; c=relaxed/simple; bh=/mtuB6dYrr4Qb/i7KWC8ooLs647ThT15uWaV103bjqk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=maDNrxJXB+v9mW2vbeT1mvRgdYFAgF9bLi0zu6PyFwenjE1V0xcCUiQl+y0QIX9jrXoxjrg29s+koCA1gmfrjRZcurFhCa8TI0G1Vxb0Pu4fIpAKsy/GragKXH2vrFT06Qresh9VC/b/aZhBkW2GNpZZmVtfAvY0tesq3+mwQGw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com; spf=pass smtp.mailfrom=suse.com; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=l+0cuuGF; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=l+0cuuGF; arc=none smtp.client-ip=195.135.223.131 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=suse.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="l+0cuuGF"; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="l+0cuuGF" Received: from pathway.suse.cz (unknown [10.100.201.202]) by smtp-out2.suse.de (Postfix) with ESMTP id 918B31F37C; Wed, 15 Jan 2025 08:24:52 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929492; 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=SIO0qTKUq1SL9yKK5f8/GqngZfCP5mRjvgBeKwJ0RWs=; b=l+0cuuGFjHABFnA4N/ro30Q4TWMIjLTlwWmo+HedJH7+rLNTcQVPpN33JLYA5ir6mS3y3b CmV3F8hCR9xUQi9c/Rk2JzO1wNwSMzAqkbRau5yDOktPSnEq0zTX9ebksPKbxqIAu5olas D6rKFBJ5tNcmNMXAM66b4VCCyzu51/M= Authentication-Results: smtp-out2.suse.de; none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929492; 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=SIO0qTKUq1SL9yKK5f8/GqngZfCP5mRjvgBeKwJ0RWs=; b=l+0cuuGFjHABFnA4N/ro30Q4TWMIjLTlwWmo+HedJH7+rLNTcQVPpN33JLYA5ir6mS3y3b CmV3F8hCR9xUQi9c/Rk2JzO1wNwSMzAqkbRau5yDOktPSnEq0zTX9ebksPKbxqIAu5olas D6rKFBJ5tNcmNMXAM66b4VCCyzu51/M= 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: [PATCH v1 01/19] livepatch: Add callbacks for introducing and removing states Date: Wed, 15 Jan 2025 09:24:13 +0100 Message-ID: <20250115082431.5550-2-pmladek@suse.com> X-Mailer: git-send-email 2.47.1 In-Reply-To: <20250115082431.5550-1-pmladek@suse.com> References: <20250115082431.5550-1-pmladek@suse.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Spam-Score: -6.80 X-Spamd-Result: default: False [-6.80 / 50.00]; REPLY(-4.00)[]; BAYES_HAM(-3.00)[100.00%]; MID_CONTAINS_FROM(1.00)[]; NEURAL_HAM_LONG(-1.00)[-1.000]; R_MISSING_CHARSET(0.50)[]; NEURAL_HAM_SHORT(-0.20)[-1.000]; MIME_GOOD(-0.10)[text/plain]; RCVD_COUNT_ZERO(0.00)[0]; MIME_TRACE(0.00)[0:+]; ARC_NA(0.00)[]; RCPT_COUNT_SEVEN(0.00)[7]; FUZZY_BLOCKED(0.00)[rspamd.com]; TO_DN_SOME(0.00)[]; FROM_HAS_DN(0.00)[]; DKIM_SIGNED(0.00)[suse.com:s=susede1]; FROM_EQ_ENVFROM(0.00)[]; TO_MATCH_ENVRCPT_ALL(0.00)[]; DBL_BLOCKED_OPENRESOLVER(0.00)[suse.com:mid,suse.com:email] X-Spam-Flag: NO X-Spam-Level: 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 no 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: + "pre_patch" 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. + "post_patch" 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. + "pre_unpatch" is called before the livepatch is disabled or replaced. When using the atomic replace, 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 some functionality which will no longer be supported after the livepatch gets disabled. + "post_unpatch" is called after the livepatch was disabled or replaced. There are the same rules for the atomic replace replacement as for "pre_patch" 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 the system processes start being transitioned. The per-object callbacks were called after the barrier. They were using and already existing for-cycle. Nobody though about the potential ordering problem when it was implemented. Signed-off-by: Petr Mladek --- include/linux/livepatch.h | 34 ++++++++ kernel/livepatch/core.c | 9 +++ kernel/livepatch/state.c | 141 ++++++++++++++++++++++++++++++++++ kernel/livepatch/state.h | 8 ++ kernel/livepatch/transition.c | 15 ++++ 5 files changed, 207 insertions(+) diff --git a/include/linux/livepatch.h b/include/linux/livepatch.h index 51a258c24ff5..79dddf3dbd52 100644 --- a/include/linux/livepatch.h +++ b/include/linux/livepatch.h @@ -129,15 +129,49 @@ struct klp_object { bool patched; }; =20 +struct klp_patch; +struct klp_state; + +/** + * struct klp_state_callbacks - callbacks manipulating the state + * @pre_patch: executed only when the state is being enabled + * before code patching + * @post_patch: executed only when the state is being enabled + * after code patching + * @pre_unpatch: executed only when the state is being disabled + * before code unpatching + * @post_unpatch: executed only when the state is being disabled + * after code unpatching + * @pre_patch_succeeded: internal state used by a rollback on error + * + * All callbacks are optional. + * + * @pre_patch callback returns 0 on success and an error code otherwise. + * + * Any error prevents enabling the livepatch. @post_unpatch() callbacks are + * then called to rollback @pre_patch callbacks which has already succeeded + * before. Also @post_patch callbacks are called for to-be-removed states + * to rollback pre_unpatch() callbacks when they were called. + */ +struct klp_state_callbacks { + int (*pre_patch)(struct klp_patch *patch, struct klp_state *state); + void (*post_patch)(struct klp_patch *patch, struct klp_state *state); + void (*pre_unpatch)(struct klp_patch *patch, struct klp_state *state); + void (*post_unpatch)(struct klp_patch *patch, struct klp_state *state); + bool pre_patch_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 enabling or disabling the state * @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 3c21c31796db..527fdb0a6b0a 100644 --- a/kernel/livepatch/core.c +++ b/kernel/livepatch/core.c @@ -986,6 +986,8 @@ static int __klp_disable_patch(struct klp_patch *patch) =20 klp_init_transition(patch, KLP_TRANSITION_UNPATCHED); =20 + klp_states_pre_unpatch(patch); + klp_for_each_object(patch, obj) if (obj->patched) klp_pre_unpatch_callback(obj); @@ -1021,6 +1023,13 @@ static int __klp_enable_patch(struct klp_patch *patc= h) =20 klp_init_transition(patch, KLP_TRANSITION_PATCHED); =20 + ret =3D klp_states_pre_patch(patch); + if (ret) + goto err; + + if (patch->replace) + klp_states_pre_unpatch_replaced(patch); + /* * Enforce the order of the func->transition writes in * klp_init_transition() and the ops->func_stack writes in diff --git a/kernel/livepatch/state.c b/kernel/livepatch/state.c index 2565d039ade0..bf7ed988d2bb 100644 --- a/kernel/livepatch/state.c +++ b/kernel/livepatch/state.c @@ -117,3 +117,144 @@ bool klp_is_patch_compatible(struct klp_patch *patch) =20 return true; } + +static bool is_state_in_other_patches(struct klp_patch *patch, + struct klp_state *state) +{ + struct klp_patch *p; + struct klp_state *s; + + klp_for_each_patch(p) { + if (p =3D=3D patch) + continue; + + klp_for_each_state(p, s) { + if (s->id =3D=3D state->id) + return true; + } + } + + return false; +} + +int klp_states_pre_patch(struct klp_patch *patch) +{ + struct klp_state *state; + + klp_for_each_state(patch, state) { + if (!is_state_in_other_patches(patch, state) && + state->callbacks.pre_patch) { + int err; + + err =3D state->callbacks.pre_patch(patch, state); + if (err) + return err; + } + + state->callbacks.pre_patch_succeeded =3D true; + } + + return 0; +} + +void klp_states_post_patch(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.post_patch) + state->callbacks.post_patch(patch, state); + } +} + +void klp_states_pre_unpatch(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.pre_unpatch) + state->callbacks.pre_unpatch(patch, state); + } +} + +void klp_states_post_unpatch(struct klp_patch *patch) +{ + struct klp_state *state; + + klp_for_each_state(patch, state) { + if (is_state_in_other_patches(patch, state)) + continue; + + /* + * This only occurs when a transition is canceled after + * a preparation step failed. + */ + if (!state->callbacks.pre_patch_succeeded) + continue; + + if (state->callbacks.post_unpatch) + state->callbacks.post_unpatch(patch, state); + + state->callbacks.pre_patch_succeeded =3D 0; + } +} + +/* + * Make it clear when pre_unpatch() callbacks need to be reverted + * in case of failure. + */ +static bool klp_states_pre_unpatch_replaced_called; + +void klp_states_pre_unpatch_replaced(struct klp_patch *patch) +{ + struct klp_patch *old_patch; + + /* Make sure that it was cleared at the end of the last transition. */ + WARN_ON(klp_states_pre_unpatch_replaced_called); + + klp_for_each_patch(old_patch) { + if (old_patch !=3D patch) + klp_states_pre_unpatch(old_patch); + } + + klp_states_pre_unpatch_replaced_called =3D true; +} + +void klp_states_post_unpatch_replaced(struct klp_patch *patch) +{ + struct klp_patch *old_patch; + + klp_for_each_patch(old_patch) { + if (old_patch !=3D patch) + klp_states_post_unpatch(old_patch); + } + + /* Reset for the next transition. */ + klp_states_pre_unpatch_replaced_called =3D false; +} + +void klp_states_post_patch_replaced(struct klp_patch *patch) +{ + struct klp_patch *old_patch; + + /* + * This only occurs when a transition is canceled after + * a preparation step failed. + */ + if (!klp_states_pre_unpatch_replaced_called) + return; + + klp_for_each_patch(old_patch) { + if (old_patch !=3D patch) + klp_states_post_patch(old_patch); + } + + /* Reset for the next transition. */ + klp_states_pre_unpatch_replaced_called =3D false; +} diff --git a/kernel/livepatch/state.h b/kernel/livepatch/state.h index 49d9c16e8762..65c0c2cde04c 100644 --- a/kernel/livepatch/state.h +++ b/kernel/livepatch/state.h @@ -5,5 +5,13 @@ #include =20 bool klp_is_patch_compatible(struct klp_patch *patch); +int klp_states_pre_patch(struct klp_patch *patch); +void klp_states_post_patch(struct klp_patch *patch); +void klp_states_pre_unpatch(struct klp_patch *patch); +void klp_states_post_unpatch(struct klp_patch *patch); + +void klp_states_pre_unpatch_replaced(struct klp_patch *patch); +void klp_states_post_unpatch_replaced(struct klp_patch *patch); +void klp_states_post_patch_replaced(struct klp_patch *patch); =20 #endif /* _LIVEPATCH_STATE_H */ diff --git a/kernel/livepatch/transition.c b/kernel/livepatch/transition.c index ba069459c101..f3dce9fe9897 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_TRANSITI= ON_PATCHED) { klp_unpatch_replaced_patches(klp_transition_patch); klp_discard_nops(klp_transition_patch); + klp_states_post_unpatch_replaced(klp_transition_patch); } =20 if (klp_target_state =3D=3D KLP_TRANSITION_UNPATCHED) { @@ -140,6 +142,19 @@ static void klp_complete_transition(void) task->patch_state =3D KLP_TRANSITION_IDLE; } =20 + if (klp_target_state =3D=3D KLP_TRANSITION_PATCHED) { + klp_states_post_patch(klp_transition_patch); + } else if (klp_target_state =3D=3D KLP_TRANSITION_UNPATCHED) { + /* + * Re-enable states which should have been replaced but + * the transition was cancelled or reverted. + */ + if (klp_transition_patch->replace) + klp_states_post_patch_replaced(klp_transition_patch); + + klp_states_post_unpatch(klp_transition_patch); + } + klp_for_each_object(klp_transition_patch, obj) { if (!klp_is_object_loaded(obj)) continue; --=20 2.47.1 From nobody Mon Dec 15 21:31:33 2025 Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.223.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 12804232431; Wed, 15 Jan 2025 08:25:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.135.223.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929507; cv=none; b=EwwHNYrYrvTDYZp3yHZWHUdlmUo7Qrc/jorQeOr1/c+mLKOgpNH1A75fB0Dn/qxGuu1LIw5VRqeY0XyALe0MnnKgzOxY7VRVU6iRXCgZQ9otdlhwxlqsCtt1fsbopfnP4lyc1hTT1wkxxg07zMkTqqHS2HKnTB6nLm2XK1af85s= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929507; c=relaxed/simple; bh=Qvl7XRQqoVAlAMDkTvRsd/o3LaA0iur2rRZiiMCvu/M=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=bFa3ap4SRinXxPju2ioYRAtPIN2StqM3dtpvULB5Toz+SXWN5Hn6Pt8sKOo5V8eMUe6EWDiMa8qWFu335SijnoNb3Dk88Fb7bbQQjBN9RywADhmRX6gi8Ig519PjR2DDrcimOT2V9idspbwsk885zdPsM/EKgNH3xEJ9v5c7b7s= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com; spf=pass smtp.mailfrom=suse.com; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=Tb9w0Yt9; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=VsC/9bDD; arc=none smtp.client-ip=195.135.223.131 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=suse.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="Tb9w0Yt9"; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="VsC/9bDD" Received: from pathway.suse.cz (unknown [10.100.201.202]) by smtp-out2.suse.de (Postfix) with ESMTP id D73B81F37C; Wed, 15 Jan 2025 08:25:02 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929503; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=is2C76ZXXmVwCUawQH+72lfwjD4WWgGM6GXjAiDmQiw=; b=Tb9w0Yt9g/MSMAM6SB6r5vbj9Czc6a1T5LcpprbgWYGHVvcw28rWXYg/wx0eU9Bt0TpiXM tAbZ+wAvE71XjMlTDWfETqz1JTf84wAVN5lEWN/1bU2Bg7r2UNJtsqJ35uE3decHLdisCw rjLWCmwthSkRyVyeB5u1j2M40Q7J5T4= Authentication-Results: smtp-out2.suse.de; none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929502; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=is2C76ZXXmVwCUawQH+72lfwjD4WWgGM6GXjAiDmQiw=; b=VsC/9bDDSo4gUuFI1uLy0GXNOD6WpOi+Y1YDXOgUC8GkvHN+BTRpx5rfu/YH3iRUtg4Ofn AyQH6y4A4yKDBLddt25MQjKP7g4GG5g2zd0bPcMnEX6CTb4DxXgHRTS3+v2u8yxb7KEUM1 rnfg2TUJ0ziauWD3MhKbL0LHUbeSnhE= 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: [PATCH v1 02/19] livepatch: Allow to handle lifetime of shadow variables using the livepatch state Date: Wed, 15 Jan 2025 09:24:14 +0100 Message-ID: <20250115082431.5550-3-pmladek@suse.com> X-Mailer: git-send-email 2.47.1 In-Reply-To: <20250115082431.5550-1-pmladek@suse.com> References: <20250115082431.5550-1-pmladek@suse.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-Spam-Score: -7.30 X-Spamd-Result: default: False [-7.30 / 50.00]; REPLY(-4.00)[]; BAYES_HAM(-3.00)[100.00%]; MID_CONTAINS_FROM(1.00)[]; NEURAL_HAM_LONG(-1.00)[-1.000]; NEURAL_HAM_SHORT(-0.20)[-1.000]; MIME_GOOD(-0.10)[text/plain]; RCVD_COUNT_ZERO(0.00)[0]; ARC_NA(0.00)[]; FROM_HAS_DN(0.00)[]; MIME_TRACE(0.00)[0:+]; RCPT_COUNT_SEVEN(0.00)[7]; DKIM_SIGNED(0.00)[suse.com:s=susede1]; FUZZY_BLOCKED(0.00)[rspamd.com]; TO_DN_SOME(0.00)[]; FROM_EQ_ENVFROM(0.00)[]; TO_MATCH_ENVRCPT_ALL(0.00)[]; DBL_BLOCKED_OPENRESOLVER(0.00)[suse.com:mid,suse.com:email] X-Spam-Flag: NO X-Spam-Level: Managing the lifetime of shadow variables becomes challenging when atomic replace is used. The new patch cannot determine whether a shadow variable has already been used by a previous live patch or if there is a shadow variable that is no longer in use. Shadow variables are typically used alongside callbacks. At a minimum, the @post_unpatch callback is called to free shadow variables that are no longer needed. Additionally, @post_patch and @pre_unpatch callbacks are sometimes used to enable or disable the use of shadow variables. This is necessary when the shadow variable can only be used when the entire system is capable of handling it. The complexity increases when using the atomic replace feature, as only the callbacks from the new live patch are executed. Newly created live patches might manage obsolete shadow variables, ensuring the upgrade functions correctly. However, older live patches are unaware of shadow variables introduced later, which could lead to leaks during a downgrade. Additionally, these leaked variables might retain outdated information, potentially causing issues if those variables are reused in a subsequent upgrade. These issues are better addressed with the new callbacks associated with a live patch state. These callbacks are triggered both when the states are first introduced and when they become obsolete. Additionally, the callbacks are invoked from the patch that originally supported the state, ensuring that even downgrades are handled safely. Let=E2=80=99s formalize the process: Associate a shadow variable with a live patch state by setting the "state.is_shadow" flag and using the same "id" in both struct klp_shadow and struct klp_state. The shadow variable will then share the same lifetime as the livepatch state, allowing obsolete shadow variables to be automatically freed without requiring an additional callback. A generic callback will free the shadow variables using the state->callbacks.shadow_dtor callback, if provided. Signed-off-by: Petr Mladek --- include/linux/livepatch.h | 15 ++++++++++----- kernel/livepatch/state.c | 3 +++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/include/linux/livepatch.h b/include/linux/livepatch.h index 79dddf3dbd52..c624f1105663 100644 --- a/include/linux/livepatch.h +++ b/include/linux/livepatch.h @@ -132,6 +132,11 @@ 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 * @pre_patch: executed only when the state is being enabled @@ -142,6 +147,7 @@ struct klp_state; * before code unpatching * @post_unpatch: executed only when the state is being disabled * after code unpatching + * @shadow_dtor: destructor for the related shadow variable * @pre_patch_succeeded: internal state used by a rollback on error * * All callbacks are optional. @@ -158,6 +164,7 @@ struct klp_state_callbacks { void (*post_patch)(struct klp_patch *patch, struct klp_state *state); void (*pre_unpatch)(struct klp_patch *patch, struct klp_state *state); void (*post_unpatch)(struct klp_patch *patch, struct klp_state *state); + klp_shadow_dtor_t shadow_dtor; bool pre_patch_succeeded; }; =20 @@ -166,12 +173,15 @@ struct klp_state_callbacks { * @id: system state identifier (non-zero) * @version: version of the change * @callbacks: optional callbacks used when enabling or disabling the state + * @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 @@ -246,11 +256,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 bf7ed988d2bb..16ad695b1e88 100644 --- a/kernel/livepatch/state.c +++ b/kernel/livepatch/state.c @@ -201,6 +201,9 @@ void klp_states_post_unpatch(struct klp_patch *patch) if (state->callbacks.post_unpatch) state->callbacks.post_unpatch(patch, state); =20 + if (state->is_shadow) + klp_shadow_free_all(state->id, state->callbacks.shadow_dtor); + state->callbacks.pre_patch_succeeded =3D 0; } } --=20 2.47.1 From nobody Mon Dec 15 21:31:33 2025 Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.223.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 23AB3232431; Wed, 15 Jan 2025 08:25:14 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.135.223.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929517; cv=none; b=CeVUcR+JRibFtQU9HcLDBRtU6IXCxcbAt15r7lsdErImgJPYhvP0i1XJtLSk32PNpsd8OMl/hwGBKTVieWhBrQPtxRAfyfoJ0m4JkGCd3T05BMx5U8iZNVkzf9YhKGvX7wRHWpz6n2pHPAu0o1hD2bvfkP2JTTokL4z8DZCTsMA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929517; c=relaxed/simple; bh=6RA7ifO54vBRTchXDOxnLKUjVrZPFSSi4DysCXRmpVU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=u6hxuFtObvHBMd6AWGx9e8NgpWOrUqfedMJPQR3xMJJthqHRu6KWtbpCt9OuCfjifjSas3qyjwOCIXDA+nd7YCz+XHlIsM4uK2B/iRrXrm/pD6JbJQgmyI41r1uGheMf6SgCFb7h48kqpvH7ijvLk6WYxkI0jtCEbBUeEIS8fB8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com; spf=pass smtp.mailfrom=suse.com; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=mN7gNJ5P; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=mN7gNJ5P; arc=none smtp.client-ip=195.135.223.131 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=suse.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="mN7gNJ5P"; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="mN7gNJ5P" Received: from pathway.suse.cz (unknown [10.100.201.202]) by smtp-out2.suse.de (Postfix) with ESMTP id 31A571F37C; Wed, 15 Jan 2025 08:25:13 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929513; 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=VtXHzF/JauImxPB2XYc0T0rWqBsnE1lPlsFPvdiPWgo=; b=mN7gNJ5P8O7Ud5lOBF6XSR9Q4MYUDOhCqoHbUsnRqPtupJPqLTmGgYVo/gq6t4L474h1Ub QWYLxkMTa/qKm9Msvd8NFbBflhjgnBNkjDusTwxNs0x+Z/PXWe+ba7kjrmENOLlB7gyvmU viBZXnzSP5rsJ/1GJ70gYJRB5rZbPxM= Authentication-Results: smtp-out2.suse.de; none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929513; 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=VtXHzF/JauImxPB2XYc0T0rWqBsnE1lPlsFPvdiPWgo=; b=mN7gNJ5P8O7Ud5lOBF6XSR9Q4MYUDOhCqoHbUsnRqPtupJPqLTmGgYVo/gq6t4L474h1Ub QWYLxkMTa/qKm9Msvd8NFbBflhjgnBNkjDusTwxNs0x+Z/PXWe+ba7kjrmENOLlB7gyvmU viBZXnzSP5rsJ/1GJ70gYJRB5rZbPxM= 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: [PATCH v1 03/19] selftests/livepatch: Use per-state callbacks in state API tests Date: Wed, 15 Jan 2025 09:24:15 +0100 Message-ID: <20250115082431.5550-4-pmladek@suse.com> X-Mailer: git-send-email 2.47.1 In-Reply-To: <20250115082431.5550-1-pmladek@suse.com> References: <20250115082431.5550-1-pmladek@suse.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Spam-Level: X-Spamd-Result: default: False [-6.80 / 50.00]; REPLY(-4.00)[]; BAYES_HAM(-3.00)[100.00%]; MID_CONTAINS_FROM(1.00)[]; NEURAL_HAM_LONG(-1.00)[-1.000]; R_MISSING_CHARSET(0.50)[]; NEURAL_HAM_SHORT(-0.20)[-1.000]; MIME_GOOD(-0.10)[text/plain]; RCVD_COUNT_ZERO(0.00)[0]; MIME_TRACE(0.00)[0:+]; ARC_NA(0.00)[]; RCPT_COUNT_SEVEN(0.00)[7]; FUZZY_BLOCKED(0.00)[rspamd.com]; TO_DN_SOME(0.00)[]; FROM_HAS_DN(0.00)[]; DKIM_SIGNED(0.00)[suse.com:s=susede1]; FROM_EQ_ENVFROM(0.00)[]; TO_MATCH_ENVRCPT_ALL(0.00)[]; DBL_BLOCKED_OPENRESOLVER(0.00)[suse.com:email,suse.com:mid] X-Spam-Score: -6.80 X-Spam-Flag: NO Content-Type: text/plain; charset="utf-8" Recent updates to the live patch core have enabled the integration of states, shadow variables, and callbacks. Utilize these new features in the state tests. Use the shadow variable API to store the original log level, as it is better suited for this purpose than directly accessing the .data pointer in klp_state. A key advantage is that the shadow variable is preserved when the current patch is replaced by a new version, eliminating the need to copy the pointer. Additionally, the lifetime of the shadow variable is now tied to the lifetime of the state and will be automatically freed when it is no 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 callbacks are per-state. + Remove obsolete callbacks which used to be needed to transfer the shadow state data pointer. + The post_unpatch_callback() is retained solely for testing purposes. Its primary function is to print a message confirming that it has been called. The shadow variable is automatically freed by the core kernel code when the state is no longer supported by any live patch. + The shadow_console_loglevel_dtor() function is added solely for testing purposes. It simply prints a message to confirm that it has been called. Since the shadow variable is straightforward, its memory is automatically freed along with the associated struct klp_shadow. The versioning of the state still prevents downgrades, but this issue is now largely mitigated because callbacks are no longer required to transfer or free shadow variables. The versioning is going to be removed in a followup commit. Signed-off-by: Petr Mladek --- .../testing/selftests/livepatch/test-state.sh | 52 ++--- .../livepatch/test_modules/test_klp_state.c | 138 +++++++------ .../livepatch/test_modules/test_klp_state2.c | 190 +----------------- 3 files changed, 95 insertions(+), 285 deletions(-) diff --git a/tools/testing/selftests/livepatch/test-state.sh b/tools/testin= g/selftests/livepatch/test-state.sh index 04b66380f8a0..2f4a5109fdf5 100755 --- a/tools/testing/selftests/livepatch/test-state.sh +++ b/tools/testing/selftests/livepatch/test-state.sh @@ -22,21 +22,21 @@ unload_lp $MOD_LIVEPATCH check_result "% insmod test_modules/$MOD_LIVEPATCH.ko livepatch: enabling patch '$MOD_LIVEPATCH' livepatch: '$MOD_LIVEPATCH': initializing patching transition -$MOD_LIVEPATCH: pre_patch_callback: vmlinux +$MOD_LIVEPATCH: pre_patch_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: post_patch_callback: state 1 $MOD_LIVEPATCH: fix_console_loglevel: fixing console_loglevel livepatch: '$MOD_LIVEPATCH': patching complete % echo 0 > $SYSFS_KLP_DIR/$MOD_LIVEPATCH/enabled livepatch: '$MOD_LIVEPATCH': initializing unpatching transition -$MOD_LIVEPATCH: pre_unpatch_callback: vmlinux +$MOD_LIVEPATCH: pre_unpatch_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: free_loglevel_state: freeing space for the stored console_= loglevel +$MOD_LIVEPATCH: post_unpatch_callback: state 1 (nope) +$MOD_LIVEPATCH: shadow_conosle_loglevel_dtor: freeing space for the stored= console_loglevel livepatch: '$MOD_LIVEPATCH': unpatching complete % rmmod $MOD_LIVEPATCH" =20 @@ -54,32 +54,28 @@ unload_lp $MOD_LIVEPATCH2 check_result "% insmod test_modules/$MOD_LIVEPATCH.ko livepatch: enabling patch '$MOD_LIVEPATCH' livepatch: '$MOD_LIVEPATCH': initializing patching transition -$MOD_LIVEPATCH: pre_patch_callback: vmlinux +$MOD_LIVEPATCH: pre_patch_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: post_patch_callback: state 1 $MOD_LIVEPATCH: fix_console_loglevel: fixing console_loglevel livepatch: '$MOD_LIVEPATCH': patching complete % insmod test_modules/$MOD_LIVEPATCH2.ko 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 > $SYSFS_KLP_DIR/$MOD_LIVEPATCH2/enabled livepatch: '$MOD_LIVEPATCH2': initializing unpatching transition -$MOD_LIVEPATCH2: pre_unpatch_callback: vmlinux +$MOD_LIVEPATCH2: pre_unpatch_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: free_loglevel_state: freeing space for the stored console= _loglevel +$MOD_LIVEPATCH2: post_unpatch_callback: state 1 (nope) +$MOD_LIVEPATCH2: shadow_conosle_loglevel_dtor: freeing space for the store= d console_loglevel livepatch: '$MOD_LIVEPATCH2': unpatching complete % rmmod $MOD_LIVEPATCH2" =20 @@ -99,42 +95,34 @@ unload_lp $MOD_LIVEPATCH3 check_result "% insmod test_modules/$MOD_LIVEPATCH2.ko livepatch: enabling patch '$MOD_LIVEPATCH2' livepatch: '$MOD_LIVEPATCH2': initializing patching transition -$MOD_LIVEPATCH2: pre_patch_callback: vmlinux +$MOD_LIVEPATCH2: pre_patch_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: post_patch_callback: state 1 $MOD_LIVEPATCH2: fix_console_loglevel: fixing console_loglevel livepatch: '$MOD_LIVEPATCH2': patching complete % insmod test_modules/$MOD_LIVEPATCH3.ko 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 % insmod test_modules/$MOD_LIVEPATCH2.ko 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 > $SYSFS_KLP_DIR/$MOD_LIVEPATCH2/enabled livepatch: '$MOD_LIVEPATCH2': initializing unpatching transition -$MOD_LIVEPATCH2: pre_unpatch_callback: vmlinux +$MOD_LIVEPATCH2: pre_unpatch_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: free_loglevel_state: freeing space for the stored console= _loglevel +$MOD_LIVEPATCH2: post_unpatch_callback: state 1 (nope) +$MOD_LIVEPATCH2: shadow_conosle_loglevel_dtor: freeing space for the store= d console_loglevel livepatch: '$MOD_LIVEPATCH2': unpatching complete % rmmod $MOD_LIVEPATCH2 % rmmod $MOD_LIVEPATCH3" @@ -152,11 +140,11 @@ unload_lp $MOD_LIVEPATCH2 check_result "% insmod test_modules/$MOD_LIVEPATCH2.ko livepatch: enabling patch '$MOD_LIVEPATCH2' livepatch: '$MOD_LIVEPATCH2': initializing patching transition -$MOD_LIVEPATCH2: pre_patch_callback: vmlinux +$MOD_LIVEPATCH2: pre_patch_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: post_patch_callback: state 1 $MOD_LIVEPATCH2: fix_console_loglevel: fixing console_loglevel livepatch: '$MOD_LIVEPATCH2': patching complete % insmod test_modules/$MOD_LIVEPATCH.ko @@ -164,12 +152,12 @@ livepatch: Livepatch patch ($MOD_LIVEPATCH) is not co= mpatible with the already i insmod: ERROR: could not insert module test_modules/$MOD_LIVEPATCH.ko: Inv= alid parameters % echo 0 > $SYSFS_KLP_DIR/$MOD_LIVEPATCH2/enabled livepatch: '$MOD_LIVEPATCH2': initializing unpatching transition -$MOD_LIVEPATCH2: pre_unpatch_callback: vmlinux +$MOD_LIVEPATCH2: pre_unpatch_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: free_loglevel_state: freeing space for the stored console= _loglevel +$MOD_LIVEPATCH2: post_unpatch_callback: state 1 (nope) +$MOD_LIVEPATCH2: shadow_conosle_loglevel_dtor: freeing space for the store= d console_loglevel livepatch: '$MOD_LIVEPATCH2': unpatching complete % rmmod $MOD_LIVEPATCH2" =20 diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_state.= c b/tools/testing/selftests/livepatch/test_modules/test_klp_state.c index 57a4253acb01..7f601898ef7c 100644 --- a/tools/testing/selftests/livepatch/test_modules/test_klp_state.c +++ b/tools/testing/selftests/livepatch/test_modules/test_klp_state.c @@ -9,109 +9,113 @@ #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 allocate shadow variable for the original loglevel= \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) +/* Executed before patching, when the state is being enabled. */ +static int pre_patch_callback(struct klp_patch *patch, struct klp_state *s= tate) { - struct klp_state *loglevel_state; - - 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); + 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 being enabled. */ +static void post_patch_callback(struct klp_patch *patch, struct klp_state = *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 being disabled. */ +static void pre_unpatch_callback(struct klp_patch *patch, struct klp_state= *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 being disabled. + * + * The callback is not really needed. It is added just to check that + * the optional callback is called at the right time. + * + * The shadow variable will be freed automatically because state->is_shadow + * is set. + */ +static void post_unpatch_callback(struct klp_patch *patch, struct klp_stat= e *state) { - callback_info(__func__, obj); - free_loglevel_state(); + pr_info("%s: state %lu (nope)\n", __func__, state->id); +} + +/* + * The shadow_dtor callback is not really needed. It is added just to show= that + * it is called automatically when disabling a klp_state with .is_shadow s= et. + */ +static void shadow_conosle_loglevel_dtor(void *obj, void *shadow_data) +{ + pr_info("%s: freeing space for the stored console_loglevel\n", + __func__); } =20 static struct klp_func no_funcs[] =3D { @@ -122,19 +126,21 @@ 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, + .is_shadow =3D true, + .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, + .shadow_dtor =3D shadow_conosle_loglevel_dtor, + }, }, { } }; =20 diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_state2= .c b/tools/testing/selftests/livepatch/test_modules/test_klp_state2.c index c978ea4d5e67..128855764bf8 100644 --- a/tools/testing/selftests/livepatch/test_modules/test_klp_state2.c +++ b/tools/testing/selftests/livepatch/test_modules/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" --=20 2.47.1 From nobody Mon Dec 15 21:31:33 2025 Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.223.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9678728EC74; Wed, 15 Jan 2025 08:25:25 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.135.223.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929527; cv=none; b=VwBhi6tA8EHXwqHmJ2wonNBZCFSSg6VQa4YSxH8dKn/may8Y//vkJLbEyNBcupf0rLLEcKHMWT6xZAzblb+IItPO3ArB1De1EQP4/0+RF1oO0xy4kw7vht39kf3H8Td7BH6XYhwT39Ii/QwPdKC5XISszksR99Dh/euP21+pbZk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929527; c=relaxed/simple; bh=1HGuldc+pCW/eL6dRJsdJOXJWJZNn7nZgbUYrVGF2Mo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=JaiOQSKOK7jhKS1mpbZmUCmv0xOo4LmpzvK9aR1T3MPHy3kMyRYhmXlvG3f1kF3+TaywE68agTvMmfv7E1g0fTPH058frGZ6oF+yIkjuPuCxQjXehxfUXen5aArBzer8ULDKptOKxWVDuzC1T1JEheyHemGmPCqZQg31JiDWB7c= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com; spf=pass smtp.mailfrom=suse.com; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=O79/huLa; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=O79/huLa; arc=none smtp.client-ip=195.135.223.131 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=suse.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="O79/huLa"; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="O79/huLa" Received: from pathway.suse.cz (unknown [10.100.201.202]) by smtp-out2.suse.de (Postfix) with ESMTP id 95C4A1F37C; Wed, 15 Jan 2025 08:25:23 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929523; 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=zMrg5sxdQn73Xwu6biv4RUUUbw+oEczuALNIQtPy9BM=; b=O79/huLayu4JjIgw4KHr6leCcN7h2mLQ8NFfPtyuEfwtDps2QL5hKK+rHeaB2T4G/uRzk9 kIGzqEraoZ0QhqKQkR3EXFphCPd6q/p7Q5H1LciSXr2OvZKm0qopuGReoSaCK4bSmbu4dP 882a/GG2O1hNmtMO1Rsyi52YOlE/2co= Authentication-Results: smtp-out2.suse.de; none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929523; 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=zMrg5sxdQn73Xwu6biv4RUUUbw+oEczuALNIQtPy9BM=; b=O79/huLayu4JjIgw4KHr6leCcN7h2mLQ8NFfPtyuEfwtDps2QL5hKK+rHeaB2T4G/uRzk9 kIGzqEraoZ0QhqKQkR3EXFphCPd6q/p7Q5H1LciSXr2OvZKm0qopuGReoSaCK4bSmbu4dP 882a/GG2O1hNmtMO1Rsyi52YOlE/2co= 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: [PATCH v1 04/19] livepatch: Add "block_disable" flag to per-state API and remove versioning Date: Wed, 15 Jan 2025 09:24:16 +0100 Message-ID: <20250115082431.5550-5-pmladek@suse.com> X-Mailer: git-send-email 2.47.1 In-Reply-To: <20250115082431.5550-1-pmladek@suse.com> References: <20250115082431.5550-1-pmladek@suse.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Spam-Score: -6.80 X-Spamd-Result: default: False [-6.80 / 50.00]; REPLY(-4.00)[]; BAYES_HAM(-3.00)[100.00%]; MID_CONTAINS_FROM(1.00)[]; NEURAL_HAM_LONG(-1.00)[-1.000]; R_MISSING_CHARSET(0.50)[]; NEURAL_HAM_SHORT(-0.20)[-1.000]; MIME_GOOD(-0.10)[text/plain]; RCVD_COUNT_ZERO(0.00)[0]; MIME_TRACE(0.00)[0:+]; ARC_NA(0.00)[]; RCPT_COUNT_SEVEN(0.00)[7]; FUZZY_BLOCKED(0.00)[rspamd.com]; TO_DN_SOME(0.00)[]; FROM_HAS_DN(0.00)[]; DKIM_SIGNED(0.00)[suse.com:s=susede1]; FROM_EQ_ENVFROM(0.00)[]; TO_MATCH_ENVRCPT_ALL(0.00)[]; DBL_BLOCKED_OPENRESOLVER(0.00)[suse.com:mid,suse.com:email] X-Spam-Flag: NO X-Spam-Level: Content-Type: text/plain; charset="utf-8" This commit enhances the livepatch state API with a new flag, "block_disable", and removes the previously introduced per-state versioning mechanism. The "block_disable" flag addresses scenarios where reverting certain system changes introduced by a livepatch is undesirable due to complexity, risk, or other factors. When set, this flag prevents: - Disabling the livepatch entirely. - Replacing the livepatch with an older version that doesn't support the blocked state. This ensures that critical system modifications remain in effect, even when updating or reverting livepatches. The per-state versioning mechanism, intended to facilitate modifications to existing states, has proven unnecessary in practice and is therefore removed. Alternative approaches, such as introducing new states, can achieve the same results if needed. These changes introduce a new approach to compatibility between livepatches. It is no longer defined by versioning, which lacked specific semantics. Instead, compatibility is determined by whether a livepatch can disable the system state changes caused by the use of callbacks and shadow variables. Consequently, compatibility is now only checked for livepatches using atomic replacement. This is because only these livepatches can disable an existing state when enabled. The related self-test has been updated to reflect these changes. It utilizes three livepatches built from the same source, with new command-line options to specify whether a livepatch supports and can disable a given state change. Signed-off-by: Petr Mladek --- include/linux/livepatch.h | 4 +- kernel/livepatch/core.c | 7 ++++ kernel/livepatch/state.c | 35 ++++++++++++---- kernel/livepatch/state.h | 1 + .../testing/selftests/livepatch/test-state.sh | 40 ++++++++++++------- .../livepatch/test_modules/test_klp_state.c | 27 +++++++------ .../livepatch/test_modules/test_klp_state2.c | 2 - .../livepatch/test_modules/test_klp_state3.c | 2 +- 8 files changed, 78 insertions(+), 40 deletions(-) diff --git a/include/linux/livepatch.h b/include/linux/livepatch.h index c624f1105663..56e71d488e71 100644 --- a/include/linux/livepatch.h +++ b/include/linux/livepatch.h @@ -171,16 +171,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 enabling or disabling the state + * @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; }; diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c index 527fdb0a6b0a..4d244eef0e53 100644 --- a/kernel/livepatch/core.c +++ b/kernel/livepatch/core.c @@ -374,6 +374,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 diff --git a/kernel/livepatch/state.c b/kernel/livepatch/state.c index 16ad695b1e88..a9af511e5d9e 100644 --- a/kernel/livepatch/state.c +++ b/kernel/livepatch/state.c @@ -83,7 +83,12 @@ struct klp_state *klp_get_prev_state(unsigned long id) } EXPORT_SYMBOL_GPL(klp_get_prev_state); =20 -/* Check if the patch is able to deal with the existing system state. */ +/* + * Check if the new patch is able to deal with the existing system state. + * Used only for livepatches with the atomic replace enabled. The patch ei= ther + * has to support the existing state or the existing patch must be able + * to disable it. + */ static bool klp_is_state_compatible(struct klp_patch *patch, struct klp_state *old_state) { @@ -91,23 +96,25 @@ 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. + * Check if the new livepatch could atomically replace existing ones. + * It must either support the existing states. Or the existing livepatches + * must be able to disable the obsolete states. */ bool klp_is_patch_compatible(struct klp_patch *patch) { struct klp_patch *old_patch; struct klp_state *old_state; =20 + 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 +125,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; +} + static bool is_state_in_other_patches(struct klp_patch *patch, struct klp_state *state) { diff --git a/kernel/livepatch/state.h b/kernel/livepatch/state.h index 65c0c2cde04c..0620fd692820 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_states_pre_patch(struct klp_patch *patch); void klp_states_post_patch(struct klp_patch *patch); void klp_states_pre_unpatch(struct klp_patch *patch); diff --git a/tools/testing/selftests/livepatch/test-state.sh b/tools/testin= g/selftests/livepatch/test-state.sh index 2f4a5109fdf5..72e9b4f0b629 100755 --- a/tools/testing/selftests/livepatch/test-state.sh +++ b/tools/testing/selftests/livepatch/test-state.sh @@ -132,12 +132,15 @@ 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 +load_lp $MOD_LIVEPATCH2 state_block_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 unload_lp $MOD_LIVEPATCH2 +disable_lp $MOD_LIVEPATCH +unload_lp $MOD_LIVEPATCH =20 -check_result "% insmod test_modules/$MOD_LIVEPATCH2.ko +check_result "% insmod test_modules/$MOD_LIVEPATCH2.ko state_block_disable= =3D1 livepatch: enabling patch '$MOD_LIVEPATCH2' livepatch: '$MOD_LIVEPATCH2': initializing patching transition $MOD_LIVEPATCH2: pre_patch_callback: state 1 @@ -147,18 +150,25 @@ livepatch: '$MOD_LIVEPATCH2': completing patching tra= nsition $MOD_LIVEPATCH2: post_patch_callback: state 1 $MOD_LIVEPATCH2: fix_console_loglevel: fixing console_loglevel livepatch: '$MOD_LIVEPATCH2': patching complete -% insmod test_modules/$MOD_LIVEPATCH.ko +% insmod test_modules/$MOD_LIVEPATCH.ko no_state=3D1 livepatch: Livepatch patch ($MOD_LIVEPATCH) is not compatible with the alr= eady installed livepatches. insmod: ERROR: could not insert module test_modules/$MOD_LIVEPATCH.ko: Inv= alid parameters -% echo 0 > $SYSFS_KLP_DIR/$MOD_LIVEPATCH2/enabled -livepatch: '$MOD_LIVEPATCH2': initializing unpatching transition -$MOD_LIVEPATCH2: pre_unpatch_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: state 1 (nope) -$MOD_LIVEPATCH2: shadow_conosle_loglevel_dtor: freeing space for the store= d console_loglevel -livepatch: '$MOD_LIVEPATCH2': unpatching complete -% rmmod $MOD_LIVEPATCH2" +% insmod test_modules/$MOD_LIVEPATCH.ko +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 +% rmmod $MOD_LIVEPATCH2 +% echo 0 > $SYSFS_KLP_DIR/$MOD_LIVEPATCH/enabled +livepatch: '$MOD_LIVEPATCH': initializing unpatching transition +$MOD_LIVEPATCH: pre_unpatch_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: state 1 (nope) +$MOD_LIVEPATCH: shadow_conosle_loglevel_dtor: freeing space for the stored= console_loglevel +livepatch: '$MOD_LIVEPATCH': unpatching complete +% rmmod $MOD_LIVEPATCH" =20 exit 0 diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_state.= c b/tools/testing/selftests/livepatch/test_modules/test_klp_state.c index 7f601898ef7c..518229815449 100644 --- a/tools/testing/selftests/livepatch/test_modules/test_klp_state.c +++ b/tools/testing/selftests/livepatch/test_modules/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) @@ -132,7 +121,6 @@ static struct klp_object objs[] =3D { static struct klp_state states[] =3D { { .id =3D CONSOLE_LOGLEVEL_FIX_ID, - .version =3D CONSOLE_LOGLEVEL_FIX_VERSION, .is_shadow =3D true, .callbacks =3D { .pre_patch =3D pre_patch_callback, @@ -151,8 +139,23 @@ static struct klp_patch patch =3D { .replace =3D true, }; =20 +static bool state_block_disable; + +module_param(state_block_disable, bool, 0600); +MODULE_PARM_DESC(state_block_disable, "Set to 1 to pretend that the livepa= tch is not capable of disabling the state (default =3D 0)."); + +static bool no_state; + +module_param(no_state, bool, 0600); +MODULE_PARM_DESC(no_state, "Set to 1 when the livepatch should not support= the state (default =3D 0)."); + static int test_klp_callbacks_demo_init(void) { + states[0].block_disable =3D state_block_disable; + + if (no_state) + patch.states =3D NULL; + return klp_enable_patch(&patch); } =20 diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_state2= .c b/tools/testing/selftests/livepatch/test_modules/test_klp_state2.c index 128855764bf8..b8fe1ca2d802 100644 --- a/tools/testing/selftests/livepatch/test_modules/test_klp_state2.c +++ b/tools/testing/selftests/livepatch/test_modules/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/tools/testing/selftests/livepatch/test_modules/test_klp_state3= .c b/tools/testing/selftests/livepatch/test_modules/test_klp_state3.c index 9226579d10c5..b8fe1ca2d802 100644 --- a/tools/testing/selftests/livepatch/test_modules/test_klp_state3.c +++ b/tools/testing/selftests/livepatch/test_modules/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" --=20 2.47.1 From nobody Mon Dec 15 21:31:33 2025 Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.223.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CBC1028EC65; Wed, 15 Jan 2025 08:25:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.135.223.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929537; cv=none; b=ZXLvlLSZYJSwZPAAKzwng8lXN8IJYkNX3gpUL28blgC5VKEk3DH9i8t6hYjM2p5m0TyCoq+QFU5sQ07fcs4p1q8LC6iQgVGDGT/pQRSOfKHznw3qMJp+qEoAzVtpu8D0r/HDQIiaStUoD4JTSLMXwfxnwMKIZ9s5qYAiqmIcu3Y= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929537; c=relaxed/simple; bh=WpDtr3D9908NMYXv+uWPCuwNXuvymxMMVXgONcR/yHE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=rMXImJSJe9WEG9ra/brMEX21L3Io7UCOQH4b8kvzx58ACy5TGPXoUqT5sLl+7Fg8xcvs2+PE1hu8dTbRO4LR8MwcL1CQeKj6RTuq8IqJcIgH/Aj1dnlDb5YFLt/D5ZGRxJSWoU6bgshlihVq+oXndrECiWGrsPckEyy4dpqoCUA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com; spf=pass smtp.mailfrom=suse.com; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=fWrUKIaO; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=fWrUKIaO; arc=none smtp.client-ip=195.135.223.131 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=suse.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="fWrUKIaO"; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="fWrUKIaO" Received: from pathway.suse.cz (unknown [10.100.201.202]) by smtp-out2.suse.de (Postfix) with ESMTP id EF7971F37C; Wed, 15 Jan 2025 08:25:33 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929534; 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=nQvtRTEW+KaxvNQ+F0E0rfsumD1yVk/X2iF4fgnw1p8=; b=fWrUKIaOW4wasx1SdI22mNxpOD7q58BxZ/2ALHlCEe9Y06SKORo7scJyABrNU2Rng5AWAv sDjce33V4M24gfUB8FIaBz0DFAf6ZYTFg2neq7k1F9Mn+vmEEQZXo4JQqDQ7LelPO05cWo jApdFFNZulsEcVtuiEOElM4F5+RUwz8= Authentication-Results: smtp-out2.suse.de; none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929534; 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=nQvtRTEW+KaxvNQ+F0E0rfsumD1yVk/X2iF4fgnw1p8=; b=fWrUKIaOW4wasx1SdI22mNxpOD7q58BxZ/2ALHlCEe9Y06SKORo7scJyABrNU2Rng5AWAv sDjce33V4M24gfUB8FIaBz0DFAf6ZYTFg2neq7k1F9Mn+vmEEQZXo4JQqDQ7LelPO05cWo jApdFFNZulsEcVtuiEOElM4F5+RUwz8= 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: [PATCH v1 05/19] livepatch: Remove "data" from struct klp_state Date: Wed, 15 Jan 2025 09:24:17 +0100 Message-ID: <20250115082431.5550-6-pmladek@suse.com> X-Mailer: git-send-email 2.47.1 In-Reply-To: <20250115082431.5550-1-pmladek@suse.com> References: <20250115082431.5550-1-pmladek@suse.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Spam-Score: -6.80 X-Spamd-Result: default: False [-6.80 / 50.00]; REPLY(-4.00)[]; BAYES_HAM(-3.00)[100.00%]; MID_CONTAINS_FROM(1.00)[]; NEURAL_HAM_LONG(-1.00)[-1.000]; R_MISSING_CHARSET(0.50)[]; NEURAL_HAM_SHORT(-0.20)[-1.000]; MIME_GOOD(-0.10)[text/plain]; RCVD_COUNT_ZERO(0.00)[0]; MIME_TRACE(0.00)[0:+]; ARC_NA(0.00)[]; RCPT_COUNT_SEVEN(0.00)[7]; FUZZY_BLOCKED(0.00)[rspamd.com]; TO_DN_SOME(0.00)[]; FROM_HAS_DN(0.00)[]; DKIM_SIGNED(0.00)[suse.com:s=susede1]; FROM_EQ_ENVFROM(0.00)[]; TO_MATCH_ENVRCPT_ALL(0.00)[]; DBL_BLOCKED_OPENRESOLVER(0.00)[suse.com:mid,suse.com:email] X-Spam-Flag: NO X-Spam-Level: Content-Type: text/plain; charset="utf-8" The "data" pointer in "struct klp_state" is associated with the lifetime of the livepatch module, not the livepatch state. This means it's lost when a livepatch is replaced, even if the new livepatch supports the same state. Shadow variables provide a more reliable way to attach data to a livepatch state. Their lifetime can be tied to the state's lifetime by: - Sharing the same "id" - Setting "is_shadow" in "struct klp_state" Removing the "data" pointer prevents potential issues once per-object callbacks are removed, as it cannot be used securely in that context. Signed-off-by: Petr Mladek --- include/linux/livepatch.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/linux/livepatch.h b/include/linux/livepatch.h index 56e71d488e71..d02d7a616338 100644 --- a/include/linux/livepatch.h +++ b/include/linux/livepatch.h @@ -175,14 +175,12 @@ struct klp_state_callbacks { * @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; struct klp_state_callbacks callbacks; bool block_disable; bool is_shadow; - void *data; }; =20 /** --=20 2.47.1 From nobody Mon Dec 15 21:31:33 2025 Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.223.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7F66C3DAC14; Wed, 15 Jan 2025 08:25:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.135.223.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929549; cv=none; b=J3AztSKreRcH+iLF0loP12EDHcKJ/Yc/ds3q6BQwQYC4llSINkQFqCS+02XkFkZ0ijOYaLv8dOX2PJmRGphnLNs2EV5KEJmlymCaePtlDzZaE70Nf6bcFoplEjs/b+1N++5A7rTJjb17rz17c6SEZ4DV9zp2wLYG7TyK91r3LwU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929549; c=relaxed/simple; bh=4QTo2NDNuzr4RK3YrSrJ5nmfkJJ89UojGACdDI+ySmg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=evt5Varkncyqxh+WtIiZRDeQrK0ogZsWrSlUgOlZBfdOtoI1/TY5ZCyE68wb4zZO5P4/Mskchizu2LlhCIY1umJCg6mj++7EFKUbhYZicfMPJhrtpjSPrdiiW4+YnHPaxKwVTXP9bV/d98FKk1ca9Ok/NsTEmRJG/xzIPyQ/zXQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com; spf=pass smtp.mailfrom=suse.com; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=sHRDrnIp; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=r1LytqUN; arc=none smtp.client-ip=195.135.223.131 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=suse.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="sHRDrnIp"; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="r1LytqUN" Received: from pathway.suse.cz (unknown [10.100.201.202]) by smtp-out2.suse.de (Postfix) with ESMTP id 461141F37C; Wed, 15 Jan 2025 08:25:44 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929545; 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=CA5Xr0nkZJTn3RPFgpmmPMXzJcaQLfN9rFU4vnHY6N0=; b=sHRDrnIpqDwEMCqnbhmGaBRVarwk3Tj/2jY30pZlDGZKp/C/BOLod0YX589fXFmsCuKwEX hPCUi12hpC2jhiI4cC0O9ONpkFPqY28EkMnx/FPL9q3HJpNstV1Q7PxTs0YPbwWij6Gjq/ 2dcaaQ5gtw908vcF7YpaHiDS/SyIdgQ= Authentication-Results: smtp-out2.suse.de; none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929544; 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=CA5Xr0nkZJTn3RPFgpmmPMXzJcaQLfN9rFU4vnHY6N0=; b=r1LytqUNQERO956ZGUUJsfG//pN/oLS7UDFgf/7c3RqLt4m2P5Ow8TcYtXnmug7YcS/++n fjHaB7oQJeK5+zBXzFILSxi2LvbK8n6L5DrTXxELJvlqZHBx4BHKHUuckp5cWgxtbRIYar F/QBQy2tNGbeV6xXjLa7OuWHq6astLY= 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: [PATCH v1 06/19] selftests/livepatch: Remove callbacks from sysfs interface testing Date: Wed, 15 Jan 2025 09:24:18 +0100 Message-ID: <20250115082431.5550-7-pmladek@suse.com> X-Mailer: git-send-email 2.47.1 In-Reply-To: <20250115082431.5550-1-pmladek@suse.com> References: <20250115082431.5550-1-pmladek@suse.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Spam-Level: X-Spamd-Result: default: False [-6.80 / 50.00]; REPLY(-4.00)[]; BAYES_HAM(-3.00)[100.00%]; MID_CONTAINS_FROM(1.00)[]; NEURAL_HAM_LONG(-1.00)[-1.000]; R_MISSING_CHARSET(0.50)[]; NEURAL_HAM_SHORT(-0.20)[-1.000]; MIME_GOOD(-0.10)[text/plain]; RCVD_COUNT_ZERO(0.00)[0]; MIME_TRACE(0.00)[0:+]; ARC_NA(0.00)[]; RCPT_COUNT_SEVEN(0.00)[7]; FUZZY_BLOCKED(0.00)[rspamd.com]; TO_DN_SOME(0.00)[]; FROM_HAS_DN(0.00)[]; DKIM_SIGNED(0.00)[suse.com:s=susede1]; FROM_EQ_ENVFROM(0.00)[]; TO_MATCH_ENVRCPT_ALL(0.00)[]; DBL_BLOCKED_OPENRESOLVER(0.00)[suse.com:email,suse.com:mid,pathway.suse.cz:helo] X-Spam-Score: -6.80 X-Spam-Flag: NO Content-Type: text/plain; charset="utf-8" This commit removes the use of callbacks in the sysfs interface testing. The callbacks are not necessary for this testing and create unnecessary noise. This commit replaces the test modules that use the obsolete per-object callbacks with a simple test module in a "Hello World" style and a corresponding livepatch. These new modules will be extended in the future to include an optional integration of livepatch states, per-state callbacks, and shadow variables. They will be used for testing all the new features. Signed-off-by: Petr Mladek --- .../testing/selftests/livepatch/test-sysfs.sh | 48 ++++++--------- .../selftests/livepatch/test_modules/Makefile | 2 + .../livepatch/test_modules/test_klp_speaker.c | 38 ++++++++++++ .../test_modules/test_klp_speaker_livepatch.c | 61 +++++++++++++++++++ 4 files changed, 121 insertions(+), 28 deletions(-) create mode 100644 tools/testing/selftests/livepatch/test_modules/test_klp= _speaker.c create mode 100644 tools/testing/selftests/livepatch/test_modules/test_klp= _speaker_livepatch.c diff --git a/tools/testing/selftests/livepatch/test-sysfs.sh b/tools/testin= g/selftests/livepatch/test-sysfs.sh index 2c91428d2997..c565e6005710 100755 --- a/tools/testing/selftests/livepatch/test-sysfs.sh +++ b/tools/testing/selftests/livepatch/test-sysfs.sh @@ -43,8 +43,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 @@ -57,32 +57,24 @@ check_sysfs_value "$MOD_LIVEPATCH" "$MOD_TARGET/patche= d" "0" disable_lp $MOD_LIVEPATCH unload_lp $MOD_LIVEPATCH =20 -check_result "% insmod test_modules/test_klp_callbacks_demo.ko -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 -% insmod test_modules/test_klp_callbacks_mod.ko -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 > $SYSFS_KLP_DIR/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 "% insmod test_modules/$MOD_LIVEPATCH.ko +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 +% insmod test_modules/$MOD_TARGET.ko +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 > $SYSFS_KLP_DIR/$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 start_test "sysfs test replace enabled" =20 diff --git a/tools/testing/selftests/livepatch/test_modules/Makefile b/tool= s/testing/selftests/livepatch/test_modules/Makefile index 939230e571f5..0978c489a67a 100644 --- a/tools/testing/selftests/livepatch/test_modules/Makefile +++ b/tools/testing/selftests/livepatch/test_modules/Makefile @@ -9,6 +9,8 @@ obj-m +=3D test_klp_atomic_replace.o \ test_klp_kprobe.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/tools/testing/selftests/livepatch/test_modules/test_klp_speake= r.c b/tools/testing/selftests/livepatch/test_modules/test_klp_speaker.c new file mode 100644 index 000000000000..b1fb135820b0 --- /dev/null +++ b/tools/testing/selftests/livepatch/test_modules/test_klp_speaker.c @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2024 SUSE + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include + +/** + * test_klp_speaker - test module for testing misc livepatching features + * + * The module provides a virtual speaker who can do: + * + * - Start a show with a greeting, see speaker_welcome(). + */ + +noinline +static void __always_used 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/tools/testing/selftests/livepatch/test_modules/test_klp_speake= r_livepatch.c b/tools/testing/selftests/livepatch/test_modules/test_klp_spe= aker_livepatch.c new file mode 100644 index 000000000000..26a8dd15f723 --- /dev/null +++ b/tools/testing/selftests/livepatch/test_modules/test_klp_speaker_livep= atch.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2024 SUSE + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include + +/** + * test_klp_speaker_livepatch - test livepatch for testing various livepat= ching + * features. + * + * The livepatch modifies the behavior of a virtual speaker provided by + * the module test_klp_speaker. It can do: + * + * - Improve the speaker's greeting from "Hello, World!" to + * "Ladies and gentleman, ..." + */ + +static void lp_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 lp_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"); +MODULE_DESCRIPTION("Livepatch test: livepatch test_klp_speaker test module= "); --=20 2.47.1 From nobody Mon Dec 15 21:31:33 2025 Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.223.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id BDF571E7C1B; Wed, 15 Jan 2025 08:25:57 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.135.223.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929559; cv=none; b=fH69iaab3MzBkUsUdxXUgT46dss8goCGO6Sqqx5R0+piLdK1kcvAJ1gXKebuJlwRjbBH5S2IpwuWgeNNMSFZUi6R1Ie91OiQDNgDTJ3XIYCVIowYBGNhiOHehMfIM0aRBs7Tfh5KjqCGffjcoa3beqWWJgND+tlT9jQ0f84o+lo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929559; c=relaxed/simple; bh=Or8V4YstlDyzCXeFBs/XuDB0ivkcgQDmP10vjcrnEGk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=FO4CnpuavBadmOGwYdUGrW7E68uXYWtLD4kQzeaVoy5lFi67oREsgU7PeSUpzTuc5qJZI/3rkgryvUsNKlTlUBUPzk2KWY/KI/rVMIK6n+PrXp9E0NgqEMLBbFQNlL1Rhwh8TaZdh3Gl/6RAUNH1g20cVWVcxHsw9r4mxzm3sBg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com; spf=pass smtp.mailfrom=suse.com; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=mCj1zjcm; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=eTeQb9jr; arc=none smtp.client-ip=195.135.223.131 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=suse.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="mCj1zjcm"; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="eTeQb9jr" Received: from pathway.suse.cz (unknown [10.100.201.202]) by smtp-out2.suse.de (Postfix) with ESMTP id D6D391F37C; Wed, 15 Jan 2025 08:25:55 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929556; 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=ieNcANd9y+chKXgFAQhmhObhpsD2MZA9EtaeYXczQi0=; b=mCj1zjcmQTXxJqQY+7CrSFV3MMoGdaR9z9+U0Ut842uCjkaEVpREt6Whpv7ismZQnj0R9+ NzboOas6V0vSEQWDvWxw5eUi9FyBOEXDBz/Gsj/tzNIxibO4U42bqhugqSo78+nt0KTN4B +8IH1Yem638PksqfdEvyWms3j9W14cY= Authentication-Results: smtp-out2.suse.de; none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929555; 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=ieNcANd9y+chKXgFAQhmhObhpsD2MZA9EtaeYXczQi0=; b=eTeQb9jrPbTWHLWfpHSGFCminuQnpR4/PjbPL033IR252o1LJRsdNmskx6bvLUWRtJb6PU qrlpm9imsFi3qimbhIMmKuwkd/XeQYFl94pzE7u+t6T+QOfVeIG32UyAWOMRWuvgt5GHVN wP2Blvi5H1OY5iLIz/TzDTPpzkKHLN0= 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: [PATCH v1 07/19] selftests/livepatch: Substitute hard-coded /sys/module path Date: Wed, 15 Jan 2025 09:24:19 +0100 Message-ID: <20250115082431.5550-8-pmladek@suse.com> X-Mailer: git-send-email 2.47.1 In-Reply-To: <20250115082431.5550-1-pmladek@suse.com> References: <20250115082431.5550-1-pmladek@suse.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Spam-Level: X-Spamd-Result: default: False [-6.80 / 50.00]; REPLY(-4.00)[]; BAYES_HAM(-3.00)[100.00%]; MID_CONTAINS_FROM(1.00)[]; NEURAL_HAM_LONG(-1.00)[-1.000]; R_MISSING_CHARSET(0.50)[]; NEURAL_HAM_SHORT(-0.20)[-1.000]; MIME_GOOD(-0.10)[text/plain]; RCVD_COUNT_ZERO(0.00)[0]; MIME_TRACE(0.00)[0:+]; ARC_NA(0.00)[]; TO_DN_SOME(0.00)[]; RCPT_COUNT_SEVEN(0.00)[7]; DKIM_SIGNED(0.00)[suse.com:s=susede1]; FUZZY_BLOCKED(0.00)[rspamd.com]; FROM_HAS_DN(0.00)[]; R_RATELIMIT(0.00)[to_ip_from(RLj3e56pwiuh8u4wxetmhsq5s5)]; FROM_EQ_ENVFROM(0.00)[]; TO_MATCH_ENVRCPT_ALL(0.00)[]; DBL_BLOCKED_OPENRESOLVER(0.00)[pathway.suse.cz:helo,suse.com:email,suse.com:mid] X-Spam-Score: -6.80 X-Spam-Flag: NO Content-Type: text/plain; charset="utf-8" Substitute hard-coded /sys/module path with $SYSFS_MODULE_DIR. It is going to be used even more often in the upcoming selftests. Signed-off-by: Petr Mladek --- tools/testing/selftests/livepatch/functions.sh | 18 ++++++++++-------- .../selftests/livepatch/test-callbacks.sh | 2 +- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/tools/testing/selftests/livepatch/functions.sh b/tools/testing= /selftests/livepatch/functions.sh index e5d06fb40233..3f86b89e6ea7 100644 --- a/tools/testing/selftests/livepatch/functions.sh +++ b/tools/testing/selftests/livepatch/functions.sh @@ -6,7 +6,9 @@ =20 MAX_RETRIES=3D600 RETRY_INTERVAL=3D".1" # seconds -SYSFS_KERNEL_DIR=3D"/sys/kernel" +SYSFS_DIR=3D"/sys" +SYSFS_KERNEL_DIR=3D"$SYSFS_DIR/kernel" +SYSFS_MODULE_DIR=3D"$SYSFS_DIR/module" SYSFS_KLP_DIR=3D"$SYSFS_KERNEL_DIR/livepatch" SYSFS_DEBUG_DIR=3D"$SYSFS_KERNEL_DIR/debug" SYSFS_KPROBES_DIR=3D"$SYSFS_DEBUG_DIR/kprobes" @@ -160,7 +162,7 @@ function __load_mod() { fi =20 # Wait for module in sysfs ... - loop_until '[[ -e "/sys/module/$mod" ]]' || + loop_until '[[ -e "$SYSFS_MODULE_DIR/$mod" ]]' || die "failed to load module $mod" } =20 @@ -228,7 +230,7 @@ function unload_mod() { local mod=3D"$1" =20 # Wait for module reference count to clear ... - loop_until '[[ $(cat "/sys/module/$mod/refcnt") =3D=3D "0" ]]' || + loop_until '[[ $(cat "$SYSFS_MODULE_DIR/$mod/refcnt") =3D=3D "0" ]]' || die "failed to unload module $mod (refcnt)" =20 log "% rmmod $mod" @@ -238,8 +240,8 @@ function unload_mod() { fi =20 # Wait for module in sysfs ... - loop_until '[[ ! -e "/sys/module/$mod" ]]' || - die "failed to unload module $mod (/sys/module)" + loop_until '[[ ! -e "$SYSFS_MODULE_DIR/$mod" ]]' || + die "failed to unload module $mod ($SYSFS_MODULE_DIR)" } =20 # unload_lp(modname) - unload a kernel module with a livepatch @@ -269,11 +271,11 @@ function set_pre_patch_ret { local mod=3D"$1"; shift local ret=3D"$1" =20 - log "% echo $ret > /sys/module/$mod/parameters/pre_patch_ret" - echo "$ret" > /sys/module/"$mod"/parameters/pre_patch_ret + log "% echo $ret > $SYSFS_MODULE_DIR/$mod/parameters/pre_patch_ret" + echo "$ret" > $SYSFS_MODULE_DIR/"$mod"/parameters/pre_patch_ret =20 # Wait for sysfs value to hold ... - loop_until '[[ $(cat "/sys/module/$mod/parameters/pre_patch_ret") =3D=3D = "$ret" ]]' || + loop_until '[[ $(cat "$SYSFS_MODULE_DIR/$mod/parameters/pre_patch_ret") = =3D=3D "$ret" ]]' || die "failed to set pre_patch_ret parameter for $mod module" } =20 diff --git a/tools/testing/selftests/livepatch/test-callbacks.sh b/tools/te= sting/selftests/livepatch/test-callbacks.sh index 37bbc3fb2780..a65fd860662e 100755 --- a/tools/testing/selftests/livepatch/test-callbacks.sh +++ b/tools/testing/selftests/livepatch/test-callbacks.sh @@ -303,7 +303,7 @@ livepatch: '$MOD_LIVEPATCH': starting patching transiti= on 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 +% echo -19 > $SYSFS_MODULE_DIR/$MOD_LIVEPATCH/parameters/pre_patch_ret % insmod test_modules/$MOD_TARGET.ko 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 --=20 2.47.1 From nobody Mon Dec 15 21:31:33 2025 Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.223.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0484E1E7C39; Wed, 15 Jan 2025 08:26:08 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.135.223.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929570; cv=none; b=mgvPSovbabqxOvDJnFslquuEPjzzAhKfrNdTDq1W4HfW8elCu2LYb8lWNwT7LAybLqoGyigsWbNEmY7oph/68HZtzHV0MjrWyXDf5PzgCf5ta4+LwOpWQoeJprXXEAqoQmPdSxAyztmViRpu0sNJ66jT6Dv692frbF5pLMzn4wk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929570; c=relaxed/simple; bh=2hjfF4ZlMCaHDECZuvvP5iA4W5UvoVBGOLluMbhrEtQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=YG3xHQHuSqkeM+IJklev7BU8VDMVXu8to7e2ImZJ4rWRdX74wqu5HXqHpyoeKdMs3r4fSqgR0CJW8CAl9U2XXXOoO13gpx+JjierzHNHaWrN4pSijtg07CRwaJo+Z0TgKL8D+Jeqbk4oa2eCHCFUCvsg7eLxk4CNlfGNqanuaPA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com; spf=pass smtp.mailfrom=suse.com; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=uowOYmLi; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=uowOYmLi; arc=none smtp.client-ip=195.135.223.131 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=suse.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="uowOYmLi"; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="uowOYmLi" Received: from pathway.suse.cz (unknown [10.100.201.202]) by smtp-out2.suse.de (Postfix) with ESMTP id 27FFB1F44E; Wed, 15 Jan 2025 08:26:06 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929566; 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=5gn6HjAcu1K9VBprx1QIrdqZVEUn+IY95Z03PvVK05E=; b=uowOYmLiuIQ51dYTnx/YxsaIAD+dLdcdt9o4aSu3zSg2gizlwi0/4gTaN4HIJ0+hKBYmb0 lM0fIAp/tgHD/7pXFL31blwQaNck8KX1AyXQo313CFgBSx5IQQF9avIHJzltaZsmu3qfHL Yc65za3+94/XQaWrG+de8NdorwiFzN4= Authentication-Results: smtp-out2.suse.de; none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929566; 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=5gn6HjAcu1K9VBprx1QIrdqZVEUn+IY95Z03PvVK05E=; b=uowOYmLiuIQ51dYTnx/YxsaIAD+dLdcdt9o4aSu3zSg2gizlwi0/4gTaN4HIJ0+hKBYmb0 lM0fIAp/tgHD/7pXFL31blwQaNck8KX1AyXQo313CFgBSx5IQQF9avIHJzltaZsmu3qfHL Yc65za3+94/XQaWrG+de8NdorwiFzN4= 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: [PATCH v1 08/19] selftests/livepatch: Move basic tests for livepatching modules Date: Wed, 15 Jan 2025 09:24:20 +0100 Message-ID: <20250115082431.5550-9-pmladek@suse.com> X-Mailer: git-send-email 2.47.1 In-Reply-To: <20250115082431.5550-1-pmladek@suse.com> References: <20250115082431.5550-1-pmladek@suse.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Spam-Score: -6.80 X-Spamd-Result: default: False [-6.80 / 50.00]; REPLY(-4.00)[]; BAYES_HAM(-3.00)[100.00%]; MID_CONTAINS_FROM(1.00)[]; NEURAL_HAM_LONG(-1.00)[-1.000]; R_MISSING_CHARSET(0.50)[]; NEURAL_HAM_SHORT(-0.20)[-1.000]; MIME_GOOD(-0.10)[text/plain]; RCVD_COUNT_ZERO(0.00)[0]; MIME_TRACE(0.00)[0:+]; ARC_NA(0.00)[]; TO_DN_SOME(0.00)[]; RCPT_COUNT_SEVEN(0.00)[7]; DKIM_SIGNED(0.00)[suse.com:s=susede1]; FUZZY_BLOCKED(0.00)[rspamd.com]; FROM_HAS_DN(0.00)[]; R_RATELIMIT(0.00)[to_ip_from(RLj3e56pwiuh8u4wxetmhsq5s5)]; FROM_EQ_ENVFROM(0.00)[]; TO_MATCH_ENVRCPT_ALL(0.00)[]; DBL_BLOCKED_OPENRESOLVER(0.00)[pathway.suse.cz:helo,suse.com:mid,suse.com:email] X-Spam-Flag: NO X-Spam-Level: Content-Type: text/plain; charset="utf-8" The per-object callbacks have been replaced by per-state callbacks and will be removed soon. There are self-tests that attempt to load a live patch with per-object callbacks and the live-patched module (object) in all possible order variations. These order variations are less relevant with per-state callbacks because they are only invoked when the live patch is enabled or disabled. However, it is still important to check all possible order variations to ensure the module is live-patched correctly. The expected state is validated using two approaches. First, the "patched" sysfs attribute is checked the same way is in the sysfs tests. Second, a new "welcome" parameter has been added to the speaker test module. By reading this parameter's state, the live-patched function can be invoked. Instead of displaying the parameter value via the sysfs interface, the .read() callback writes a welcome message to the kernel log. This message is compared with the expected value at the end of the test. This approach ensures that the self-test attempts to unload the test modules even if something goes wrong. Signed-off-by: Petr Mladek --- tools/testing/selftests/livepatch/Makefile | 1 + .../testing/selftests/livepatch/functions.sh | 29 +++ .../selftests/livepatch/test-callbacks.sh | 226 ------------------ .../testing/selftests/livepatch/test-order.sh | 226 ++++++++++++++++++ .../livepatch/test_modules/test_klp_speaker.c | 19 +- 5 files changed, 274 insertions(+), 227 deletions(-) create mode 100755 tools/testing/selftests/livepatch/test-order.sh diff --git a/tools/testing/selftests/livepatch/Makefile b/tools/testing/sel= ftests/livepatch/Makefile index a080eb54a215..971d0c6f8059 100644 --- a/tools/testing/selftests/livepatch/Makefile +++ b/tools/testing/selftests/livepatch/Makefile @@ -5,6 +5,7 @@ TEST_GEN_MODS_DIR :=3D test_modules TEST_PROGS_EXTENDED :=3D functions.sh TEST_PROGS :=3D \ test-livepatch.sh \ + test-order.sh \ test-callbacks.sh \ test-shadow-vars.sh \ test-state.sh \ diff --git a/tools/testing/selftests/livepatch/functions.sh b/tools/testing= /selftests/livepatch/functions.sh index 3f86b89e6ea7..bc1e100e47a7 100644 --- a/tools/testing/selftests/livepatch/functions.sh +++ b/tools/testing/selftests/livepatch/functions.sh @@ -279,6 +279,23 @@ 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 $SYSFS_MODULE_DIR/$mod/parameters/$param" + val=3D$(cat $SYSFS_MODULE_DIR/$mod/parameters/$param 2>&1) + # Log only non-empty values. Some test modules write a message + # to the log on its own when reading the parameter, for example, + # the "welcome" parameter of the "test_klp_speaker" module. + if [[ "$val" !=3D "" ]]; then + log "$mod:$param: $ret" + fi +} + function start_test { local test=3D"$1" =20 @@ -353,3 +370,15 @@ function check_sysfs_value() { 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 index a65fd860662e..614ed0aa2e40 100755 --- a/tools/testing/selftests/livepatch/test-callbacks.sh +++ b/tools/testing/selftests/livepatch/test-callbacks.sh @@ -11,232 +11,6 @@ MOD_TARGET_BUSY=3Dtest_klp_callbacks_busy =20 setup_config =20 - -# 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 "% insmod test_modules/$MOD_TARGET.ko -$MOD_TARGET: ${MOD_TARGET}_init -% insmod test_modules/$MOD_LIVEPATCH.ko -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 > $SYSFS_KLP_DIR/$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 "% insmod test_modules/$MOD_LIVEPATCH.ko -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 -% insmod test_modules/$MOD_TARGET.ko -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 > $SYSFS_KLP_DIR/$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 "% insmod test_modules/$MOD_TARGET.ko -$MOD_TARGET: ${MOD_TARGET}_init -% insmod test_modules/$MOD_LIVEPATCH.ko -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 > $SYSFS_KLP_DIR/$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 "% insmod test_modules/$MOD_LIVEPATCH.ko -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 -% insmod test_modules/$MOD_TARGET.ko -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 > $SYSFS_KLP_DIR/$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 "% insmod test_modules/$MOD_LIVEPATCH.ko -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 > $SYSFS_KLP_DIR/$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). # diff --git a/tools/testing/selftests/livepatch/test-order.sh b/tools/testin= g/selftests/livepatch/test-order.sh new file mode 100755 index 000000000000..869b06605597 --- /dev/null +++ b/tools/testing/selftests/livepatch/test-order.sh @@ -0,0 +1,226 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2018 Joe Lawrence +# Copyright (C) 2024 SUSE + +. $(dirname $0)/functions.sh + +MOD_LIVEPATCH=3Dtest_klp_speaker_livepatch +MOD_TARGET=3Dtest_klp_speaker + +setup_config + +# Test basic livepatch enable/disable functionality when livepatching +# modules. +# +# Loading the livepatch module without the target module being loaded. +# +# The transition should succeed. It is basically just a reference for +# for the following tests. + +start_test "module not present" + +load_lp $MOD_LIVEPATCH +check_object_patched $MOD_LIVEPATCH $MOD_TARGET "0" +disable_lp $MOD_LIVEPATCH +unload_lp $MOD_LIVEPATCH + +check_result "% insmod test_modules/$MOD_LIVEPATCH.ko +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 > $SYSFS_KLP_DIR/$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" + +# Load the target module before the livepatch module. Unload them +# in the reverse order. +# +# The expected state is double-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 +check_object_patched $MOD_LIVEPATCH $MOD_TARGET "1" + +disable_lp $MOD_LIVEPATCH +unload_lp $MOD_LIVEPATCH +read_module_param $MOD_TARGET welcome + +unload_mod $MOD_TARGET + +check_result "% insmod test_modules/$MOD_TARGET.ko +$MOD_TARGET: ${MOD_TARGET}_init +% cat $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome +$MOD_TARGET: speaker_welcome: Hello, World! +% insmod test_modules/$MOD_LIVEPATCH.ko +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 $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome +$MOD_LIVEPATCH: lp_speaker_welcome: Ladies and gentleman, ... +% echo 0 > $SYSFS_KLP_DIR/$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 $SYSFS_MODULE_DIR/$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 double-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 +check_object_patched $MOD_LIVEPATCH $MOD_TARGET "0" + +load_mod $MOD_TARGET +read_module_param $MOD_TARGET welcome +check_object_patched $MOD_LIVEPATCH $MOD_TARGET "1" + +disable_lp $MOD_LIVEPATCH +read_module_param $MOD_TARGET welcome + +unload_lp $MOD_LIVEPATCH +unload_mod $MOD_TARGET + +check_result "% insmod test_modules/$MOD_LIVEPATCH.ko +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 +% insmod test_modules/$MOD_TARGET.ko +livepatch: applying patch '$MOD_LIVEPATCH' to loading module '$MOD_TARGET' +$MOD_TARGET: ${MOD_TARGET}_init +% cat $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome +$MOD_LIVEPATCH: lp_speaker_welcome: Ladies and gentleman, ... +% echo 0 > $SYSFS_KLP_DIR/$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 $SYSFS_MODULE_DIR/$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 double-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 "% insmod test_modules/$MOD_TARGET.ko +$MOD_TARGET: ${MOD_TARGET}_init +% cat $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome +$MOD_TARGET: speaker_welcome: Hello, World! +% insmod test_modules/$MOD_LIVEPATCH.ko +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 $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome +$MOD_LIVEPATCH: lp_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 > $SYSFS_KLP_DIR/$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 double-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 +check_object_patched $MOD_LIVEPATCH $MOD_TARGET "0" + +load_mod $MOD_TARGET +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 "% insmod test_modules/$MOD_LIVEPATCH.ko +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 +% insmod test_modules/$MOD_TARGET.ko +livepatch: applying patch '$MOD_LIVEPATCH' to loading module '$MOD_TARGET' +$MOD_TARGET: ${MOD_TARGET}_init +% cat $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome +$MOD_LIVEPATCH: lp_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 > $SYSFS_KLP_DIR/$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" diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_speake= r.c b/tools/testing/selftests/livepatch/test_modules/test_klp_speaker.c index b1fb135820b0..22f6e5fcb009 100644 --- a/tools/testing/selftests/livepatch/test_modules/test_klp_speaker.c +++ b/tools/testing/selftests/livepatch/test_modules/test_klp_speaker.c @@ -12,14 +12,31 @@ * The module provides a virtual speaker who can do: * * - Start a show with a greeting, see speaker_welcome(). + * + * - Log the greeting by reading the "welcome" module parameter, see + * welcome_get(). */ =20 noinline -static void __always_used speaker_welcome(void) +static void speaker_welcome(void) { pr_info("%s: Hello, World!\n", __func__); } =20 +static int welcome_get(char *buffer, const struct kernel_param *kp) +{ + 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."); + static int test_klp_speaker_init(void) { pr_info("%s\n", __func__); --=20 2.47.1 From nobody Mon Dec 15 21:31:33 2025 Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.223.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5C8B21E7C39; Wed, 15 Jan 2025 08:26:18 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.135.223.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929580; cv=none; b=GbG4siS2pWQxeJCE9a4+gEAByCP5XE993wuPHK18mPdtPX/QjtVAySeP2vsrs1IWKPwwrrF7rAa9eHhesTeDxAbYETMt+dr4anfb7FKqLtEp8aGbD7/ArTABjt23O0+gxj5bZw7PLnHxXxHXIkUYkmbPYB0UBm8HFabZeRA8Z/w= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929580; c=relaxed/simple; bh=+/aYqB9X4ddkL4ahwzObcMp1VvHynzmc+CCb68tKSo4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=hJmuvUs1Pp3279vDrqqYedu5z0Qk/dW0yS1gmYweCM3mfA/txaAPT25eE1sBU6bJRmTsvDMxIezTrYRdyZ1/lKE+LNIx6cbPm+xEhv+Kp9qfl4x0MMGzl8vw32BDQHWT7ALz9XW+WvY0n/qN8lX6RU31EwPlRXB/zvFDLY5vGBc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com; spf=pass smtp.mailfrom=suse.com; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=Y8d5/w2p; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=Y8d5/w2p; arc=none smtp.client-ip=195.135.223.131 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=suse.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="Y8d5/w2p"; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="Y8d5/w2p" Received: from pathway.suse.cz (unknown [10.100.201.202]) by smtp-out2.suse.de (Postfix) with ESMTP id 8ABE31F37C; Wed, 15 Jan 2025 08:26:16 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929576; 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=j+QghZn9w2cXkVEJZDAG7GtuKZuu/WCV7cLR1e9WOpw=; b=Y8d5/w2ppdqQapggmNC2IljxnmJ2FgjmXkXyQC3hxWGbRNPj1xamTCv+nl1Jsrhm3kp2j5 6CuZA/h2000rMgC003lObtc7UbNn1/N6DJfBbn5+qLaPhpQNmHl4Za6AUsgEXPfOb1uKlo 08h+/2OwCvLRzcPbIDYup42iHNC0ttY= Authentication-Results: smtp-out2.suse.de; none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929576; 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=j+QghZn9w2cXkVEJZDAG7GtuKZuu/WCV7cLR1e9WOpw=; b=Y8d5/w2ppdqQapggmNC2IljxnmJ2FgjmXkXyQC3hxWGbRNPj1xamTCv+nl1Jsrhm3kp2j5 6CuZA/h2000rMgC003lObtc7UbNn1/N6DJfBbn5+qLaPhpQNmHl4Za6AUsgEXPfOb1uKlo 08h+/2OwCvLRzcPbIDYup42iHNC0ttY= 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: [PATCH v1 09/19] selftests/livepatch: Convert testing of multiple target modules Date: Wed, 15 Jan 2025 09:24:21 +0100 Message-ID: <20250115082431.5550-10-pmladek@suse.com> X-Mailer: git-send-email 2.47.1 In-Reply-To: <20250115082431.5550-1-pmladek@suse.com> References: <20250115082431.5550-1-pmladek@suse.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Spam-Score: -6.80 X-Spamd-Result: default: False [-6.80 / 50.00]; REPLY(-4.00)[]; BAYES_HAM(-3.00)[100.00%]; MID_CONTAINS_FROM(1.00)[]; NEURAL_HAM_LONG(-1.00)[-1.000]; R_MISSING_CHARSET(0.50)[]; NEURAL_HAM_SHORT(-0.20)[-1.000]; MIME_GOOD(-0.10)[text/plain]; RCVD_COUNT_ZERO(0.00)[0]; MIME_TRACE(0.00)[0:+]; ARC_NA(0.00)[]; TO_DN_SOME(0.00)[]; RCPT_COUNT_SEVEN(0.00)[7]; DKIM_SIGNED(0.00)[suse.com:s=susede1]; FUZZY_BLOCKED(0.00)[rspamd.com]; FROM_HAS_DN(0.00)[]; R_RATELIMIT(0.00)[to_ip_from(RLj3e56pwiuh8u4wxetmhsq5s5)]; FROM_EQ_ENVFROM(0.00)[]; TO_MATCH_ENVRCPT_ALL(0.00)[]; DBL_BLOCKED_OPENRESOLVER(0.00)[suse.com:mid,suse.com:email,pathway.suse.cz:helo] X-Spam-Flag: NO X-Spam-Level: Content-Type: text/plain; charset="utf-8" The per-object callbacks have been replaced by per-state callbacks and will be removed soon. A selftest previously loaded two livepatched modules to ensure that the per-object callbacks were called for both. Although per-state callbacks are only invoked when the live patch is enabled or disabled, it is still important to verify that multiple objects can be live-patched. Convert the test into a generic one by reusing the speaker test module to create the second target module. In the second variant, speaker_welcome() will only print "(2)" after the function name to distinguish it, while keeping the real function name the same to maintain simplicity. Signed-off-by: Petr Mladek --- .../selftests/livepatch/test-callbacks.sh | 59 ---------------- .../testing/selftests/livepatch/test-order.sh | 69 +++++++++++++++++++ .../selftests/livepatch/test_modules/Makefile | 1 + .../livepatch/test_modules/test_klp_speaker.c | 8 ++- .../test_modules/test_klp_speaker2.c | 8 +++ .../test_modules/test_klp_speaker_livepatch.c | 26 ++++++- 6 files changed, 110 insertions(+), 61 deletions(-) create mode 100644 tools/testing/selftests/livepatch/test_modules/test_klp= _speaker2.c diff --git a/tools/testing/selftests/livepatch/test-callbacks.sh b/tools/te= sting/selftests/livepatch/test-callbacks.sh index 614ed0aa2e40..a9bb90920c0a 100755 --- a/tools/testing/selftests/livepatch/test-callbacks.sh +++ b/tools/testing/selftests/livepatch/test-callbacks.sh @@ -93,65 +93,6 @@ $MOD_LIVEPATCH: post_unpatch_callback: vmlinux livepatch: '$MOD_LIVEPATCH': unpatching complete % rmmod $MOD_LIVEPATCH" =20 - -# 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 "% insmod test_modules/$MOD_TARGET_BUSY.ko block_transition= =3DN -$MOD_TARGET_BUSY: ${MOD_TARGET_BUSY}_init -$MOD_TARGET_BUSY: busymod_work_func enter -$MOD_TARGET_BUSY: busymod_work_func exit -% insmod test_modules/$MOD_LIVEPATCH.ko -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 -% insmod test_modules/$MOD_TARGET.ko -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 > $SYSFS_KLP_DIR/$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. # diff --git a/tools/testing/selftests/livepatch/test-order.sh b/tools/testin= g/selftests/livepatch/test-order.sh index 869b06605597..189132b01bac 100755 --- a/tools/testing/selftests/livepatch/test-order.sh +++ b/tools/testing/selftests/livepatch/test-order.sh @@ -7,6 +7,7 @@ =20 MOD_LIVEPATCH=3Dtest_klp_speaker_livepatch MOD_TARGET=3Dtest_klp_speaker +MOD_TARGET2=3Dtest_klp_speaker2 =20 setup_config =20 @@ -224,3 +225,71 @@ livepatch: '$MOD_LIVEPATCH': starting unpatching trans= ition livepatch: '$MOD_LIVEPATCH': completing unpatching transition livepatch: '$MOD_LIVEPATCH': unpatching complete % rmmod $MOD_LIVEPATCH" + +# Test loading multiple targeted kernel modules. +# +# Load the first target module before the livepatch and the second one lat= er. +# Disable and unload them in the opposite order. +# +# The module loader hooks should print a message about applying/reverting +# the livepatch for the 2nd module when it is being loaded/unloaded. +# +# The expected state is double-checked by reading "welcome" parameter +# of both target modules. The livepatched variant should be printed +# when both the target and livepatch modules are loaded. + +start_test "multiple target modules" + +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" +check_object_patched $MOD_LIVEPATCH $MOD_TARGET2 "0" + +load_mod $MOD_TARGET2 +read_module_param $MOD_TARGET2 welcome +check_object_patched $MOD_LIVEPATCH $MOD_TARGET "1" +check_object_patched $MOD_LIVEPATCH $MOD_TARGET2 "1" + +unload_mod $MOD_TARGET2 +check_object_patched $MOD_LIVEPATCH $MOD_TARGET "1" +check_object_patched $MOD_LIVEPATCH $MOD_TARGET2 "0" + +disable_lp $MOD_LIVEPATCH +read_module_param $MOD_TARGET welcome + +unload_lp $MOD_LIVEPATCH +unload_mod $MOD_TARGET + +check_result "% insmod test_modules/$MOD_TARGET.ko +$MOD_TARGET: ${MOD_TARGET}_init +% cat $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome +$MOD_TARGET: speaker_welcome: Hello, World! +% insmod test_modules/$MOD_LIVEPATCH.ko +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 $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome +$MOD_LIVEPATCH: lp_speaker_welcome: Ladies and gentleman, ... +% insmod test_modules/$MOD_TARGET2.ko +livepatch: applying patch '$MOD_LIVEPATCH' to loading module '$MOD_TARGET2' +$MOD_TARGET2: ${MOD_TARGET}_init +% cat $SYSFS_MODULE_DIR/$MOD_TARGET2/parameters/welcome +$MOD_LIVEPATCH: lp_speaker2_welcome(2): Ladies and gentleman, ... +% rmmod $MOD_TARGET2 +$MOD_TARGET2: ${MOD_TARGET}_exit +livepatch: reverting patch '$MOD_LIVEPATCH' on unloading module '$MOD_TARG= ET2' +% echo 0 > $SYSFS_KLP_DIR/$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 $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome +$MOD_TARGET: speaker_welcome: Hello, World! +% rmmod $MOD_LIVEPATCH +% rmmod $MOD_TARGET +$MOD_TARGET: ${MOD_TARGET}_exit" diff --git a/tools/testing/selftests/livepatch/test_modules/Makefile b/tool= s/testing/selftests/livepatch/test_modules/Makefile index 0978c489a67a..72a817d1ddd9 100644 --- a/tools/testing/selftests/livepatch/test_modules/Makefile +++ b/tools/testing/selftests/livepatch/test_modules/Makefile @@ -10,6 +10,7 @@ obj-m +=3D test_klp_atomic_replace.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_state.o \ test_klp_state2.o \ diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_speake= r.c b/tools/testing/selftests/livepatch/test_modules/test_klp_speaker.c index 22f6e5fcb009..92c577addb8e 100644 --- a/tools/testing/selftests/livepatch/test_modules/test_klp_speaker.c +++ b/tools/testing/selftests/livepatch/test_modules/test_klp_speaker.c @@ -6,6 +6,10 @@ #include #include =20 +#ifndef SPEAKER_ID +#define SPEAKER_ID "" +#endif + /** * test_klp_speaker - test module for testing misc livepatching features * @@ -15,12 +19,14 @@ * * - Log the greeting by reading the "welcome" module parameter, see * welcome_get(). + * + * - Reuse the module source for more speakers, see SPEAKER_ID. */ =20 noinline static void speaker_welcome(void) { - pr_info("%s: Hello, World!\n", __func__); + pr_info("%s%s: Hello, World!\n", __func__, SPEAKER_ID); } =20 static int welcome_get(char *buffer, const struct kernel_param *kp) diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_speake= r2.c b/tools/testing/selftests/livepatch/test_modules/test_klp_speaker2.c new file mode 100644 index 000000000000..d38ab51414bf --- /dev/null +++ b/tools/testing/selftests/livepatch/test_modules/test_klp_speaker2.c @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2023 SUSE + +/* Use versioned function name for livepatched functions */ +#define SPEAKER_ID "(2)" + +/* Same module with the same features. */ +#include "test_klp_speaker.c" diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_speake= r_livepatch.c b/tools/testing/selftests/livepatch/test_modules/test_klp_spe= aker_livepatch.c index 26a8dd15f723..8d7e74a69a5d 100644 --- a/tools/testing/selftests/livepatch/test_modules/test_klp_speaker_livep= atch.c +++ b/tools/testing/selftests/livepatch/test_modules/test_klp_speaker_livep= atch.c @@ -17,11 +17,23 @@ * * - Improve the speaker's greeting from "Hello, World!" to * "Ladies and gentleman, ..." + * + * - Support more speaker modules, see __lp_speaker_welcome(). */ =20 +static void __lp_speaker_welcome(const char *caller_func, const char *spea= ker_id) +{ + pr_info("%s%s: Ladies and gentleman, ...\n", caller_func, speaker_id); +} + static void lp_speaker_welcome(void) { - pr_info("%s: Ladies and gentleman, ...\n", __func__); + __lp_speaker_welcome(__func__, ""); +} + +static void lp_speaker2_welcome(void) +{ + __lp_speaker_welcome(__func__, "(2)"); } =20 static struct klp_func test_klp_speaker_funcs[] =3D { @@ -32,11 +44,23 @@ static struct klp_func test_klp_speaker_funcs[] =3D { { } }; =20 +static struct klp_func test_klp_speaker2_funcs[] =3D { + { + .old_name =3D "speaker_welcome", + .new_func =3D lp_speaker2_welcome, + }, + { } +}; + 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 --=20 2.47.1 From nobody Mon Dec 15 21:31:33 2025 Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.223.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 93FEF28EC73; Wed, 15 Jan 2025 08:26:28 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.135.223.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929590; cv=none; b=VTYZiaWueHihioMxE7fw7/Df0SGirfVvw7HiKjLMR9GMxX8154FcPhHxc3Z57PTUWDCa444pi6g7Y9h40AmOkoUpztGNjRe0zVN5wJ4+pmWGwTWdNAg6tJeVLYShrbVcVsJDgmYAkYBbNySuUvcD9zIQyJFZVJnDMs2i7KE0FU8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929590; c=relaxed/simple; bh=7b28B89R36COwwi44/BDi79HbmJz3oqSYlM02v32eVU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=aEsJbyw4ElL6pJigASb+hYxbZ8rqmlLGfXXoep705ypMtuaqLxUJU7jL9sJG7YiuTw4drO2MyxS3O4sFQ+a5kKHMpteuC6NGs32OCUyCYuvgW8we0shUleMh3Cc2dlLHnr5pOhnL7Lw1uMAp7LaXomXWzB9G8dUx1aZgoizdfxo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com; spf=pass smtp.mailfrom=suse.com; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=p9mRXbJn; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=QV3q+il8; arc=none smtp.client-ip=195.135.223.131 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=suse.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="p9mRXbJn"; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="QV3q+il8" Received: from pathway.suse.cz (unknown [10.100.201.202]) by smtp-out2.suse.de (Postfix) with ESMTP id D1D1C1F44E; Wed, 15 Jan 2025 08:26:26 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929587; 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=GjAwr2Ln79XWN9PYgoGLfYjOgXDJ+K4XlFe1vR6AFts=; b=p9mRXbJnc5sfftg/7IEDkal54/P3BMcc2r4TclKo2b+aph/y8O4fk+Yn7FInhcc4zdK3mm 8s/3tlig+1ijOBOrfeLk3ZIia7oIhEGqlcncIKWs9u27rQ3YqliOzZ+o9NmoagEmAgrGDo jXeyCeogvWGU9sHchjvpqYXmRCyIRrk= Authentication-Results: smtp-out2.suse.de; none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929586; 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=GjAwr2Ln79XWN9PYgoGLfYjOgXDJ+K4XlFe1vR6AFts=; b=QV3q+il8QYWxuXUKwQWiKcbgLvXRwF2pl9meD7wO135JIn2mUHP7n0rV0ZLlU/q5WgYVyT fg+ITcjH4zSdwZQMLt+o+ydxNT3y1D/ykyk4s7yBi/G9eo2HlMKbcYC7PYjjJGLlcGyJ7U 0GP/Gv2m6xb4RmVrFPH8SwgsLK/+Hng= 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: [PATCH v1 10/19] selftests/livepatch: Create a simple selftest for state callbacks Date: Wed, 15 Jan 2025 09:24:22 +0100 Message-ID: <20250115082431.5550-11-pmladek@suse.com> X-Mailer: git-send-email 2.47.1 In-Reply-To: <20250115082431.5550-1-pmladek@suse.com> References: <20250115082431.5550-1-pmladek@suse.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Spam-Score: -6.80 X-Spamd-Result: default: False [-6.80 / 50.00]; REPLY(-4.00)[]; BAYES_HAM(-3.00)[100.00%]; MID_CONTAINS_FROM(1.00)[]; NEURAL_HAM_LONG(-1.00)[-1.000]; R_MISSING_CHARSET(0.50)[]; NEURAL_HAM_SHORT(-0.20)[-1.000]; MIME_GOOD(-0.10)[text/plain]; RCVD_COUNT_ZERO(0.00)[0]; MIME_TRACE(0.00)[0:+]; ARC_NA(0.00)[]; TO_DN_SOME(0.00)[]; RCPT_COUNT_SEVEN(0.00)[7]; DKIM_SIGNED(0.00)[suse.com:s=susede1]; FUZZY_BLOCKED(0.00)[rspamd.com]; FROM_HAS_DN(0.00)[]; R_RATELIMIT(0.00)[to_ip_from(RLj3e56pwiuh8u4wxetmhsq5s5)]; FROM_EQ_ENVFROM(0.00)[]; TO_MATCH_ENVRCPT_ALL(0.00)[]; DBL_BLOCKED_OPENRESOLVER(0.00)[pathway.suse.cz:helo,suse.com:mid,suse.com:email] X-Spam-Flag: NO X-Spam-Level: Content-Type: text/plain; charset="utf-8" The per-object callbacks have been deprecated in favor of per-state callbacks and will be removed soon. The behavior of these two types of callbacks is completely different. Per-object callbacks are triggered when the livepatching of the related object is enabled or disabled. When using the atomic replace feature, only the callbacks from the new livepatch are called. Per-state callbacks, on the other hand, are triggered when the state is being added or removed. When using the atomic replace feature, the callbacks that remove the state are called from the old livepatch that is being replaced. For testing purposes, the state represents a part of an audience in the livepatch for the speaker module. The audience applauds when the speaker delivers the welcome message. All four callbacks are used as follows: + pre_patch() allocates a shadow variable with a string and fills it with "[]". + post_patch() fills the string with "[APPLAUSE]". + pre_unpatch() reverts the string back to "[]". + post_unpatch() releases the shadow variable. The welcome message printed by the livepatched function allows us to distinguish between the transition and the completed transition. Specifically, the speaker's welcome message appears as: + Not patched system: "Hello, World!" + Transition (unpatched task): "[] Hello, World!" + Transition (patched task): "[] Ladies and gentlemen, ..." + Patched system: "[APPLAUSE] Ladies and gentlemen, ..." Create an initial self-test that uses a single livepatch and a single state. This serves as a base for more complex self-tests. Signed-off-by: Petr Mladek --- tools/testing/selftests/livepatch/Makefile | 1 + .../livepatch/test-state-callbacks.sh | 58 +++++++ .../test_modules/test_klp_speaker_livepatch.c | 154 +++++++++++++++++- 3 files changed, 212 insertions(+), 1 deletion(-) create mode 100755 tools/testing/selftests/livepatch/test-state-callbacks.= sh diff --git a/tools/testing/selftests/livepatch/Makefile b/tools/testing/sel= ftests/livepatch/Makefile index 971d0c6f8059..ae00e98ba79f 100644 --- a/tools/testing/selftests/livepatch/Makefile +++ b/tools/testing/selftests/livepatch/Makefile @@ -9,6 +9,7 @@ TEST_PROGS :=3D \ test-callbacks.sh \ test-shadow-vars.sh \ test-state.sh \ + test-state-callbacks.sh \ test-ftrace.sh \ test-sysfs.sh \ test-syscall.sh \ diff --git a/tools/testing/selftests/livepatch/test-state-callbacks.sh b/to= ols/testing/selftests/livepatch/test-state-callbacks.sh new file mode 100755 index 000000000000..1183b9d15782 --- /dev/null +++ b/tools/testing/selftests/livepatch/test-state-callbacks.sh @@ -0,0 +1,58 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2018 Joe Lawrence +# Copyright (C) 2024 SUSE + +. $(dirname $0)/functions.sh + +MOD_LIVEPATCH=3Dtest_klp_speaker_livepatch +MOD_TARGET=3Dtest_klp_speaker + +setup_config + +# 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 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 "% insmod test_modules/$MOD_TARGET.ko +$MOD_TARGET: ${MOD_TARGET}_init +% cat $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome +$MOD_TARGET: speaker_welcome: Hello, World! +% insmod test_modules/$MOD_LIVEPATCH.ko applause=3D1 +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +$MOD_LIVEPATCH: applause_pre_patch_callback: state 10 +livepatch: '$MOD_LIVEPATCH': starting patching transition +livepatch: '$MOD_LIVEPATCH': completing patching transition +$MOD_LIVEPATCH: applause_post_patch_callback: state 10 +livepatch: '$MOD_LIVEPATCH': patching complete +% cat $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome +$MOD_LIVEPATCH: lp_speaker_welcome: [APPLAUSE] Ladies and gentleman, ... +% echo 0 > $SYSFS_KLP_DIR/$MOD_LIVEPATCH/enabled +livepatch: '$MOD_LIVEPATCH': initializing unpatching transition +$MOD_LIVEPATCH: applause_pre_unpatch_callback: state 10 +livepatch: '$MOD_LIVEPATCH': starting unpatching transition +livepatch: '$MOD_LIVEPATCH': completing unpatching transition +$MOD_LIVEPATCH: applause_post_unpatch_callback: state 10 (nope) +$MOD_LIVEPATCH: applause_shadow_dtor: freeing applause [] (nope) +livepatch: '$MOD_LIVEPATCH': unpatching complete +% rmmod $MOD_LIVEPATCH +% cat $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome +$MOD_TARGET: speaker_welcome: Hello, World! +% rmmod $MOD_TARGET +$MOD_TARGET: ${MOD_TARGET}_exit" + +exit 0 diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_speake= r_livepatch.c b/tools/testing/selftests/livepatch/test_modules/test_klp_spe= aker_livepatch.c index 8d7e74a69a5d..ab409df0b0e3 100644 --- a/tools/testing/selftests/livepatch/test_modules/test_klp_speaker_livep= atch.c +++ b/tools/testing/selftests/livepatch/test_modules/test_klp_speaker_livep= atch.c @@ -21,9 +21,27 @@ * - Support more speaker modules, see __lp_speaker_welcome(). */ =20 +#define APPLAUSE_ID 10 +#define APPLAUSE_STR_SIZE 16 + +/* associate the shadow variable with NULL address */; +static void *shadow_object =3D NULL; + +static bool add_applause; +module_param_named(applause, add_applause, bool, 0400); +MODULE_PARM_DESC(applause, "Use shadow variable to add applause (default= =3Dfalse)"); + static void __lp_speaker_welcome(const char *caller_func, const char *spea= ker_id) { - pr_info("%s%s: Ladies and gentleman, ...\n", caller_func, speaker_id); + char entire_applause[APPLAUSE_STR_SIZE + 1] =3D ""; + const char *applause; + + applause =3D (char *)klp_shadow_get(shadow_object, APPLAUSE_ID); + if (applause) + snprintf(entire_applause, sizeof(entire_applause), "%s ", applause); + + pr_info("%s%s: %sLadies and gentleman, ...\n", caller_func, speaker_id, + entire_applause); } =20 static void lp_speaker_welcome(void) @@ -36,6 +54,122 @@ static void lp_speaker2_welcome(void) __lp_speaker_welcome(__func__, "(2)"); } =20 +static int allocate_applause(unsigned long id) +{ + 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, id, + APPLAUSE_STR_SIZE, GFP_KERNEL, + NULL, NULL); + + if (!applause) { + pr_err("%s: failed to allocated shadow variable for storing an applause = description\n", + __func__); + return -ENOMEM; + } + + strscpy(applause, "[]", APPLAUSE_STR_SIZE); + + return 0; +} + +static void set_applause(unsigned long id) +{ + char *applause; + + applause =3D (char *)klp_shadow_get(shadow_object, id); + if (!applause) { + pr_err("%s: failed to get shadow variable with the applause description:= %lu\n", + __func__, id); + return; + } + + strscpy(applause, "[APPLAUSE]", APPLAUSE_STR_SIZE); +} + +static void unset_applause(unsigned long id) +{ + char *applause; + + applause =3D (char *)klp_shadow_get(shadow_object, id); + if (!applause) { + pr_err("%s: failed to get shadow variable with the applause description:= %lu\n", + __func__, id); + return; + } + + strscpy(applause, "[]", APPLAUSE_STR_SIZE); +} + +static void check_applause(unsigned long id) +{ + char *applause; + + applause =3D (char *)klp_shadow_get(shadow_object, id); + if (!applause) { + pr_err("%s: failed to get shadow variable with the applause description:= %lu\n", + __func__, id); + return; + } +} + +/* Executed before patching when the state is being enabled. */ +static int applause_pre_patch_callback(struct klp_patch *patch, struct klp= _state *state) +{ + pr_info("%s: state %lu\n", __func__, state->id); + return allocate_applause(state->id); +} + +/* Executed after patching when the state being enabled. */ +static void applause_post_patch_callback(struct klp_patch *patch, struct k= lp_state *state) +{ + pr_info("%s: state %lu\n", __func__, state->id); + set_applause(state->id); +} + +/* Executed before unpatching when the state is being disabled. */ +static void applause_pre_unpatch_callback(struct klp_patch *patch, struct = klp_state *state) +{ + pr_info("%s: state %lu\n", __func__, state->id); + unset_applause(state->id); +} + +/* Executed after unpatching when the state is being disabled. */ +static void applause_post_unpatch_callback(struct klp_patch *patch, struct= klp_state *state) +{ + /* + * Just check that the shadow variable still exist. It will be + * freed automatically because state->is_shadow is set. + */ + pr_info("%s: state %lu (nope)\n", __func__, state->id); + check_applause(state->id); +} + +/* + * The shadow_dtor callback is not really needed. The space for the string + * has been allocated as part of struct klp_shadow. The callback is added + * just to check that the shadow variable is freed automatically because of + * state->is_shadow is set. + */ +static void applause_shadow_dtor(void *obj, void *shadow_data) +{ + char *applause =3D (char *)shadow_data; + + /* + * It would be better to print the related state->id. And it would be + * easy to get the pointer to struct klp_shadow via the @shadow_data + * pointer. But struct klp_state is not defined in a public header. + */ + pr_info("%s: freeing applause %s (nope)\n", + __func__, applause); +} + static struct klp_func test_klp_speaker_funcs[] =3D { { .old_name =3D "speaker_welcome", @@ -64,6 +198,21 @@ static struct klp_object objs[] =3D { { } }; =20 +static struct klp_state states[] =3D { + { + .id =3D APPLAUSE_ID, + .is_shadow =3D true, + .callbacks =3D { + .pre_patch =3D applause_pre_patch_callback, + .post_patch =3D applause_post_patch_callback, + .pre_unpatch =3D applause_pre_unpatch_callback, + .post_unpatch =3D applause_post_unpatch_callback, + .shadow_dtor =3D applause_shadow_dtor, + }, + }, + {} +}; + static struct klp_patch patch =3D { .mod =3D THIS_MODULE, .objs =3D objs, @@ -71,6 +220,9 @@ static struct klp_patch patch =3D { =20 static int test_klp_speaker_livepatch_init(void) { + if (add_applause) + patch.states =3D states; + return klp_enable_patch(&patch); } =20 --=20 2.47.1 From nobody Mon Dec 15 21:31:33 2025 Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.223.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id F14BA2361C9; Wed, 15 Jan 2025 08:26:38 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.135.223.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929600; cv=none; b=n0XKA2ru63OyCM8kj0hyhPr8MPorLKLt7uPvUP6ppLQNm508Agh88/6VjO9d1PFwyFl3E6eH8JTlO4h2bd7arJ2C1HwYVVlqjsDx53mxL0fCGaSQ7GLkGO2gDIgOjTgIag44NgvN1zNHNgBNywbh5aenKL7uBfdQUEBCR3mcLDc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929600; c=relaxed/simple; bh=8EztCbiXjD5rt90xhxh7pNclvHzSNkSJNMC7FiRFOa0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=QzIEWA2bLypiO18cf4pbZofRgf0uMAnHVMiQkkdAmEjL1LrhNFB8H4CUx2m7vih4429F8RRCmLZi7pfUx6KMhTLSoz+IsDfIFPJHZ8G7ahLtB9hax8dD4sxRS2sRQdxrF7xMWSc7Q7pUY3Hj94OCfMCX7Q6zn5Pqfie5z8bnunA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com; spf=pass smtp.mailfrom=suse.com; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=nXYCZBPH; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=nXYCZBPH; arc=none smtp.client-ip=195.135.223.131 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=suse.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="nXYCZBPH"; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="nXYCZBPH" Received: from pathway.suse.cz (unknown [10.100.201.202]) by smtp-out2.suse.de (Postfix) with ESMTP id 44D9B1F37C; Wed, 15 Jan 2025 08:26:37 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929597; 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=flHQHyHm/pIfyGnps+uCicYHUheiiEiLwAMJXGi+ajM=; b=nXYCZBPHjQonabD42MUoq5Q+CqLBqo3so+W0Vc8HJmucAbTLVSB+5vSANPjsDnFndH6M7v p7AcbM7b6d/XW5pNv5jdr7XnKN0LgDvUiLoHlfIY1XNuZrAVJc+uwtNoacHcDcClxFnpcs q2H3trv3EPfrsnuueuap4Zf9EiffptU= Authentication-Results: smtp-out2.suse.de; none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929597; 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=flHQHyHm/pIfyGnps+uCicYHUheiiEiLwAMJXGi+ajM=; b=nXYCZBPHjQonabD42MUoq5Q+CqLBqo3so+W0Vc8HJmucAbTLVSB+5vSANPjsDnFndH6M7v p7AcbM7b6d/XW5pNv5jdr7XnKN0LgDvUiLoHlfIY1XNuZrAVJc+uwtNoacHcDcClxFnpcs q2H3trv3EPfrsnuueuap4Zf9EiffptU= 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: [PATCH v1 11/19] selftests/livepatch: Convert selftests for failing pre_patch callback Date: Wed, 15 Jan 2025 09:24:23 +0100 Message-ID: <20250115082431.5550-12-pmladek@suse.com> X-Mailer: git-send-email 2.47.1 In-Reply-To: <20250115082431.5550-1-pmladek@suse.com> References: <20250115082431.5550-1-pmladek@suse.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Spam-Level: X-Spamd-Result: default: False [-6.80 / 50.00]; REPLY(-4.00)[]; BAYES_HAM(-3.00)[100.00%]; MID_CONTAINS_FROM(1.00)[]; NEURAL_HAM_LONG(-1.00)[-1.000]; R_MISSING_CHARSET(0.50)[]; NEURAL_HAM_SHORT(-0.20)[-1.000]; MIME_GOOD(-0.10)[text/plain]; RCVD_COUNT_ZERO(0.00)[0]; MIME_TRACE(0.00)[0:+]; ARC_NA(0.00)[]; TO_DN_SOME(0.00)[]; RCPT_COUNT_SEVEN(0.00)[7]; DKIM_SIGNED(0.00)[suse.com:s=susede1]; FUZZY_BLOCKED(0.00)[rspamd.com]; FROM_HAS_DN(0.00)[]; R_RATELIMIT(0.00)[to_ip_from(RLj3e56pwiuh8u4wxetmhsq5s5)]; FROM_EQ_ENVFROM(0.00)[]; TO_MATCH_ENVRCPT_ALL(0.00)[]; DBL_BLOCKED_OPENRESOLVER(0.00)[suse.com:email,suse.com:mid,pathway.suse.cz:helo] X-Spam-Score: -6.80 X-Spam-Flag: NO Content-Type: text/plain; charset="utf-8" The per-object callbacks have been deprecated in favor of per-state callbacks and will be removed soon. The behavior of these two types of callbacks is completely different. Per-object callbacks are triggered when the livepatching of the related object is enabled or disabled. Per-state callbacks, on the other hand, are triggered when the state is being added or removed. In other words, it does not matter whether the livepatched object is loaded before or after the livepatch. Therefore, it makes sense to preserve only one variant of the self-test for the failing pre_patch callback. Use the new speaker module for testing. Simply add a new parameter, "pre_patch_ret", similar to the one in the "test_klp_callbacks_demo" livepatch module. Signed-off-by: Petr Mladek --- .../selftests/livepatch/test-callbacks.sh | 82 ------------------- .../livepatch/test-state-callbacks.sh | 35 ++++++++ .../test_modules/test_klp_speaker_livepatch.c | 10 +++ 3 files changed, 45 insertions(+), 82 deletions(-) diff --git a/tools/testing/selftests/livepatch/test-callbacks.sh b/tools/te= sting/selftests/livepatch/test-callbacks.sh index a9bb90920c0a..1ecd8f08a613 100755 --- a/tools/testing/selftests/livepatch/test-callbacks.sh +++ b/tools/testing/selftests/livepatch/test-callbacks.sh @@ -11,88 +11,6 @@ MOD_TARGET_BUSY=3Dtest_klp_callbacks_busy =20 setup_config =20 -# 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 "% insmod test_modules/$MOD_TARGET.ko -$MOD_TARGET: ${MOD_TARGET}_init -% insmod test_modules/$MOD_LIVEPATCH.ko 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 -insmod: ERROR: could not insert module test_modules/$MOD_LIVEPATCH.ko: 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 "% insmod test_modules/$MOD_LIVEPATCH.ko -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 > $SYSFS_MODULE_DIR/$MOD_LIVEPATCH/parameters/pre_patch_ret -% insmod test_modules/$MOD_TARGET.ko -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' -insmod: ERROR: could not insert module test_modules/$MOD_TARGET.ko: No suc= h device -% echo 0 > $SYSFS_KLP_DIR/$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 similar test as the previous one, but force the "busy" kernel module # to block the livepatch transition. # diff --git a/tools/testing/selftests/livepatch/test-state-callbacks.sh b/to= ols/testing/selftests/livepatch/test-state-callbacks.sh index 1183b9d15782..28ef88a2dfc3 100755 --- a/tools/testing/selftests/livepatch/test-state-callbacks.sh +++ b/tools/testing/selftests/livepatch/test-state-callbacks.sh @@ -55,4 +55,39 @@ $MOD_TARGET: speaker_welcome: Hello, World! % rmmod $MOD_TARGET $MOD_TARGET: ${MOD_TARGET}_exit" =20 +# Test failure of the "pre_patch" state callback. +# +# The livepatch should not get loaded. The test module should +# should stay unpatched which is checked by reading the "welcome" +# parameter. + +start_test "failing pre_patch callback with -ENODEV" + +load_mod $MOD_TARGET +read_module_param $MOD_TARGET welcome + +load_failing_mod $MOD_LIVEPATCH applause=3D1 pre_patch_ret=3D-19 +read_module_param $MOD_TARGET welcome + +unload_mod $MOD_TARGET + +check_result "% insmod test_modules/$MOD_TARGET.ko +$MOD_TARGET: ${MOD_TARGET}_init +% cat $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome +$MOD_TARGET: speaker_welcome: Hello, World! +% insmod test_modules/$MOD_LIVEPATCH.ko applause=3D1 pre_patch_ret=3D-19 +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +$MOD_LIVEPATCH: applause_pre_patch_callback: state 10 +$MOD_LIVEPATCH: applause_pre_patch_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 +insmod: ERROR: could not insert module test_modules/$MOD_LIVEPATCH.ko: No = such device +% cat $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome +$MOD_TARGET: speaker_welcome: Hello, World! +% rmmod $MOD_TARGET +$MOD_TARGET: ${MOD_TARGET}_exit" + exit 0 diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_speake= r_livepatch.c b/tools/testing/selftests/livepatch/test_modules/test_klp_spe= aker_livepatch.c index ab409df0b0e3..c46c98a3c1e6 100644 --- a/tools/testing/selftests/livepatch/test_modules/test_klp_speaker_livep= atch.c +++ b/tools/testing/selftests/livepatch/test_modules/test_klp_speaker_livep= atch.c @@ -31,6 +31,10 @@ static bool add_applause; module_param_named(applause, add_applause, bool, 0400); MODULE_PARM_DESC(applause, "Use shadow variable to add applause (default= =3Dfalse)"); =20 +static int pre_patch_ret; +module_param(pre_patch_ret, int, 0400); +MODULE_PARM_DESC(pre_patch_ret, "Allow to force failure for the pre_patch = callback (default=3D0)"); + static void __lp_speaker_welcome(const char *caller_func, const char *spea= ker_id) { char entire_applause[APPLAUSE_STR_SIZE + 1] =3D ""; @@ -123,6 +127,12 @@ static void check_applause(unsigned long id) static int applause_pre_patch_callback(struct klp_patch *patch, struct klp= _state *state) { pr_info("%s: state %lu\n", __func__, state->id); + + if (pre_patch_ret) { + pr_err("%s: forcing err: %pe\n", __func__, ERR_PTR(pre_patch_ret)); + return pre_patch_ret; + } + return allocate_applause(state->id); } =20 --=20 2.47.1 From nobody Mon Dec 15 21:31:33 2025 Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.223.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 99A6B1E7C00; Wed, 15 Jan 2025 08:26:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.135.223.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929611; cv=none; b=oFKMeSQ/j0aSy39dk9MaBPQ4M9bhsezH6yiN8LikKO8b1Hd8W5uWRHfUbCYGo4yslbTpktjCyFxY47pkm8LyK2FiuWn5oOPSng6ES0WWlLFWM5P45p5Pud18xbVjmTK66M7SJwsmokeWxS+s/P2JCMktNwkqj5kIFd/HYKOzbc8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929611; c=relaxed/simple; bh=JtNRq7HUV9zN9pWi4dZyJa4fNvTW21iqVkOmg4Hs5BY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=qYkiisNGbbirzCCPZ3yIhW4Tg2AqCRH4nu3/U1dyb0dH5Ahz7SQQ6Vb0oaTbsBuBwIqiZQBYXsX3aB2en+GaZAzTUnkSBEU3fKULad7L77Oq9t4B3r111F7B+7wVcc6juUOjJZveEsjsLHs2urNupVh04jtF/U+YavxT/OD17YU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com; spf=pass smtp.mailfrom=suse.com; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=T00rhEBV; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=T00rhEBV; arc=none smtp.client-ip=195.135.223.131 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=suse.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="T00rhEBV"; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="T00rhEBV" Received: from pathway.suse.cz (unknown [10.100.201.202]) by smtp-out2.suse.de (Postfix) with ESMTP id A35ED1F37C; Wed, 15 Jan 2025 08:26:47 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929607; 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=W50AFJVu3Hn9GCC6l0ptb8I+fT5fKa3wfP2A9cKgsYw=; b=T00rhEBV5q5/B8LI7i2pfQZOmej14e4nbK8j7IAc03QvGUoeF+9dCtpDjyILxE+XUJFhmL B07WiIasZuo/t4Je4a0pE+bBcsNnLDFEBg7WdtOkxVBAjGCsEu7EEwk3zNiJ4SA5mI7gkw O3pXh2qbeHLMtT2SQaKt4Y/u7CG02F4= Authentication-Results: smtp-out2.suse.de; none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929607; 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=W50AFJVu3Hn9GCC6l0ptb8I+fT5fKa3wfP2A9cKgsYw=; b=T00rhEBV5q5/B8LI7i2pfQZOmej14e4nbK8j7IAc03QvGUoeF+9dCtpDjyILxE+XUJFhmL B07WiIasZuo/t4Je4a0pE+bBcsNnLDFEBg7WdtOkxVBAjGCsEu7EEwk3zNiJ4SA5mI7gkw O3pXh2qbeHLMtT2SQaKt4Y/u7CG02F4= 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: [PATCH v1 12/19] selftests/livepatch: Convert selftest with blocked transition Date: Wed, 15 Jan 2025 09:24:24 +0100 Message-ID: <20250115082431.5550-13-pmladek@suse.com> X-Mailer: git-send-email 2.47.1 In-Reply-To: <20250115082431.5550-1-pmladek@suse.com> References: <20250115082431.5550-1-pmladek@suse.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Spam-Level: X-Spamd-Result: default: False [-6.80 / 50.00]; REPLY(-4.00)[]; BAYES_HAM(-3.00)[100.00%]; MID_CONTAINS_FROM(1.00)[]; NEURAL_HAM_LONG(-1.00)[-1.000]; R_MISSING_CHARSET(0.50)[]; NEURAL_HAM_SHORT(-0.20)[-1.000]; MIME_GOOD(-0.10)[text/plain]; RCVD_COUNT_ZERO(0.00)[0]; MIME_TRACE(0.00)[0:+]; ARC_NA(0.00)[]; TO_DN_SOME(0.00)[]; RCPT_COUNT_SEVEN(0.00)[7]; DKIM_SIGNED(0.00)[suse.com:s=susede1]; FUZZY_BLOCKED(0.00)[rspamd.com]; FROM_HAS_DN(0.00)[]; R_RATELIMIT(0.00)[to_ip_from(RLj3e56pwiuh8u4wxetmhsq5s5)]; FROM_EQ_ENVFROM(0.00)[]; TO_MATCH_ENVRCPT_ALL(0.00)[]; DBL_BLOCKED_OPENRESOLVER(0.00)[pathway.suse.cz:helo,suse.com:email,suse.com:mid] X-Spam-Score: -6.80 X-Spam-Flag: NO Content-Type: text/plain; charset="utf-8" The per-object callbacks have been deprecated in favor of per-state callbacks and will be removed soon. For the conversion of the self-test of the callbacks with a blocked transition, use the new module with a virtual speaker. Implementation Details: + The implementation of the blocked function is inspired by the module "test_klp_callbacks_busy". Specifically, the blocked function is called in a workqueue, synchronization is done using a completion, and the replacement of the blocked function is not fully functional[*]. + The blocked function is queued and called only if the parameter "block_doors" is set. Important Note on Sibling Call Optimization: Sibling call optimization must be disabled for functions designed to block transitions. Otherwise, they won't appear on the stack, leading to test failure. These functions can be livepatched because they are called with the call instruction. But when an optimized version just jumps to a nested then the jump instruction obviously doesn't store any return address on the stack. [*] The livepatch variant of the blocked function is never called because the transition is reverted. It is going to change in a followup patch. Signed-off-by: Petr Mladek --- .../selftests/livepatch/test-callbacks.sh | 73 ------------------- .../livepatch/test-state-callbacks.sh | 56 ++++++++++++++ .../livepatch/test_modules/test_klp_speaker.c | 61 ++++++++++++++++ .../test_modules/test_klp_speaker_livepatch.c | 28 +++++++ 4 files changed, 145 insertions(+), 73 deletions(-) diff --git a/tools/testing/selftests/livepatch/test-callbacks.sh b/tools/te= sting/selftests/livepatch/test-callbacks.sh index 1ecd8f08a613..858e8f0b14d5 100755 --- a/tools/testing/selftests/livepatch/test-callbacks.sh +++ b/tools/testing/selftests/livepatch/test-callbacks.sh @@ -11,79 +11,6 @@ MOD_TARGET_BUSY=3Dtest_klp_callbacks_busy =20 setup_config =20 -# 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$' $SYSFS_KLP_DIR/$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 "% insmod test_modules/$MOD_TARGET_BUSY.ko block_transition= =3DY -$MOD_TARGET_BUSY: ${MOD_TARGET_BUSY}_init -$MOD_TARGET_BUSY: busymod_work_func enter -% insmod test_modules/$MOD_LIVEPATCH.ko -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 -% insmod test_modules/$MOD_TARGET.ko -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 > $SYSFS_KLP_DIR/$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. # diff --git a/tools/testing/selftests/livepatch/test-state-callbacks.sh b/to= ols/testing/selftests/livepatch/test-state-callbacks.sh index 28ef88a2dfc3..043e2062d71c 100755 --- a/tools/testing/selftests/livepatch/test-state-callbacks.sh +++ b/tools/testing/selftests/livepatch/test-state-callbacks.sh @@ -90,4 +90,60 @@ $MOD_TARGET: speaker_welcome: Hello, World! % rmmod $MOD_TARGET $MOD_TARGET: ${MOD_TARGET}_exit" =20 +# Test state callbacks handling with blocked and reverted transitons. +# +# The started patching transion never finishes. Only "pre_patch" +# 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 "post_patch" callback has not been called. +# +# When the livepatch gets disabled, the current transiton gets +# reverted instead of starting a new disable transition. Only +# the "post_unpatch" callback is called. +start_test "blocked transition" + +load_mod $MOD_TARGET block_doors=3D1 +read_module_param $MOD_TARGET welcome + +load_lp_nowait $MOD_LIVEPATCH 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$' $SYSFS_KLP_DIR/$MOD_LIVEPATCH/transition' || + die "failed to stall transition" +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 "% insmod test_modules/$MOD_TARGET.ko block_doors=3D1 +$MOD_TARGET: ${MOD_TARGET}_init +$MOD_TARGET: block_doors_func: Going to block doors. +$MOD_TARGET: do_block_doors: Started blocking doors. +% cat $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome +$MOD_TARGET: speaker_welcome: Hello, World! +% insmod test_modules/$MOD_LIVEPATCH.ko applause=3D1 +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +$MOD_LIVEPATCH: applause_pre_patch_callback: state 10 +livepatch: '$MOD_LIVEPATCH': starting patching transition +% cat $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome +$MOD_LIVEPATCH: lp_speaker_welcome: [] Ladies and gentleman, ... +% echo 0 > $SYSFS_KLP_DIR/$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: applause_post_unpatch_callback: state 10 (nope) +$MOD_LIVEPATCH: applause_shadow_dtor: freeing applause [] (nope) +livepatch: '$MOD_LIVEPATCH': unpatching complete +% cat $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome +$MOD_TARGET: speaker_welcome: Hello, World! +% rmmod $MOD_LIVEPATCH +% rmmod $MOD_TARGET +$MOD_TARGET: ${MOD_TARGET}_exit" + exit 0 diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_speake= r.c b/tools/testing/selftests/livepatch/test_modules/test_klp_speaker.c index 92c577addb8e..6dcf15b4154b 100644 --- a/tools/testing/selftests/livepatch/test_modules/test_klp_speaker.c +++ b/tools/testing/selftests/livepatch/test_modules/test_klp_speaker.c @@ -5,6 +5,9 @@ =20 #include #include +#include +#include +#include =20 #ifndef SPEAKER_ID #define SPEAKER_ID "" @@ -21,6 +24,10 @@ * welcome_get(). * * - Reuse the module source for more speakers, see SPEAKER_ID. + * + * - Add "block_doors" parameter which could block the livepatch transi= tion. + * The stalled function is offloaded to a workqueue so that it does not + * block the module load. */ =20 noinline @@ -43,16 +50,70 @@ static const struct kernel_param_ops welcome_ops =3D { 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."); =20 +static DECLARE_COMPLETION(started_blocking_doors); +struct work_struct block_doors_work; +static bool block_doors; +static bool show_over; + +noinline +static void do_block_doors(void) +{ + pr_info("%s: Started blocking doors.\n", __func__); + complete(&started_blocking_doors); + + while (!READ_ONCE(show_over)) { + /* Busy-wait until the module gets unloaded. */ + msleep(20); + } +} + +/* + * Prevent tail call optimizations to make sure that this function + * appears in the backtrace and blocks the transition. + */ +__attribute__((__optimize__("no-optimize-sibling-calls"))) +static void block_doors_func(struct work_struct *work) +{ + pr_info("%s: Going to block doors%s.\n", __func__, SPEAKER_ID); + do_block_doors(); +} + +static void block_doors_set(void) +{ + init_completion(&started_blocking_doors); + INIT_WORK(&block_doors_work, block_doors_func); + + schedule_work(&block_doors_work); + + /* + * To synchronize kernel messages, hold this callback from + * exiting until the work function's entry message has got printed. + */ + wait_for_completion(&started_blocking_doors); + +} + +module_param(block_doors, bool, 0400); +MODULE_PARM_DESC(block_doors, "Block doors so that the audience could not = enter. It blocks the livepatch transition. (default=3Dfalse)"); + static int test_klp_speaker_init(void) { pr_info("%s\n", __func__); =20 + if (block_doors) + block_doors_set(); + return 0; } =20 static void test_klp_speaker_exit(void) { pr_info("%s\n", __func__); + + if (block_doors) { + WRITE_ONCE(show_over, true); + flush_work(&block_doors_work); + } } =20 module_init(test_klp_speaker_init); diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_speake= r_livepatch.c b/tools/testing/selftests/livepatch/test_modules/test_klp_spe= aker_livepatch.c index c46c98a3c1e6..76402947c789 100644 --- a/tools/testing/selftests/livepatch/test_modules/test_klp_speaker_livep= atch.c +++ b/tools/testing/selftests/livepatch/test_modules/test_klp_speaker_livep= atch.c @@ -19,6 +19,8 @@ * "Ladies and gentleman, ..." * * - Support more speaker modules, see __lp_speaker_welcome(). + * + * - Livepatch block_doors_func() which can block the transition. */ =20 #define APPLAUSE_ID 10 @@ -180,11 +182,33 @@ static void applause_shadow_dtor(void *obj, void *sha= dow_data) __func__, applause); } =20 +static void __lp_block_doors_func(struct work_struct *work, const char *ca= ller_func, + const char *speaker_id) +{ + /* Just print the message. Never really used. */ + pr_info("%s: Going to block doors%s (this should never happen).\n", + caller_func, speaker_id); +} + +static void lp_block_doors_func(struct work_struct *work) +{ + __lp_block_doors_func(work, __func__, ""); +} + +static void lp_block_doors_func2(struct work_struct *work) +{ + __lp_block_doors_func(work, __func__, "(2)"); +} + static struct klp_func test_klp_speaker_funcs[] =3D { { .old_name =3D "speaker_welcome", .new_func =3D lp_speaker_welcome, }, + { + .old_name =3D "block_doors_func", + .new_func =3D lp_block_doors_func, + }, { } }; =20 @@ -193,6 +217,10 @@ static struct klp_func test_klp_speaker2_funcs[] =3D { .old_name =3D "speaker_welcome", .new_func =3D lp_speaker2_welcome, }, + { + .old_name =3D "block_doors_func", + .new_func =3D lp_block_doors_func2, + }, { } }; =20 --=20 2.47.1 From nobody Mon Dec 15 21:31:33 2025 Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.223.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id F2EFE1EEA36; Wed, 15 Jan 2025 08:26:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.135.223.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929622; cv=none; b=uQlmCG8vKpgJrVxUMBDuHJCsUaYC3vvyb+zqnJNnPZcBrTBtMOJZJd3wWv099uCcik7XqJNnO+RTkeGkqxzCCfRek9AbgBDEs9pj+k6cKyw+Krz1O7SyzbbW1oXlvP95Wso/C8BGZXzIN8JIEDz9fPq/S6yWYeySdvg2y9N2aoQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929622; c=relaxed/simple; bh=NavPbJFBvMcgks94+1CtvgczTKwXetkubAWbpquSb2o=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=XRLozz81Qg5QMNY3rESxeM+74+e3+UsUDaRxNh7/Y8M4fcxIhtWl5O47fuIj/1holi5pi0cxQv3JzKaMeWxi+Sy/goz+5TS9VOKQTroJ6PF6V6/NiD75MAfMxxTdNjsokktD9oik26G7JMWMDa4AYFRT9utjltflyQn3Ai/cLz4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com; spf=pass smtp.mailfrom=suse.com; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=RqES1h53; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=RqES1h53; arc=none smtp.client-ip=195.135.223.131 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=suse.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="RqES1h53"; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="RqES1h53" Received: from pathway.suse.cz (unknown [10.100.201.202]) by smtp-out2.suse.de (Postfix) with ESMTP id 087B11F37C; Wed, 15 Jan 2025 08:26:58 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929618; 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=sAvpVG8P2b4EE+5j8jySKGjcMWTuEFrQjkKGnJ4bzxI=; b=RqES1h53EHkCsWTYHCwmum5ptaOsxLGJ36MaMuX9UmCMk497hn+OB38j4T9QAHA7A42zRk YM5QBdkQau/9Pewp0fy9vDg1hfekAFcCbIcTwUcHs2VJ7NOIPDFGzv/YaUkBKdWBjsNgjh qSuPUdY4lNx6qjADtQ6UQmXrKLLxii8= Authentication-Results: smtp-out2.suse.de; none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929618; 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=sAvpVG8P2b4EE+5j8jySKGjcMWTuEFrQjkKGnJ4bzxI=; b=RqES1h53EHkCsWTYHCwmum5ptaOsxLGJ36MaMuX9UmCMk497hn+OB38j4T9QAHA7A42zRk YM5QBdkQau/9Pewp0fy9vDg1hfekAFcCbIcTwUcHs2VJ7NOIPDFGzv/YaUkBKdWBjsNgjh qSuPUdY4lNx6qjADtQ6UQmXrKLLxii8= 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: [PATCH v1 13/19] selftests/livepatch: Add more tests for state callbacks with blocked transitions Date: Wed, 15 Jan 2025 09:24:25 +0100 Message-ID: <20250115082431.5550-14-pmladek@suse.com> X-Mailer: git-send-email 2.47.1 In-Reply-To: <20250115082431.5550-1-pmladek@suse.com> References: <20250115082431.5550-1-pmladek@suse.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Spam-Level: X-Spamd-Result: default: False [-6.80 / 50.00]; REPLY(-4.00)[]; BAYES_HAM(-3.00)[100.00%]; MID_CONTAINS_FROM(1.00)[]; NEURAL_HAM_LONG(-1.00)[-1.000]; R_MISSING_CHARSET(0.50)[]; NEURAL_HAM_SHORT(-0.20)[-1.000]; MIME_GOOD(-0.10)[text/plain]; RCVD_COUNT_ZERO(0.00)[0]; MIME_TRACE(0.00)[0:+]; ARC_NA(0.00)[]; TO_DN_SOME(0.00)[]; RCPT_COUNT_SEVEN(0.00)[7]; DKIM_SIGNED(0.00)[suse.com:s=susede1]; FUZZY_BLOCKED(0.00)[rspamd.com]; FROM_HAS_DN(0.00)[]; R_RATELIMIT(0.00)[to_ip_from(RLj3e56pwiuh8u4wxetmhsq5s5)]; FROM_EQ_ENVFROM(0.00)[]; TO_MATCH_ENVRCPT_ALL(0.00)[]; DBL_BLOCKED_OPENRESOLVER(0.00)[pathway.suse.cz:helo,suse.com:email,suse.com:mid] X-Spam-Score: -6.80 X-Spam-Flag: NO Content-Type: text/plain; charset="utf-8" This commit adds two new self tests to the livepatch state callback testing, specifically focusing on scenarios with blocked transitions. Previously, only the transition reversion scenario was tested. New Test Scenarios: + Transition Unblocking: Verifies the behavior when a blocked transition is unblocked. + Disable Operation Blocking: Tests the scenario where a function blocks the disable operation. Implementation Details: + To ensure the blocking function remains fully functional within the livepatch, its pointer is bundled with the associated struct work. + The 'block_doors' parameter now controls both enabling and disabling the blocking function. This necessitates a specific initialization of the work struct to guarantee it's flushed in the module's .exit() callbacks. Note that if 'block_doors' is defined on the command line, its .set() callback is called before the module's .init() callback. + The livepatched speaker_welcome() function is also called from the blocking function, enabling verification of separate process transitions. + A suffix is added to the welcome message when printed via the blocked process (controlled by 'block_doors') for better process differentiation. Important Note on Sibling Call Optimization: Sibling call optimization must be disabled for functions designed to block transitions. Otherwise, they won't appear on the stack, leading to test failure. These functions can be livepatched because they are called with the call instruction. But when an optimized version just jumps to a nested then the jump instruction obviously doesn't store any return address on the stack. Signed-off-by: Petr Mladek --- .../testing/selftests/livepatch/functions.sh | 28 +++- .../livepatch/test-state-callbacks.sh | 143 +++++++++++++++++- .../livepatch/test_modules/test_klp_speaker.c | 125 ++++++++++++--- .../livepatch/test_modules/test_klp_speaker.h | 15 ++ .../test_modules/test_klp_speaker_livepatch.c | 35 +++-- 5 files changed, 310 insertions(+), 36 deletions(-) create mode 100644 tools/testing/selftests/livepatch/test_modules/test_klp= _speaker.h diff --git a/tools/testing/selftests/livepatch/functions.sh b/tools/testing= /selftests/livepatch/functions.sh index bc1e100e47a7..e10a55427b71 100644 --- a/tools/testing/selftests/livepatch/functions.sh +++ b/tools/testing/selftests/livepatch/functions.sh @@ -250,13 +250,22 @@ function unload_lp() { unload_mod "$1" } =20 -# disable_lp(modname) - disable a livepatch -# modname - module name to unload -function disable_lp() { +# disable_lp_nowait(modname) - disable a livepatch but do not wait +# until the transition finishes +# modname - module name of the livepatch to disable +function disable_lp_nowait() { local mod=3D"$1" =20 log "% echo 0 > $SYSFS_KLP_DIR/$mod/enabled" echo 0 > "$SYSFS_KLP_DIR/$mod/enabled" +} + +# disable_lp(modname) - disable a livepatch +# modname - module name of the livepatch to disable +function disable_lp() { + local mod=3D"$1"; shift + + disable_lp_nowait "$mod" "$@" =20 # Wait until the transition finishes and the livepatch gets # removed from sysfs... @@ -279,6 +288,19 @@ function set_pre_patch_ret { die "failed to set pre_patch_ret parameter for $mod module" } =20 +# write_module_param(modname, param, val) +# modname - module name which provides the given parameter +# param - parameter name to be written +# val =3D value to be written +function write_module_param { + local mod=3D"$1"; shift + local param=3D"$1"; shift + local val=3D"$1" + + log "% echo $val > $SYSFS_MODULE_DIR/$mod/parameters/$param" + echo "$val" > $SYSFS_MODULE_DIR/"$mod"/parameters/"$param" +} + # 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-callbacks.sh b/to= ols/testing/selftests/livepatch/test-state-callbacks.sh index 043e2062d71c..7d8c872eccfe 100755 --- a/tools/testing/selftests/livepatch/test-state-callbacks.sh +++ b/tools/testing/selftests/livepatch/test-state-callbacks.sh @@ -121,9 +121,9 @@ unload_lp $MOD_LIVEPATCH unload_mod $MOD_TARGET =20 check_result "% insmod test_modules/$MOD_TARGET.ko block_doors=3D1 -$MOD_TARGET: ${MOD_TARGET}_init $MOD_TARGET: block_doors_func: Going to block doors. $MOD_TARGET: do_block_doors: Started blocking doors. +$MOD_TARGET: ${MOD_TARGET}_init % cat $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome $MOD_TARGET: speaker_welcome: Hello, World! % insmod test_modules/$MOD_LIVEPATCH.ko applause=3D1 @@ -146,4 +146,145 @@ $MOD_TARGET: speaker_welcome: Hello, World! % rmmod $MOD_TARGET $MOD_TARGET: ${MOD_TARGET}_exit" =20 +# Test state callbacks handling with blocked and later unblocked +# transiton. +# +# Load the test module with the blocked operation. Then load the livepatch +# and the transition should get stuck. Then unblock the operation +# so that the transition could finish. Finally, disable the livepatch +# and unload the modules as usual. +# +# Note that every process is transitioned separately. This is visible +# on the difference between the welcome message printed when reading +# the "welcome" parameter and the same message printed by the unblocked +# do_block_doors() function. + +start_test "(un)blocked transition" + +load_mod $MOD_TARGET block_doors=3D1 +read_module_param $MOD_TARGET welcome + +load_lp_nowait $MOD_LIVEPATCH 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$' $SYSFS_KLP_DIR/$MOD_LIVEPATCH/transition' || + die "failed to stall transition" +read_module_param $MOD_TARGET welcome + +# Unblock the doors (livepatch transtition) +write_module_param "$MOD_TARGET" block_doors 0 +# Wait until the livepatch reports that the transition has finished +loop_until 'grep -q '^0$' $SYSFS_KLP_DIR/$MOD_LIVEPATCH/transition' || + die "failed to finish transition" +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 "% insmod test_modules/$MOD_TARGET.ko block_doors=3D1 +$MOD_TARGET: block_doors_func: Going to block doors. +$MOD_TARGET: do_block_doors: Started blocking doors. +$MOD_TARGET: ${MOD_TARGET}_init +% cat $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome +$MOD_TARGET: speaker_welcome: Hello, World! +% insmod test_modules/$MOD_LIVEPATCH.ko applause=3D1 +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +$MOD_LIVEPATCH: applause_pre_patch_callback: state 10 +livepatch: '$MOD_LIVEPATCH': starting patching transition +% cat $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome +$MOD_LIVEPATCH: lp_speaker_welcome: [] Ladies and gentleman, ... +% echo 0 > $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/block_doors +$MOD_TARGET: do_block_doors: Stopped blocking doors. +$MOD_TARGET: speaker_welcome: Hello, World! <--- from blocked doors +livepatch: '$MOD_LIVEPATCH': completing patching transition +$MOD_LIVEPATCH: applause_post_patch_callback: state 10 +livepatch: '$MOD_LIVEPATCH': patching complete +% cat $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome +$MOD_LIVEPATCH: lp_speaker_welcome: [APPLAUSE] Ladies and gentleman, ... +% echo 0 > $SYSFS_KLP_DIR/$MOD_LIVEPATCH/enabled +livepatch: '$MOD_LIVEPATCH': initializing unpatching transition +$MOD_LIVEPATCH: applause_pre_unpatch_callback: state 10 +livepatch: '$MOD_LIVEPATCH': starting unpatching transition +livepatch: '$MOD_LIVEPATCH': completing unpatching transition +$MOD_LIVEPATCH: applause_post_unpatch_callback: state 10 (nope) +$MOD_LIVEPATCH: applause_shadow_dtor: freeing applause [] (nope) +livepatch: '$MOD_LIVEPATCH': unpatching complete +% cat $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome +$MOD_TARGET: speaker_welcome: Hello, World! +% rmmod $MOD_LIVEPATCH +% rmmod $MOD_TARGET +$MOD_TARGET: ${MOD_TARGET}_exit" + +# Test state callbacks handling with blocked disable transition. +# +# Load the livepatch first. Then load the test module with the blocking +# operation and disable the livepatch. The transition should get stuck. +# Finally, get rid of the blocked function so that the transition could +# finish and the livepatch could get unloaded. +# +# Note that every process is transitioned separately. This is visible +# on the difference between the welcome message printed when reading +# the "welcome" parameter and the same message printed by the unblocked +# do_block_doors() function. +start_test "blocked disable transition" + +load_lp $MOD_LIVEPATCH applause=3D1 +load_mod $MOD_TARGET block_doors=3D1 +read_module_param $MOD_TARGET welcome + +disable_lp_nowait $MOD_LIVEPATCH +# 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$' $SYSFS_KLP_DIR/$MOD_LIVEPATCH/transition' || + die "failed to stall transition" +read_module_param $MOD_TARGET welcome + +# Unblock the doors (livepatch transtition) +write_module_param "$MOD_TARGET" block_doors 0 +# Wait until the livepatch reports that the transition has finished +loop_until 'test ! -f $SYSFS_KLP_DIR/$MOD_LIVEPATCH/transition' || + die "failed to finish transition" +read_module_param $MOD_TARGET welcome + +unload_lp $MOD_LIVEPATCH +unload_mod $MOD_TARGET + +check_result "% insmod test_modules/$MOD_LIVEPATCH.ko applause=3D1 +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +$MOD_LIVEPATCH: applause_pre_patch_callback: state 10 +livepatch: '$MOD_LIVEPATCH': starting patching transition +livepatch: '$MOD_LIVEPATCH': completing patching transition +$MOD_LIVEPATCH: applause_post_patch_callback: state 10 +livepatch: '$MOD_LIVEPATCH': patching complete +% insmod test_modules/$MOD_TARGET.ko block_doors=3D1 +livepatch: applying patch '$MOD_LIVEPATCH' to loading module '$MOD_TARGET' +$MOD_LIVEPATCH: lp_block_doors_func: Going to block doors (fixed). +$MOD_TARGET: do_block_doors: Started blocking doors. +$MOD_TARGET: ${MOD_TARGET}_init +% cat $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome +$MOD_LIVEPATCH: lp_speaker_welcome: [APPLAUSE] Ladies and gentleman, ... +% echo 0 > $SYSFS_KLP_DIR/$MOD_LIVEPATCH/enabled +livepatch: '$MOD_LIVEPATCH': initializing unpatching transition +$MOD_LIVEPATCH: applause_pre_unpatch_callback: state 10 +livepatch: '$MOD_LIVEPATCH': starting unpatching transition +% cat $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome +$MOD_TARGET: speaker_welcome: Hello, World! +% echo 0 > $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/block_doors +$MOD_TARGET: do_block_doors: Stopped blocking doors. +$MOD_LIVEPATCH: lp_speaker_welcome: [] Ladies and gentleman, ... <--- from= blocked doors +livepatch: '$MOD_LIVEPATCH': completing unpatching transition +$MOD_LIVEPATCH: applause_post_unpatch_callback: state 10 (nope) +$MOD_LIVEPATCH: applause_shadow_dtor: freeing applause [] (nope) +livepatch: '$MOD_LIVEPATCH': unpatching complete +% cat $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome +$MOD_TARGET: speaker_welcome: Hello, World! +% rmmod $MOD_LIVEPATCH +% rmmod $MOD_TARGET +$MOD_TARGET: ${MOD_TARGET}_exit" + exit 0 diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_speake= r.c b/tools/testing/selftests/livepatch/test_modules/test_klp_speaker.c index 6dcf15b4154b..b1eebceb5964 100644 --- a/tools/testing/selftests/livepatch/test_modules/test_klp_speaker.c +++ b/tools/testing/selftests/livepatch/test_modules/test_klp_speaker.c @@ -9,6 +9,9 @@ #include #include =20 +#include "test_klp_speaker.h" + + #ifndef SPEAKER_ID #define SPEAKER_ID "" #endif @@ -27,18 +30,19 @@ * * - Add "block_doors" parameter which could block the livepatch transi= tion. * The stalled function is offloaded to a workqueue so that it does not - * block the module load. + * block the module load. The transition can be unblocked by setting + * the parameter value back to "0" via the sysfs interface. */ =20 noinline -static void speaker_welcome(void) +static void speaker_welcome(const char *context) { - pr_info("%s%s: Hello, World!\n", __func__, SPEAKER_ID); + pr_info("%s%s: Hello, World!%s\n", __func__, SPEAKER_ID, context); } =20 static int welcome_get(char *buffer, const struct kernel_param *kp) { - speaker_welcome(); + speaker_welcome(""); =20 return 0; } @@ -51,7 +55,6 @@ 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."); =20 static DECLARE_COMPLETION(started_blocking_doors); -struct work_struct block_doors_work; static bool block_doors; static bool show_over; =20 @@ -61,12 +64,28 @@ static void do_block_doors(void) pr_info("%s: Started blocking doors.\n", __func__); complete(&started_blocking_doors); =20 - while (!READ_ONCE(show_over)) { - /* Busy-wait until the module gets unloaded. */ + while (READ_ONCE(block_doors) && !READ_ONCE(show_over)) { + /* + * Busy-wait until the parameter "block_doors" is cleared or + * until the module gets unloaded. + */ msleep(20); } + + if (!block_doors) { + pr_info("%s: Stopped blocking doors.\n", __func__); + /* + * Show how the livepatched message looks in the process which + * blocked the transition. + */ + speaker_welcome(" <--- from blocked doors"); + } } =20 +static struct hall hall =3D { + .do_block_doors =3D do_block_doors, +}; + /* * Prevent tail call optimizations to make sure that this function * appears in the backtrace and blocks the transition. @@ -74,34 +93,97 @@ static void do_block_doors(void) __attribute__((__optimize__("no-optimize-sibling-calls"))) static void block_doors_func(struct work_struct *work) { + struct hall *hall =3D container_of(work, struct hall, block_doors_work); + pr_info("%s: Going to block doors%s.\n", __func__, SPEAKER_ID); - do_block_doors(); + hall->do_block_doors(); } =20 -static void block_doors_set(void) +/* + * The work must be initialized when "bool" parameter is proceed + * during the module load. Which is done before calling the module init + * callback. + * + * Also it must be initialized even when the parameter was not used because + * the work must be flushed in the module exit callback. + */ +static void block_doors_work_init(struct hall *hall) { - init_completion(&started_blocking_doors); - INIT_WORK(&block_doors_work, block_doors_func); + static bool block_doors_work_initialized; =20 - schedule_work(&block_doors_work); + if (block_doors_work_initialized) + return; + + INIT_WORK(&hall->block_doors_work, block_doors_func); + block_doors_work_initialized =3D true; +} + +static int block_doors_get(char *buffer, const struct kernel_param *kp) +{ + if (block_doors) + pr_info("The doors are blocked.\n"); + else + pr_info("The doors are not blocked.\n"); + + return 0; +} + +static int block_doors_set(const char *val, const struct kernel_param *kp) +{ + bool block; + int ret; + + ret =3D kstrtobool(val, &block); + if (ret) + return ret; + + if (block =3D=3D block_doors) { + if (block) { + pr_err("%s: The doors are already blocked.\n", __func__); + return -EBUSY; + } + + pr_err("%s: The doors are not being blocked.\n", __func__); + return -EINVAL; + } =20 /* - * To synchronize kernel messages, hold this callback from - * exiting until the work function's entry message has got printed. + * Update the global value before scheduling the work so that it + * stays blocked. */ - wait_for_completion(&started_blocking_doors); + block_doors =3D block; + if (block) { + init_completion(&started_blocking_doors); + block_doors_work_init(&hall); =20 + schedule_work(&hall.block_doors_work); + + /* + * To synchronize kernel messages, hold this callback from + * exiting until the work function's entry message has got + * printed. + */ + wait_for_completion(&started_blocking_doors); + } else { + flush_work(&hall.block_doors_work); + } + + return 0; } =20 -module_param(block_doors, bool, 0400); +static const struct kernel_param_ops block_doors_ops =3D { + .set =3D block_doors_set, + .get =3D block_doors_get, +}; + +module_param_cb(block_doors, &block_doors_ops, NULL, 0600); MODULE_PARM_DESC(block_doors, "Block doors so that the audience could not = enter. It blocks the livepatch transition. (default=3Dfalse)"); =20 static int test_klp_speaker_init(void) { pr_info("%s\n", __func__); =20 - if (block_doors) - block_doors_set(); + block_doors_work_init(&hall); =20 return 0; } @@ -110,10 +192,9 @@ static void test_klp_speaker_exit(void) { pr_info("%s\n", __func__); =20 - if (block_doors) { - WRITE_ONCE(show_over, true); - flush_work(&block_doors_work); - } + /* Make sure that do_block_doors() is not running. */ + WRITE_ONCE(show_over, true); + flush_work(&hall.block_doors_work); } =20 module_init(test_klp_speaker_init); diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_speake= r.h b/tools/testing/selftests/livepatch/test_modules/test_klp_speaker.h new file mode 100644 index 000000000000..89309a3acfde --- /dev/null +++ b/tools/testing/selftests/livepatch/test_modules/test_klp_speaker.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _TEST_KLP_SPEAKER_H_ +#define _TEST_KLP_SPEAKER_H_ + +#include + +typedef void (*do_block_doors_t)(void); + +struct hall { + struct work_struct block_doors_work; + do_block_doors_t do_block_doors; +}; + +#endif // _TEST_KLP_SPEAKER_H_ diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_speake= r_livepatch.c b/tools/testing/selftests/livepatch/test_modules/test_klp_spe= aker_livepatch.c index 76402947c789..6b82c5636845 100644 --- a/tools/testing/selftests/livepatch/test_modules/test_klp_speaker_livep= atch.c +++ b/tools/testing/selftests/livepatch/test_modules/test_klp_speaker_livep= atch.c @@ -8,6 +8,8 @@ #include #include =20 +#include "test_klp_speaker.h" + /** * test_klp_speaker_livepatch - test livepatch for testing various livepat= ching * features. @@ -37,7 +39,9 @@ static int pre_patch_ret; module_param(pre_patch_ret, int, 0400); MODULE_PARM_DESC(pre_patch_ret, "Allow to force failure for the pre_patch = callback (default=3D0)"); =20 -static void __lp_speaker_welcome(const char *caller_func, const char *spea= ker_id) +static void __lp_speaker_welcome(const char *caller_func, + const char *speaker_id, + const char *context) { char entire_applause[APPLAUSE_STR_SIZE + 1] =3D ""; const char *applause; @@ -46,18 +50,18 @@ static void __lp_speaker_welcome(const char *caller_fun= c, const char *speaker_id if (applause) snprintf(entire_applause, sizeof(entire_applause), "%s ", applause); =20 - pr_info("%s%s: %sLadies and gentleman, ...\n", caller_func, speaker_id, - entire_applause); + pr_info("%s%s: %sLadies and gentleman, ...%s\n", + caller_func, speaker_id, entire_applause, context); } =20 -static void lp_speaker_welcome(void) +static void lp_speaker_welcome(const char *context) { - __lp_speaker_welcome(__func__, ""); + __lp_speaker_welcome(__func__, "", context); } =20 -static void lp_speaker2_welcome(void) +static void lp_speaker2_welcome(const char *context) { - __lp_speaker_welcome(__func__, "(2)"); + __lp_speaker_welcome(__func__, "(2)", context); } =20 static int allocate_applause(unsigned long id) @@ -185,16 +189,27 @@ static void applause_shadow_dtor(void *obj, void *sha= dow_data) static void __lp_block_doors_func(struct work_struct *work, const char *ca= ller_func, const char *speaker_id) { - /* Just print the message. Never really used. */ - pr_info("%s: Going to block doors%s (this should never happen).\n", - caller_func, speaker_id); + struct hall *hall =3D container_of(work, struct hall, block_doors_work); + + pr_info("%s: Going to block doors%s (fixed).\n", caller_func, speaker_id); + hall->do_block_doors(); } =20 +/* + * Prevent tail call optimizations to make sure that this function + * appears in the backtrace and can block the disable transition. + */ +__attribute__((__optimize__("no-optimize-sibling-calls"))) static void lp_block_doors_func(struct work_struct *work) { __lp_block_doors_func(work, __func__, ""); } =20 +/* + * Prevent tail call optimizations to make sure that this function + * appears in the backtrace and can block the disable transition. + */ +__attribute__((__optimize__("no-optimize-sibling-calls"))) static void lp_block_doors_func2(struct work_struct *work) { __lp_block_doors_func(work, __func__, "(2)"); --=20 2.47.1 From nobody Mon Dec 15 21:31:33 2025 Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.223.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0E1F41EEA59; Wed, 15 Jan 2025 08:27:10 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.135.223.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929632; cv=none; b=NIMEdgxkf8S++lIh17XDRlYPLfyYiMBOmqdzcffpwphbe/ZSqe3g5m+6aAyjqffirHIJkydsLj8DGjzbdvAU/d2/WSq2w9D425qqDOfpjjEvEkmsut/oSMZ8ZQptp71eXeuwfXA4W90rurPyeYiK4yhCHDU2pak+v5g0fkPpSy8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929632; c=relaxed/simple; bh=ZnwXCbN2B+C/1eXrZT8o+dAhWgFXxhdxpZAVumcmq4s=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=At7Wqf1w6GQ5ck6gjM+2+26GevwWYthqLiek3OyMDeiApG+lf8Sf9SLLWy6S//JpAUZgOPnez4+TJEtvd0f6CjDPQh+b59zz/wYvDGYYAi/LwnZVtjpcjN84/lpIPkN6hnhqPfAWKERLZju0CbQYdJD+kXaeXnAWRT8gvri8Bks= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com; spf=pass smtp.mailfrom=suse.com; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=sO2E1yEf; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=sO2E1yEf; arc=none smtp.client-ip=195.135.223.131 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=suse.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="sO2E1yEf"; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="sO2E1yEf" Received: from pathway.suse.cz (unknown [10.100.201.202]) by smtp-out2.suse.de (Postfix) with ESMTP id 66DB21F37C; Wed, 15 Jan 2025 08:27:08 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929628; 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=0WcazxsZBOMrDAe/tESlkLiJZ4upXAw/WWHlFWaCO78=; b=sO2E1yEfIpoFfddrzIArVv+dVcsNumCaNtveMyf/t5I8jNq4LySELKtDBP+YyB6Tp0pS34 RvzT8cbCs55XYv7FrikvEZ4BgoGYd68Tqmm1GXsqeEDJ/7SbV9DbTf7aaCo7fjv45WPK6V JitsGc7KoW6lNZdXEvu4cC/CuzVGwpc= Authentication-Results: smtp-out2.suse.de; none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929628; 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=0WcazxsZBOMrDAe/tESlkLiJZ4upXAw/WWHlFWaCO78=; b=sO2E1yEfIpoFfddrzIArVv+dVcsNumCaNtveMyf/t5I8jNq4LySELKtDBP+YyB6Tp0pS34 RvzT8cbCs55XYv7FrikvEZ4BgoGYd68Tqmm1GXsqeEDJ/7SbV9DbTf7aaCo7fjv45WPK6V JitsGc7KoW6lNZdXEvu4cC/CuzVGwpc= 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: [PATCH v1 14/19] selftests/livepatch: Convert selftests for testing callbacks with more livepatches Date: Wed, 15 Jan 2025 09:24:26 +0100 Message-ID: <20250115082431.5550-15-pmladek@suse.com> X-Mailer: git-send-email 2.47.1 In-Reply-To: <20250115082431.5550-1-pmladek@suse.com> References: <20250115082431.5550-1-pmladek@suse.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Spam-Score: -6.80 X-Spamd-Result: default: False [-6.80 / 50.00]; REPLY(-4.00)[]; BAYES_HAM(-3.00)[100.00%]; MID_CONTAINS_FROM(1.00)[]; NEURAL_HAM_LONG(-1.00)[-1.000]; R_MISSING_CHARSET(0.50)[]; NEURAL_HAM_SHORT(-0.20)[-1.000]; MIME_GOOD(-0.10)[text/plain]; RCVD_COUNT_ZERO(0.00)[0]; MIME_TRACE(0.00)[0:+]; ARC_NA(0.00)[]; TO_DN_SOME(0.00)[]; RCPT_COUNT_SEVEN(0.00)[7]; DKIM_SIGNED(0.00)[suse.com:s=susede1]; FUZZY_BLOCKED(0.00)[rspamd.com]; FROM_HAS_DN(0.00)[]; R_RATELIMIT(0.00)[to_ip_from(RLj3e56pwiuh8u4wxetmhsq5s5)]; FROM_EQ_ENVFROM(0.00)[]; TO_MATCH_ENVRCPT_ALL(0.00)[]; DBL_BLOCKED_OPENRESOLVER(0.00)[suse.com:mid,suse.com:email,pathway.suse.cz:helo] X-Spam-Flag: NO X-Spam-Level: Content-Type: text/plain; charset="utf-8" This patch converts selftests for the obsolete per-object callbacks and more livepatches. The new tests for the per-state callbacks use the new speaker test module. The second livepatch simply reuses the sources from the existing one. For greater variability, the livepatch is extended to support more shadow variables. The second shadow variable can be enabled using the "add_applause2" module parameter. It appears in the speaker's welcome message as follows: + not patched system: "Hello, World!" + transition (unpatched task): "[2] Hello, World!" + transition (patched task): "[2] Ladies and gentleman, ..." + patched system: "[APPLAUSE2] Ladies and gentleman, ..." For backward compatibility, the first shadow variable is enabled using the non-versioned parameter "add_applause" and appears as the non-versioned "[APPLAUSE]" string. Both shadow variables can be enabled together. They will appear next to each other in the speaker's welcome message. For example, on the patched system: "[APPLAUSE][APPLAUSE2] Ladies and gentlemen, ..." Finally, the livepatch will get installed in parallel with other livepatches by default. The atomic replace feature can be enabled by a new module parameter "replace". Signed-off-by: Petr Mladek --- .../selftests/livepatch/test-callbacks.sh | 113 ------------ .../livepatch/test-state-callbacks.sh | 161 ++++++++++++++++ .../selftests/livepatch/test_modules/Makefile | 1 + .../test_modules/test_klp_speaker_livepatch.c | 173 +++++++++++++++--- .../test_klp_speaker_livepatch2.c | 5 + 5 files changed, 312 insertions(+), 141 deletions(-) delete mode 100755 tools/testing/selftests/livepatch/test-callbacks.sh create mode 100644 tools/testing/selftests/livepatch/test_modules/test_klp= _speaker_livepatch2.c diff --git a/tools/testing/selftests/livepatch/test-callbacks.sh b/tools/te= sting/selftests/livepatch/test-callbacks.sh deleted file mode 100755 index 858e8f0b14d5..000000000000 --- a/tools/testing/selftests/livepatch/test-callbacks.sh +++ /dev/null @@ -1,113 +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 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 "% insmod test_modules/$MOD_LIVEPATCH.ko -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 -% insmod test_modules/$MOD_LIVEPATCH2.ko -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 > $SYSFS_KLP_DIR/$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 > $SYSFS_KLP_DIR/$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 "% insmod test_modules/$MOD_LIVEPATCH.ko -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 -% insmod test_modules/$MOD_LIVEPATCH2.ko 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 > $SYSFS_KLP_DIR/$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-state-callbacks.sh b/to= ols/testing/selftests/livepatch/test-state-callbacks.sh index 7d8c872eccfe..5349beae2735 100755 --- a/tools/testing/selftests/livepatch/test-state-callbacks.sh +++ b/tools/testing/selftests/livepatch/test-state-callbacks.sh @@ -6,6 +6,7 @@ . $(dirname $0)/functions.sh =20 MOD_LIVEPATCH=3Dtest_klp_speaker_livepatch +MOD_LIVEPATCH2=3Dtest_klp_speaker_livepatch2 MOD_TARGET=3Dtest_klp_speaker =20 setup_config @@ -287,4 +288,164 @@ $MOD_TARGET: speaker_welcome: Hello, World! % rmmod $MOD_TARGET $MOD_TARGET: ${MOD_TARGET}_exit" =20 +# Test loading multiple livepatches in parallel. +# +# Both livepatches fix the speaker's welcome message. The first one +# also adds the base "[APPLAUSE]". The second one adds an extra "[APPLAUSE= 2]", +# aka from another level of the concert hall. +# +# The per-state callbacks are called when the state is introduced or +# or removed. +# +# The [APPLAUSE] and [APPLAUSE2] strings should appear in the speaker's +# welcome message when the respective livepatches are enabled. +start_test "multiple livepatches in parallel" + +load_mod $MOD_TARGET +read_module_param $MOD_TARGET welcome + +load_lp $MOD_LIVEPATCH applause=3D1 +read_module_param $MOD_TARGET welcome + +load_lp $MOD_LIVEPATCH2 applause2=3D1 +read_module_param $MOD_TARGET welcome + +disable_lp $MOD_LIVEPATCH2 +unload_lp $MOD_LIVEPATCH2 +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 "% insmod test_modules/$MOD_TARGET.ko +$MOD_TARGET: ${MOD_TARGET}_init +% cat $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome +$MOD_TARGET: speaker_welcome: Hello, World! +% insmod test_modules/$MOD_LIVEPATCH.ko applause=3D1 +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +$MOD_LIVEPATCH: applause_pre_patch_callback: state 10 +livepatch: '$MOD_LIVEPATCH': starting patching transition +livepatch: '$MOD_LIVEPATCH': completing patching transition +$MOD_LIVEPATCH: applause_post_patch_callback: state 10 +livepatch: '$MOD_LIVEPATCH': patching complete +% cat $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome +$MOD_LIVEPATCH: lp_speaker_welcome: [APPLAUSE] Ladies and gentleman, ... +% insmod test_modules/$MOD_LIVEPATCH2.ko applause2=3D1 +livepatch: enabling patch '$MOD_LIVEPATCH2' +livepatch: '$MOD_LIVEPATCH2': initializing patching transition +$MOD_LIVEPATCH2: applause_pre_patch_callback: state 11 +livepatch: '$MOD_LIVEPATCH2': starting patching transition +livepatch: '$MOD_LIVEPATCH2': completing patching transition +$MOD_LIVEPATCH2: applause_post_patch_callback: state 11 +livepatch: '$MOD_LIVEPATCH2': patching complete +% cat $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome +$MOD_LIVEPATCH2: lp_speaker_welcome: [APPLAUSE][APPLAUSE2] Ladies and gent= leman, ... +% echo 0 > $SYSFS_KLP_DIR/$MOD_LIVEPATCH2/enabled +livepatch: '$MOD_LIVEPATCH2': initializing unpatching transition +$MOD_LIVEPATCH2: applause_pre_unpatch_callback: state 11 +livepatch: '$MOD_LIVEPATCH2': starting unpatching transition +livepatch: '$MOD_LIVEPATCH2': completing unpatching transition +$MOD_LIVEPATCH2: applause_post_unpatch_callback: state 11 (nope) +$MOD_LIVEPATCH2: applause_shadow_dtor: freeing applause [2] (nope) +livepatch: '$MOD_LIVEPATCH2': unpatching complete +% rmmod $MOD_LIVEPATCH2 +% cat $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome +$MOD_LIVEPATCH: lp_speaker_welcome: [APPLAUSE] Ladies and gentleman, ... +% echo 0 > $SYSFS_KLP_DIR/$MOD_LIVEPATCH/enabled +livepatch: '$MOD_LIVEPATCH': initializing unpatching transition +$MOD_LIVEPATCH: applause_pre_unpatch_callback: state 10 +livepatch: '$MOD_LIVEPATCH': starting unpatching transition +livepatch: '$MOD_LIVEPATCH': completing unpatching transition +$MOD_LIVEPATCH: applause_post_unpatch_callback: state 10 (nope) +$MOD_LIVEPATCH: applause_shadow_dtor: freeing applause [] (nope) +livepatch: '$MOD_LIVEPATCH': unpatching complete +% rmmod $MOD_LIVEPATCH +% cat $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome +$MOD_TARGET: speaker_welcome: Hello, World! +% rmmod $MOD_TARGET +$MOD_TARGET: ${MOD_TARGET}_exit" + +# Test loading multiple livepatches using the atomic replace. +# +# Both livepatches fix the speaker's welcome message. The first one +# also adds the base "[APPLAUSE]". The second one also enables +# "[APPLAUSE2]", aka from another level of the concert hall. +# +# In compare with the previous selftest, the 2nd livepatch has +# to enable both "add_applause" and "add_applause2" module parameters. +# By other words, the second livepatch has to support both states. +# Otherwise, the base "[APPLAUSE]" would get disabled. +# +# The first livepatch is replaced. It does not need to be explicitly +# disabled. +# +# The per-state callbacks are called when the state is introduced or +# or removed. +# +# The [APPLAUSE] and [APPLAUSE2] strings should appear in the speaker's +# welcome message when the respective livepatches are enabled. +start_test "atomic replace" + +load_mod $MOD_TARGET +read_module_param $MOD_TARGET welcome + +load_lp $MOD_LIVEPATCH applause=3D1 +read_module_param $MOD_TARGET welcome + +load_lp $MOD_LIVEPATCH2 replace=3D1 applause=3D1 applause2=3D1 +unload_lp $MOD_LIVEPATCH +read_module_param $MOD_TARGET welcome + +disable_lp $MOD_LIVEPATCH2 +unload_lp $MOD_LIVEPATCH2 +read_module_param $MOD_TARGET welcome + +unload_mod $MOD_TARGET + +check_result "% insmod test_modules/$MOD_TARGET.ko +$MOD_TARGET: ${MOD_TARGET}_init +% cat $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome +$MOD_TARGET: speaker_welcome: Hello, World! +% insmod test_modules/$MOD_LIVEPATCH.ko applause=3D1 +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +$MOD_LIVEPATCH: applause_pre_patch_callback: state 10 +livepatch: '$MOD_LIVEPATCH': starting patching transition +livepatch: '$MOD_LIVEPATCH': completing patching transition +$MOD_LIVEPATCH: applause_post_patch_callback: state 10 +livepatch: '$MOD_LIVEPATCH': patching complete +% cat $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome +$MOD_LIVEPATCH: lp_speaker_welcome: [APPLAUSE] Ladies and gentleman, ... +% insmod test_modules/$MOD_LIVEPATCH2.ko replace=3D1 applause=3D1 applause= 2=3D1 +livepatch: enabling patch '$MOD_LIVEPATCH2' +livepatch: '$MOD_LIVEPATCH2': initializing patching transition +$MOD_LIVEPATCH2: applause_pre_patch_callback: state 11 +livepatch: '$MOD_LIVEPATCH2': starting patching transition +livepatch: '$MOD_LIVEPATCH2': completing patching transition +$MOD_LIVEPATCH2: applause_post_patch_callback: state 11 +livepatch: '$MOD_LIVEPATCH2': patching complete +% rmmod $MOD_LIVEPATCH +% cat $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome +$MOD_LIVEPATCH2: lp_speaker_welcome: [APPLAUSE][APPLAUSE2] Ladies and gent= leman, ... +% echo 0 > $SYSFS_KLP_DIR/$MOD_LIVEPATCH2/enabled +livepatch: '$MOD_LIVEPATCH2': initializing unpatching transition +$MOD_LIVEPATCH2: applause_pre_unpatch_callback: state 10 +$MOD_LIVEPATCH2: applause_pre_unpatch_callback: state 11 +livepatch: '$MOD_LIVEPATCH2': starting unpatching transition +livepatch: '$MOD_LIVEPATCH2': completing unpatching transition +$MOD_LIVEPATCH2: applause_post_unpatch_callback: state 10 (nope) +$MOD_LIVEPATCH2: applause_shadow_dtor: freeing applause [] (nope) +$MOD_LIVEPATCH2: applause_post_unpatch_callback: state 11 (nope) +$MOD_LIVEPATCH2: applause_shadow_dtor: freeing applause [2] (nope) +livepatch: '$MOD_LIVEPATCH2': unpatching complete +% rmmod $MOD_LIVEPATCH2 +% cat $SYSFS_MODULE_DIR/$MOD_TARGET/parameters/welcome +$MOD_TARGET: speaker_welcome: Hello, World! +% rmmod $MOD_TARGET +$MOD_TARGET: ${MOD_TARGET}_exit" + exit 0 diff --git a/tools/testing/selftests/livepatch/test_modules/Makefile b/tool= s/testing/selftests/livepatch/test_modules/Makefile index 72a817d1ddd9..f1e7b9d64c8e 100644 --- a/tools/testing/selftests/livepatch/test_modules/Makefile +++ b/tools/testing/selftests/livepatch/test_modules/Makefile @@ -12,6 +12,7 @@ obj-m +=3D test_klp_atomic_replace.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/tools/testing/selftests/livepatch/test_modules/test_klp_speake= r_livepatch.c b/tools/testing/selftests/livepatch/test_modules/test_klp_spe= aker_livepatch.c index 6b82c5636845..cdc7010f0e93 100644 --- a/tools/testing/selftests/livepatch/test_modules/test_klp_speaker_livep= atch.c +++ b/tools/testing/selftests/livepatch/test_modules/test_klp_speaker_livep= atch.c @@ -23,32 +23,75 @@ * - Support more speaker modules, see __lp_speaker_welcome(). * * - Livepatch block_doors_func() which can block the transition. + * + * - Support testing of more shadow variables and state callbacks. see + * "applause", and "applause2" module parameters. + * + * - Allow to enable the atomic replace via "replace" parameter. */ =20 -#define APPLAUSE_ID 10 +#define APPLAUSE_NUM 2 +#define APPLAUSE_START_ID 10 #define APPLAUSE_STR_SIZE 16 +#define APPLAUSE_IDX_STR_SIZE 8 =20 /* associate the shadow variable with NULL address */; static void *shadow_object =3D NULL; =20 -static bool add_applause; -module_param_named(applause, add_applause, bool, 0400); +static bool add_applause[APPLAUSE_NUM]; +module_param_named(applause, add_applause[0], bool, 0400); MODULE_PARM_DESC(applause, "Use shadow variable to add applause (default= =3Dfalse)"); +module_param_named(applause2, add_applause[1], bool, 0400); +MODULE_PARM_DESC(applause2, "Use shadow variable to add 2nd applause (defa= ult=3Dfalse)"); =20 static int pre_patch_ret; module_param(pre_patch_ret, int, 0400); MODULE_PARM_DESC(pre_patch_ret, "Allow to force failure for the pre_patch = callback (default=3D0)"); =20 +static bool replace; +module_param(replace, bool, 0400); +MODULE_PARM_DESC(replace, "Enable the atomic replace feature when loading = the livepatch. (default=3Dfalse)"); + +/* Conversion between the index to the @add_applause table and state ID. */ +#define __idx_to_state_id(idx) (idx + APPLAUSE_START_ID) +#define __state_id_to_idx(state_id) (state_id - APPLAUSE_START_ID) + static void __lp_speaker_welcome(const char *caller_func, const char *speaker_id, const char *context) { - char entire_applause[APPLAUSE_STR_SIZE + 1] =3D ""; - const char *applause; + char entire_applause[APPLAUSE_NUM * APPLAUSE_STR_SIZE + 1] =3D ""; + int idx, ret; + int len =3D 0; =20 - applause =3D (char *)klp_shadow_get(shadow_object, APPLAUSE_ID); - if (applause) - snprintf(entire_applause, sizeof(entire_applause), "%s ", applause); + for (idx =3D 0; idx < APPLAUSE_NUM ; idx++) { + const char *applause; + + applause =3D (char *)klp_shadow_get(shadow_object, + __idx_to_state_id(idx)); + + if (applause) { + ret =3D strscpy(entire_applause + len, applause, + sizeof(entire_applause) - len); + if (ret < 0) { + pr_warn("Too small buffer for entire_applause. Truncating...\n"); + len =3D sizeof(entire_applause) - 1; + break; + } + len +=3D ret; + } + } + + if (len) { + ret =3D strscpy(entire_applause + len, " ", + sizeof(entire_applause) - len); + if (ret < 0) { + pr_warn("Too small buffer for entire_applause. Truncating...\n"); + len =3D sizeof(entire_applause) - 1; + } else { + len +=3D ret; + } + } =20 pr_info("%s%s: %sLadies and gentleman, ...%s\n", caller_func, speaker_id, entire_applause, context); @@ -64,8 +107,28 @@ static void lp_speaker2_welcome(const char *context) __lp_speaker_welcome(__func__, "(2)", context); } =20 +static char *state_id_to_idx_str(char *buf, size_t size, + unsigned long state_id) +{ + int idx; + + idx =3D __state_id_to_idx(state_id); + + if (idx < 0 || idx >=3D APPLAUSE_NUM) { + pr_err("%s: Applause table index out of scope: %d\n", __func__, idx); + return ""; + } + + if (idx =3D=3D 0) + return ""; + + snprintf(buf, size, "%d", idx + 1); + return buf; +} + static int allocate_applause(unsigned long id) { + char idx_str[APPLAUSE_IDX_STR_SIZE]; char *applause; =20 /* @@ -84,13 +147,15 @@ static int allocate_applause(unsigned long id) return -ENOMEM; } =20 - strscpy(applause, "[]", APPLAUSE_STR_SIZE); + snprintf(applause, APPLAUSE_STR_SIZE, "[%s]", + state_id_to_idx_str(idx_str, sizeof(idx_str), id)); =20 return 0; } =20 static void set_applause(unsigned long id) { + char idx_str[APPLAUSE_IDX_STR_SIZE]; char *applause; =20 applause =3D (char *)klp_shadow_get(shadow_object, id); @@ -100,11 +165,13 @@ static void set_applause(unsigned long id) return; } =20 - strscpy(applause, "[APPLAUSE]", APPLAUSE_STR_SIZE); + snprintf(applause, APPLAUSE_STR_SIZE, "[APPLAUSE%s]", + state_id_to_idx_str(idx_str, sizeof(idx_str), id)); } =20 static void unset_applause(unsigned long id) { + char idx_str[APPLAUSE_IDX_STR_SIZE]; char *applause; =20 applause =3D (char *)klp_shadow_get(shadow_object, id); @@ -114,7 +181,8 @@ static void unset_applause(unsigned long id) return; } =20 - strscpy(applause, "[]", APPLAUSE_STR_SIZE); + snprintf(applause, APPLAUSE_STR_SIZE, "[%s]", + state_id_to_idx_str(idx_str, sizeof(idx_str), id)); } =20 static void check_applause(unsigned long id) @@ -251,36 +319,85 @@ static struct klp_object objs[] =3D { { } }; =20 -static struct klp_state states[] =3D { - { - .id =3D APPLAUSE_ID, - .is_shadow =3D true, - .callbacks =3D { - .pre_patch =3D applause_pre_patch_callback, - .post_patch =3D applause_post_patch_callback, - .pre_unpatch =3D applause_pre_unpatch_callback, - .post_unpatch =3D applause_post_unpatch_callback, - .shadow_dtor =3D applause_shadow_dtor, - }, - }, - {} -}; - static struct klp_patch patch =3D { .mod =3D THIS_MODULE, .objs =3D objs, }; =20 + +/* + * The array with states is dynamically allocated depending on which states + * are enabled on the command line. + */ +static struct klp_state *applause_states; + +static int applause_init(void) +{ + int idx, idx_allowed, id, enabled_cnt; + + enabled_cnt =3D 0; + + for (idx =3D 0, id =3D APPLAUSE_START_ID, enabled_cnt =3D 0; + idx < APPLAUSE_NUM; + idx++, id++) { + if (add_applause[idx]) + enabled_cnt++; + } + + if (enabled_cnt) { + /* Allocate one more state as the trailing entry. */ + applause_states =3D + kzalloc(sizeof(applause_states[0]) * (enabled_cnt + 1), GFP_KERNEL); + if (!applause_states) + return -ENOMEM; + + patch.states =3D applause_states; + + for (idx =3D 0, idx_allowed =3D 0; + idx < APPLAUSE_NUM; + idx++) { + struct klp_state *state; + + if (!add_applause[idx]) + continue; + + if (idx_allowed >=3D enabled_cnt) { + pr_warn("Too many enabled applause states\n"); + continue; + } + + state =3D &applause_states[idx_allowed++]; + + state->id =3D __idx_to_state_id(idx); + state->is_shadow =3D true; + state->callbacks.pre_patch =3D applause_pre_patch_callback; + state->callbacks.post_patch =3D applause_post_patch_callback; + state->callbacks.pre_unpatch =3D applause_pre_unpatch_callback; + state->callbacks.post_unpatch =3D applause_post_unpatch_callback; + state->callbacks.shadow_dtor =3D applause_shadow_dtor; + } + } + + return 0; +} + static int test_klp_speaker_livepatch_init(void) { - if (add_applause) - patch.states =3D states; + int err; + + err =3D applause_init(); + if (err) + return err; + + if (replace) + patch.replace =3D true; =20 return klp_enable_patch(&patch); } =20 static void test_klp_speaker_livepatch_exit(void) { + kfree(applause_states); } =20 module_init(test_klp_speaker_livepatch_init); diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_speake= r_livepatch2.c b/tools/testing/selftests/livepatch/test_modules/test_klp_sp= eaker_livepatch2.c new file mode 100644 index 000000000000..c011d2ee8301 --- /dev/null +++ b/tools/testing/selftests/livepatch/test_modules/test_klp_speaker_livep= atch2.c @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2024 SUSE + +/* Same livepatch with the same features. */ +#include "test_klp_speaker_livepatch.c" --=20 2.47.1 From nobody Mon Dec 15 21:31:33 2025 Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.223.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 54B071EEA59; Wed, 15 Jan 2025 08:27:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.135.223.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929642; cv=none; b=Q9obOgO1J62wq1YXxJEdw28mXJTkdgiL6HHiL0FfpzRtiaseS8R50/ABbfcyTBoWs14zi6qL84bhlUI8II0Dk+6Zn8JhPHjJ+2FplT/tvg5so9UDBxKiipONgNNzpkSvJCCLi6rnF3aueJNgEYERMlWc3Uf4oKivQhRdvL5RX/M= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929642; c=relaxed/simple; bh=0RHscELgUV1CB9tgGqKMDjmehQ6c0mMx8prEd09pXO4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=V6J5VmSvEJpXRjVbieU7Cx7uQtU110FNi2A+HnlCh8XdqX5XwqumgrmxoA1FU8ZfK05r5tS5AZoLOIGvGosQfAKFCC9zUzleUVYMucohGv0MIjsMNs68KlPB7ETZm3k3J+UqzO2/rZ6tH5m3b1Q+6zvo+K01BcehgvJgF8KXBrM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com; spf=pass smtp.mailfrom=suse.com; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=AwlrNoUb; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=AwlrNoUb; arc=none smtp.client-ip=195.135.223.131 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=suse.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="AwlrNoUb"; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="AwlrNoUb" Received: from pathway.suse.cz (unknown [10.100.201.202]) by smtp-out2.suse.de (Postfix) with ESMTP id B4C1F1F37C; Wed, 15 Jan 2025 08:27:18 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929638; 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=ChL2is7BM7UhqvWBMZxnH0LEK0ucd+/1xXIHgbJhKaQ=; b=AwlrNoUbv7I4SrLjqV4cjhjOuLgF17vSdeLcjV/Au/ofZX7dAcmC6jcszcoW1dZ0UYjBJw tky7OJA31rAqWzTKljaRnuHNMRK+r1ovT+E2/14fLqmJKP3JQqouaH76aTqTs8jUxstJAf JgRYunrLOf9Yewel9/ikEtQj2pHrgAE= Authentication-Results: smtp-out2.suse.de; none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929638; 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=ChL2is7BM7UhqvWBMZxnH0LEK0ucd+/1xXIHgbJhKaQ=; b=AwlrNoUbv7I4SrLjqV4cjhjOuLgF17vSdeLcjV/Au/ofZX7dAcmC6jcszcoW1dZ0UYjBJw tky7OJA31rAqWzTKljaRnuHNMRK+r1ovT+E2/14fLqmJKP3JQqouaH76aTqTs8jUxstJAf JgRYunrLOf9Yewel9/ikEtQj2pHrgAE= 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: [PATCH v1 15/19] selftests/livepatch: Do not use a livepatch with the obsolete per-object callbacks in the basic selftests Date: Wed, 15 Jan 2025 09:24:27 +0100 Message-ID: <20250115082431.5550-16-pmladek@suse.com> X-Mailer: git-send-email 2.47.1 In-Reply-To: <20250115082431.5550-1-pmladek@suse.com> References: <20250115082431.5550-1-pmladek@suse.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Spam-Level: X-Spamd-Result: default: False [-6.80 / 50.00]; REPLY(-4.00)[]; BAYES_HAM(-3.00)[100.00%]; MID_CONTAINS_FROM(1.00)[]; NEURAL_HAM_LONG(-1.00)[-1.000]; R_MISSING_CHARSET(0.50)[]; NEURAL_HAM_SHORT(-0.20)[-1.000]; MIME_GOOD(-0.10)[text/plain]; RCVD_COUNT_ZERO(0.00)[0]; MIME_TRACE(0.00)[0:+]; ARC_NA(0.00)[]; TO_DN_SOME(0.00)[]; RCPT_COUNT_SEVEN(0.00)[7]; DKIM_SIGNED(0.00)[suse.com:s=susede1]; FUZZY_BLOCKED(0.00)[rspamd.com]; FROM_HAS_DN(0.00)[]; R_RATELIMIT(0.00)[to_ip_from(RLj3e56pwiuh8u4wxetmhsq5s5)]; FROM_EQ_ENVFROM(0.00)[]; TO_MATCH_ENVRCPT_ALL(0.00)[]; DBL_BLOCKED_OPENRESOLVER(0.00)[suse.com:email,suse.com:mid,pathway.suse.cz:helo] X-Spam-Score: -6.80 X-Spam-Flag: NO Content-Type: text/plain; charset="utf-8" The per-object callbacks have been deprecated in favor of per-state callbacks and will be removed soon. Do not use the test livepatch with the obsolete callbacks in the basic livepatch tests. Replace it with the new generic livepatch, which does not call any callbacks by default. Signed-off-by: Petr Mladek --- tools/testing/selftests/livepatch/test-livepatch.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tools/testing/selftests/livepatch/test-livepatch.sh b/tools/te= sting/selftests/livepatch/test-livepatch.sh index 6673023d2b66..5e53adb47c58 100755 --- a/tools/testing/selftests/livepatch/test-livepatch.sh +++ b/tools/testing/selftests/livepatch/test-livepatch.sh @@ -6,7 +6,7 @@ =20 MOD_LIVEPATCH1=3Dtest_klp_livepatch MOD_LIVEPATCH2=3Dtest_klp_syscall -MOD_LIVEPATCH3=3Dtest_klp_callbacks_demo +MOD_LIVEPATCH3=3Dtest_klp_speaker_livepatch MOD_REPLACE=3Dtest_klp_atomic_replace =20 setup_config @@ -172,10 +172,8 @@ livepatch: '$MOD_LIVEPATCH2': patching complete % insmod test_modules/$MOD_LIVEPATCH3.ko livepatch: enabling patch '$MOD_LIVEPATCH3' livepatch: '$MOD_LIVEPATCH3': initializing patching transition -$MOD_LIVEPATCH3: pre_patch_callback: vmlinux livepatch: '$MOD_LIVEPATCH3': starting patching transition livepatch: '$MOD_LIVEPATCH3': completing patching transition -$MOD_LIVEPATCH3: post_patch_callback: vmlinux livepatch: '$MOD_LIVEPATCH3': patching complete % insmod test_modules/$MOD_REPLACE.ko replace=3D1 livepatch: enabling patch '$MOD_REPLACE' --=20 2.47.1 From nobody Mon Dec 15 21:31:33 2025 Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.223.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 98D3B1E7C36; Wed, 15 Jan 2025 08:27:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.135.223.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929653; cv=none; b=OOHZvpqftzx9F6H224U3MbhmEo6As17M7lB/CzkWofVkkKRPPSCXzIY8ILtcHsMQy2DN2ZWm3rvbkcUzsGzdc6u8BRYoB+88KKtkLeZTFOJOMgEyBLaRxSESJ7GcnptTUqc4zg26BAQZaZfKGBIIeSZABqsvZrQkuspa/vNy7vI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929653; c=relaxed/simple; bh=QZwxEBVrvHuhiAeBqHJiRKnif9EV/1w7tMlon0+lqTY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=a7WGPkycv/5YPC711bGm1sXZcZNS/OF4hxi7yp7kInC5N5hgtZqGmcUuiVv9H1i7dcY2ZKMrSV9XyMe9FfZsJ4coiHAS38NH4IJBaGtmszk8eFcx+W4S3+AFH1GLeBmqy0aKMwVLPZlGJCjGajEd63ivjkt69Cf5KJ7ryYefyQ4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com; spf=pass smtp.mailfrom=suse.com; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=UC9YqqrG; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=U52GS/lg; arc=none smtp.client-ip=195.135.223.131 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=suse.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="UC9YqqrG"; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="U52GS/lg" Received: from pathway.suse.cz (unknown [10.100.201.202]) by smtp-out2.suse.de (Postfix) with ESMTP id 0909B1F37C; Wed, 15 Jan 2025 08:27:29 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929650; 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=zp+2FY8wvNTBgSoSA3n3MGUZ2OF/XudmpdMQ+3KJdpk=; b=UC9YqqrGohDY2DBx1isZ6wc5QSrhEbTN9bKKLqOzaw0OaGBOpWgqJWCtYPyI6DVpen5PIw lXnoYSKFm/+BJeoSQFJxl/anEfZ2uPZ4N1kmXcX4H/qLK2do9qc2KbCsub3so/wNzNlAAK sgzxHJiZWLbzQrLH+0y6AnUQ3qk+HyM= Authentication-Results: smtp-out2.suse.de; none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929649; 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=zp+2FY8wvNTBgSoSA3n3MGUZ2OF/XudmpdMQ+3KJdpk=; b=U52GS/lg6Fv7IFy2q5x9pnbp+NpFJytPVoXNLPsMUW4oh7IwKqRGbkBz9fYK/EpoW5M3l1 TERnGEB1uDJMUq06tFElyK9hQfN6ipaXP5CV/OHzXForgOPQ716uK1kdKYx3UvpByEVSTK uPaTaO096G74YVonzNtbzEZJb1e/JBo= 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: [PATCH v1 16/19] selftests/livepatch: Remove obsolete test modules for per-object callbacks Date: Wed, 15 Jan 2025 09:24:28 +0100 Message-ID: <20250115082431.5550-17-pmladek@suse.com> X-Mailer: git-send-email 2.47.1 In-Reply-To: <20250115082431.5550-1-pmladek@suse.com> References: <20250115082431.5550-1-pmladek@suse.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Spam-Score: -6.80 X-Spamd-Result: default: False [-6.80 / 50.00]; REPLY(-4.00)[]; BAYES_HAM(-3.00)[100.00%]; MID_CONTAINS_FROM(1.00)[]; NEURAL_HAM_LONG(-1.00)[-1.000]; R_MISSING_CHARSET(0.50)[]; NEURAL_HAM_SHORT(-0.20)[-1.000]; MIME_GOOD(-0.10)[text/plain]; RCVD_COUNT_ZERO(0.00)[0]; MIME_TRACE(0.00)[0:+]; ARC_NA(0.00)[]; TO_DN_SOME(0.00)[]; RCPT_COUNT_SEVEN(0.00)[7]; DKIM_SIGNED(0.00)[suse.com:s=susede1]; FUZZY_BLOCKED(0.00)[rspamd.com]; FROM_HAS_DN(0.00)[]; R_RATELIMIT(0.00)[to_ip_from(RLj3e56pwiuh8u4wxetmhsq5s5)]; FROM_EQ_ENVFROM(0.00)[]; TO_MATCH_ENVRCPT_ALL(0.00)[]; DBL_BLOCKED_OPENRESOLVER(0.00)[pathway.suse.cz:helo,suse.com:mid,suse.com:email] X-Spam-Flag: NO X-Spam-Level: Content-Type: text/plain; charset="utf-8" The per-object callbacks have been deprecated in favor of per-state callbacks and are scheduled for removal. This commit removes the corresponding test modules that are no longer needed. These test modules have been superseded by new tests that exercise the per-state callback functionality. Signed-off-by: Petr Mladek --- .../selftests/livepatch/test_modules/Makefile | 4 - .../test_modules/test_klp_callbacks_busy.c | 70 ---------- .../test_modules/test_klp_callbacks_demo.c | 121 ------------------ .../test_modules/test_klp_callbacks_demo2.c | 93 -------------- .../test_modules/test_klp_callbacks_mod.c | 24 ---- 5 files changed, 312 deletions(-) delete mode 100644 tools/testing/selftests/livepatch/test_modules/test_klp= _callbacks_busy.c delete mode 100644 tools/testing/selftests/livepatch/test_modules/test_klp= _callbacks_demo.c delete mode 100644 tools/testing/selftests/livepatch/test_modules/test_klp= _callbacks_demo2.c delete mode 100644 tools/testing/selftests/livepatch/test_modules/test_klp= _callbacks_mod.c diff --git a/tools/testing/selftests/livepatch/test_modules/Makefile b/tool= s/testing/selftests/livepatch/test_modules/Makefile index f1e7b9d64c8e..18b0c23ef656 100644 --- a/tools/testing/selftests/livepatch/test_modules/Makefile +++ b/tools/testing/selftests/livepatch/test_modules/Makefile @@ -2,10 +2,6 @@ TESTMODS_DIR :=3D $(realpath $(dir $(abspath $(lastword $(= MAKEFILE_LIST))))) KDIR ?=3D /lib/modules/$(shell uname -r)/build =20 obj-m +=3D test_klp_atomic_replace.o \ - test_klp_callbacks_busy.o \ - test_klp_callbacks_demo.o \ - test_klp_callbacks_demo2.o \ - test_klp_callbacks_mod.o \ test_klp_kprobe.o \ test_klp_livepatch.o \ test_klp_shadow_vars.o \ diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_callba= cks_busy.c b/tools/testing/selftests/livepatch/test_modules/test_klp_callba= cks_busy.c deleted file mode 100644 index 133929e0ce8f..000000000000 --- a/tools/testing/selftests/livepatch/test_modules/test_klp_callbacks_bus= y.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/tools/testing/selftests/livepatch/test_modules/test_klp_callba= cks_demo.c b/tools/testing/selftests/livepatch/test_modules/test_klp_callba= cks_demo.c deleted file mode 100644 index 3fd8fe1cd1cc..000000000000 --- a/tools/testing/selftests/livepatch/test_modules/test_klp_callbacks_dem= o.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/tools/testing/selftests/livepatch/test_modules/test_klp_callba= cks_demo2.c b/tools/testing/selftests/livepatch/test_modules/test_klp_callb= acks_demo2.c deleted file mode 100644 index 5417573e80af..000000000000 --- a/tools/testing/selftests/livepatch/test_modules/test_klp_callbacks_dem= o2.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/tools/testing/selftests/livepatch/test_modules/test_klp_callba= cks_mod.c b/tools/testing/selftests/livepatch/test_modules/test_klp_callbac= ks_mod.c deleted file mode 100644 index 8fbe645b1c2c..000000000000 --- a/tools/testing/selftests/livepatch/test_modules/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"); --=20 2.47.1 From nobody Mon Dec 15 21:31:33 2025 Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.223.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 401922500B0; Wed, 15 Jan 2025 08:27:42 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.135.223.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929665; cv=none; b=GfsQMEoYsgB6nM5mZmac0l6yjhtCt+Z5rzJEI9pof2Z8s8cUVuRsh6CK/qRZFKwucZsVJqaezqU8YzbHSWYrBO4zRgc04vnBY+nYoNOFF5ruRppvDuxMTlRTqipjR0sE26EiRWQ95IL+boefMpQJjBlt61brfx0ivNl+iE89Sfo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929665; c=relaxed/simple; bh=lfpH+SYQPvS8eYAQu04yBbzDPitKpt3QeMnR9mGQzog=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=UPVJYqzy744bCv9iEFi79toBEP4oQ7TKejT95InzrN7IT63Fb12OmdKW2KvNmi56TA7XEep5qzWec7IVTiQUdPUpmxRXa2z2UaL36wyN8DajY9Y6Pv8A9OzNCW1wSw6rZXPBsrq0xNJMY3rpvA5cfsecVMD3z3MPrIvhy8BkE1o= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com; spf=pass smtp.mailfrom=suse.com; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=PJcg60+/; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=PJcg60+/; arc=none smtp.client-ip=195.135.223.131 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=suse.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="PJcg60+/"; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="PJcg60+/" Received: from pathway.suse.cz (unknown [10.100.201.202]) by smtp-out2.suse.de (Postfix) with ESMTP id 4B6C81F44E; Wed, 15 Jan 2025 08:27:40 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929660; 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=pbWoPeMJynLa3RnHI5QerS0wmhJTAad8HekLmHmbViA=; b=PJcg60+/e7TR+QFKe4NCltQr/V/efw3hrNLou0On2UEkY8FqibVtFeElNyoM8HS3zq3kg7 AHBE56iMz3IJWUqKeTswM2hk0aqjMJ/3kV9BXvxz8mnopl5g1jHQsR4uJ32pcCZM2tA3Yl FZ6WVwWrpAUaxfNqKBafn1BzUKRcgyE= Authentication-Results: smtp-out2.suse.de; none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929660; 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=pbWoPeMJynLa3RnHI5QerS0wmhJTAad8HekLmHmbViA=; b=PJcg60+/e7TR+QFKe4NCltQr/V/efw3hrNLou0On2UEkY8FqibVtFeElNyoM8HS3zq3kg7 AHBE56iMz3IJWUqKeTswM2hk0aqjMJ/3kV9BXvxz8mnopl5g1jHQsR4uJ32pcCZM2tA3Yl FZ6WVwWrpAUaxfNqKBafn1BzUKRcgyE= 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: [PATCH v1 17/19] samples/livepatch: Replace sample module with obsolete per-object callbacks Date: Wed, 15 Jan 2025 09:24:29 +0100 Message-ID: <20250115082431.5550-18-pmladek@suse.com> X-Mailer: git-send-email 2.47.1 In-Reply-To: <20250115082431.5550-1-pmladek@suse.com> References: <20250115082431.5550-1-pmladek@suse.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Spam-Level: X-Spamd-Result: default: False [-6.80 / 50.00]; REPLY(-4.00)[]; BAYES_HAM(-3.00)[100.00%]; MID_CONTAINS_FROM(1.00)[]; NEURAL_HAM_LONG(-1.00)[-1.000]; R_MISSING_CHARSET(0.50)[]; NEURAL_HAM_SHORT(-0.20)[-1.000]; MIME_GOOD(-0.10)[text/plain]; RCVD_COUNT_ZERO(0.00)[0]; MIME_TRACE(0.00)[0:+]; ARC_NA(0.00)[]; TO_DN_SOME(0.00)[]; RCPT_COUNT_SEVEN(0.00)[7]; DKIM_SIGNED(0.00)[suse.com:s=susede1]; FUZZY_BLOCKED(0.00)[rspamd.com]; FROM_HAS_DN(0.00)[]; R_RATELIMIT(0.00)[to_ip_from(RLj3e56pwiuh8u4wxetmhsq5s5)]; FROM_EQ_ENVFROM(0.00)[]; TO_MATCH_ENVRCPT_ALL(0.00)[]; DBL_BLOCKED_OPENRESOLVER(0.00)[pathway.suse.cz:helo,suse.com:email,suse.com:mid] X-Spam-Score: -6.80 X-Spam-Flag: NO Content-Type: text/plain; charset="utf-8" The per-object callbacks have been deprecated in favor of per-state callbacks and are scheduled for removal. Remove the sample modules with the obsolete per-object callbacks. Instead add new sample modules based on the new selftests for per-state callbacks. The test modules are just renamed to follow the naming scheme of the existing sample modules. Also only one version of the test module and one version of the livepatch module is built. Signed-off-by: Petr Mladek --- samples/livepatch/Makefile | 5 +- .../livepatch/livepatch-callbacks-busymod.c | 60 --- samples/livepatch/livepatch-callbacks-demo.c | 196 --------- samples/livepatch/livepatch-callbacks-mod.c | 41 -- samples/livepatch/livepatch-speaker-fix.c | 376 ++++++++++++++++++ samples/livepatch/livepatch-speaker-mod.c | 203 ++++++++++ samples/livepatch/livepatch-speaker.h | 15 + 7 files changed, 596 insertions(+), 300 deletions(-) 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 create mode 100644 samples/livepatch/livepatch-speaker-fix.c create mode 100644 samples/livepatch/livepatch-speaker-mod.c create mode 100644 samples/livepatch/livepatch-speaker.h diff --git a/samples/livepatch/Makefile b/samples/livepatch/Makefile index 9f853eeb6140..b3e2bc965a0c 100644 --- a/samples/livepatch/Makefile +++ b/samples/livepatch/Makefile @@ -3,6 +3,5 @@ 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 +obj-$(CONFIG_SAMPLE_LIVEPATCH) +=3D livepatch-speaker-mod.o +obj-$(CONFIG_SAMPLE_LIVEPATCH) +=3D livepatch-speaker-fix.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"); diff --git a/samples/livepatch/livepatch-speaker-fix.c b/samples/livepatch/= livepatch-speaker-fix.c new file mode 100644 index 000000000000..bfeb6db42e33 --- /dev/null +++ b/samples/livepatch/livepatch-speaker-fix.c @@ -0,0 +1,376 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2024 SUSE + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include + +#include "livepatch-speaker.h" + +/** + * test_klp_speaker_livepatch - test livepatch for testing various livepat= ching + * features. + * + * The livepatch modifies the behavior of a virtual speaker provided by + * the module test_klp_speaker. It can do: + * + * - Improve the speaker's greeting from "Hello, World!" to + * "Ladies and gentleman, ..." + * + * - Support more speaker modules, see __lp_speaker_welcome(). + * + * - Livepatch block_doors_func() which can block the transition. + * + * - Support testing of more shadow variables and state callbacks. see + * "applause", and "applause2" module parameters. + * + * - Allow to enable the atomic replace via "replace" parameter. + */ + +#define APPLAUSE_NUM 2 +#define APPLAUSE_START_ID 10 +#define APPLAUSE_STR_SIZE 16 +#define APPLAUSE_IDX_STR_SIZE 8 + +/* associate the shadow variable with NULL address */; +static void *shadow_object =3D NULL; + +static bool add_applause[APPLAUSE_NUM]; +module_param_named(applause, add_applause[0], bool, 0400); +MODULE_PARM_DESC(applause, "Use shadow variable to add applause (default= =3Dfalse)"); +module_param_named(applause2, add_applause[1], bool, 0400); +MODULE_PARM_DESC(applause2, "Use shadow variable to add 2nd applause (defa= ult=3Dfalse)"); + +static int pre_patch_ret; +module_param(pre_patch_ret, int, 0400); +MODULE_PARM_DESC(pre_patch_ret, "Allow to force failure for the pre_patch = callback (default=3D0)"); + +static bool replace; +module_param(replace, bool, 0400); +MODULE_PARM_DESC(replace, "Enable the atomic replace feature when loading = the livepatch. (default=3Dfalse)"); + +/* Conversion between the index to the @add_applause table and state ID. */ +#define __idx_to_state_id(idx) (idx + APPLAUSE_START_ID) +#define __state_id_to_idx(state_id) (state_id - APPLAUSE_START_ID) + +static void __lp_speaker_welcome(const char *caller_func, + const char *speaker_id, + const char *context) +{ + char entire_applause[APPLAUSE_NUM * APPLAUSE_STR_SIZE + 1] =3D ""; + int idx, ret; + int len =3D 0; + + for (idx =3D 0; idx < APPLAUSE_NUM ; idx++) { + const char *applause; + + applause =3D (char *)klp_shadow_get(shadow_object, + __idx_to_state_id(idx)); + + if (applause) { + ret =3D strscpy(entire_applause + len, applause, + sizeof(entire_applause) - len); + if (ret < 0) { + pr_warn("Too small buffer for entire_applause. Truncating...\n"); + len =3D sizeof(entire_applause) - 1; + break; + } + len +=3D ret; + } + } + + if (len) { + ret =3D strscpy(entire_applause + len, " ", + sizeof(entire_applause) - len); + if (ret < 0) { + pr_warn("Too small buffer for entire_applause. Truncating...\n"); + len =3D sizeof(entire_applause) - 1; + } else { + len +=3D ret; + } + } + + pr_info("%s%s: %sLadies and gentleman, ...%s\n", + caller_func, speaker_id, entire_applause, context); +} + +static void lp_speaker_welcome(const char *context) +{ + __lp_speaker_welcome(__func__, "", context); +} + +static char *state_id_to_idx_str(char *buf, size_t size, + unsigned long state_id) +{ + int idx; + + idx =3D __state_id_to_idx(state_id); + + if (idx < 0 || idx >=3D APPLAUSE_NUM) { + pr_err("%s: Applause table index out of scope: %d\n", __func__, idx); + return ""; + } + + if (idx =3D=3D 0) + return ""; + + snprintf(buf, size, "%d", idx + 1); + return buf; +} + +static int allocate_applause(unsigned long id) +{ + char idx_str[APPLAUSE_IDX_STR_SIZE]; + 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, id, + APPLAUSE_STR_SIZE, GFP_KERNEL, + NULL, NULL); + + if (!applause) { + pr_err("%s: failed to allocated shadow variable for storing an applause = description\n", + __func__); + return -ENOMEM; + } + + snprintf(applause, APPLAUSE_STR_SIZE, "[%s]", + state_id_to_idx_str(idx_str, sizeof(idx_str), id)); + + return 0; +} + +static void set_applause(unsigned long id) +{ + char idx_str[APPLAUSE_IDX_STR_SIZE]; + char *applause; + + applause =3D (char *)klp_shadow_get(shadow_object, id); + if (!applause) { + pr_err("%s: failed to get shadow variable with the applause description:= %lu\n", + __func__, id); + return; + } + + snprintf(applause, APPLAUSE_STR_SIZE, "[APPLAUSE%s]", + state_id_to_idx_str(idx_str, sizeof(idx_str), id)); +} + +static void unset_applause(unsigned long id) +{ + char idx_str[APPLAUSE_IDX_STR_SIZE]; + char *applause; + + applause =3D (char *)klp_shadow_get(shadow_object, id); + if (!applause) { + pr_err("%s: failed to get shadow variable with the applause description:= %lu\n", + __func__, id); + return; + } + + snprintf(applause, APPLAUSE_STR_SIZE, "[%s]", + state_id_to_idx_str(idx_str, sizeof(idx_str), id)); +} + +static void check_applause(unsigned long id) +{ + char *applause; + + applause =3D (char *)klp_shadow_get(shadow_object, id); + if (!applause) { + pr_err("%s: failed to get shadow variable with the applause description:= %lu\n", + __func__, id); + return; + } +} + +/* Executed before patching when the state is being enabled. */ +static int applause_pre_patch_callback(struct klp_patch *patch, struct klp= _state *state) +{ + pr_info("%s: state %lu\n", __func__, state->id); + + if (pre_patch_ret) { + pr_err("%s: forcing err: %pe\n", __func__, ERR_PTR(pre_patch_ret)); + return pre_patch_ret; + } + + return allocate_applause(state->id); +} + +/* Executed after patching when the state being enabled. */ +static void applause_post_patch_callback(struct klp_patch *patch, struct k= lp_state *state) +{ + pr_info("%s: state %lu\n", __func__, state->id); + set_applause(state->id); +} + +/* Executed before unpatching when the state is being disabled. */ +static void applause_pre_unpatch_callback(struct klp_patch *patch, struct = klp_state *state) +{ + pr_info("%s: state %lu\n", __func__, state->id); + unset_applause(state->id); +} + +/* Executed after unpatching when the state is being disabled. */ +static void applause_post_unpatch_callback(struct klp_patch *patch, struct= klp_state *state) +{ + /* + * Just check that the shadow variable still exist. It will be + * freed automatically because state->is_shadow is set. + */ + pr_info("%s: state %lu (nope)\n", __func__, state->id); + check_applause(state->id); +} + +/* + * The shadow_dtor callback is not really needed. The space for the string + * has been allocated as part of struct klp_shadow. The callback is added + * just to check that the shadow variable is freed automatically because of + * state->is_shadow is set. + */ +static void applause_shadow_dtor(void *obj, void *shadow_data) +{ + char *applause =3D (char *)shadow_data; + + /* + * It would be better to print the related state->id. And it would be + * easy to get the pointer to struct klp_shadow via the @shadow_data + * pointer. But struct klp_state is not defined in a public header. + */ + pr_info("%s: freeing applause %s (nope)\n", + __func__, applause); +} + +static void __lp_block_doors_func(struct work_struct *work, const char *ca= ller_func, + const char *speaker_id) +{ + struct hall *hall =3D container_of(work, struct hall, block_doors_work); + + pr_info("%s: Going to block doors%s (fixed).\n", caller_func, speaker_id); + hall->do_block_doors(); +} + +/* + * Prevent tail call optimizations to make sure that this function + * appears in the backtrace and can block the disable transition. + */ +__attribute__((__optimize__("no-optimize-sibling-calls"))) +static void lp_block_doors_func(struct work_struct *work) +{ + __lp_block_doors_func(work, __func__, ""); +} + +static struct klp_func livepatch_speaker_mod_funcs[] =3D { + { + .old_name =3D "speaker_welcome", + .new_func =3D lp_speaker_welcome, + }, + { + .old_name =3D "block_doors_func", + .new_func =3D lp_block_doors_func, + }, + { } +}; + +static struct klp_object objs[] =3D { + { + .name =3D "livepatch_speaker_mod", + .funcs =3D livepatch_speaker_mod_funcs, + }, + { } +}; + +static struct klp_patch patch =3D { + .mod =3D THIS_MODULE, + .objs =3D objs, +}; + + +/* + * The array with states is dynamically allocated depending on which states + * are enabled on the command line. + */ +static struct klp_state *applause_states; + +static int applause_init(void) +{ + int idx, idx_allowed, id, enabled_cnt; + + enabled_cnt =3D 0; + + for (idx =3D 0, id =3D APPLAUSE_START_ID, enabled_cnt =3D 0; + idx < APPLAUSE_NUM; + idx++, id++) { + if (add_applause[idx]) + enabled_cnt++; + } + + if (enabled_cnt) { + /* Allocate one more state as the trailing entry. */ + applause_states =3D + kzalloc(sizeof(applause_states[0]) * (enabled_cnt + 1), GFP_KERNEL); + if (!applause_states) + return -ENOMEM; + + patch.states =3D applause_states; + + for (idx =3D 0, idx_allowed =3D 0; + idx < APPLAUSE_NUM; + idx++) { + struct klp_state *state; + + if (!add_applause[idx]) + continue; + + if (idx_allowed >=3D enabled_cnt) { + pr_warn("Too many enabled applause states\n"); + continue; + } + + state =3D &applause_states[idx_allowed++]; + + state->id =3D __idx_to_state_id(idx); + state->is_shadow =3D true; + state->callbacks.pre_patch =3D applause_pre_patch_callback; + state->callbacks.post_patch =3D applause_post_patch_callback; + state->callbacks.pre_unpatch =3D applause_pre_unpatch_callback; + state->callbacks.post_unpatch =3D applause_post_unpatch_callback; + state->callbacks.shadow_dtor =3D applause_shadow_dtor; + } + } + + return 0; +} + +static int livepatch_speaker_fix_init(void) +{ + int err; + + err =3D applause_init(); + if (err) + return err; + + if (replace) + patch.replace =3D true; + + return klp_enable_patch(&patch); +} + +static void livepatch_speaker_fix_exit(void) +{ + kfree(applause_states); +} + +module_init(livepatch_speaker_fix_init); +module_exit(livepatch_speaker_fix_exit); +MODULE_LICENSE("GPL"); +MODULE_INFO(livepatch, "Y"); +MODULE_DESCRIPTION("Livepatch sample: livepatch speaker module fix"); diff --git a/samples/livepatch/livepatch-speaker-mod.c b/samples/livepatch/= livepatch-speaker-mod.c new file mode 100644 index 000000000000..2a3ff5a26a59 --- /dev/null +++ b/samples/livepatch/livepatch-speaker-mod.c @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2024 SUSE + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include + +#include "livepatch-speaker.h" + + +#ifndef SPEAKER_ID +#define SPEAKER_ID "" +#endif + +/** + * test_klp_speaker - test module for testing misc livepatching features + * + * The module provides a virtual speaker who can do: + * + * - Start a show with a greeting, see speaker_welcome(). + * + * - Log the greeting by reading the "welcome" module parameter, see + * welcome_get(). + * + * - Reuse the module source for more speakers, see SPEAKER_ID. + * + * - Add "block_doors" parameter which could block the livepatch transi= tion. + * The stalled function is offloaded to a workqueue so that it does not + * block the module load. The transition can be unblocked by setting + * the parameter value back to "0" via the sysfs interface. + */ + +noinline +static void speaker_welcome(const char *context) +{ + pr_info("%s%s: Hello, World!%s\n", __func__, SPEAKER_ID, context); +} + +static int welcome_get(char *buffer, const struct kernel_param *kp) +{ + 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."); + +static DECLARE_COMPLETION(started_blocking_doors); +static bool block_doors; +static bool show_over; + +noinline +static void do_block_doors(void) +{ + pr_info("%s: Started blocking doors.\n", __func__); + complete(&started_blocking_doors); + + while (READ_ONCE(block_doors) && !READ_ONCE(show_over)) { + /* + * Busy-wait until the parameter "block_doors" is cleared or + * until the module gets unloaded. + */ + msleep(20); + } + + if (!block_doors) { + pr_info("%s: Stopped blocking doors.\n", __func__); + /* + * Show how the livepatched message looks in the process which + * blocked the transition. + */ + speaker_welcome(" <--- from blocked doors"); + } +} + +static struct hall hall =3D { + .do_block_doors =3D do_block_doors, +}; + +/* + * Prevent tail call optimizations to make sure that this function + * appears in the backtrace and blocks the transition. + */ +__attribute__((__optimize__("no-optimize-sibling-calls"))) +static void block_doors_func(struct work_struct *work) +{ + struct hall *hall =3D container_of(work, struct hall, block_doors_work); + + pr_info("%s: Going to block doors%s.\n", __func__, SPEAKER_ID); + hall->do_block_doors(); +} + +/* + * The work must be initialized when "bool" parameter is proceed + * during the module load. Which is done before calling the module init + * callback. + * + * Also it must be initialized even when the parameter was not used because + * the work must be flushed in the module exit callback. + */ +static void block_doors_work_init(struct hall *hall) +{ + static bool block_doors_work_initialized; + + if (block_doors_work_initialized) + return; + + INIT_WORK(&hall->block_doors_work, block_doors_func); + block_doors_work_initialized =3D true; +} + +static int block_doors_get(char *buffer, const struct kernel_param *kp) +{ + if (block_doors) + pr_info("The doors are blocked.\n"); + else + pr_info("The doors are not blocked.\n"); + + return 0; +} + +static int block_doors_set(const char *val, const struct kernel_param *kp) +{ + bool block; + int ret; + + ret =3D kstrtobool(val, &block); + if (ret) + return ret; + + if (block =3D=3D block_doors) { + if (block) { + pr_err("%s: The doors are already blocked.\n", __func__); + return -EBUSY; + } + + pr_err("%s: The doors are not being blocked.\n", __func__); + return -EINVAL; + } + + /* + * Update the global value before scheduling the work so that it + * stays blocked. + */ + block_doors =3D block; + if (block) { + init_completion(&started_blocking_doors); + block_doors_work_init(&hall); + + schedule_work(&hall.block_doors_work); + + /* + * To synchronize kernel messages, hold this callback from + * exiting until the work function's entry message has got + * printed. + */ + wait_for_completion(&started_blocking_doors); + } else { + flush_work(&hall.block_doors_work); + } + + return 0; +} + +static const struct kernel_param_ops block_doors_ops =3D { + .set =3D block_doors_set, + .get =3D block_doors_get, +}; + +module_param_cb(block_doors, &block_doors_ops, NULL, 0600); +MODULE_PARM_DESC(block_doors, "Block doors so that the audience could not = enter. It blocks the livepatch transition. (default=3Dfalse)"); + +static int livepatch_speaker_mod_init(void) +{ + pr_info("%s\n", __func__); + + block_doors_work_init(&hall); + + return 0; +} + +static void livepatch_speaker_mod_exit(void) +{ + pr_info("%s\n", __func__); + + /* Make sure that do_block_doors() is not running. */ + WRITE_ONCE(show_over, true); + flush_work(&hall.block_doors_work); +} + +module_init(livepatch_speaker_mod_init); +module_exit(livepatch_speaker_mod_exit); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Livepatch sample: test functions"); diff --git a/samples/livepatch/livepatch-speaker.h b/samples/livepatch/live= patch-speaker.h new file mode 100644 index 000000000000..a0e65c27ea56 --- /dev/null +++ b/samples/livepatch/livepatch-speaker.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _LIVEPATCH_SPEAKER_H_ +#define _LIVEPATCH_SPEAKER_H_ + +#include + +typedef void (*do_block_doors_t)(void); + +struct hall { + struct work_struct block_doors_work; + do_block_doors_t do_block_doors; +}; + +#endif // _LIVEPATCH_SPEAKER_H_ --=20 2.47.1 From nobody Mon Dec 15 21:31:33 2025 Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.223.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 623AB28EC9E; Wed, 15 Jan 2025 08:27:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.135.223.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929675; cv=none; b=b2hvZq4ai7QcIc6wuWLkhFnmknlEPkrKjjxqlIng+bb7MDJP7iErjS+VdA/gJkRn71P3aTgcMuZ7S0811Nzfmx3c2bBcLI7d2l7GtLkr0ge24y0nx3iN/gsGwo/ixBpkLml4anfQpFetyeVmbqnflqaa1oVHqCejGzK7Aso8NMw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929675; c=relaxed/simple; bh=PpDKmn0hdf6mBfTap3LxX4bJm+kS1vWMRUWJugkssxs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=oI67YO+aKtMOOrhsTo7fne80g+AsvaoKUI+7euInmg7wZBoibGUNMWHkoExi4QC7DVYRNive+VJkzyH1nEQDaKt1BorGR+a55ME5lT125LvfuOu1SKlvc2gPTWiQXJb6tGusZG9Ba/gpORmUD+PQyo+7wfaF17V90L4Xjds+6B0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com; spf=pass smtp.mailfrom=suse.com; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=o/q0YUj/; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=o/q0YUj/; arc=none smtp.client-ip=195.135.223.131 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=suse.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="o/q0YUj/"; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="o/q0YUj/" Received: from pathway.suse.cz (unknown [10.100.201.202]) by smtp-out2.suse.de (Postfix) with ESMTP id AE2F81F37C; Wed, 15 Jan 2025 08:27:50 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929670; 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=8aX20gM5aqWvgQjDs7ShfHHgki1yOgBmRAA7bLQJt8w=; b=o/q0YUj/MdMF00+8Zw7ydxqxCMLKAgGRUmPgdhJuGg+PMNffHn55qy0sf6IuJYNPwkY844 GJM+Xf7k692wZwLZXM7A8KLSCIQ2KkVWAkFn1kLl3WOMjJqLZYGhqp8zK6fKRnbRC0AX1h 3IjGhdZ9UIcY9c5Cdj0be+1iZESHMxE= Authentication-Results: smtp-out2.suse.de; none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929670; 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=8aX20gM5aqWvgQjDs7ShfHHgki1yOgBmRAA7bLQJt8w=; b=o/q0YUj/MdMF00+8Zw7ydxqxCMLKAgGRUmPgdhJuGg+PMNffHn55qy0sf6IuJYNPwkY844 GJM+Xf7k692wZwLZXM7A8KLSCIQ2KkVWAkFn1kLl3WOMjJqLZYGhqp8zK6fKRnbRC0AX1h 3IjGhdZ9UIcY9c5Cdj0be+1iZESHMxE= 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: [PATCH v1 18/19] Documentation/livepatch: Update documentation for state, callbacks, and shadow variables Date: Wed, 15 Jan 2025 09:24:30 +0100 Message-ID: <20250115082431.5550-19-pmladek@suse.com> X-Mailer: git-send-email 2.47.1 In-Reply-To: <20250115082431.5550-1-pmladek@suse.com> References: <20250115082431.5550-1-pmladek@suse.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Spam-Score: -6.80 X-Spamd-Result: default: False [-6.80 / 50.00]; REPLY(-4.00)[]; BAYES_HAM(-3.00)[100.00%]; MID_CONTAINS_FROM(1.00)[]; NEURAL_HAM_LONG(-1.00)[-1.000]; R_MISSING_CHARSET(0.50)[]; NEURAL_HAM_SHORT(-0.20)[-1.000]; MIME_GOOD(-0.10)[text/plain]; RCVD_COUNT_ZERO(0.00)[0]; MIME_TRACE(0.00)[0:+]; ARC_NA(0.00)[]; TO_DN_SOME(0.00)[]; RCPT_COUNT_SEVEN(0.00)[7]; DKIM_SIGNED(0.00)[suse.com:s=susede1]; FUZZY_BLOCKED(0.00)[rspamd.com]; FROM_HAS_DN(0.00)[]; R_RATELIMIT(0.00)[to_ip_from(RLj3e56pwiuh8u4wxetmhsq5s5)]; FROM_EQ_ENVFROM(0.00)[]; TO_MATCH_ENVRCPT_ALL(0.00)[]; DBL_BLOCKED_OPENRESOLVER(0.00)[pathway.suse.cz:helo,suse.com:mid,suse.com:email] X-Spam-Flag: NO X-Spam-Level: Content-Type: text/plain; charset="utf-8" This commit updates the livepatch documentation to reflect recent changes in the behavior of states, callbacks, and shadow variables. Key changes include: - Per-state callbacks replace per-object callbacks, invoked only when a livepatch introduces or removes a state. - Shadow variable lifetime is now tied to the corresponding livepatch state lifetime. - The "version" field in `struct klp_state` has been replaced with the "block_disable" flag for improved compatibility handling. - The "data" field has been removed from `struct klp_state`; shadow variables are now the recommended way to store state-related data. This update ensures the documentation accurately describes the current livepatch functionality. Signed-off-by: Petr Mladek --- Documentation/livepatch/api.rst | 2 +- Documentation/livepatch/callbacks.rst | 166 ++++++++++-------- Documentation/livepatch/index.rst | 4 +- Documentation/livepatch/shadow-vars.rst | 47 ++++- Documentation/livepatch/system-state.rst | 208 ++++++++--------------- 5 files changed, 218 insertions(+), 209 deletions(-) diff --git a/Documentation/livepatch/api.rst b/Documentation/livepatch/api.= rst index 78944b63d74b..9535138bd52d 100644 --- a/Documentation/livepatch/api.rst +++ b/Documentation/livepatch/api.rst @@ -27,4 +27,4 @@ Object Types =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =20 .. kernel-doc:: include/linux/livepatch.h - :identifiers: klp_patch klp_object klp_func klp_callbacks klp_state + :identifiers: klp_patch klp_object klp_func klp_state_callbacks klp_sta= te diff --git a/Documentation/livepatch/callbacks.rst b/Documentation/livepatc= h/callbacks.rst index 914445784ce4..c619e024d92d 100644 --- a/Documentation/livepatch/callbacks.rst +++ b/Documentation/livepatch/callbacks.rst @@ -3,9 +3,9 @@ =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =20 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: +to execute callback functions before and after transitioning the system. +They can be considered a **power feature** that **extends livepatching +abilities** to include: =20 - Safe updates to global data =20 @@ -17,105 +17,137 @@ In most cases, (un)patch callbacks will need to be us= ed in conjunction with memory barriers and kernel synchronization primitives, like mutexes/spinlocks, or even stop_machine(), to avoid concurrency issues. =20 -1. Motivation -=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =20 -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 +1. Callback types =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =20 -Callbacks can be registered for the following livepatch actions: +The pointers to the callbacks are stored in `struct klp_state_callbacks`. +This structure is bundled into `struct klp_state`. The connection with +the state helps to maintain the lifetime of the changes made by the callba= cks, +see also Documentation/livepatch/system-state.rst =20 - * Pre-patch - - before a klp_object is patched +The `struct klp_state_callbacks` allows to define the following +callbacks. All of them are optional: =20 - * Post-patch - - after a klp_object has been patched and is active - across all tasks +*pre_patch()* =20 - * Pre-unpatch - - before a klp_object is unpatched (ie, patched code is - active), used to clean up post-patch callback - resources + - Called only when the related state is being enabled at the beginning + of the transition. This is the only callback with a return value. + The livepatch module won't be loaded when it returns an error code. + +*post_patch()* + + - Called only when the related state is being enabled at the end + of the transition. + +*pre_unpatch()* + + - Called only when the related state is being disabled at the beginning + of the transition. + +*post_patch()* + + - Called only when the related state is being disabled at the end + of the transition. + +*shadow_dtor()* + + - Destruct callback which is used for releasing obsolete shadow variables + using the same *@id*. They are freed right after calling *post_unpatch= ()* + callback. =20 - * 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 =20 3. How it works =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =20 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 +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 +executed. Typical use cases pair a patch handler that acquires and configures resources with an unpatch handler tears down and releases those same resources. =20 -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. +A callback is only executed when the related livepatch introduces or +removes the state. Specifically, the *pre_patch()* and *post_patch()* +callbacks are not called if any already enabled livepatch supports +the given state, regardless of whether atomic replacement is used or +livepatches are installed in parallel. Similarly, the *pre_unpatch()* +and *post_unpatch()* callbacks are called during atomic replacement +only for states from currently enabled livepatches that will no longer +be supported by the new livepatch. =20 -The pre-patch callback, if specified, is expected to return a status +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 +to the livepatching core that the requested state could not be enabled +a safe way 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: +*pre_patch()* callback returns failure, the kernel's module loader will +refuse to load the livepatch. =20 - - Refuse to load a livepatch, if the livepatch is loaded after - targeted code. +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. =20 - or: =20 - - Refuse to load a module, if the livepatch was already successfully - loaded. +4. Expected usage +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =20 -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. +The expected role of each callback is as follows: =20 -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). +*pre_patch()* + + - Allocate memory, using a shadow variable, when necessary. The allocati= on + might fail and *pre_patch()* is the only callback that could stop load= ing + of the livepatch. + + - Do any other preparatory action that is needed by the new code even + before the transition gets finished. For example, initialize + the allocated memory. + + The system state itself is typically modified in *post_patch()* + when the entire system is able to handle it. + + - Clean up its own mess in case of error. It might be done by a custom + code or by calling *post_unpatch()* explicitly. + +*post_patch()* + + - Do the actual system state modification. Eventually allow + the new code to use it. + +*pre_unpatch()* + + - Prevent the code, added by the livepatch, relying on the system + state change. + + - Revert the system state modification.. + +*post_unpatch()* + + - Remove any not longer needed setting or data. Note that all shadow + variables using the same *@id* are freed automatically. =20 -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. =20 4. Use cases =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =20 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. +samples/livepatch/ directory. These samples were modified for use in +kselftests and can be found in the tools/testing/selftests/livepatch/ +directory. =20 Global data update ------------------ =20 -A pre-patch callback can be useful to update a global variable. For +A *pre_patch()* callback can be useful to update a global variable. For example, commit 75ff39ccc1bd ("tcp: make challenge acks less predictable") changes a global sysctl, as well as patches the tcp_send_challenge_ack() function. =20 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, +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. =20 @@ -123,11 +155,11 @@ __init and probe function patches support ----------------------------------------- =20 Although __init and probe functions are not directly livepatch-able, it -may be possible to implement similar updates via pre/post-patch +may be possible to implement similar updates via *pre_patch()*/*post_patch= ()* callbacks. =20 -The commit 48900cb6af42 ("virtio-net: drop NETIF_F_FRAGLIST") change the w= ay 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 +The commit 48900cb6af42 ("virtio-net: drop NETIF_F_FRAGLIST") change the w= ay +that virtnet_probe() initialized its driver's net_device features. A +*pre_patch()*/*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..8f799c0b85f3 100644 --- a/Documentation/livepatch/index.rst +++ b/Documentation/livepatch/index.rst @@ -8,11 +8,11 @@ Kernel Livepatching :maxdepth: 1 =20 livepatch - callbacks cumulative-patches module-elf-format - shadow-vars system-state + callbacks + shadow-vars reliable-stacktrace api =20 diff --git a/Documentation/livepatch/shadow-vars.rst b/Documentation/livepa= tch/shadow-vars.rst index 7a7098bfb5c8..702d38cd7571 100644 --- a/Documentation/livepatch/shadow-vars.rst +++ b/Documentation/livepatch/shadow-vars.rst @@ -42,7 +42,7 @@ They also allow to call a custom constructor function whe= n a non-zero value is needed. Callers should provide whatever mutual exclusion is required. =20 -Note that the constructor is called under klp_shadow_lock spinlock. It all= ows +Note that the constructor is called under *klp_shadow_lock* spinlock. It a= llows to do actions that can be done only once when a new variable is allocated. =20 * klp_shadow_get() - retrieve a shadow variable data pointer @@ -90,8 +90,49 @@ to do actions that can be done only once when a new vari= able is allocated. - call destructor function if defined - free shadow variable =20 +2. Lifetime +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =20 -2. Use cases +Shadow variables are allocated only when there is code that can use them. +This typically occurs when the entire system is livepatched and all +processes are able to hanle them. + +To ensure proper management, shadow variables are associated with a specif= ic +system state using a unique identifier (*id*). This association governs +their lifecycle: + + - The *pre_patch()* callback can be used to allocate the shadow variable. + + - The *post_patch()* callback can be used to enable the usage of the shad= ow + variable system wide. + + - The *pre_unpatch()* callback can be used to disable the usage of the sh= adow + variable system wide. + + - The *post_unpatch()* callback can be used for some clean up before + the obsolete shadow variables are freed. + + - All instances of the shadow variable are automatically freed when their + associated state is removed. This occurs when: + + - The livepatch is disabled. + + - A new cumulative livepatch is applied that no longer supports + the associated state. + +**Important Notes:** + + - **Persistence across Cumulative Livepatches:** Shadow variables are + preserved when a livepatch is replaced by another cumulative + livepatch that still supports the associated state. This ensures + continuity across updates. + + - **Automatic Freeing:** There is no need to explicitly free shadow + variables. The livepatching mechanism handles their freeing + automatically after the associated state has been removed. + + +3. Use cases =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =20 (See the example shadow variable livepatch modules in samples/livepatch/ @@ -211,7 +252,7 @@ doesn't matter what data value the shadow variable hold= s, its existence suggests how to handle the parent object. =20 =20 -3. References +4. References =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =20 * https://github.com/dynup/kpatch diff --git a/Documentation/livepatch/system-state.rst b/Documentation/livep= atch/system-state.rst index 7a3935fd812b..ced7267d42ca 100644 --- a/Documentation/livepatch/system-state.rst +++ b/Documentation/livepatch/system-state.rst @@ -2,166 +2,102 @@ System State Changes =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =20 -Some users are really reluctant to reboot a system. This brings the need -to provide more livepatches and maintain some compatibility between them. +Livepatches provide a way to update running systems without requiring a re= boot. +However, managing compatibility between multiple livepatches can be challe= nging, +especially when they introduce changes that affect system behavior or memo= ry +management. =20 -Maintaining more livepatches is much easier with cumulative livepatches. -Each new livepatch completely replaces any older one. It can keep, -add, and even remove fixes. And it is typically safe to replace any version -of the livepatch with any other one thanks to the atomic replace feature. +Cumulative livepatches simplify this process by completely replacing older +versions with each update. This allows for the addition, modification, and +removal of fixes while maintaining compatibility through atomic replacemen= t. +However, challenges can arise with callbacks and shadow variables. =20 -The problems might come with shadow variables and callbacks. They might -change the system behavior or state so that it is no longer safe to -go back and use an older livepatch or the original kernel code. Also -any new livepatch must be able to detect what changes have already been -done by the already installed livepatches. +Callbacks are functions that can alter system behavior when a livepatch is +applied. Shadow variables associate additional memory with existing data +structures. These modifications need to be reverted when a livepatch is +disabled or replaced with a livepatch not supporting the same state to ens= ure +system stability. =20 -This is where the livepatch system state tracking gets useful. It -allows to: +Unused shadow variables can lead to memory leaks and synchronization issue= s. +If a livepatch is replaced with one that doesn't maintain these variables, +their content may become outdated, potentially causing problems if a future +livepatch attempts to use them again. =20 - - store data needed to manipulate and restore the system state +To address these challenges, the livepatch system employs state tracking. +This mechanism offers several benefits: =20 - - define compatibility between livepatches using a change id - and version + - Callbacks associated with a specific state are called only when that s= tate + is introduced or removed. + + - Shadow variables associated with a state are automatically freed when = that + state is no longer supported. + + - When a livepatch is atomically replaced with another supporting the sa= me + state, associated callbacks are not called, and shadow variables are n= ot + freed, ensuring continuity. + + - State tracking can prevent disabling a livepatch or proceeding with + an atomic replacement if the current livepatch cannot revert the state. + This safeguard is crucial when reverting modifications would be too co= mplex + or risky. + +This approach ensures that changes introduced by livepatches are managed +effectively, minimizing the risk of conflicts and maintaining system stabi= lity. =20 =20 1. Livepatch system state API =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D =20 -The state of the system might get modified either by several livepatch cal= lbacks -or by the newly used code. Also it must be possible to find changes done by -already installed livepatches. +Any livepatch might support an arbitrary number of states. A particular st= ate +represents either a change made by the associated callbacks and/or shadow +variables using the same *@id*. =20 -Each modified state is described by struct klp_state, see -include/linux/livepatch.h. +The states are described by an array of `struct klp_state`, which is usual= ly +statically defined. The `struct klp_state` is defined in +`include/linux/livepatch.h` and provides the following fields: =20 -Each livepatch defines an array of struct klp_states. They mention -all states that the livepatch modifies. +*id* =20 -The livepatch author must define the following two fields for each -struct klp_state: + - A unique, non-zero number that identifies the state. =20 - - *id* +*is_shadow* =20 - - Non-zero number used to identify the affected system state. + - A boolean value indicating whether the state is associated with a shad= ow + variable using the same *@id*. These are automatically freed when + the state is no longer supported after the livepatch transition. + See also Documentation/livepatch/shadow-vars.rst. =20 - - *version* +*block_disable* =20 - - Number describing the variant of the system state change that - is supported by the given livepatch. + - A boolean value that, when set, prevents transitions that would disable + the state. In other words, it indicates that reverting the state is + not supported. =20 -The state can be manipulated using two functions: +*callbacks* =20 - - klp_get_state() + - A `struct klp_state_callbacks` containing (optional) pointers to + callbacks. These are invoked when a livepatch transition introduces + or removes the state. See Documentation/livepatch/callbacks.rst + for more information. =20 - - Get struct klp_state associated with the given livepatch - and state id. - - - klp_get_prev_state() - - - Get struct klp_state associated with the given feature id and - already installed livepatches. =20 2. Livepatch compatibility =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D =20 -The system state version is used to prevent loading incompatible livepatch= es. -The check is done when the livepatch is enabled. The rules are: +The *@block_disable* state flag is used when a livepatch modifies the syst= em +state in a way that cannot be easily or safely reverted. This might be due +to the complexity of the changes or the risk of instability during +the reversion process. =20 - - Any completely new system state modification is allowed. +Preventing the disable operation can also be a strategic decision to save +development costs, as implementing and testing the *pre_unpatch()* and +*post_unpatch()* callbacks can significantly increase resource requirement= s. =20 - - System state modifications with the same or higher version are allowed - for already modified system states. +This flag prevents the livepatch from being disabled and also prevents ato= mic +replacement with a livepatch that does not support this state. These +livepatches are considered incompatible. =20 - - Cumulative livepatches must handle all system state modifications from - already installed livepatches. - - - Non-cumulative livepatches are allowed to touch already modified - system states. - -3. Supported scenarios -=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D - -Livepatches have their life-cycle and the same is true for the system -state changes. Every compatible livepatch has to support the following -scenarios: - - - Modify the system state when the livepatch gets enabled and the state - has not been already modified by a livepatches that are being - replaced. - - - Take over or update the system state modification when is has already - been done by a livepatch that is being replaced. - - - Restore the original state when the livepatch is disabled. - - - Restore the previous state when the transition is reverted. - It might be the original system state or the state modification - done by livepatches that were being replaced. - - - Remove any already made changes when error occurs and the livepatch - cannot get enabled. - -4. Expected usage -=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D - -System states are usually modified by livepatch callbacks. The expected -role of each callback is as follows: - -*pre_patch()* - - - Allocate *state->data* when necessary. The allocation might fail - and *pre_patch()* is the only callback that could stop loading - of the livepatch. The allocation is not needed when the data - are already provided by previously installed livepatches. - - - Do any other preparatory action that is needed by - the new code even before the transition gets finished. - For example, initialize *state->data*. - - The system state itself is typically modified in *post_patch()* - when the entire system is able to handle it. - - - Clean up its own mess in case of error. It might be done by a custom - code or by calling *post_unpatch()* explicitly. - -*post_patch()* - - - Copy *state->data* from the previous livepatch when they are - compatible. - - - Do the actual system state modification. Eventually allow - the new code to use it. - - - Make sure that *state->data* has all necessary information. - - - Free *state->data* from replaces livepatches when they are - not longer needed. - -*pre_unpatch()* - - - Prevent the code, added by the livepatch, relying on the system - state change. - - - Revert the system state modification.. - -*post_unpatch()* - - - Distinguish transition reverse and livepatch disabling by - checking *klp_get_prev_state()*. - - - In case of transition reverse, restore the previous system - state. It might mean doing nothing. - - - Remove any not longer needed setting or data. - -.. note:: - - *pre_unpatch()* typically does symmetric operations to *post_patch()*. - Except that it is called only when the livepatch is being disabled. - Therefore it does not need to care about any previously installed - livepatch. - - *post_unpatch()* typically does symmetric operations to *pre_patch()*. - It might be called also during the transition reverse. Therefore it - has to handle the state of the previously installed livepatches. +The kernel provides no mechanism for detecting incompatibility when atomic +replacement is not used. Livepatch authors must manage incompatibility in +other ways, such as through dependencies between the packages that install +the livepatch modules. --=20 2.47.1 From nobody Mon Dec 15 21:31:33 2025 Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.223.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id BA44523F288; Wed, 15 Jan 2025 08:28:02 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.135.223.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929684; cv=none; b=lS6tMsb4FbdBa7vppNiI2/AHxoukFHnkbl89Alrs/YrMGAZXioTm5lGLkQuFdewLWoI4DT4eulP+eOcq+nximbg/Ijh6swcyhIYn7NiMIbaQN8+xJUsLZQ+V7dtg2QEq3bGJuTWHgLrbHvl7Zx8GQVeywgM2HhpfSt4k936vpBE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736929684; c=relaxed/simple; bh=zTarmIWertlY2GjRWyvop7PsEtBWKIqZkogNylG1ayI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=szTVS3UUxBHJwSRJTsNgXNC32b0FtTzmywu0vf2ImpwMg7gf7xasFUN1U3MQvXa9VKg7IIvOI1docuTO+GptNecmP4fGQk5FTsoAP3QuG5BLO9E0/a2RMk80JDQKf4wkIQXekClBjVBPMAQ+XeH2NefijgkrrsTVlBFoJL62bsU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com; spf=pass smtp.mailfrom=suse.com; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=UoFNmoqv; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=UoFNmoqv; arc=none smtp.client-ip=195.135.223.131 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=suse.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="UoFNmoqv"; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="UoFNmoqv" Received: from pathway.suse.cz (unknown [10.100.201.202]) by smtp-out2.suse.de (Postfix) with ESMTP id 10D611F458; Wed, 15 Jan 2025 08:28:01 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929681; 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=kGnIdKOPLqvU2776tJFJ9PSlaxyqDyXk3XczcXYUoBA=; b=UoFNmoqvs3d13Q6BAQmgjPgvSfpl5+v8xGu2LvPLqCqlR8O9YKSVGcaoaP6KizZ1jSSFVT 4C8T3V3fSywz5UlE/CAufaJpiIFrfVCzCd7ZyrwY3QFurCpab5d5wMg2b1W8IrOthw7hPH NZjX9e0cPojIHmBwBqD3zWREG5Q0V+A= Authentication-Results: smtp-out2.suse.de; none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1736929681; 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=kGnIdKOPLqvU2776tJFJ9PSlaxyqDyXk3XczcXYUoBA=; b=UoFNmoqvs3d13Q6BAQmgjPgvSfpl5+v8xGu2LvPLqCqlR8O9YKSVGcaoaP6KizZ1jSSFVT 4C8T3V3fSywz5UlE/CAufaJpiIFrfVCzCd7ZyrwY3QFurCpab5d5wMg2b1W8IrOthw7hPH NZjX9e0cPojIHmBwBqD3zWREG5Q0V+A= 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: [PATCH v1 19/19] livepatch: Remove obsolete per-object callbacks Date: Wed, 15 Jan 2025 09:24:31 +0100 Message-ID: <20250115082431.5550-20-pmladek@suse.com> X-Mailer: git-send-email 2.47.1 In-Reply-To: <20250115082431.5550-1-pmladek@suse.com> References: <20250115082431.5550-1-pmladek@suse.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Spam-Level: X-Spamd-Result: default: False [-6.80 / 50.00]; REPLY(-4.00)[]; BAYES_HAM(-3.00)[100.00%]; MID_CONTAINS_FROM(1.00)[]; NEURAL_HAM_LONG(-1.00)[-1.000]; R_MISSING_CHARSET(0.50)[]; NEURAL_HAM_SHORT(-0.20)[-1.000]; MIME_GOOD(-0.10)[text/plain]; RCVD_COUNT_ZERO(0.00)[0]; MIME_TRACE(0.00)[0:+]; ARC_NA(0.00)[]; TO_DN_SOME(0.00)[]; RCPT_COUNT_SEVEN(0.00)[7]; DKIM_SIGNED(0.00)[suse.com:s=susede1]; FUZZY_BLOCKED(0.00)[rspamd.com]; FROM_HAS_DN(0.00)[]; R_RATELIMIT(0.00)[to_ip_from(RLj3e56pwiuh8u4wxetmhsq5s5)]; FROM_EQ_ENVFROM(0.00)[]; TO_MATCH_ENVRCPT_ALL(0.00)[]; DBL_BLOCKED_OPENRESOLVER(0.00)[suse.com:email,suse.com:mid,pathway.suse.cz:helo] X-Spam-Score: -6.80 X-Spam-Flag: NO Content-Type: text/plain; charset="utf-8" This commit removes the obsolete per-object callbacks from the livepatch framework. All selftests have been migrated to the new per-state callbacks, making the per-object callbacks redundant. Instead, use the new per-state callbacks. They offer improved semantics by associating callbacks and shadow variables with a specific state, enabling better lifetime management of changes. Signed-off-by: Petr Mladek --- include/linux/livepatch.h | 26 -------------------------- kernel/livepatch/core.c | 29 ----------------------------- kernel/livepatch/core.h | 33 --------------------------------- kernel/livepatch/transition.c | 9 --------- 4 files changed, 97 deletions(-) diff --git a/include/linux/livepatch.h b/include/linux/livepatch.h index d02d7a616338..428300181af3 100644 --- a/include/linux/livepatch.h +++ b/include/linux/livepatch.h @@ -77,35 +77,10 @@ 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) * @funcs: function entries for functions to be patched in the object - * @callbacks: functions to be executed pre/post (un)patching * @kobj: kobject for sysfs resources * @func_list: dynamic list of the function entries * @node: list node for klp_patch obj_list @@ -118,7 +93,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 4d244eef0e53..3d6b8edb3e2b 100644 --- a/kernel/livepatch/core.c +++ b/kernel/livepatch/core.c @@ -983,8 +983,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 @@ -995,10 +993,6 @@ static int __klp_disable_patch(struct klp_patch *patch) =20 klp_states_pre_unpatch(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 @@ -1050,13 +1044,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; - } - ret =3D klp_patch_object(obj); if (ret) { pr_warn("failed to patch object '%s'\n", @@ -1226,14 +1213,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; @@ -1280,25 +1263,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 f3dce9fe9897..c5e9dcf3e453 100644 --- a/kernel/livepatch/transition.c +++ b/kernel/livepatch/transition.c @@ -155,15 +155,6 @@ static void klp_complete_transition(void) klp_states_post_unpatch(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_TRANSITION_PATCHED) - klp_post_patch_callback(obj); - else if (klp_target_state =3D=3D KLP_TRANSITION_UNPATCHED) - klp_post_unpatch_callback(obj); - } - pr_notice("'%s': %s complete\n", klp_transition_patch->mod->name, klp_target_state =3D=3D KLP_TRANSITION_PATCHED ? "patching" : "unpatch= ing"); =20 --=20 2.47.1