From: Masami Hiramatsu (Google) <[email protected]>

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, and use it for
btf_type_kflag() in get_bitoffset_of_field().

Fixes: c440adfbe302 ("tracing/probes: Support BTF based data structure field 
access")
Assisted-by: Antigravity:gemini-3.7-flash
Signed-off-by: Masami Hiramatsu (Google) <[email protected]>
---
Changes in v14:
 - Newly added.
---
 kernel/trace/trace_btf.c   |   11 +++++++++--
 kernel/trace/trace_btf.h   |    3 ++-
 kernel/trace/trace_probe.c |   15 ++++++++-------
 3 files changed, 19 insertions(+), 10 deletions(-)

diff --git a/kernel/trace/trace_btf.c b/kernel/trace/trace_btf.c
index 00172f301f25..7d93be2872ff 100644
--- a/kernel/trace/trace_btf.c
+++ b/kernel/trace/trace_btf.c
@@ -70,7 +70,8 @@ struct btf_anon_stack {
 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;
@@ -91,17 +92,23 @@ const struct btf_member *btf_find_struct_member(struct btf 
*btf,
        for_each_member(i, type, member) {
                if (!member->name_off) {
                        /* Anonymous union/struct: push it for later use */
+                       u32 m_off = btf_type_kflag(type) ?
+                               BTF_MEMBER_BIT_OFFSET(member->offset) :
+                               member->offset;
+
                        if (btf_type_skip_modifiers(btf, member->type, &tid) &&
                            top < BTF_ANON_STACK_MAX) {
                                anon_stack[top].tid = tid;
                                anon_stack[top++].offset =
-                                       cur_offset + member->offset;
+                                       cur_offset + m_off;
                        }
                } 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;
                        }
                }
diff --git a/kernel/trace/trace_btf.h b/kernel/trace/trace_btf.h
index 4bc44bc261e6..4bd26bceae23 100644
--- a/kernel/trace/trace_btf.h
+++ b/kernel/trace/trace_btf.h
@@ -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);
diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c
index c4163904ba74..908b4b6bc2df 100644
--- a/kernel/trace/trace_probe.c
+++ b/kernel/trace/trace_probe.c
@@ -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;


Reply via email to