Skip to content

Commit c10dc5c

Browse files
committed
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
2 parents fe5881e + 1693286 commit c10dc5c

3 files changed

Lines changed: 113 additions & 16 deletions

File tree

arch/x86/kernel/uprobes.c

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -761,9 +761,9 @@ void arch_uprobe_clear_state(struct mm_struct *mm)
761761
destroy_uprobe_trampoline(tramp);
762762
}
763763

764-
static bool __in_uprobe_trampoline(unsigned long ip)
764+
static bool __in_uprobe_trampoline(struct mm_struct *mm, unsigned long ip)
765765
{
766-
struct vm_area_struct *vma = vma_lookup(current->mm, ip);
766+
struct vm_area_struct *vma = vma_lookup(mm, ip);
767767

768768
return vma && vma_is_special_mapping(vma, &tramp_mapping);
769769
}
@@ -776,14 +776,14 @@ static bool in_uprobe_trampoline(unsigned long ip)
776776

777777
rcu_read_lock();
778778
if (mmap_lock_speculate_try_begin(mm, &seq)) {
779-
found = __in_uprobe_trampoline(ip);
779+
found = __in_uprobe_trampoline(mm, ip);
780780
retry = mmap_lock_speculate_retry(mm, seq);
781781
}
782782
rcu_read_unlock();
783783

784784
if (retry) {
785785
mmap_read_lock(mm);
786-
found = __in_uprobe_trampoline(ip);
786+
found = __in_uprobe_trampoline(mm, ip);
787787
mmap_read_unlock(mm);
788788
}
789789
return found;
@@ -1044,7 +1044,7 @@ static int copy_from_vaddr(struct mm_struct *mm, unsigned long vaddr, void *dst,
10441044
return 0;
10451045
}
10461046

1047-
static bool __is_optimized(uprobe_opcode_t *insn, unsigned long vaddr)
1047+
static bool __is_optimized(struct mm_struct *mm, uprobe_opcode_t *insn, unsigned long vaddr)
10481048
{
10491049
struct __packed __arch_relative_insn {
10501050
u8 op;
@@ -1053,7 +1053,7 @@ static bool __is_optimized(uprobe_opcode_t *insn, unsigned long vaddr)
10531053

10541054
if (!is_call_insn(insn))
10551055
return false;
1056-
return __in_uprobe_trampoline(vaddr + 5 + call->raddr);
1056+
return __in_uprobe_trampoline(mm, vaddr + 5 + call->raddr);
10571057
}
10581058

10591059
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)
10641064
err = copy_from_vaddr(mm, vaddr, &insn, 5);
10651065
if (err)
10661066
return err;
1067-
return __is_optimized((uprobe_opcode_t *)&insn, vaddr);
1067+
return __is_optimized(mm, (uprobe_opcode_t *)&insn, vaddr);
10681068
}
10691069

10701070
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
12461246
long correction = utask->vaddr - utask->xol_vaddr;
12471247
regs->ip += correction;
12481248
} else if (auprobe->defparam.fixups & UPROBE_FIX_CALL) {
1249+
unsigned long retaddr = utask->vaddr + auprobe->defparam.ilen;
1250+
int err;
1251+
12491252
regs->sp += sizeof_long(regs); /* Pop incorrect return address */
1250-
if (emulate_push_stack(regs, utask->vaddr + auprobe->defparam.ilen))
1253+
if (emulate_push_stack(regs, retaddr))
12511254
return -ERESTART;
1255+
err = shstk_update_last_frame(retaddr);
1256+
if (err)
1257+
return err;
12521258
}
12531259
/* popf; tell the caller to not touch TF */
12541260
if (auprobe->defparam.fixups & UPROBE_FIX_SETF)
@@ -1338,6 +1344,10 @@ static bool branch_emulate_op(struct arch_uprobe *auprobe, struct pt_regs *regs)
13381344
*/
13391345
if (emulate_push_stack(regs, new_ip))
13401346
return false;
1347+
if (shstk_push(new_ip) == -EFAULT) {
1348+
regs->sp += sizeof_long(regs);
1349+
return false;
1350+
}
13411351
} else if (!check_jmp_cond(auprobe, regs)) {
13421352
offs = 0;
13431353
}

kernel/events/core.c

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4729,7 +4729,7 @@ static void perf_remove_from_owner(struct perf_event *event);
47294729
static void perf_event_exit_event(struct perf_event *event,
47304730
struct perf_event_context *ctx,
47314731
struct task_struct *task,
4732-
bool revoke);
4732+
unsigned long detach_flags);
47334733

47344734
/*
47354735
* 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)
47564756

47574757
modified = true;
47584758

4759-
perf_event_exit_event(event, ctx, ctx->task, false);
4759+
perf_event_exit_event(event, ctx, ctx->task, DETACH_GROUP);
47604760
}
47614761

47624762
raw_spin_lock_irqsave(&ctx->lock, flags);
@@ -12937,7 +12937,7 @@ static void __pmu_detach_event(struct pmu *pmu, struct perf_event *event,
1293712937
/*
1293812938
* De-schedule the event and mark it REVOKED.
1293912939
*/
12940-
perf_event_exit_event(event, ctx, ctx->task, true);
12940+
perf_event_exit_event(event, ctx, ctx->task, DETACH_REVOKE);
1294112941

1294212942
/*
1294312943
* All _free_event() bits that rely on event->pmu:
@@ -14525,12 +14525,13 @@ static void
1452514525
perf_event_exit_event(struct perf_event *event,
1452614526
struct perf_event_context *ctx,
1452714527
struct task_struct *task,
14528-
bool revoke)
14528+
unsigned long detach_flags)
1452914529
{
1453014530
struct perf_event *parent_event = event->parent;
14531-
unsigned long detach_flags = DETACH_EXIT;
1453214531
unsigned int attach_state;
1453314532

14533+
detach_flags |= DETACH_EXIT;
14534+
1453414535
if (parent_event) {
1453514536
/*
1453614537
* Do not destroy the 'original' grouping; because of the
@@ -14553,8 +14554,8 @@ perf_event_exit_event(struct perf_event *event,
1455314554
sync_child_event(event, task);
1455414555
}
1455514556

14556-
if (revoke)
14557-
detach_flags |= DETACH_GROUP | DETACH_REVOKE;
14557+
if (detach_flags & DETACH_REVOKE)
14558+
detach_flags |= DETACH_GROUP;
1455814559

1455914560
perf_remove_from_context(event, detach_flags);
1456014561
/*
@@ -14642,7 +14643,7 @@ static void perf_event_exit_task_context(struct task_struct *task, bool exit)
1464214643
perf_event_task(task, ctx, 0);
1464314644

1464414645
list_for_each_entry_safe(child_event, next, &ctx->event_list, event_entry)
14645-
perf_event_exit_event(child_event, ctx, exit ? task : NULL, false);
14646+
perf_event_exit_event(child_event, ctx, exit ? task : NULL, 0);
1464614647

1464714648
mutex_unlock(&ctx->mutex);
1464814649

tools/testing/selftests/x86/test_shadow_stack.c

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -873,6 +873,86 @@ static int test_uretprobe(void)
873873
return err;
874874
}
875875

876+
/* Keep the CALL first so the function address is exactly the probed CALL. */
877+
extern void uprobe_call_trigger(void);
878+
asm (".pushsection .text\n"
879+
".global uprobe_call_target\n"
880+
".type uprobe_call_target, @function\n"
881+
"uprobe_call_target:\n"
882+
" ret\n"
883+
".size uprobe_call_target, .-uprobe_call_target\n"
884+
885+
".global uprobe_call_trigger\n"
886+
".type uprobe_call_trigger, @function\n"
887+
"uprobe_call_trigger:\n"
888+
" call uprobe_call_target\n"
889+
" ret\n"
890+
".size uprobe_call_trigger, .-uprobe_call_trigger\n"
891+
".popsection\n"
892+
);
893+
894+
/* If CALL emulation misses the shadow stack update, this exits via SIGSEGV. */
895+
static int test_uprobe_call(void)
896+
{
897+
const size_t attr_sz = sizeof(struct perf_event_attr);
898+
const char *file = "/proc/self/exe";
899+
int fd = -1, type, err = 1;
900+
struct perf_event_attr attr;
901+
struct sigaction sa = {};
902+
ssize_t offset;
903+
904+
type = determine_uprobe_perf_type();
905+
if (type < 0) {
906+
if (type == -ENOENT)
907+
printf("[SKIP]\tUprobe on CALL test, uprobes are not available\n");
908+
return 0;
909+
}
910+
911+
offset = get_uprobe_offset(uprobe_call_trigger);
912+
if (offset < 0)
913+
return 1;
914+
915+
sa.sa_sigaction = segv_gp_handler;
916+
sa.sa_flags = SA_SIGINFO;
917+
if (sigaction(SIGSEGV, &sa, NULL))
918+
return 1;
919+
920+
/* Setup entry uprobe through perf event interface. */
921+
memset(&attr, 0, attr_sz);
922+
attr.size = attr_sz;
923+
attr.type = type;
924+
attr.config = 0;
925+
attr.config1 = (__u64)(unsigned long)file;
926+
attr.config2 = offset;
927+
928+
fd = syscall(__NR_perf_event_open, &attr, 0 /* pid */, -1 /* cpu */,
929+
-1 /* group_fd */, PERF_FLAG_FD_CLOEXEC);
930+
if (fd < 0)
931+
goto out;
932+
933+
if (sigsetjmp(jmp_buffer, 1))
934+
goto out;
935+
936+
if (ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK))
937+
goto out;
938+
939+
/*
940+
* This either segfaults and goes through sigsetjmp above
941+
* or succeeds and we're good.
942+
*/
943+
uprobe_call_trigger();
944+
945+
printf("[OK]\tUprobe on CALL test\n");
946+
err = 0;
947+
948+
out:
949+
ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK);
950+
signal(SIGSEGV, SIG_DFL);
951+
if (fd >= 0)
952+
close(fd);
953+
return err;
954+
}
955+
876956
void segv_handler_ptrace(int signum, siginfo_t *si, void *uc)
877957
{
878958
/* The SSP adjustment caused a segfault. */
@@ -1071,6 +1151,12 @@ int main(int argc, char *argv[])
10711151
goto out;
10721152
}
10731153

1154+
if (test_uprobe_call()) {
1155+
ret = 1;
1156+
printf("[FAIL]\tuprobe on CALL test\n");
1157+
goto out;
1158+
}
1159+
10741160
return ret;
10751161

10761162
out:

0 commit comments

Comments
 (0)