mirror of
https://github.com/torvalds/linux.git
synced 2026-09-12 04:23:03 +02:00
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:
commit
421066905c
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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[];
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user