Skip to main content

planarian_backend_netwatch/
btf.rs

1//! Just enough of the kernel's BTF (`/sys/kernel/btf/vmlinux`) to find
2//! where a struct member lives, so the probes can read the netns of a
3//! request on any kernel: Rust eBPF has no CO-RE, so the loader resolves
4//! the offsets here and hands them to the object as globals (crate docs,
5//! "Namespaces"). Trusted code in the loader, so: read-only, every read
6//! bounds-checked, recursion bounded, and an answer only when each member
7//! found lies inside its struct; anything else is an error (the loader
8//! writes `failed`), never a panic.
9
10const MAGIC: u16 = 0xEB9F;
11const KIND_INT: u32 = 1;
12const KIND_PTR: u32 = 2;
13const KIND_ARRAY: u32 = 3;
14const KIND_STRUCT: u32 = 4;
15const KIND_UNION: u32 = 5;
16const KIND_ENUM: u32 = 6;
17const KIND_TYPEDEF: u32 = 8;
18const KIND_VOLATILE: u32 = 9;
19const KIND_CONST: u32 = 10;
20const KIND_RESTRICT: u32 = 11;
21const KIND_FUNC_PROTO: u32 = 13;
22const KIND_VAR: u32 = 14;
23const KIND_DATASEC: u32 = 15;
24const KIND_DECL_TAG: u32 = 17;
25const KIND_TYPE_TAG: u32 = 18;
26const KIND_ENUM64: u32 = 19;
27const KIND_FLOAT: u32 = 16;
28/// How deep anonymous members and typedef chains are followed.
29const DEPTH: usize = 16;
30
31struct Member {
32    name: u32,
33    ty: u32,
34    bit_offset: u32,
35}
36
37struct Ty {
38    name: u32,
39    kind: u32,
40    /// The referenced type (typedef, const, volatile, pointer) or the size
41    /// in bytes (int, enum, struct, union, float), by kind.
42    refers: u32,
43    /// An array's element count (its element type is `refers`).
44    nelems: u32,
45    members: Vec<Member>,
46}
47
48pub struct Btf {
49    /// Indexed by type id; id 0 is void.
50    types: Vec<Ty>,
51    strings: Vec<u8>,
52}
53
54fn u32_at(b: &[u8], at: usize) -> Result<u32, String> {
55    b.get(at..at + 4).map(|s| u32::from_le_bytes([s[0], s[1], s[2], s[3]])).ok_or_else(|| format!("BTF: short at {at}"))
56}
57
58impl Btf {
59    pub fn parse(b: &[u8]) -> Result<Btf, String> {
60        if b.get(0..2) != Some(&MAGIC.to_le_bytes()[..]) {
61            return Err("BTF: bad magic (or big-endian)".into());
62        }
63        let hdr = u32_at(b, 4)? as usize;
64        let (type_off, type_len) = (u32_at(b, 8)? as usize, u32_at(b, 12)? as usize);
65        let (str_off, str_len) = (u32_at(b, 16)? as usize, u32_at(b, 20)? as usize);
66        let types_b = b.get(hdr + type_off..hdr + type_off + type_len).ok_or("BTF: type section out of range")?;
67        let strings = b.get(hdr + str_off..hdr + str_off + str_len).ok_or("BTF: string section out of range")?.to_vec();
68        let mut types = vec![Ty { name: 0, kind: 0, refers: 0, nelems: 0, members: vec![] }];
69        let mut at = 0;
70        while at < types_b.len() {
71            let (name, info, size_or_type) = (u32_at(types_b, at)?, u32_at(types_b, at + 4)?, u32_at(types_b, at + 8)?);
72            at += 12;
73            let vlen = (info & 0xffff) as usize;
74            let kind = (info >> 24) & 0x1f;
75            let kind_flag = info >> 31 == 1;
76            let mut members = Vec::new();
77            let mut refers = size_or_type;
78            let mut nelems = 0;
79            let extra = match kind {
80                KIND_INT | KIND_VAR | KIND_DECL_TAG => 4,
81                KIND_ARRAY => {
82                    // struct btf_array { type, index_type, nelems }
83                    refers = u32_at(types_b, at)?;
84                    nelems = u32_at(types_b, at + 8)?;
85                    12
86                }
87                KIND_STRUCT | KIND_UNION => {
88                    for i in 0..vlen {
89                        let m = at + 12 * i;
90                        let off = u32_at(types_b, m + 8)?;
91                        let bit_offset = if kind_flag { off & 0x00ff_ffff } else { off };
92                        members.push(Member { name: u32_at(types_b, m)?, ty: u32_at(types_b, m + 4)?, bit_offset });
93                    }
94                    12 * vlen
95                }
96                KIND_ENUM | KIND_FUNC_PROTO => 8 * vlen,
97                KIND_DATASEC | KIND_ENUM64 => 12 * vlen,
98                // No trailing data: ptr, fwd, typedef, the qualifiers,
99                // func, float, type tag.
100                2 | 7..=12 | KIND_FLOAT | KIND_TYPE_TAG => 0,
101                // A kind this reader does not know has data it cannot size:
102                // stop rather than misread everything after it.
103                k => return Err(format!("BTF: unknown kind {k} at type {}", types.len())),
104            };
105            at += extra;
106            types.push(Ty { name, kind, refers, nelems, members });
107        }
108        Ok(Btf { types, strings })
109    }
110
111    fn name(&self, off: u32) -> &[u8] {
112        let s = self.strings.get(off as usize..).unwrap_or(&[]);
113        &s[..s.iter().position(|&c| c == 0).unwrap_or(s.len())]
114    }
115
116    /// Through typedefs and qualifiers to the type itself.
117    fn resolve(&self, mut id: u32) -> Option<&Ty> {
118        for _ in 0..DEPTH {
119            let t = self.types.get(id as usize)?;
120            match t.kind {
121                KIND_TYPEDEF | KIND_VOLATILE | KIND_CONST | KIND_RESTRICT | KIND_TYPE_TAG => id = t.refers,
122                _ => return Some(t),
123            }
124        }
125        None
126    }
127
128    /// A member by name, looking into anonymous structs and unions; its
129    /// type id and bit offset from the start of `t`.
130    fn member(&self, t: &Ty, name: &str, depth: usize) -> Option<(u32, u32)> {
131        if depth > DEPTH {
132            return None;
133        }
134        for m in &t.members {
135            if self.name(m.name) == name.as_bytes() {
136                return Some((m.ty, m.bit_offset));
137            }
138            if m.name == 0
139                && let Some(inner) = self.resolve(m.ty)
140                && matches!(inner.kind, KIND_STRUCT | KIND_UNION)
141                && let Some((ty, off)) = self.member(inner, name, depth + 1)
142            {
143                return Some((ty, m.bit_offset.checked_add(off)?));
144            }
145        }
146        None
147    }
148
149    /// The size in bytes of type `id`, where it has one.
150    fn size(&self, id: u32) -> Option<u32> {
151        self.size_at(id, 0)
152    }
153
154    fn size_at(&self, id: u32, depth: usize) -> Option<u32> {
155        if depth > DEPTH {
156            return None;
157        }
158        let t = self.resolve(id)?;
159        match t.kind {
160            KIND_PTR => Some(8),
161            KIND_ARRAY => self.size_at(t.refers, depth + 1)?.checked_mul(t.nelems),
162            KIND_INT | KIND_ENUM | KIND_ENUM64 | KIND_STRUCT | KIND_UNION | KIND_FLOAT => Some(t.refers),
163            _ => None,
164        }
165    }
166
167    /// The byte offset of `path` (member, member of that, …) in `struct
168    /// strukt`. Each member must lie whole inside the struct or union it
169    /// was found in.
170    pub fn offset(&self, strukt: &str, path: &[&str]) -> Result<u32, String> {
171        let what = || format!("struct {strukt}.{}", path.join("."));
172        let mut t = self
173            .types
174            .iter()
175            .find(|t| t.kind == KIND_STRUCT && self.name(t.name) == strukt.as_bytes())
176            .ok_or_else(|| format!("BTF: no {}", what()))?;
177        let mut bits: u32 = 0;
178        for (i, name) in path.iter().enumerate() {
179            let (ty, off) = self.member(t, name, 0).ok_or_else(|| format!("BTF: no member {name} in {}", what()))?;
180            let size = self.size(ty).ok_or_else(|| format!("BTF: {name} has no size in {}", what()))?;
181            if !off.is_multiple_of(8) || (off / 8).checked_add(size).is_none_or(|end| end > t.refers) {
182                return Err(format!("BTF: {name} lies outside its struct in {}", what()));
183            }
184            bits = bits.checked_add(off).ok_or_else(|| format!("BTF: offset overflow in {}", what()))?;
185            if i + 1 < path.len() {
186                t = self
187                    .resolve(ty)
188                    .filter(|t| matches!(t.kind, KIND_STRUCT | KIND_UNION))
189                    .ok_or_else(|| format!("BTF: {name} is not a struct in {}", what()))?;
190            }
191        }
192        if !bits.is_multiple_of(8) {
193            return Err(format!("BTF: {} is a bitfield", what()));
194        }
195        Ok(bits / 8)
196    }
197}
198
199/// Where the probes find a request's network namespace (crate docs,
200/// "Namespaces"): `skb->sk`, `sk->__sk_common.skc_net.net`, and
201/// `net->ns.inum`.
202#[derive(Clone, Copy, Debug, PartialEq, Eq)]
203pub struct NetnsOffsets {
204    pub skb_sk: u32,
205    pub sk_net: u32,
206    pub net_inum: u32,
207}
208
209impl NetnsOffsets {
210    pub fn from_btf(btf: &Btf) -> Result<NetnsOffsets, String> {
211        Ok(NetnsOffsets {
212            skb_sk: btf.offset("sk_buff", &["sk"])?,
213            sk_net: btf.offset("sock", &["__sk_common", "skc_net", "net"])?,
214            net_inum: btf.offset("net", &["ns", "inum"])?,
215        })
216    }
217}
218
219/// Where the probes find the sender's cgroup names (crate docs,
220/// "Attribution"): `task_struct.cgroups`, `css_set.dfl_cgrp`,
221/// `cgroup.kn`, `kernfs_node.name`, and its parent (`__parent` since 6.15,
222/// `parent` before).
223#[derive(Clone, Copy, Debug, PartialEq, Eq)]
224pub struct CgroupOffsets {
225    pub task_cgroups: u32,
226    pub css_dfl: u32,
227    pub cgroup_kn: u32,
228    pub kn_name: u32,
229    pub kn_parent: u32,
230}
231
232impl CgroupOffsets {
233    pub fn from_btf(btf: &Btf) -> Result<CgroupOffsets, String> {
234        Ok(CgroupOffsets {
235            task_cgroups: btf.offset("task_struct", &["cgroups"])?,
236            css_dfl: btf.offset("css_set", &["dfl_cgrp"])?,
237            cgroup_kn: btf.offset("cgroup", &["kn"])?,
238            kn_name: btf.offset("kernfs_node", &["name"])?,
239            kn_parent: btf.offset("kernfs_node", &["__parent"]).or_else(|_| btf.offset("kernfs_node", &["parent"]))?,
240        })
241    }
242}
243
244/// Where the `register_netdevice` probe finds the new device's name and
245/// netns: `net_device.name` (`char[IFNAMSIZ]`) and `net_device.nd_net.net`.
246#[derive(Clone, Copy, Debug, PartialEq, Eq)]
247pub struct NetdevOffsets {
248    pub name: u32,
249    pub net: u32,
250}
251
252impl NetdevOffsets {
253    pub fn from_btf(btf: &Btf) -> Result<NetdevOffsets, String> {
254        Ok(NetdevOffsets {
255            name: btf.offset("net_device", &["name"])?,
256            net: btf.offset("net_device", &["nd_net", "net"])?,
257        })
258    }
259}