uefi_nvram_specvars/
boot_order.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Code to parse bootorder-related nvram variables.
5
6use guid::Guid;
7use std::ffi::CStr;
8use thiserror::Error;
9use ucs2::Ucs2LeSlice;
10use uefi_specs::uefi::boot;
11use zerocopy::FromBytes;
12
13#[derive(Debug, Error)]
14pub enum Error {
15    #[error("Data length incorrect or inconsistent with other fields")]
16    InvalidLength,
17    #[error("Missing null-termination")]
18    NullTerminated(#[source] std::ffi::FromBytesUntilNulError),
19    #[error("Invalid Ucs2 string")]
20    InvalidUcs2(#[source] ucs2::Ucs2ParseError),
21    #[error("Invalid UTF-8 string")]
22    InvalidUtf8(#[source] std::str::Utf8Error),
23    #[error("Device path end structure missing or corrupted")]
24    DevicePathEnd,
25}
26
27#[derive(Debug, PartialEq)]
28pub enum HardwareDevice<'a> {
29    MemoryMapped(boot::EfiMemoryMappedDevice),
30    Vendor {
31        vendor_guid: Guid,
32        data: &'a [u8],
33    },
34    Unknown {
35        device_subtype: boot::EfiHardwareDeviceSubType,
36        path_data: &'a [u8],
37    },
38}
39
40#[derive(Debug, PartialEq)]
41pub enum AcpiDevice<'a> {
42    ExpandedAcpi {
43        numeric: boot::EfiExpandedAcpiDevice,
44        hidstr: &'a CStr,
45        uidstr: &'a CStr,
46        cidstr: &'a CStr,
47    },
48    Unknown {
49        device_subtype: boot::EfiAcpiDeviceSubType,
50        path_data: &'a [u8],
51    },
52}
53
54#[derive(Debug, PartialEq)]
55pub enum MessagingDevice<'a> {
56    Scsi(boot::EfiScsiDevice),
57    Unknown {
58        device_subtype: boot::EfiMessagingDeviceSubType,
59        path_data: &'a [u8],
60    },
61}
62
63#[derive(Debug, PartialEq)]
64pub enum MediaDevice<'a> {
65    HardDrive(boot::EfiHardDriveDevice),
66    File(&'a Ucs2LeSlice),
67    PiwgFirmwareFile(Guid),
68    PiwgFirmwareVolume(Guid),
69    Unknown {
70        device_subtype: boot::EfiMediaDeviceSubType,
71        path_data: &'a [u8],
72    },
73}
74
75#[derive(Debug, PartialEq)]
76pub enum EndDevice<'a> {
77    Instance,
78    Entire,
79    Unknown {
80        device_subtype: boot::EfiEndDeviceSubType,
81        path_data: &'a [u8],
82    },
83}
84
85#[derive(Debug)]
86pub enum EfiDevicePathProtocol<'a> {
87    Hardware(HardwareDevice<'a>),
88    Acpi(AcpiDevice<'a>),
89    Messaging(MessagingDevice<'a>),
90    Media(MediaDevice<'a>),
91    End(EndDevice<'a>),
92    Unknown {
93        device_type: boot::EfiDeviceType,
94        device_subtype: u8,
95        path_data: &'a [u8],
96    },
97}
98
99impl<'a> EfiDevicePathProtocol<'a> {
100    pub fn parse(data: &'a [u8]) -> Result<(Self, &'a [u8]), Error> {
101        let (header, path_data) = boot::EfiDevicePathProtocol::read_from_prefix(data)
102            .map_err(|_| Error::InvalidLength)?; // TODO: zerocopy: map_err (https://github.com/microsoft/openvmm/issues/759)
103
104        let length = u16::from_le_bytes(header.length) as usize;
105        // TODO: Switch to split_at_checked below once stable and remove this check
106        if data.len() < length {
107            return Err(Error::InvalidLength);
108        }
109
110        let (path_data, remaining) = path_data.split_at(
111            length
112                .checked_sub(size_of::<boot::EfiDevicePathProtocol>())
113                .ok_or(Error::InvalidLength)?,
114        );
115
116        Ok((
117            match header.device_type {
118                boot::EfiDeviceType::HARDWARE => EfiDevicePathProtocol::Hardware(
119                    match boot::EfiHardwareDeviceSubType(header.sub_type) {
120                        boot::EfiHardwareDeviceSubType::MEMORY_MAPPED => {
121                            HardwareDevice::MemoryMapped(
122                                boot::EfiMemoryMappedDevice::read_from_bytes(path_data)
123                                    .map_err(|_| Error::InvalidLength)?, // TODO: zerocopy: map_err (https://github.com/microsoft/openvmm/issues/759)
124                            )
125                        }
126                        boot::EfiHardwareDeviceSubType::VENDOR => {
127                            let (vendor_guid, path_data) = Guid::read_from_prefix(path_data)
128                                .map_err(|_| Error::InvalidLength)?; // TODO: zerocopy: map_err (https://github.com/microsoft/openvmm/issues/759)
129                            HardwareDevice::Vendor {
130                                vendor_guid,
131                                data: path_data,
132                            }
133                        }
134                        device_subtype => HardwareDevice::Unknown {
135                            device_subtype,
136                            path_data,
137                        },
138                    },
139                ),
140                boot::EfiDeviceType::ACPI => {
141                    EfiDevicePathProtocol::Acpi(match boot::EfiAcpiDeviceSubType(header.sub_type) {
142                        boot::EfiAcpiDeviceSubType::EXPANDED_ACPI => {
143                            // minimum length is numeric representation + 3 null terminators from empty strings
144                            if path_data.len() < size_of::<boot::EfiExpandedAcpiDevice>() + 3 {
145                                return Err(Error::InvalidLength);
146                            }
147                            let (numeric, path_data) =
148                                boot::EfiExpandedAcpiDevice::read_from_prefix(path_data).unwrap(); // TODO: zerocopy: unwrap (https://github.com/microsoft/openvmm/issues/759)
149                            let hidstr = CStr::from_bytes_until_nul(path_data)
150                                .map_err(Error::NullTerminated)?;
151                            let path_data = &path_data[hidstr.to_bytes_with_nul().len()..];
152                            let uidstr = CStr::from_bytes_until_nul(path_data)
153                                .map_err(Error::NullTerminated)?;
154                            let path_data = &path_data[uidstr.to_bytes_with_nul().len()..];
155                            let cidstr = CStr::from_bytes_until_nul(path_data)
156                                .map_err(Error::NullTerminated)?;
157                            if cidstr.to_bytes_with_nul().len() != path_data.len() {
158                                return Err(Error::InvalidLength);
159                            }
160                            AcpiDevice::ExpandedAcpi {
161                                numeric,
162                                hidstr,
163                                uidstr,
164                                cidstr,
165                            }
166                        }
167                        device_subtype => AcpiDevice::Unknown {
168                            device_subtype,
169                            path_data,
170                        },
171                    })
172                }
173                boot::EfiDeviceType::MESSAGING => EfiDevicePathProtocol::Messaging(
174                    match boot::EfiMessagingDeviceSubType(header.sub_type) {
175                        boot::EfiMessagingDeviceSubType::SCSI => MessagingDevice::Scsi(
176                            boot::EfiScsiDevice::read_from_bytes(path_data)
177                                .map_err(|_| Error::InvalidLength)?, // TODO: zerocopy: map_err (https://github.com/microsoft/openvmm/issues/759)
178                        ),
179                        device_subtype => MessagingDevice::Unknown {
180                            device_subtype,
181                            path_data,
182                        },
183                    },
184                ),
185                boot::EfiDeviceType::MEDIA => EfiDevicePathProtocol::Media(
186                    match boot::EfiMediaDeviceSubType(header.sub_type) {
187                        boot::EfiMediaDeviceSubType::HARD_DRIVE => MediaDevice::HardDrive(
188                            boot::EfiHardDriveDevice::read_from_bytes(path_data)
189                                .map_err(|_| Error::InvalidLength)?, // TODO: zerocopy: map_err (https://github.com/microsoft/openvmm/issues/759)
190                        ),
191                        boot::EfiMediaDeviceSubType::FILE => {
192                            let file_name = Ucs2LeSlice::from_slice_with_nul(path_data)
193                                .map_err(Error::InvalidUcs2)?;
194                            if file_name.as_bytes().len() != path_data.len() {
195                                return Err(Error::InvalidLength);
196                            }
197                            MediaDevice::File(file_name)
198                        }
199                        boot::EfiMediaDeviceSubType::PIWG_FIRMWARE_FILE => {
200                            MediaDevice::PiwgFirmwareFile(
201                                Guid::read_from_bytes(path_data)
202                                    .map_err(|_| Error::InvalidLength)?, // TODO: zerocopy: map_err (https://github.com/microsoft/openvmm/issues/759)
203                            )
204                        }
205                        boot::EfiMediaDeviceSubType::PIWG_FIRMWARE_VOLUME => {
206                            MediaDevice::PiwgFirmwareVolume(
207                                Guid::read_from_bytes(path_data)
208                                    .map_err(|_| Error::InvalidLength)?, // TODO: zerocopy: map_err (https://github.com/microsoft/openvmm/issues/759)
209                            )
210                        }
211                        device_subtype => MediaDevice::Unknown {
212                            device_subtype,
213                            path_data,
214                        },
215                    },
216                ),
217                boot::EfiDeviceType::END => {
218                    EfiDevicePathProtocol::End(match boot::EfiEndDeviceSubType(header.sub_type) {
219                        boot::EfiEndDeviceSubType::INSTANCE => EndDevice::Instance,
220                        boot::EfiEndDeviceSubType::ENTIRE => EndDevice::Entire,
221                        device_subtype => EndDevice::Unknown {
222                            device_subtype,
223                            path_data,
224                        },
225                    })
226                }
227                device_type => EfiDevicePathProtocol::Unknown {
228                    device_type,
229                    device_subtype: header.sub_type,
230                    path_data,
231                },
232            },
233            remaining,
234        ))
235    }
236}
237
238#[derive(Debug)]
239pub struct EfiLoadOption<'a> {
240    pub attributes: u32,
241    pub description: &'a Ucs2LeSlice,
242    pub device_paths: Vec<EfiDevicePathProtocol<'a>>,
243    pub opt: Option<&'a [u8]>,
244}
245
246impl<'a> EfiLoadOption<'a> {
247    pub fn parse(data: &'a [u8]) -> Result<Self, Error> {
248        let (header, data) =
249            boot::EfiLoadOption::read_from_prefix(data).map_err(|_| Error::InvalidLength)?; // TODO: zerocopy: map_err (https://github.com/microsoft/openvmm/issues/759)
250
251        let description = Ucs2LeSlice::from_slice_with_nul(data).map_err(Error::InvalidUcs2)?;
252        let mut data = &data[description.as_bytes().len()..];
253
254        let mut device_paths = Vec::new();
255        loop {
256            if data.len() < size_of::<boot::EfiDevicePathProtocol>() {
257                return Err(Error::DevicePathEnd);
258            }
259            let path;
260            (path, data) = EfiDevicePathProtocol::parse(data)?;
261            match path {
262                EfiDevicePathProtocol::End(end) => match end {
263                    EndDevice::Instance => continue,
264                    EndDevice::Entire => break,
265                    _ => return Err(Error::DevicePathEnd),
266                },
267                path => device_paths.push(path),
268            }
269        }
270
271        let opt = if !data.is_empty() { Some(data) } else { None };
272
273        Ok(EfiLoadOption {
274            attributes: header.attributes,
275            description,
276            device_paths,
277            opt,
278        })
279    }
280}
281
282pub fn parse_boot_order(data: &[u8]) -> Result<impl Iterator<Item = u16> + '_, Error> {
283    let boot_order_iter = data.chunks_exact(2);
284    if !boot_order_iter.remainder().is_empty() {
285        return Err(Error::InvalidLength);
286    }
287    Ok(boot_order_iter.map(|x| u16::from_le_bytes(x.try_into().unwrap())))
288}