1use std::fmt;
4use std::path::Path;
5
6const BTF_KIND_INT: u32 = 1;
7const BTF_KIND_PTR: u32 = 2;
8const BTF_KIND_ARRAY: u32 = 3;
9const BTF_KIND_STRUCT: u32 = 4;
10const BTF_KIND_UNION: u32 = 5;
11const BTF_KIND_ENUM: u32 = 6;
12const BTF_KIND_FWD: u32 = 7;
13const BTF_KIND_TYPEDEF: u32 = 8;
14const BTF_KIND_VOLATILE: u32 = 9;
15const BTF_KIND_CONST: u32 = 10;
16const BTF_KIND_RESTRICT: u32 = 11;
17const BTF_KIND_FUNC: u32 = 12;
18const BTF_KIND_FUNC_PROTO: u32 = 13;
19const BTF_KIND_VAR: u32 = 14;
20const BTF_KIND_DATASEC: u32 = 15;
21const BTF_KIND_FLOAT: u32 = 16;
22const BTF_KIND_DECL_TAG: u32 = 17;
23const BTF_KIND_TYPE_TAG: u32 = 18;
24const BTF_KIND_ENUM64: u32 = 19;
25
26const PT_TLS: u32 = 7;
27const EM_X86_64: u16 = 62;
28
29#[derive(Debug)]
30pub enum TlsLayoutError {
31 Io(std::io::Error),
32 Invalid(&'static str),
33 Missing(&'static str),
34}
35
36impl fmt::Display for TlsLayoutError {
37 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38 match self {
39 Self::Io(err) => write!(f, "{err}"),
40 Self::Invalid(detail) => write!(f, "invalid TLS layout data: {detail}"),
41 Self::Missing(detail) => write!(f, "missing TLS layout data: {detail}"),
42 }
43 }
44}
45
46impl std::error::Error for TlsLayoutError {}
47
48impl From<std::io::Error> for TlsLayoutError {
49 fn from(err: std::io::Error) -> Self {
50 Self::Io(err)
51 }
52}
53
54#[derive(Debug)]
55struct BtfRecord {
56 name: String,
57 kind: u32,
58 members: Vec<BtfMember>,
59}
60
61#[derive(Debug)]
62struct BtfMember {
63 name: String,
64 bit_offset: u32,
65}
66
67pub fn current_task_fsbase_offset() -> Result<u64, TlsLayoutError> {
69 let data = std::fs::read("/sys/kernel/btf/vmlinux")?;
70 let records = parse_btf_records(&data)?;
71 let task_thread_bits = find_member_bit_offset(&records, "task_struct", "thread")?;
72 let thread_fsbase_bits = find_member_bit_offset(&records, "thread_struct", "fsbase")?;
73
74 if task_thread_bits % 8 != 0 || thread_fsbase_bits % 8 != 0 {
75 return Err(TlsLayoutError::Invalid(
76 "task_struct.thread.fsbase is not byte-aligned",
77 ));
78 }
79
80 Ok(u64::from(task_thread_bits / 8 + thread_fsbase_bits / 8))
81}
82
83pub fn static_tls_bias_for_elf(path: &Path) -> Result<Option<i64>, TlsLayoutError> {
89 let data = std::fs::read(path)?;
90 if data.len() < 64 || &data[0..4] != b"\x7fELF" {
91 return Err(TlsLayoutError::Invalid("not an ELF file"));
92 }
93 if data[4] != 2 || data[5] != 1 {
94 return Err(TlsLayoutError::Invalid(
95 "only little-endian ELF64 is supported for TLS layout",
96 ));
97 }
98 if read_u16(&data, 18)? != EM_X86_64 {
99 return Err(TlsLayoutError::Invalid(
100 "only x86_64 ELF TLS layout is supported",
101 ));
102 }
103
104 let phoff = read_u64(&data, 32)? as usize;
105 let phentsize = read_u16(&data, 54)? as usize;
106 let phnum = read_u16(&data, 56)? as usize;
107 if phentsize < 56 {
108 return Err(TlsLayoutError::Invalid(
109 "ELF program header entry too small",
110 ));
111 }
112
113 for index in 0..phnum {
114 let offset =
115 phoff
116 .checked_add(index.saturating_mul(phentsize))
117 .ok_or(TlsLayoutError::Invalid(
118 "ELF program header offset overflow",
119 ))?;
120 if offset + phentsize > data.len() {
121 return Err(TlsLayoutError::Invalid("ELF program header out of bounds"));
122 }
123 if read_u32(&data, offset)? != PT_TLS {
124 continue;
125 }
126
127 let memsz = read_u64(&data, offset + 40)?;
128 let align = read_u64(&data, offset + 48)?.max(1);
129 let aligned = round_up(memsz, align)?;
130 if aligned > i64::MAX as u64 {
131 return Err(TlsLayoutError::Invalid("TLS segment too large"));
132 }
133 return Ok(Some(-(aligned as i64)));
134 }
135
136 Ok(None)
137}
138
139fn parse_btf_records(data: &[u8]) -> Result<Vec<Option<BtfRecord>>, TlsLayoutError> {
140 if data.len() < 24 {
141 return Err(TlsLayoutError::Invalid("BTF header too small"));
142 }
143 if read_u16(data, 0)? != 0xeb9f {
144 return Err(TlsLayoutError::Invalid(
145 "only little-endian BTF is supported",
146 ));
147 }
148
149 let hdr_len = read_u32(data, 4)? as usize;
150 let type_off = read_u32(data, 8)? as usize;
151 let type_len = read_u32(data, 12)? as usize;
152 let str_off = read_u32(data, 16)? as usize;
153 let str_len = read_u32(data, 20)? as usize;
154 let type_start = checked_range_start(hdr_len, type_off)?;
155 let type_end = checked_range_end(type_start, type_len, data.len())?;
156 let str_start = checked_range_start(hdr_len, str_off)?;
157 let str_end = checked_range_end(str_start, str_len, data.len())?;
158 let strings = &data[str_start..str_end];
159
160 let mut offset = type_start;
161 let mut records = vec![None];
162 while offset < type_end {
163 if offset + 12 > type_end {
164 return Err(TlsLayoutError::Invalid("truncated BTF type header"));
165 }
166 let name_offset = read_u32(data, offset)?;
167 let info = read_u32(data, offset + 4)?;
168 let kind = (info >> 24) & 0x1f;
169 let vlen = (info & 0xffff) as usize;
170 let kflag = (info & 0x8000_0000) != 0;
171 offset += 12;
172
173 let name = read_btf_string(strings, name_offset)?;
174 let mut members = Vec::new();
175 let extra_size = match kind {
176 BTF_KIND_STRUCT | BTF_KIND_UNION => {
177 for _ in 0..vlen {
178 if offset + 12 > type_end {
179 return Err(TlsLayoutError::Invalid("truncated BTF member"));
180 }
181 let member_name_offset = read_u32(data, offset)?;
182 let raw_offset = read_u32(data, offset + 8)?;
183 let bit_offset = if kflag {
184 raw_offset & 0x00ff_ffff
185 } else {
186 raw_offset
187 };
188 members.push(BtfMember {
189 name: read_btf_string(strings, member_name_offset)?,
190 bit_offset,
191 });
192 offset += 12;
193 }
194 0
195 }
196 BTF_KIND_INT | BTF_KIND_VAR | BTF_KIND_DECL_TAG => 4,
197 BTF_KIND_ARRAY => 12,
198 BTF_KIND_ENUM | BTF_KIND_FUNC_PROTO => vlen.saturating_mul(8),
199 BTF_KIND_DATASEC | BTF_KIND_ENUM64 => vlen.saturating_mul(12),
200 BTF_KIND_PTR | BTF_KIND_FWD | BTF_KIND_TYPEDEF | BTF_KIND_VOLATILE | BTF_KIND_CONST
201 | BTF_KIND_RESTRICT | BTF_KIND_FUNC | BTF_KIND_FLOAT | BTF_KIND_TYPE_TAG => 0,
202 _ => return Err(TlsLayoutError::Invalid("unknown BTF kind")),
203 };
204
205 if extra_size != 0 {
206 offset = offset
207 .checked_add(extra_size)
208 .ok_or(TlsLayoutError::Invalid("BTF type offset overflow"))?;
209 if offset > type_end {
210 return Err(TlsLayoutError::Invalid("truncated BTF type payload"));
211 }
212 }
213
214 records.push(Some(BtfRecord {
215 name,
216 kind,
217 members,
218 }));
219 }
220
221 Ok(records)
222}
223
224fn find_member_bit_offset(
225 records: &[Option<BtfRecord>],
226 struct_name: &'static str,
227 member_name: &'static str,
228) -> Result<u32, TlsLayoutError> {
229 let record = records
230 .iter()
231 .flatten()
232 .find(|record| record.kind == BTF_KIND_STRUCT && record.name == struct_name)
233 .ok_or(TlsLayoutError::Missing(struct_name))?;
234
235 record
236 .members
237 .iter()
238 .find(|member| member.name == member_name)
239 .map(|member| member.bit_offset)
240 .ok_or(TlsLayoutError::Missing(member_name))
241}
242
243fn read_btf_string(strings: &[u8], offset: u32) -> Result<String, TlsLayoutError> {
244 let start = offset as usize;
245 if start >= strings.len() {
246 return Err(TlsLayoutError::Invalid("BTF string offset out of bounds"));
247 }
248 let tail = &strings[start..];
249 let nul = tail
250 .iter()
251 .position(|byte| *byte == 0)
252 .ok_or(TlsLayoutError::Invalid("unterminated BTF string"))?;
253 Ok(String::from_utf8_lossy(&tail[..nul]).into_owned())
254}
255
256fn checked_range_start(base: usize, offset: usize) -> Result<usize, TlsLayoutError> {
257 base.checked_add(offset)
258 .ok_or(TlsLayoutError::Invalid("range start overflow"))
259}
260
261fn checked_range_end(start: usize, len: usize, data_len: usize) -> Result<usize, TlsLayoutError> {
262 let end = start
263 .checked_add(len)
264 .ok_or(TlsLayoutError::Invalid("range end overflow"))?;
265 if end > data_len {
266 return Err(TlsLayoutError::Invalid("range out of bounds"));
267 }
268 Ok(end)
269}
270
271fn round_up(value: u64, align: u64) -> Result<u64, TlsLayoutError> {
272 if align <= 1 {
273 return Ok(value);
274 }
275 let addend = align - 1;
276 let rounded = value
277 .checked_add(addend)
278 .ok_or(TlsLayoutError::Invalid("TLS alignment overflow"))?
279 / align
280 * align;
281 Ok(rounded)
282}
283
284fn read_u16(data: &[u8], offset: usize) -> Result<u16, TlsLayoutError> {
285 let bytes = data
286 .get(offset..offset + 2)
287 .ok_or(TlsLayoutError::Invalid("u16 read out of bounds"))?;
288 Ok(u16::from_le_bytes([bytes[0], bytes[1]]))
289}
290
291fn read_u32(data: &[u8], offset: usize) -> Result<u32, TlsLayoutError> {
292 let bytes = data
293 .get(offset..offset + 4)
294 .ok_or(TlsLayoutError::Invalid("u32 read out of bounds"))?;
295 Ok(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
296}
297
298fn read_u64(data: &[u8], offset: usize) -> Result<u64, TlsLayoutError> {
299 let bytes = data
300 .get(offset..offset + 8)
301 .ok_or(TlsLayoutError::Invalid("u64 read out of bounds"))?;
302 Ok(u64::from_le_bytes([
303 bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
304 ]))
305}