From 379bb313b13dd259a26fe3513ae6784bb85291ef Mon Sep 17 00:00:00 2001 From: Dave Tucker Date: Thu, 2 Dec 2021 01:16:01 +0000 Subject: [PATCH 1/3] obj: Add Btf::to_bytes This allows for parsed BTF to be re-encoded such that it could be loaded in to the kernel. It moves bytes_of to the utils package. We could use Object::bytes_of, but this requires the impl of the Pod trait on generated code. Signed-off-by: Dave Tucker --- aya/src/obj/btf/btf.rs | 66 ++++++++++++++++++ aya/src/obj/btf/types.rs | 143 ++++++++++++++++++++++++++++++++++----- 2 files changed, 192 insertions(+), 17 deletions(-) diff --git a/aya/src/obj/btf/btf.rs b/aya/src/obj/btf/btf.rs index a6ba2990..67a6cb11 100644 --- a/aya/src/obj/btf/btf.rs +++ b/aya/src/obj/btf/btf.rs @@ -7,12 +7,15 @@ use std::{ ptr, }; +use bytes::BufMut; + use object::Endianness; use thiserror::Error; use crate::{ generated::{btf_ext_header, btf_header}, obj::btf::{relocation::Relocation, BtfKind, BtfType}, + util::bytes_of, }; pub(crate) const MAX_RESOLVE_DEPTH: u8 = 32; @@ -291,6 +294,17 @@ impl Btf { type_id: root_type_id, }) } + + pub(crate) fn to_bytes(&self) -> Vec { + // Safety: btf_header is POD + let mut buf = unsafe { bytes_of::(&self.header).to_vec() }; + for t in self.types() { + let b = t.to_bytes(); + buf.put(b.as_slice()) + } + buf.put(self.strings.as_slice()); + buf + } } unsafe fn read_btf_header(data: &[u8]) -> btf_header { @@ -472,4 +486,56 @@ mod tests { assert_eq!(header.str_off, 0x2a5464); assert_eq!(header.str_len, 0x1c6410); } + + #[test] + fn test_parse_btf() { + // this generated BTF data is from an XDP program that simply returns XDP_PASS + // compiled using clang + let data: &[u8] = &[ + 0x9f, 0xeb, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x01, + 0x00, 0x00, 0x0c, 0x01, 0x00, 0x00, 0xe1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, + 0x00, 0x04, 0x18, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x20, 0x00, + 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x30, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x04, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x0d, 0x06, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, + 0x00, 0x01, 0x69, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0c, 0x05, 0x00, 0x00, 0x00, + 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xbc, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x09, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0xd9, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x78, + 0x64, 0x70, 0x5f, 0x6d, 0x64, 0x00, 0x64, 0x61, 0x74, 0x61, 0x00, 0x64, 0x61, 0x74, + 0x61, 0x5f, 0x65, 0x6e, 0x64, 0x00, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6d, 0x65, 0x74, + 0x61, 0x00, 0x69, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x69, 0x66, 0x69, 0x6e, + 0x64, 0x65, 0x78, 0x00, 0x72, 0x78, 0x5f, 0x71, 0x75, 0x65, 0x75, 0x65, 0x5f, 0x69, + 0x6e, 0x64, 0x65, 0x78, 0x00, 0x65, 0x67, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x69, 0x66, + 0x69, 0x6e, 0x64, 0x65, 0x78, 0x00, 0x5f, 0x5f, 0x75, 0x33, 0x32, 0x00, 0x75, 0x6e, + 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x74, 0x00, 0x63, 0x74, 0x78, + 0x00, 0x69, 0x6e, 0x74, 0x00, 0x78, 0x64, 0x70, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x00, + 0x78, 0x64, 0x70, 0x2f, 0x70, 0x61, 0x73, 0x73, 0x00, 0x2f, 0x68, 0x6f, 0x6d, 0x65, + 0x2f, 0x64, 0x61, 0x76, 0x65, 0x2f, 0x64, 0x65, 0x76, 0x2f, 0x62, 0x70, 0x66, 0x64, + 0x2f, 0x62, 0x70, 0x66, 0x2f, 0x78, 0x64, 0x70, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x2e, + 0x62, 0x70, 0x66, 0x2e, 0x63, 0x00, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, + 0x72, 0x6e, 0x20, 0x58, 0x44, 0x50, 0x5f, 0x50, 0x41, 0x53, 0x53, 0x3b, 0x00, 0x63, + 0x68, 0x61, 0x72, 0x00, 0x5f, 0x5f, 0x41, 0x52, 0x52, 0x41, 0x59, 0x5f, 0x53, 0x49, + 0x5a, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x5f, 0x00, 0x5f, 0x6c, 0x69, 0x63, + 0x65, 0x6e, 0x73, 0x65, 0x00, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x00, + ]; + let got = Btf::parse(data, Endianness::default()); + match got { + Ok(_) => {} + Err(e) => panic!("{}", e), + } + let data2 = got.unwrap().to_bytes(); + assert_eq!(data, data2); + } } diff --git a/aya/src/obj/btf/types.rs b/aya/src/obj/btf/types.rs index acbe57c6..9bda5242 100644 --- a/aya/src/obj/btf/types.rs +++ b/aya/src/obj/btf/types.rs @@ -149,6 +149,67 @@ impl BtfType { }) } + pub(crate) fn to_bytes(&self) -> Vec { + fn bytes_of(val: &T) -> &[u8] { + // Safety: all btf types are POD + unsafe { crate::util::bytes_of(val) } + } + match self { + BtfType::Fwd(btf_type) + | BtfType::Const(btf_type) + | BtfType::Volatile(btf_type) + | BtfType::Restrict(btf_type) + | BtfType::Ptr(btf_type) + | BtfType::Typedef(btf_type) + | BtfType::Func(btf_type) + | BtfType::Float(btf_type) => bytes_of::(btf_type).to_vec(), + BtfType::Int(btf_type, len) => { + let mut buf = bytes_of::(btf_type).to_vec(); + buf.append(&mut len.to_ne_bytes().to_vec()); + buf + } + BtfType::Enum(btf_type, enums) => { + let mut buf = bytes_of::(btf_type).to_vec(); + for en in enums { + buf.append(&mut bytes_of::(en).to_vec()); + } + buf + } + BtfType::Array(btf_type, btf_array) => { + let mut buf = bytes_of::(btf_type).to_vec(); + buf.append(&mut bytes_of::(btf_array).to_vec()); + buf + } + BtfType::Struct(btf_type, btf_members) | BtfType::Union(btf_type, btf_members) => { + let mut buf = bytes_of::(btf_type).to_vec(); + for m in btf_members { + buf.append(&mut bytes_of::(m).to_vec()); + } + buf + } + BtfType::FuncProto(btf_type, btf_params) => { + let mut buf = bytes_of::(btf_type).to_vec(); + for p in btf_params { + buf.append(&mut bytes_of::(p).to_vec()); + } + buf + } + BtfType::Var(btf_type, btf_var) => { + let mut buf = bytes_of::(btf_type).to_vec(); + buf.append(&mut bytes_of::(btf_var).to_vec()); + buf + } + BtfType::DataSec(btf_type, btf_var_secinfo) => { + let mut buf = bytes_of::(btf_type).to_vec(); + for s in btf_var_secinfo { + buf.append(&mut bytes_of::(s).to_vec()); + } + buf + } + BtfType::Unknown => vec![], + } + } + pub(crate) fn type_info_size(&self) -> usize { let ty_size = mem::size_of::(); @@ -406,7 +467,8 @@ mod tests { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, ]; - match unsafe { BtfType::read(data, endianness) } { + let got = unsafe { BtfType::read(data, endianness) }; + match got { Ok(BtfType::Int(ty, nr_bits)) => { assert_eq!(ty.name_off, 1); assert_eq!(nr_bits, 64); @@ -414,6 +476,8 @@ mod tests { Ok(t) => panic!("expected int type, got {:#?}", t), Err(_) => panic!("unexpected error"), } + let data2 = got.unwrap().to_bytes(); + assert_eq!(data, data2.as_slice()) } #[test] @@ -422,11 +486,14 @@ mod tests { let data: &[u8] = &[ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x06, 0x00, 0x00, 0x00, ]; - match unsafe { BtfType::read(data, endianness) } { + let got = unsafe { BtfType::read(data, endianness) }; + match got { Ok(BtfType::Ptr(_)) => {} Ok(t) => panic!("expected ptr type, got {:#?}", t), Err(_) => panic!("unexpected error"), } + let data2 = got.unwrap().to_bytes(); + assert_eq!(data, data2.as_slice()) } #[test] @@ -436,11 +503,14 @@ mod tests { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, ]; - match unsafe { BtfType::read(data, endianness) } { + let got = unsafe { BtfType::read(data, endianness) }; + match got { Ok(BtfType::Array(_, _)) => {} Ok(t) => panic!("expected array type, got {:#?}", t), Err(_) => panic!("unexpected error"), } + let data2 = got.unwrap().to_bytes(); + assert_eq!(data, data2.as_slice()) } #[test] @@ -450,11 +520,14 @@ mod tests { 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x04, 0x04, 0x00, 0x00, 0x00, 0x47, 0x02, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; - match unsafe { BtfType::read(data, endianness) } { + let got = unsafe { BtfType::read(data, endianness) }; + match got { Ok(BtfType::Struct(_, _)) => {} Ok(t) => panic!("expected struct type, got {:#?}", t), Err(_) => panic!("unexpected error"), } + let data2 = got.unwrap().to_bytes(); + assert_eq!(data, data2.as_slice()) } #[test] @@ -464,11 +537,14 @@ mod tests { 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x05, 0x04, 0x00, 0x00, 0x00, 0x0d, 0x04, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; - match unsafe { BtfType::read(data, endianness) } { + let got = unsafe { BtfType::read(data, endianness) }; + match got { Ok(BtfType::Union(_, _)) => {} Ok(t) => panic!("expected union type, got {:#?}", t), Err(_) => panic!("unexpected error"), } + let data2 = got.unwrap().to_bytes(); + assert_eq!(data, data2.as_slice()) } #[test] @@ -478,11 +554,14 @@ mod tests { 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x06, 0x04, 0x00, 0x00, 0x00, 0xc9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcf, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, ]; - match unsafe { BtfType::read(data, endianness) } { + let got = unsafe { BtfType::read(data, endianness) }; + match got { Ok(BtfType::Enum(_, _)) => {} Ok(t) => panic!("expected enum type, got {:#?}", t), Err(_) => panic!("unexpected error"), } + let data2 = got.unwrap().to_bytes(); + assert_eq!(data, data2.as_slice()) } #[test] @@ -491,11 +570,14 @@ mod tests { let data: &[u8] = &[ 0x0b, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, ]; - match unsafe { BtfType::read(data, endianness) } { + let got = unsafe { BtfType::read(data, endianness) }; + match got { Ok(BtfType::Fwd(_)) => {} Ok(t) => panic!("expected fwd type, got {:#?}", t), Err(_) => panic!("unexpected error"), } + let data2 = got.unwrap().to_bytes(); + assert_eq!(data, data2.as_slice()) } #[test] @@ -504,11 +586,14 @@ mod tests { let data: &[u8] = &[ 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x0b, 0x00, 0x00, 0x00, ]; - match unsafe { BtfType::read(data, endianness) } { + let got = unsafe { BtfType::read(data, endianness) }; + match got { Ok(BtfType::Typedef(_)) => {} Ok(t) => panic!("expected typedef type, got {:#?}", t), Err(_) => panic!("unexpected error"), } + let data2 = got.unwrap().to_bytes(); + assert_eq!(data, data2.as_slice()) } #[test] @@ -517,11 +602,14 @@ mod tests { let data: &[u8] = &[ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x24, 0x00, 0x00, 0x00, ]; - match unsafe { BtfType::read(data, endianness) } { + let got = unsafe { BtfType::read(data, endianness) }; + match got { Ok(BtfType::Volatile(_)) => {} Ok(t) => panic!("expected volatile type, got {:#?}", t), Err(_) => panic!("unexpected error"), } + let data2 = got.unwrap().to_bytes(); + assert_eq!(data, data2.as_slice()) } #[test] @@ -530,11 +618,14 @@ mod tests { let data: &[u8] = &[ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x01, 0x00, 0x00, 0x00, ]; - match unsafe { BtfType::read(data, endianness) } { + let got = unsafe { BtfType::read(data, endianness) }; + match got { Ok(BtfType::Const(_)) => {} Ok(t) => panic!("expected const type, got {:#?}", t), Err(_) => panic!("unexpected error"), } + let data2 = got.unwrap().to_bytes(); + assert_eq!(data, data2.as_slice()) } #[test] @@ -543,11 +634,14 @@ mod tests { let data: &[u8] = &[ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x04, 0x00, 0x00, 0x00, ]; - match unsafe { BtfType::read(data, endianness) } { + let got = unsafe { BtfType::read(data, endianness) }; + match got { Ok(BtfType::Restrict(_)) => {} Ok(t) => panic!("expected restrict type gpt {:#?}", t), Err(_) => panic!("unexpected error"), } + let data2 = got.unwrap().to_bytes(); + assert_eq!(data, data2.as_slice()) } #[test] @@ -556,11 +650,14 @@ mod tests { let data: &[u8] = &[ 0x17, 0x8b, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xf0, 0xe4, 0x00, 0x00, ]; - match unsafe { BtfType::read(data, endianness) } { + let got = unsafe { BtfType::read(data, endianness) }; + match got { Ok(BtfType::Func(_)) => {} Ok(t) => panic!("expected func type gpt {:#?}", t), Err(_) => panic!("unexpected error"), } + let data2 = got.unwrap().to_bytes(); + assert_eq!(data, data2.as_slice()) } #[test] @@ -570,11 +667,14 @@ mod tests { 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, ]; - match unsafe { BtfType::read(data, endianness) } { + let got = unsafe { BtfType::read(data, endianness) }; + match got { Ok(BtfType::FuncProto(_, _)) => {} Ok(t) => panic!("expected func_proto type, got {:#?}", t), Err(_) => panic!("unexpected error"), } + let data2 = got.unwrap().to_bytes(); + assert_eq!(data, data2.as_slice()) } #[test] @@ -585,11 +685,14 @@ mod tests { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; - match unsafe { BtfType::read(data, endianness) } { + let got = unsafe { BtfType::read(data, endianness) }; + match got { Ok(BtfType::Var(_, _)) => {} Ok(t) => panic!("expected var type, got {:#?}", t), Err(_) => panic!("unexpected error"), - } + }; + let data2 = got.unwrap().to_bytes(); + assert_eq!(data, data2.as_slice()) } #[test] @@ -600,11 +703,14 @@ mod tests { 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; - match unsafe { BtfType::read(data, endianness) } { + let got = unsafe { BtfType::read(data, endianness) }; + match got { Ok(BtfType::DataSec(_, _)) => {} Ok(t) => panic!("expected datasec type, got {:#?}", t), Err(_) => panic!("unexpected error"), } + let data2 = got.unwrap().to_bytes(); + assert_eq!(data, data2.as_slice()) } #[test] @@ -613,10 +719,13 @@ mod tests { let data: &[u8] = &[ 0x78, 0xfd, 0x02, 0x00, 0x00, 0x00, 0x00, 0x10, 0x08, 0x00, 0x00, 0x00, ]; - match unsafe { BtfType::read(data, endianness) } { + let got = unsafe { BtfType::read(data, endianness) }; + match got { Ok(BtfType::Float(_)) => {} Ok(t) => panic!("expected float type, got {:#?}", t), Err(_) => panic!("unexpected error"), } + let data2 = got.unwrap().to_bytes(); + assert_eq!(data, data2.as_slice()) } } From 5c6131afba02e22531fa82d8f40444311aeec5c9 Mon Sep 17 00:00:00 2001 From: Dave Tucker Date: Wed, 8 Dec 2021 12:21:37 +0000 Subject: [PATCH 2/3] Add BPF_PROG_TYPE_EXT This requires loading the BTF to kernel when loading all programs as well as implementing Extension program type Signed-off-by: Dave Tucker --- aya/Cargo.toml | 1 + aya/src/bpf.rs | 251 ++++++++++++---- aya/src/obj/btf/btf.rs | 525 ++++++++++++++++++++++++++++++++- aya/src/obj/btf/info.rs | 189 ++++++++++++ aya/src/obj/btf/mod.rs | 2 + aya/src/obj/btf/types.rs | 216 +++++++++++++- aya/src/obj/mod.rs | 119 +++++++- aya/src/obj/relocation.rs | 34 ++- aya/src/programs/cgroup_skb.rs | 13 +- aya/src/programs/extension.rs | 140 +++++++++ aya/src/programs/mod.rs | 122 ++++---- aya/src/programs/xdp.rs | 10 +- aya/src/sys/bpf.rs | 310 ++++++++++++++++++- aya/src/util.rs | 57 +++- 14 files changed, 1813 insertions(+), 176 deletions(-) create mode 100644 aya/src/obj/btf/info.rs create mode 100644 aya/src/programs/extension.rs diff --git a/aya/Cargo.toml b/aya/Cargo.toml index aeb8f06f..cba636ca 100644 --- a/aya/Cargo.toml +++ b/aya/Cargo.toml @@ -22,6 +22,7 @@ futures = { version = "0.3.12", optional = true, default-features = false, featu tokio = { version = "1.2.0", features = ["macros", "rt", "rt-multi-thread", "net"], optional = true } async-std = { version = "1.9.0", optional = true } async-io = { version = "1.3", optional = true } +log = "0.4" [dev-dependencies] matches = "0.1.8" diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs index 1127556d..8de8384a 100644 --- a/aya/src/bpf.rs +++ b/aya/src/bpf.rs @@ -1,6 +1,6 @@ use std::{ borrow::Cow, - collections::HashMap, + collections::{HashMap, HashSet}, error::Error, ffi::CString, fs, io, @@ -8,6 +8,7 @@ use std::{ path::{Path, PathBuf}, }; +use log::debug; use thiserror::Error; use crate::{ @@ -21,12 +22,16 @@ use crate::{ MapKind, Object, ParseError, ProgramSection, }, programs::{ - BtfTracePoint, CgroupSkb, CgroupSkbAttachType, FEntry, FExit, KProbe, LircMode2, Lsm, - PerfEvent, ProbeKind, Program, ProgramData, ProgramError, RawTracePoint, SchedClassifier, - SkMsg, SkSkb, SkSkbKind, SockOps, SocketFilter, TracePoint, UProbe, Xdp, + BtfTracePoint, CgroupSkb, CgroupSkbAttachType, Extension, FEntry, FExit, KProbe, LircMode2, + Lsm, PerfEvent, ProbeKind, Program, ProgramData, ProgramError, RawTracePoint, + SchedClassifier, SkMsg, SkSkb, SkSkbKind, SockOps, SocketFilter, TracePoint, UProbe, Xdp, }, - sys::{bpf_map_freeze, bpf_map_update_elem_ptr}, - util::{bytes_of, possible_cpus, POSSIBLE_CPUS}, + sys::{ + bpf_load_btf, bpf_map_freeze, bpf_map_update_elem_ptr, is_btf_datasec_supported, + is_btf_float_supported, is_btf_func_global_supported, is_btf_func_supported, + is_btf_supported, is_prog_name_supported, + }, + util::{bytes_of, possible_cpus, VerifierLog, POSSIBLE_CPUS}, }; pub(crate) const BPF_OBJ_NAME_LEN: usize = 16; @@ -77,6 +82,47 @@ impl Default for PinningType { } } +// Features implements BPF and BTF feature detection +#[derive(Default, Debug)] +pub(crate) struct Features { + pub bpf_name: bool, + pub btf: bool, + pub btf_func: bool, + pub btf_func_global: bool, + pub btf_datasec: bool, + pub btf_float: bool, +} + +impl Features { + fn probe_features(&mut self) { + self.bpf_name = is_prog_name_supported(); + debug!("[FEAT PROBE] BPF program name support: {}", self.bpf_name); + + self.btf = is_btf_supported(); + debug!("[FEAT PROBE] BTF support: {}", self.btf); + + if self.btf { + self.btf_func = is_btf_func_supported(); + debug!("[FEAT PROBE] BTF func support: {}", self.btf_func); + + self.btf_func_global = is_btf_func_global_supported(); + debug!( + "[FEAT PROBE] BTF global func support: {}", + self.btf_func_global + ); + + self.btf_datasec = is_btf_datasec_supported(); + debug!( + "[FEAT PROBE] BTF var and datasec support: {}", + self.btf_datasec + ); + + self.btf_float = is_btf_float_supported(); + debug!("[FEAT PROBE] BTF float support: {}", self.btf_float); + } + } +} + /// Builder style API for advanced loading of eBPF programs. /// /// Loading eBPF code involves a few steps, including loading maps and applying @@ -103,15 +149,21 @@ pub struct BpfLoader<'a> { btf: Option>, map_pin_path: Option, globals: HashMap<&'a str, &'a [u8]>, + features: Features, + extensions: HashSet<&'a str>, } impl<'a> BpfLoader<'a> { /// Creates a new loader instance. pub fn new() -> BpfLoader<'a> { + let mut features = Features::default(); + features.probe_features(); BpfLoader { btf: Btf::from_sys_fs().ok().map(Cow::Owned), map_pin_path: None, globals: HashMap::new(), + features, + extensions: HashSet::new(), } } @@ -187,6 +239,28 @@ impl<'a> BpfLoader<'a> { self } + /// Treat the provided program as an [`Extension`] + /// + /// When attempting to load the program with the provided `name` + /// the program type is forced to be ] [`Extension`] and is not + /// inferred from the ELF section name. + /// + /// # Example + /// + /// ```no_run + /// use aya::BpfLoader; + /// + /// let bpf = BpfLoader::new() + /// .extension("myfunc") + /// .load_file("file.o")?; + /// # Ok::<(), aya::BpfError>(()) + /// ``` + /// + pub fn extension(&mut self, name: &'a str) -> &mut BpfLoader<'a> { + self.extensions.insert(name); + self + } + /// Loads eBPF bytecode from a file. /// /// # Examples @@ -221,6 +295,39 @@ impl<'a> BpfLoader<'a> { let mut obj = Object::parse(data)?; obj.patch_map_data(self.globals.clone())?; + let btf_fd = if self.features.btf { + if let Some(ref mut obj_btf) = obj.btf { + // fixup btf + let section_data = obj.section_sizes.clone(); + let symbol_offsets = obj.symbol_offset_by_name.clone(); + obj_btf.fixup(§ion_data, &symbol_offsets)?; + let btf = obj_btf.sanitize(&self.features)?; + + // load btf to the kernel + let raw_btf = btf.to_bytes(); + let mut log_buf = VerifierLog::new(); + log_buf.grow(); + let ret = bpf_load_btf(raw_btf.as_slice(), &mut log_buf); + match ret { + Ok(fd) => Some(fd), + Err(io_error) => { + log_buf.truncate(); + return Err(BpfError::BtfError(BtfError::LoadError { + io_error, + verifier_log: log_buf + .as_c_str() + .map(|s| s.to_string_lossy().to_string()) + .unwrap_or_else(|| "[none]".to_owned()), + })); + } + } + } else { + None + } + } else { + None + }; + if let Some(btf) = &self.btf { obj.relocate_btf(btf)?; } @@ -288,76 +395,90 @@ impl<'a> BpfLoader<'a> { .programs .drain() .map(|(name, obj)| { + let prog_name = if self.features.bpf_name { + Some(name.clone()) + } else { + None + }; let data = ProgramData { + name: prog_name, obj, fd: None, links: Vec::new(), expected_attach_type: None, attach_btf_obj_fd: None, attach_btf_id: None, + attach_prog_fd: None, + btf_fd, }; - let program = match &data.obj.section { - ProgramSection::KProbe { .. } => Program::KProbe(KProbe { - data, - kind: ProbeKind::KProbe, - }), - ProgramSection::KRetProbe { .. } => Program::KProbe(KProbe { - data, - kind: ProbeKind::KRetProbe, - }), - ProgramSection::UProbe { .. } => Program::UProbe(UProbe { - data, - kind: ProbeKind::UProbe, - }), - ProgramSection::URetProbe { .. } => Program::UProbe(UProbe { - data, - kind: ProbeKind::URetProbe, - }), - ProgramSection::TracePoint { .. } => Program::TracePoint(TracePoint { data }), - ProgramSection::SocketFilter { .. } => { - Program::SocketFilter(SocketFilter { data }) - } - ProgramSection::Xdp { .. } => Program::Xdp(Xdp { data }), - ProgramSection::SkMsg { .. } => Program::SkMsg(SkMsg { data }), - ProgramSection::SkSkbStreamParser { .. } => Program::SkSkb(SkSkb { - data, - kind: SkSkbKind::StreamParser, - }), - ProgramSection::SkSkbStreamVerdict { .. } => Program::SkSkb(SkSkb { - data, - kind: SkSkbKind::StreamVerdict, - }), - ProgramSection::SockOps { .. } => Program::SockOps(SockOps { data }), - ProgramSection::SchedClassifier { .. } => { - Program::SchedClassifier(SchedClassifier { + let program = if self.extensions.contains(name.as_str()) { + Program::Extension(Extension { data }) + } else { + match &data.obj.section { + ProgramSection::KProbe { .. } => Program::KProbe(KProbe { data, - name: unsafe { - CString::from_vec_unchecked(Vec::from(name.clone())) - .into_boxed_c_str() - }, - }) - } - ProgramSection::CgroupSkbIngress { .. } => Program::CgroupSkb(CgroupSkb { - data, - expected_attach_type: Some(CgroupSkbAttachType::Ingress), - }), - ProgramSection::CgroupSkbEgress { .. } => Program::CgroupSkb(CgroupSkb { - data, - expected_attach_type: Some(CgroupSkbAttachType::Egress), - }), - ProgramSection::LircMode2 { .. } => Program::LircMode2(LircMode2 { data }), - ProgramSection::PerfEvent { .. } => Program::PerfEvent(PerfEvent { data }), - ProgramSection::RawTracePoint { .. } => { - Program::RawTracePoint(RawTracePoint { data }) - } - ProgramSection::Lsm { .. } => Program::Lsm(Lsm { data }), - ProgramSection::BtfTracePoint { .. } => { - Program::BtfTracePoint(BtfTracePoint { data }) + kind: ProbeKind::KProbe, + }), + ProgramSection::KRetProbe { .. } => Program::KProbe(KProbe { + data, + kind: ProbeKind::KRetProbe, + }), + ProgramSection::UProbe { .. } => Program::UProbe(UProbe { + data, + kind: ProbeKind::UProbe, + }), + ProgramSection::URetProbe { .. } => Program::UProbe(UProbe { + data, + kind: ProbeKind::URetProbe, + }), + ProgramSection::TracePoint { .. } => { + Program::TracePoint(TracePoint { data }) + } + ProgramSection::SocketFilter { .. } => { + Program::SocketFilter(SocketFilter { data }) + } + ProgramSection::Xdp { .. } => Program::Xdp(Xdp { data }), + ProgramSection::SkMsg { .. } => Program::SkMsg(SkMsg { data }), + ProgramSection::SkSkbStreamParser { .. } => Program::SkSkb(SkSkb { + data, + kind: SkSkbKind::StreamParser, + }), + ProgramSection::SkSkbStreamVerdict { .. } => Program::SkSkb(SkSkb { + data, + kind: SkSkbKind::StreamVerdict, + }), + ProgramSection::SockOps { .. } => Program::SockOps(SockOps { data }), + ProgramSection::SchedClassifier { .. } => { + Program::SchedClassifier(SchedClassifier { + data, + name: unsafe { + CString::from_vec_unchecked(Vec::from(name.clone())) + .into_boxed_c_str() + }, + }) + } + ProgramSection::CgroupSkbIngress { .. } => Program::CgroupSkb(CgroupSkb { + data, + expected_attach_type: Some(CgroupSkbAttachType::Ingress), + }), + ProgramSection::CgroupSkbEgress { .. } => Program::CgroupSkb(CgroupSkb { + data, + expected_attach_type: Some(CgroupSkbAttachType::Egress), + }), + ProgramSection::LircMode2 { .. } => Program::LircMode2(LircMode2 { data }), + ProgramSection::PerfEvent { .. } => Program::PerfEvent(PerfEvent { data }), + ProgramSection::RawTracePoint { .. } => { + Program::RawTracePoint(RawTracePoint { data }) + } + ProgramSection::Lsm { .. } => Program::Lsm(Lsm { data }), + ProgramSection::BtfTracePoint { .. } => { + Program::BtfTracePoint(BtfTracePoint { data }) + } + ProgramSection::FEntry { .. } => Program::FEntry(FEntry { data }), + ProgramSection::FExit { .. } => Program::FExit(FExit { data }), + ProgramSection::Extension { .. } => Program::Extension(Extension { data }), } - ProgramSection::FEntry { .. } => Program::FEntry(FEntry { data }), - ProgramSection::FExit { .. } => Program::FExit(FExit { data }), }; - (name, program) }) .collect(); diff --git a/aya/src/obj/btf/btf.rs b/aya/src/obj/btf/btf.rs index 67a6cb11..3314cc05 100644 --- a/aya/src/obj/btf/btf.rs +++ b/aya/src/obj/btf/btf.rs @@ -1,7 +1,8 @@ use std::{ borrow::Cow, + collections::HashMap, convert::TryInto, - ffi::{c_void, CStr}, + ffi::{c_void, CStr, CString}, fs, io, mem, path::{Path, PathBuf}, ptr, @@ -9,13 +10,22 @@ use std::{ use bytes::BufMut; +use log::debug; use object::Endianness; use thiserror::Error; use crate::{ - generated::{btf_ext_header, btf_header}, + generated::{ + btf_enum, btf_ext_header, btf_func_linkage, btf_header, btf_member, btf_var_secinfo, + }, obj::btf::{relocation::Relocation, BtfKind, BtfType}, util::bytes_of, + Features, +}; + +use super::{ + info::{FuncSecInfo, LineSecInfo}, + type_vlen, FuncInfo, LineInfo, }; pub(crate) const MAX_RESOLVE_DEPTH: u8 = 32; @@ -74,6 +84,28 @@ pub enum BtfError { #[error("maximum depth reached resolving BTF type")] MaximumTypeDepthReached { type_id: u32 }, + + /// Loading the btf failed + #[error("the BPF_BTF_LOAD syscall failed. Verifier output: {verifier_log}")] + LoadError { + /// The [`io::Error`] returned by the `BPF_BTF_LOAD` syscall. + #[source] + io_error: io::Error, + /// The error log produced by the kernel verifier. + verifier_log: String, + }, + + #[error("Offset not found for symbol `{symbol_name}`")] + SymbolOffsetNotFound { symbol_name: String }, + + #[error("BTF type that is not VAR was found in DATASEC")] + InvalidDatasec, + + #[error("Unable to determine the size of section `{section_name}`")] + UnknownSectionSize { section_name: String }, + + #[error("Unable to get symbol name")] + InvalidSymbolName, } /// Bpf Type Format metadata. @@ -93,6 +125,40 @@ pub struct Btf { } impl Btf { + pub(crate) fn new() -> Btf { + Btf { + header: btf_header { + magic: 0xeb9f, + version: 0x01, + flags: 0x00, + hdr_len: 0x18, + type_off: 0x00, + type_len: 0x00, + str_off: 0x00, + str_len: 0x00, + }, + strings: vec![0], + types: vec![], + _endianness: Endianness::default(), + } + } + + pub(crate) fn add_string(&mut self, name: String) -> u32 { + let str = CString::new(name).unwrap(); + let name_off = self.strings.len(); + self.strings.extend(str.as_c_str().to_bytes_with_nul()); + self.header.str_len = self.strings.len() as u32; + name_off as u32 + } + + pub(crate) fn add_type(&mut self, type_: BtfType) -> u32 { + let size = type_.type_info_size() as u32; + self.types.push(type_); + self.header.type_len += size; + self.header.str_off += size; + self.types.len() as u32 + } + /// Loads BTF metadata from `/sys/kernel/btf/vmlinux`. pub fn from_sys_fs() -> Result { Btf::parse_file("/sys/kernel/btf/vmlinux", Endianness::default()) @@ -305,6 +371,194 @@ impl Btf { buf.put(self.strings.as_slice()); buf } + + pub(crate) fn fixup( + &mut self, + section_sizes: &HashMap, + symbol_offsets: &HashMap, + ) -> Result<(), BtfError> { + // FIXME: there is probably a more elegant way of doing operation in place + // for now, and to keep on the good side of the borrow checker, we'll create + // a new Vec and populate it as we go + let mut types = vec![]; + for t in &self.types { + let kind = t.kind()?.unwrap_or_default(); + // datasec sizes aren't set by llvm + // we need to fix them here before loading the btf to the kernel + match t { + BtfType::DataSec(mut ty, data) => { + // Start DataSec Fixups + let sec_name = self.type_name(t)?.ok_or(BtfError::InvalidTypeInfo)?; + let name = sec_name.to_string(); + // There are cases when the compiler does indeed populate the + // size. If we hit this case, push to the types vector and + // continue + if unsafe { ty.__bindgen_anon_1.size > 0 } { + debug!("{} {}: fixup not required", kind, name); + types.push(BtfType::DataSec(ty, data.clone())); + continue; + } + + // We need to get the size of the section from the ELF file + // Fortunately, we cached these when parsing it initially + // and we can this up by name in section_sizes + if let Some(size) = section_sizes.get(&name) { + debug!("{} {}: fixup size to {}", kind, name, size); + ty.__bindgen_anon_1.size = *size as u32; + } else { + return Err(BtfError::UnknownSectionSize { section_name: name }); + } + + // The Vec contains BTF_KIND_VAR sections + // that need to have their offsets adjusted. To do this, + // we need to get the offset from the ELF file. + // This was also cached during initial parsing and + // we can query by name in symbol_offsets + let mut adjusted_data: Vec = vec![]; + for d in data { + let var_type = self.type_by_id(d.type_)?; + let var_kind = var_type.kind()?.unwrap(); + if let BtfType::Var(vty, var) = var_type { + let var_name = self.string_at(vty.name_off)?.to_string(); + if var.linkage == btf_func_linkage::BTF_FUNC_STATIC as u32 { + debug!( + "{} {}: {} {}: fixup not required", + kind, name, var_kind, var_name + ); + adjusted_data.push(*d); + continue; + } + + let offset = symbol_offsets.get(&var_name).ok_or( + BtfError::SymbolOffsetNotFound { + symbol_name: var_name.clone(), + }, + )?; + adjusted_data.push(btf_var_secinfo { + type_: d.type_, + offset: *offset as u32, + size: d.size, + }); + debug!( + "{} {}: {} {}: fixup offset {}", + kind, name, var_kind, var_name, offset + ); + } else { + return Err(BtfError::InvalidDatasec); + } + } + types.push(BtfType::DataSec(ty, adjusted_data)) + } + // The type does not need fixing up + // Push it to the new types vec unmodified + ty => types.push(ty.clone()), + } + } + self.types = types; + Ok(()) + } + + pub(crate) fn sanitize(&self, features: &Features) -> Result { + let mut btf = Btf::new(); + + btf.strings = self.strings.to_vec(); + btf.header.str_len = btf.strings.len() as u32; + + // Skip the first type as it's only there + // to make type_by_id work + for t in &self.types[1..] { + let kind = t.kind()?.unwrap_or_default(); + match t { + BtfType::Var(ty, vars) => { + if !features.btf_datasec { + debug!("{}: not supported. replacing with INT", kind); + let int_type = BtfType::new_int(ty.name_off, 1, 0, 0); + btf.add_type(int_type); + } else { + btf.add_type(BtfType::Var(*ty, *vars)); + } + } + BtfType::DataSec(ty, data) => { + if !features.btf_datasec { + debug!("{}: not supported. replacing with STRUCT", kind); + let members: Vec = data + .iter() + .map(|p| -> btf_member { + let mt = self.type_by_id(p.type_).unwrap(); + btf_member { + name_off: mt.btf_type().unwrap().name_off, + type_: p.type_, + offset: p.offset * 8, + } + }) + .collect(); + let struct_type = BtfType::new_struct(ty.name_off, members, 0); + btf.add_type(struct_type); + } else { + btf.add_type(BtfType::DataSec(*ty, data.to_vec())); + } + } + BtfType::FuncProto(ty, vars) => { + if !features.btf_func { + debug!("{}: not supported. replacing with ENUM", kind); + let members: Vec = vars + .iter() + .map(|p| -> btf_enum { + btf_enum { + name_off: p.name_off, + val: p.type_ as i32, + } + }) + .collect(); + let enum_type = BtfType::new_enum(ty.name_off, members); + btf.add_type(enum_type); + } else { + btf.add_type(BtfType::FuncProto(*ty, vars.to_vec())); + } + } + BtfType::Func(mut ty) => { + if !features.btf_func { + debug!("{}: not supported. replacing with TYPEDEF", kind); + let typedef_type = + BtfType::new_typedef(ty.name_off, unsafe { ty.__bindgen_anon_1.type_ }); + btf.add_type(typedef_type); + } else if type_vlen(&ty) == btf_func_linkage::BTF_FUNC_GLOBAL as usize + && !features.btf_func_global + { + debug!( + "{}: BTF_FUNC_GLOBAL not supported. replacing with BTF_FUNC_STATIC", + kind + ); + ty.info |= (btf_func_linkage::BTF_FUNC_STATIC as u32) & 0xFFFF; + btf.add_type(BtfType::Func(ty)); + } else { + btf.add_type(BtfType::Func(ty)); + } + } + BtfType::Float(ty) => { + if !features.btf_float { + debug!("{}: not supported. replacing with STRUCT", kind); + let struct_ty = + BtfType::new_struct(0, vec![], unsafe { ty.__bindgen_anon_1.size }); + btf.add_type(struct_ty); + } else { + btf.add_type(BtfType::Float(*ty)); + } + } + // The type does not need sanitizing + ty => { + btf.add_type(ty.clone()); + } + } + } + Ok(btf) + } +} + +impl Default for Btf { + fn default() -> Self { + Self::new() + } } unsafe fn read_btf_header(data: &[u8]) -> btf_header { @@ -318,13 +572,19 @@ pub struct BtfExt { _endianness: Endianness, relocations: Vec<(u32, Vec)>, header: btf_ext_header, - _func_info_rec_size: usize, - _line_info_rec_size: usize, + func_info_rec_size: usize, + pub(crate) func_info: FuncInfo, + line_info_rec_size: usize, + pub(crate) line_info: LineInfo, core_relo_rec_size: usize, } impl BtfExt { - pub(crate) fn parse(data: &[u8], endianness: Endianness) -> Result { + pub(crate) fn parse( + data: &[u8], + endianness: Endianness, + btf: &Btf, + ) -> Result { // Safety: btf_ext_header is POD so read_unaligned is safe let header = unsafe { ptr::read_unaligned::(data.as_ptr() as *const btf_ext_header) @@ -366,13 +626,57 @@ impl BtfExt { let mut ext = BtfExt { header, relocations: Vec::new(), - _func_info_rec_size: rec_size(func_info_off, func_info_len)?, - _line_info_rec_size: rec_size(line_info_off, line_info_len)?, + func_info: FuncInfo::new(), + line_info: LineInfo::new(), + func_info_rec_size: rec_size(func_info_off, func_info_len)?, + line_info_rec_size: rec_size(line_info_off, line_info_len)?, core_relo_rec_size: rec_size(core_relo_off, core_relo_len)?, data: data.to_vec(), _endianness: endianness, }; + let func_info_rec_size = ext.func_info_rec_size; + ext.func_info.data.extend( + SecInfoIter::new(ext.func_info_data(), ext.func_info_rec_size, endianness) + .map(move |sec| { + let name = btf + .string_at(sec.sec_name_off) + .ok() + .map(String::from) + .unwrap(); + let info = FuncSecInfo::parse( + sec.sec_name_off, + sec.num_info, + func_info_rec_size, + sec.data, + endianness, + ); + Ok((name, info)) + }) + .collect::, _>>()?, + ); + + let line_info_rec_size = ext.line_info_rec_size; + ext.line_info.data.extend( + SecInfoIter::new(ext.line_info_data(), ext.line_info_rec_size, endianness) + .map(move |sec| { + let name = btf + .string_at(sec.sec_name_off) + .ok() + .map(String::from) + .unwrap(); + let info = LineSecInfo::parse( + sec.sec_name_off, + sec.num_info, + line_info_rec_size, + sec.data, + endianness, + ); + Ok((name, info)) + }) + .collect::, _>>()?, + ); + let rec_size = ext.core_relo_rec_size; ext.relocations.extend( SecInfoIter::new(ext.core_relo_data(), ext.core_relo_rec_size, endianness) @@ -406,9 +710,25 @@ impl BtfExt { self.info_data(self.header.core_relo_off, self.header.core_relo_len) } + fn func_info_data(&self) -> &[u8] { + self.info_data(self.header.func_info_off, self.header.func_info_len) + } + + fn line_info_data(&self) -> &[u8] { + self.info_data(self.header.line_info_off, self.header.line_info_len) + } + pub(crate) fn relocations(&self) -> impl Iterator)> { self.relocations.iter() } + + pub(crate) fn func_info_rec_size(&self) -> usize { + self.func_info_rec_size + } + + pub(crate) fn line_info_rec_size(&self) -> usize { + self.line_info_rec_size + } } pub(crate) struct SecInfoIter<'a> { @@ -453,7 +773,7 @@ impl<'a> Iterator for SecInfoIter<'a> { Some(SecInfo { sec_name_off, - _num_info: num_info, + num_info, data, }) } @@ -462,12 +782,14 @@ impl<'a> Iterator for SecInfoIter<'a> { #[derive(Debug)] pub(crate) struct SecInfo<'a> { sec_name_off: u32, - _num_info: u32, + num_info: u32, data: &'a [u8], } #[cfg(test)] mod tests { + use crate::generated::{btf_param, BTF_INT_SIGNED, BTF_VAR_STATIC}; + use super::*; #[test] @@ -530,12 +852,195 @@ mod tests { 0x5a, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x5f, 0x00, 0x5f, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x00, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x00, ]; + assert_eq!(data.len(), 517); let got = Btf::parse(data, Endianness::default()); match got { Ok(_) => {} Err(e) => panic!("{}", e), } - let data2 = got.unwrap().to_bytes(); + let btf = got.unwrap(); + let data2 = btf.to_bytes(); + assert_eq!(data2.len(), 517); assert_eq!(data, data2); + + let ext_data: &[u8] = &[ + 0x9f, 0xeb, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, + 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x72, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7b, 0x00, + 0x00, 0x00, 0xa2, 0x00, 0x00, 0x00, 0x05, 0x2c, 0x00, 0x00, + ]; + + assert_eq!(ext_data.len(), 80); + let got = BtfExt::parse(ext_data, Endianness::default(), &btf); + if let Err(e) = got { + panic!("{}", e) + } + } + + #[test] + fn test_write_btf() { + let mut btf = Btf::new(); + let name_offset = btf.add_string("int".to_string()); + let int_type = BtfType::new_int(name_offset, 4, BTF_INT_SIGNED, 0); + btf.add_type(int_type); + + let name_offset = btf.add_string("widget".to_string()); + let int_type = BtfType::new_int(name_offset, 4, BTF_INT_SIGNED, 0); + btf.add_type(int_type); + + let btf_bytes = btf.to_bytes(); + let raw_btf = btf_bytes.as_slice(); + + let parsed = Btf::parse(raw_btf, Endianness::default()); + match parsed { + Ok(btf) => { + assert_eq!(btf.string_at(1).unwrap(), "int"); + assert_eq!(btf.string_at(5).unwrap(), "widget"); + } + Err(e) => { + panic!("{}", e) + } + } + } + + #[test] + fn test_sanitize_btf() { + let mut btf = Btf::new(); + let name_offset = btf.add_string("int".to_string()); + let int_type = BtfType::new_int(name_offset, 4, BTF_INT_SIGNED, 0); + let int_type_id = btf.add_type(int_type); + + let name_offset = btf.add_string("foo".to_string()); + let var_type = BtfType::new_var(name_offset, int_type_id, BTF_VAR_STATIC); + let var_type_id = btf.add_type(var_type); + + let name_offset = btf.add_string(".data".to_string()); + let variables = vec![btf_var_secinfo { + type_: var_type_id, + offset: 0, + size: 4, + }]; + let datasec_type = BtfType::new_datasec(name_offset, variables, 4); + btf.add_type(datasec_type); + + let name_offset = btf.add_string("float".to_string()); + let float_type = BtfType::new_float(name_offset, 16); + btf.add_type(float_type); + + let a_name = btf.add_string("a".to_string()); + let b_name = btf.add_string("b".to_string()); + let params = vec![ + btf_param { + name_off: a_name, + type_: int_type_id, + }, + btf_param { + name_off: b_name, + type_: int_type_id, + }, + ]; + let func_proto = BtfType::new_func_proto(params, int_type_id); + let func_proto_type_id = btf.add_type(func_proto); + + let add = btf.add_string("static".to_string()); + let func = BtfType::new_func(add, func_proto_type_id, btf_func_linkage::BTF_FUNC_STATIC); + btf.add_type(func); + + let c_name = btf.add_string("c".to_string()); + let d_name = btf.add_string("d".to_string()); + let params = vec![ + btf_param { + name_off: c_name, + type_: int_type_id, + }, + btf_param { + name_off: d_name, + type_: int_type_id, + }, + ]; + let func_proto = BtfType::new_func_proto(params, int_type_id); + let func_proto_type_id = btf.add_type(func_proto); + + let add = btf.add_string("global".to_string()); + let func = BtfType::new_func(add, func_proto_type_id, btf_func_linkage::BTF_FUNC_GLOBAL); + btf.add_type(func); + + let cases = HashMap::from([ + ( + "noop", + Features { + bpf_name: true, + btf: true, + btf_func: true, + btf_func_global: true, + btf_datasec: true, + btf_float: true, + }, + ), + ( + "no datasec", + Features { + bpf_name: true, + btf: true, + btf_func: true, + btf_func_global: true, + btf_datasec: false, + btf_float: true, + }, + ), + ( + "no float", + Features { + bpf_name: true, + btf: true, + btf_func: true, + btf_func_global: true, + btf_datasec: true, + btf_float: false, + }, + ), + ( + "no func", + Features { + bpf_name: true, + btf: true, + btf_func: false, + btf_func_global: true, + btf_datasec: true, + btf_float: true, + }, + ), + ( + "no global func", + Features { + bpf_name: true, + btf: true, + btf_func: true, + btf_func_global: false, + btf_datasec: true, + btf_float: true, + }, + ), + ( + "all off", + Features { + bpf_name: true, + btf: true, + btf_func: false, + btf_func_global: false, + btf_datasec: false, + btf_float: false, + }, + ), + ]); + + for (name, features) in cases { + println!("[CASE] Sanitize {}", name); + let new_btf = btf.sanitize(&features).unwrap(); + let raw_new_btf = new_btf.to_bytes(); + Btf::parse(&raw_new_btf, Endianness::default()).unwrap(); + } } } diff --git a/aya/src/obj/btf/info.rs b/aya/src/obj/btf/info.rs new file mode 100644 index 00000000..3743d5cf --- /dev/null +++ b/aya/src/obj/btf/info.rs @@ -0,0 +1,189 @@ +use std::{collections::HashMap, convert::TryInto}; + +use bytes::BufMut; +use object::Endianness; + +use crate::{ + generated::{bpf_func_info, bpf_line_info}, + obj::relocation::INS_SIZE, + util::bytes_of, +}; + +/* The func_info subsection layout: + * record size for struct bpf_func_info in the func_info subsection + * struct btf_sec_func_info for section #1 + * a list of bpf_func_info records for section #1 + * where struct bpf_func_info mimics one in include/uapi/linux/bpf.h + * but may not be identical + * struct btf_sec_func_info for section #2 + * a list of bpf_func_info records for section #2 + * ...... + */ +#[derive(Debug, Clone, Default)] +pub(crate) struct FuncSecInfo { + pub _sec_name_offset: u32, + pub num_info: u32, + pub func_info: Vec, +} + +impl FuncSecInfo { + pub(crate) fn parse( + sec_name_offset: u32, + num_info: u32, + rec_size: usize, + func_info_data: &[u8], + endianness: Endianness, + ) -> FuncSecInfo { + let func_info = func_info_data + .chunks(rec_size) + .map(|data| { + let read_u32 = if endianness == Endianness::Little { + u32::from_le_bytes + } else { + u32::from_be_bytes + }; + + let mut offset = 0; + + // ELF instruction offsets are in bytes + // Kernel instruction offsets are in instructions units + // We can convert by dividing the length in bytes by INS_SIZE + let insn_off = + read_u32(data[offset..offset + 4].try_into().unwrap()) / INS_SIZE as u32; + offset += 4; + let type_id = read_u32(data[offset..offset + 4].try_into().unwrap()); + + bpf_func_info { insn_off, type_id } + }) + .collect(); + + FuncSecInfo { + _sec_name_offset: sec_name_offset, + num_info, + func_info, + } + } + + pub(crate) fn func_info_bytes(&self) -> Vec { + let mut buf = vec![]; + for l in &self.func_info { + // Safety: bpf_func_info is POD + buf.put(unsafe { bytes_of::(l) }) + } + buf + } + + pub(crate) fn len(&self) -> usize { + self.func_info.len() + } +} + +#[derive(Debug, Clone)] +pub(crate) struct FuncInfo { + pub data: HashMap, +} + +impl FuncInfo { + pub(crate) fn new() -> FuncInfo { + FuncInfo { + data: HashMap::new(), + } + } + + pub(crate) fn get(&self, name: &str) -> FuncSecInfo { + match self.data.get(name) { + Some(d) => d.clone(), + None => FuncSecInfo::default(), + } + } +} + +#[derive(Debug, Clone, Default)] +pub(crate) struct LineSecInfo { + // each line info section has a header + pub _sec_name_offset: u32, + pub num_info: u32, + // followed by one or more bpf_line_info structs + pub line_info: Vec, +} + +impl LineSecInfo { + pub(crate) fn parse( + sec_name_offset: u32, + num_info: u32, + rec_size: usize, + func_info_data: &[u8], + endianness: Endianness, + ) -> LineSecInfo { + let line_info = func_info_data + .chunks(rec_size) + .map(|data| { + let read_u32 = if endianness == Endianness::Little { + u32::from_le_bytes + } else { + u32::from_be_bytes + }; + + let mut offset = 0; + + // ELF instruction offsets are in bytes + // Kernel instruction offsets are in instructions units + // We can convert by dividing the length in bytes by INS_SIZE + let insn_off = + read_u32(data[offset..offset + 4].try_into().unwrap()) / INS_SIZE as u32; + offset += 4; + let file_name_off = read_u32(data[offset..offset + 4].try_into().unwrap()); + offset += 4; + let line_off = read_u32(data[offset..offset + 4].try_into().unwrap()); + offset += 4; + let line_col = read_u32(data[offset..offset + 4].try_into().unwrap()); + + bpf_line_info { + insn_off, + file_name_off, + line_off, + line_col, + } + }) + .collect(); + + LineSecInfo { + _sec_name_offset: sec_name_offset, + num_info, + line_info, + } + } + + pub(crate) fn line_info_bytes(&self) -> Vec { + let mut buf = vec![]; + for l in &self.line_info { + // Safety: bpf_func_info is POD + buf.put(unsafe { bytes_of::(l) }) + } + buf + } + + pub(crate) fn len(&self) -> usize { + self.line_info.len() + } +} + +#[derive(Debug, Clone)] +pub(crate) struct LineInfo { + pub data: HashMap, +} + +impl LineInfo { + pub(crate) fn new() -> LineInfo { + LineInfo { + data: HashMap::new(), + } + } + + pub(crate) fn get(&self, name: &str) -> LineSecInfo { + match self.data.get(name) { + Some(d) => d.clone(), + None => LineSecInfo::default(), + } + } +} diff --git a/aya/src/obj/btf/mod.rs b/aya/src/obj/btf/mod.rs index ae117d69..d5324094 100644 --- a/aya/src/obj/btf/mod.rs +++ b/aya/src/obj/btf/mod.rs @@ -1,8 +1,10 @@ #[allow(clippy::module_inception)] mod btf; +mod info; mod relocation; mod types; pub use btf::*; +pub(crate) use info::*; pub use relocation::RelocationError; pub(crate) use types::*; diff --git a/aya/src/obj/btf/types.rs b/aya/src/obj/btf/types.rs index 9bda5242..86e711df 100644 --- a/aya/src/obj/btf/types.rs +++ b/aya/src/obj/btf/types.rs @@ -1,5 +1,6 @@ use std::{ convert::{TryFrom, TryInto}, + fmt::Display, mem, ptr, }; @@ -7,11 +8,11 @@ use object::Endianness; use crate::{ generated::{ - btf_array, btf_enum, btf_member, btf_param, btf_type, btf_type__bindgen_ty_1, btf_var, - btf_var_secinfo, BTF_KIND_ARRAY, BTF_KIND_CONST, BTF_KIND_DATASEC, BTF_KIND_ENUM, - BTF_KIND_FLOAT, BTF_KIND_FUNC, BTF_KIND_FUNC_PROTO, BTF_KIND_FWD, BTF_KIND_INT, - BTF_KIND_PTR, BTF_KIND_RESTRICT, BTF_KIND_STRUCT, BTF_KIND_TYPEDEF, BTF_KIND_UNION, - BTF_KIND_UNKN, BTF_KIND_VAR, BTF_KIND_VOLATILE, + btf_array, btf_enum, btf_func_linkage, btf_member, btf_param, btf_type, + btf_type__bindgen_ty_1, btf_var, btf_var_secinfo, BTF_KIND_ARRAY, BTF_KIND_CONST, + BTF_KIND_DATASEC, BTF_KIND_ENUM, BTF_KIND_FLOAT, BTF_KIND_FUNC, BTF_KIND_FUNC_PROTO, + BTF_KIND_FWD, BTF_KIND_INT, BTF_KIND_PTR, BTF_KIND_RESTRICT, BTF_KIND_STRUCT, + BTF_KIND_TYPEDEF, BTF_KIND_UNION, BTF_KIND_UNKN, BTF_KIND_VAR, BTF_KIND_VOLATILE, }, obj::btf::{Btf, BtfError, MAX_RESOLVE_DEPTH}, }; @@ -87,6 +88,36 @@ impl TryFrom for BtfKind { } } +impl Display for BtfKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BtfKind::Unknown => write!(f, "[UNKNOWN]"), + BtfKind::Int => write!(f, "[INT]"), + BtfKind::Float => write!(f, "[FLOAT]"), + BtfKind::Ptr => write!(f, "[PTR]"), + BtfKind::Array => write!(f, "[ARRAY]"), + BtfKind::Struct => write!(f, "[STRUCT]"), + BtfKind::Union => write!(f, "[UNION]"), + BtfKind::Enum => write!(f, "[ENUM]"), + BtfKind::Fwd => write!(f, "[FWD]"), + BtfKind::Typedef => write!(f, "[TYPEDEF]"), + BtfKind::Volatile => write!(f, "[VOLATILE]"), + BtfKind::Const => write!(f, "[CONST]"), + BtfKind::Restrict => write!(f, "[RESTRICT]"), + BtfKind::Func => write!(f, "[FUNC]"), + BtfKind::FuncProto => write!(f, "[FUNC_PROTO]"), + BtfKind::Var => write!(f, "[VAR]"), + BtfKind::DataSec => write!(f, "[DATASEC]"), + } + } +} + +impl Default for BtfKind { + fn default() -> Self { + BtfKind::Unknown + } +} + unsafe fn read(data: &[u8]) -> Result { if mem::size_of::() > data.len() { return Err(BtfError::InvalidTypeInfo); @@ -170,7 +201,7 @@ impl BtfType { } BtfType::Enum(btf_type, enums) => { let mut buf = bytes_of::(btf_type).to_vec(); - for en in enums { + for en in enums { buf.append(&mut bytes_of::(en).to_vec()); } buf @@ -267,13 +298,109 @@ impl BtfType { pub(crate) fn is_composite(&self) -> bool { matches!(self, BtfType::Struct(_, _) | BtfType::Union(_, _)) } + + pub(crate) fn new_int(name_off: u32, size: u32, encoding: u32, offset: u32) -> BtfType { + let info = (BTF_KIND_INT) << 24; + let mut btf_type = unsafe { std::mem::zeroed::() }; + btf_type.name_off = name_off; + btf_type.info = info; + btf_type.__bindgen_anon_1.size = size; + + let mut data = 0u32; + data |= (encoding & 0x0f) << 24; + data |= (offset & 0xff) << 16; + data |= (size * 8) & 0xff; + BtfType::Int(btf_type, data) + } + + pub(crate) fn new_func(name_off: u32, proto: u32, linkage: btf_func_linkage) -> BtfType { + let mut info = (BTF_KIND_FUNC) << 24; + info |= (linkage as u32) & 0xFFFF; + let mut btf_type = unsafe { std::mem::zeroed::() }; + btf_type.name_off = name_off; + btf_type.info = info; + btf_type.__bindgen_anon_1.type_ = proto; + BtfType::Func(btf_type) + } + + pub(crate) fn new_func_proto(params: Vec, return_type: u32) -> BtfType { + let mut info = (BTF_KIND_FUNC_PROTO) << 24; + info |= (params.len() as u32) & 0xFFFF; + let mut btf_type = unsafe { std::mem::zeroed::() }; + btf_type.name_off = 0; + btf_type.info = info; + btf_type.__bindgen_anon_1.type_ = return_type; + BtfType::FuncProto(btf_type, params) + } + + pub(crate) fn new_var(name_off: u32, type_: u32, linkage: u32) -> BtfType { + let info = (BTF_KIND_VAR) << 24; + let mut btf_type = unsafe { std::mem::zeroed::() }; + btf_type.name_off = name_off; + btf_type.info = info; + btf_type.__bindgen_anon_1.type_ = type_; + let var = btf_var { linkage }; + BtfType::Var(btf_type, var) + } + + pub(crate) fn new_datasec( + name_off: u32, + variables: Vec, + size: u32, + ) -> BtfType { + let mut info = (BTF_KIND_DATASEC) << 24; + info |= (variables.len() as u32) & 0xFFFF; + let mut btf_type = unsafe { std::mem::zeroed::() }; + btf_type.name_off = name_off; + btf_type.info = info; + btf_type.__bindgen_anon_1.size = size; + BtfType::DataSec(btf_type, variables) + } + + pub(crate) fn new_float(name_off: u32, size: u32) -> BtfType { + let info = (BTF_KIND_FLOAT) << 24; + let mut btf_type = unsafe { std::mem::zeroed::() }; + btf_type.name_off = name_off; + btf_type.info = info; + btf_type.__bindgen_anon_1.size = size; + BtfType::Float(btf_type) + } + + pub(crate) fn new_struct(name_off: u32, members: Vec, size: u32) -> BtfType { + let mut info = (BTF_KIND_STRUCT) << 24; + info |= (members.len() as u32) & 0xFFFF; + let mut btf_type = unsafe { std::mem::zeroed::() }; + btf_type.name_off = name_off; + btf_type.info = info; + btf_type.__bindgen_anon_1.size = size; + BtfType::Struct(btf_type, members) + } + + pub(crate) fn new_enum(name_off: u32, members: Vec) -> BtfType { + let mut info = (BTF_KIND_ENUM) << 24; + info |= (members.len() as u32) & 0xFFFF; + let mut btf_type = unsafe { std::mem::zeroed::() }; + btf_type.name_off = name_off; + btf_type.info = info; + btf_type.__bindgen_anon_1.size = 4; + BtfType::Enum(btf_type, members) + } + + pub(crate) fn new_typedef(name_off: u32, type_: u32) -> BtfType { + let info = (BTF_KIND_TYPEDEF) << 24; + let mut btf_type = unsafe { std::mem::zeroed::() }; + btf_type.name_off = name_off; + btf_type.info = info; + btf_type.__bindgen_anon_1.type_ = type_; + BtfType::Typedef(btf_type) + } } fn type_kind(ty: &btf_type) -> Result { ((ty.info >> 24) & 0x1F).try_into() } -fn type_vlen(ty: &btf_type) -> usize { +pub(crate) fn type_vlen(ty: &btf_type) -> usize { (ty.info & 0xFFFF) as usize } @@ -458,6 +585,8 @@ impl std::fmt::Debug for btf_type__bindgen_ty_1 { #[cfg(test)] mod tests { + use crate::generated::BTF_INT_SIGNED; + use super::*; #[test] @@ -471,13 +600,44 @@ mod tests { match got { Ok(BtfType::Int(ty, nr_bits)) => { assert_eq!(ty.name_off, 1); + assert_eq!(unsafe { ty.__bindgen_anon_1.size }, 8); assert_eq!(nr_bits, 64); } Ok(t) => panic!("expected int type, got {:#?}", t), Err(_) => panic!("unexpected error"), } let data2 = got.unwrap().to_bytes(); - assert_eq!(data, data2.as_slice()) + assert_eq!(data, data2.as_slice()); + } + + #[test] + fn test_write_btf_long_unsigned_int() { + let data: &[u8] = &[ + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, 0x40, 0x00, + 0x00, 0x00, + ]; + let int = BtfType::new_int(1, 8, 0, 0); + assert_eq!(int.to_bytes(), data); + } + + #[test] + fn test_write_btf_uchar() { + let data: &[u8] = &[ + 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, + 0x00, 0x00, + ]; + let int = BtfType::new_int(0x13, 1, 0, 0); + assert_eq!(int.to_bytes(), data); + } + + #[test] + fn test_write_btf_signed_short_int() { + let data: &[u8] = &[ + 0x4a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x00, 0x01, + ]; + let int = BtfType::new_int(0x4a, 2, BTF_INT_SIGNED, 0); + assert_eq!(int.to_bytes(), data); } #[test] @@ -698,14 +858,20 @@ mod tests { #[test] fn test_read_btf_type_func_datasec() { let endianness = Endianness::default(); - // NOTE: There was no data in /sys/kernell/btf/vmlinux for this type let data: &[u8] = &[ - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xd9, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, ]; let got = unsafe { BtfType::read(data, endianness) }; - match got { - Ok(BtfType::DataSec(_, _)) => {} + match &got { + Ok(BtfType::DataSec(ty, info)) => { + assert_eq!(0, unsafe { ty.__bindgen_anon_1.size } as usize); + assert_eq!(1, type_vlen(ty) as usize); + assert_eq!(1, info.len()); + assert_eq!(11, info[0].type_); + assert_eq!(0, info[0].offset); + assert_eq!(4, info[0].size); + } Ok(t) => panic!("expected datasec type, got {:#?}", t), Err(_) => panic!("unexpected error"), } @@ -728,4 +894,28 @@ mod tests { let data2 = got.unwrap().to_bytes(); assert_eq!(data, data2.as_slice()) } + + #[test] + fn test_write_btf_func_proto() { + let params = vec![ + btf_param { + name_off: 1, + type_: 1, + }, + btf_param { + name_off: 3, + type_: 1, + }, + ]; + let func_proto = BtfType::new_func_proto(params, 2); + let data = func_proto.to_bytes(); + let got = unsafe { BtfType::read(&data, Endianness::default()) }; + match got { + Ok(BtfType::FuncProto(btf_type, _params)) => { + assert_eq!(type_vlen(&btf_type), 2); + } + Ok(t) => panic!("expected func proto type, got {:#?}", t), + Err(_) => panic!("unexpected error"), + } + } } diff --git a/aya/src/obj/mod.rs b/aya/src/obj/mod.rs index 61dd9af6..bdef7d2f 100644 --- a/aya/src/obj/mod.rs +++ b/aya/src/obj/mod.rs @@ -25,6 +25,8 @@ use crate::{ }; use std::slice::from_raw_parts_mut; +use self::btf::{FuncSecInfo, LineSecInfo}; + const KERNEL_VERSION_ANY: u32 = 0xFFFF_FFFE; /// The first five __u32 of `bpf_map_def` must be defined. const MINIMUM_MAP_SIZE: usize = mem::size_of::() * 5; @@ -41,6 +43,10 @@ pub struct Object { pub(crate) functions: HashMap, pub(crate) relocations: HashMap>, pub(crate) symbols_by_index: HashMap, + pub(crate) section_sizes: HashMap, + // symbol_offset_by_name caches symbols that could be referenced from a + // BTF VAR type so the offsets can be fixed up + pub(crate) symbol_offset_by_name: HashMap, } #[derive(Debug, Clone, PartialEq)] @@ -88,6 +94,10 @@ pub(crate) struct Function { pub(crate) section_index: SectionIndex, pub(crate) section_offset: usize, pub(crate) instructions: Vec, + pub(crate) func_info: FuncSecInfo, + pub(crate) line_info: LineSecInfo, + pub(crate) func_info_rec_size: usize, + pub(crate) line_info_rec_size: usize, } #[derive(Debug, Clone)] @@ -113,6 +123,7 @@ pub enum ProgramSection { BtfTracePoint { name: String }, FEntry { name: String }, FExit { name: String }, + Extension { name: String }, } impl ProgramSection { @@ -139,6 +150,7 @@ impl ProgramSection { ProgramSection::BtfTracePoint { name } => name, ProgramSection::FEntry { name } => name, ProgramSection::FExit { name } => name, + ProgramSection::Extension { name } => name, } } } @@ -194,6 +206,7 @@ impl FromStr for ProgramSection { "lsm" => Lsm { name }, "fentry" => FEntry { name }, "fexit" => FExit { name }, + "freplace" => Extension { name }, _ => { return Err(ParseError::InvalidProgramSection { section: section.to_owned(), @@ -224,9 +237,14 @@ impl Object { if let Some(symbol_table) = obj.symbol_table() { for symbol in symbol_table.symbols() { + let name = symbol + .name() + .ok() + .map(String::from) + .ok_or(BtfError::InvalidSymbolName)?; let sym = Symbol { index: symbol.index().0, - name: symbol.name().ok().map(String::from), + name: Some(name.clone()), section_index: symbol.section().index(), address: symbol.address(), size: symbol.size(), @@ -236,10 +254,30 @@ impl Object { bpf_obj .symbols_by_index .insert(symbol.index().0, sym.clone()); + + if symbol.is_global() || symbol.kind() == SymbolKind::Data { + bpf_obj.symbol_offset_by_name.insert(name, symbol.address()); + } + } + } + + // .BTF and .BTF.ext sections must be parsed first + // as they're required to prepare function and line information + // when parsing program sections + if let Some(s) = obj.section_by_name(".BTF") { + bpf_obj.parse_section(Section::try_from(&s)?)?; + if let Some(s) = obj.section_by_name(".BTF.ext") { + bpf_obj.parse_section(Section::try_from(&s)?)?; } } for s in obj.sections() { + if let Ok(name) = s.name() { + if name == ".BTF" || name == ".BTF.ext" { + continue; + } + } + bpf_obj.parse_section(Section::try_from(&s)?)?; } @@ -258,6 +296,8 @@ impl Object { functions: HashMap::new(), relocations: HashMap::new(), symbols_by_index: HashMap::new(), + section_sizes: HashMap::new(), + symbol_offset_by_name: HashMap::new(), } } @@ -313,13 +353,32 @@ impl Object { } fn parse_btf_ext(&mut self, section: &Section) -> Result<(), BtfError> { - self.btf_ext = Some(BtfExt::parse(section.data, self.endianness)?); + self.btf_ext = Some(BtfExt::parse( + section.data, + self.endianness, + self.btf.as_ref().unwrap(), + )?); Ok(()) } fn parse_program(&self, section: &Section) -> Result { let prog_sec = ProgramSection::from_str(section.name)?; let name = prog_sec.name().to_owned(); + + let (func_info, line_info, func_info_rec_size, line_info_rec_size) = + if let Some(btf_ext) = &self.btf_ext { + let func_info = btf_ext.func_info.get(section.name); + let line_info = btf_ext.line_info.get(section.name); + ( + func_info, + line_info, + btf_ext.func_info_rec_size(), + btf_ext.line_info_rec_size(), + ) + } else { + (FuncSecInfo::default(), LineSecInfo::default(), 0, 0) + }; + Ok(Program { license: self.license.clone(), kernel_version: self.kernel_version, @@ -330,6 +389,10 @@ impl Object { section_index: section.index, section_offset: 0, instructions: copy_instructions(section.data)?, + func_info, + line_info, + func_info_rec_size, + line_info_rec_size, }, }) } @@ -365,6 +428,38 @@ impl Object { }); } + let (func_info, line_info, func_info_rec_size, line_info_rec_size) = + if let Some(btf_ext) = &self.btf_ext { + let bytes_offset = offset as u32 / INS_SIZE as u32; + let section_size_bytes = sym.size as u32 / INS_SIZE as u32; + + let mut func_info = btf_ext.func_info.get(section.name); + func_info.func_info = func_info + .func_info + .into_iter() + .filter(|f| f.insn_off == bytes_offset) + .collect(); + + let mut line_info = btf_ext.line_info.get(section.name); + line_info.line_info = line_info + .line_info + .into_iter() + .filter(|l| { + l.insn_off >= bytes_offset + && l.insn_off < (bytes_offset + section_size_bytes) as u32 + }) + .collect(); + + ( + func_info, + line_info, + btf_ext.func_info_rec_size(), + btf_ext.line_info_rec_size(), + ) + } else { + (FuncSecInfo::default(), LineSecInfo::default(), 0, 0) + }; + self.functions.insert( sym.address, Function { @@ -375,6 +470,10 @@ impl Object { instructions: copy_instructions( §ion.data[offset..offset + sym.size as usize], )?, + func_info, + line_info, + func_info_rec_size, + line_info_rec_size, }, ); @@ -407,7 +506,8 @@ impl Object { { parts.push(parts[0]); } - + self.section_sizes + .insert(section.name.to_owned(), section.size); match section.kind { BpfSectionKind::Data => { self.maps @@ -436,8 +536,10 @@ impl Object { ); } } - - _ => {} + BpfSectionKind::Undefined + | BpfSectionKind::BtfMaps + | BpfSectionKind::License + | BpfSectionKind::Version => {} } Ok(()) @@ -700,7 +802,7 @@ fn parse_map_def(name: &str, data: &[u8]) -> Result { } } -fn copy_instructions(data: &[u8]) -> Result, ParseError> { +pub(crate) fn copy_instructions(data: &[u8]) -> Result, ParseError> { if data.len() % mem::size_of::() > 0 { return Err(ParseError::InvalidProgramCode); } @@ -978,9 +1080,8 @@ mod tests { address: 0, section_index: SectionIndex(0), section_offset: 0, - instructions - } - }) if license.to_string_lossy() == "GPL" && name == "foo" && instructions.len() == 1 + instructions, + ..} }) if license.to_string_lossy() == "GPL" && name == "foo" && instructions.len() == 1 ); } diff --git a/aya/src/obj/relocation.rs b/aya/src/obj/relocation.rs index bc70f9b6..e8886bcc 100644 --- a/aya/src/obj/relocation.rs +++ b/aya/src/obj/relocation.rs @@ -13,7 +13,7 @@ use crate::{ BpfError, }; -const INS_SIZE: usize = mem::size_of::(); +pub(crate) const INS_SIZE: usize = mem::size_of::(); #[derive(Debug, Error)] enum RelocationError { @@ -227,6 +227,11 @@ impl<'a> FunctionLinker<'a> { // at `start_ins`. We'll use `start_ins` to do pc-relative calls. let start_ins = program.instructions.len(); program.instructions.extend(&fun.instructions); + + // link func and line info into the main program + // the offset needs to be adjusted + self.link_func_and_line_info(program, fun, start_ins)?; + self.linked_functions.insert(fun.address, start_ins); // relocate `fun`, recursively linking in all the callees @@ -296,6 +301,33 @@ impl<'a> FunctionLinker<'a> { Ok(()) } + + fn link_func_and_line_info( + &mut self, + program: &mut Function, + fun: &Function, + start: usize, + ) -> Result<(), RelocationError> { + let off_adj = start - (fun.section_offset as usize / INS_SIZE); + let func_info = &fun.func_info.func_info; + let func_info = func_info.iter().map(|f| { + let mut new = *f; + new.insn_off = f.insn_off + off_adj as u32; + new + }); + program.func_info.func_info.extend(func_info); + program.func_info.num_info = program.func_info.func_info.len() as u32; + + let line_info = &fun.line_info.line_info; + let line_info = line_info.iter().map(|l| { + let mut new = *l; + new.insn_off = l.insn_off + off_adj as u32; + new + }); + program.line_info.line_info.extend(line_info); + program.line_info.num_info = program.func_info.func_info.len() as u32; + Ok(()) + } } fn is_call(ins: &bpf_insn) -> bool { diff --git a/aya/src/programs/cgroup_skb.rs b/aya/src/programs/cgroup_skb.rs index 9aff1295..286e1fc8 100644 --- a/aya/src/programs/cgroup_skb.rs +++ b/aya/src/programs/cgroup_skb.rs @@ -88,13 +88,12 @@ impl CgroupSkb { }; let k_ver = kernel_version().unwrap(); if k_ver >= (5, 7, 0) { - let link_fd = - bpf_link_create(prog_fd, cgroup_fd, attach_type, 0).map_err(|(_, io_error)| { - ProgramError::SyscallError { - call: "bpf_link_create".to_owned(), - io_error, - } - })? as RawFd; + let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err( + |(_, io_error)| ProgramError::SyscallError { + call: "bpf_link_create".to_owned(), + io_error, + }, + )? as RawFd; Ok(self.data.link(FdLink { fd: Some(link_fd) })) } else { bpf_prog_attach(prog_fd, cgroup_fd, attach_type).map_err(|(_, io_error)| { diff --git a/aya/src/programs/extension.rs b/aya/src/programs/extension.rs new file mode 100644 index 00000000..f22462f5 --- /dev/null +++ b/aya/src/programs/extension.rs @@ -0,0 +1,140 @@ +use std::os::unix::prelude::{AsRawFd, RawFd}; +use thiserror::Error; + +use object::Endianness; + +use crate::{ + generated::{bpf_attach_type::BPF_CGROUP_INET_INGRESS, bpf_prog_type::BPF_PROG_TYPE_EXT}, + obj::btf::BtfKind, + programs::{load_program, FdLink, LinkRef, ProgramData, ProgramError}, + sys::{self, bpf_link_create}, + Btf, +}; + +/// The type returned when loading or attaching an [`Extension`] fails +#[derive(Debug, Error)] +pub enum ExtensionError { + #[error("target BPF program does not have BTF loaded to the kernel")] + NoBTF, +} + +/// A program used to extend existing BPF programs +/// +/// [`Extension`] programs can be loaded to replace a global +/// function in a program that has already been loaded. +/// +/// # Minimum kernel version +/// +/// The minimum kernel version required to use this feature is 5.9 +/// +/// # Examples +/// +/// ```no_run +/// use aya::{BpfLoader, programs::{Xdp, XdpFlags, Extension, ProgramFd}}; +/// use std::convert::TryInto; +/// +/// let mut bpf = BpfLoader::new().extension("extension").load_file("app.o")?; +/// let prog: &mut Xdp = bpf.program_mut("main").unwrap().try_into()?; +/// prog.load()?; +/// prog.attach("eth0", XdpFlags::default())?; +/// +/// let prog_fd = prog.fd().unwrap(); +/// let ext: &mut Extension = bpf.program_mut("extension").unwrap().try_into()?; +/// ext.load(prog_fd, "function_to_replace")?; +/// ext.attach()?; +/// Ok::<(), aya::BpfError>(()) +/// ``` +#[derive(Debug)] +#[doc(alias = "BPF_PROG_TYPE_EXT")] +pub struct Extension { + pub(crate) data: ProgramData, +} + +impl Extension { + /// Loads the extension inside the kernel. + /// + /// Prepares the code included in the extension to replace the code of the function + /// `func_name` within the eBPF program represented by the `program` file descriptor. + /// This requires that both the [`Extension`] and `program` have had their BTF + /// loaded into the kernel as the verifier must check that the function signatures + /// match. + /// + /// The extension code will be loaded but inactive until it's attached. + /// There are no restrictions on what functions may be replaced, so you could replace + /// the main entry point of your program with an extension. + /// + /// See also [`Program::load`](crate::programs::Program::load). + pub fn load(&mut self, program: T, func_name: &str) -> Result<(), ProgramError> { + let target_prog_fd = program.as_raw_fd(); + + let info = sys::bpf_obj_get_info_by_fd(target_prog_fd).map_err(|io_error| { + ProgramError::SyscallError { + call: "bpf_obj_get_info_by_fd".to_owned(), + io_error, + } + })?; + + if info.btf_id == 0 { + return Err(ProgramError::ExtensionError(ExtensionError::NoBTF)); + } + + let btf_fd = sys::bpf_btf_get_fd_by_id(info.btf_id).map_err(|io_error| { + ProgramError::SyscallError { + call: "bpf_btf_get_fd_by_id".to_owned(), + io_error, + } + })?; + + let mut buf = vec![0u8; 4096]; + let btf_info = match sys::btf_obj_get_info_by_fd(btf_fd, &mut buf) { + Ok(info) => { + if info.btf_size > buf.len() as u32 { + buf.resize(info.btf_size as usize, 0u8); + let btf_info = + sys::btf_obj_get_info_by_fd(btf_fd, &mut buf).map_err(|io_error| { + ProgramError::SyscallError { + call: "bpf_obj_get_info_by_fd".to_owned(), + io_error, + } + })?; + Ok(btf_info) + } else { + Ok(info) + } + } + Err(io_error) => Err(ProgramError::SyscallError { + call: "bpf_obj_get_info_by_fd".to_owned(), + io_error, + }), + }?; + + let btf = Btf::parse(&buf[0..btf_info.btf_size as usize], Endianness::default()) + .map_err(ProgramError::Btf)?; + + let btf_id = btf + .id_by_type_name_kind(func_name, BtfKind::Func) + .map_err(ProgramError::Btf)?; + + self.data.attach_btf_obj_fd = Some(btf_fd as u32); + self.data.attach_prog_fd = Some(target_prog_fd); + self.data.attach_btf_id = Some(btf_id); + load_program(BPF_PROG_TYPE_EXT, &mut self.data) + } + + /// Attaches the extension + /// + /// Attaches the extension effectively replacing the original target function. + /// Detaching the returned link restores the original function. + pub fn attach(&mut self) -> Result { + let prog_fd = self.data.fd_or_err()?; + let target_fd = self.data.attach_prog_fd.ok_or(ProgramError::NotLoaded)?; + let btf_id = self.data.attach_btf_id.ok_or(ProgramError::NotLoaded)?; + // the attach type must be set as 0, which is bpf_attach_type::BPF_CGROUP_INET_INGRESS + let link_fd = bpf_link_create(prog_fd, target_fd, BPF_CGROUP_INET_INGRESS, Some(btf_id), 0) + .map_err(|(_, io_error)| ProgramError::SyscallError { + call: "bpf_link_create".to_owned(), + io_error, + })? as RawFd; + Ok(self.data.link(FdLink { fd: Some(link_fd) })) + } +} diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs index 5b10e3e8..6e269cb3 100644 --- a/aya/src/programs/mod.rs +++ b/aya/src/programs/mod.rs @@ -37,6 +37,7 @@ //! [`Bpf::program_mut`]: crate::Bpf::program_mut //! [`maps`]: crate::maps mod cgroup_skb; +mod extension; mod fentry; mod fexit; mod kprobe; @@ -60,9 +61,8 @@ mod xdp; use libc::{close, dup, ENOSPC}; use std::{ cell::RefCell, - cmp, convert::TryFrom, - ffi::{CStr, CString}, + ffi::CString, io, os::unix::io::{AsRawFd, RawFd}, path::Path, @@ -71,6 +71,7 @@ use std::{ use thiserror::Error; pub use cgroup_skb::{CgroupSkb, CgroupSkbAttachType}; +pub use extension::{Extension, ExtensionError}; pub use fentry::FEntry; pub use fexit::FExit; pub use kprobe::{KProbe, KProbeError}; @@ -95,6 +96,7 @@ use crate::{ maps::MapError, obj::{self, btf::BtfError, Function, KernelVersion}, sys::{bpf_load_program, bpf_pin_object, bpf_prog_detach, bpf_prog_query, BpfLoadProgramAttrs}, + util::VerifierLog, }; /// Error type returned when working with programs. @@ -175,9 +177,21 @@ pub enum ProgramError { #[error(transparent)] TcError(#[from] TcError), + /// An error occurred while working with an [`Extension`] program. + #[error(transparent)] + ExtensionError(#[from] ExtensionError), + /// An error occurred while working with BTF. #[error(transparent)] Btf(#[from] BtfError), + + /// The program is not attached. + #[error("the program name `{name}` is invalid")] + InvalidName { name: String }, + + /// The program is too long. + #[error("the program name `{name}` it longer than 16 characters")] + NameTooLong { name: String }, } pub trait ProgramFd { @@ -204,6 +218,7 @@ pub enum Program { BtfTracePoint(BtfTracePoint), FEntry(FEntry), FExit(FExit), + Extension(Extension), } impl Program { @@ -242,6 +257,7 @@ impl Program { Program::BtfTracePoint(_) => BPF_PROG_TYPE_TRACING, Program::FEntry(_) => BPF_PROG_TYPE_TRACING, Program::FExit(_) => BPF_PROG_TYPE_TRACING, + Program::Extension(_) => BPF_PROG_TYPE_EXT, } } @@ -269,6 +285,7 @@ impl Program { Program::BtfTracePoint(p) => &p.data, Program::FEntry(p) => &p.data, Program::FExit(p) => &p.data, + Program::Extension(p) => &p.data, } } @@ -291,18 +308,22 @@ impl Program { Program::BtfTracePoint(p) => &mut p.data, Program::FEntry(p) => &mut p.data, Program::FExit(p) => &mut p.data, + Program::Extension(p) => &mut p.data, } } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub(crate) struct ProgramData { + pub(crate) name: Option, pub(crate) obj: obj::Program, pub(crate) fd: Option, pub(crate) links: Vec>>, pub(crate) expected_attach_type: Option, pub(crate) attach_btf_obj_fd: Option, pub(crate) attach_btf_id: Option, + pub(crate) attach_prog_fd: Option, + pub(crate) btf_fd: Option, } impl ProgramData { @@ -334,67 +355,21 @@ impl ProgramData { } } -const MIN_LOG_BUF_SIZE: usize = 1024 * 10; -const MAX_LOG_BUF_SIZE: usize = (std::u32::MAX >> 8) as usize; - -pub(crate) struct VerifierLog { - buf: Vec, -} - -impl VerifierLog { - fn new() -> VerifierLog { - VerifierLog { buf: Vec::new() } - } - - pub(crate) fn buf(&mut self) -> &mut Vec { - &mut self.buf - } - - fn grow(&mut self) { - let len = cmp::max( - MIN_LOG_BUF_SIZE, - cmp::min(MAX_LOG_BUF_SIZE, self.buf.capacity() * 10), - ); - self.buf.resize(len, 0); - self.reset(); - } - - fn reset(&mut self) { - if !self.buf.is_empty() { - self.buf[0] = 0; - } - } - - fn truncate(&mut self) { - if self.buf.is_empty() { - return; - } - - let pos = self - .buf - .iter() - .position(|b| *b == 0) - .unwrap_or(self.buf.len() - 1); - self.buf[pos] = 0; - self.buf.truncate(pos + 1); - } - - pub fn as_c_str(&self) -> Option<&CStr> { - if self.buf.is_empty() { - None - } else { - Some(CStr::from_bytes_with_nul(&self.buf).unwrap()) - } - } -} - fn load_program(prog_type: bpf_prog_type, data: &mut ProgramData) -> Result<(), ProgramError> { let ProgramData { obj, fd, .. } = data; if fd.is_some() { return Err(ProgramError::AlreadyLoaded); } let crate::obj::Program { - function: Function { instructions, .. }, + function: + Function { + instructions, + func_info, + line_info, + func_info_rec_size, + line_info_rec_size, + .. + }, license, kernel_version, .. @@ -411,16 +386,37 @@ fn load_program(prog_type: bpf_prog_type, data: &mut ProgramData) -> Result<(), let mut log_buf = VerifierLog::new(); let mut retries = 0; let mut ret; + + let prog_name = if let Some(name) = &data.name { + let name = name.clone(); + let prog_name = CString::new(name.clone()) + .map_err(|_| ProgramError::InvalidName { name: name.clone() })?; + + if prog_name.to_bytes().len() > 16 { + return Err(ProgramError::NameTooLong { name }); + } + Some(prog_name) + } else { + None + }; + loop { let attr = BpfLoadProgramAttrs { + name: prog_name.clone(), ty: prog_type, insns: instructions, license, kernel_version: target_kernel_version, expected_attach_type: data.expected_attach_type, + prog_btf_fd: data.btf_fd, attach_btf_obj_fd: data.attach_btf_obj_fd, attach_btf_id: data.attach_btf_id, + attach_prog_fd: data.attach_prog_fd, log: &mut log_buf, + func_info_rec_size: *func_info_rec_size, + func_info: func_info.clone(), + line_info_rec_size: *line_info_rec_size, + line_info: line_info.clone(), }; ret = bpf_load_program(attr); match &ret { @@ -491,7 +487,7 @@ pub(crate) fn query( } } -/// Detach an attached program. +/// Detach an attached program pub trait Link: std::fmt::Debug { fn detach(&mut self) -> Result<(), ProgramError>; } @@ -599,6 +595,12 @@ macro_rules! impl_program_fd { self.data.fd } } + + impl ProgramFd for &mut $struct_name { + fn fd(&self) -> Option { + self.data.fd + } + } )+ } } @@ -620,6 +622,7 @@ impl_program_fd!( BtfTracePoint, FEntry, FExit, + Extension, ); macro_rules! impl_try_from_program { @@ -668,6 +671,7 @@ impl_try_from_program!( BtfTracePoint, FEntry, FExit, + Extension, ); /// Provides information about a loaded program, like name, id and statistics diff --git a/aya/src/programs/xdp.rs b/aya/src/programs/xdp.rs index 3cb75856..cb88c864 100644 --- a/aya/src/programs/xdp.rs +++ b/aya/src/programs/xdp.rs @@ -5,8 +5,10 @@ use thiserror::Error; use crate::{ generated::{ - bpf_attach_type::BPF_XDP, bpf_prog_type::BPF_PROG_TYPE_XDP, XDP_FLAGS_DRV_MODE, - XDP_FLAGS_HW_MODE, XDP_FLAGS_REPLACE, XDP_FLAGS_SKB_MODE, XDP_FLAGS_UPDATE_IF_NOEXIST, + bpf_attach_type::{self, BPF_XDP}, + bpf_prog_type::BPF_PROG_TYPE_XDP, + XDP_FLAGS_DRV_MODE, XDP_FLAGS_HW_MODE, XDP_FLAGS_REPLACE, XDP_FLAGS_SKB_MODE, + XDP_FLAGS_UPDATE_IF_NOEXIST, }, programs::{load_program, FdLink, Link, LinkRef, ProgramData, ProgramError}, sys::{bpf_link_create, kernel_version, netlink_set_xdp_fd}, @@ -72,6 +74,7 @@ impl Xdp { /// /// See also [`Program::load`](crate::programs::Program::load). pub fn load(&mut self) -> Result<(), ProgramError> { + self.data.expected_attach_type = Some(bpf_attach_type::BPF_XDP); load_program(BPF_PROG_TYPE_XDP, &mut self.data) } @@ -88,7 +91,6 @@ impl Xdp { /// kernels. pub fn attach(&mut self, interface: &str, flags: XdpFlags) -> Result { let prog_fd = self.data.fd_or_err()?; - let c_interface = CString::new(interface).unwrap(); let if_index = unsafe { if_nametoindex(c_interface.as_ptr()) } as RawFd; if if_index == 0 { @@ -99,7 +101,7 @@ impl Xdp { let k_ver = kernel_version().unwrap(); if k_ver >= (5, 9, 0) { - let link_fd = bpf_link_create(prog_fd, if_index, BPF_XDP, flags.bits).map_err( + let link_fd = bpf_link_create(prog_fd, if_index, BPF_XDP, None, flags.bits).map_err( |(_, io_error)| ProgramError::SyscallError { call: "bpf_link_create".to_owned(), io_error, diff --git a/aya/src/sys/bpf.rs b/aya/src/sys/bpf.rs index 5ef38d89..a21033a3 100644 --- a/aya/src/sys/bpf.rs +++ b/aya/src/sys/bpf.rs @@ -1,20 +1,28 @@ +use crate::{ + generated::{btf_func_linkage, btf_param, btf_var_secinfo, BTF_INT_SIGNED, BTF_VAR_STATIC}, + obj::{btf::BtfType, copy_instructions}, + Btf, +}; +use libc::{c_char, c_long, close, ENOENT}; + use std::{ - cmp, - ffi::CStr, + cmp::{self, min}, + ffi::{CStr, CString}, io, mem::{self, MaybeUninit}, os::unix::io::RawFd, slice, }; -use libc::{c_long, ENOENT}; - use crate::{ bpf_map_def, - generated::{bpf_attach_type, bpf_attr, bpf_cmd, bpf_insn, bpf_prog_info, bpf_prog_type}, + generated::{ + bpf_attach_type, bpf_attr, bpf_btf_info, bpf_cmd, bpf_insn, bpf_prog_info, bpf_prog_type, + }, maps::PerCpuValues, - programs::VerifierLog, + obj::btf::{FuncSecInfo, LineSecInfo}, sys::{kernel_version, SysResult}, + util::VerifierLog, Pod, BPF_OBJ_NAME_LEN, }; @@ -60,20 +68,38 @@ pub(crate) fn bpf_get_object(path: &CStr) -> SysResult { } pub(crate) struct BpfLoadProgramAttrs<'a> { + pub(crate) name: Option, pub(crate) ty: bpf_prog_type, pub(crate) insns: &'a [bpf_insn], pub(crate) license: &'a CStr, pub(crate) kernel_version: u32, pub(crate) expected_attach_type: Option, + pub(crate) prog_btf_fd: Option, pub(crate) attach_btf_obj_fd: Option, pub(crate) attach_btf_id: Option, + pub(crate) attach_prog_fd: Option, pub(crate) log: &'a mut VerifierLog, + pub(crate) func_info_rec_size: usize, + pub(crate) func_info: FuncSecInfo, + pub(crate) line_info_rec_size: usize, + pub(crate) line_info: LineSecInfo, } pub(crate) fn bpf_load_program(aya_attr: BpfLoadProgramAttrs) -> SysResult { let mut attr = unsafe { mem::zeroed::() }; let u = unsafe { &mut attr.__bindgen_anon_3 }; + + if let Some(prog_name) = aya_attr.name { + let mut name: [c_char; 16] = [0; 16]; + let name_bytes = prog_name.to_bytes(); + let len = min(name.len(), name_bytes.len()); + name[..len].copy_from_slice(unsafe { + slice::from_raw_parts(name_bytes.as_ptr() as *const c_char, len) + }); + u.prog_name = name; + } + u.prog_type = aya_attr.ty as u32; if let Some(v) = aya_attr.expected_attach_type { u.expected_attach_type = v as u32; @@ -82,6 +108,22 @@ pub(crate) fn bpf_load_program(aya_attr: BpfLoadProgramAttrs) -> SysResult { u.insn_cnt = aya_attr.insns.len() as u32; u.license = aya_attr.license.as_ptr() as u64; u.kern_version = aya_attr.kernel_version; + + if let Some(btf_fd) = aya_attr.prog_btf_fd { + let line_info_buf = aya_attr.line_info.line_info_bytes(); + let func_info_buf = aya_attr.func_info.func_info_bytes(); + u.prog_btf_fd = btf_fd as u32; + if aya_attr.line_info_rec_size > 0 { + u.line_info = line_info_buf.as_ptr() as *const _ as u64; + u.line_info_cnt = aya_attr.line_info.len() as u32; + u.line_info_rec_size = aya_attr.line_info_rec_size as u32; + } + if aya_attr.func_info_rec_size > 0 { + u.func_info = func_info_buf.as_ptr() as *const _ as u64; + u.func_info_cnt = aya_attr.func_info.len() as u32; + u.func_info_rec_size = aya_attr.func_info_rec_size as u32; + } + } let log_buf = aya_attr.log.buf(); if log_buf.capacity() > 0 { u.log_level = 7; @@ -91,6 +133,10 @@ pub(crate) fn bpf_load_program(aya_attr: BpfLoadProgramAttrs) -> SysResult { if let Some(v) = aya_attr.attach_btf_obj_fd { u.__bindgen_anon_1.attach_btf_obj_fd = v; } + if let Some(v) = aya_attr.attach_prog_fd { + u.__bindgen_anon_1.attach_prog_fd = v as u32; + } + if let Some(v) = aya_attr.attach_btf_id { u.attach_btf_id = v; } @@ -266,6 +312,7 @@ pub(crate) fn bpf_link_create( prog_fd: RawFd, target_fd: RawFd, attach_type: bpf_attach_type, + btf_id: Option, flags: u32, ) -> SysResult { let mut attr = unsafe { mem::zeroed::() }; @@ -274,6 +321,9 @@ pub(crate) fn bpf_link_create( attr.link_create.__bindgen_anon_1.target_fd = target_fd as u32; attr.link_create.attach_type = attach_type as u32; attr.link_create.flags = flags; + if let Some(btf_id) = btf_id { + attr.link_create.__bindgen_anon_2.target_btf_id = btf_id; + } sys_bpf(bpf_cmd::BPF_LINK_CREATE, &attr) } @@ -359,6 +409,25 @@ pub(crate) fn bpf_obj_get_info_by_fd(prog_fd: RawFd) -> Result Result { + let mut attr = unsafe { mem::zeroed::() }; + let mut info = unsafe { mem::zeroed::() }; + let buf_size = buf.len() as u32; + info.btf = buf.as_ptr() as u64; + info.btf_size = buf_size; + attr.info.bpf_fd = prog_fd as u32; + attr.info.info = &info as *const bpf_btf_info as u64; + attr.info.info_len = mem::size_of::() as u32; + + match sys_bpf(bpf_cmd::BPF_OBJ_GET_INFO_BY_FD, &attr) { + Ok(_) => Ok(info), + Err((_, err)) => Err(err), + } +} + pub(crate) fn bpf_raw_tracepoint_open(name: Option<&CStr>, prog_fd: RawFd) -> SysResult { let mut attr = unsafe { mem::zeroed::() }; @@ -371,6 +440,233 @@ pub(crate) fn bpf_raw_tracepoint_open(name: Option<&CStr>, prog_fd: RawFd) -> Sy sys_bpf(bpf_cmd::BPF_RAW_TRACEPOINT_OPEN, &attr) } -fn sys_bpf(cmd: bpf_cmd, attr: &bpf_attr) -> SysResult { +pub(crate) fn bpf_load_btf(raw_btf: &[u8], log: &mut VerifierLog) -> Result { + let mut attr = unsafe { mem::zeroed::() }; + let u = unsafe { &mut attr.__bindgen_anon_7 }; + u.btf = raw_btf.as_ptr() as *const _ as u64; + u.btf_size = mem::size_of_val(raw_btf) as u32; + let log_buf = log.buf(); + if log_buf.capacity() > 0 { + u.btf_log_level = 1; + u.btf_log_buf = log_buf.as_mut_ptr() as u64; + u.btf_log_size = log_buf.capacity() as u32; + } + match sys_bpf(bpf_cmd::BPF_BTF_LOAD, &attr) { + Ok(v) => Ok(v as RawFd), + Err((_, err)) => Err(err), + } +} + +pub(crate) fn bpf_btf_get_fd_by_id(id: u32) -> Result { + let mut attr = unsafe { mem::zeroed::() }; + attr.__bindgen_anon_6.__bindgen_anon_1.btf_id = id; + + match sys_bpf(bpf_cmd::BPF_BTF_GET_FD_BY_ID, &attr) { + Ok(v) => Ok(v as RawFd), + Err((_, err)) => Err(err), + } +} + +pub(crate) fn is_prog_name_supported() -> bool { + let mut attr = unsafe { mem::zeroed::() }; + let u = unsafe { &mut attr.__bindgen_anon_3 }; + let mut name: [c_char; 16] = [0; 16]; + let cstring = CString::new("aya_name_check").unwrap(); + let name_bytes = cstring.to_bytes(); + let len = min(name.len(), name_bytes.len()); + name[..len].copy_from_slice(unsafe { + slice::from_raw_parts(name_bytes.as_ptr() as *const c_char, len) + }); + u.prog_name = name; + + let prog: &[u8] = &[ + 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r0 = 0 + 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit + ]; + + let gpl = b"GPL\0"; + u.license = gpl.as_ptr() as u64; + + let insns = copy_instructions(prog).unwrap(); + u.insn_cnt = insns.len() as u32; + u.insns = insns.as_ptr() as u64; + u.prog_type = bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER as u32; + + match sys_bpf(bpf_cmd::BPF_PROG_LOAD, &attr) { + Ok(v) => { + let fd = v as RawFd; + unsafe { close(fd) }; + true + } + Err(_) => false, + } +} + +pub(crate) fn is_btf_supported() -> bool { + let mut btf = Btf::new(); + let name_offset = btf.add_string("int".to_string()); + let int_type = BtfType::new_int(name_offset, 4, BTF_INT_SIGNED, 0); + btf.add_type(int_type); + let btf_bytes = btf.to_bytes(); + + let mut attr = unsafe { mem::zeroed::() }; + let u = unsafe { &mut attr.__bindgen_anon_7 }; + u.btf = btf_bytes.as_ptr() as u64; + u.btf_size = btf_bytes.len() as u32; + + match sys_bpf(bpf_cmd::BPF_BTF_LOAD, &attr) { + Ok(v) => { + let fd = v as RawFd; + unsafe { close(fd) }; + true + } + Err(_) => false, + } +} + +pub(crate) fn is_btf_func_supported() -> bool { + let mut btf = Btf::new(); + let name_offset = btf.add_string("int".to_string()); + let int_type = BtfType::new_int(name_offset, 4, BTF_INT_SIGNED, 0); + let int_type_id = btf.add_type(int_type); + + let a_name = btf.add_string("a".to_string()); + let b_name = btf.add_string("b".to_string()); + let params = vec![ + btf_param { + name_off: a_name, + type_: int_type_id, + }, + btf_param { + name_off: b_name, + type_: int_type_id, + }, + ]; + let func_proto = BtfType::new_func_proto(params, int_type_id); + let func_proto_type_id = btf.add_type(func_proto); + + let add = btf.add_string("inc".to_string()); + let func = BtfType::new_func(add, func_proto_type_id, btf_func_linkage::BTF_FUNC_STATIC); + btf.add_type(func); + + let btf_bytes = btf.to_bytes(); + + let mut attr = unsafe { mem::zeroed::() }; + let u = unsafe { &mut attr.__bindgen_anon_7 }; + u.btf = btf_bytes.as_ptr() as u64; + u.btf_size = btf_bytes.len() as u32; + + match sys_bpf(bpf_cmd::BPF_BTF_LOAD, &attr) { + Ok(v) => { + let fd = v as RawFd; + unsafe { close(fd) }; + true + } + Err(_) => false, + } +} + +pub(crate) fn is_btf_func_global_supported() -> bool { + let mut btf = Btf::new(); + let name_offset = btf.add_string("int".to_string()); + let int_type = BtfType::new_int(name_offset, 4, BTF_INT_SIGNED, 0); + let int_type_id = btf.add_type(int_type); + + let a_name = btf.add_string("a".to_string()); + let b_name = btf.add_string("b".to_string()); + let params = vec![ + btf_param { + name_off: a_name, + type_: int_type_id, + }, + btf_param { + name_off: b_name, + type_: int_type_id, + }, + ]; + let func_proto = BtfType::new_func_proto(params, int_type_id); + let func_proto_type_id = btf.add_type(func_proto); + + let add = btf.add_string("inc".to_string()); + let func = BtfType::new_func(add, func_proto_type_id, btf_func_linkage::BTF_FUNC_GLOBAL); + btf.add_type(func); + + let btf_bytes = btf.to_bytes(); + + let mut attr = unsafe { mem::zeroed::() }; + let u = unsafe { &mut attr.__bindgen_anon_7 }; + u.btf = btf_bytes.as_ptr() as u64; + u.btf_size = btf_bytes.len() as u32; + + match sys_bpf(bpf_cmd::BPF_BTF_LOAD, &attr) { + Ok(v) => { + let fd = v as RawFd; + unsafe { close(fd) }; + true + } + Err(_) => false, + } +} + +pub(crate) fn is_btf_datasec_supported() -> bool { + let mut btf = Btf::new(); + let name_offset = btf.add_string("int".to_string()); + let int_type = BtfType::new_int(name_offset, 4, BTF_INT_SIGNED, 0); + let int_type_id = btf.add_type(int_type); + + let name_offset = btf.add_string("foo".to_string()); + let var_type = BtfType::new_var(name_offset, int_type_id, BTF_VAR_STATIC); + let var_type_id = btf.add_type(var_type); + + let name_offset = btf.add_string(".data".to_string()); + let variables = vec![btf_var_secinfo { + type_: var_type_id, + offset: 0, + size: 4, + }]; + let datasec_type = BtfType::new_datasec(name_offset, variables, 4); + btf.add_type(datasec_type); + + let btf_bytes = btf.to_bytes(); + + let mut attr = unsafe { mem::zeroed::() }; + let u = unsafe { &mut attr.__bindgen_anon_7 }; + u.btf = btf_bytes.as_ptr() as u64; + u.btf_size = btf_bytes.len() as u32; + + match sys_bpf(bpf_cmd::BPF_BTF_LOAD, &attr) { + Ok(v) => { + let fd = v as RawFd; + unsafe { close(fd) }; + true + } + Err(_) => false, + } +} + +pub(crate) fn is_btf_float_supported() -> bool { + let mut btf = Btf::new(); + let name_offset = btf.add_string("float".to_string()); + let float_type = BtfType::new_float(name_offset, 16); + btf.add_type(float_type); + + let btf_bytes = btf.to_bytes(); + + let mut attr = unsafe { mem::zeroed::() }; + let u = unsafe { &mut attr.__bindgen_anon_7 }; + u.btf = btf_bytes.as_ptr() as u64; + u.btf_size = btf_bytes.len() as u32; + + match sys_bpf(bpf_cmd::BPF_BTF_LOAD, &attr) { + Ok(v) => { + let fd = v as RawFd; + unsafe { close(fd) }; + true + } + Err(_) => false, + } +} + +pub fn sys_bpf(cmd: bpf_cmd, attr: &bpf_attr) -> SysResult { syscall(Syscall::Bpf { cmd, attr }) } diff --git a/aya/src/util.rs b/aya/src/util.rs index f2c31e68..7cc524f9 100644 --- a/aya/src/util.rs +++ b/aya/src/util.rs @@ -1,7 +1,8 @@ //! Utility functions. use std::{ + cmp, collections::BTreeMap, - ffi::CString, + ffi::{CStr, CString}, fs::{self, File}, io::{self, BufReader}, mem, slice, @@ -155,6 +156,60 @@ pub(crate) unsafe fn bytes_of(val: &T) -> &[u8] { slice::from_raw_parts(slice::from_ref(val).as_ptr().cast(), size) } +const MIN_LOG_BUF_SIZE: usize = 1024 * 10; +const MAX_LOG_BUF_SIZE: usize = (std::u32::MAX >> 8) as usize; + +pub(crate) struct VerifierLog { + buf: Vec, +} + +impl VerifierLog { + pub(crate) fn new() -> VerifierLog { + VerifierLog { buf: Vec::new() } + } + + pub(crate) fn buf(&mut self) -> &mut Vec { + &mut self.buf + } + + pub(crate) fn grow(&mut self) { + let len = cmp::max( + MIN_LOG_BUF_SIZE, + cmp::min(MAX_LOG_BUF_SIZE, self.buf.capacity() * 10), + ); + self.buf.resize(len, 0); + self.reset(); + } + + pub(crate) fn reset(&mut self) { + if !self.buf.is_empty() { + self.buf[0] = 0; + } + } + + pub(crate) fn truncate(&mut self) { + if self.buf.is_empty() { + return; + } + + let pos = self + .buf + .iter() + .position(|b| *b == 0) + .unwrap_or(self.buf.len() - 1); + self.buf[pos] = 0; + self.buf.truncate(pos + 1); + } + + pub(crate) fn as_c_str(&self) -> Option<&CStr> { + if self.buf.is_empty() { + None + } else { + Some(CStr::from_bytes_with_nul(&self.buf).unwrap()) + } + } +} + #[cfg(test)] mod tests { use super::*; From 877c76043a6d313391159523dc40046426800c43 Mon Sep 17 00:00:00 2001 From: Dave Tucker Date: Sat, 18 Dec 2021 20:54:06 +0000 Subject: [PATCH 3/3] btf: Add fixup for PTR types from Rust Signed-off-by: Dave Tucker --- aya/src/obj/btf/btf.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/aya/src/obj/btf/btf.rs b/aya/src/obj/btf/btf.rs index 3314cc05..3eced682 100644 --- a/aya/src/obj/btf/btf.rs +++ b/aya/src/obj/btf/btf.rs @@ -386,6 +386,13 @@ impl Btf { // datasec sizes aren't set by llvm // we need to fix them here before loading the btf to the kernel match t { + BtfType::Ptr(mut ty) => { + // Rust emits names for pointer types, which the kernel doesn't like + // While I figure out if this needs fixing in the Kernel or LLVM, we'll + // do a fixup here + ty.name_off = 0; + types.push(BtfType::Ptr(ty)); + } BtfType::DataSec(mut ty, data) => { // Start DataSec Fixups let sec_name = self.type_name(t)?.ok_or(BtfError::InvalidTypeInfo)?;