Skip to content

Ask BTF which pointers the kernel is not allowed to follow - #55

Merged
tamnd merged 1 commit into
mainfrom
btf-annotations
Sep 5, 2026
Merged

tamnd merged 1 commit into
mainfrom
btf-annotations

Conversation

@tamnd

@tamnd tamnd commented Sep 5, 2026 •

Copy link
Copy Markdown
Owner

struct file *f and char __user *buf are both pointers. The second one is a pointer the kernel is not allowed to dereference, and nothing about that shows up in a memory layout, because an annotation changes no offset and no size. It is a rule about who may follow the pointer, and it is the rule behind a large share of the bugs a lesson about system calls is about.

The reader already printed the annotation as part of a type name, so a layout table showed char __user *. There was no way to ask about one. This adds the asking.

>>> tiny = btf.parse_file("corpora/btf/handwritten/tiny.btf")
>>> tiny.tag_counts()
{'user': 1, 'rcu': 1, 'percpu': 1}
>>> [f.path for f in tiny.annotated("rcu", "demo_annotated")]
['next']
>>> tags.explain("__rcu")
'This may be replaced at any moment by a writer, and the old value freed later. Read it with rcu_dereference inside an RCU read side section, and publish it with rcu_assign_pointer.'

That is the handwritten fixture and not a kernel, because there is no built kernel here yet. The same three calls are what a lesson will make against /sys/kernel/btf/vmlinux, where the counts run to thousands and task_struct has a screenful of __rcu fields in it.

kxray/btf/tags.py holds the five annotations, what each promises, and how you are supposed to reach the thing behind the pointer. Btf.tags(type_id) answers which ones a type carries, Field.tags puts them on every field of a layout, and Btf.annotated(tag, struct) answers the question a lesson asks.

The two refusals

__iomem is not in BTF and never will be. It is a sparse annotation, defined as address_space(__iomem) under __CHECKER__ and as nothing at all otherwise, so the compiler drops it before BTF is written. __user, __rcu and __percpu go through BTF_TYPE_TAG and survive. So asking for __iomem raises, with the reason and with where to look instead, rather than returning an empty list. An empty list reads as "there are none" and the truth is "the file cannot say".

The second refusal is the one I would have got wrong. A type tag reaches BTF only when the compiler that built the kernel emits it, and an image built by a toolchain that does not has no tags anywhere in it. Every question then answers empty, which looks exactly like a kernel whose structs happen not to be annotated. So annotated checks tag_counts() first and refuses outright on a blob with no tags, saying it is almost always the toolchain. A reader who gets an empty list back from a kernel image should be told which of the two they are looking at.

How it walks

The annotation is written on the pointee, not on the pointer. char __user *buf is a pointer to a char that lives in user memory, so in BTF the chain is ptr then type_tag then char. Every question anybody asks is about the field, so tags() walks through pointers as well as through typedefs and const, and reports a tag found anywhere on that chain. A pointer to a pointer into user memory counts, which is right: it is still a field you may not follow without copying. There is a test for that shape and one for a tag behind a typedef.

Also here

Btf.enum(name) and Btf.enum_name(name, value). The values were parsed and reachable only through the raw type record. Reading a number back into the constant the source calls it is the direction a lesson needs, because the trace, the /proc file or the crash dump hands you the number.

The fixture

demo_annotated is four pointers, three annotated with __user, __rcu and __percpu and one annotated with nothing. All four are eight bytes at the offsets you would expect, which is the point of it, and the fourth is there because a test where every field matches proves less than one with a control in it.

That is five more types in tiny.btf, so the baseline moved from 39 to 44 on that one artefact. Nothing else in it changed and no other number moved.

Testing

19 new tests. The gate is green: ruff, 500 or so pytest tests, node, and all fourteen checkers.

`struct file *f` and `char __user *buf` are both pointers, and the second one
is a pointer the kernel may not dereference. That difference is invisible in a
memory layout, because an annotation changes no offset and no size. It is a
rule about who may follow the pointer, and it is the rule behind a large share
of the bugs a lesson about system calls is about.

The reader printed the annotation in a type name and there was no way to ask
about one. Now there is. `kxray/btf/tags.py` holds what each annotation
promises and how you are supposed to reach the thing behind it, `Btf.tags`
answers which ones a type carries, and `Btf.annotated("rcu", "task_struct")`
answers which fields of a struct carry one.

Two things it refuses to answer, both on purpose. `__iomem` is a sparse
annotation the compiler drops, so BTF never sees it and the answer is a
refusal naming where to look instead. A blob with no type tags anywhere is
refused too, because a kernel built by a toolchain that does not emit them
looks exactly like a kernel whose structs are not annotated, and an empty list
reads as a no when the truth is a shrug.

Also adds `Btf.enum` and `Btf.enum_name`, since reading a number back into the
constant the source calls it is the direction a lesson needs.

The fixture gained `demo_annotated`: four pointers, three annotated and one
not, all the same size at the offsets you would expect. That is five more
types in `tiny.btf`, which is the one line that moved in the baseline.
@tamnd
tamnd merged commit 36ca86a into main Sep 5, 2026
3 checks passed
@tamnd
tamnd deleted the btf-annotations branch September 5, 2026 16:20
@tamnd tamnd mentioned this pull request Sep 5, 2026
20 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant