Browse Source

Fix load errors for empty (but existent) BTF/BTF.ext sections (#608)

* use the hdr_len of BTF.ext sections rather than size of struct

Otherwise this will erroneously fail on older btf_ext_header that have
less fields than the bindgen'd struct

* do not attempt to load a BTF object that has no types

* add tests

* fix: hdr_len i32 -> u32

* guard against a bigger header in the future

* use separate unsafe blocks

* simplify writing to zero'd out header

* merge safe block and address typo
Andrés 1 year ago
parent
commit
5894c4ce82
2 changed files with 105 additions and 15 deletions
  1. 80 15
      aya-obj/src/btf/btf.rs
  2. 25 0
      aya-obj/src/obj.rs

+ 80 - 15
aya-obj/src/btf/btf.rs

@@ -210,6 +210,11 @@ impl Btf {
         }
     }
 
+    pub(crate) fn is_empty(&self) -> bool {
+        // the first one is awlays BtfType::Unknown
+        self.types.types.len() < 2
+    }
+
     pub(crate) fn types(&self) -> impl Iterator<Item = &BtfType> {
         self.types.types.iter()
     }
@@ -628,7 +633,10 @@ impl Object {
         &mut self,
         features: &BtfFeatures,
     ) -> Result<Option<&Btf>, BtfError> {
-        if let Some(ref mut obj_btf) = self.btf {
+        if let Some(ref mut obj_btf) = &mut self.btf {
+            if obj_btf.is_empty() {
+                return Ok(None);
+            }
             // fixup btf
             obj_btf.fixup_and_sanitize(
                 &self.section_sizes,
@@ -667,13 +675,65 @@ impl BtfExt {
         endianness: Endianness,
         btf: &Btf,
     ) -> Result<BtfExt, BtfError> {
-        // Safety: btf_ext_header is POD so read_unaligned is safe
-        let header = unsafe {
-            ptr::read_unaligned::<btf_ext_header>(data.as_ptr() as *const btf_ext_header)
+        #[repr(C)]
+        #[derive(Debug, Copy, Clone)]
+        struct MinimalHeader {
+            pub magic: u16,
+            pub version: u8,
+            pub flags: u8,
+            pub hdr_len: u32,
+        }
+
+        if data.len() < std::mem::size_of::<MinimalHeader>() {
+            return Err(BtfError::InvalidHeader);
+        }
+
+        let header = {
+            // first find the actual size of the header by converting into the minimal valid header
+            // Safety: MinimalHeader is POD so read_unaligned is safe
+            let minimal_header = unsafe {
+                ptr::read_unaligned::<MinimalHeader>(data.as_ptr() as *const MinimalHeader)
+            };
+
+            let len_to_read = minimal_header.hdr_len as usize;
+
+            // prevent invalid input from causing UB
+            if data.len() < len_to_read {
+                return Err(BtfError::InvalidHeader);
+            }
+
+            // forwards compatibility: if newer headers are bigger
+            // than the pre-generated btf_ext_header we should only
+            // read up to btf_ext_header
+            let len_to_read = len_to_read.min(std::mem::size_of::<btf_ext_header>());
+
+            // now create our full-fledge header; but start with it
+            // zeroed out so unavailable fields stay as zero on older
+            // BTF.ext sections
+            let mut header = std::mem::MaybeUninit::<btf_ext_header>::zeroed();
+            // Safety: we have checked that len_to_read is less than
+            // size_of::<btf_ext_header> and less than
+            // data.len(). Additionally, we know that the header has
+            // been initialized so it's safe to call for assume_init.
+            unsafe {
+                std::ptr::copy(data.as_ptr(), header.as_mut_ptr() as *mut u8, len_to_read);
+                header.assume_init()
+            }
         };
 
+        let btf_ext_header {
+            hdr_len,
+            func_info_off,
+            func_info_len,
+            line_info_off,
+            line_info_len,
+            core_relo_off,
+            core_relo_len,
+            ..
+        } = header;
+
         let rec_size = |offset, len| {
-            let offset = mem::size_of::<btf_ext_header>() + offset as usize;
+            let offset = hdr_len as usize + offset as usize;
             let len = len as usize;
             // check that there's at least enough space for the `rec_size` field
             if (len > 0 && len < 4) || offset + len > data.len() {
@@ -695,16 +755,6 @@ impl BtfExt {
             })
         };
 
-        let btf_ext_header {
-            func_info_off,
-            func_info_len,
-            line_info_off,
-            line_info_len,
-            core_relo_off,
-            core_relo_len,
-            ..
-        } = header;
-
         let mut ext = BtfExt {
             header,
             relocations: Vec::new(),
@@ -1039,6 +1089,21 @@ mod tests {
         }
     }
 
+    #[test]
+    fn parsing_older_ext_data() {
+        let btf_data = [
+            159, 235, 1, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+        ];
+        let btf_ext_data = [
+            159, 235, 1, 0, 24, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 8, 0, 0,
+            0, 16, 0, 0, 0,
+        ];
+        let btf = Btf::parse(&btf_data, Endianness::default()).unwrap();
+        let btf_ext = BtfExt::parse(&btf_ext_data, Endianness::default(), &btf).unwrap();
+        assert_eq!(btf_ext.func_info_rec_size(), 8);
+        assert_eq!(btf_ext.line_info_rec_size(), 16);
+    }
+
     #[test]
     fn test_write_btf() {
         let mut btf = Btf::new();

+ 25 - 0
aya-obj/src/obj.rs

@@ -1634,6 +1634,31 @@ mod tests {
         )
     }
 
+    #[test]
+    fn sanitizes_empty_btf_files_to_none() {
+        let mut obj = fake_obj();
+        obj.parse_section(fake_section(
+            BpfSectionKind::Btf,
+            ".BTF",
+            &[
+                159, 235, 1, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+            ],
+        ))
+        .unwrap();
+        obj.parse_section(fake_section(
+            BpfSectionKind::BtfExt,
+            ".BTF.ext",
+            &[
+                159, 235, 1, 0, 24, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 8, 0,
+                0, 0, 16, 0, 0, 0,
+            ],
+        ))
+        .unwrap();
+
+        let btf = obj.fixup_and_sanitize_btf(&BtfFeatures::default()).unwrap();
+        assert!(btf.is_none());
+    }
+
     #[test]
     fn test_parse_program_error() {
         let obj = fake_obj();