On Thu, 27 Aug 2026 10:57:56 +0800 Jinke Han <[email protected]> wrote:
> When I used eBPF to probe the call instructions within a function, > we encountered a kernel crash. > > The ebpf tool probes the 257 offset of the __hrtimer_run_queues > function. > > <__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1) > <__hrtimer_run_queues+254>: mov %r14,%rdi > <__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12> > <__hrtimer_run_queues+263>: mov %eax,%r12d > <__hrtimer_run_queues+266>: xchg %ax,%ax > <__hrtimer_run_queues+268>: mov %r13,%rdi > > The scene of kernel crash is as follows: > > [73665.737181] BUG: unable to handle page fault for address: 00000000000f41c9 > [73665.744253] #PF: supervisor write access in kernel mode > [73665.749643] #PF: error_code(0x0002) - not-present page > [73665.754843] PGD 0 P4D 0 > [73665.757390] Oops: 0002 [#1] SMP NOPTI > [73665.761073] CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P > [73665.782671] RIP: 0010:__hrtimer_run_queues+0x106/0x230 > > Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is > at the 6th byte of the above cs call instruction. Since the cs call > instruction occupies 6 bytes, the exception occurred in the middle of that > call instruction. Ah, OK. So it adds a dummy CS prefix for padding the instruction. Also, please Cc probe patch to [email protected] too. > > The root cause is that when using eBPF tools to probe in the middle of a > function, kprobe with int3 is used as the underlying implementation. > During single-step emulation of the original call instruction, > int3_emulate_call assumes that the probed call instruction is 5 bytes > long. However, the actual CS-prefixed call instruction occupies 6 bytes, > so it constructs an incorrect exception return address. When the CPU > returns from the kprobe handler, the next instruction to be executed is at > the address of the last byte of that CS call instruction. Coincidentally, > starting from that address, the CPU fetches and decodes a completely > different instruction, which ultimately triggers a kernel crash. > > Fix the issue by using the actual instruction length obtained from > the instruction decoder when constructing the exception return > address, rather than relying on the hardcoded CALL_INSN_SIZE macro. > OK, this fixes kprobe local, but for cleanliness, I have a comment. > Cc: [email protected] > Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for > single-step") > Signed-off-by: Jinke Han <[email protected]> > --- > arch/x86/kernel/kprobes/core.c | 4 +++- > 1 file changed, 3 insertions(+), 1 deletion(-) > > diff --git a/arch/x86/kernel/kprobes/core.c b/arch/x86/kernel/kprobes/core.c > index 4e5f8c1736ec..b50e4a60bdfe 100644 > --- a/arch/x86/kernel/kprobes/core.c > +++ b/arch/x86/kernel/kprobes/core.c > @@ -511,9 +511,11 @@ NOKPROBE_SYMBOL(kprobe_emulate_ret); > static void kprobe_emulate_call(struct kprobe *p, struct pt_regs *regs) > { > unsigned long func = regs->ip - INT3_INSN_SIZE + p->ainsn.size; > + unsigned long ip = func; > > func += p->ainsn.rel32; This looks a bit less readability because func is once have a return address and later it becomes call address. Moreover int3_emulate_call() is kept broken :(. > - int3_emulate_call(regs, func); > + int3_emulate_push(regs, ip); > + int3_emulate_jmp(regs, func); What about updating int3_emulate_call() to get ip (=return address) and func? void int3_emulate_call(struct pt_regs *regs, unsigned long ip, unsigned long func) { int3_emulate_push(regs, ip); int3_emulate_jmp(regs, func); } Then in smp_text_poke_int3_handler(struct pt_regs *regs), we will call: int3_emulate_call(regs, (long)ip, (long)ip + tpl->disp); And in this, we simply call; unsigned long ip = regs->ip - INT3_INSN_SIZE + p->ainsn.size; int3_emulate_call(regs, ip, ip + p->ainsn.rel32); (Note: currently smp_text_poke_batch() does not handle non-5 byte nop. it checks the size.) Thanks, > } > NOKPROBE_SYMBOL(kprobe_emulate_call); > > -- > 2.34.1 > -- Masami Hiramatsu (Google) <[email protected]>
