[BUG] tracing/uprobe: GPF in path_put() via __free() on alloc failure

From: Farhad Alemi <hidden>
Date: 2026-05-28 17:22:52
Also in: lkml

Hello Masami and the linux-trace-kernel team,

I am reporting a tracing/uprobe error-path crash found by syzkaller
with fault injection.

Summary:
A write(2) to /sys/kernel/tracing/uprobe_events that fails inside
alloc_trace_uprobe() leaves the caller's `tu` variable holding
ERR_PTR(-ENOMEM). The cleanup macro at kernel/trace/trace_uprobe.c:536:

  DEFINE_FREE(free_trace_uprobe, struct trace_uprobe *,
              if (_T) free_trace_uprobe(_T))

guards only against NULL, not IS_ERR. free_trace_uprobe() (invoked by
the __free() helper at __trace_uprobe_create's return) has the same
guard shape -- `if (!tu) return;` -- and then calls path_put(&tu->path)
on the ERR_PTR-valued tu. KASAN catches the resulting dereference as a
null-ptr-deref-in-range at path_put+0x29.

Observed on:
- Linux v7.1-rc3-200-g70eda68668d1-dirty (where the bug was originally
  found), x86_64, QEMU Q35
- KASAN enabled; panic_on_warn set; CONFIG_FAULT_INJECTION enabled
- The only local dirty file in my tree is drivers/tty/serial/serial_core.c,
  containing a local ttyS0 console guard for the fuzzing harness. It is
  unrelated to kernel/trace/.
- Trigger requires CAP_SYS_ADMIN to open /sys/kernel/tracing/uprobe_events
  for write (mode 0640, TRACE_MODE_WRITE) plus CONFIG_FAULT_INJECTION
  with a forced kmalloc failure inside alloc_trace_uprobe().
- Source inspection of linus/master at commit e8c2f9fdadee
  (v7.1-rc4-754-ge8c2f9fdadee) shows the buggy structure is unchanged:
  DEFINE_FREE(free_trace_uprobe, ..., if (_T) free_trace_uprobe(_T)) at
  trace_uprobe.c:536 has only a NULL guard, free_trace_uprobe() at
  trace_uprobe.c:369 still has only `if (!tu) return;`, and
  __trace_uprobe_create() declares `struct trace_uprobe *tu
  __free(free_trace_uprobe) = NULL` and assigns the alloc_trace_uprobe()
  return value into tu before any IS_ERR check.

Impact:
With CONFIG_FAULT_INJECTION enabled, a fail_nth-injected allocation
failure inside alloc_trace_uprobe() (kzalloc_flex at line 341 returns
NULL, the function returns ERR_PTR(-ENOMEM)) causes the
__free(free_trace_uprobe) cleanup in __trace_uprobe_create() to
dereference the ERR_PTR via path_put():

  Oops: general protection fault, probably for non-canonical address
0xdffffc0000000008: 0000 [#1] SMP KASAN NOPTI
  KASAN: null-ptr-deref in range [0x0000000000000040-0x0000000000000047]
  RIP: 0010:path_put+0x29/0x60 fs/namei.c:717

The R12 register in the crash dump shows the smoking gun:
R12 = 0xfffffffffffffff4 = -ENOMEM = ERR_PTR(-12) -- i.e. the `tu`
pointer being freed is an ERR_PTR, not a real object.

Relevant stack:

  path_put+0x29/0x60 fs/namei.c:717
  free_trace_uprobe kernel/trace/trace_uprobe.c:374 [inline]
  __free_free_trace_uprobe kernel/trace/trace_uprobe.c:536 [inline]
  __trace_uprobe_create+0x53c/0xe40 kernel/trace/trace_uprobe.c:725
  trace_probe_create+0xce/0x130 kernel/trace/trace_probe.c:2252
  dyn_event_create+0x4f/0x70 kernel/trace/trace_dynevent.c:128
  create_or_delete_trace_uprobe+0x65/0xa0 kernel/trace/trace_uprobe.c:739
  trace_parse_run_command+0x1f3/0x380 kernel/trace/trace.c:9565
  vfs_write+0x29f/0xb90 fs/read_write.c:686
  ksys_write+0x155/0x270 fs/read_write.c:740

Expected behavior:
Any one of these closes the hole:

  1. Tighten free_trace_uprobe()'s entry guard:
       if (IS_ERR_OR_NULL(tu))
               return;

  2. Or change the DEFINE_FREE macro to skip ERR_PTR values:
       DEFINE_FREE(free_trace_uprobe, struct trace_uprobe *,
                   if (!IS_ERR_OR_NULL(_T)) free_trace_uprobe(_T))

  3. Or, after the IS_ERR(tu) check in __trace_uprobe_create(), assign
     `tu = NULL;` before returning so the __free helper sees NULL and
     skips the path_put.

Reproducer:
I attached the generated C reproducer as reproducer.c. I also attached the
syzkaller program as reproducer.syz and the console
report as crash-report.txt.

Novelty check:
I searched syzbot dashboard data across upstream, fixed, invalid, stable,
and Android namespaces, and searched lore.kernel.org for "path_put" +
"trace_uprobe", "free_trace_uprobe", and "__trace_uprobe_create" + "GPF" /
"KASAN". I did not find an exact match. Adjacent uprobe_unregister /
bpf_uprobe_multi_link UAF reports have different free paths.


I appreciate your time and consideration, and I'm grateful for your
work on this subsystem.

Regards,
Farhad

Attachments

Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help