1use thiserror::Error;
9use zerocopy::{
10 little_endian::{U16, U32},
11 FromBytes, Immutable, KnownLayout,
12};
13
14use crate::os_release::OsReleaseInfo;
15
16#[derive(Debug, FromBytes, Immutable, KnownLayout)]
18#[cfg_attr(test, derive(zerocopy::IntoBytes, Default))]
19#[repr(C)]
20struct DosStub {
21 _unused1: [u8; 0x20],
22 _unused2: [u8; 0x1c],
23 pe_offset: U32,
24}
25
26#[derive(Debug, FromBytes, Immutable, KnownLayout)]
27#[cfg_attr(test, derive(zerocopy::IntoBytes, Default))]
28#[repr(C)]
29struct CoffFileHeader {
30 machine: U16,
31 number_of_sections: U16,
32 time_date_stamp: U32,
33 pointer_to_symbol_table: U32,
34 number_of_symbols: U32,
35 size_of_optional_header: U16,
36 characteristics: U16,
37}
38
39#[derive(Debug, FromBytes, Immutable, KnownLayout)]
40#[cfg_attr(test, derive(zerocopy::IntoBytes, Default))]
41#[repr(C)]
42struct PeHeader {
43 pe_magic: [u8; 4], coff_file_header: CoffFileHeader,
45}
46const PE_MAGIC: [u8; 4] = *b"PE\0\0";
47
48#[derive(Debug, FromBytes, Immutable, KnownLayout)]
49#[cfg_attr(test, derive(zerocopy::IntoBytes, Default))]
50#[repr(C)]
51struct SectionHeader {
52 name: [u8; 8],
53 virtual_size: U32,
54 virtual_address: U32,
55 size_of_raw_data: U32,
56 pointer_to_raw_data: U32,
57 pointer_to_relocations: U32,
58 pointer_to_line_numbers: U32,
59 number_of_relocations: U16,
60 number_of_line_numbers: U16,
61 characteristics: U32,
62}
63
64#[derive(Debug, Error, PartialEq)]
66pub enum UkiError {
67 #[error("UKI is not valid EFI executable")]
69 PortableExecutableError,
70 #[error("UKI doesn't contain a '{0}' section")]
72 MissingSection(&'static str),
73 #[error("UKI section '{0}' is not UTF-8")]
75 UnicodeError(&'static str),
76 #[error("No name information found in .osrel section")]
78 NoName,
79}
80
81pub fn get_text_section<'a>(
96 image: &'a [u8],
97 section_name: &'static str,
98) -> Result<&'a str, UkiError> {
99 let bytes = get_section(image, section_name).ok_or(UkiError::PortableExecutableError)??;
100 std::str::from_utf8(bytes).or(Err(UkiError::UnicodeError(section_name)))
101}
102
103pub fn get_section<'a>(
127 image: &'a [u8],
128 section_name: &'static str,
129) -> Option<Result<&'a [u8], UkiError>> {
130 let mut section_key = [0u8; 8];
133 section_key[..section_name.len()].copy_from_slice(section_name.as_bytes());
134
135 let (dos_stub, ..) = DosStub::ref_from_prefix(image).ok()?;
137 let rest = image.get(dos_stub.pe_offset.get() as usize..)?;
138
139 let (pe_header, rest) = PeHeader::ref_from_prefix(rest).ok()?;
141 if pe_header.pe_magic != PE_MAGIC {
142 return None;
143 }
144
145 let rest = rest.get(pe_header.coff_file_header.size_of_optional_header.get() as usize..)?;
147
148 let n_sections = pe_header.coff_file_header.number_of_sections.get() as usize;
150 let (sections, ..) = <[SectionHeader]>::ref_from_prefix_with_elems(rest, n_sections).ok()?;
151
152 for section in sections {
153 if section.name == section_key {
154 let bytes = image
155 .get(section.pointer_to_raw_data.get() as usize..)?
156 .get(..section.virtual_size.get() as usize)?;
157 return Some(Ok(bytes));
158 }
159 }
160
161 Some(Err(UkiError::MissingSection(section_name)))
162}
163
164pub fn get_boot_label(image: &[u8]) -> Result<String, UkiError> {
186 let osrel = get_text_section(image, ".osrel")?;
187 OsReleaseInfo::parse(osrel)
188 .get_boot_label()
189 .ok_or(UkiError::NoName)
190}
191
192pub fn get_cmdline(image: &[u8]) -> Result<&str, UkiError> {
194 get_text_section(image, ".cmdline")
195}
196
197#[cfg(test)]
198mod test {
199 use core::mem::size_of;
200
201 use similar_asserts::assert_eq;
202 use zerocopy::IntoBytes;
203
204 use super::*;
205
206 fn data_offset(n_sections: usize) -> usize {
207 size_of::<DosStub>() + size_of::<PeHeader>() + n_sections * size_of::<SectionHeader>()
208 }
209
210 fn peify(optional: &[u8], sections: &[SectionHeader], rest: &[&[u8]]) -> Vec<u8> {
211 let mut output = vec![];
212 output.extend_from_slice(
213 DosStub {
214 pe_offset: U32::new(size_of::<DosStub>() as u32),
215 ..Default::default()
216 }
217 .as_bytes(),
218 );
219 output.extend_from_slice(
220 PeHeader {
221 pe_magic: PE_MAGIC,
222 coff_file_header: CoffFileHeader {
223 number_of_sections: U16::new(sections.len() as u16),
224 size_of_optional_header: U16::new(optional.len() as u16),
225 ..Default::default()
226 },
227 }
228 .as_bytes(),
229 );
230 output.extend_from_slice(optional);
231 for section in sections {
232 output.extend_from_slice(section.as_bytes());
233 }
234 assert_eq!(output.len(), data_offset(sections.len()));
235 for data in rest {
236 output.extend_from_slice(data);
237 }
238
239 output
240 }
241
242 fn ukify(osrel: &[u8]) -> Vec<u8> {
243 let osrel_offset = data_offset(1);
244 peify(
245 b"",
246 &[SectionHeader {
247 name: *b".osrel\0\0",
248 virtual_size: U32::new(osrel.len() as u32),
249 pointer_to_raw_data: U32::new(osrel_offset as u32),
250 ..Default::default()
251 }],
252 &[osrel],
253 )
254 }
255
256 #[test]
257 fn test_simple() {
258 let uki = ukify(
259 br#"
260PRETTY_NAME='prettyOS'
261VERSION_ID="Rocky Racoon"
262VERSION=42
263ID=pretty-os
264"#,
265 );
266
267 assert_eq!(
268 get_boot_label(uki.as_ref()).unwrap(),
269 "prettyOS Rocky Racoon"
270 );
271 }
272
273 #[test]
274 fn test_bad_pe() {
275 fn pe_err(img: &[u8]) {
276 assert_eq!(get_boot_label(img), Err(UkiError::PortableExecutableError));
277 }
278 fn no_sec(img: &[u8]) {
279 assert_eq!(get_boot_label(img), Err(UkiError::MissingSection(".osrel")));
280 }
281
282 pe_err(b"");
283 pe_err(b"This is definitely not an EFI executable, but it's big enough to pass the first step...");
284
285 pe_err(
286 DosStub {
287 pe_offset: U32::new(0),
288 ..Default::default()
289 }
290 .as_bytes(),
291 );
292
293 no_sec(&peify(b"", &[], &[]));
295 no_sec(&peify(
297 b"",
298 &[
299 SectionHeader {
300 name: *b".text\0\0\0",
301 ..Default::default()
302 },
303 SectionHeader {
304 name: *b".rodata\0",
305 ..Default::default()
306 },
307 ],
308 &[],
309 ));
310
311 pe_err(&peify(
313 b"",
314 &[SectionHeader {
315 name: *b".osrel\0\0",
316 pointer_to_raw_data: U32::new(1234567),
317 ..Default::default()
318 }],
319 &[],
320 ));
321 }
322}