use padlock_core::arch::ArchConfig;
use padlock_core::ir::{AccessPattern, Field, StructLayout, TypeInfo};
const BTF_MAGIC: u16 = 0xEB9F;
const BTF_KIND_INT: u32 = 1;
const BTF_KIND_PTR: u32 = 2;
const BTF_KIND_ARRAY: u32 = 3;
const BTF_KIND_STRUCT: u32 = 4;
const BTF_KIND_UNION: u32 = 5;
const BTF_KIND_ENUM: u32 = 6;
const BTF_KIND_FWD: u32 = 7;
const BTF_KIND_TYPEDEF: u32 = 8;
const BTF_KIND_VOLATILE: u32 = 9;
const BTF_KIND_CONST: u32 = 10;
const BTF_KIND_RESTRICT: u32 = 11;
const BTF_KIND_FUNC: u32 = 12;
const BTF_KIND_FUNC_PROTO: u32 = 13;
const BTF_KIND_VAR: u32 = 14;
const BTF_KIND_DATASEC: u32 = 15;
const BTF_KIND_FLOAT: u32 = 16;
const BTF_KIND_DECL_TAG: u32 = 17;
const BTF_KIND_TYPE_TAG: u32 = 18;
const BTF_KIND_ENUM64: u32 = 19;
#[derive(Debug, Clone)]
struct BtfHeader {
hdr_len: u32,
type_off: u32,
type_len: u32,
str_off: u32,
str_len: u32,
}
#[derive(Debug, Clone)]
struct RawBtfType {
info: u32,
}
impl RawBtfType {
fn kind(&self) -> u32 {
(self.info >> 24) & 0x1f
}
fn vlen(&self) -> u32 {
self.info & 0xffff
}
fn kind_flag(&self) -> bool {
(self.info >> 31) & 1 == 1
}
}
#[derive(Debug, Clone)]
enum BtfType {
Int {
name: String,
size: u32,
},
Ptr,
Array {
elem_type: u32,
nelems: u32,
},
Struct {
name: String,
size: u32,
members: Vec<BtfMember>,
is_union: bool,
},
Enum {
size: u32,
},
Typedef {
type_id: u32,
},
Qualifier {
type_id: u32,
}, Float {
size: u32,
},
Unknown,
}
#[derive(Debug, Clone)]
struct BtfMember {
name: String,
type_id: u32,
bit_offset: u32,
bitfield_size: u32, }
pub struct BtfParser<'a> {
data: &'a [u8],
types: Vec<BtfType>,
arch: &'static ArchConfig,
}
impl<'a> BtfParser<'a> {
pub fn new(data: &'a [u8], arch: &'static ArchConfig) -> anyhow::Result<Self> {
let mut p = BtfParser {
data,
types: Vec::new(),
arch,
};
p.parse()?;
Ok(p)
}
fn parse(&mut self) -> anyhow::Result<()> {
if self.data.len() < 24 {
anyhow::bail!("BTF data too short");
}
let magic = u16::from_le_bytes([self.data[0], self.data[1]]);
if magic != BTF_MAGIC {
anyhow::bail!("invalid BTF magic: 0x{:04x}", magic);
}
let hdr = BtfHeader {
hdr_len: u32::from_le_bytes(self.data[4..8].try_into()?),
type_off: u32::from_le_bytes(self.data[8..12].try_into()?),
type_len: u32::from_le_bytes(self.data[12..16].try_into()?),
str_off: u32::from_le_bytes(self.data[16..20].try_into()?),
str_len: u32::from_le_bytes(self.data[20..24].try_into()?),
};
let type_base = hdr.hdr_len as usize + hdr.type_off as usize;
let type_end = type_base + hdr.type_len as usize;
let str_base = hdr.hdr_len as usize + hdr.str_off as usize;
let str_end = str_base + hdr.str_len as usize;
if type_end > self.data.len() || str_end > self.data.len() {
anyhow::bail!("BTF sections extend beyond data");
}
let type_data = &self.data[type_base..type_end];
let str_data = &self.data[str_base..str_end];
self.types.push(BtfType::Unknown);
let mut off = 0usize;
while off + 12 <= type_data.len() {
let name_off = u32::from_le_bytes(type_data[off..off + 4].try_into()?);
let info = u32::from_le_bytes(type_data[off + 4..off + 8].try_into()?);
let size_or_type = u32::from_le_bytes(type_data[off + 8..off + 12].try_into()?);
off += 12;
let raw = RawBtfType { info };
let name = read_btf_str(str_data, name_off as usize);
let kind = raw.kind();
let vlen = raw.vlen() as usize;
let btf_type = match kind {
BTF_KIND_INT => {
off += 4; BtfType::Int {
name,
size: size_or_type,
}
}
BTF_KIND_PTR => BtfType::Ptr,
BTF_KIND_ARRAY => {
if off + 12 > type_data.len() {
break;
}
let elem_type = u32::from_le_bytes(type_data[off..off + 4].try_into()?);
let nelems = u32::from_le_bytes(type_data[off + 8..off + 12].try_into()?);
off += 12;
BtfType::Array { elem_type, nelems }
}
BTF_KIND_STRUCT | BTF_KIND_UNION => {
let mut members = Vec::with_capacity(vlen);
for _ in 0..vlen {
if off + 12 > type_data.len() {
break;
}
let m_name_off = u32::from_le_bytes(type_data[off..off + 4].try_into()?);
let m_type = u32::from_le_bytes(type_data[off + 4..off + 8].try_into()?);
let m_offset = u32::from_le_bytes(type_data[off + 8..off + 12].try_into()?);
off += 12;
let (bit_offset, bitfield_size) = if raw.kind_flag() {
(m_offset & 0xffffff, (m_offset >> 24) & 0xff)
} else {
(m_offset, 0)
};
members.push(BtfMember {
name: read_btf_str(str_data, m_name_off as usize),
type_id: m_type,
bit_offset,
bitfield_size,
});
}
BtfType::Struct {
name,
size: size_or_type,
members,
is_union: kind == BTF_KIND_UNION,
}
}
BTF_KIND_ENUM => {
off += vlen * 8; BtfType::Enum { size: size_or_type }
}
BTF_KIND_TYPEDEF => BtfType::Typedef {
type_id: size_or_type,
},
BTF_KIND_VOLATILE | BTF_KIND_CONST | BTF_KIND_RESTRICT => BtfType::Qualifier {
type_id: size_or_type,
},
BTF_KIND_FLOAT => BtfType::Float { size: size_or_type },
BTF_KIND_ENUM64 => {
off += vlen * 12; BtfType::Enum { size: size_or_type }
}
BTF_KIND_FWD | BTF_KIND_FUNC | BTF_KIND_TYPE_TAG => BtfType::Unknown,
BTF_KIND_FUNC_PROTO => {
off += vlen * 8;
BtfType::Unknown
}
BTF_KIND_VAR => {
off += 4;
BtfType::Unknown
}
BTF_KIND_DATASEC => {
off += vlen * 12;
BtfType::Unknown
}
BTF_KIND_DECL_TAG => {
off += 4;
BtfType::Unknown
}
_ => {
break;
}
};
self.types.push(btf_type);
}
Ok(())
}
fn type_size(&self, type_id: u32) -> usize {
if type_id == 0 {
return 0; }
let idx = type_id as usize;
if idx >= self.types.len() {
return self.arch.pointer_size;
}
match &self.types[idx] {
BtfType::Int { size, .. } | BtfType::Float { size } | BtfType::Enum { size } => {
*size as usize
}
BtfType::Ptr => self.arch.pointer_size,
BtfType::Array { elem_type, nelems } => self.type_size(*elem_type) * (*nelems as usize),
BtfType::Struct { size, .. } => *size as usize,
BtfType::Typedef { type_id } | BtfType::Qualifier { type_id } => {
self.type_size(*type_id)
}
BtfType::Unknown => self.arch.pointer_size,
}
}
fn type_align(&self, type_id: u32) -> usize {
if type_id == 0 {
return 1;
}
let idx = type_id as usize;
if idx >= self.types.len() {
return self.arch.pointer_size;
}
match &self.types[idx] {
BtfType::Int { size, .. } | BtfType::Float { size } | BtfType::Enum { size } => {
(*size as usize).min(self.arch.max_align)
}
BtfType::Ptr => self.arch.pointer_size,
BtfType::Array { elem_type, .. } => self.type_align(*elem_type),
BtfType::Struct { members, .. } => members
.iter()
.map(|m| self.type_align(m.type_id))
.max()
.unwrap_or(1),
BtfType::Typedef { type_id } | BtfType::Qualifier { type_id } => {
self.type_align(*type_id)
}
BtfType::Unknown => self.arch.pointer_size,
}
}
fn type_name(&self, type_id: u32) -> String {
if type_id == 0 {
return "void".to_string();
}
let idx = type_id as usize;
if idx >= self.types.len() {
return format!("type_{}", type_id);
}
match &self.types[idx] {
BtfType::Int { name, .. } | BtfType::Struct { name, .. } => {
if name.is_empty() {
format!("type_{}", type_id)
} else {
name.clone()
}
}
BtfType::Float { size } => format!("f{}", size * 8),
BtfType::Ptr => "*".to_string(),
BtfType::Array { elem_type, nelems } => {
format!("[{}]{}", nelems, self.type_name(*elem_type))
}
BtfType::Enum { .. } => format!("enum_{}", type_id),
BtfType::Typedef { type_id } => self.type_name(*type_id),
BtfType::Qualifier { type_id } => self.type_name(*type_id),
BtfType::Unknown => format!("unknown_{}", type_id),
}
}
pub fn extract_structs(&self) -> Vec<StructLayout> {
let mut layouts = Vec::new();
for (idx, ty) in self.types.iter().enumerate() {
if let BtfType::Struct {
name,
size,
members,
is_union,
} = ty
{
if name.is_empty() {
continue;
}
let mut fields: Vec<Field> = Vec::new();
let mut covered_until: usize = 0;
for member in members {
let is_bitfield = member.bitfield_size != 0 || member.bit_offset % 8 != 0;
if is_bitfield {
let storage_size = self.type_size(member.type_id).max(1);
let storage_bits = (storage_size * 8) as u32;
let unit_start_bit = (member.bit_offset / storage_bits) * storage_bits;
let unit_byte_offset = (unit_start_bit / 8) as usize;
let unit_end = unit_byte_offset + storage_size;
if unit_byte_offset >= covered_until {
let fname = format!("{}__bits", member.name);
let falign = storage_size.min(self.arch.max_align);
fields.push(Field {
name: fname.clone(),
ty: TypeInfo::Primitive {
name: format!("u{}", storage_size * 8),
size: storage_size,
align: falign,
},
offset: unit_byte_offset,
size: storage_size,
align: falign,
source_file: None,
source_line: None,
access: AccessPattern::Unknown,
});
covered_until = unit_end;
}
continue;
}
let byte_offset = (member.bit_offset / 8) as usize;
let fsize = self.type_size(member.type_id);
let falign = self.type_align(member.type_id);
let fname = if member.name.is_empty() {
format!("field_{}", fields.len())
} else {
member.name.clone()
};
covered_until = covered_until.max(byte_offset + fsize);
fields.push(Field {
name: fname.clone(),
ty: TypeInfo::Primitive {
name: self.type_name(member.type_id),
size: fsize,
align: falign,
},
offset: byte_offset,
size: fsize,
align: falign,
source_file: None,
source_line: None,
access: AccessPattern::Unknown,
});
}
if fields.is_empty() {
continue;
}
let max_align = fields.iter().map(|f| f.align).max().unwrap_or(1);
let natural_size = {
let mut off2 = 0usize;
for f in &fields {
if max_align > 0 {
off2 = off2.next_multiple_of(f.align.max(1));
}
off2 += f.size;
}
if max_align > 0 {
off2 = off2.next_multiple_of(max_align.max(1));
}
off2
};
let is_packed = !*is_union && (*size as usize) < natural_size;
layouts.push(StructLayout {
name: name.clone(),
total_size: *size as usize,
align: max_align,
fields,
source_file: None,
source_line: None,
arch: self.arch,
is_packed,
is_union: *is_union,
is_repr_rust: false,
suppressed_findings: Vec::new(),
uncertain_fields: Vec::new(),
});
let _ = idx;
}
}
layouts
}
}
fn read_btf_str(str_data: &[u8], off: usize) -> String {
if off >= str_data.len() {
return String::new();
}
let end = str_data[off..]
.iter()
.position(|&b| b == 0)
.map(|p| off + p)
.unwrap_or(str_data.len());
String::from_utf8_lossy(&str_data[off..end]).into_owned()
}
pub fn extract_from_btf(
btf_data: &[u8],
arch: &'static ArchConfig,
) -> anyhow::Result<Vec<StructLayout>> {
let parser = BtfParser::new(btf_data, arch)?;
Ok(parser.extract_structs())
}
#[cfg(test)]
mod tests {
use super::*;
use padlock_core::arch::X86_64_SYSV;
fn build_test_btf() -> Vec<u8> {
let strings: &[u8] = b"\0point\0x\0y\0";
let str_len = strings.len() as u32;
let mut type_data: Vec<u8> = Vec::new();
let x_name_off: u32 = 7;
type_data.extend_from_slice(&x_name_off.to_le_bytes());
type_data.extend_from_slice(&(BTF_KIND_INT << 24).to_le_bytes());
type_data.extend_from_slice(&4u32.to_le_bytes());
type_data.extend_from_slice(&0u32.to_le_bytes());
let y_name_off: u32 = 9;
type_data.extend_from_slice(&y_name_off.to_le_bytes());
type_data.extend_from_slice(&(BTF_KIND_INT << 24).to_le_bytes());
type_data.extend_from_slice(&4u32.to_le_bytes());
type_data.extend_from_slice(&0u32.to_le_bytes());
let point_name_off: u32 = 1;
type_data.extend_from_slice(&point_name_off.to_le_bytes());
type_data.extend_from_slice(&((BTF_KIND_STRUCT << 24) | 2u32).to_le_bytes()); type_data.extend_from_slice(&8u32.to_le_bytes()); type_data.extend_from_slice(&x_name_off.to_le_bytes()); type_data.extend_from_slice(&1u32.to_le_bytes()); type_data.extend_from_slice(&0u32.to_le_bytes()); type_data.extend_from_slice(&y_name_off.to_le_bytes()); type_data.extend_from_slice(&2u32.to_le_bytes()); type_data.extend_from_slice(&32u32.to_le_bytes());
let type_len = type_data.len() as u32;
let hdr_len: u32 = 24;
let mut btf = Vec::new();
btf.extend_from_slice(&BTF_MAGIC.to_le_bytes()); btf.push(1); btf.push(0); btf.extend_from_slice(&hdr_len.to_le_bytes()); btf.extend_from_slice(&0u32.to_le_bytes()); btf.extend_from_slice(&type_len.to_le_bytes()); btf.extend_from_slice(&type_len.to_le_bytes()); btf.extend_from_slice(&str_len.to_le_bytes()); btf.extend_from_slice(&type_data);
btf.extend_from_slice(strings);
btf
}
#[test]
fn btf_parse_simple_struct() {
let btf = build_test_btf();
let layouts = extract_from_btf(&btf, &X86_64_SYSV).unwrap();
assert_eq!(layouts.len(), 1);
assert_eq!(layouts[0].name, "point");
assert_eq!(layouts[0].total_size, 8);
assert_eq!(layouts[0].fields.len(), 2);
}
#[test]
fn btf_field_offsets_correct() {
let btf = build_test_btf();
let layouts = extract_from_btf(&btf, &X86_64_SYSV).unwrap();
let l = &layouts[0];
assert_eq!(l.fields[0].name, "x");
assert_eq!(l.fields[0].offset, 0);
assert_eq!(l.fields[1].name, "y");
assert_eq!(l.fields[1].offset, 4);
}
#[test]
fn btf_invalid_magic_errors() {
let mut btf = build_test_btf();
btf[0] = 0xff;
btf[1] = 0xff;
assert!(extract_from_btf(&btf, &X86_64_SYSV).is_err());
}
#[test]
fn btf_bitfield_members_become_synthetic_storage_unit_fields() {
let strings: &[u8] = b"\0mystruct\0flags\0";
let str_len = strings.len() as u32;
let mut type_data: Vec<u8> = Vec::new();
let flags_name_off: u32 = 10;
type_data.extend_from_slice(&flags_name_off.to_le_bytes());
type_data.extend_from_slice(&(BTF_KIND_INT << 24).to_le_bytes());
type_data.extend_from_slice(&4u32.to_le_bytes());
type_data.extend_from_slice(&0u32.to_le_bytes());
let struct_name_off: u32 = 1;
let struct_info: u32 = (1u32 << 31) | (BTF_KIND_STRUCT << 24) | 1u32;
type_data.extend_from_slice(&struct_name_off.to_le_bytes());
type_data.extend_from_slice(&struct_info.to_le_bytes());
type_data.extend_from_slice(&4u32.to_le_bytes()); let m_offset: u32 = (3u32 << 24) | 0u32; type_data.extend_from_slice(&flags_name_off.to_le_bytes());
type_data.extend_from_slice(&1u32.to_le_bytes()); type_data.extend_from_slice(&m_offset.to_le_bytes());
let type_len = type_data.len() as u32;
let hdr_len: u32 = 24;
let mut btf = Vec::new();
btf.extend_from_slice(&BTF_MAGIC.to_le_bytes());
btf.push(1);
btf.push(0);
btf.extend_from_slice(&hdr_len.to_le_bytes());
btf.extend_from_slice(&0u32.to_le_bytes());
btf.extend_from_slice(&type_len.to_le_bytes());
btf.extend_from_slice(&type_len.to_le_bytes());
btf.extend_from_slice(&str_len.to_le_bytes());
btf.extend_from_slice(&type_data);
btf.extend_from_slice(strings);
let layouts = extract_from_btf(&btf, &X86_64_SYSV).unwrap();
assert_eq!(layouts.len(), 1);
let l = &layouts[0];
assert_eq!(l.name, "mystruct");
assert_eq!(l.fields.len(), 1);
assert_eq!(l.fields[0].offset, 0);
assert_eq!(l.fields[0].size, 4); assert!(l.fields[0].name.ends_with("__bits"));
}
#[test]
fn btf_skips_unknown_kinds_gracefully() {
let strings: &[u8] = b"\0foo\0x\0myfunc\0";
let str_len = strings.len() as u32;
let mut type_data: Vec<u8> = Vec::new();
type_data.extend_from_slice(&5u32.to_le_bytes());
type_data.extend_from_slice(&(BTF_KIND_INT << 24).to_le_bytes());
type_data.extend_from_slice(&4u32.to_le_bytes());
type_data.extend_from_slice(&0u32.to_le_bytes());
type_data.extend_from_slice(&7u32.to_le_bytes());
type_data.extend_from_slice(&(BTF_KIND_FUNC << 24).to_le_bytes());
type_data.extend_from_slice(&1u32.to_le_bytes());
type_data.extend_from_slice(&1u32.to_le_bytes());
type_data.extend_from_slice(&((BTF_KIND_STRUCT << 24) | 1u32).to_le_bytes());
type_data.extend_from_slice(&4u32.to_le_bytes());
type_data.extend_from_slice(&5u32.to_le_bytes());
type_data.extend_from_slice(&1u32.to_le_bytes());
type_data.extend_from_slice(&0u32.to_le_bytes());
let type_len = type_data.len() as u32;
let hdr_len: u32 = 24;
let mut btf = Vec::new();
btf.extend_from_slice(&BTF_MAGIC.to_le_bytes());
btf.push(1);
btf.push(0);
btf.extend_from_slice(&hdr_len.to_le_bytes());
btf.extend_from_slice(&0u32.to_le_bytes());
btf.extend_from_slice(&type_len.to_le_bytes());
btf.extend_from_slice(&type_len.to_le_bytes());
btf.extend_from_slice(&str_len.to_le_bytes());
btf.extend_from_slice(&type_data);
btf.extend_from_slice(strings);
let layouts = extract_from_btf(&btf, &X86_64_SYSV).unwrap();
assert_eq!(layouts.len(), 1);
assert_eq!(layouts[0].name, "foo");
assert_eq!(layouts[0].fields[0].name, "x");
}
}