History

tracing/probes: Fix BTF kflag check for anonymous struct member access

btf_find_struct_member() traverses into nested anonymous structures and unions to find a struct member. However, get_bitoffset_of_field() in trace_probe.c checked btf_type_kflag(type) using the outer parent type instead of the actual anonymous structure/union that directly contains the found member. If the parent structure and anonymous structure have mismatched kflags (e.g., the parent has kflag=0 while the anonymous structure has kflag=1 because it contains bitfields), the bitfield size encoded in the upper 8 bits of member->offset is erroneously treated as part of the byte/bit offset, corrupting the resolved offset and failing to set last_bitsize. Similarly, btf_find_struct_member() pushed anonymous member offsets onto anon_stack without masking BTF_MEMBER_BIT_OFFSET() when kflag is set. To fix this problem, update btf_find_struct_member() to return actual containing structure/union type via member_type, use appropriate __btf_member_bit_offset() to get bit offset, and use member_type for btf_type_kflag() in get_bitoffset_of_field(). Link: https://lore.kernel.org/all/178827250904.123716.17452648791331881284.stgit@devnote2/

Masami Hiramatsu (Google) committed 2 months ago

Commit 47e93041 parent f36d94a3 files · +16 −11Browse files

Showing the changes to kernel (3 of 3 files). Show all

Unified · Split

3 files
  1. kernel/trace/trace_btf.c +11 −8
  2. kernel/trace/trace_btf.h +2 −1
  3. kernel/trace/trace_probe.c +3 −2
kernel/trace/trace_btf.c+11−8
@@ -61,16 +61,17 @@ struct btf_anon_stack { · 60 unmodified lines
6161
6262 /*
6363 * Find a member of data structure/union by name and return it.
64− * Return NULL if not found, or -EINVAL if parameter is invalid.
65− * If the member is an member of anonymous union/structure, the offset
66− * of that anonymous union/structure is stored into @anon_offset. Caller
67− * can calculate the correct offset from the root data structure by
68− * adding anon_offset to the member's offset.
64+ * Return NULL if not found, or ERR_PTR(-EINVAL) if parameter is invalid.
65+ * If the member is a member of an anonymous union/structure, the bit offset
66+ * of that anonymous union/structure is stored into @anon_offset.
67+ * If @member_type is non-NULL, the actual containing structure/union type
68+ * of the found member is stored into @member_type.
6969 */
7070 const struct btf_member *btf_find_struct_member(struct btf *btf,
7171 const struct btf_type *type,
7272 const char *member_name,
73− u32 *anon_offset)
73+ u32 *anon_offset,
74+ const struct btf_type **member_type)
7475 {
7576 struct btf_anon_stack *anon_stack;
7677 const struct btf_member *member;
@@ -94,14 +95,16 @@ retry: · 17 unmodified lines
9495 if (mtype && btf_type_is_struct(mtype) &&
9596 top < BTF_ANON_STACK_MAX) {
9697 anon_stack[top].tid = tid;
97− anon_stack[top++].offset =
98− cur_offset + member->offset;
98+ anon_stack[top++].offset = cur_offset +
99+ __btf_member_bit_offset(type, member);
99100 }
100101 } else {
101102 name = btf_name_by_offset(btf, member->name_off);
102103 if (name && !strcmp(member_name, name)) {
103104 if (anon_offset)
104105 *anon_offset = cur_offset;
106+ if (member_type)
107+ *member_type = type;
105108 goto out;
106109 }
107110 }
kernel/trace/trace_btf.h+2−1
@@ -8,4 +8,5 @@ const struct btf_param *btf_get_func_param(const struct btf_type *func_proto, · 7 unmodified lines
88 const struct btf_member *btf_find_struct_member(struct btf *btf,
99 const struct btf_type *type,
1010 const char *member_name,
11− u32 *anon_offset);
11+ u32 *anon_offset,
12+ const struct btf_type **member_type);
kernel/trace/trace_probe.c+3−2
@@ -625,6 +625,7 @@ static int get_bitoffset_of_field(char **pfieldname, const struct btf_type **pty · 624 unmodified lines
625625 {
626626 const struct btf_type *type = *ptype;
627627 const struct btf_member *field;
628+ const struct btf_type *mtype;
628629 struct btf *btf = ctx_btf(ctx);
629630 char *fieldname = *pfieldname;
630631 int bitoffs = 0;
@@ -640,7 +641,7 @@ static int get_bitoffset_of_field(char **pfieldname, const struct btf_type **pty · 9 unmodified lines
640641
641642 anon_offs = 0;
642643 field = btf_find_struct_member(btf, type, fieldname,
643− &anon_offs);
644+ &anon_offs, &mtype);
644645 if (IS_ERR(field)) {
645646 trace_probe_log_err(ctx->offset, BAD_BTF_TID);
646647 return PTR_ERR(field);
@@ -653,7 +654,7 @@ static int get_bitoffset_of_field(char **pfieldname, const struct btf_type **pty · 6 unmodified lines
653654 bitoffs += anon_offs;
654655
655656 /* Accumulate the bit-offsets of the dot-connected fields */
656− if (btf_type_kflag(type)) {
657+ if (btf_type_kflag(mtype)) {
657658 bitoffs += BTF_MEMBER_BIT_OFFSET(field->offset);
658659 ctx->last_bitsize = BTF_MEMBER_BITFIELD_SIZE(field->offset);
659660 } else {