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:
Linus Torvalds 2026-07-05 05:34:43 -10:00
commit c10dc5c03e
3 changed files with 113 additions and 16 deletions

View File

@ -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;
}

View File

@ -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);

View File

@ -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: