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