diff options
| author | Daniel Borkmann <daniel@iogearbox.net> | 2026-10-28 21:23:44 +0100 |
|---|---|---|
| committer | Alexei Starovoitov <ast@kernel.org> | 2026-10-30 21:21:46 +0000 |
| commit | 31ac959c915cc7f379d84cf9dadf3664cbcace02 (patch) | |
| tree | ad3730fa9b27ae76b065293fbb26fc8a4a2ad0ce /scripts/basic | |
| parent | d89e1d186ec9271bf78e62771779e9fc03bf0fc3 (diff) | |
| download | linux-31ac959c915cc7f379d84cf9dadf3664cbcace02.tar.gz linux-31ac959c915cc7f379d84cf9dadf3664cbcace02.zip | |
x86/ftrace: Mark jmp mode by clearing bit 63 of trampoline address
Since commit 0c3772a8db1f ("x86/ftrace: Implement DYNAMIC_FTRACE_WITH_JMP")
ftrace_call_replace() emits a jmp instead of a call when bit 0 of the
target is set, the mark of a direct trampoline that is entered by a jmp.
ftrace_update_ftrace_func() and arch_ftrace_update_trampoline() use it
too, for the call of the ftrace callback in ftrace_caller, ftrace_regs_caller
and the ftrace trampolines, but a callback is a C function and can start
at an odd address. It does with gcc13 and CONFIG_CC_OPTIMIZE_FOR_SIZE=y:
gcc13 has no -fmin-function-alignment, so CONFIG_CC_HAS_SANE_FUNCTION_ALIGNMENT
is not set, and the -falign-functions=16 used in its place does not apply
to functions optimized for size. Here ftrace_ops_list_func() starts at:
ffffffff810e0147 T ftrace_ops_list_func
ftrace_regs_call then becomes a jmp to the byte before it:
ftrace_regs_call ffffffff810374f3: e9 4e 8c 0a 00
That byte is the last one of the preceding function's tail jump, and with
the first two of ftrace_ops_list_func() it decodes as an inc DWORD PTR
[rcx+0x57], which swallows the push %r15 of the prologue. Entered without
a return address and one push short, the list function returns through
the pt_regs that ftrace_regs_caller saved, here to the saved R14:
BUG: unable to handle page fault for address: ffff888116c29f40
#PF: supervisor instruction fetch in kernel mode
RIP: 0010:0xffff888116c29f40
R13: 0000000000000000 R14: ffff888116c29f40 R15: 0000000000000000
Call Trace:
? bpf_trampoline_6442499918+0x1/0x149
? bpf_lsm_file_open+0x5/0x8
? bpf_lsm_file_open+0x5/0x8
? security_file_open+0x19/0x46
? do_dentry_open+0x16c/0x32a
The list function is installed whenever two ftrace_ops are registered,
and a function that two of them trace calls it. A kprobe next to a BPF
trampoline is one such case, and with the single ftrace_ops of the BPF
trampolines every update_ftrace_direct_mod() is another, as it registers
a stub ops for the sites it modifies. Here, detaching one of several BPF
LSM programs from a busy hook crashed the tasks that ran the hook during
the update.
Bit 0 is free only in the direct trampolines that a dyn_ftrace record can
point to, but bit 63 is set in every kernel text address on x86_64. Thus,
mark jmp mode by clearing bit 63 instead, so that ftrace_is_jmp() is false
for any callback, whatever its alignment, and ftrace_regs_caller sets the
bit again where it cleared bit 0 before. A marked address is not canonical,
so a return or indirect branch to one that missed ftrace_jmp_get() faults
instead of running from a byte before its target.
Fixes: 0c3772a8db1f ("x86/ftrace: Implement DYNAMIC_FTRACE_WITH_JMP")
Suggested-by: Steven Rostedt <rostedt@goodmis.org>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Reviewed-by: Jiri Olsa <jolsa@kernel.org>
Reviewed-by: Steven Rostedt <rostedt@goodmis.org>
Link: https://patch.msgid.link/20261028202345.1215859-1-daniel@iogearbox.net
Diffstat (limited to 'scripts/basic')
0 files changed, 0 insertions, 0 deletions
