Skip to content

Commit

Permalink
Merge tag 'probes-v6.11' of git://git.kernel.org/pub/scm/linux/kernel…
Browse files Browse the repository at this point in the history
…/git/trace/linux-trace

Pull probes updates from Masami Hiramatsu:
 "Uprobes:

   - x86/shstk: Make return uprobe work with shadow stack

   - Add uretprobe syscall which speeds up the uretprobe 10-30% faster.
     This syscall is automatically used from user-space trampolines
     which are generated by the uretprobe. If this syscall is used by
     normal user program, it will cause SIGILL. Note that this is
     currently only implemented on x86_64.

     (This also has two fixes for adjusting the syscall number to avoid
     conflict with new *attrat syscalls.)

   - uprobes/perf: fix user stack traces in the presence of pending
     uretprobe. This corrects the uretprobe's trampoline address in the
     stacktrace with correct return address

   - selftests/x86: Add a return uprobe with shadow stack test

   - selftests/bpf: Add uretprobe syscall related tests.
      - test case for register integrity check
      - test case with register changing case
      - test case for uretprobe syscall without uprobes (expected to fail)
      - test case for uretprobe with shadow stack

   - selftests/bpf: add test validating uprobe/uretprobe stack traces

   - MAINTAINERS: Add uprobes entry. This does not specify the tree but
     to clarify who maintains and reviews the uprobes

  Kprobes:

   - tracing/kprobes: Test case cleanups.

     Replace redundant WARN_ON_ONCE() + pr_warn() with WARN_ONCE() and
     remove unnecessary code from selftest

   - tracing/kprobes: Add symbol counting check when module loads.

     This checks the uniqueness of the probed symbol on modules. The
     same check has already done for kernel symbols

     (This also has a fix for build error with CONFIG_MODULES=n)

  Cleanup:

   - Add MODULE_DESCRIPTION() macros for fprobe and kprobe examples"

* tag 'probes-v6.11' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace:
  MAINTAINERS: Add uprobes entry
  selftests/bpf: Change uretprobe syscall number in uprobe_syscall test
  uprobe: Change uretprobe syscall scope and number
  tracing/kprobes: Fix build error when find_module() is not available
  tracing/kprobes: Add symbol counting check when module loads
  selftests/bpf: add test validating uprobe/uretprobe stack traces
  perf,uprobes: fix user stack traces in the presence of pending uretprobes
  tracing/kprobe: Remove cleanup code unrelated to selftest
  tracing/kprobe: Integrate test warnings into WARN_ONCE
  selftests/bpf: Add uretprobe shadow stack test
  selftests/bpf: Add uretprobe syscall call from user space test
  selftests/bpf: Add uretprobe syscall test for regs changes
  selftests/bpf: Add uretprobe syscall test for regs integrity
  selftests/x86: Add return uprobe shadow stack test
  uprobe: Add uretprobe syscall to speed up return probe
  uprobe: Wire up uretprobe system call
  x86/shstk: Make return uprobe work with shadow stack
  samples: kprobes: add missing MODULE_DESCRIPTION() macros
  fprobe: add missing MODULE_DESCRIPTION() macro
  • Loading branch information
torvalds committed Jul 18, 2024
2 parents cb273eb + c26b1b8 commit 91bd008
Show file tree
Hide file tree
Showing 23 changed files with 1,320 additions and 92 deletions.
13 changes: 13 additions & 0 deletions MAINTAINERS
Original file line number Diff line number Diff line change
Expand Up @@ -23367,6 +23367,19 @@ F: drivers/mtd/ubi/
F: include/linux/mtd/ubi.h
F: include/uapi/mtd/ubi-user.h

UPROBES
M: Masami Hiramatsu <[email protected]>
M: Oleg Nesterov <[email protected]>
M: Peter Zijlstra <[email protected]>
L: [email protected]
L: [email protected]
S: Maintained
F: arch/*/include/asm/uprobes.h
F: arch/*/kernel/probes/uprobes.c
F: arch/*/kernel/uprobes.c
F: include/linux/uprobes.h
F: kernel/events/uprobes.c

USB "USBNET" DRIVER FRAMEWORK
M: Oliver Neukum <[email protected]>
L: [email protected]
Expand Down
1 change: 1 addition & 0 deletions arch/x86/entry/syscalls/syscall_64.tbl
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,7 @@
460 common lsm_set_self_attr sys_lsm_set_self_attr
461 common lsm_list_modules sys_lsm_list_modules
462 common mseal sys_mseal
467 common uretprobe sys_uretprobe

#
# Due to a historical design error, certain syscalls are numbered differently
Expand Down
4 changes: 4 additions & 0 deletions arch/x86/include/asm/shstk.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ unsigned long shstk_alloc_thread_stack(struct task_struct *p, unsigned long clon
void shstk_free(struct task_struct *p);
int setup_signal_shadow_stack(struct ksignal *ksig);
int restore_signal_shadow_stack(void);
int shstk_update_last_frame(unsigned long val);
bool shstk_is_enabled(void);
#else
static inline long shstk_prctl(struct task_struct *task, int option,
unsigned long arg2) { return -EINVAL; }
Expand All @@ -31,6 +33,8 @@ static inline unsigned long shstk_alloc_thread_stack(struct task_struct *p,
static inline void shstk_free(struct task_struct *p) {}
static inline int setup_signal_shadow_stack(struct ksignal *ksig) { return 0; }
static inline int restore_signal_shadow_stack(void) { return 0; }
static inline int shstk_update_last_frame(unsigned long val) { return 0; }
static inline bool shstk_is_enabled(void) { return false; }
#endif /* CONFIG_X86_USER_SHADOW_STACK */

#endif /* __ASSEMBLY__ */
Expand Down
16 changes: 16 additions & 0 deletions arch/x86/kernel/shstk.c
Original file line number Diff line number Diff line change
Expand Up @@ -577,3 +577,19 @@ long shstk_prctl(struct task_struct *task, int option, unsigned long arg2)
return wrss_control(true);
return -EINVAL;
}

int shstk_update_last_frame(unsigned long val)
{
unsigned long ssp;

if (!features_enabled(ARCH_SHSTK_SHSTK))
return 0;

ssp = get_user_shstk_addr();
return write_user_shstk_64((u64 __user *)ssp, (u64)val);
}

bool shstk_is_enabled(void)
{
return features_enabled(ARCH_SHSTK_SHSTK);
}
124 changes: 123 additions & 1 deletion arch/x86/kernel/uprobes.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <linux/ptrace.h>
#include <linux/uprobes.h>
#include <linux/uaccess.h>
#include <linux/syscalls.h>

#include <linux/kdebug.h>
#include <asm/processor.h>
Expand Down Expand Up @@ -308,6 +309,122 @@ static int uprobe_init_insn(struct arch_uprobe *auprobe, struct insn *insn, bool
}

#ifdef CONFIG_X86_64

asm (
".pushsection .rodata\n"
".global uretprobe_trampoline_entry\n"
"uretprobe_trampoline_entry:\n"
"pushq %rax\n"
"pushq %rcx\n"
"pushq %r11\n"
"movq $" __stringify(__NR_uretprobe) ", %rax\n"
"syscall\n"
".global uretprobe_syscall_check\n"
"uretprobe_syscall_check:\n"
"popq %r11\n"
"popq %rcx\n"

/* The uretprobe syscall replaces stored %rax value with final
* return address, so we don't restore %rax in here and just
* call ret.
*/
"retq\n"
".global uretprobe_trampoline_end\n"
"uretprobe_trampoline_end:\n"
".popsection\n"
);

extern u8 uretprobe_trampoline_entry[];
extern u8 uretprobe_trampoline_end[];
extern u8 uretprobe_syscall_check[];

void *arch_uprobe_trampoline(unsigned long *psize)
{
static uprobe_opcode_t insn = UPROBE_SWBP_INSN;
struct pt_regs *regs = task_pt_regs(current);

/*
* At the moment the uretprobe syscall trampoline is supported
* only for native 64-bit process, the compat process still uses
* standard breakpoint.
*/
if (user_64bit_mode(regs)) {
*psize = uretprobe_trampoline_end - uretprobe_trampoline_entry;
return uretprobe_trampoline_entry;
}

*psize = UPROBE_SWBP_INSN_SIZE;
return &insn;
}

static unsigned long trampoline_check_ip(void)
{
unsigned long tramp = uprobe_get_trampoline_vaddr();

return tramp + (uretprobe_syscall_check - uretprobe_trampoline_entry);
}

SYSCALL_DEFINE0(uretprobe)
{
struct pt_regs *regs = task_pt_regs(current);
unsigned long err, ip, sp, r11_cx_ax[3];

if (regs->ip != trampoline_check_ip())
goto sigill;

err = copy_from_user(r11_cx_ax, (void __user *)regs->sp, sizeof(r11_cx_ax));
if (err)
goto sigill;

/* expose the "right" values of r11/cx/ax/sp to uprobe_consumer/s */
regs->r11 = r11_cx_ax[0];
regs->cx = r11_cx_ax[1];
regs->ax = r11_cx_ax[2];
regs->sp += sizeof(r11_cx_ax);
regs->orig_ax = -1;

ip = regs->ip;
sp = regs->sp;

uprobe_handle_trampoline(regs);

/*
* Some of the uprobe consumers has changed sp, we can do nothing,
* just return via iret.
* .. or shadow stack is enabled, in which case we need to skip
* return through the user space stack address.
*/
if (regs->sp != sp || shstk_is_enabled())
return regs->ax;
regs->sp -= sizeof(r11_cx_ax);

/* for the case uprobe_consumer has changed r11/cx */
r11_cx_ax[0] = regs->r11;
r11_cx_ax[1] = regs->cx;

/*
* ax register is passed through as return value, so we can use
* its space on stack for ip value and jump to it through the
* trampoline's ret instruction
*/
r11_cx_ax[2] = regs->ip;
regs->ip = ip;

err = copy_to_user((void __user *)regs->sp, r11_cx_ax, sizeof(r11_cx_ax));
if (err)
goto sigill;

/* ensure sysret, see do_syscall_64() */
regs->r11 = regs->flags;
regs->cx = regs->ip;

return regs->ax;

sigill:
force_sig(SIGILL);
return -1;
}

/*
* If arch_uprobe->insn doesn't use rip-relative addressing, return
* immediately. Otherwise, rewrite the instruction so that it accesses
Expand Down Expand Up @@ -1076,8 +1193,13 @@ arch_uretprobe_hijack_return_addr(unsigned long trampoline_vaddr, struct pt_regs
return orig_ret_vaddr;

nleft = copy_to_user((void __user *)regs->sp, &trampoline_vaddr, rasize);
if (likely(!nleft))
if (likely(!nleft)) {
if (shstk_update_last_frame(trampoline_vaddr)) {
force_sig(SIGSEGV);
return -1;
}
return orig_ret_vaddr;
}

if (nleft != rasize) {
pr_err("return address clobbered: pid=%d, %%sp=%#lx, %%ip=%#lx\n",
Expand Down
2 changes: 2 additions & 0 deletions include/linux/syscalls.h
Original file line number Diff line number Diff line change
Expand Up @@ -979,6 +979,8 @@ asmlinkage long sys_lsm_list_modules(u64 __user *ids, u32 __user *size, u32 flag
/* x86 */
asmlinkage long sys_ioperm(unsigned long from, unsigned long num, int on);

asmlinkage long sys_uretprobe(void);

/* pciconfig: alpha, arm, arm64, ia64, sparc */
asmlinkage long sys_pciconfig_read(unsigned long bus, unsigned long dfn,
unsigned long off, unsigned long len,
Expand Down
3 changes: 3 additions & 0 deletions include/linux/uprobes.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ extern bool arch_uretprobe_is_alive(struct return_instance *ret, enum rp_check c
extern bool arch_uprobe_ignore(struct arch_uprobe *aup, struct pt_regs *regs);
extern void arch_uprobe_copy_ixol(struct page *page, unsigned long vaddr,
void *src, unsigned long len);
extern void uprobe_handle_trampoline(struct pt_regs *regs);
extern void *arch_uprobe_trampoline(unsigned long *psize);
extern unsigned long uprobe_get_trampoline_vaddr(void);
#else /* !CONFIG_UPROBES */
struct uprobes_state {
};
Expand Down
5 changes: 4 additions & 1 deletion include/uapi/asm-generic/unistd.h
Original file line number Diff line number Diff line change
Expand Up @@ -841,8 +841,11 @@ __SYSCALL(__NR_lsm_list_modules, sys_lsm_list_modules)
#define __NR_mseal 462
__SYSCALL(__NR_mseal, sys_mseal)

#define __NR_uretprobe 463
__SYSCALL(__NR_uretprobe, sys_uretprobe)

#undef __NR_syscalls
#define __NR_syscalls 463
#define __NR_syscalls 464

/*
* 32 bit systems traditionally used different
Expand Down
43 changes: 42 additions & 1 deletion kernel/events/callchain.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <linux/perf_event.h>
#include <linux/slab.h>
#include <linux/sched/task_stack.h>
#include <linux/uprobes.h>

#include "internal.h"

Expand Down Expand Up @@ -176,13 +177,51 @@ put_callchain_entry(int rctx)
put_recursion_context(this_cpu_ptr(callchain_recursion), rctx);
}

static void fixup_uretprobe_trampoline_entries(struct perf_callchain_entry *entry,
int start_entry_idx)
{
#ifdef CONFIG_UPROBES
struct uprobe_task *utask = current->utask;
struct return_instance *ri;
__u64 *cur_ip, *last_ip, tramp_addr;

if (likely(!utask || !utask->return_instances))
return;

cur_ip = &entry->ip[start_entry_idx];
last_ip = &entry->ip[entry->nr - 1];
ri = utask->return_instances;
tramp_addr = uprobe_get_trampoline_vaddr();

/*
* If there are pending uretprobes for the current thread, they are
* recorded in a list inside utask->return_instances; each such
* pending uretprobe replaces traced user function's return address on
* the stack, so when stack trace is captured, instead of seeing
* actual function's return address, we'll have one or many uretprobe
* trampoline addresses in the stack trace, which are not helpful and
* misleading to users.
* So here we go over the pending list of uretprobes, and each
* encountered trampoline address is replaced with actual return
* address.
*/
while (ri && cur_ip <= last_ip) {
if (*cur_ip == tramp_addr) {
*cur_ip = ri->orig_ret_vaddr;
ri = ri->next;
}
cur_ip++;
}
#endif
}

struct perf_callchain_entry *
get_perf_callchain(struct pt_regs *regs, u32 init_nr, bool kernel, bool user,
u32 max_stack, bool crosstask, bool add_mark)
{
struct perf_callchain_entry *entry;
struct perf_callchain_entry_ctx ctx;
int rctx;
int rctx, start_entry_idx;

entry = get_callchain_entry(&rctx);
if (!entry)
Expand Down Expand Up @@ -215,7 +254,9 @@ get_perf_callchain(struct pt_regs *regs, u32 init_nr, bool kernel, bool user,
if (add_mark)
perf_callchain_store_context(&ctx, PERF_CONTEXT_USER);

start_entry_idx = entry->nr;
perf_callchain_user(&ctx, regs);
fixup_uretprobe_trampoline_entries(entry, start_entry_idx);
}
}

Expand Down
Loading

0 comments on commit 91bd008

Please sign in to comment.