elf/
note.rs

1//! Parsing ELF notes: `.note.*`, [SHT_NOTE](crate::abi::SHT_NOTE), [PT_NOTE](crate::abi::PT_NOTE)
2//!
3//! Example for getting the GNU ABI-tag note:
4//! ```
5//! use elf::ElfBytes;
6//! use elf::endian::AnyEndian;
7//! use elf::note::Note;
8//! use elf::note::NoteGnuAbiTag;
9//!
10//! let path = std::path::PathBuf::from("sample-objects/basic.x86_64");
11//! let file_data = std::fs::read(path).expect("Could not read file.");
12//! let slice = file_data.as_slice();
13//! let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
14//!
15//! let shdr = file
16//!     .section_header_by_name(".note.ABI-tag")
17//!     .expect("section table should be parseable")
18//!     .expect("file should have a .note.ABI-tag section");
19//!
20//! let notes: Vec<_> = file
21//!     .section_data_as_notes(&shdr)
22//!     .expect("Should be able to get note section data")
23//!     .collect();
24//! assert_eq!(
25//!     notes[0],
26//!     Note::GnuAbiTag(NoteGnuAbiTag {
27//!         os: 0,
28//!         major: 2,
29//!         minor: 6,
30//!         subminor: 32
31//!     })
32//! );
33//! ```
34use crate::abi;
35use crate::endian::EndianParse;
36use crate::file::Class;
37use crate::parse::{ParseAt, ParseError, ReadBytesExt};
38use core::mem::size_of;
39use core::str::from_utf8;
40
41/// This enum contains parsed Note variants which can be matched on
42#[derive(Debug, PartialEq, Eq)]
43pub enum Note<'data> {
44    /// (name: [abi::ELF_NOTE_GNU], n_type: [abi::NT_GNU_ABI_TAG])
45    GnuAbiTag(NoteGnuAbiTag),
46    /// (name: [abi::ELF_NOTE_GNU], n_type: [abi::NT_GNU_BUILD_ID])
47    GnuBuildId(NoteGnuBuildId<'data>),
48    /// All other notes that we don't know how to parse
49    Unknown(NoteAny<'data>),
50}
51
52impl<'data> Note<'data> {
53    fn parse_at<E: EndianParse>(
54        endian: E,
55        _class: Class,
56        align: usize,
57        offset: &mut usize,
58        data: &'data [u8],
59    ) -> Result<Self, ParseError> {
60        // We don't know what to do if the section or segment header specified a zero alignment, so error
61        // (this is likely a file corruption)
62        if align == 0 {
63            return Err(ParseError::UnexpectedAlignment(align));
64        }
65
66        // It looks like clang and gcc emit 32-bit notes for 64-bit files, so we
67        // currently always parse all note headers as 32-bit.
68        let nhdr = NoteHeader::parse_at(endian, Class::ELF32, offset, data)?;
69
70        let name_start = *offset;
71        let name_buf_size: usize = nhdr.n_namesz.saturating_sub(1).try_into()?;
72        let name_buf_end = name_start
73            .checked_add(name_buf_size)
74            .ok_or(ParseError::IntegerOverflow)?;
75        let name_buf = data.get_bytes(name_start..name_buf_end)?;
76        let name = from_utf8(name_buf)?;
77
78        // move forward for entire namesz, including the NUL byte we left out of our str
79        *offset = (*offset)
80            .checked_add(nhdr.n_namesz.try_into()?)
81            .ok_or(ParseError::IntegerOverflow)?;
82
83        // skip over padding if needed to get back to 4-byte alignment
84        if *offset % align > 0 {
85            *offset = (*offset)
86                .checked_add(align - *offset % align)
87                .ok_or(ParseError::IntegerOverflow)?;
88        }
89
90        let desc_start = *offset;
91        let desc_size: usize = nhdr.n_descsz.try_into()?;
92        let desc_end = desc_start
93            .checked_add(desc_size)
94            .ok_or(ParseError::IntegerOverflow)?;
95        let raw_desc = data.get_bytes(desc_start..desc_end)?;
96        *offset = desc_end;
97
98        // skip over padding if needed to get back to 4-byte alignment
99        if *offset % align > 0 {
100            *offset = (*offset)
101                .checked_add(align - *offset % align)
102                .ok_or(ParseError::IntegerOverflow)?;
103        }
104
105        // Interpret the note contents to try to return a known note variant
106        match name {
107            abi::ELF_NOTE_GNU => match nhdr.n_type {
108                abi::NT_GNU_ABI_TAG => {
109                    let mut offset = 0;
110                    Ok(Note::GnuAbiTag(NoteGnuAbiTag::parse_at(
111                        endian,
112                        _class,
113                        &mut offset,
114                        raw_desc,
115                    )?))
116                }
117                abi::NT_GNU_BUILD_ID => Ok(Note::GnuBuildId(NoteGnuBuildId(raw_desc))),
118                _ => Ok(Note::Unknown(NoteAny {
119                    n_type: nhdr.n_type,
120                    name,
121                    desc: raw_desc,
122                })),
123            },
124            _ => Ok(Note::Unknown(NoteAny {
125                n_type: nhdr.n_type,
126                name,
127                desc: raw_desc,
128            })),
129        }
130    }
131}
132
133/// Contains four 4-byte integers.
134/// The first 4-byte integer specifies the os. The second, third, and fourth
135/// 4-byte integers contain the earliest compatible kernel version.
136/// For example, if the 3 integers are 6, 0, and 7, this signifies a 6.0.7 kernel.
137///
138/// (see: <https://raw.githubusercontent.com/wiki/hjl-tools/linux-abi/linux-abi-draft.pdf>)
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140pub struct NoteGnuAbiTag {
141    pub os: u32,
142    pub major: u32,
143    pub minor: u32,
144    pub subminor: u32,
145}
146
147impl ParseAt for NoteGnuAbiTag {
148    fn parse_at<E: EndianParse>(
149        endian: E,
150        _class: Class,
151        offset: &mut usize,
152        data: &[u8],
153    ) -> Result<Self, ParseError> {
154        Ok(NoteGnuAbiTag {
155            os: endian.parse_u32_at(offset, data)?,
156            major: endian.parse_u32_at(offset, data)?,
157            minor: endian.parse_u32_at(offset, data)?,
158            subminor: endian.parse_u32_at(offset, data)?,
159        })
160    }
161
162    fn size_for(_class: Class) -> usize {
163        size_of::<u32>() * 4
164    }
165}
166
167/// Contains a build ID note which is unique among the set of meaningful contents
168/// for ELF files and identical when the output file would otherwise have been identical.
169/// This is a zero-copy type which merely contains a slice of the note data from which it was parsed.
170///
171/// (see: <https://raw.githubusercontent.com/wiki/hjl-tools/linux-abi/linux-abi-draft.pdf>)
172#[derive(Debug, Clone, Copy, PartialEq, Eq)]
173pub struct NoteGnuBuildId<'data>(pub &'data [u8]);
174
175/// Contains the raw fields found in any ELF note. Used for notes that we don't know
176/// how to parse into more specific types.
177#[derive(Debug, PartialEq, Eq)]
178pub struct NoteAny<'data> {
179    pub n_type: u64,
180    pub name: &'data str,
181    pub desc: &'data [u8],
182}
183
184#[derive(Debug)]
185pub struct NoteIterator<'data, E: EndianParse> {
186    endian: E,
187    class: Class,
188    align: usize,
189    data: &'data [u8],
190    offset: usize,
191}
192
193impl<'data, E: EndianParse> NoteIterator<'data, E> {
194    pub fn new(endian: E, class: Class, align: usize, data: &'data [u8]) -> Self {
195        NoteIterator {
196            endian,
197            class,
198            align,
199            data,
200            offset: 0,
201        }
202    }
203}
204
205impl<'data, E: EndianParse> Iterator for NoteIterator<'data, E> {
206    type Item = Note<'data>;
207    fn next(&mut self) -> Option<Self::Item> {
208        if self.data.is_empty() {
209            return None;
210        }
211
212        Note::parse_at(
213            self.endian,
214            self.class,
215            self.align,
216            &mut self.offset,
217            self.data,
218        )
219        .ok()
220    }
221}
222
223#[derive(Debug, Clone, PartialEq, Eq)]
224struct NoteHeader {
225    pub n_namesz: u64,
226    pub n_descsz: u64,
227    pub n_type: u64,
228}
229
230impl ParseAt for NoteHeader {
231    fn parse_at<E: EndianParse>(
232        endian: E,
233        class: Class,
234        offset: &mut usize,
235        data: &[u8],
236    ) -> Result<Self, ParseError> {
237        match class {
238            Class::ELF32 => Ok(NoteHeader {
239                n_namesz: endian.parse_u32_at(offset, data)? as u64,
240                n_descsz: endian.parse_u32_at(offset, data)? as u64,
241                n_type: endian.parse_u32_at(offset, data)? as u64,
242            }),
243            Class::ELF64 => Ok(NoteHeader {
244                n_namesz: endian.parse_u64_at(offset, data)?,
245                n_descsz: endian.parse_u64_at(offset, data)?,
246                n_type: endian.parse_u64_at(offset, data)?,
247            }),
248        }
249    }
250
251    #[inline]
252    fn size_for(class: Class) -> usize {
253        match class {
254            Class::ELF32 => 12,
255            Class::ELF64 => 24,
256        }
257    }
258}
259
260#[cfg(test)]
261mod parse_tests {
262    use super::*;
263    use crate::abi;
264    use crate::endian::{BigEndian, LittleEndian};
265
266    #[test]
267    fn parse_nt_gnu_abi_tag() {
268        #[rustfmt::skip]
269        let data = [
270            0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
271            0x01, 0x00, 0x00, 0x00, 0x47, 0x4e, 0x55, 0x00,
272            0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
273            0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
274        ];
275
276        let mut offset = 0;
277        let note = Note::parse_at(LittleEndian, Class::ELF32, 4, &mut offset, &data)
278            .expect("Failed to parse");
279
280        assert_eq!(
281            note,
282            Note::GnuAbiTag(NoteGnuAbiTag {
283                os: abi::ELF_NOTE_GNU_ABI_TAG_OS_LINUX,
284                major: 2,
285                minor: 6,
286                subminor: 32
287            })
288        );
289    }
290
291    #[test]
292    fn parse_desc_gnu_build_id() {
293        let data = [
294            0x04, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x47, 0x4e,
295            0x55, 0x00, 0x77, 0x41, 0x9f, 0x0d, 0xa5, 0x10, 0x83, 0x0c, 0x57, 0xa7, 0xc8, 0xcc,
296            0xb0, 0xee, 0x85, 0x5f, 0xee, 0xd3, 0x76, 0xa3,
297        ];
298
299        let mut offset = 0;
300        let note = Note::parse_at(LittleEndian, Class::ELF32, 4, &mut offset, &data)
301            .expect("Failed to parse");
302
303        assert_eq!(
304            note,
305            Note::GnuBuildId(NoteGnuBuildId(&[
306                0x77, 0x41, 0x9f, 0x0d, 0xa5, 0x10, 0x83, 0x0c, 0x57, 0xa7, 0xc8, 0xcc, 0xb0, 0xee,
307                0x85, 0x5f, 0xee, 0xd3, 0x76, 0xa3,
308            ]))
309        );
310    }
311
312    #[test]
313    fn parse_note_errors_with_zero_alignment() {
314        // This is a .note.gnu.property section
315        #[rustfmt::skip]
316        let data = [
317            0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
318            0x05, 0x00, 0x00, 0x00, 0x47, 0x4e, 0x55, 0x00,
319            0x02, 0x00, 0x00, 0xc0, 0x04, 0x00, 0x00, 0x00,
320            0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
321        ];
322
323        let mut offset = 0;
324        Note::parse_at(LittleEndian, Class::ELF64, 0, &mut offset, &data)
325            .expect_err("Should have gotten an alignment error");
326    }
327    #[test]
328    fn parse_note_with_8_byte_alignment() {
329        // This is a .note.gnu.property section, which has been seen generated with 8-byte alignment
330        #[rustfmt::skip]
331        let data = [
332            0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
333            0x05, 0x00, 0x00, 0x00, 0x47, 0x4e, 0x55, 0x00,
334            0x02, 0x00, 0x00, 0xc0, 0x04, 0x00, 0x00, 0x00,
335            0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
336        ];
337
338        // Even though the file class is ELF64, we parse it as a 32-bit struct. gcc/clang seem to output 32-bit notes
339        // even though the gABI states that ELF64 files should contain 64-bit notes. Sometimes those notes are generated
340        // in sections with 4-byte alignment, and other times with 8-byte alignment, as specified by shdr.sh_addralign.
341        //
342        // See https://raw.githubusercontent.com/wiki/hjl-tools/linux-abi/linux-abi-draft.pdf
343        // Excerpt:
344        //     All entries in a PT_NOTE segment have the same alignment which equals to the
345        //     p_align field in program header.
346        //     According to gABI, each note entry should be aligned to 4 bytes in 32-bit
347        //     objects or 8 bytes in 64-bit objects. But .note.ABI-tag section (see Sec-
348        //     tion 2.1.6) and .note.gnu.build-id section (see Section 2.1.4) are aligned
349        //     to 4 bytes in both 32-bit and 64-bit objects. Note parser should use p_align for
350        //     note alignment, instead of assuming alignment based on ELF file class.
351        let mut offset = 0;
352        let note = Note::parse_at(LittleEndian, Class::ELF64, 8, &mut offset, &data)
353            .expect("Failed to parse");
354        assert_eq!(
355            note,
356            Note::Unknown(NoteAny {
357                n_type: 5,
358                name: abi::ELF_NOTE_GNU,
359                desc: &[
360                    0x2, 0x0, 0x0, 0xc0, 0x4, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
361                ]
362            })
363        );
364    }
365
366    #[test]
367    fn parse_note_with_8_byte_alignment_unaligned_namesz() {
368        let data = [
369            0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // namesz 5, descsz 2
370            0x42, 0x00, 0x00, 0x00, 0x47, 0x4e, 0x55, 0x55, // type 42 (unknown), name GNUU
371            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // NUL + 7 pad for 8 alignment
372            0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // desc 0102 + 6 pad for alignment
373        ];
374
375        let mut offset = 0;
376        let note = Note::parse_at(LittleEndian, Class::ELF32, 8, &mut offset, &data)
377            .expect("Failed to parse");
378        assert_eq!(
379            note,
380            Note::Unknown(NoteAny {
381                n_type: 0x42,
382                name: &"GNUU",
383                desc: &[0x01, 0x02],
384            })
385        );
386        assert_eq!(offset, 32);
387    }
388
389    #[test]
390    fn parse_note_for_elf64_expects_nhdr32() {
391        let data = [
392            0x04, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x47, 0x4e,
393            0x55, 0x00, 0x77, 0x41, 0x9f, 0x0d, 0xa5, 0x10, 0x83, 0x0c, 0x57, 0xa7, 0xc8, 0xcc,
394            0xb0, 0xee, 0x85, 0x5f, 0xee, 0xd3, 0x76, 0xa3,
395        ];
396
397        let mut offset = 0;
398        // Even though the file class is ELF64, we parse it as a 32-bit struct. gcc/clang seem to output 32-bit notes
399        // even though the gABI states that ELF64 files should contain 64-bit notes.
400        let note = Note::parse_at(LittleEndian, Class::ELF64, 4, &mut offset, &data)
401            .expect("Failed to parse");
402        assert_eq!(
403            note,
404            Note::GnuBuildId(NoteGnuBuildId(&[
405                0x77, 0x41, 0x9f, 0x0d, 0xa5, 0x10, 0x83, 0x0c, 0x57, 0xa7, 0xc8, 0xcc, 0xb0, 0xee,
406                0x85, 0x5f, 0xee, 0xd3, 0x76, 0xa3,
407            ]))
408        );
409    }
410
411    #[test]
412    fn parse_note_32_lsb() {
413        let data = [
414            0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00,
415            0x00, 0x00,
416        ];
417
418        let mut offset = 0;
419        let note = Note::parse_at(LittleEndian, Class::ELF32, 4, &mut offset, &data)
420            .expect("Failed to parse");
421        assert_eq!(
422            note,
423            Note::Unknown(NoteAny {
424                n_type: 6,
425                name: "",
426                desc: &[0x20, 0x0],
427            })
428        );
429        assert_eq!(offset, 16);
430    }
431
432    #[test]
433    fn parse_note_32_lsb_with_name_padding() {
434        let data = [
435            0x03, 0x00, 0x00, 0x00, // namesz 3
436            0x04, 0x00, 0x00, 0x00, // descsz 4
437            0x01, 0x00, 0x00, 0x00, // type 1
438            0x47, 0x4e, 0x00, 0x00, // name GN\0 + 1 pad byte
439            0x01, 0x02, 0x03, 0x04,
440        ]; // desc 01020304
441
442        let mut offset = 0;
443        let note = Note::parse_at(LittleEndian, Class::ELF32, 4, &mut offset, &data)
444            .expect("Failed to parse");
445        assert_eq!(
446            note,
447            Note::Unknown(NoteAny {
448                n_type: 1,
449                name: "GN",
450                desc: &[0x01, 0x02, 0x03, 0x04],
451            })
452        );
453        assert_eq!(offset, 20);
454    }
455
456    #[test]
457    fn parse_note_32_lsb_with_desc_padding() {
458        let data = [
459            0x04, 0x00, 0x00, 0x00, // namesz 4
460            0x02, 0x00, 0x00, 0x00, // descsz 2
461            0x42, 0x00, 0x00, 0x00, // type 42 (unknown)
462            0x47, 0x4e, 0x55, 0x00, // name GNU\0
463            0x01, 0x02, 0x00, 0x00, // desc 0102 + 2 pad bytes
464        ];
465
466        let mut offset = 0;
467        let note = Note::parse_at(LittleEndian, Class::ELF32, 4, &mut offset, &data)
468            .expect("Failed to parse");
469        assert_eq!(
470            note,
471            Note::Unknown(NoteAny {
472                n_type: 0x42,
473                name: abi::ELF_NOTE_GNU,
474                desc: &[0x01, 0x02],
475            })
476        );
477        assert_eq!(offset, 20);
478    }
479
480    #[test]
481    fn parse_note_32_lsb_with_no_name() {
482        let data = [
483            0x00, 0x00, 0x00, 0x00, // namesz 0
484            0x02, 0x00, 0x00, 0x00, // descsz 2
485            0x42, 0x00, 0x00, 0x00, // type 42 (unknown)
486            0x20, 0x00, 0x00, 0x00, // desc 20, 00 + 2 pad bytes
487        ];
488
489        let mut offset = 0;
490        let note = Note::parse_at(LittleEndian, Class::ELF32, 4, &mut offset, &data)
491            .expect("Failed to parse");
492        assert_eq!(
493            note,
494            Note::Unknown(NoteAny {
495                n_type: 0x42,
496                name: "",
497                desc: &[0x20, 0x0],
498            })
499        );
500        assert_eq!(offset, 16);
501    }
502
503    #[test]
504    fn parse_note_32_lsb_with_no_desc() {
505        let data = [
506            0x04, 0x00, 0x00, 0x00, // namesz 4
507            0x00, 0x00, 0x00, 0x00, // descsz 0
508            0x42, 0x00, 0x00, 0x00, // type 42 (unknown)
509            0x47, 0x4e, 0x55, 0x00, // name GNU\0
510        ];
511
512        let mut offset = 0;
513        let note = Note::parse_at(LittleEndian, Class::ELF32, 4, &mut offset, &data)
514            .expect("Failed to parse");
515        assert_eq!(
516            note,
517            Note::Unknown(NoteAny {
518                n_type: 0x42,
519                name: abi::ELF_NOTE_GNU,
520                desc: &[],
521            })
522        );
523        assert_eq!(offset, 16);
524    }
525
526    use crate::parse::{test_parse_for, test_parse_fuzz_too_short};
527
528    #[test]
529    fn parse_nhdr32_lsb() {
530        test_parse_for(
531            LittleEndian,
532            Class::ELF32,
533            NoteHeader {
534                n_namesz: 0x03020100,
535                n_descsz: 0x07060504,
536                n_type: 0x0B0A0908,
537            },
538        );
539    }
540
541    #[test]
542    fn parse_nhdr32_msb() {
543        test_parse_for(
544            BigEndian,
545            Class::ELF32,
546            NoteHeader {
547                n_namesz: 0x00010203,
548                n_descsz: 0x04050607,
549                n_type: 0x08090A0B,
550            },
551        );
552    }
553
554    #[test]
555    fn parse_nhdr64_lsb() {
556        test_parse_for(
557            LittleEndian,
558            Class::ELF64,
559            NoteHeader {
560                n_namesz: 0x0706050403020100,
561                n_descsz: 0x0F0E0D0C0B0A0908,
562                n_type: 0x1716151413121110,
563            },
564        );
565    }
566
567    #[test]
568    fn parse_nhdr64_msb() {
569        test_parse_for(
570            BigEndian,
571            Class::ELF64,
572            NoteHeader {
573                n_namesz: 0x0001020304050607,
574                n_descsz: 0x08090A0B0C0D0E0F,
575                n_type: 0x1011121314151617,
576            },
577        );
578    }
579
580    #[test]
581    fn parse_nhdr32_lsb_fuzz_too_short() {
582        test_parse_fuzz_too_short::<_, NoteHeader>(LittleEndian, Class::ELF32);
583    }
584
585    #[test]
586    fn parse_nhdr32_msb_fuzz_too_short() {
587        test_parse_fuzz_too_short::<_, NoteHeader>(BigEndian, Class::ELF32);
588    }
589
590    #[test]
591    fn parse_nhdr64_lsb_fuzz_too_short() {
592        test_parse_fuzz_too_short::<_, NoteHeader>(LittleEndian, Class::ELF64);
593    }
594
595    #[test]
596    fn parse_nhdr64_msb_fuzz_too_short() {
597        test_parse_fuzz_too_short::<_, NoteHeader>(BigEndian, Class::ELF64);
598    }
599}