1use byteorder::{ByteOrder, LittleEndian};
2use num_derive::FromPrimitive;
3use num_traits::FromPrimitive;
4use serde::Serialize;
5use std::{collections::HashMap, convert::TryInto};
6use thiserror::Error;
7use uuid::Uuid;
8
9use crate::{serialize_as_base64, EventType};
10
11fn string_from_widechar(wchar: &[u8]) -> Result<String, EventParseError> {
12 let (head, wchar, tail) = unsafe { wchar.align_to::<u16>() };
13 if !head.is_empty() {
14 return Err(EventParseError::Unaligned);
15 }
16 if !tail.is_empty() {
17 return Err(EventParseError::Unaligned);
18 }
19 let ustr = widestring::U16Str::from_slice(wchar);
20 Ok(ustr
21 .to_string()
22 .map(|s| String::from(s.trim_end_matches('\0')))
23 .map_err(|_| EventParseError::TextDecoding)?)
24}
25
26#[derive(Debug, Serialize)]
27pub struct EfiVariableData {
28 pub variable_guid: Uuid,
29 pub name: String,
30 #[serde(serialize_with = "serialize_as_base64")]
31 pub data: Vec<u8>,
32}
33
34impl EfiVariableData {
35 fn parse(data: &[u8]) -> Result<EfiVariableData, EventParseError> {
36 let variable_guid = &data[0..16];
37 let variable_guid: [u8; 16] = variable_guid.try_into().unwrap();
38 let variable_guid = Uuid::from_bytes(variable_guid);
39 let num_name_chars = LittleEndian::read_u64(&data[16..24]);
40 let name_len = (num_name_chars * 2) as usize;
41 let data_len = LittleEndian::read_u64(&data[24..32]) as usize;
42
43 if data.len() != 16 + 8 + 8 + name_len + data_len {
44 return Err(EventParseError::TooShort);
45 }
46
47 let name_data = &data[32..32 + name_len];
48 let data = data[32 + name_len..].to_vec();
49
50 let name = string_from_widechar(name_data)?;
51
52 Ok(EfiVariableData {
53 variable_guid,
54 name,
55 data,
56 })
57 }
58}
59
60#[derive(Debug, Serialize)]
61#[serde(tag = "end_type", rename_all = "lowercase")]
62pub enum EndOfPathType {
63 EntireDevicePath,
64 Instance,
65}
66
67#[derive(Debug, Serialize, FromPrimitive)]
68#[serde(rename_all = "lowercase")]
69#[repr(u8)]
70pub enum DevicePathInfoHardDrivePartitionFormat {
71 Mbr = 0x01,
72 Gpt = 0x02,
73}
74
75#[derive(Debug, Serialize, FromPrimitive)]
76#[serde(rename_all = "lowercase")]
77#[repr(u8)]
78pub enum DevicePathInfoHardDriveSignatureType {
79 NoSignature = 0x00,
80 MbrType = 0x01,
81 Guid = 0x02,
82}
83
84#[derive(Debug, Serialize)]
85#[serde(tag = "type", rename_all = "lowercase")]
86pub enum DevicePathInfo {
87 UnknownDevice {
89 device_type: u8,
90 device_subtype: u8,
91
92 #[serde(serialize_with = "serialize_as_base64")]
93 data: Vec<u8>,
94 },
95
96 EndOfPath(EndOfPathType),
98
99 DevicePCI {
101 function: u8,
102 device: u8,
103 },
104 DeviceMemoryMapped {
105 memory_type: u32,
106 start_address: u64,
107 end_address: u64,
108 },
109
110 Acpi {
112 hid: u32,
113 uid: u32,
114 },
115
116 HardDrive {
120 partition_number: u32,
121 partition_start: u64,
122 partition_size: u64,
123 #[serde(serialize_with = "serialize_as_base64")]
124 partition_signature: Vec<u8>,
125 partition_format: DevicePathInfoHardDrivePartitionFormat,
126 signature_type: DevicePathInfoHardDriveSignatureType,
127 },
128 FilePath {
129 path: String,
130 },
131}
132
133impl DevicePathInfo {
134 fn parse(
135 device_type: u8,
136 device_subtype: u8,
137 data: &[u8],
138 ) -> Result<DevicePathInfo, EventParseError> {
139 match (device_type, device_subtype) {
140 (0x7F, 0xFF) => Ok(DevicePathInfo::EndOfPath(EndOfPathType::EntireDevicePath)),
143 (0x7F, 0x01) => Ok(DevicePathInfo::EndOfPath(EndOfPathType::Instance)),
145
146 (0x01, 0x01) => {
149 if data.len() != 2 {
150 return Err(EventParseError::TooShort);
151 }
152 Ok(DevicePathInfo::DevicePCI {
153 function: data[0],
154 device: data[1],
155 })
156 }
157
158 (0x01, 0x03) => {
160 if data.len() != 20 {
161 return Err(EventParseError::TooShort);
162 }
163 Ok(DevicePathInfo::DeviceMemoryMapped {
164 memory_type: LittleEndian::read_u32(&data[0..4]),
165 start_address: LittleEndian::read_u64(&data[4..12]),
166 end_address: LittleEndian::read_u64(&data[12..20]),
167 })
168 }
169
170 (0x02, 0x01) => {
173 if data.len() != 8 {
174 return Err(EventParseError::TooShort);
175 }
176 Ok(DevicePathInfo::Acpi {
177 hid: LittleEndian::read_u32(&data[0..4]),
178 uid: LittleEndian::read_u32(&data[4..8]),
179 })
180 }
181
182 (0x04, 0x01) => {
185 if data.len() != 38 {
186 return Err(EventParseError::TooShort);
187 }
188 let partition_format =
189 match DevicePathInfoHardDrivePartitionFormat::from_u8(data[36]) {
190 None => return Err(EventParseError::InvalidValue),
191 Some(v) => v,
192 };
193 let signature_type = match DevicePathInfoHardDriveSignatureType::from_u8(data[37]) {
194 None => return Err(EventParseError::InvalidValue),
195 Some(v) => v,
196 };
197 Ok(DevicePathInfo::HardDrive {
198 partition_number: LittleEndian::read_u32(&data[0..4]),
199 partition_start: LittleEndian::read_u64(&data[4..12]),
200 partition_size: LittleEndian::read_u64(&data[12..20]),
201 partition_signature: data[20..36].to_vec(),
202 partition_format,
203 signature_type,
204 })
205 }
206
207 (0x04, 0x04) => Ok(DevicePathInfo::FilePath {
209 path: string_from_widechar(data)?,
210 }),
211
212 _ => Ok(DevicePathInfo::UnknownDevice {
214 device_type,
215 device_subtype,
216 data: data.to_vec(),
217 }),
218 }
219 }
220}
221
222#[derive(Debug, Serialize)]
223pub struct DevicePath {
224 #[serde(flatten)]
225 pub info: DevicePathInfo,
226 pub next: Option<Box<DevicePath>>,
227}
228
229impl DevicePath {
230 fn parse(data: &[u8]) -> Result<Option<DevicePath>, EventParseError> {
231 if data.is_empty() {
232 Ok(None)
233 } else {
234 let device_type = data[0];
235 let device_subtype = data[1];
236 let path_len = (LittleEndian::read_u16(&data[2..4]) - 4) as usize;
237
238 let path_data = data[4..4 + path_len].to_vec();
239
240 let next = DevicePath::parse(&data[4 + path_len..])?.map(Box::new);
241
242 Ok(Some(DevicePath {
243 info: DevicePathInfo::parse(device_type, device_subtype, &path_data)?,
244 next,
245 }))
246 }
247 }
248}
249
250#[derive(Debug, Serialize)]
251pub struct EfiTableHeader {
252 pub signature: u64,
253 pub revision: u32,
254 pub size: u32,
255 pub reserved: u32,
257}
258
259impl EfiTableHeader {
260 fn parse(data: &[u8]) -> Result<EfiTableHeader, EventParseError> {
261 if data.len() != 24 {
262 return Err(EventParseError::TooShort);
263 }
264 Ok(EfiTableHeader {
265 signature: LittleEndian::read_u64(&data[0..8]),
266 revision: LittleEndian::read_u32(&data[8..12]),
267 size: LittleEndian::read_u32(&data[12..16]),
268 reserved: LittleEndian::read_u32(&data[20..24]),
269 })
270 }
271}
272
273#[derive(Debug, Serialize)]
274pub struct EfiPartitionHeader {
275 #[serde(flatten)]
276 pub header: EfiTableHeader,
277 pub my_lba: u64,
278 pub alternate_lba: u64,
279 pub first_usable_lba: u64,
280 pub last_usable_lba: u64,
281 pub disk_guid: Uuid,
282 pub partition_entry_lba: u64,
283 #[serde(serialize_with = "serialize_as_base64")]
284 pub reserved: Vec<u8>,
285}
286
287#[derive(Debug, Serialize)]
288pub struct EfiPartitionEntry {
289 pub partition_type: Uuid,
290 pub unique_partition_guid: Uuid,
291 pub starting_lba: u64,
292 pub ending_lba: u64,
293 pub attributes: u64,
294 pub partition_name: String,
295 #[serde(serialize_with = "serialize_as_base64")]
296 pub reserved: Vec<u8>,
297}
298
299fn parse_uuid(data: &[u8]) -> Result<Uuid, EventParseError> {
300 if data.len() != 16 {
301 return Err(EventParseError::TooShort);
302 }
303
304 let data1 = LittleEndian::read_u32(&data[0..4]);
306 let data2 = LittleEndian::read_u16(&data[4..6]);
307 let data3 = LittleEndian::read_u16(&data[6..8]);
308 let data4 = &data[8..16];
309
310 Ok(Uuid::from_fields(data1, data2, data3, data4)?)
311}
312
313impl EfiPartitionEntry {
314 fn parse(data: &[u8]) -> Result<EfiPartitionEntry, EventParseError> {
315 if data.len() < 128 {
316 return Err(EventParseError::TooShort);
317 }
318
319 Ok(EfiPartitionEntry {
320 partition_type: parse_uuid(&data[0..16])?,
321 unique_partition_guid: parse_uuid(&data[16..32])?,
322 starting_lba: LittleEndian::read_u64(&data[32..40]),
323 ending_lba: LittleEndian::read_u64(&data[40..48]),
324 attributes: LittleEndian::read_u64(&data[48..56]),
325 partition_name: string_from_widechar(&data[56..128])?,
326 reserved: data[128..].to_vec(),
327 })
328 }
329}
330
331fn parse_efi_partition_data(
332 data: &[u8],
333) -> Result<(EfiPartitionHeader, Vec<EfiPartitionEntry>), EventParseError> {
334 if data.len() < 24 + 64 {
335 return Err(EventParseError::TooShort);
336 }
337
338 let table_header = EfiTableHeader::parse(&data[0..24])?;
339
340 if table_header.signature != 0x5452415020494645 {
341 return Err(EventParseError::InvalidSignature);
342 }
343 if table_header.revision != 0x00010000 {
344 return Err(EventParseError::InvalidValue);
345 }
346 if data.len() < (table_header.size as usize) {
347 return Err(EventParseError::TooShort);
348 }
349
350 let size_of_partition_entry = LittleEndian::read_u32(&data[84..88]) as usize;
351
352 let mut header_end = data.len();
353 while header_end > size_of_partition_entry + (table_header.size as usize) {
354 header_end -= size_of_partition_entry;
355 }
356
357 let header = EfiPartitionHeader {
358 header: table_header,
359 my_lba: LittleEndian::read_u64(&data[24..32]),
360 alternate_lba: LittleEndian::read_u64(&data[32..40]),
361 first_usable_lba: LittleEndian::read_u64(&data[40..48]),
362 last_usable_lba: LittleEndian::read_u64(&data[48..56]),
363 disk_guid: parse_uuid(&data[56..72])?,
364 partition_entry_lba: LittleEndian::read_u64(&data[72..80]),
365 reserved: data[92..header_end].to_vec(),
366 };
367
368 let mut partitions = Vec::new();
369
370 for offset in (header_end..data.len()).step_by(size_of_partition_entry) {
371 partitions.push(EfiPartitionEntry::parse(
372 &data[offset..offset + size_of_partition_entry],
373 )?);
374 }
375
376 Ok((header, partitions))
377}
378
379#[derive(Debug, Serialize)]
380pub enum SeparatorType {
381 ConventionalBIOS,
382 UEFI,
383}
384
385#[derive(Debug, Serialize)]
386#[serde(rename_all = "lowercase")]
387pub enum ParsedEventData {
388 FirmwareBlobLocation {
389 base: u64,
390 length: u64,
391 },
392 Text(String),
393 EfiVariable(EfiVariableData),
394 ImageLoadEvent {
395 image_location_in_memory: u64,
396 image_length_in_memory: u64,
397 image_link_time_address: u64,
398 device_path: Option<DevicePath>,
399 #[serde(serialize_with = "serialize_as_base64")]
400 extra_data: Vec<u8>,
401 },
402 GptInfo {
403 header: EfiPartitionHeader,
404 partitions: Vec<EfiPartitionEntry>,
405 },
406 ValidSeparator(SeparatorType),
407}
408
409#[derive(Error, Debug)]
410pub enum EventParseError {
411 #[error("Text decoding error")]
412 TextDecoding,
413 #[error("Contents are too short")]
414 TooShort,
415 #[error("Invalid structure signature")]
416 InvalidSignature,
417 #[error("Unsupported log version")]
418 UnsupportedLog,
419 #[error("A value was unaligned")]
420 Unaligned,
421 #[error("An invalid value was encountered")]
422 InvalidValue,
423 #[error("Invalid GUID: {0}")]
424 InvalidGuid(#[from] uuid::Error),
425}
426
427impl ParsedEventData {
428 fn parse_efi_text(data: &[u8]) -> Result<ParsedEventData, EventParseError> {
429 Ok(ParsedEventData::Text(
430 std::str::from_utf8(data)
431 .map_err(|_| EventParseError::TextDecoding)?
432 .trim_end_matches('\0')
433 .to_string(),
434 ))
435 }
436
437 fn parse_efi_image_load_event(data: &[u8]) -> Result<ParsedEventData, EventParseError> {
438 if data.len() < 32 {
439 return Err(EventParseError::TooShort);
440 }
441 let image_location_in_memory = LittleEndian::read_u64(&data[0..8]);
442 let image_length_in_memory = LittleEndian::read_u64(&data[8..16]);
443 let image_link_time_address = LittleEndian::read_u64(&data[16..24]);
444 let device_path_len = LittleEndian::read_u64(&data[24..32]) as usize;
445
446 let device_path = DevicePath::parse(&data[32..32 + device_path_len])?;
447 let extra_data = data[32 + device_path_len..].to_vec();
448
449 Ok(ParsedEventData::ImageLoadEvent {
450 image_location_in_memory,
451 image_length_in_memory,
452 image_link_time_address,
453 device_path,
454 extra_data,
455 })
456 }
457
458 fn parse_efi_firmware_blob(data: &[u8]) -> Result<ParsedEventData, EventParseError> {
459 if data.len() != 16 {
460 return Err(EventParseError::TooShort);
461 }
462 let base = LittleEndian::read_u64(&data[0..8]);
463 let length = LittleEndian::read_u64(&data[8..16]);
464 Ok(ParsedEventData::FirmwareBlobLocation { base, length })
465 }
466
467 fn parse_gpt_event(data: &[u8]) -> Result<ParsedEventData, EventParseError> {
468 let (header, partitions) = parse_efi_partition_data(data)?;
469
470 Ok(ParsedEventData::GptInfo { header, partitions })
471 }
472
473 pub(crate) fn parse(
474 event: EventType,
475 data: &[u8],
476 ) -> Result<Option<ParsedEventData>, EventParseError> {
477 match event {
478 EventType::CrtmVersion => Ok(Some(ParsedEventData::Text(string_from_widechar(data)?))),
480 EventType::EFIVariableDriverConfig
481 | EventType::EFIVariableBoot
482 | EventType::EFIVariableAuthority => Ok(Some(ParsedEventData::EfiVariable(
483 EfiVariableData::parse(data)?,
484 ))),
485 EventType::PostCode | EventType::IPL | EventType::EFIAction => {
486 Ok(Some(ParsedEventData::parse_efi_text(data)?))
487 }
488 EventType::EFIBootServicesApplication
489 | EventType::EFIBootServicesDriver
490 | EventType::EFIRuntimeServicesDriver => {
491 Ok(Some(ParsedEventData::parse_efi_image_load_event(data)?))
492 }
493 EventType::EFIPlatformFirmwareBlob => {
494 Ok(Some(ParsedEventData::parse_efi_firmware_blob(data)?))
495 }
496 EventType::EFIGptEvent => Ok(Some(ParsedEventData::parse_gpt_event(data)?)),
497
498 EventType::Separator => {
500 if data == [0, 0, 0, 0] {
501 Ok(Some(ParsedEventData::ValidSeparator(SeparatorType::UEFI)))
502 } else if data == [0xff, 0xff, 0xff, 0xff] {
503 Ok(Some(ParsedEventData::ValidSeparator(
504 SeparatorType::ConventionalBIOS,
505 )))
506 } else {
507 Ok(None)
508 }
509 }
510 _ => Ok(None),
511 }
512 }
513}
514
515#[derive(Debug)]
516pub(crate) struct EfiSpecId {
517 pub(crate) platform_class: u32,
518 pub(crate) spec_version_major: u8,
519 pub(crate) spec_version_minor: u8,
520 pub(crate) spec_errata: u8,
521 pub(crate) uintn_size: u8,
522 pub(crate) algo_sizes: HashMap<u16, u16>,
523 pub(crate) vendor_info: Vec<u8>,
524}
525
526impl EfiSpecId {
527 pub(crate) fn parse(data: &[u8]) -> Result<EfiSpecId, EventParseError> {
528 if data.len() < 29 {
529 return Err(EventParseError::TooShort);
530 }
531 let signature = &data[0..16];
532 if signature
533 != [
534 0x53, 0x70, 0x65, 0x63, 0x20, 0x49, 0x44, 0x20, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30,
535 0x33, 0x00,
536 ]
537 {
538 return Err(EventParseError::InvalidSignature);
539 }
540 let platform_class = LittleEndian::read_u32(&data[16..20]);
541 let spec_version_minor = data[20];
542 let spec_version_major = data[21];
543 let spec_errata = data[22];
544 let uintn_size = data[23];
545 let num_algorithms = LittleEndian::read_u32(&data[24..28]);
546 let mut algo_sizes = HashMap::new();
547 for i in 0..num_algorithms {
548 let i = i as usize;
549 let algo_id = LittleEndian::read_u16(&data[28 + (i * 4)..28 + (i * 4) + 2]);
550 let digest_size = LittleEndian::read_u16(&data[28 + (i * 4) + 2..28 + (i * 4) + 4]);
551 algo_sizes.insert(algo_id, digest_size);
552 }
553 let offset = 28 + (num_algorithms * 4) as usize;
554 let vendor_info_size = data[offset] as usize;
555 if data.len() != (offset + vendor_info_size + 1) {
556 return Err(EventParseError::TooShort);
557 }
558 let vendor_info = data[offset + 1..].to_vec();
559
560 Ok(EfiSpecId {
561 platform_class,
562 spec_version_major,
563 spec_version_minor,
564 spec_errata,
565 uintn_size,
566 algo_sizes,
567 vendor_info,
568 })
569 }
570}