1use anyhow::{Error, anyhow, bail};
6use mapped_vmo::Mapping;
7use std::fmt::Write as _;
8use std::mem::size_of;
9use std::slice;
10use zbi::zbi_format::{
11 ZBI_CONTAINER_MAGIC, ZBI_FLAGS_CRC32, ZBI_FLAGS_STORAGE_COMPRESSED, ZBI_ITEM_MAGIC,
12 ZBI_ITEM_NO_CRC32,
13};
14use zbi::{ZBI_ALIGNMENT_USIZE, ZbiFlags, ZbiHeader, ZbiType};
15use zerocopy::IntoBytes as _;
16use zx::{Name, VmarFlags, Vmo};
17use zx_libc::sanitizer::Log;
18
19const HEADER_SIZE: u64 = size_of::<ZbiHeader>() as u64;
20
21#[derive(Debug, Clone, Copy)]
23pub struct ZbiItem {
24 pub header: ZbiHeader,
26 pub payload_offset: u64,
28}
29
30fn check_item_header(header: &ZbiHeader) -> Result<(), Error> {
31 if header.magic != ZBI_ITEM_MAGIC {
32 bail!("ZBI header has bad magic");
33 }
34 if !header.get_flags().contains(ZbiFlags::VERSION) {
35 bail!("ZBI header has bad version");
36 }
37 if !header.get_flags().contains(ZbiFlags::CRC32) && header.crc32 != ZBI_ITEM_NO_CRC32 {
40 bail!("ZBI header without CRC32 flag must have ZBI_ITEM_NO_CRC32");
41 }
42 Ok(())
43}
44
45#[derive(Debug)]
48pub struct ZbiItemIterator<'a> {
49 zbi_vmo: &'a Vmo,
50 offset: u64,
51 container_end: u64,
52 failed: bool,
53}
54
55impl<'a> ZbiItemIterator<'a> {
56 pub fn new(zbi_vmo: &'a Vmo) -> Result<Self, Error> {
59 let vmo_size = zbi_vmo.get_size()?;
60 if vmo_size < HEADER_SIZE {
61 bail!("ZBI VMO is too small for container header");
62 }
63 let header: ZbiHeader = zbi_vmo.read_to_object(0)?;
64 if header.type_ != ZbiType::Container as u32 {
65 bail!("ZBI container header has bad type: {:#x}", header.type_);
66 }
67 if header.extra != ZBI_CONTAINER_MAGIC {
68 bail!("ZBI container header has bad magic");
69 }
70 if (header.flags & ZBI_FLAGS_CRC32) != 0 {
71 bail!("ZBI container header must not have CRC32 flag");
72 }
73 if (header.length as usize) % ZBI_ALIGNMENT_USIZE != 0 {
74 bail!("ZBI container length is not aligned");
75 }
76 check_item_header(&header)?;
77 let container_end = HEADER_SIZE
78 .checked_add(u64::from(header.length))
79 .ok_or_else(|| anyhow!("ZBI container length overflow"))?;
80 if container_end > vmo_size {
81 bail!("ZBI container is truncated");
82 }
83 Ok(Self { zbi_vmo, offset: HEADER_SIZE, container_end, failed: false })
84 }
85}
86
87impl Iterator for ZbiItemIterator<'_> {
88 type Item = Result<ZbiItem, Error>;
89
90 fn next(&mut self) -> Option<Self::Item> {
91 if self.failed || self.offset == self.container_end {
92 return None;
93 }
94 let step = (|| -> Result<ZbiItem, Error> {
95 let payload_offset = self
96 .offset
97 .checked_add(HEADER_SIZE)
98 .ok_or_else(|| anyhow!("ZBI item header offset overflow"))?;
99 if payload_offset > self.container_end {
100 bail!("ZBI item header is truncated");
101 }
102 let header: ZbiHeader = self.zbi_vmo.read_to_object(self.offset)?;
103 check_item_header(&header)?;
104 let payload_end = payload_offset
105 .checked_add(u64::from(header.length))
106 .ok_or_else(|| anyhow!("ZBI item length overflow"))?;
107 if payload_end > self.container_end {
108 bail!("ZBI item payload is truncated");
109 }
110 let next_offset = payload_end
111 .checked_next_multiple_of(ZBI_ALIGNMENT_USIZE as u64)
112 .ok_or_else(|| anyhow!("ZBI item alignment overflow"))?;
113 if next_offset > self.container_end {
114 bail!("ZBI item alignment padding is truncated");
115 }
116 self.offset = next_offset;
117 Ok(ZbiItem { header, payload_offset })
118 })();
119 if step.is_err() {
120 self.failed = true;
121 }
122 Some(step)
123 }
124}
125
126pub fn get_bootfs_vmo(zbi_vmo: &Vmo, check_crc: bool, log: &mut Log) -> Result<Vmo, Error> {
129 let mut bootfs_item = None;
130 for item in ZbiItemIterator::new(zbi_vmo)? {
131 let item = item?;
132 if item.header.type_ == ZbiType::StorageBootFs as u32 {
133 bootfs_item = Some(item);
134 break;
135 }
136 }
137 let bootfs_item = bootfs_item.ok_or_else(|| anyhow!("StorageBootFs item not found in ZBI"))?;
138
139 let payload_len = bootfs_item.header.length as usize;
140 if payload_len == 0 {
141 bail!("StorageBootFs item has empty payload");
142 }
143 let page_size = u64::from(zx::system_get_page_size());
144 let map_offset = bootfs_item.payload_offset & !(page_size - 1);
145 let offset_in_mapping = (bootfs_item.payload_offset - map_offset) as usize;
146 let map_size = (offset_in_mapping as u64)
147 .checked_add(payload_len as u64)
148 .and_then(|end| end.checked_next_multiple_of(page_size))
149 .ok_or_else(|| anyhow!("BOOTFS mapping size overflow"))?;
150 let mut payload_mapping = Mapping::create_from_vmo_with_offset(
151 zbi_vmo,
152 map_offset,
153 map_size as usize,
154 VmarFlags::PERM_READ | VmarFlags::MAP_RANGE,
155 )?;
156 let (payload_ptr, payload_map_len) = payload_mapping.as_ptr_len();
157 let payload = unsafe {
161 &slice::from_raw_parts(payload_ptr.cast_const(), payload_map_len)
162 [offset_in_mapping..offset_in_mapping + payload_len]
163 };
164
165 if check_crc && (bootfs_item.header.flags & ZBI_FLAGS_CRC32) != 0 {
166 writeln!(log, "Checking BOOTFS item CRC32 {:#x}...", bootfs_item.header.crc32)?;
167 let mut header_without_crc = bootfs_item.header;
168 header_without_crc.crc32 = 0;
169 let mut hasher = crc32fast::Hasher::new();
170 hasher.update(header_without_crc.as_bytes());
171 hasher.update(payload);
172 if hasher.finalize() == bootfs_item.header.crc32 {
173 writeln!(log, "BOOTFS payload matches item CRC32.")?;
174 } else {
175 writeln!(log, "*** BOOTFS payload DOES NOT MATCH item CRC32! ***")?;
176 bail!("BOOTFS payload CRC32 mismatch");
177 }
178 }
179
180 let is_compressed = (bootfs_item.header.flags & ZBI_FLAGS_STORAGE_COMPRESSED) != 0;
181 let bootfs_vmo = if is_compressed {
182 let uncompressed_size = bootfs_item.header.extra as usize;
183 if uncompressed_size == 0 {
184 bail!("Compressed BOOTFS item has uncompressed size 0");
185 }
186 let vmo = Vmo::create(uncompressed_size as u64)?;
187 vmo.op_range(zx::VmoOp::COMMIT, 0, uncompressed_size as u64)?;
190 let mut mapping = Mapping::create_from_vmo(
191 &vmo,
192 uncompressed_size,
193 VmarFlags::PERM_READ | VmarFlags::PERM_WRITE | VmarFlags::MAP_RANGE,
194 )?;
195 let (ptr, len) = mapping.as_ptr_len();
196 let buffer = unsafe { slice::from_raw_parts_mut(ptr, len) };
200
201 zstd_safe::decompress(buffer, payload)
202 .map_err(|e| anyhow!("zstd decompression failed with error code: {}", e))?;
203 vmo
204 } else {
205 let vmo = Vmo::create(payload.len() as u64)?;
206 vmo.write(payload, 0)?;
207 vmo
208 };
209
210 bootfs_vmo.set_name(&Name::new("uncompressed-bootfs")?)?;
211 Ok(bootfs_vmo)
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217 use zbi::{ZbiContainer, align_buffer};
218
219 fn create_test_zbi_vmo_with_prefix(
220 prefix_items: &[(ZbiType, &[u8])],
221 payload: &[u8],
222 compressed: bool,
223 uncompressed_len_override: Option<u32>,
224 with_crc: bool,
225 corrupt_crc: bool,
226 ) -> Vmo {
227 let mut raw_buf = vec![0u8; 32768];
228 let aligned = align_buffer(&mut raw_buf[..]).unwrap();
229 let size = {
230 let mut container = ZbiContainer::new(&mut aligned[..]).unwrap();
231 for &(ty, data) in prefix_items {
232 container.create_entry_with_payload(ty, 0, ZbiFlags::default(), data).unwrap();
233 }
234 let extra = uncompressed_len_override.unwrap_or(payload.len() as u32);
235 if compressed {
236 let mut compressed_buf = vec![0u8; zstd_safe::compress_bound(payload.len())];
237 let compressed_len = zstd_safe::compress(&mut compressed_buf, payload, 1).unwrap();
238 container
239 .create_entry_with_payload(
240 ZbiType::StorageBootFs,
241 extra,
242 ZbiFlags::default(),
243 &compressed_buf[..compressed_len],
244 )
245 .unwrap();
246 container.iter_mut().last().unwrap().header.flags |= ZBI_FLAGS_STORAGE_COMPRESSED;
247 } else {
248 container
249 .create_entry_with_payload(
250 ZbiType::StorageBootFs,
251 extra,
252 ZbiFlags::default(),
253 payload,
254 )
255 .unwrap();
256 }
257 if with_crc {
258 let mut item = container.iter_mut().last().unwrap();
259 item.header.flags |= ZBI_FLAGS_CRC32;
260 let mut header_without_crc = *item.header;
261 header_without_crc.crc32 = 0;
262 let mut hasher = crc32fast::Hasher::new();
263 hasher.update(header_without_crc.as_bytes());
264 hasher.update(item.payload.as_bytes());
265 let crc = hasher.finalize();
266 item.header.crc32 = if corrupt_crc { crc ^ 0xffff_ffff } else { crc };
267 }
268 container.container_size().unwrap()
269 };
270 let zbi_bytes = &aligned[..size];
271 let vmo = Vmo::create(zbi_bytes.len() as u64).unwrap();
272 vmo.write(zbi_bytes, 0).unwrap();
273 vmo
274 }
275
276 fn create_test_zbi_vmo(
277 payload: &[u8],
278 compressed: bool,
279 uncompressed_len_override: Option<u32>,
280 with_crc: bool,
281 corrupt_crc: bool,
282 ) -> Vmo {
283 create_test_zbi_vmo_with_prefix(
284 &[],
285 payload,
286 compressed,
287 uncompressed_len_override,
288 with_crc,
289 corrupt_crc,
290 )
291 }
292
293 #[fuchsia::test]
294 fn test_get_bootfs_vmo_uncompressed() {
295 let payload = b"uncompressed bootfs payload data";
296 let zbi_vmo = create_test_zbi_vmo(payload, false, None, false, false);
297 let mut log = Log::new();
298
299 let bootfs_vmo = get_bootfs_vmo(&zbi_vmo, false, &mut log).unwrap();
300
301 assert_eq!(zbi_vmo.info().unwrap().num_mappings, 0);
302 assert_eq!(zbi_vmo.info().unwrap().num_children, 0);
303 assert_eq!(bootfs_vmo.get_name().unwrap(), "uncompressed-bootfs");
304 let mut read_back = vec![0u8; payload.len()];
305 bootfs_vmo.read(&mut read_back, 0).unwrap();
306 assert_eq!(&read_back, payload);
307 }
308
309 #[fuchsia::test]
310 fn test_get_bootfs_vmo_compressed_with_crc() {
311 let payload = vec![0x5au8; 8192];
312 let zbi_vmo = create_test_zbi_vmo(&payload, true, None, true, false);
313 let mut log = Log::new();
314
315 let bootfs_vmo = get_bootfs_vmo(&zbi_vmo, true, &mut log).unwrap();
316
317 assert_eq!(zbi_vmo.info().unwrap().num_mappings, 0);
318 assert_eq!(zbi_vmo.info().unwrap().num_children, 0);
319 assert_eq!(bootfs_vmo.info().unwrap().num_mappings, 0);
320 assert_eq!(bootfs_vmo.get_name().unwrap(), "uncompressed-bootfs");
321 let mut read_back = vec![0u8; payload.len()];
322 bootfs_vmo.read(&mut read_back, 0).unwrap();
323 assert_eq!(read_back, payload);
324 }
325
326 #[fuchsia::test]
327 fn test_get_bootfs_vmo_after_non_page_aligned_prefix_items() {
328 let dummy_kernel = vec![0xa5u8; 5000];
329 let cmdline = b"userboot.next=bin/test";
330 let payload = vec![0x3cu8; 6000];
331 let zbi_vmo = create_test_zbi_vmo_with_prefix(
332 &[(ZbiType::Discard, &dummy_kernel), (ZbiType::CmdLine, cmdline)],
333 &payload,
334 true,
335 None,
336 true,
337 false,
338 );
339 let mut log = Log::new();
340
341 let items = ZbiItemIterator::new(&zbi_vmo).unwrap().collect::<Result<Vec<_>, _>>().unwrap();
342 assert_eq!(items.len(), 3);
343 assert_eq!(items[1].header.type_, ZbiType::CmdLine as u32);
344 let read_cmdline = zbi_vmo
345 .read_to_vec::<u8>(items[1].payload_offset, items[1].header.length.into())
346 .unwrap();
347 assert_eq!(&read_cmdline, cmdline);
348
349 let bootfs_vmo = get_bootfs_vmo(&zbi_vmo, true, &mut log).unwrap();
350 assert_eq!(zbi_vmo.info().unwrap().num_mappings, 0);
351 assert_eq!(zbi_vmo.info().unwrap().num_children, 0);
352 let mut read_back = vec![0u8; payload.len()];
353 bootfs_vmo.read(&mut read_back, 0).unwrap();
354 assert_eq!(read_back, payload);
355 }
356
357 #[fuchsia::test]
358 fn test_get_bootfs_vmo_crc_mismatch() {
359 let payload = b"bootfs payload with bad crc";
360 let zbi_vmo = create_test_zbi_vmo(payload, false, None, true, true);
361 let mut log = Log::new();
362
363 let res = get_bootfs_vmo(&zbi_vmo, true, &mut log);
364 assert!(res.is_err());
365 }
366
367 #[fuchsia::test]
368 fn test_get_bootfs_vmo_compressed_zero_size() {
369 let payload = b"non-empty";
370 let zbi_vmo = create_test_zbi_vmo(payload, true, Some(0), false, false);
371 let mut log = Log::new();
372
373 let res = get_bootfs_vmo(&zbi_vmo, false, &mut log);
374 assert!(res.is_err());
375 }
376
377 #[fuchsia::test]
378 fn test_zbi_container_header_validation() {
379 let payload = b"bootfs payload";
380 let zbi_vmo = create_test_zbi_vmo(payload, false, None, false, false);
381 let orig_header: ZbiHeader = zbi_vmo.read_to_object(0).unwrap();
382
383 let mut header_with_crc = orig_header;
385 header_with_crc.flags |= ZBI_FLAGS_CRC32;
386 header_with_crc.crc32 = 0x1234_5678;
387 zbi_vmo.write(header_with_crc.as_bytes(), 0).unwrap();
388 assert!(ZbiItemIterator::new(&zbi_vmo).is_err());
389
390 let mut header_unaligned_len = orig_header;
392 header_unaligned_len.length -= 1;
393 zbi_vmo.write(header_unaligned_len.as_bytes(), 0).unwrap();
394 assert!(ZbiItemIterator::new(&zbi_vmo).is_err());
395 }
396}