1pub mod bootfs;
6
7use bootfs::{
8 zbi_bootfs_dirent_t, zbi_bootfs_header_t, ZBI_BOOTFS_MAGIC, ZBI_BOOTFS_MAX_NAME_LEN,
9 ZBI_BOOTFS_PAGE_SIZE,
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 lazy_static::lazy_static;
265 use std::collections::HashMap;
266 use std::fs::File;
267 use std::io::prelude::*;
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 lazy_static! {
285 static ref GOLDEN_FILES: HashMap<String, Vec<u8>> = {
286 let mut m = HashMap::new();
287 read_file_into_hashmap(GOLDEN_DIR, "dir/empty", &mut m);
288 read_file_into_hashmap(GOLDEN_DIR, "dir/lorem.txt", &mut m);
289 read_file_into_hashmap(GOLDEN_DIR, "dir/simple-copy.txt", &mut m);
290 read_file_into_hashmap(GOLDEN_DIR, "empty", &mut m);
291 read_file_into_hashmap(GOLDEN_DIR, "random.dat", &mut m);
292 read_file_into_hashmap(GOLDEN_DIR, "simple.txt", &mut m);
293 m
294 };
295 }
296
297 fn read_file_to_vmo(path: &str) -> Result<zx::Vmo, Error> {
298 let mut file_buffer = Vec::new();
299 File::open(path)?.read_to_end(&mut file_buffer)?;
300
301 let vmo = zx::Vmo::create(file_buffer.len() as u64)?;
302 vmo.write(&file_buffer, 0)?;
303 Ok(vmo)
304 }
305
306 #[test]
307 fn dirent_from_raw_fails_on_bad_cstring() {
308 const NAME_LEN: u8 = 3;
309 let mut dirent_buf = [0; ZBI_BOOTFS_DIRENT_SIZE + NAME_LEN as usize];
310
311 dirent_buf[0] = NAME_LEN;
312 dirent_buf[ZBI_BOOTFS_DIRENT_SIZE] = 'o' as u8;
313 dirent_buf[ZBI_BOOTFS_DIRENT_SIZE + 1] = 'k' as u8;
314 dirent_buf[ZBI_BOOTFS_DIRENT_SIZE + 2] = 'a' as u8;
316
317 let dirent = ZbiBootfsDirent::parse(&dirent_buf[..])
318 .expect("Failed to create ZbiBootfsDirent from raw buffer");
319 match dirent.name().unwrap_err() {
320 BootfsParserError::InvalidNameString { cause: _cause } => (),
321 _ => panic!("ZbiBootfsDirent.name did not fail with correct error"),
322 }
323 }
324
325 #[test]
326 fn dirent_from_raw_fails_on_non_utf8_string() {
327 const NAME_LEN: u8 = 3;
328 let mut dirent_buf = [0; ZBI_BOOTFS_DIRENT_SIZE + NAME_LEN as usize];
329
330 dirent_buf[0] = NAME_LEN;
332 dirent_buf[ZBI_BOOTFS_DIRENT_SIZE] = 0xC3;
333 dirent_buf[ZBI_BOOTFS_DIRENT_SIZE + 1] = 0x28;
334 dirent_buf[ZBI_BOOTFS_DIRENT_SIZE + 2] = '\0' as u8;
335
336 let char_sequence = &dirent_buf[ZBI_BOOTFS_DIRENT_SIZE..dirent_buf.len()];
338 assert_eq!(true, String::from_utf8(char_sequence.to_vec()).is_err());
339
340 let dirent = ZbiBootfsDirent::parse(&dirent_buf[..])
341 .expect("Failed to create ZbiBootfsDirent from raw buffer");
342 match dirent.name().unwrap_err() {
343 BootfsParserError::InvalidNameFormat { cause: _cause } => (),
344 _ => panic!("ZbiBootfsDirent.name did not fail with correct error"),
345 }
346 }
347
348 #[test]
349 fn create_bootfs_parser() {
350 let vmo = read_file_to_vmo(BASIC_BOOTFS_UNCOMPRESSED_FILE).unwrap();
351 assert_eq!(true, BootfsParser::create_from_vmo(vmo).is_ok());
352 }
353
354 #[test]
355 fn process_basic_bootfs() {
356 let vmo = read_file_to_vmo(BASIC_BOOTFS_UNCOMPRESSED_FILE).unwrap();
357 let parser = BootfsParser::create_from_vmo(vmo).expect("Failed to read bootfs file");
358
359 let mut files = Box::new(HashMap::new());
360
361 parser.iter().for_each(|result| {
362 let result = result.expect("Failed to process bootfs payload");
363 let BootfsEntry { name, payload, .. } = result;
364 files.insert(name, payload.unwrap());
365 });
366
367 assert_eq!(*GOLDEN_FILES, *files);
368 }
369
370 #[test]
371 fn process_bootfs_zero_copy() {
372 let vmo = read_file_to_vmo(BASIC_BOOTFS_UNCOMPRESSED_FILE).unwrap();
373 let vmo_dup =
374 vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("Failed to duplicate vmo");
375
376 let mut files = Box::new(HashMap::new());
377
378 let parser = BootfsParser::create_from_vmo(vmo_dup).expect("Failed to read bootfs file");
379 parser.zero_copy_iter().for_each(|result| {
380 let result = result.expect("Failed to process bootfs payload");
381 let BootfsEntry { name, offset, size, payload } = result;
382
383 assert!(payload.is_none());
386
387 let buffer_size = usize::try_from(size).unwrap();
388 let mut bytes = vec![0; buffer_size];
389 vmo.read(&mut bytes, offset).expect("Failed to read data from the vmo");
390
391 files.insert(name, bytes);
392 });
393
394 assert_eq!(*GOLDEN_FILES, *files);
395 }
396
397 #[test]
398 fn process_bootfs_with_invalid_header() {
399 let vmo = read_file_to_vmo(BASIC_BOOTFS_UNCOMPRESSED_FILE).unwrap();
400 let new_header = [0; ZBI_BOOTFS_HEADER_SIZE];
401
402 vmo.write(&new_header, 0).expect("Failed to wipe bootfs header");
404
405 match BootfsParser::create_from_vmo(vmo).unwrap_err() {
406 BootfsParserError::BadMagic => (),
407 _ => panic!("BootfsParser::create_from_vmo did not fail with correct error"),
408 }
409 }
410
411 #[test]
412 fn process_bootfs_with_invalid_direntry() {
413 let vmo = read_file_to_vmo(BASIC_BOOTFS_UNCOMPRESSED_FILE).unwrap();
414 let new_header = [0; ZBI_BOOTFS_DIRENT_SIZE];
415
416 vmo.write(&new_header, ZBI_BOOTFS_HEADER_SIZE as u64).expect("Failed to wipe direntry");
419
420 let parser = BootfsParser::create_from_vmo(vmo).expect("Failed to create BootfsParser");
421 parser.iter().for_each(|result| match result.unwrap_err() {
422 BootfsParserError::InvalidNameLength { entry_index, max_name_len, name_len } => {
423 assert_eq!(0, entry_index);
424 assert_eq!(ZBI_BOOTFS_MAX_NAME_LEN, max_name_len);
425 assert_eq!(0, name_len);
426 }
427 _ => panic!("parser did not fail with correct error"),
428 });
429 }
430
431 #[test]
432 fn process_bootfs_undersized_dirsize() {
433 let vmo = read_file_to_vmo(BASIC_BOOTFS_UNCOMPRESSED_FILE).unwrap();
434 let new_header = [(ZBI_BOOTFS_DIRENT_SIZE + 1) as u8, 0, 0, 0];
435
436 vmo.write(&new_header, size_of::<u32>() as u64).expect("Failed to change dirsize");
439
440 let parser = BootfsParser::create_from_vmo(vmo).expect("Failed to create BootfsParser");
441 parser.iter().for_each(|result| match result.unwrap_err() {
442 BootfsParserError::DirEntryTooBig { entry_index, dirsize } => {
443 assert_eq!(0, entry_index);
444 assert_eq!(ZBI_BOOTFS_DIRENT_SIZE + 1, dirsize as usize);
445 }
446 _ => panic!("parser did not fail with correct error"),
447 });
448 }
449}