lightswitch_object/
kernel.rs1use anyhow::anyhow;
2use object::Endianness;
3use object::FileKind;
4use object::ReadCache;
5use object::elf::{ELF_NOTE_GNU, FileHeader32, FileHeader64, NT_GNU_BUILD_ID, PT_NOTE};
6use object::read::elf::FileHeader;
7use object::read::elf::NoteIterator;
8use object::read::elf::ProgramHeader;
9use std::fs::File;
10
11use crate::BuildId;
12
13const KCORE_PATH: &str = "/proc/kcore";
14const VMCORE_INFO_NAME: &[u8] = b"VMCOREINFO";
15const KERNEL_OFFSET: &[u8] = b"KERNELOFFSET";
16
17pub fn parse_gnu_build_id_from_notes(data: &[u8]) -> Result<BuildId, anyhow::Error> {
19 let notes: NoteIterator<'_, FileHeader32<Endianness>> =
20 NoteIterator::new(Endianness::Little, 4, data)?;
21
22 for note in notes {
23 let Ok(note) = note else {
24 continue;
25 };
26
27 let name = note.name();
28 let ntype = note.n_type(Endianness::Little);
29
30 if name != ELF_NOTE_GNU || ntype != NT_GNU_BUILD_ID {
31 continue;
32 }
33
34 return Ok(BuildId::gnu_from_bytes(note.desc())?);
35 }
36
37 Err(anyhow!("no GNU build id note found"))
38}
39
40fn _parse_vm_core_info_line(data: &[u8]) -> impl Iterator<Item = (&[u8], &[u8])> {
42 data.split(|&e| e == b'\n').filter_map(|key_val| {
43 let mut split = key_val.split(|&e| e == b'=');
44 match (split.next(), split.next()) {
45 (Some(a), Some(b)) => Some((a, b)),
46 (_, _) => None,
47 }
48 })
49}
50
51pub fn kaslr_offset() -> anyhow::Result<u64> {
53 let data = ReadCache::new(File::open(KCORE_PATH)?);
54
55 match FileKind::parse(&data) {
56 Ok(FileKind::Elf64) => {
57 let header: &FileHeader64<Endianness> = FileHeader64::<Endianness>::parse(&data)?;
58 let endian = header.endian()?;
59 let headers = header.program_headers(endian, &data)?;
60
61 for header in headers {
62 if header.p_type(endian) != PT_NOTE {
63 continue;
64 }
65
66 let notes: NoteIterator<'_, FileHeader64<Endianness>> = NoteIterator::new(
67 Endianness::Little,
68 header.p_align(endian),
69 header
70 .data(endian, &data)
71 .map_err(|_| anyhow!("invalid header data"))?,
72 )?;
73
74 for note in notes {
75 let Ok(note) = note else {
76 continue;
77 };
78
79 if note.name() == VMCORE_INFO_NAME {
80 let found = _parse_vm_core_info_line(note.desc())
81 .find(|(key, _val)| key == &KERNEL_OFFSET)
82 .map(|(_key, val)| val);
83
84 return Ok(
85 u64::from_str_radix(std::str::from_utf8(found.unwrap())?, 16)?,
91 );
92 }
93 }
94 }
95 }
96 Ok(_) => {
97 todo!("only 64 bit ELF kcore is supported")
98 }
99 Err(_) => {}
100 }
101
102 Err(anyhow!("could not find the kASLR offset"))
103}
104
105#[cfg(test)]
106mod tests {
107 use std::fs::File;
108 use std::io::Read;
109
110 use crate::kernel::*;
111 use crate::*;
112
113 #[test]
114 fn test_parse_gnu_build_id_from_notes() {
115 let mut file = File::open("src/testdata/fedora-kernel-notes").unwrap();
116 let mut data = Vec::new();
117 file.read_to_end(&mut data).unwrap();
118
119 assert_eq!(
120 parse_gnu_build_id_from_notes(&data).unwrap(),
121 BuildId {
122 flavour: buildid::BuildIdFlavour::Gnu,
123 data: vec![
124 184, 215, 12, 245, 25, 250, 197, 165, 204, 205, 218, 26, 97, 195, 137, 149,
125 189, 155, 48, 89
126 ],
127 }
128 );
129 }
130
131 #[test]
132 fn test_parse_vm_core_info_line() {
133 let data = b"OSRELEASE=6.12.8-100.fc40.x86_64
134BUILD-ID=0730dd9e6b959a79e0797de379bd078c3792ea98
135PAGESIZE=4096
136SYMBOL(init_uts_ns)=ffffffff9ebdefa0
137OFFSET(uts_namespace.name)=0
138SYMBOL(node_online_map)=ffffffff9ec48420
139SYMBOL(swapper_pg_dir)=ffffffff9e82a000
140SYMBOL(_stext)=ffffffff9c000000
141NUMBER(VMALLOC_START)=0xffffa5c140000000
142SYMBOL(vmemmap)=ffffd90a40000000
143SYMBOL(mem_section)=ffff8f3a6dfcd2c0
144LENGTH(mem_section)=4096
145SIZE(mem_section)=32
146OFFSET(mem_section.section_mem_map)=0
147NUMBER(SECTION_SIZE_BITS)=27
148NUMBER(MAX_PHYSMEM_BITS)=46
149SIZE(page)=64
150SIZE(pglist_data)=175424
151SIZE(zone)=1728
152SIZE(free_area)=104
153SIZE(list_head)=16
154SIZE(nodemask_t)=128
155OFFSET(page.flags)=0
156OFFSET(page._refcount)=52
157KERNELOFFSET=1b000000
158NUMBER(KERNEL_IMAGE_SIZE)=1073741824
159NUMBER(sme_mask)=0";
160 assert_eq!(
161 _parse_vm_core_info_line(data).find(|(k, _v)| k == b"KERNELOFFSET"),
162 Some(("KERNELOFFSET".as_bytes(), "1b000000".as_bytes()))
163 );
164 }
165
166 #[test]
167 fn test_aslr_offset() {
168 assert!(kaslr_offset().is_ok());
169 }
170}