lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250115082431.5550-13-pmladek@suse.com>
Date: Wed, 15 Jan 2025 09:24:24 +0100
From: Petr Mladek <pmladek@...e.com>
To: Josh Poimboeuf <jpoimboe@...nel.org>,
	Miroslav Benes <mbenes@...e.cz>
Cc: Joe Lawrence <joe.lawrence@...hat.com>,
	Nicolai Stange <nstange@...e.de>,
	live-patching@...r.kernel.org,
	linux-kernel@...r.kernel.org,
	Petr Mladek <pmladek@...e.com>
Subject: [PATCH v1 12/19] selftests/livepatch: Convert selftest with blocked transition

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 <pmladek@...e.com>
---
 .../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/testing/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=test_klp_callbacks_busy
 
 setup_config
 
-# 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=Y
-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=Y
-$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] Full 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_TARGET'
-$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 unpatching
-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_LIVE] 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 comparing
 # with the next test-case.
 #
diff --git a/tools/testing/selftests/livepatch/test-state-callbacks.sh b/tools/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"
 
+# 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=1
+read_module_param $MOD_TARGET welcome
+
+load_lp_nowait $MOD_LIVEPATCH applause=1
+# 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=1
+$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=1
+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 unpatching
+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_speaker.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 @@
 
 #include <linux/module.h>
 #include <linux/printk.h>
+#include <linux/delay.h>
+#include <linux/sysfs.h>
+#include <linux/completion.h>
 
 #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 transition.
+ *	The stalled function is offloaded to a workqueue so that it does not
+ *	block the module load.
  */
 
 noinline
@@ -43,16 +50,70 @@ static const struct kernel_param_ops welcome_ops = {
 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);
+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=false)");
+
 static int test_klp_speaker_init(void)
 {
 	pr_info("%s\n", __func__);
 
+	if (block_doors)
+		block_doors_set();
+
 	return 0;
 }
 
 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);
+	}
 }
 
 module_init(test_klp_speaker_init);
diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_speaker_livepatch.c b/tools/testing/selftests/livepatch/test_modules/test_klp_speaker_livepatch.c
index c46c98a3c1e6..76402947c789 100644
--- a/tools/testing/selftests/livepatch/test_modules/test_klp_speaker_livepatch.c
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_speaker_livepatch.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.
  */
 
 #define APPLAUSE_ID 10
@@ -180,11 +182,33 @@ static void applause_shadow_dtor(void *obj, void *shadow_data)
 		__func__, applause);
 }
 
+static void __lp_block_doors_func(struct work_struct *work, const char *caller_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[] = {
 	{
 		.old_name = "speaker_welcome",
 		.new_func = lp_speaker_welcome,
 	},
+	{
+		.old_name = "block_doors_func",
+		.new_func = lp_block_doors_func,
+	},
 	{ }
 };
 
@@ -193,6 +217,10 @@ static struct klp_func test_klp_speaker2_funcs[] = {
 		.old_name = "speaker_welcome",
 		.new_func = lp_speaker2_welcome,
 	},
+	{
+		.old_name = "block_doors_func",
+		.new_func = lp_block_doors_func2,
+	},
 	{ }
 };
 
-- 
2.47.1


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ