mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs.git
synced 2026-10-30 22:03:17 +01:00
When using eBPF to probe CS CALL instructions within a function,
a crash can be triggered.
The eBPF tool probes offset 257 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
Which triggers this crash:
BUG: unable to handle page fault for address: 00000000000f41c9
#PF: supervisor write access in kernel mode
#PF: error_code(0x0002) - not-present page
PGD 0 P4D 0
Oops: 0002 [#1] SMP NOPTI
CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P
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.
The root cause is that when using eBPF tools to probe in the middle of a
function, a 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.
[ mingo: Refined the changelog ]
Fixes: 6256e668b7 ("x86/kprobes: Use int3 instead of debug trap for single-step")
Suggested-by: Masami Hiramatsu (Google) <[email protected]>
Signed-off-by: Jinke Han <[email protected]>
Signed-off-by: Ingo Molnar <[email protected]>
Reviewed-by: Masami Hiramatsu (Google) <[email protected]>
Acked-by: Yafang Shao <[email protected]>
Acked-by: Borislav Petkov <[email protected]>
Cc: Peter Zijlstra <[email protected]>
Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000
219 lines
5.7 KiB
C
219 lines
5.7 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
#ifndef _ASM_X86_TEXT_PATCHING_H
|
|
#define _ASM_X86_TEXT_PATCHING_H
|
|
|
|
#include <linux/types.h>
|
|
#include <linux/stddef.h>
|
|
#include <asm/ptrace.h>
|
|
|
|
/*
|
|
* Currently, the max observed size in the kernel code is
|
|
* JUMP_LABEL_NOP_SIZE/RELATIVEJUMP_SIZE, which are 5.
|
|
* Raise it if needed.
|
|
*/
|
|
#define TEXT_POKE_MAX_OPCODE_SIZE 5
|
|
|
|
extern void text_poke_early(void *addr, const void *opcode, size_t len);
|
|
|
|
extern void text_poke_apply_relocation(u8 *buf, const u8 * const instr, size_t instrlen, u8 *repl, size_t repl_len);
|
|
|
|
/*
|
|
* Clear and restore the kernel write-protection flag on the local CPU.
|
|
* Allows the kernel to edit read-only pages.
|
|
* Side-effect: any interrupt handler running between save and restore will have
|
|
* the ability to write to read-only pages.
|
|
*
|
|
* Warning:
|
|
* Code patching in the UP case is safe if NMIs and MCE handlers are stopped and
|
|
* no thread can be preempted in the instructions being modified (no iret to an
|
|
* invalid instruction possible) or if the instructions are changed from a
|
|
* consistent state to another consistent state atomically.
|
|
* On the local CPU you need to be protected against NMI or MCE handlers seeing
|
|
* an inconsistent instruction while you patch.
|
|
*/
|
|
extern void *text_poke(void *addr, const void *opcode, size_t len);
|
|
extern void smp_text_poke_sync_each_cpu(void);
|
|
extern void *text_poke_kgdb(void *addr, const void *opcode, size_t len);
|
|
extern void *text_poke_copy(void *addr, const void *opcode, size_t len);
|
|
#define text_poke_copy text_poke_copy
|
|
extern void *text_poke_copy_locked(void *addr, const void *opcode, size_t len, bool core_ok);
|
|
extern void *text_poke_set(void *addr, int c, size_t len);
|
|
extern int smp_text_poke_int3_handler(struct pt_regs *regs);
|
|
extern void smp_text_poke_single(void *addr, const void *opcode, size_t len, const void *emulate);
|
|
|
|
extern void smp_text_poke_batch_add(void *addr, const void *opcode, size_t len, const void *emulate);
|
|
extern void smp_text_poke_batch_finish(void);
|
|
|
|
#define INT3_INSN_SIZE 1
|
|
#define INT3_INSN_OPCODE 0xCC
|
|
|
|
#define RET_INSN_SIZE 1
|
|
#define RET_INSN_OPCODE 0xC3
|
|
|
|
#define CALL_INSN_SIZE 5
|
|
#define CALL_INSN_OPCODE 0xE8
|
|
|
|
#define JMP32_INSN_SIZE 5
|
|
#define JMP32_INSN_OPCODE 0xE9
|
|
|
|
#define JMP8_INSN_SIZE 2
|
|
#define JMP8_INSN_OPCODE 0xEB
|
|
|
|
#define DISP32_SIZE 4
|
|
|
|
static __always_inline int text_opcode_size(u8 opcode)
|
|
{
|
|
int size = 0;
|
|
|
|
#define __CASE(insn) \
|
|
case insn##_INSN_OPCODE: size = insn##_INSN_SIZE; break
|
|
|
|
switch(opcode) {
|
|
__CASE(INT3);
|
|
__CASE(RET);
|
|
__CASE(CALL);
|
|
__CASE(JMP32);
|
|
__CASE(JMP8);
|
|
}
|
|
|
|
#undef __CASE
|
|
|
|
return size;
|
|
}
|
|
|
|
union text_poke_insn {
|
|
u8 text[TEXT_POKE_MAX_OPCODE_SIZE];
|
|
struct {
|
|
u8 opcode;
|
|
s32 disp;
|
|
} __attribute__((packed));
|
|
};
|
|
|
|
static __always_inline
|
|
void __text_gen_insn(void *buf, u8 opcode, const void *addr, const void *dest, int size)
|
|
{
|
|
union text_poke_insn *insn = buf;
|
|
|
|
BUG_ON(size < text_opcode_size(opcode));
|
|
|
|
/*
|
|
* Hide the addresses to avoid the compiler folding in constants when
|
|
* referencing code, these can mess up annotations like
|
|
* ANNOTATE_NOENDBR.
|
|
*/
|
|
OPTIMIZER_HIDE_VAR(insn);
|
|
OPTIMIZER_HIDE_VAR(addr);
|
|
OPTIMIZER_HIDE_VAR(dest);
|
|
|
|
insn->opcode = opcode;
|
|
|
|
if (size > 1) {
|
|
insn->disp = (long)dest - (long)(addr + size);
|
|
if (size == 2) {
|
|
/*
|
|
* Ensure that for JMP8 the displacement
|
|
* actually fits the signed byte.
|
|
*/
|
|
BUG_ON((insn->disp >> 31) != (insn->disp >> 7));
|
|
}
|
|
}
|
|
}
|
|
|
|
static __always_inline
|
|
void *text_gen_insn(u8 opcode, const void *addr, const void *dest)
|
|
{
|
|
static union text_poke_insn insn; /* per instance */
|
|
__text_gen_insn(&insn, opcode, addr, dest, text_opcode_size(opcode));
|
|
return &insn.text;
|
|
}
|
|
|
|
extern int after_bootmem;
|
|
extern __ro_after_init struct mm_struct *text_poke_mm;
|
|
extern __ro_after_init unsigned long text_poke_mm_addr;
|
|
|
|
#ifndef CONFIG_UML_X86
|
|
static __always_inline
|
|
void int3_emulate_jmp(struct pt_regs *regs, unsigned long ip)
|
|
{
|
|
regs->ip = ip;
|
|
}
|
|
|
|
static __always_inline
|
|
void int3_emulate_push(struct pt_regs *regs, unsigned long val)
|
|
{
|
|
/*
|
|
* The INT3 handler in entry_64.S adds a gap between the
|
|
* stack where the break point happened, and the saving of
|
|
* pt_regs. We can extend the original stack because of
|
|
* this gap. See the idtentry macro's X86_TRAP_BP logic.
|
|
*
|
|
* Similarly, entry_32.S will have a gap on the stack for
|
|
* (any) hardware exception and pt_regs; see the
|
|
* FIXUP_FRAME macro.
|
|
*/
|
|
regs->sp -= sizeof(unsigned long);
|
|
*(unsigned long *)regs->sp = val;
|
|
}
|
|
|
|
static __always_inline
|
|
unsigned long int3_emulate_pop(struct pt_regs *regs)
|
|
{
|
|
unsigned long val = *(unsigned long *)regs->sp;
|
|
regs->sp += sizeof(unsigned long);
|
|
return val;
|
|
}
|
|
|
|
static __always_inline
|
|
void int3_emulate_call(struct pt_regs *regs, unsigned long ip, unsigned long func)
|
|
{
|
|
int3_emulate_push(regs, ip);
|
|
int3_emulate_jmp(regs, func);
|
|
}
|
|
|
|
static __always_inline
|
|
void int3_emulate_ret(struct pt_regs *regs)
|
|
{
|
|
unsigned long ip = int3_emulate_pop(regs);
|
|
int3_emulate_jmp(regs, ip);
|
|
}
|
|
|
|
static __always_inline
|
|
bool __emulate_cc(unsigned long flags, u8 cc)
|
|
{
|
|
static const unsigned long cc_mask[6] = {
|
|
[0] = X86_EFLAGS_OF,
|
|
[1] = X86_EFLAGS_CF,
|
|
[2] = X86_EFLAGS_ZF,
|
|
[3] = X86_EFLAGS_CF | X86_EFLAGS_ZF,
|
|
[4] = X86_EFLAGS_SF,
|
|
[5] = X86_EFLAGS_PF,
|
|
};
|
|
|
|
bool invert = cc & 1;
|
|
bool match;
|
|
|
|
if (cc < 0xc) {
|
|
match = flags & cc_mask[cc >> 1];
|
|
} else {
|
|
match = ((flags & X86_EFLAGS_SF) >> X86_EFLAGS_SF_BIT) ^
|
|
((flags & X86_EFLAGS_OF) >> X86_EFLAGS_OF_BIT);
|
|
if (cc >= 0xe)
|
|
match = match || (flags & X86_EFLAGS_ZF);
|
|
}
|
|
|
|
return (match && !invert) || (!match && invert);
|
|
}
|
|
|
|
static __always_inline
|
|
void int3_emulate_jcc(struct pt_regs *regs, u8 cc, unsigned long ip, unsigned long disp)
|
|
{
|
|
if (__emulate_cc(regs->flags, cc))
|
|
ip += disp;
|
|
|
|
int3_emulate_jmp(regs, ip);
|
|
}
|
|
|
|
#endif /* !CONFIG_UML_X86 */
|
|
|
|
#endif /* _ASM_X86_TEXT_PATCHING_H */
|