Skip to main content

uefi_eventlog/
parsed.rs

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    // Device types we couldn't parse
88    UnknownDevice {
89        device_type: u8,
90        device_subtype: u8,
91
92        #[serde(serialize_with = "serialize_as_base64")]
93        data: Vec<u8>,
94    },
95
96    // Type: Device Path End
97    EndOfPath(EndOfPathType),
98
99    // Type: Hardware Device Path
100    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    // Type: ACPI Device Path
111    Acpi {
112        hid: u32,
113        uid: u32,
114    },
115
116    // Type: Messaging Device Path
117
118    // Type: Media Device Path
119    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            // Type: Device Path End
141            //  Sub-type: End Entire Device Path
142            (0x7F, 0xFF) => Ok(DevicePathInfo::EndOfPath(EndOfPathType::EntireDevicePath)),
143            //  Sub-type: End this instance
144            (0x7F, 0x01) => Ok(DevicePathInfo::EndOfPath(EndOfPathType::Instance)),
145
146            // Type: Hardware Device Path
147            //  Sub-type: PCI
148            (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            //  Sub-type: Memory Mapped Device
159            (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            // Type: ACPI Device Path
171            //  Sub-type: ACPI
172            (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            // Type Media Devices
183            //  Sub-type: Hard Drive
184            (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            //  Sub-type: File Path
208            (0x04, 0x04) => Ok(DevicePathInfo::FilePath {
209                path: string_from_widechar(data)?,
210            }),
211
212            // Unknown device types
213            _ => 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    // crc: u32
256    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    // The Microsoft GUID format is.... silly
305    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            // EFI Events
479            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            // EFI Event types to do: HandoffTables
499            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}