blob: fd9210fab50e8d6dad7b78479a41c178d8286c7d [file] [log] [blame]
//! Parsing ELF notes: `.note.*`, [SHT_NOTE](crate::abi::SHT_NOTE), [PT_NOTE](crate::abi::PT_NOTE)
//!
//! Example for getting the GNU ABI-tag note:
//! ```
//! use elf::ElfBytes;
//! use elf::endian::AnyEndian;
//! use elf::note::Note;
//! use elf::note::NoteGnuAbiTag;
//!
//! let path = std::path::PathBuf::from("sample-objects/basic.x86_64");
//! let file_data = std::fs::read(path).expect("Could not read file.");
//! let slice = file_data.as_slice();
//! let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
//!
//! let shdr = file
//! .section_header_by_name(".note.ABI-tag")
//! .expect("section table should be parseable")
//! .expect("file should have a .note.ABI-tag section");
//!
//! let notes: Vec<_> = file
//! .section_data_as_notes(&shdr)
//! .expect("Should be able to get note section data")
//! .collect();
//! assert_eq!(
//! notes[0],
//! Note::GnuAbiTag(NoteGnuAbiTag {
//! os: 0,
//! major: 2,
//! minor: 6,
//! subminor: 32
//! })
//! );
//! ```
use crate::abi;
use crate::endian::EndianParse;
use crate::file::Class;
use crate::parse::{ParseAt, ParseError, ReadBytesExt};
use core::mem::size_of;
use core::str::from_utf8;
/// This enum contains parsed Note variants which can be matched on
#[derive(Debug, PartialEq, Eq)]
pub enum Note<'data> {
/// (name: [abi::ELF_NOTE_GNU], n_type: [abi::NT_GNU_ABI_TAG])
GnuAbiTag(NoteGnuAbiTag),
/// (name: [abi::ELF_NOTE_GNU], n_type: [abi::NT_GNU_BUILD_ID])
GnuBuildId(NoteGnuBuildId<'data>),
/// All other notes that we don't know how to parse
Unknown(NoteAny<'data>),
}
impl<'data> Note<'data> {
fn parse_at<E: EndianParse>(
endian: E,
_class: Class,
align: usize,
offset: &mut usize,
data: &'data [u8],
) -> Result<Self, ParseError> {
// We don't know what to do if the section or segment header specified a zero alignment, so error
// (this is likely a file corruption)
if align == 0 {
return Err(ParseError::UnexpectedAlignment(align));
}
// It looks like clang and gcc emit 32-bit notes for 64-bit files, so we
// currently always parse all note headers as 32-bit.
let nhdr = NoteHeader::parse_at(endian, Class::ELF32, offset, data)?;
let name_start = *offset;
let name_size: usize = nhdr.n_namesz.try_into()?;
let name_end = name_start
.checked_add(name_size)
.ok_or(ParseError::IntegerOverflow)?;
let name = data.get_bytes(name_start..name_end)?;
*offset = name_end;
// skip over padding if needed to get back to 4-byte alignment
if *offset % align > 0 {
*offset = (*offset)
.checked_add(align - *offset % align)
.ok_or(ParseError::IntegerOverflow)?;
}
let desc_start = *offset;
let desc_size: usize = nhdr.n_descsz.try_into()?;
let desc_end = desc_start
.checked_add(desc_size)
.ok_or(ParseError::IntegerOverflow)?;
let raw_desc = data.get_bytes(desc_start..desc_end)?;
*offset = desc_end;
// skip over padding if needed to get back to 4-byte alignment
if *offset % align > 0 {
*offset = (*offset)
.checked_add(align - *offset % align)
.ok_or(ParseError::IntegerOverflow)?;
}
// Interpret the note contents to try to return a known note variant
match name {
abi::ELF_NOTE_GNU => match nhdr.n_type {
abi::NT_GNU_ABI_TAG => {
let mut offset = 0;
Ok(Note::GnuAbiTag(NoteGnuAbiTag::parse_at(
endian,
_class,
&mut offset,
raw_desc,
)?))
}
abi::NT_GNU_BUILD_ID => Ok(Note::GnuBuildId(NoteGnuBuildId(raw_desc))),
_ => Ok(Note::Unknown(NoteAny {
n_type: nhdr.n_type,
name,
desc: raw_desc,
})),
},
_ => Ok(Note::Unknown(NoteAny {
n_type: nhdr.n_type,
name,
desc: raw_desc,
})),
}
}
}
/// Contains four 4-byte integers.
/// The first 4-byte integer specifies the os. The second, third, and fourth
/// 4-byte integers contain the earliest compatible kernel version.
/// For example, if the 3 integers are 6, 0, and 7, this signifies a 6.0.7 kernel.
///
/// (see: <https://raw.githubusercontent.com/wiki/hjl-tools/linux-abi/linux-abi-draft.pdf>)
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NoteGnuAbiTag {
pub os: u32,
pub major: u32,
pub minor: u32,
pub subminor: u32,
}
impl ParseAt for NoteGnuAbiTag {
fn parse_at<E: EndianParse>(
endian: E,
_class: Class,
offset: &mut usize,
data: &[u8],
) -> Result<Self, ParseError> {
Ok(NoteGnuAbiTag {
os: endian.parse_u32_at(offset, data)?,
major: endian.parse_u32_at(offset, data)?,
minor: endian.parse_u32_at(offset, data)?,
subminor: endian.parse_u32_at(offset, data)?,
})
}
fn size_for(_class: Class) -> usize {
size_of::<u32>() * 4
}
}
/// Contains a build ID note which is unique among the set of meaningful contents
/// for ELF files and identical when the output file would otherwise have been identical.
/// This is a zero-copy type which merely contains a slice of the note data from which it was parsed.
///
/// (see: <https://raw.githubusercontent.com/wiki/hjl-tools/linux-abi/linux-abi-draft.pdf>)
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NoteGnuBuildId<'data>(pub &'data [u8]);
/// Contains the raw fields found in any ELF note. Used for notes that we don't know
/// how to parse into more specific types.
#[derive(Debug, PartialEq, Eq)]
pub struct NoteAny<'data> {
pub n_type: u64,
pub name: &'data [u8],
pub desc: &'data [u8],
}
impl NoteAny<'_> {
/// Parses the note's name bytes as a utf8 sequence, with any trailing NUL bytes removed
pub fn name_str(&self) -> Result<&str, ParseError> {
let name = from_utf8(self.name)?;
Ok(name.trim_end_matches('\0'))
}
}
#[derive(Debug)]
pub struct NoteIterator<'data, E: EndianParse> {
endian: E,
class: Class,
align: usize,
data: &'data [u8],
offset: usize,
}
impl<'data, E: EndianParse> NoteIterator<'data, E> {
pub fn new(endian: E, class: Class, align: usize, data: &'data [u8]) -> Self {
NoteIterator {
endian,
class,
align,
data,
offset: 0,
}
}
}
impl<'data, E: EndianParse> Iterator for NoteIterator<'data, E> {
type Item = Note<'data>;
fn next(&mut self) -> Option<Self::Item> {
if self.data.is_empty() {
return None;
}
Note::parse_at(
self.endian,
self.class,
self.align,
&mut self.offset,
self.data,
)
.ok()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct NoteHeader {
pub n_namesz: u64,
pub n_descsz: u64,
pub n_type: u64,
}
impl ParseAt for NoteHeader {
fn parse_at<E: EndianParse>(
endian: E,
class: Class,
offset: &mut usize,
data: &[u8],
) -> Result<Self, ParseError> {
match class {
Class::ELF32 => Ok(NoteHeader {
n_namesz: endian.parse_u32_at(offset, data)? as u64,
n_descsz: endian.parse_u32_at(offset, data)? as u64,
n_type: endian.parse_u32_at(offset, data)? as u64,
}),
Class::ELF64 => Ok(NoteHeader {
n_namesz: endian.parse_u64_at(offset, data)?,
n_descsz: endian.parse_u64_at(offset, data)?,
n_type: endian.parse_u64_at(offset, data)?,
}),
}
}
#[inline]
fn size_for(class: Class) -> usize {
match class {
Class::ELF32 => 12,
Class::ELF64 => 24,
}
}
}
#[cfg(test)]
mod parse_tests {
use super::*;
use crate::endian::{BigEndian, LittleEndian};
#[test]
fn parse_nt_gnu_abi_tag() {
#[rustfmt::skip]
let data = [
0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x47, 0x4e, 0x55, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
];
let mut offset = 0;
let note = Note::parse_at(LittleEndian, Class::ELF32, 4, &mut offset, &data)
.expect("Failed to parse");
assert_eq!(
note,
Note::GnuAbiTag(NoteGnuAbiTag {
os: abi::ELF_NOTE_GNU_ABI_TAG_OS_LINUX,
major: 2,
minor: 6,
subminor: 32
})
);
}
#[test]
fn parse_desc_gnu_build_id() {
let data = [
0x04, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x47, 0x4e,
0x55, 0x00, 0x77, 0x41, 0x9f, 0x0d, 0xa5, 0x10, 0x83, 0x0c, 0x57, 0xa7, 0xc8, 0xcc,
0xb0, 0xee, 0x85, 0x5f, 0xee, 0xd3, 0x76, 0xa3,
];
let mut offset = 0;
let note = Note::parse_at(LittleEndian, Class::ELF32, 4, &mut offset, &data)
.expect("Failed to parse");
assert_eq!(
note,
Note::GnuBuildId(NoteGnuBuildId(&[
0x77, 0x41, 0x9f, 0x0d, 0xa5, 0x10, 0x83, 0x0c, 0x57, 0xa7, 0xc8, 0xcc, 0xb0, 0xee,
0x85, 0x5f, 0xee, 0xd3, 0x76, 0xa3,
]))
);
}
#[test]
fn parse_note_errors_with_zero_alignment() {
// This is a .note.gnu.property section
#[rustfmt::skip]
let data = [
0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x47, 0x4e, 0x55, 0x00,
0x02, 0x00, 0x00, 0xc0, 0x04, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let mut offset = 0;
Note::parse_at(LittleEndian, Class::ELF64, 0, &mut offset, &data)
.expect_err("Should have gotten an alignment error");
}
#[test]
fn parse_note_with_8_byte_alignment() {
// This is a .note.gnu.property section, which has been seen generated with 8-byte alignment
#[rustfmt::skip]
let data = [
0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x47, 0x4e, 0x55, 0x00,
0x02, 0x00, 0x00, 0xc0, 0x04, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
// Even though the file class is ELF64, we parse it as a 32-bit struct. gcc/clang seem to output 32-bit notes
// even though the gABI states that ELF64 files should contain 64-bit notes. Sometimes those notes are generated
// in sections with 4-byte alignment, and other times with 8-byte alignment, as specified by shdr.sh_addralign.
//
// See https://raw.githubusercontent.com/wiki/hjl-tools/linux-abi/linux-abi-draft.pdf
// Excerpt:
// All entries in a PT_NOTE segment have the same alignment which equals to the
// p_align field in program header.
// According to gABI, each note entry should be aligned to 4 bytes in 32-bit
// objects or 8 bytes in 64-bit objects. But .note.ABI-tag section (see Sec-
// tion 2.1.6) and .note.gnu.build-id section (see Section 2.1.4) are aligned
// to 4 bytes in both 32-bit and 64-bit objects. Note parser should use p_align for
// note alignment, instead of assuming alignment based on ELF file class.
let mut offset = 0;
let note = Note::parse_at(LittleEndian, Class::ELF64, 8, &mut offset, &data)
.expect("Failed to parse");
assert_eq!(
note,
Note::Unknown(NoteAny {
n_type: 5,
name: abi::ELF_NOTE_GNU,
desc: &[
0x2, 0x0, 0x0, 0xc0, 0x4, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
]
})
);
}
#[test]
fn parse_note_with_8_byte_alignment_unaligned_namesz() {
let data = [
0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // namesz 5, descsz 2
0x42, 0x00, 0x00, 0x00, 0x47, 0x4e, 0x55, 0x55, // type 42 (unknown), name GNUU
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // NUL + 7 pad for 8 alignment
0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // desc 0102 + 6 pad for alignment
];
let mut offset = 0;
let note = Note::parse_at(LittleEndian, Class::ELF32, 8, &mut offset, &data)
.expect("Failed to parse");
assert_eq!(
note,
Note::Unknown(NoteAny {
n_type: 0x42,
name: b"GNUU\0",
desc: &[0x01, 0x02],
})
);
assert_eq!(offset, 32);
}
#[test]
fn parse_note_for_elf64_expects_nhdr32() {
let data = [
0x04, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x47, 0x4e,
0x55, 0x00, 0x77, 0x41, 0x9f, 0x0d, 0xa5, 0x10, 0x83, 0x0c, 0x57, 0xa7, 0xc8, 0xcc,
0xb0, 0xee, 0x85, 0x5f, 0xee, 0xd3, 0x76, 0xa3,
];
let mut offset = 0;
// Even though the file class is ELF64, we parse it as a 32-bit struct. gcc/clang seem to output 32-bit notes
// even though the gABI states that ELF64 files should contain 64-bit notes.
let note = Note::parse_at(LittleEndian, Class::ELF64, 4, &mut offset, &data)
.expect("Failed to parse");
assert_eq!(
note,
Note::GnuBuildId(NoteGnuBuildId(&[
0x77, 0x41, 0x9f, 0x0d, 0xa5, 0x10, 0x83, 0x0c, 0x57, 0xa7, 0xc8, 0xcc, 0xb0, 0xee,
0x85, 0x5f, 0xee, 0xd3, 0x76, 0xa3,
]))
);
}
#[test]
fn parse_note_32_lsb() {
let data = [
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00,
0x00, 0x00,
];
let mut offset = 0;
let note = Note::parse_at(LittleEndian, Class::ELF32, 4, &mut offset, &data)
.expect("Failed to parse");
assert_eq!(
note,
Note::Unknown(NoteAny {
n_type: 6,
name: &[],
desc: &[0x20, 0x0],
})
);
assert_eq!(offset, 16);
}
#[test]
fn parse_note_32_lsb_with_name_padding() {
let data = [
0x03, 0x00, 0x00, 0x00, // namesz 3
0x04, 0x00, 0x00, 0x00, // descsz 4
0x01, 0x00, 0x00, 0x00, // type 1
0x47, 0x4e, 0x00, 0x00, // name GN\0 + 1 pad byte
0x01, 0x02, 0x03, 0x04,
]; // desc 01020304
let mut offset = 0;
let note = Note::parse_at(LittleEndian, Class::ELF32, 4, &mut offset, &data)
.expect("Failed to parse");
assert_eq!(
note,
Note::Unknown(NoteAny {
n_type: 1,
name: b"GN\0",
desc: &[0x01, 0x02, 0x03, 0x04],
})
);
assert_eq!(offset, 20);
}
#[test]
fn parse_note_32_lsb_with_desc_padding() {
let data = [
0x04, 0x00, 0x00, 0x00, // namesz 4
0x02, 0x00, 0x00, 0x00, // descsz 2
0x42, 0x00, 0x00, 0x00, // type 42 (unknown)
0x47, 0x4e, 0x55, 0x00, // name GNU\0
0x01, 0x02, 0x00, 0x00, // desc 0102 + 2 pad bytes
];
let mut offset = 0;
let note = Note::parse_at(LittleEndian, Class::ELF32, 4, &mut offset, &data)
.expect("Failed to parse");
assert_eq!(
note,
Note::Unknown(NoteAny {
n_type: 0x42,
name: abi::ELF_NOTE_GNU,
desc: &[0x01, 0x02],
})
);
assert_eq!(offset, 20);
}
#[test]
fn parse_note_32_lsb_with_no_name() {
let data = [
0x00, 0x00, 0x00, 0x00, // namesz 0
0x02, 0x00, 0x00, 0x00, // descsz 2
0x42, 0x00, 0x00, 0x00, // type 42 (unknown)
0x20, 0x00, 0x00, 0x00, // desc 20, 00 + 2 pad bytes
];
let mut offset = 0;
let note = Note::parse_at(LittleEndian, Class::ELF32, 4, &mut offset, &data)
.expect("Failed to parse");
assert_eq!(
note,
Note::Unknown(NoteAny {
n_type: 0x42,
name: &[],
desc: &[0x20, 0x0],
})
);
assert_eq!(offset, 16);
}
#[test]
fn parse_note_32_lsb_with_no_desc() {
let data = [
0x04, 0x00, 0x00, 0x00, // namesz 4
0x00, 0x00, 0x00, 0x00, // descsz 0
0x42, 0x00, 0x00, 0x00, // type 42 (unknown)
0x47, 0x4e, 0x55, 0x00, // name GNU\0
];
let mut offset = 0;
let note = Note::parse_at(LittleEndian, Class::ELF32, 4, &mut offset, &data)
.expect("Failed to parse");
assert_eq!(
note,
Note::Unknown(NoteAny {
n_type: 0x42,
name: abi::ELF_NOTE_GNU,
desc: &[],
})
);
assert_eq!(offset, 16);
}
#[test]
fn parse_note_any_with_invalid_utf8_name() {
let data = [
0x04, 0x00, 0x00, 0x00, // namesz 4
0x00, 0x00, 0x00, 0x00, // descsz 0
0x42, 0x00, 0x00, 0x00, // type 42 (unknown)
0x47, 0xc3, 0x28, 0x00, // name G..\0 (dots are an invalid utf8 sequence)
];
let mut offset = 0;
let note = Note::parse_at(LittleEndian, Class::ELF32, 4, &mut offset, &data)
.expect("Failed to parse");
assert_eq!(
note,
Note::Unknown(NoteAny {
n_type: 0x42,
name: &[0x47, 0xc3, 0x28, 0x0],
desc: &[],
})
);
assert_eq!(offset, 16);
}
#[test]
fn name_str_works_for_note_any_with_valid_utf8_name() {
let note = NoteAny {
n_type: 1,
name: &[0x47, 0x4e, 0x55, 0x00],
desc: &[],
};
let name = note.name_str().expect("Failed to parse utf8");
assert_eq!(name, "GNU");
}
#[test]
fn name_str_errors_for_note_any_with_invalid_utf8_name() {
let note = NoteAny {
n_type: 1,
name: &[0x47, 0xc3, 0x28, 0x00],
desc: &[],
};
assert!(matches!(note.name_str(), Err(ParseError::Utf8Error(_))));
}
use crate::parse::{test_parse_for, test_parse_fuzz_too_short};
#[test]
fn parse_nhdr32_lsb() {
test_parse_for(
LittleEndian,
Class::ELF32,
NoteHeader {
n_namesz: 0x03020100,
n_descsz: 0x07060504,
n_type: 0x0B0A0908,
},
);
}
#[test]
fn parse_nhdr32_msb() {
test_parse_for(
BigEndian,
Class::ELF32,
NoteHeader {
n_namesz: 0x00010203,
n_descsz: 0x04050607,
n_type: 0x08090A0B,
},
);
}
#[test]
fn parse_nhdr64_lsb() {
test_parse_for(
LittleEndian,
Class::ELF64,
NoteHeader {
n_namesz: 0x0706050403020100,
n_descsz: 0x0F0E0D0C0B0A0908,
n_type: 0x1716151413121110,
},
);
}
#[test]
fn parse_nhdr64_msb() {
test_parse_for(
BigEndian,
Class::ELF64,
NoteHeader {
n_namesz: 0x0001020304050607,
n_descsz: 0x08090A0B0C0D0E0F,
n_type: 0x1011121314151617,
},
);
}
#[test]
fn parse_nhdr32_lsb_fuzz_too_short() {
test_parse_fuzz_too_short::<_, NoteHeader>(LittleEndian, Class::ELF32);
}
#[test]
fn parse_nhdr32_msb_fuzz_too_short() {
test_parse_fuzz_too_short::<_, NoteHeader>(BigEndian, Class::ELF32);
}
#[test]
fn parse_nhdr64_lsb_fuzz_too_short() {
test_parse_fuzz_too_short::<_, NoteHeader>(LittleEndian, Class::ELF64);
}
#[test]
fn parse_nhdr64_msb_fuzz_too_short() {
test_parse_fuzz_too_short::<_, NoteHeader>(BigEndian, Class::ELF64);
}
}