fuchsia_zbi/
lib.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use fuchsia_zbi_abi::{ZBI_ALIGNMENT_BYTES, ZBI_FLAGS_CRC32};
6
7use lazy_static::lazy_static;
8use log::info;
9use std::collections::{HashMap, HashSet};
10use std::mem::size_of;
11use thiserror::Error;
12use zerocopy::Ref;
13
14pub use fuchsia_zbi_abi::{
15    zbi_container_header, zbi_header_t, ZbiType, ZBI_CONTAINER_MAGIC, ZBI_FLAGS_VERSION,
16    ZBI_ITEM_MAGIC, ZBI_ITEM_NO_CRC32,
17};
18
19const ZBI_HEADER_SIZE: usize = size_of::<zbi_header_t>();
20
21lazy_static! {
22    static ref PAGE_SIZE: u32 = zx::system_get_page_size();
23}
24
25#[derive(Debug, Error, Eq, PartialEq)]
26pub enum ZbiParserError {
27    #[error("Failed to read {} bytes at offset {}: {}", size, offset, status)]
28    FailedToReadPayload { size: usize, offset: u32, status: zx::Status },
29
30    #[error("Failed to zero {} bytes at offset {}: {}", size, offset, status)]
31    FailedToZeroMemory { size: usize, offset: u32, status: zx::Status },
32
33    #[error("Failed to parse bytes as an unaligned zbi_header_t")]
34    FailedToParseHeader,
35
36    #[error("Failed to validate header, magic was {} but expected {}", actual, ZBI_ITEM_MAGIC)]
37    InvalidHeaderMagic { actual: u32 },
38
39    #[error(
40        "Failed to validate container header, type was {:#?} but expected {:#?}",
41        zbi_type,
42        ZbiType::Container
43    )]
44    InvalidContainerHeaderType { zbi_type: ZbiType },
45
46    #[error(
47        "Failed to validate container header, extra magic was {} but expected {}",
48        actual,
49        ZBI_CONTAINER_MAGIC
50    )]
51    InvalidContainerHeaderExtraMagic { actual: u32 },
52
53    #[error("Header flags {:#b} missing flag version {:#b}", flags, ZBI_FLAGS_VERSION)]
54    MissingZbiVersionFlag { flags: u32 },
55
56    #[error("ZBI header contains a bad CRC32")]
57    BadCRC32,
58
59    #[error("{:?} was not found in the ZBI", zbi_type)]
60    ItemNotFound { zbi_type: ZbiType },
61
62    #[error(
63        "{:?} is not being stored by this parser configuration, and will never be present",
64        zbi_type
65    )]
66    ItemNotStored { zbi_type: ZbiType },
67
68    #[error("{:?} with an extra value of {} was not found in the ZBI", zbi_type, extra)]
69    ItemWithExtraNotFound { zbi_type: ZbiType, extra: u32 },
70
71    #[error("Failed to decommit pages: {}", status)]
72    FailedToDecommitPages { status: zx::Status },
73
74    #[error("ZBI parser bailed to prevent a u32 overflow.")]
75    Overflow,
76
77    #[error("Unknown error in the ZBI parser!")]
78    Unknown,
79}
80
81#[derive(Debug, PartialEq)]
82pub struct DecommitRange {
83    start: u32,
84    end: u32,
85}
86
87#[derive(Debug, Clone, Copy)]
88pub struct ZbiItem {
89    header_offset: u32,
90    item_offset: u32,
91    item_length: u32,
92    extra: u32,
93    raw_type: u32,
94}
95
96#[derive(Debug, PartialEq)]
97pub struct ZbiResult {
98    pub bytes: Vec<u8>,
99    pub extra: u32, // Optional metadata that can be used to identify ZBI items with the same type.
100}
101
102#[derive(Debug)]
103pub struct ZbiParser {
104    vmo: zx::Vmo,
105    parsed: bool,
106    items: HashMap<ZbiType, Vec<ZbiItem>>,
107    items_to_store: HashSet<ZbiType>,
108    decommit_ranges: Vec<DecommitRange>,
109}
110
111impl ZbiParser {
112    // ZBI items are padded to 8 byte boundaries.
113    pub fn align_zbi_item(length: u32) -> Result<u32, ZbiParserError> {
114        let rem = length % ZBI_ALIGNMENT_BYTES;
115        if rem > 0 {
116            length.checked_add(ZBI_ALIGNMENT_BYTES - rem).ok_or(ZbiParserError::Overflow)
117        } else {
118            Ok(length)
119        }
120    }
121
122    // Get the address of the next page. This will be replaced by the nightly
123    // `checked_next_multiple_of` when available.
124    fn round_up_to_page(address: u32) -> Result<u32, ZbiParserError> {
125        Ok(ZbiParser::round_down_to_page(
126            address.checked_add(*PAGE_SIZE - 1).ok_or(ZbiParserError::Overflow)?,
127        ))
128    }
129
130    // Get the address of the previous page. This will be replaced by the nightly
131    // `checked_next_multiple_of` when available.
132    fn round_down_to_page(address: u32) -> u32 {
133        address - (address % *PAGE_SIZE)
134    }
135
136    fn get_header<'a>(
137        &self,
138        bytes: &'a [u8],
139    ) -> Result<(ZbiType, Ref<&'a [u8], zbi_header_t>), ZbiParserError> {
140        let header = Ref::<&[u8], zbi_header_t>::from_bytes(&bytes[..])
141            .map_err(Into::into)
142            .map_err(|_: zerocopy::SizeError<_, _>| ZbiParserError::FailedToParseHeader)?;
143
144        if header.magic.get() != ZBI_ITEM_MAGIC {
145            return Err(ZbiParserError::InvalidHeaderMagic { actual: header.magic.get() });
146        }
147
148        if header.flags.get() & ZBI_FLAGS_VERSION == 0 {
149            return Err(ZbiParserError::MissingZbiVersionFlag { flags: header.flags.get() });
150        }
151
152        if (header.flags.get() & ZBI_FLAGS_CRC32 == 0) && (header.crc32.get() != ZBI_ITEM_NO_CRC32)
153        {
154            return Err(ZbiParserError::BadCRC32);
155        }
156
157        Ok((ZbiType::from_raw(header.zbi_type.get()), header))
158    }
159
160    fn should_store_item(&self, zbi_type: ZbiType) -> bool {
161        if self.items_to_store.is_empty() {
162            // If there isn't a list of items to store, it means store every item found
163            // in the ZBI unless the type is unknown.
164            zbi_type != ZbiType::Unknown
165        } else {
166            self.items_to_store.contains(&zbi_type)
167        }
168    }
169
170    fn decommit_range(&mut self, start: u32, end: u32) -> Result<(), ZbiParserError> {
171        let start = ZbiParser::round_up_to_page(start)?;
172        let end = ZbiParser::round_down_to_page(end);
173
174        if start < end {
175            self.vmo
176                .op_range(zx::VmoOp::DECOMMIT, start.into(), (end - start).into())
177                .map_err(|status| ZbiParserError::FailedToDecommitPages { status })?;
178            self.decommit_ranges.push(DecommitRange { start, end });
179            info!("[ZBI Parser] Decommitted BOOTDATA VMO from {:x} to {:x}", start, end);
180        }
181
182        Ok(())
183    }
184
185    fn get_zbi_result(
186        &self,
187        zbi_type: ZbiType,
188        zbi_item: &ZbiItem,
189    ) -> Result<ZbiResult, ZbiParserError> {
190        // StorageRamdisk item users currently expect the header to be contained in the item
191        // result.
192        // TODO(https://fxbug.dev/42174998): Remove special cases for StorageRamdisk.
193        let (length, offset) = if zbi_type == ZbiType::StorageRamdisk {
194            (ZBI_HEADER_SIZE + zbi_item.item_length as usize, zbi_item.header_offset)
195        } else {
196            (zbi_item.item_length as usize, zbi_item.item_offset)
197        };
198
199        let mut bytes = vec![0; length];
200        self.vmo.read(&mut bytes, offset.into()).map_err(|status| {
201            ZbiParserError::FailedToReadPayload { size: bytes.len(), offset, status }
202        })?;
203
204        Ok(ZbiResult { bytes, extra: zbi_item.extra })
205    }
206
207    pub fn new(vmo: zx::Vmo) -> ZbiParser {
208        Self {
209            vmo,
210            parsed: false,
211            items: HashMap::new(),
212            items_to_store: HashSet::new(),
213            decommit_ranges: Vec::new(),
214        }
215    }
216
217    /// Set a ZBI item type as should be stored. If no item types are set to store, then all
218    /// known items are stored.
219    pub fn set_store_item(mut self, zbi_type: ZbiType) -> Self {
220        assert!(
221            ZbiType::from_raw(zbi_type as u32) != ZbiType::Unknown,
222            "You must add a u32 -> ZbiType mapping for any new item you wish to store"
223        );
224        self.items_to_store.insert(zbi_type);
225        self
226    }
227
228    /// Try and get one stored item type, optionally restricted by the item's extra. The raw type
229    /// is passed to allow differentiating between different types of driver metadata.
230    pub fn try_get_item(
231        &self,
232        zbi_type_raw: u32,
233        extra: Option<u32>,
234    ) -> Result<Vec<ZbiResult>, ZbiParserError> {
235        let zbi_type = ZbiType::from_raw(zbi_type_raw);
236        if !self.items_to_store.is_empty() && !self.items_to_store.contains(&zbi_type) {
237            // The ZBI parser is only storing select items (and decommitting the rest), but
238            // the requested item is not being stored, and so will never be present.
239            return Err(ZbiParserError::ItemNotStored { zbi_type });
240        }
241
242        if !self.items.contains_key(&zbi_type) {
243            return Err(ZbiParserError::ItemNotFound { zbi_type });
244        }
245
246        let want_item = |item: &ZbiItem| -> bool {
247            // If the ZBI type can't be losslessly converted back to the raw type, this is a
248            // driver metadata subtype and the raw type of the item must match.
249            if zbi_type.into_raw() != zbi_type_raw && item.raw_type != zbi_type_raw {
250                return false;
251            }
252
253            // The extra is a way differentiate user defined "types" of a given ZBI type.
254            if let Some(extra) = extra {
255                if extra != item.extra {
256                    return false;
257                }
258            }
259
260            true
261        };
262
263        let mut result: Vec<ZbiResult> = Vec::new();
264        for item in &self.items[&zbi_type] {
265            if want_item(item) {
266                result.push(self.get_zbi_result(zbi_type, &item)?);
267            }
268        }
269
270        Ok(result)
271    }
272
273    /// Helper function to return the last item matching a given type and extra value. This avoids
274    /// reading unwanted results from the underlying VMO. The raw type is passed to allow
275    /// differentiating between different types of driver metadata.
276    pub fn try_get_last_matching_item(
277        &self,
278        zbi_type_raw: u32,
279        extra: u32,
280    ) -> Result<ZbiResult, ZbiParserError> {
281        let zbi_type = ZbiType::from_raw(zbi_type_raw);
282        if !self.items.contains_key(&zbi_type) {
283            return Err(ZbiParserError::ItemNotFound { zbi_type });
284        }
285
286        // TODO(https://fxbug.dev/42109921): The ZBI spec doesn't require (type, extra) to be a unique key, so
287        // follow the existing convention of giving the last occurrence priority.
288        for item in self.items[&zbi_type].iter().rev() {
289            if item.extra == extra && item.raw_type == zbi_type_raw {
290                return Ok(self.get_zbi_result(zbi_type, &item)?);
291            }
292        }
293
294        Err(ZbiParserError::ItemWithExtraNotFound { zbi_type, extra })
295    }
296
297    /// Get all stored ZBI items.
298    pub fn get_items(&self) -> Result<HashMap<ZbiType, Vec<ZbiResult>>, ZbiParserError> {
299        let mut result = HashMap::new();
300        for key in self.items.keys() {
301            result.insert(key.clone(), self.try_get_item(key.into_raw(), None)?);
302        }
303        Ok(result)
304    }
305
306    /// Release an item type, zeroing the VMO memory and decommitting the pages if possible.
307    pub fn release_item(&mut self, zbi_type: ZbiType) -> Result<(), ZbiParserError> {
308        if !self.items.contains_key(&zbi_type) {
309            return Err(ZbiParserError::ItemNotFound { zbi_type });
310        }
311
312        let mut possible_decommit_range = Vec::new();
313        for item in &self.items[&zbi_type] {
314            let length = u32::try_from(ZBI_HEADER_SIZE).map_err(|_| ZbiParserError::Unknown)?
315                + item.item_length;
316            self.vmo.op_range(zx::VmoOp::ZERO, item.header_offset.into(), length.into()).map_err(
317                |status| ZbiParserError::FailedToZeroMemory {
318                    size: length as usize,
319                    offset: item.header_offset,
320                    status,
321                },
322            )?;
323
324            possible_decommit_range.push(DecommitRange {
325                start: item.header_offset,
326                end: item.item_offset + item.item_length,
327            });
328        }
329
330        self.items.remove(&zbi_type);
331        let on_same_page = |start: u32, end: u32| -> Result<bool, ZbiParserError> {
332            // Start is inclusive, but end is exclusive. That means that an item ending on
333            // PAGE_SIZE does not overlap with an item starting on PAGE_SIZE. We can trivially
334            // fix that during this calculation by subtracting 1 from end to bring it into an
335            // inclusive range.
336            let end = end.checked_sub(1).ok_or(ZbiParserError::Overflow)?;
337            Ok((start / *PAGE_SIZE) == (end / *PAGE_SIZE))
338        };
339
340        for mut decommit_range in possible_decommit_range {
341            let mut adjusted_end = false;
342            let mut adjusted_start = false;
343            'zbi_key_loop: for items in self.items.values() {
344                for item in items {
345                    // If any other item starts or ends on the same pages as our range, retreat
346                    // the range one page away. Either we cross ourselves and know we have nothing
347                    // to decommit, or we have retreated into the bounds of this single item.
348                    let item_start = item.header_offset;
349                    let item_end = item.item_offset + item.item_length;
350
351                    if !adjusted_start && on_same_page(decommit_range.start, item_end)? {
352                        // An item ends on the same page this item starts.
353                        adjusted_start = true;
354                        decommit_range.start = decommit_range
355                            .start
356                            .checked_add(*PAGE_SIZE)
357                            .ok_or(ZbiParserError::Overflow)?;
358                    }
359
360                    if !adjusted_end && on_same_page(item_start, decommit_range.end)? {
361                        // An item starts on the same page this item ends.
362                        adjusted_end = true;
363                        decommit_range.end =
364                            decommit_range.end.checked_sub(*PAGE_SIZE).unwrap_or(0);
365                    }
366
367                    if adjusted_start && adjusted_end {
368                        break 'zbi_key_loop;
369                    }
370                }
371            }
372
373            // The previous loop verified that no other items start or end on the current page
374            // (they might have already been decomitted), so it's safe to extend the start and
375            // end ranges to the limit of their respective pages.
376            decommit_range.end = ZbiParser::round_up_to_page(decommit_range.end)?;
377            decommit_range.start = ZbiParser::round_down_to_page(decommit_range.start);
378
379            self.decommit_range(decommit_range.start, decommit_range.end)?;
380        }
381
382        Ok(())
383    }
384
385    /// Parse a ZBI VMO, storing the offset for each item. If `set_store_item` was used, only
386    /// those item types will be stored, and the other item types will be zeroed and decommitted.
387    pub fn parse(mut self) -> Result<Self, ZbiParserError> {
388        // The ZBI parser may decommit unused pages of memory, so trying to parse this ZBI twice
389        // will result in nonsense.
390        assert!(!self.parsed, "Parse should only be invoked once!");
391        self.parsed = true;
392
393        let mut vmo_offset: u32 = 0;
394        let mut header_bytes = [0; ZBI_HEADER_SIZE];
395
396        self.vmo.read(&mut header_bytes, vmo_offset.into()).map_err(|status| {
397            ZbiParserError::FailedToReadPayload {
398                size: header_bytes.len(),
399                offset: vmo_offset,
400                status,
401            }
402        })?;
403
404        let (zbi_type, header) = self.get_header(&header_bytes)?;
405        if zbi_type != ZbiType::Container {
406            return Err(ZbiParserError::InvalidContainerHeaderType { zbi_type });
407        }
408        if header.extra.get() != ZBI_CONTAINER_MAGIC {
409            return Err(ZbiParserError::InvalidContainerHeaderExtraMagic {
410                actual: header.extra.get(),
411            });
412        }
413
414        // Cast this to a u32 once for convenience.
415        let header_offset = u32::try_from(ZBI_HEADER_SIZE).map_err(|_| ZbiParserError::Unknown)?;
416
417        vmo_offset = header_offset;
418        let mut remaining_length = header.length.get();
419
420        let mut decommit_start = 0;
421        let mut decommit_end = 0;
422
423        while remaining_length > header_offset {
424            let mut header_bytes = [0; ZBI_HEADER_SIZE];
425            let current_offset = vmo_offset;
426            self.vmo.read(&mut header_bytes, current_offset.into()).map_err(|status| {
427                ZbiParserError::FailedToReadPayload {
428                    size: header_bytes.len(),
429                    offset: vmo_offset,
430                    status,
431                }
432            })?;
433
434            let (zbi_type, header) = self.get_header(&header_bytes)?;
435            let next_item = ZbiParser::align_zbi_item(
436                header_offset.checked_add(header.length.get()).ok_or(ZbiParserError::Overflow)?,
437            )?;
438
439            vmo_offset = vmo_offset.checked_add(next_item).ok_or(ZbiParserError::Overflow)?;
440            remaining_length =
441                remaining_length.checked_sub(next_item).ok_or(ZbiParserError::Overflow)?;
442
443            if self.should_store_item(zbi_type) {
444                let entry = self.items.entry(zbi_type).or_insert(Vec::new());
445                // TODO(https://fxbug.dev/42174998): Remove special case for StorageRamdisk.
446                entry.push(ZbiItem {
447                    header_offset: current_offset,
448                    item_offset: current_offset + header_offset,
449                    item_length: header.length.get(),
450                    extra: if zbi_type == ZbiType::StorageRamdisk { 0 } else { header.extra.get() },
451                    raw_type: header.zbi_type.get(),
452                });
453
454                // If there is a decommit range, resolve it now so that it doesn't include this
455                // item, and then start the next range at the end of this item. If start >= end,
456                // nothing will be decommitted.
457                self.decommit_range(decommit_start, decommit_end)?;
458                decommit_start = vmo_offset;
459            } else {
460                // Extend the decommit range to cover this item.
461                decommit_end = vmo_offset;
462            }
463        }
464
465        if decommit_end > decommit_start {
466            // The last item was included in a decommit range, so the parser should extend the
467            // range up to the end of the current page.
468            decommit_end = ZbiParser::round_up_to_page(decommit_end)?;
469            self.decommit_range(decommit_start, decommit_end)?;
470        }
471
472        Ok(self)
473    }
474}
475#[cfg(test)]
476mod tests {
477    use super::*;
478    use anyhow::Error;
479    use zerocopy::byteorder::little_endian::U32;
480    use zerocopy::IntoBytes;
481
482    fn check_item_bytes(builder: &ZbiBuilder, parser: &ZbiParser) {
483        for (zbi_type, items) in &parser.items {
484            let expected = builder.get_bytes(items);
485            let actual = match parser.try_get_item(zbi_type.into_raw(), None) {
486                Ok(val) => val.iter().map(|result| result.bytes.clone()).collect(),
487                Err(_) => {
488                    assert!(false);
489                    Vec::new()
490                }
491            };
492            assert_eq!(expected, actual);
493        }
494    }
495
496    fn check_extracted_items(parser: &ZbiParser, expected: &[ZbiType]) {
497        let actual = parser.get_items().unwrap();
498        assert_eq!(actual.keys().len(), expected.len());
499        assert!(expected.iter().all(|k| { actual.contains_key(k) }));
500    }
501
502    struct ZbiBuilder {
503        zbi_bytes: Vec<u8>,
504    }
505
506    impl ZbiBuilder {
507        fn get_bytes(&self, zbi_items: &[ZbiItem]) -> Vec<&[u8]> {
508            zbi_items
509                .iter()
510                .map(|item| {
511                    let start = item.item_offset as usize;
512                    let end = (item.item_offset + item.item_length) as usize;
513
514                    &self.zbi_bytes[start..end]
515                })
516                .collect()
517        }
518
519        fn simple_header(zbi_type: ZbiType, length: u32) -> zbi_header_t {
520            zbi_header_t {
521                zbi_type: U32::new(zbi_type.into_raw()),
522                length: U32::new(length),
523                extra: U32::new(if zbi_type == ZbiType::Container {
524                    ZBI_CONTAINER_MAGIC
525                } else {
526                    0
527                }),
528                flags: U32::new(ZBI_FLAGS_VERSION),
529                reserved_0: U32::new(0),
530                reserved_1: U32::new(0),
531                magic: U32::new(ZBI_ITEM_MAGIC),
532                crc32: U32::new(ZBI_ITEM_NO_CRC32),
533            }
534        }
535
536        fn new() -> Self {
537            Self { zbi_bytes: Vec::new() }
538        }
539
540        fn add_header(mut self, header: zbi_header_t) -> Self {
541            let bytes = header.as_bytes();
542            self.zbi_bytes.extend(bytes);
543            self
544        }
545
546        fn add_item(mut self, length: u32) -> Self {
547            for i in 0..ZbiParser::align_zbi_item(length).unwrap() {
548                // Arbitrary values we can check later.
549                self.zbi_bytes.push((i % u8::MAX as u32).try_into().unwrap());
550            }
551            self
552        }
553
554        fn calculate_item_length(mut self) -> Self {
555            let item_length =
556                U32::new(u32::try_from(self.zbi_bytes.len() - ZBI_HEADER_SIZE).unwrap());
557            let item_length_bytes = item_length.as_bytes();
558
559            let mut i = 4usize;
560            for x in item_length_bytes {
561                self.zbi_bytes[i] = *x;
562                i += 1;
563            }
564
565            self
566        }
567
568        fn generate(self) -> Result<(zx::Vmo, Self), Error> {
569            let vmo = zx::Vmo::create(self.zbi_bytes.len().try_into()?)?;
570            vmo.write(&self.zbi_bytes, 0)?;
571            Ok((vmo, self))
572        }
573    }
574
575    #[fuchsia::test]
576    async fn empty_zbi() {
577        let (zbi, _builder) = ZbiBuilder::new().generate().expect("Failed to create ZBI");
578        let parser = ZbiParser::new(zbi).parse();
579
580        assert!(parser.is_err());
581        assert_eq!(
582            parser.unwrap_err(),
583            ZbiParserError::FailedToReadPayload {
584                size: ZBI_HEADER_SIZE,
585                offset: 0,
586                status: zx::Status::OUT_OF_RANGE
587            }
588        );
589    }
590
591    #[fuchsia::test]
592    async fn zero_content_zbi() {
593        let (zbi, _builder) = ZbiBuilder::new()
594            .add_header(zbi_container_header(0))
595            .generate()
596            .expect("Failed to create ZBI");
597        let parser = ZbiParser::new(zbi).parse().expect("Failed to parse ZBI");
598
599        assert_eq!(parser.items.len(), 0);
600    }
601
602    #[fuchsia::test]
603    async fn zbi_smaller_than_header() {
604        let mut builder =
605            ZbiBuilder::new().add_header(ZbiBuilder::simple_header(ZbiType::Container, 0));
606
607        // Truncate the header giving us an invalid header.
608        builder.zbi_bytes.resize(builder.zbi_bytes.len() / 2, 0);
609
610        let (zbi, _builder) = builder.generate().expect("Failed to create ZBI");
611        let parser = ZbiParser::new(zbi).parse();
612
613        // VMOs have size rounded up to the nearest page, so when our VMO doesn't contain
614        // a complete header we'll generally see invalid header magic instead of a read failure.
615        assert!(parser.is_err());
616        assert_eq!(parser.unwrap_err(), ZbiParserError::InvalidHeaderMagic { actual: 0 });
617    }
618
619    #[fuchsia::test]
620    async fn invalid_container_header_fields() {
621        let mut header = ZbiBuilder::simple_header(ZbiType::Container, 0);
622
623        // Remove the container specific magic field.
624        header.extra = U32::new(0);
625
626        let (zbi, _builder) =
627            ZbiBuilder::new().add_header(header).generate().expect("Failed to create ZBI");
628        let parser = ZbiParser::new(zbi).parse();
629
630        assert!(parser.is_err());
631        assert_eq!(
632            parser.unwrap_err(),
633            ZbiParserError::InvalidContainerHeaderExtraMagic { actual: 0 }
634        );
635
636        // The first header is the wrong type -- ZBI images start with a container type header
637        // describing the entire volume.
638        let (zbi, _builder) = ZbiBuilder::new()
639            .add_header(ZbiBuilder::simple_header(ZbiType::Crashlog, 0))
640            .generate()
641            .expect("failed to create zbi");
642        let parser = ZbiParser::new(zbi).parse();
643
644        assert!(parser.is_err());
645        assert_eq!(
646            parser.unwrap_err(),
647            ZbiParserError::InvalidContainerHeaderType { zbi_type: ZbiType::Crashlog }
648        );
649    }
650
651    #[fuchsia::test]
652    async fn invalid_header_flags() {
653        let mut header = ZbiBuilder::simple_header(ZbiType::Container, 0);
654
655        // Remove the required ZBI_FLAGS_VERSION flag.
656        header.flags = U32::new(0);
657
658        let (zbi, _builder) =
659            ZbiBuilder::new().add_header(header).generate().expect("Failed to create ZBI");
660        let parser = ZbiParser::new(zbi).parse();
661
662        assert!(parser.is_err());
663        assert_eq!(parser.unwrap_err(), ZbiParserError::MissingZbiVersionFlag { flags: 0 });
664
665        let mut header = ZbiBuilder::simple_header(ZbiType::Container, 0);
666
667        // Remove the required CRC32 disabled value.
668        header.crc32 = U32::new(0);
669
670        let (zbi, _builder) =
671            ZbiBuilder::new().add_header(header).generate().expect("failed to create zbi");
672        let parser = ZbiParser::new(zbi).parse();
673
674        assert!(parser.is_err());
675        assert_eq!(parser.unwrap_err(), ZbiParserError::BadCRC32);
676    }
677
678    #[fuchsia::test]
679    async fn zbi_item_overflows_u32() {
680        let (zbi, _builder) = ZbiBuilder::new()
681            .add_header(ZbiBuilder::simple_header(ZbiType::Container, 12345))
682            .add_header(ZbiBuilder::simple_header(ZbiType::Crashlog, u32::MAX))
683            .generate()
684            .expect("failed to create zbi");
685        let parser = ZbiParser::new(zbi).parse();
686
687        assert!(parser.is_err());
688        assert_eq!(parser.unwrap_err(), ZbiParserError::Overflow);
689    }
690
691    #[fuchsia::test]
692    async fn zbi_item_has_correct_extra_field() {
693        let mut crash_header = ZbiBuilder::simple_header(ZbiType::Crashlog, 0x80);
694        crash_header.extra = U32::new(0xABCD);
695
696        let (zbi, _builder) = ZbiBuilder::new()
697            .add_header(ZbiBuilder::simple_header(ZbiType::Container, 0))
698            .add_header(crash_header)
699            .add_item(0x80)
700            .calculate_item_length()
701            .generate()
702            .expect("failed to create zbi");
703        let parser = ZbiParser::new(zbi).parse().expect("Failed to parse ZBI");
704
705        let item = parser
706            .try_get_last_matching_item(ZbiType::Crashlog.into_raw(), 0xABCD)
707            .expect("Failed to get item");
708        assert_eq!(item.extra, 0xABCD);
709
710        assert_eq!(
711            parser.try_get_last_matching_item(ZbiType::Crashlog.into_raw(), 0xDEF).unwrap_err(),
712            ZbiParserError::ItemWithExtraNotFound { zbi_type: ZbiType::Crashlog, extra: 0xDEF }
713        );
714    }
715
716    #[fuchsia::test]
717    async fn zbi_items_have_eight_byte_alignment() {
718        let (zbi, builder) = ZbiBuilder::new()
719            .add_header(ZbiBuilder::simple_header(ZbiType::Container, 0))
720            .add_header(ZbiBuilder::simple_header(ZbiType::Crashlog, 25)) // Not 8 byte aligned.
721            .add_item(25)
722            .add_header(ZbiBuilder::simple_header(ZbiType::Cmdline, 32))
723            .add_item(32)
724            .calculate_item_length()
725            .generate()
726            .expect("failed to create zbi");
727        let parser = ZbiParser::new(zbi).parse().expect("Failed to parse ZBI");
728
729        let crash_items = &parser.items[&ZbiType::Crashlog];
730        assert!(crash_items.len() == 1);
731        let crash_item = &crash_items[0];
732
733        let cmdline_items = &parser.items[&ZbiType::Cmdline];
734        assert!(cmdline_items.len() == 1);
735        let cmdline_item = &cmdline_items[0];
736
737        // The cmdline item's header should be eight byte aligned, but also within eight bytes
738        // of where the crashlog item ends.
739        let crash_item_length = crash_item.item_offset + crash_item.item_length;
740        assert!(crash_item_length != cmdline_item.header_offset + 1);
741        assert!(cmdline_item.header_offset % 8 == 0);
742        assert!((8 - crash_item_length % 8) + crash_item_length == cmdline_item.header_offset);
743
744        check_item_bytes(&builder, &parser);
745    }
746
747    #[fuchsia::test]
748    async fn ramdisk_item_contains_header() {
749        // This is a special case which we really should remove. The fshost expects storage
750        // ramdisk items to contain the header, and zero for an extra. See https://fxbug.dev/42174998 for details.
751        let size = 0x40;
752        let mut ramdisk_header = ZbiBuilder::simple_header(ZbiType::StorageRamdisk, size);
753        ramdisk_header.extra = U32::new(0xABCD);
754
755        let (zbi, builder) = ZbiBuilder::new()
756            .add_header(ZbiBuilder::simple_header(ZbiType::Container, 0))
757            .add_header(ramdisk_header)
758            .add_item(size)
759            .calculate_item_length()
760            .generate()
761            .expect("failed to create zbi");
762
763        let parser = ZbiParser::new(zbi).parse().expect("Failed to parse ZBI");
764
765        // Extra has been set to zero, while it was 0xABCD in the ZBI.
766        let item = parser
767            .try_get_last_matching_item(ZbiType::StorageRamdisk.into_raw(), 0x0)
768            .expect("Failed to get item");
769
770        // Bytes include the header.
771        assert_eq!(item.bytes.len(), ZBI_HEADER_SIZE + size as usize);
772        let offset = parser.items[&ZbiType::StorageRamdisk][0].header_offset as usize;
773        assert_eq!(builder.zbi_bytes[offset..(offset + item.bytes.len())], item.bytes);
774    }
775
776    #[fuchsia::test]
777    async fn pages_with_only_filtered_items_decommitted() {
778        // In this test setup we have the following page allocation
779        // Page 0  : Container header
780        //         : Crashlog #1
781        //         : Crashlog #2 (Start)
782        // Page 1  : Crashlog #2 (End)
783        //         : Cmdline     (Start)
784        // Page 2-4: Cmdline     (Continued)
785        // Page 5  : Cmdline     (End)
786        //         : StorageBootfsFactory
787        //         : ImageArgs (Start)
788        // Page 6: : ImageArgs (End)
789        //
790        // As we want to save Crashlog and StorageBootfsFactory, only pages that are purely
791        // Cmdline or ImageArgs items can be decommitted.
792        let (zbi, builder) = ZbiBuilder::new()
793            .add_header(ZbiBuilder::simple_header(ZbiType::Container, 0))
794            .add_header(ZbiBuilder::simple_header(ZbiType::Crashlog, 2048))
795            .add_item(2048)
796            .add_header(ZbiBuilder::simple_header(ZbiType::Crashlog, 4096))
797            .add_item(4096)
798            .add_header(ZbiBuilder::simple_header(ZbiType::Cmdline, 16384))
799            .add_item(16384)
800            .add_header(ZbiBuilder::simple_header(ZbiType::StorageBootfsFactory, 250))
801            .add_item(250)
802            .add_header(ZbiBuilder::simple_header(ZbiType::ImageArgs, 2048))
803            .add_item(2048)
804            .calculate_item_length()
805            .generate()
806            .expect("failed to create zbi");
807        let parser = ZbiParser::new(zbi)
808            .set_store_item(ZbiType::Crashlog)
809            .set_store_item(ZbiType::StorageBootfsFactory)
810            .parse()
811            .expect("Failed to parse ZBI");
812
813        check_extracted_items(&parser, &[ZbiType::Crashlog, ZbiType::StorageBootfsFactory]);
814        check_item_bytes(&builder, &parser);
815
816        assert_eq!(
817            parser.decommit_ranges,
818            vec![
819                // The Cmdline item on pages 2 to 4. This item is also present on page
820                // 1 and 5, but stored items are also present on those pages preventing us from
821                // decommitting them.
822                DecommitRange { start: 0x2000, end: 0x5000 },
823                // The ImageArgs item on page 6. This item is also present on the end of page 5,
824                // but StorageBootfsFactory is a stored item which prevents us from decommitting.
825                DecommitRange { start: 0x6000, end: 0x7000 }
826            ]
827        );
828    }
829
830    #[fuchsia::test]
831    async fn get_then_decommit_driver_metadata() {
832        // Driver metadata ZBI types are special -- they can have any u32 value as long as the
833        // least significant byte is 0x6D (lowercase 'm' in ASCII).
834        //
835        // Note that the length of the first driver metadata item is 0xFC0, which puts it right
836        // at the page boundary as the two headers before this as 32 bytes. The third driver
837        // metadata item is also page aligned on the third page. This is to check for off by one
838        // errors.
839        let mut driver_metadata_header1 = ZbiBuilder::simple_header(ZbiType::Unknown, 0xFC0);
840        let mut driver_metadata_header2 = ZbiBuilder::simple_header(ZbiType::Unknown, 0x40);
841        let mut driver_metadata_header3 = ZbiBuilder::simple_header(ZbiType::Unknown, 0x40);
842
843        driver_metadata_header1.zbi_type =
844            U32::new((0xABCD << 8) | ZbiType::DriverMetadata.into_raw());
845        assert_eq!(
846            ZbiType::from_raw(driver_metadata_header1.zbi_type.get()),
847            ZbiType::DriverMetadata
848        );
849
850        driver_metadata_header2.zbi_type =
851            U32::new((0xDCBA << 8) | ZbiType::DriverMetadata.into_raw());
852        assert_eq!(
853            ZbiType::from_raw(driver_metadata_header2.zbi_type.get()),
854            ZbiType::DriverMetadata
855        );
856
857        driver_metadata_header3.zbi_type =
858            U32::new((0xEEEE << 8) | ZbiType::DriverMetadata.into_raw());
859        assert_eq!(
860            ZbiType::from_raw(driver_metadata_header3.zbi_type.get()),
861            ZbiType::DriverMetadata
862        );
863
864        let (zbi, builder) = ZbiBuilder::new()
865            .add_header(ZbiBuilder::simple_header(ZbiType::Container, 0))
866            .add_header(driver_metadata_header1.clone())
867            .add_item(0xFC0)
868            .add_header(ZbiBuilder::simple_header(ZbiType::Crashlog, 0xF80))
869            .add_item(0xF80)
870            .add_header(driver_metadata_header2.clone())
871            .add_item(0x40)
872            .add_header(driver_metadata_header3.clone())
873            .add_item(0x40)
874            .calculate_item_length()
875            .generate()
876            .expect("failed to create zbi");
877        let mut parser = ZbiParser::new(zbi)
878            .set_store_item(ZbiType::DriverMetadata)
879            .set_store_item(ZbiType::Crashlog)
880            .parse()
881            .expect("Failed to parse ZBI");
882
883        // Check we set the ZBI up correctly in this test. The first driver metadata should end
884        // at the end of the first page, and the third driver metadata should start at the start
885        // of the third page.
886        assert_eq!(parser.items.get(&ZbiType::Crashlog).unwrap()[0].header_offset, 0x1000);
887        assert_eq!(parser.items.get(&ZbiType::DriverMetadata).unwrap()[2].header_offset, 0x2000);
888
889        check_item_bytes(&builder, &parser);
890
891        // We should be able to get specific driver metadata entries indexed by their raw types.
892        // Note that all of the extras are the same, with only the types differing.
893        assert_eq!(
894            parser
895                .try_get_last_matching_item(driver_metadata_header1.zbi_type.get(), 0)
896                .unwrap()
897                .bytes
898                .len(),
899            driver_metadata_header1.length.get() as usize
900        );
901
902        assert_eq!(
903            parser
904                .try_get_last_matching_item(driver_metadata_header2.zbi_type.get(), 0)
905                .unwrap()
906                .bytes
907                .len(),
908            driver_metadata_header2.length.get() as usize
909        );
910
911        assert_eq!(
912            parser
913                .try_get_last_matching_item(driver_metadata_header3.zbi_type.get(), 0)
914                .unwrap()
915                .bytes
916                .len(),
917            driver_metadata_header3.length.get() as usize
918        );
919
920        assert!(parser.release_item(ZbiType::DriverMetadata).is_ok());
921        assert!(parser.try_get_item(ZbiType::DriverMetadata.into_raw(), None).is_err());
922
923        assert_eq!(
924            parser.decommit_ranges,
925            vec![
926                // The first page contains the ZBI container header, and one of the driver
927                // metadata items which we just released.
928                DecommitRange { start: 0x0, end: 0x1000 },
929                // Third page only contains a single driver metadata item.
930                DecommitRange { start: 0x2000, end: 0x3000 },
931            ]
932        );
933
934        check_item_bytes(&builder, &parser);
935    }
936
937    #[fuchsia::test]
938    async fn unknown_items_are_decommitted() {
939        let (zbi, builder) = ZbiBuilder::new()
940            .add_header(ZbiBuilder::simple_header(ZbiType::Container, 0))
941            .add_header(ZbiBuilder::simple_header(ZbiType::Unknown, 0x1500))
942            .add_item(0x1500)
943            .add_header(ZbiBuilder::simple_header(ZbiType::Crashlog, 0x100))
944            .add_item(0x100)
945            .calculate_item_length()
946            .generate()
947            .expect("failed to create zbi");
948        let parser = ZbiParser::new(zbi).parse().expect("Failed to parse ZBI");
949
950        check_extracted_items(&parser, &[ZbiType::Crashlog]);
951        check_item_bytes(&builder, &parser);
952
953        assert_eq!(
954            parser.decommit_ranges,
955            vec![
956                // First page just contains an unknown item and the container header.
957                DecommitRange { start: 0x0, end: 0x1000 },
958            ]
959        );
960    }
961
962    #[fuchsia::test]
963    async fn get_once_item_zeroes_parent_memory() {
964        let (zbi, builder) = ZbiBuilder::new()
965            .add_header(ZbiBuilder::simple_header(ZbiType::Container, 0))
966            .add_header(ZbiBuilder::simple_header(ZbiType::Crashlog, 2048))
967            .add_item(2048)
968            .add_header(ZbiBuilder::simple_header(ZbiType::ImageArgs, 2048))
969            .add_item(2048)
970            .add_header(ZbiBuilder::simple_header(ZbiType::ImageArgs, 8192))
971            .add_item(8192)
972            .add_header(ZbiBuilder::simple_header(ZbiType::StorageBootfsFactory, 32))
973            .add_item(32)
974            .calculate_item_length()
975            .generate()
976            .expect("failed to create zbi");
977
978        let mut parser = ZbiParser::new(zbi).parse().expect("Failed to parse ZBI");
979
980        check_extracted_items(
981            &parser,
982            &[ZbiType::Crashlog, ZbiType::ImageArgs, ZbiType::StorageBootfsFactory],
983        );
984        check_item_bytes(&builder, &parser);
985
986        assert!(parser.try_get_item(ZbiType::ImageArgs.into_raw(), None).is_ok());
987
988        let first_image_item = parser.items[&ZbiType::ImageArgs][0];
989        let second_image_item = parser.items[&ZbiType::ImageArgs][0];
990
991        assert!(parser.release_item(ZbiType::ImageArgs).is_ok());
992
993        // The item's actual memory in the VMO has been zeroed.
994        let mut bytes = vec![0; ZBI_HEADER_SIZE + first_image_item.item_length as usize];
995        let expected_bytes = vec![0; ZBI_HEADER_SIZE + first_image_item.item_length as usize];
996        parser.vmo.read(&mut bytes, first_image_item.header_offset.into()).unwrap();
997        assert_eq!(bytes, expected_bytes);
998
999        let mut bytes = vec![0; ZBI_HEADER_SIZE + second_image_item.item_length as usize];
1000        let expected_bytes = vec![0; ZBI_HEADER_SIZE + second_image_item.item_length as usize];
1001        parser.vmo.read(&mut bytes, second_image_item.header_offset.into()).unwrap();
1002        assert_eq!(bytes, expected_bytes);
1003
1004        // Trying to get the item again results in a failure.
1005        assert!(parser.try_get_item(ZbiType::ImageArgs.into_raw(), None).is_err());
1006
1007        assert_eq!(
1008            parser.decommit_ranges,
1009            vec![
1010                // This covers both the first and second ImageArgs item. Note the overlap in the
1011                // range since we have calculated this in one pass without trying to de-overlap,
1012                // but an additional syscall is worth the reduction in code complexity.
1013                DecommitRange { start: 0x1000, end: 0x2000 },
1014                DecommitRange { start: 0x1000, end: 0x3000 }
1015            ]
1016        );
1017
1018        // Check bytes for all remaining items to ensure we didn't wipe something unexpectedly.
1019        check_item_bytes(&builder, &parser);
1020    }
1021
1022    #[fuchsia::test]
1023    async fn get_items_of_unstored_type() {
1024        let (zbi, _builder) = ZbiBuilder::new()
1025            .add_header(ZbiBuilder::simple_header(ZbiType::Container, 0))
1026            .add_header(ZbiBuilder::simple_header(ZbiType::Crashlog, 2048))
1027            .add_item(2048)
1028            .add_header(ZbiBuilder::simple_header(ZbiType::Cmdline, 16384))
1029            .add_item(16384)
1030            .calculate_item_length()
1031            .generate()
1032            .expect("failed to create zbi");
1033
1034        let parser = ZbiParser::new(zbi)
1035            .set_store_item(ZbiType::Crashlog)
1036            .set_store_item(ZbiType::StorageBootfsFactory)
1037            .parse()
1038            .expect("Failed to parse ZBI");
1039
1040        let result = parser.try_get_item(ZbiType::Cmdline.into_raw(), None);
1041        assert_eq!(
1042            result.unwrap_err(),
1043            ZbiParserError::ItemNotStored { zbi_type: ZbiType::Cmdline }
1044        );
1045    }
1046
1047    #[fuchsia::test]
1048    async fn get_items_of_type_and_optional_extra() {
1049        let dm1: u32 = (0xABCD << 8) | ZbiType::DriverMetadata.into_raw();
1050        let mut dm_header1 = ZbiBuilder::simple_header(ZbiType::Unknown, 0x40);
1051        dm_header1.zbi_type = U32::new(dm1);
1052
1053        let dm2: u32 = (0xDCBA << 8) | ZbiType::DriverMetadata.into_raw();
1054        let mut dm2_header1 = ZbiBuilder::simple_header(ZbiType::Unknown, 0x40);
1055        dm2_header1.zbi_type = U32::new(dm2);
1056        dm2_header1.extra = U32::new(1);
1057
1058        let mut dm2_header2 = ZbiBuilder::simple_header(ZbiType::Unknown, 0x40);
1059        dm2_header2.zbi_type = U32::new(dm2);
1060        dm2_header2.extra = U32::new(2);
1061
1062        // This ZBI contains three driver metadata items, with two of the same type but with
1063        // different extras. It also contains a single crashlog item.
1064        let (zbi, _builder) = ZbiBuilder::new()
1065            .add_header(ZbiBuilder::simple_header(ZbiType::Container, 0))
1066            .add_header(ZbiBuilder::simple_header(ZbiType::Crashlog, 0x200))
1067            .add_item(0x200)
1068            .add_header(dm_header1.clone())
1069            .add_item(0x40)
1070            .add_header(dm2_header1.clone())
1071            .add_item(0x40)
1072            .add_header(dm2_header2.clone())
1073            .add_item(0x40)
1074            .calculate_item_length()
1075            .generate()
1076            .expect("failed to create zbi");
1077        let parser = ZbiParser::new(zbi)
1078            .set_store_item(ZbiType::Crashlog)
1079            .set_store_item(ZbiType::DriverMetadata)
1080            .parse()
1081            .expect("Failed to parse ZBI");
1082
1083        // This driver metadata type was not present in the ZBI, but is otherwise valid.
1084        let not_present_dm = (0xAAA << 8) | ZbiType::DriverMetadata.into_raw();
1085        let result = parser.try_get_item(not_present_dm, None).expect("failed to get item");
1086        assert!(result.is_empty());
1087
1088        // All three driver metadata items.
1089        let result = parser
1090            .try_get_item(ZbiType::DriverMetadata.into_raw(), None)
1091            .expect("failed to get item");
1092        assert_eq!(result.len(), 3);
1093
1094        // Narrow the result down to driver metadata of type 'dm2'.
1095        let result = parser.try_get_item(dm2, None).expect("failed to get item");
1096        assert_eq!(result.len(), 2);
1097
1098        // Narrow the result down further to driver metadata of type 'dm2' and extra '2'.
1099        let result = parser.try_get_item(dm2, Some(2)).expect("failed to get item");
1100        assert_eq!(result.len(), 1);
1101        assert_eq!(result[0].extra, 2);
1102    }
1103}