mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 09:36:22 +02:00
Misc perf events fixes:
- Fix a perf_event_attr::remove_on_exec bug for group
events (Taeyang Lee)
- Fix uprobes CALL emulation interaction with shadow stacks,
and add a testcase for this (David Windsor)
- Fix uprobes unregister bug (Jiri Olsa)
Signed-off-by: Ingo Molnar <mingo@kernel.org>
-----BEGIN PGP SIGNATURE-----
iQJFBAABCgAvFiEEBpT5eoXrXCwVQwEKEnMQ0APhK1gFAmpKCX8RHG1pbmdvQGtl
cm5lbC5vcmcACgkQEnMQ0APhK1hwJRAAne1HniwuV9PV2GQHNKipeKV320tod2iR
WY9yy9ez5WJbPttS7Fy28NobQKxuTanNWnAJqqM3TJF0tPPSrrYqkhAGrpEb2ab0
G7gl4KlrO//5kKTZnnI86t/quSV5BDt00UUdMBFp+hbNYXNul5/AeUxqMhNoGqB6
DpJHrV+7kNQZ4I7tjatYWL11hZHpEhrx4QWLUsnd+nDwddpmdsRNXVZwpGh9+Dh4
+XXcgLD7M8FqNFN3GCVfhJKO8x8HRaBWv3FHeGqbCUL9k5viWcn+N91IbrcPVat1
HBk0JDtpLK286WHLFy7uROafCA59AlYp5DX7mobXi0VF1FdMqPtjaXEsZM7Ng/P5
/tpbV5P4irrUnMSCEDSDzqZJWXbcBSqCJ9p6z5/Tjzo3VegHyrXe29wjVoqvXjLx
og/9OnPZv/2QEsE37rBRwNC889ihFMUDZh3T+uUc1YKUEwYWFXwtUECTg+0Oi+lL
mLTdK05j/6NmVkcY77mFjQfTMFAZeD78g6cPY3yDHiHRFtxPOIhknCKBeLD5BjXD
tz08x3MN0ItVEBXfuKAlkb1KPhHy8x02IrpdfVojoQ7lUBRH/JqLvnSGgvVsRM+0
v9D3N9BAuB3s8DmxEdh0wyRzISl6jzBHJDJ83+uWSfZWLlEW+RVn9NbH2ubNU/jt
FO3U81r1MvE=
=Mb4z
-----END PGP SIGNATURE-----
Merge tag 'perf-urgent-2026-07-05' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip
Pull perf events fixes from Ingo Molnar:
- Fix a perf_event_attr::remove_on_exec bug for group events
(Taeyang Lee)
- Fix uprobes CALL emulation interaction with shadow stacks, and
add a testcase for this (David Windsor)
- Fix uprobes unregister bug (Jiri Olsa)
* tag 'perf-urgent-2026-07-05' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip:
uprobes/x86: Use proper mm_struct in __in_uprobe_trampoline
selftests/x86: Add shadow stack uprobe CALL test
x86/uprobes: Keep shadow stack in sync for emulated CALLs
perf/core: Detach event groups during remove_on_exec
This commit is contained in:
commit
c10dc5c03e
|
|
@ -761,9 +761,9 @@ void arch_uprobe_clear_state(struct mm_struct *mm)
|
|||
destroy_uprobe_trampoline(tramp);
|
||||
}
|
||||
|
||||
static bool __in_uprobe_trampoline(unsigned long ip)
|
||||
static bool __in_uprobe_trampoline(struct mm_struct *mm, unsigned long ip)
|
||||
{
|
||||
struct vm_area_struct *vma = vma_lookup(current->mm, ip);
|
||||
struct vm_area_struct *vma = vma_lookup(mm, ip);
|
||||
|
||||
return vma && vma_is_special_mapping(vma, &tramp_mapping);
|
||||
}
|
||||
|
|
@ -776,14 +776,14 @@ static bool in_uprobe_trampoline(unsigned long ip)
|
|||
|
||||
rcu_read_lock();
|
||||
if (mmap_lock_speculate_try_begin(mm, &seq)) {
|
||||
found = __in_uprobe_trampoline(ip);
|
||||
found = __in_uprobe_trampoline(mm, ip);
|
||||
retry = mmap_lock_speculate_retry(mm, seq);
|
||||
}
|
||||
rcu_read_unlock();
|
||||
|
||||
if (retry) {
|
||||
mmap_read_lock(mm);
|
||||
found = __in_uprobe_trampoline(ip);
|
||||
found = __in_uprobe_trampoline(mm, ip);
|
||||
mmap_read_unlock(mm);
|
||||
}
|
||||
return found;
|
||||
|
|
@ -1044,7 +1044,7 @@ static int copy_from_vaddr(struct mm_struct *mm, unsigned long vaddr, void *dst,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static bool __is_optimized(uprobe_opcode_t *insn, unsigned long vaddr)
|
||||
static bool __is_optimized(struct mm_struct *mm, uprobe_opcode_t *insn, unsigned long vaddr)
|
||||
{
|
||||
struct __packed __arch_relative_insn {
|
||||
u8 op;
|
||||
|
|
@ -1053,7 +1053,7 @@ static bool __is_optimized(uprobe_opcode_t *insn, unsigned long vaddr)
|
|||
|
||||
if (!is_call_insn(insn))
|
||||
return false;
|
||||
return __in_uprobe_trampoline(vaddr + 5 + call->raddr);
|
||||
return __in_uprobe_trampoline(mm, vaddr + 5 + call->raddr);
|
||||
}
|
||||
|
||||
static int is_optimized(struct mm_struct *mm, unsigned long vaddr)
|
||||
|
|
@ -1064,7 +1064,7 @@ static int is_optimized(struct mm_struct *mm, unsigned long vaddr)
|
|||
err = copy_from_vaddr(mm, vaddr, &insn, 5);
|
||||
if (err)
|
||||
return err;
|
||||
return __is_optimized((uprobe_opcode_t *)&insn, vaddr);
|
||||
return __is_optimized(mm, (uprobe_opcode_t *)&insn, vaddr);
|
||||
}
|
||||
|
||||
static bool should_optimize(struct arch_uprobe *auprobe)
|
||||
|
|
@ -1246,9 +1246,15 @@ static int default_post_xol_op(struct arch_uprobe *auprobe, struct pt_regs *regs
|
|||
long correction = utask->vaddr - utask->xol_vaddr;
|
||||
regs->ip += correction;
|
||||
} else if (auprobe->defparam.fixups & UPROBE_FIX_CALL) {
|
||||
unsigned long retaddr = utask->vaddr + auprobe->defparam.ilen;
|
||||
int err;
|
||||
|
||||
regs->sp += sizeof_long(regs); /* Pop incorrect return address */
|
||||
if (emulate_push_stack(regs, utask->vaddr + auprobe->defparam.ilen))
|
||||
if (emulate_push_stack(regs, retaddr))
|
||||
return -ERESTART;
|
||||
err = shstk_update_last_frame(retaddr);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
/* popf; tell the caller to not touch TF */
|
||||
if (auprobe->defparam.fixups & UPROBE_FIX_SETF)
|
||||
|
|
@ -1338,6 +1344,10 @@ static bool branch_emulate_op(struct arch_uprobe *auprobe, struct pt_regs *regs)
|
|||
*/
|
||||
if (emulate_push_stack(regs, new_ip))
|
||||
return false;
|
||||
if (shstk_push(new_ip) == -EFAULT) {
|
||||
regs->sp += sizeof_long(regs);
|
||||
return false;
|
||||
}
|
||||
} else if (!check_jmp_cond(auprobe, regs)) {
|
||||
offs = 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4729,7 +4729,7 @@ static void perf_remove_from_owner(struct perf_event *event);
|
|||
static void perf_event_exit_event(struct perf_event *event,
|
||||
struct perf_event_context *ctx,
|
||||
struct task_struct *task,
|
||||
bool revoke);
|
||||
unsigned long detach_flags);
|
||||
|
||||
/*
|
||||
* Removes all events from the current task that have been marked
|
||||
|
|
@ -4756,7 +4756,7 @@ static void perf_event_remove_on_exec(struct perf_event_context *ctx)
|
|||
|
||||
modified = true;
|
||||
|
||||
perf_event_exit_event(event, ctx, ctx->task, false);
|
||||
perf_event_exit_event(event, ctx, ctx->task, DETACH_GROUP);
|
||||
}
|
||||
|
||||
raw_spin_lock_irqsave(&ctx->lock, flags);
|
||||
|
|
@ -12937,7 +12937,7 @@ static void __pmu_detach_event(struct pmu *pmu, struct perf_event *event,
|
|||
/*
|
||||
* De-schedule the event and mark it REVOKED.
|
||||
*/
|
||||
perf_event_exit_event(event, ctx, ctx->task, true);
|
||||
perf_event_exit_event(event, ctx, ctx->task, DETACH_REVOKE);
|
||||
|
||||
/*
|
||||
* All _free_event() bits that rely on event->pmu:
|
||||
|
|
@ -14525,12 +14525,13 @@ static void
|
|||
perf_event_exit_event(struct perf_event *event,
|
||||
struct perf_event_context *ctx,
|
||||
struct task_struct *task,
|
||||
bool revoke)
|
||||
unsigned long detach_flags)
|
||||
{
|
||||
struct perf_event *parent_event = event->parent;
|
||||
unsigned long detach_flags = DETACH_EXIT;
|
||||
unsigned int attach_state;
|
||||
|
||||
detach_flags |= DETACH_EXIT;
|
||||
|
||||
if (parent_event) {
|
||||
/*
|
||||
* Do not destroy the 'original' grouping; because of the
|
||||
|
|
@ -14553,8 +14554,8 @@ perf_event_exit_event(struct perf_event *event,
|
|||
sync_child_event(event, task);
|
||||
}
|
||||
|
||||
if (revoke)
|
||||
detach_flags |= DETACH_GROUP | DETACH_REVOKE;
|
||||
if (detach_flags & DETACH_REVOKE)
|
||||
detach_flags |= DETACH_GROUP;
|
||||
|
||||
perf_remove_from_context(event, detach_flags);
|
||||
/*
|
||||
|
|
@ -14642,7 +14643,7 @@ static void perf_event_exit_task_context(struct task_struct *task, bool exit)
|
|||
perf_event_task(task, ctx, 0);
|
||||
|
||||
list_for_each_entry_safe(child_event, next, &ctx->event_list, event_entry)
|
||||
perf_event_exit_event(child_event, ctx, exit ? task : NULL, false);
|
||||
perf_event_exit_event(child_event, ctx, exit ? task : NULL, 0);
|
||||
|
||||
mutex_unlock(&ctx->mutex);
|
||||
|
||||
|
|
|
|||
|
|
@ -873,6 +873,86 @@ static int test_uretprobe(void)
|
|||
return err;
|
||||
}
|
||||
|
||||
/* Keep the CALL first so the function address is exactly the probed CALL. */
|
||||
extern void uprobe_call_trigger(void);
|
||||
asm (".pushsection .text\n"
|
||||
".global uprobe_call_target\n"
|
||||
".type uprobe_call_target, @function\n"
|
||||
"uprobe_call_target:\n"
|
||||
" ret\n"
|
||||
".size uprobe_call_target, .-uprobe_call_target\n"
|
||||
|
||||
".global uprobe_call_trigger\n"
|
||||
".type uprobe_call_trigger, @function\n"
|
||||
"uprobe_call_trigger:\n"
|
||||
" call uprobe_call_target\n"
|
||||
" ret\n"
|
||||
".size uprobe_call_trigger, .-uprobe_call_trigger\n"
|
||||
".popsection\n"
|
||||
);
|
||||
|
||||
/* If CALL emulation misses the shadow stack update, this exits via SIGSEGV. */
|
||||
static int test_uprobe_call(void)
|
||||
{
|
||||
const size_t attr_sz = sizeof(struct perf_event_attr);
|
||||
const char *file = "/proc/self/exe";
|
||||
int fd = -1, type, err = 1;
|
||||
struct perf_event_attr attr;
|
||||
struct sigaction sa = {};
|
||||
ssize_t offset;
|
||||
|
||||
type = determine_uprobe_perf_type();
|
||||
if (type < 0) {
|
||||
if (type == -ENOENT)
|
||||
printf("[SKIP]\tUprobe on CALL test, uprobes are not available\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
offset = get_uprobe_offset(uprobe_call_trigger);
|
||||
if (offset < 0)
|
||||
return 1;
|
||||
|
||||
sa.sa_sigaction = segv_gp_handler;
|
||||
sa.sa_flags = SA_SIGINFO;
|
||||
if (sigaction(SIGSEGV, &sa, NULL))
|
||||
return 1;
|
||||
|
||||
/* Setup entry uprobe through perf event interface. */
|
||||
memset(&attr, 0, attr_sz);
|
||||
attr.size = attr_sz;
|
||||
attr.type = type;
|
||||
attr.config = 0;
|
||||
attr.config1 = (__u64)(unsigned long)file;
|
||||
attr.config2 = offset;
|
||||
|
||||
fd = syscall(__NR_perf_event_open, &attr, 0 /* pid */, -1 /* cpu */,
|
||||
-1 /* group_fd */, PERF_FLAG_FD_CLOEXEC);
|
||||
if (fd < 0)
|
||||
goto out;
|
||||
|
||||
if (sigsetjmp(jmp_buffer, 1))
|
||||
goto out;
|
||||
|
||||
if (ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK))
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* This either segfaults and goes through sigsetjmp above
|
||||
* or succeeds and we're good.
|
||||
*/
|
||||
uprobe_call_trigger();
|
||||
|
||||
printf("[OK]\tUprobe on CALL test\n");
|
||||
err = 0;
|
||||
|
||||
out:
|
||||
ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK);
|
||||
signal(SIGSEGV, SIG_DFL);
|
||||
if (fd >= 0)
|
||||
close(fd);
|
||||
return err;
|
||||
}
|
||||
|
||||
void segv_handler_ptrace(int signum, siginfo_t *si, void *uc)
|
||||
{
|
||||
/* The SSP adjustment caused a segfault. */
|
||||
|
|
@ -1071,6 +1151,12 @@ int main(int argc, char *argv[])
|
|||
goto out;
|
||||
}
|
||||
|
||||
if (test_uprobe_call()) {
|
||||
ret = 1;
|
||||
printf("[FAIL]\tuprobe on CALL test\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
out:
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user