1pub mod bootfs;
6
7use bootfs::{
8 ZBI_BOOTFS_MAGIC, ZBI_BOOTFS_MAX_NAME_LEN, ZBI_BOOTFS_PAGE_SIZE, zbi_bootfs_dirent_t,
9 zbi_bootfs_header_t,
10};
11use byteorder::{ByteOrder, LittleEndian};
12
13use std::ffi::CStr;
14use std::mem::size_of;
15use std::str::Utf8Error;
16use thiserror::Error;
17use zerocopy::{Ref, SplitByteSlice};
18
19const ZBI_BOOTFS_DIRENT_SIZE: usize = size_of::<zbi_bootfs_dirent_t>();
20const ZBI_BOOTFS_HEADER_SIZE: usize = size_of::<zbi_bootfs_header_t>();
21
22fn zbi_bootfs_dirent_size(name_len: u32) -> u32 {
25 (ZBI_BOOTFS_DIRENT_SIZE as u32 + name_len + 3) & !3u32
26}
27
28pub fn zbi_bootfs_page_align(size: u32) -> u32 {
29 size.wrapping_add(ZBI_BOOTFS_PAGE_SIZE - 1) & !(ZBI_BOOTFS_PAGE_SIZE - 1)
30}
31
32pub fn zbi_bootfs_is_aligned(size: u32) -> bool {
33 size % ZBI_BOOTFS_PAGE_SIZE == 0
34}
35
36#[derive(Debug, Error, Eq, PartialEq)]
37pub enum BootfsParserError {
38 #[error("Invalid magic for bootfs payload")]
39 BadMagic,
40
41 #[error("Directory entry {} exceeds available dirsize of {}", entry_index, dirsize)]
42 DirEntryTooBig { entry_index: u32, dirsize: u32 },
43
44 #[error("Failed to read payload: {}", status)]
45 FailedToReadPayload { status: zx::Status },
46
47 #[error("Failed to parse bootfs header")]
48 FailedToParseHeader,
49
50 #[error("Failed to parse directory entry")]
51 FailedToParseDirEntry,
52
53 #[error("Failed to read name as UTF-8: {}", cause)]
54 InvalidNameFormat {
55 #[source]
56 cause: Utf8Error,
57 },
58
59 #[error("Failed to find null terminated string for name: {}", cause)]
60 InvalidNameString {
61 #[source]
62 cause: std::ffi::FromBytesWithNulError,
63 },
64
65 #[error(
66 "name_len must be between 1 and {}, found {} for directory entry {}",
67 max_name_len,
68 name_len,
69 entry_index
70 )]
71 InvalidNameLength { name_len: u32, max_name_len: u32, entry_index: u32 },
72}
73
74#[derive(Debug)]
75struct ZbiBootfsDirent<B: SplitByteSlice> {
76 header: Ref<B, zbi_bootfs_dirent_t>,
77 name_bytes: B,
78}
79impl<B: SplitByteSlice> ZbiBootfsDirent<B> {
80 pub fn parse(bytes: B) -> Result<ZbiBootfsDirent<B>, BootfsParserError> {
81 let (header, name_bytes) = Ref::<B, zbi_bootfs_dirent_t>::from_prefix(bytes)
82 .map_err(Into::into)
83 .map_err(|_: zerocopy::SizeError<_, _>| BootfsParserError::FailedToParseDirEntry)?;
84
85 Ok(ZbiBootfsDirent { header, name_bytes })
86 }
87
88 pub fn data_len(&self) -> u32 {
89 return self.header.data_len.get();
90 }
91
92 pub fn data_off(&self) -> u32 {
93 return self.header.data_off.get();
94 }
95
96 pub fn name(&self) -> Result<&str, BootfsParserError> {
97 match CStr::from_bytes_with_nul(&self.name_bytes[..self.header.name_len.get() as usize]) {
102 Ok(bytes) => {
103 bytes.to_str().map_err(|cause| BootfsParserError::InvalidNameFormat { cause })
104 }
105 Err(cause) => Err(BootfsParserError::InvalidNameString { cause }),
106 }
107 }
108}
109
110#[derive(Debug)]
112pub struct BootfsParser {
113 pub(self) dirsize: u32,
115 pub(self) vmo: zx::Vmo,
116}
117impl BootfsParser {
118 pub fn create_from_vmo(vmo: zx::Vmo) -> Result<BootfsParser, BootfsParserError> {
122 let mut header_bytes = [0; ZBI_BOOTFS_HEADER_SIZE];
123 vmo.read(&mut header_bytes, 0)
124 .map_err(|status| BootfsParserError::FailedToReadPayload { status })?;
125
126 let header = Ref::<_, zbi_bootfs_header_t>::from_bytes(&header_bytes[..])
127 .map_err(Into::into)
128 .map_err(|_: zerocopy::SizeError<_, _>| BootfsParserError::FailedToParseHeader)?;
129 if header.magic.get() == ZBI_BOOTFS_MAGIC {
130 Ok(Self { vmo, dirsize: header.dirsize.get() })
131 } else {
132 Err(BootfsParserError::BadMagic)
133 }
134 }
135
136 pub fn iter(&self) -> impl Iterator<Item = Result<BootfsEntry, BootfsParserError>> + '_ {
137 BootfsParserIterator::new(&self, false)
138 }
139
140 pub fn zero_copy_iter(
141 &self,
142 ) -> impl Iterator<Item = Result<BootfsEntry, BootfsParserError>> + '_ {
143 BootfsParserIterator::new(&self, true)
144 }
145}
146
147#[derive(Debug)]
148pub struct BootfsEntry {
149 pub name: String,
150 pub offset: u64,
151 pub size: u64,
152
153 pub payload: Option<Vec<u8>>,
155}
156
157#[derive(Debug)]
158struct BootfsParserIterator<'parser> {
159 available_dirsize: u32,
160 dir_offset: u32,
161 entry_index: u32,
162 errored: bool,
163 zero_copy: bool,
164 parser: &'parser BootfsParser,
165}
166impl<'parser> BootfsParserIterator<'parser> {
167 pub fn new(parser: &'parser BootfsParser, zero_copy: bool) -> Self {
168 Self {
169 available_dirsize: parser.dirsize,
170 dir_offset: ZBI_BOOTFS_HEADER_SIZE as u32,
171 entry_index: 0,
172 errored: false,
173 zero_copy,
174 parser,
175 }
176 }
177}
178
179impl<'parser> Iterator for BootfsParserIterator<'parser> {
180 type Item = Result<BootfsEntry, BootfsParserError>;
181
182 fn next(&mut self) -> Option<Self::Item> {
183 if self.available_dirsize <= ZBI_BOOTFS_DIRENT_SIZE as u32 || self.errored {
184 return None;
185 }
186
187 let mut name_len_buf = [0; size_of::<u32>()];
189 if let Err(status) = self.parser.vmo.read(&mut name_len_buf, self.dir_offset.into()) {
190 self.errored = true;
191 return Some(Err(BootfsParserError::FailedToReadPayload { status }));
192 }
193
194 let name_len = LittleEndian::read_u32(&name_len_buf);
195 if name_len < 1 || name_len > ZBI_BOOTFS_MAX_NAME_LEN {
196 self.errored = true;
197 return Some(Err(BootfsParserError::InvalidNameLength {
198 entry_index: self.entry_index,
199 max_name_len: ZBI_BOOTFS_MAX_NAME_LEN,
200 name_len,
201 }));
202 }
203
204 let dirent_size = zbi_bootfs_dirent_size(name_len);
205 if dirent_size > self.available_dirsize {
206 self.errored = true;
207 return Some(Err(BootfsParserError::DirEntryTooBig {
208 dirsize: self.available_dirsize,
209 entry_index: self.entry_index,
210 }));
211 }
212
213 let mut dirent_buffer = vec![0; dirent_size as usize];
215 if let Err(status) = self.parser.vmo.read(&mut dirent_buffer, self.dir_offset.into()) {
216 self.errored = true;
217 return Some(Err(BootfsParserError::FailedToReadPayload { status }));
218 }
219
220 match ZbiBootfsDirent::parse(&dirent_buffer[..]) {
221 Ok(dirent) => {
222 let mut payload = None;
224 let offset: u64 = zbi_bootfs_page_align(dirent.data_off()).into();
225 let size: u64 = dirent.data_len().into();
226
227 if !self.zero_copy {
228 let buffer_size = usize::try_from(size).unwrap_or_else(|_| {
229 self.errored = true;
230 return 0;
231 });
232 let mut buffer = vec![0; buffer_size];
233 if let Err(status) = self.parser.vmo.read(&mut buffer, offset) {
234 self.errored = true;
235 return Some(Err(BootfsParserError::FailedToReadPayload { status }));
236 }
237
238 payload = Some(buffer);
239 }
240
241 self.dir_offset += dirent_buffer.len() as u32;
242 self.available_dirsize -= dirent_size;
243 self.entry_index += 1;
244
245 Some(dirent.name().map(|name| BootfsEntry {
246 name: name.to_owned(),
247 offset,
248 size,
249 payload,
250 }))
251 }
252 Err(err) => {
253 self.errored = true;
254 Some(Err(err))
255 }
256 }
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263 use anyhow::Error;
264 use std::collections::HashMap;
265 use std::fs::File;
266 use std::io::prelude::*;
267 use std::sync::LazyLock;
268 use zx::HandleBased;
269
270 static GOLDEN_DIR: &str = "/pkg/data/golden/";
271 static BASIC_BOOTFS_UNCOMPRESSED_FILE: &str = "/pkg/data/basic.bootfs.uncompressed";
272
273 fn read_file_into_hashmap(dir: &str, filename: &str, map: &mut HashMap<String, Vec<u8>>) {
274 let mut file_buffer = Vec::new();
275 let path = format!("{}{}", dir, filename);
276
277 File::open(&path)
278 .unwrap_or_else(|e| panic!("Failed to open file {}: {:?}", &path, e))
279 .read_to_end(&mut file_buffer)
280 .unwrap_or_else(|e| panic!("Failed to read file {}: {:?}", &path, e));
281 map.insert(filename.to_string(), file_buffer);
282 }
283
284 static GOLDEN_FILES: LazyLock<HashMap<String, Vec<u8>>> = LazyLock::new(|| {
285 let mut m = HashMap::new();
286 read_file_into_hashmap(GOLDEN_DIR, "dir/empty", &mut m);
287 read_file_into_hashmap(GOLDEN_DIR, "dir/lorem.txt", &mut m);
288 read_file_into_hashmap(GOLDEN_DIR, "dir/simple-copy.txt", &mut m);
289 read_file_into_hashmap(GOLDEN_DIR, "empty", &mut m);
290 read_file_into_hashmap(GOLDEN_DIR, "random.dat", &mut m);
291 read_file_into_hashmap(GOLDEN_DIR, "simple.txt", &mut m);
292 m
293 });
294
295 fn read_file_to_vmo(path: &str) -> Result<zx::Vmo, Error> {
296 let mut file_buffer = Vec::new();
297 File::open(path)?.read_to_end(&mut file_buffer)?;
298
299 let vmo = zx::Vmo::create(file_buffer.len() as u64)?;
300 vmo.write(&file_buffer, 0)?;
301 Ok(vmo)
302 }
303
304 #[test]
305 fn dirent_from_raw_fails_on_bad_cstring() {
306 const NAME_LEN: u8 = 3;
307 let mut dirent_buf = [0; ZBI_BOOTFS_DIRENT_SIZE + NAME_LEN as usize];
308
309 dirent_buf[0] = NAME_LEN;
310 dirent_buf[ZBI_BOOTFS_DIRENT_SIZE] = 'o' as u8;
311 dirent_buf[ZBI_BOOTFS_DIRENT_SIZE + 1] = 'k' as u8;
312 dirent_buf[ZBI_BOOTFS_DIRENT_SIZE + 2] = 'a' as u8;
314
315 let dirent = ZbiBootfsDirent::parse(&dirent_buf[..])
316 .expect("Failed to create ZbiBootfsDirent from raw buffer");
317 match dirent.name().unwrap_err() {
318 BootfsParserError::InvalidNameString { cause: _cause } => (),
319 _ => panic!("ZbiBootfsDirent.name did not fail with correct error"),
320 }
321 }
322
323 #[test]
324 fn dirent_from_raw_fails_on_non_utf8_string() {
325 const NAME_LEN: u8 = 3;
326 let mut dirent_buf = [0; ZBI_BOOTFS_DIRENT_SIZE + NAME_LEN as usize];
327
328 dirent_buf[0] = NAME_LEN;
330 dirent_buf[ZBI_BOOTFS_DIRENT_SIZE] = 0xC3;
331 dirent_buf[ZBI_BOOTFS_DIRENT_SIZE + 1] = 0x28;
332 dirent_buf[ZBI_BOOTFS_DIRENT_SIZE + 2] = '\0' as u8;
333
334 let char_sequence = &dirent_buf[ZBI_BOOTFS_DIRENT_SIZE..dirent_buf.len()];
336 assert_eq!(true, String::from_utf8(char_sequence.to_vec()).is_err());
337
338 let dirent = ZbiBootfsDirent::parse(&dirent_buf[..])
339 .expect("Failed to create ZbiBootfsDirent from raw buffer");
340 match dirent.name().unwrap_err() {
341 BootfsParserError::InvalidNameFormat { cause: _cause } => (),
342 _ => panic!("ZbiBootfsDirent.name did not fail with correct error"),
343 }
344 }
345
346 #[test]
347 fn create_bootfs_parser() {
348 let vmo = read_file_to_vmo(BASIC_BOOTFS_UNCOMPRESSED_FILE).unwrap();
349 assert_eq!(true, BootfsParser::create_from_vmo(vmo).is_ok());
350 }
351
352 #[test]
353 fn process_basic_bootfs() {
354 let vmo = read_file_to_vmo(BASIC_BOOTFS_UNCOMPRESSED_FILE).unwrap();
355 let parser = BootfsParser::create_from_vmo(vmo).expect("Failed to read bootfs file");
356
357 let mut files = Box::new(HashMap::new());
358
359 parser.iter().for_each(|result| {
360 let result = result.expect("Failed to process bootfs payload");
361 let BootfsEntry { name, payload, .. } = result;
362 files.insert(name, payload.unwrap());
363 });
364
365 assert_eq!(*GOLDEN_FILES, *files);
366 }
367
368 #[test]
369 fn process_bootfs_zero_copy() {
370 let vmo = read_file_to_vmo(BASIC_BOOTFS_UNCOMPRESSED_FILE).unwrap();
371 let vmo_dup =
372 vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("Failed to duplicate vmo");
373
374 let mut files = Box::new(HashMap::new());
375
376 let parser = BootfsParser::create_from_vmo(vmo_dup).expect("Failed to read bootfs file");
377 parser.zero_copy_iter().for_each(|result| {
378 let result = result.expect("Failed to process bootfs payload");
379 let BootfsEntry { name, offset, size, payload } = result;
380
381 assert!(payload.is_none());
384
385 let buffer_size = usize::try_from(size).unwrap();
386 let mut bytes = vec![0; buffer_size];
387 vmo.read(&mut bytes, offset).expect("Failed to read data from the vmo");
388
389 files.insert(name, bytes);
390 });
391
392 assert_eq!(*GOLDEN_FILES, *files);
393 }
394
395 #[test]
396 fn process_bootfs_with_invalid_header() {
397 let vmo = read_file_to_vmo(BASIC_BOOTFS_UNCOMPRESSED_FILE).unwrap();
398 let new_header = [0; ZBI_BOOTFS_HEADER_SIZE];
399
400 vmo.write(&new_header, 0).expect("Failed to wipe bootfs header");
402
403 match BootfsParser::create_from_vmo(vmo).unwrap_err() {
404 BootfsParserError::BadMagic => (),
405 _ => panic!("BootfsParser::create_from_vmo did not fail with correct error"),
406 }
407 }
408
409 #[test]
410 fn process_bootfs_with_invalid_direntry() {
411 let vmo = read_file_to_vmo(BASIC_BOOTFS_UNCOMPRESSED_FILE).unwrap();
412 let new_header = [0; ZBI_BOOTFS_DIRENT_SIZE];
413
414 vmo.write(&new_header, ZBI_BOOTFS_HEADER_SIZE as u64).expect("Failed to wipe direntry");
417
418 let parser = BootfsParser::create_from_vmo(vmo).expect("Failed to create BootfsParser");
419 parser.iter().for_each(|result| match result.unwrap_err() {
420 BootfsParserError::InvalidNameLength { entry_index, max_name_len, name_len } => {
421 assert_eq!(0, entry_index);
422 assert_eq!(ZBI_BOOTFS_MAX_NAME_LEN, max_name_len);
423 assert_eq!(0, name_len);
424 }
425 _ => panic!("parser did not fail with correct error"),
426 });
427 }
428
429 #[test]
430 fn process_bootfs_undersized_dirsize() {
431 let vmo = read_file_to_vmo(BASIC_BOOTFS_UNCOMPRESSED_FILE).unwrap();
432 let new_header = [(ZBI_BOOTFS_DIRENT_SIZE + 1) as u8, 0, 0, 0];
433
434 vmo.write(&new_header, size_of::<u32>() as u64).expect("Failed to change dirsize");
437
438 let parser = BootfsParser::create_from_vmo(vmo).expect("Failed to create BootfsParser");
439 parser.iter().for_each(|result| match result.unwrap_err() {
440 BootfsParserError::DirEntryTooBig { entry_index, dirsize } => {
441 assert_eq!(0, entry_index);
442 assert_eq!(ZBI_BOOTFS_DIRENT_SIZE + 1, dirsize as usize);
443 }
444 _ => panic!("parser did not fail with correct error"),
445 });
446 }
447}