Probes fixes for v7.3-rc1:

- kprobes: Protect kprobe_blacklist with RCU
   . RCU-protect kprobe_blacklist and use kfree_rcu() to prevent UAF
     races during module unloading and enable safe atomic lookups.
 - tracing/probes: Fix multi-probe field use-after-free and BTF parsing
   . Multi-probe UAF fix: Duplicate field and type strings on
     trace_probe_event to prevent UAF when freeing primary probe.
   . BTF member lookup fixes:
     - Check the containing inner struct/union kflag when resolving
       anonymous members to ensure correct bitfield offset calculation.
     - Prevent unnamed bitfields from being pushed to anon_stack in
       btf_find_struct_member(), avoiding false lookup errors.
     - Fix code block indentation in get_bitoffset_of_field().
 - uprobes: Error pointer safety
   . Guard free_trace_uprobe() with IS_ERR_OR_NULL() to avoid crashing
     during automatic cleanup when an error pointer is returned.
 -----BEGIN PGP SIGNATURE-----
 
 iQFPBAABCgA5FiEEh7BulGwFlgAOi5DV2/sHvwUrPxsFAmqahXQbHG1hc2FtaS5o
 aXJhbWF0c3VAZ21haWwuY29tAAoJENv7B78FKz8bOJIH/1RuAq2y8fvfqWKwDBNG
 9CrSIMmZ0915s4LVSGQrrjNYfpj2rFYkEMsJcFo2pavKwWNyaxXFjXu8Vy9JGckx
 VFAHA52x2QaEYdwBeoo/Jd3+7Ks/3zH1XwfSILFa0PMn86/JCKHx/+5ah6Sk4vcu
 /he61Auyp6lJtvv88n95j1evCJNouU6lJ3fnvm8mNYTeLOIvPZ3qku6SsiOqNdeQ
 Ln8bcNP2Iis33PqfeydiRWv9nPog/ifH4a9WJ+fdqKA+06AHKVsfHB+fP0hxyUDp
 TS4oxKrIk6HIbEQRjgo8YcOPWurHAm0GQ2ZslgLFuWDE9rDQCtPdOYv/YzJe0ByG
 nHc=
 =hX9F
 -----END PGP SIGNATURE-----

Merge tag 'probes-fixes-v7.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace

Pull probes fixes from Masami Hiramatsu:

 - Protect kprobe_blacklist with RCU

   RCU-protect kprobe_blacklist and use kfree_rcu() to prevent UAF races
   during module unloading and enable safe atomic lookups.

 - Fix multi-probe field use-after-free

   Duplicate field and type strings on trace_probe_event to prevent UAF
   when freeing primary probe

 - Fix probe BTF member lookup:

   Check the containing inner struct/union kflag when resolving
   anonymous members to ensure correct bitfield offset calculation

   Prevent unnamed bitfields from being pushed to anon_stack in
   btf_find_struct_member(), avoiding false lookup errors

   Fix code block indentation in get_bitoffset_of_field()

 - uprobes error pointer safety

   Guard free_trace_uprobe() with IS_ERR_OR_NULL() to avoid crashing
   during automatic cleanup when an error pointer is returned

* tag 'probes-fixes-v7.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace:
  kprobes: Protect kprobe_blacklist with RCU
  tracing/probes: Fix use-after-free on field name/type of events with multiple probes
  tracing/probes: Fix code indent in get_bitoffset_of_field()
  tracing/probes: Fix BTF kflag check for anonymous struct member access
  tracing/probes: Fix anon_stack check for unnamed bitfields in btf_find_struct_member
  uprobes: guard trace cleanup against error pointers
This commit is contained in:
Linus Torvalds 2026-09-04 08:24:09 -07:00
commit 421066905c
7 changed files with 89 additions and 29 deletions

View File

@ -181,6 +181,7 @@ struct kprobe_blacklist_entry {
struct list_head list;
unsigned long start_addr;
unsigned long end_addr;
struct rcu_head rcu;
};
#ifdef CONFIG_KPROBES

View File

@ -1447,8 +1447,14 @@ static bool __within_kprobe_blacklist(unsigned long addr)
/*
* If 'kprobe_blacklist' is defined, check the address and
* reject any probe registration in the prohibited area.
* Note: this can return true during transition period where
* (start_addr, end_addr) in the black list is shrinking
* but old entry has not been removed yet. This is acceptable
* because the worst case is that we reject more probes than
* we should.
*/
list_for_each_entry(ent, &kprobe_blacklist, list) {
guard(rcu)();
list_for_each_entry_rcu(ent, &kprobe_blacklist, list) {
if (addr >= ent->start_addr && addr < ent->end_addr)
return true;
}
@ -2509,7 +2515,7 @@ int kprobe_add_ksym_blacklist(unsigned long entry)
ent->start_addr = entry;
ent->end_addr = entry + size;
INIT_LIST_HEAD(&ent->list);
list_add_tail(&ent->list, &kprobe_blacklist);
list_add_tail_rcu(&ent->list, &kprobe_blacklist);
return (int)size;
}
@ -2603,8 +2609,8 @@ static void kprobe_remove_area_blacklist(unsigned long start, unsigned long end)
list_for_each_entry_safe(ent, n, &kprobe_blacklist, list) {
if (ent->start_addr < start || ent->start_addr >= end)
continue;
list_del(&ent->list);
kfree(ent);
list_del_rcu(&ent->list);
kfree_rcu(ent, rcu);
}
}

View File

@ -61,47 +61,50 @@ struct btf_anon_stack {
/*
* Find a member of data structure/union by name and return it.
* Return NULL if not found, or -EINVAL if parameter is invalid.
* If the member is an member of anonymous union/structure, the offset
* of that anonymous union/structure is stored into @anon_offset. Caller
* can calculate the correct offset from the root data structure by
* adding anon_offset to the member's offset.
* Return NULL if not found, or ERR_PTR(-EINVAL) if parameter is invalid.
* If the member is a member of an anonymous union/structure, the bit offset
* of that anonymous union/structure is stored into @anon_offset.
* If @member_type is non-NULL, the actual containing structure/union type
* of the found member is stored into @member_type.
*/
const struct btf_member *btf_find_struct_member(struct btf *btf,
const struct btf_type *type,
const char *member_name,
u32 *anon_offset)
u32 *anon_offset,
const struct btf_type **member_type)
{
struct btf_anon_stack *anon_stack;
const struct btf_member *member;
const struct btf_type *mtype;
u32 tid, cur_offset = 0;
const char *name;
int i, top = 0;
if (!btf_type_is_struct(type))
return ERR_PTR(-EINVAL);
anon_stack = kzalloc_objs(*anon_stack, BTF_ANON_STACK_MAX);
if (!anon_stack)
return ERR_PTR(-ENOMEM);
retry:
if (!btf_type_is_struct(type)) {
member = ERR_PTR(-EINVAL);
goto out;
}
for_each_member(i, type, member) {
if (!member->name_off) {
/* Anonymous union/struct: push it for later use */
if (btf_type_skip_modifiers(btf, member->type, &tid) &&
mtype = btf_type_skip_modifiers(btf, member->type, &tid);
if (mtype && btf_type_is_struct(mtype) &&
top < BTF_ANON_STACK_MAX) {
anon_stack[top].tid = tid;
anon_stack[top++].offset =
cur_offset + member->offset;
anon_stack[top++].offset = cur_offset +
__btf_member_bit_offset(type, member);
}
} else {
name = btf_name_by_offset(btf, member->name_off);
if (name && !strcmp(member_name, name)) {
if (anon_offset)
*anon_offset = cur_offset;
if (member_type)
*member_type = type;
goto out;
}
}

View File

@ -8,4 +8,5 @@ const struct btf_param *btf_get_func_param(const struct btf_type *func_proto,
const struct btf_member *btf_find_struct_member(struct btf *btf,
const struct btf_type *type,
const char *member_name,
u32 *anon_offset);
u32 *anon_offset,
const struct btf_type **member_type);

View File

@ -625,6 +625,7 @@ static int get_bitoffset_of_field(char **pfieldname, const struct btf_type **pty
{
const struct btf_type *type = *ptype;
const struct btf_member *field;
const struct btf_type *mtype;
struct btf *btf = ctx_btf(ctx);
char *fieldname = *pfieldname;
int bitoffs = 0;
@ -640,7 +641,7 @@ static int get_bitoffset_of_field(char **pfieldname, const struct btf_type **pty
anon_offs = 0;
field = btf_find_struct_member(btf, type, fieldname,
&anon_offs);
&anon_offs, &mtype);
if (IS_ERR(field)) {
trace_probe_log_err(ctx->offset, BAD_BTF_TID);
return PTR_ERR(field);
@ -653,7 +654,7 @@ static int get_bitoffset_of_field(char **pfieldname, const struct btf_type **pty
bitoffs += anon_offs;
/* Accumulate the bit-offsets of the dot-connected fields */
if (btf_type_kflag(type)) {
if (btf_type_kflag(mtype)) {
bitoffs += BTF_MEMBER_BIT_OFFSET(field->offset);
ctx->last_bitsize = BTF_MEMBER_BITFIELD_SIZE(field->offset);
} else {
@ -661,11 +662,11 @@ static int get_bitoffset_of_field(char **pfieldname, const struct btf_type **pty
ctx->last_bitsize = 0;
}
type = btf_type_skip_modifiers(btf, field->type, NULL);
if (!type) {
trace_probe_log_err(ctx->offset, BAD_BTF_TID);
return -EINVAL;
}
type = btf_type_skip_modifiers(btf, field->type, NULL);
if (!type) {
trace_probe_log_err(ctx->offset, BAD_BTF_TID);
return -EINVAL;
}
if (next)
ctx->offset += next - fieldname;
@ -2552,19 +2553,60 @@ int traceprobe_set_print_fmt(struct trace_probe *tp, enum probe_print_type ptype
int traceprobe_define_arg_fields(struct trace_event_call *event_call,
size_t offset, struct trace_probe *tp)
{
struct trace_probe_event *tpe = trace_probe_event_from_call(event_call);
int ret, i;
/*
* A field created by trace_define_field() only stores the name and
* type pointers, it does not copy the strings. Here they point into
* the probe_arg of @tp, which is freed when @tp is removed. For an
* event with multiple probes attached, the field list is defined
* once by the first probe but kept alive by the surviving siblings,
* so removing that first probe would leave the fields referencing
* freed memory. Duplicate the strings and anchor the copies on the
* trace_probe_event, which lives as long as the field list itself.
*
* event_define_fields() ignores the return value of this hook, so
* if a previous attempt failed before creating any field, it may
* call here again. Release duplicates left behind by such an
* attempt before starting over.
*/
for (i = 0; i < tpe->nr_field_strings; i++)
kfree(tpe->field_strings[i]);
kfree(tpe->field_strings);
tpe->field_strings = NULL;
tpe->nr_field_strings = 0;
if (tp->nr_args) {
tpe->field_strings = kcalloc(tp->nr_args * 2, sizeof(char *),
GFP_KERNEL);
if (!tpe->field_strings)
return -ENOMEM;
}
/* Set argument names as fields */
for (i = 0; i < tp->nr_args; i++) {
struct probe_arg *parg = &tp->args[i];
const char *fmt = parg->type->fmttype;
int size = parg->type->size;
char *name, *type;
if (parg->fmt)
fmt = parg->fmt;
if (parg->count)
size *= parg->count;
ret = trace_define_field(event_call, fmt, parg->name,
name = kstrdup(parg->name, GFP_KERNEL);
type = kstrdup(fmt, GFP_KERNEL);
if (!name || !type) {
kfree(name);
kfree(type);
return -ENOMEM;
}
tpe->field_strings[tpe->nr_field_strings++] = name;
tpe->field_strings[tpe->nr_field_strings++] = type;
ret = trace_define_field(event_call, type, name,
offset + parg->offset, size,
parg->type->is_signed,
FILTER_OTHER);
@ -2576,6 +2618,11 @@ int traceprobe_define_arg_fields(struct trace_event_call *event_call,
static void trace_probe_event_free(struct trace_probe_event *tpe)
{
int i;
for (i = 0; i < tpe->nr_field_strings; i++)
kfree(tpe->field_strings[i]);
kfree(tpe->field_strings);
kfree(tpe->class.system);
kfree(tpe->call.name);
kfree(tpe->call.print_fmt);

View File

@ -264,6 +264,8 @@ struct trace_probe_event {
struct trace_event_call call;
struct list_head files;
struct list_head probes;
char **field_strings;
int nr_field_strings;
struct trace_uprobe_filter filter[];
};

View File

@ -368,7 +368,7 @@ alloc_trace_uprobe(const char *group, const char *event, int nargs, bool is_ret)
static void free_trace_uprobe(struct trace_uprobe *tu)
{
if (!tu)
if (IS_ERR_OR_NULL(tu))
return;
path_put(&tu->path);
@ -533,7 +533,7 @@ static int register_trace_uprobe(struct trace_uprobe *tu)
return ret;
}
DEFINE_FREE(free_trace_uprobe, struct trace_uprobe *, if (_T) free_trace_uprobe(_T))
DEFINE_FREE(free_trace_uprobe, struct trace_uprobe *, free_trace_uprobe(_T))
/*
* Argument syntax: