Skip to main content

efivar_fix/boot/parse/
device_path.rs

1//! This module contains parsing code for a device path, part of a device path list
2
3use std::{convert::TryInto, fmt::Display};
4
5use byteorder::{LittleEndian, ReadBytesExt};
6use uuid::Uuid;
7
8use crate::{push::PushVecU8, utils::read_nt_utf16_string, Error};
9
10use super::consts;
11
12pub enum DevicePath {
13    FilePath(FilePath),
14    HardDrive(EFIHardDrive),
15}
16
17#[derive(Debug, PartialEq, Clone)]
18pub enum EFIHardDriveType {
19    Mbr,
20    Gpt,
21    Unknown, // TODO: remove ?
22}
23
24impl EFIHardDriveType {
25    pub fn parse(sig_type: u8) -> EFIHardDriveType {
26        match sig_type {
27            0x01 => Self::Mbr,
28            0x02 => Self::Gpt,
29            _ => Self::Unknown,
30        }
31    }
32
33    pub fn as_u8(&self) -> u8 {
34        match self {
35            EFIHardDriveType::Mbr => 0x01,
36            EFIHardDriveType::Gpt => 0x02,
37            EFIHardDriveType::Unknown => panic!(),
38        }
39    }
40}
41
42impl Display for EFIHardDriveType {
43    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44        match self {
45            EFIHardDriveType::Mbr => f.write_str("MBR"),
46            EFIHardDriveType::Gpt => f.write_str("GPT"),
47            EFIHardDriveType::Unknown => f.write_str("Unknown"),
48        }
49    }
50}
51
52#[derive(Debug, PartialEq, Clone)]
53// See spec, 10.3.6.1
54pub struct EFIHardDrive {
55    pub partition_number: u32,
56    pub partition_start: u64,
57    pub partition_size: u64,
58    pub partition_sig: Uuid,
59    pub format: u8,
60    pub sig_type: EFIHardDriveType,
61}
62
63impl EFIHardDrive {
64    pub fn parse(buf: &mut &[u8]) -> crate::Result<EFIHardDrive> {
65        Ok(EFIHardDrive {
66            partition_number: buf
67                .read_u32::<LittleEndian>()
68                .map_err(|_| Error::VarParseError)?,
69            partition_start: buf
70                .read_u64::<LittleEndian>()
71                .map_err(|_| Error::VarParseError)?,
72            partition_size: buf
73                .read_u64::<LittleEndian>()
74                .map_err(|_| Error::VarParseError)?,
75            partition_sig: Uuid::from_fields(
76                buf.read_u32::<LittleEndian>()
77                    .map_err(|_| Error::VarParseError)?,
78                buf.read_u16::<LittleEndian>()
79                    .map_err(|_| Error::VarParseError)?,
80                buf.read_u16::<LittleEndian>()
81                    .map_err(|_| Error::VarParseError)?,
82                &buf.read_u64::<LittleEndian>()
83                    .map_err(|_| Error::VarParseError)?
84                    .to_le_bytes(),
85            ),
86            format: buf.read_u8().map_err(|_| Error::VarParseError)?,
87            sig_type: EFIHardDriveType::parse(buf.read_u8().map_err(|_| Error::VarParseError)?),
88        })
89    }
90}
91
92impl Display for EFIHardDrive {
93    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94        write!(
95            f,
96            "HD({},{},{})",
97            self.partition_number, self.sig_type, self.partition_sig
98        )
99    }
100}
101
102impl DevicePath {
103    pub fn parse(buf: &mut &[u8]) -> crate::Result<Option<DevicePath>> {
104        let r#type = buf.read_u8().map_err(|_| Error::VarParseError)?;
105        let subtype = buf.read_u8().map_err(|_| Error::VarParseError)?;
106        let length = buf
107            .read_u16::<LittleEndian>()
108            .map_err(|_| Error::VarParseError)?;
109
110        let data_size = length - 1 - 1 - 2;
111
112        if data_size as usize > buf.len() {
113            return Err(Error::VarParseError);
114        }
115
116        let (mut device_path_data, new_buf) = buf.split_at(data_size.into());
117        *buf = new_buf;
118
119        #[allow(clippy::single_match)]
120        match r#type {
121            consts::DEVICE_PATH_TYPE::MEDIA_DEVICE_PATH => match subtype {
122                consts::MEDIA_DEVICE_PATH_SUBTYPE::FILE_PATH => {
123                    return Ok(Some(DevicePath::FilePath(FilePath {
124                        path: read_nt_utf16_string(&mut device_path_data)
125                            .map_err(crate::Error::StringParseError)?,
126                    })));
127                }
128                consts::MEDIA_DEVICE_PATH_SUBTYPE::HARD_DRIVE => {
129                    return Ok(Some(DevicePath::HardDrive(EFIHardDrive::parse(
130                        &mut device_path_data,
131                    )?)));
132                }
133                _ => {}
134            },
135            _ => {}
136        };
137
138        Ok(None)
139    }
140}
141
142fn encap_as_device_path(r#type: u8, r#subtype: u8, mut raw_data: Vec<u8>) -> Vec<u8> {
143    let mut bytes: Vec<u8> = vec![];
144
145    bytes.push_u8(r#type);
146    bytes.push_u8(r#subtype);
147
148    let raw_data_size: u16 = raw_data.len().try_into().expect("length should fit in u16");
149    bytes.push_u16(raw_data_size + 1 + 1 + 2);
150
151    bytes.append(&mut raw_data);
152
153    bytes
154}
155
156impl EFIHardDrive {
157    /// get bytes representation for a EFIHardDrive, without encapsulating them in a DevicePath structure
158    fn to_bytes_raw(&self) -> Vec<u8> {
159        let mut bytes: Vec<u8> = vec![];
160        bytes.push_u32(self.partition_number);
161        bytes.push_u64(self.partition_start);
162        bytes.push_u64(self.partition_size);
163
164        let (f1, f2, f3, f4) = self.partition_sig.as_fields();
165        bytes.push_u32(f1);
166        bytes.push_u16(f2);
167        bytes.push_u16(f3);
168        bytes.append(&mut f4.to_vec());
169        bytes.push_u8(self.format);
170        bytes.push_u8(self.sig_type.as_u8());
171
172        bytes
173    }
174
175    /// get bytes representation for a EFIHardDrive, as a DevicePath (EFI_DEVICE_PATH_PROTOCOL) structure
176    pub fn to_bytes_encap(&self) -> Vec<u8> {
177        encap_as_device_path(
178            consts::DEVICE_PATH_TYPE::MEDIA_DEVICE_PATH,
179            consts::MEDIA_DEVICE_PATH_SUBTYPE::HARD_DRIVE,
180            self.to_bytes_raw(),
181        )
182    }
183}
184
185#[derive(Debug, PartialEq, Clone)]
186pub struct FilePath {
187    /// the UEFI standard seem to use UCS-2 strings (a subset of UTF-16), so Rust UTF8 strings should be able to represent them
188    pub path: String,
189}
190
191impl FilePath {
192    /// get bytes representation for a FilePath, without encapsulating them in a DevicePath structure
193    fn to_bytes_raw(&self) -> Vec<u8> {
194        let utf16_bytes = self.path.encode_utf16();
195        let mut utf8_bytes: Vec<u8> = utf16_bytes
196            .into_iter()
197            .flat_map(|var| var.to_le_bytes())
198            .collect();
199
200        // write null termination
201        utf8_bytes.push_u16(0x0000);
202
203        utf8_bytes
204    }
205
206    /// get bytes representation for a FilePath, as a DevicePath (EFI_DEVICE_PATH_PROTOCOL) structure
207    pub fn to_bytes_encap(&self) -> Vec<u8> {
208        encap_as_device_path(
209            consts::DEVICE_PATH_TYPE::MEDIA_DEVICE_PATH,
210            consts::MEDIA_DEVICE_PATH_SUBTYPE::FILE_PATH,
211            self.to_bytes_raw(),
212        )
213    }
214}
215
216pub fn get_end_device_path_bytes() -> Vec<u8> {
217    encap_as_device_path(
218        consts::DEVICE_PATH_TYPE::END_OF_HARDWARE_DEVICE_PATH,
219        consts::END_OF_HARDWARE_DEVICE_PATH_SUBTYPE::END_ENTIRE_DEVICE_PATH,
220        vec![],
221    )
222}
223
224#[cfg(test)]
225mod tests {
226    use std::str::FromStr;
227
228    use uuid::Uuid;
229
230    use super::{EFIHardDrive, EFIHardDriveType};
231
232    #[test]
233    fn efi_hard_drive_type_parse() {
234        assert_eq!(EFIHardDriveType::parse(0x01), EFIHardDriveType::Mbr);
235        assert_eq!(EFIHardDriveType::parse(0x02), EFIHardDriveType::Gpt);
236        assert_eq!(EFIHardDriveType::parse(0x03), EFIHardDriveType::Unknown);
237        assert_eq!(EFIHardDriveType::parse(0xFF), EFIHardDriveType::Unknown);
238    }
239
240    #[test]
241    fn efi_hard_drive_type_dump() {
242        assert_eq!(EFIHardDriveType::Mbr.as_u8(), 0x01);
243        assert_eq!(EFIHardDriveType::Gpt.as_u8(), 0x02);
244    }
245
246    #[test]
247    fn efi_hard_drive_type_print() {
248        assert_eq!(format!("{}", EFIHardDriveType::Mbr), "MBR");
249        assert_eq!(format!("{}", EFIHardDriveType::Gpt), "GPT");
250        assert_eq!(format!("{}", EFIHardDriveType::Unknown), "Unknown");
251    }
252
253    #[test]
254    #[should_panic]
255    fn efi_hard_drive_type_dump_invalid() {
256        EFIHardDriveType::Unknown.as_u8();
257    }
258
259    #[test]
260    fn print_hard_drive() {
261        assert_eq!(
262            "HD(1,GPT,90364bbd-1000-47fc-8c05-8707e01b4593)",
263            format!(
264                "{}",
265                EFIHardDrive {
266                    partition_number: 1,
267                    partition_start: 2,
268                    partition_size: 3,
269                    partition_sig: Uuid::from_str("90364bbd-1000-47fc-8c05-8707e01b4593").unwrap(),
270                    format: 5,
271                    sig_type: EFIHardDriveType::Gpt,
272                }
273            )
274        );
275    }
276
277    #[test]
278    fn to_from_bytes() {
279        let drive = EFIHardDrive {
280            partition_number: 1,
281            partition_start: 2,
282            partition_size: 3,
283            partition_sig: Uuid::from_str("90364bbd-1000-47fc-8c05-8707e01b4593").unwrap(),
284            format: 5,
285            sig_type: EFIHardDriveType::Gpt,
286        };
287        let bytes = drive.to_bytes_raw();
288        let mut x = bytes.as_slice();
289        let test_parse = EFIHardDrive::parse(&mut x).unwrap();
290        assert_eq!(drive, test_parse)
291    }
292}