mirror of
https://github.com/torvalds/linux.git
synced 2025-12-01 07:26:02 +07:00
When testing the kernel live patching with "modprobe livepatch-sample",
there is a timeout over 15 seconds from "starting patching transition"
to "patching complete". The dmesg command shows "unreliable stack" for
user tasks in debug mode, here is one of the messages:
livepatch: klp_try_switch_task: bash:1193 has an unreliable stack
The "unreliable stack" is because it can not unwind from do_syscall()
to its previous frame handle_syscall(). It should use fp to find the
original stack top due to secondary stack in do_syscall(), but fp is
not used for some other functions, then fp can not be restored by the
next frame of do_syscall(), so it is necessary to save fp if task is
not current, in order to get the stack top of do_syscall().
Here are the call chains:
klp_enable_patch()
klp_try_complete_transition()
klp_try_switch_task()
klp_check_and_switch_task()
klp_check_stack()
stack_trace_save_tsk_reliable()
arch_stack_walk_reliable()
When executing "rmmod livepatch-sample", there exists a similar issue.
With this patch, it takes a short time for patching and unpatching.
Before:
# modprobe livepatch-sample
# dmesg -T | tail -3
[Sat Sep 6 11:00:20 2025] livepatch: 'livepatch_sample': starting patching transition
[Sat Sep 6 11:00:35 2025] livepatch: signaling remaining tasks
[Sat Sep 6 11:00:36 2025] livepatch: 'livepatch_sample': patching complete
# echo 0 > /sys/kernel/livepatch/livepatch_sample/enabled
# rmmod livepatch_sample
rmmod: ERROR: Module livepatch_sample is in use
# rmmod livepatch_sample
# dmesg -T | tail -3
[Sat Sep 6 11:06:05 2025] livepatch: 'livepatch_sample': starting unpatching transition
[Sat Sep 6 11:06:20 2025] livepatch: signaling remaining tasks
[Sat Sep 6 11:06:21 2025] livepatch: 'livepatch_sample': unpatching complete
After:
# modprobe livepatch-sample
# dmesg -T | tail -2
[Tue Sep 16 16:19:30 2025] livepatch: 'livepatch_sample': starting patching transition
[Tue Sep 16 16:19:31 2025] livepatch: 'livepatch_sample': patching complete
# echo 0 > /sys/kernel/livepatch/livepatch_sample/enabled
# rmmod livepatch_sample
# dmesg -T | tail -2
[Tue Sep 16 16:19:36 2025] livepatch: 'livepatch_sample': starting unpatching transition
[Tue Sep 16 16:19:37 2025] livepatch: 'livepatch_sample': unpatching complete
Cc: stable@vger.kernel.org # v6.9+
Fixes: 199cc14cb4 ("LoongArch: Add kernel livepatching support")
Reported-by: Xi Zhang <zhangxi@kylinos.cn>
Signed-off-by: Tiezhu Yang <yangtiezhu@loongson.cn>
Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
123 lines
2.9 KiB
C
123 lines
2.9 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Stack trace management functions
|
|
*
|
|
* Copyright (C) 2022 Loongson Technology Corporation Limited
|
|
*/
|
|
#include <linux/sched.h>
|
|
#include <linux/stacktrace.h>
|
|
#include <linux/uaccess.h>
|
|
|
|
#include <asm/stacktrace.h>
|
|
#include <asm/unwind.h>
|
|
|
|
void arch_stack_walk(stack_trace_consume_fn consume_entry, void *cookie,
|
|
struct task_struct *task, struct pt_regs *regs)
|
|
{
|
|
unsigned long addr;
|
|
struct pt_regs dummyregs;
|
|
struct unwind_state state;
|
|
|
|
if (!regs) {
|
|
regs = &dummyregs;
|
|
|
|
if (task == current) {
|
|
regs->regs[3] = (unsigned long)__builtin_frame_address(0);
|
|
regs->csr_era = (unsigned long)__builtin_return_address(0);
|
|
} else {
|
|
regs->regs[3] = thread_saved_fp(task);
|
|
regs->csr_era = thread_saved_ra(task);
|
|
}
|
|
regs->regs[1] = 0;
|
|
regs->regs[22] = 0;
|
|
}
|
|
|
|
for (unwind_start(&state, task, regs);
|
|
!unwind_done(&state); unwind_next_frame(&state)) {
|
|
addr = unwind_get_return_address(&state);
|
|
if (!addr || !consume_entry(cookie, addr))
|
|
break;
|
|
}
|
|
}
|
|
|
|
int arch_stack_walk_reliable(stack_trace_consume_fn consume_entry,
|
|
void *cookie, struct task_struct *task)
|
|
{
|
|
unsigned long addr;
|
|
struct pt_regs dummyregs;
|
|
struct pt_regs *regs = &dummyregs;
|
|
struct unwind_state state;
|
|
|
|
if (task == current) {
|
|
regs->regs[3] = (unsigned long)__builtin_frame_address(0);
|
|
regs->csr_era = (unsigned long)__builtin_return_address(0);
|
|
regs->regs[22] = 0;
|
|
} else {
|
|
regs->regs[3] = thread_saved_fp(task);
|
|
regs->csr_era = thread_saved_ra(task);
|
|
regs->regs[22] = task->thread.reg22;
|
|
}
|
|
regs->regs[1] = 0;
|
|
|
|
for (unwind_start(&state, task, regs);
|
|
!unwind_done(&state) && !unwind_error(&state); unwind_next_frame(&state)) {
|
|
addr = unwind_get_return_address(&state);
|
|
|
|
/*
|
|
* A NULL or invalid return address probably means there's some
|
|
* generated code which __kernel_text_address() doesn't know about.
|
|
*/
|
|
if (!addr)
|
|
return -EINVAL;
|
|
|
|
if (!consume_entry(cookie, addr))
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Check for stack corruption */
|
|
if (unwind_error(&state))
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
copy_stack_frame(unsigned long fp, struct stack_frame *frame)
|
|
{
|
|
int ret = 1;
|
|
unsigned long err;
|
|
unsigned long __user *user_frame_tail;
|
|
|
|
user_frame_tail = (unsigned long *)(fp - sizeof(struct stack_frame));
|
|
if (!access_ok(user_frame_tail, sizeof(*frame)))
|
|
return 0;
|
|
|
|
pagefault_disable();
|
|
err = (__copy_from_user_inatomic(frame, user_frame_tail, sizeof(*frame)));
|
|
if (err || (unsigned long)user_frame_tail >= frame->fp)
|
|
ret = 0;
|
|
pagefault_enable();
|
|
|
|
return ret;
|
|
}
|
|
|
|
void arch_stack_walk_user(stack_trace_consume_fn consume_entry, void *cookie,
|
|
const struct pt_regs *regs)
|
|
{
|
|
unsigned long fp = regs->regs[22];
|
|
|
|
while (fp && !((unsigned long)fp & 0xf)) {
|
|
struct stack_frame frame;
|
|
|
|
frame.fp = 0;
|
|
frame.ra = 0;
|
|
if (!copy_stack_frame(fp, &frame))
|
|
break;
|
|
if (!frame.ra)
|
|
break;
|
|
if (!consume_entry(cookie, frame.ra))
|
|
break;
|
|
fp = frame.fp;
|
|
}
|
|
}
|