lightswitch_object/
object.rs1use std::fs;
2use std::io::Read;
3use std::path::Path;
4
5use anyhow::{anyhow, Result};
6use memmap2::Mmap;
7use ring::digest::{Context, Digest, SHA256};
8
9use crate::BuildId;
10use object::elf::{FileHeader32, FileHeader64, PT_LOAD};
11use object::read::elf::FileHeader;
12use object::read::elf::ProgramHeader;
13use object::Endianness;
14use object::FileKind;
15use object::Object;
16use object::ObjectKind;
17use object::ObjectSection;
18
19pub type ExecutableId = u64;
26
27#[derive(Debug, Clone)]
30pub struct ElfLoad {
31 pub p_offset: u64,
32 pub p_vaddr: u64,
33 pub p_memsz: u64,
34}
35
36#[derive(Debug)]
37pub struct ObjectFile {
38 object: object::File<'static>, mmap: Box<Mmap>,
42 code_hash: Digest,
43}
44
45impl ObjectFile {
46 pub fn new(path: &Path) -> Result<Self> {
47 let file = fs::File::open(path)?;
48 let mmap = Box::new(unsafe { Mmap::map(&file) }?);
52 let object = object::File::parse(&**mmap)?;
53 let object =
56 unsafe { std::mem::transmute::<object::File<'_>, object::File<'static>>(object) };
57 let Some(code_hash) = code_hash(&object) else {
58 return Err(anyhow!("code hash is None"));
59 };
60 Ok(ObjectFile {
61 object,
62 mmap,
63 code_hash,
64 })
65 }
66
67 pub fn id(&self) -> Result<ExecutableId> {
69 let mut buffer = [0; 8];
70 let _ = self.code_hash.as_ref().read(&mut buffer)?;
71 Ok(u64::from_ne_bytes(buffer))
72 }
73
74 pub fn build_id(&self) -> Result<BuildId> {
77 let object = &self.object;
78 let gnu_build_id = object.build_id()?;
79
80 if let Some(data) = gnu_build_id {
81 return Ok(BuildId::gnu_from_bytes(data));
82 }
83
84 for section in object.sections() {
86 if section.name()? == ".note.go.buildid" {
87 if let Ok(data) = section.data() {
88 return BuildId::go_from_bytes(data);
89 }
90 }
91 }
92
93 Ok(BuildId::sha256_from_digest(&self.code_hash))
95 }
96
97 pub fn is_dynamic(&self) -> bool {
98 self.object.kind() == ObjectKind::Dynamic
99 }
100
101 pub fn is_go(&self) -> bool {
102 for section in self.object.sections() {
103 if let Ok(section_name) = section.name() {
104 if section_name == ".gosymtab"
105 || section_name == ".gopclntab"
106 || section_name == ".note.go.buildid"
107 {
108 return true;
109 }
110 }
111 }
112 false
113 }
114
115 pub fn elf_load_segments(&self) -> Result<Vec<ElfLoad>> {
116 let mmap = &**self.mmap;
117
118 match FileKind::parse(mmap) {
119 Ok(FileKind::Elf32) => {
120 let header: &FileHeader32<Endianness> = FileHeader32::<Endianness>::parse(mmap)?;
121 let endian = header.endian()?;
122 let segments = header.program_headers(endian, mmap)?;
123
124 let mut elf_loads = Vec::new();
125 for segment in segments {
126 if segment.p_type(endian) == PT_LOAD {
127 elf_loads.push(ElfLoad {
128 p_offset: segment.p_offset(endian) as u64,
129 p_vaddr: segment.p_vaddr(endian) as u64,
130 p_memsz: segment.p_memsz(endian) as u64,
131 });
132 }
133 }
134 Ok(elf_loads)
135 }
136 Ok(FileKind::Elf64) => {
137 let header: &FileHeader64<Endianness> = FileHeader64::<Endianness>::parse(mmap)?;
138 let endian = header.endian()?;
139 let segments = header.program_headers(endian, mmap)?;
140
141 let mut elf_loads = Vec::new();
142 for segment in segments {
143 if segment.p_type(endian) == PT_LOAD {
144 elf_loads.push(ElfLoad {
145 p_offset: segment.p_offset(endian),
146 p_vaddr: segment.p_vaddr(endian),
147 p_memsz: segment.p_memsz(endian),
148 });
149 }
150 }
151 Ok(elf_loads)
152 }
153 Ok(other_file_kind) => Err(anyhow!(
154 "object is not an 32 or 64 bits ELF but {:?}",
155 other_file_kind
156 )),
157 Err(e) => Err(anyhow!("FileKind failed with {:?}", e)),
158 }
159 }
160}
161
162pub fn code_hash(object: &object::File) -> Option<Digest> {
163 for section in object.sections() {
164 let Ok(section_name) = section.name() else {
165 continue;
166 };
167
168 if section_name == ".text" {
169 if let Ok(section) = section.data() {
170 return Some(sha256_digest(section));
171 }
172 }
173 }
174
175 None
176}
177
178fn sha256_digest<R: Read>(mut reader: R) -> Digest {
179 let mut context = Context::new(&SHA256);
180 let mut buffer = [0; 1024];
181
182 loop {
183 let count = reader
184 .read(&mut buffer)
185 .expect("reading digest into buffer should not fail");
186 if count == 0 {
187 break;
188 }
189 context.update(&buffer[..count]);
190 }
191
192 context.finish()
193}