builtins/
items.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 anyhow::{anyhow, Error};
6use core::mem::size_of;
7use fidl_fuchsia_boot as fboot;
8use fuchsia_zbi::ZbiType::BootloaderFile;
9use fuchsia_zbi::{ZbiParser, ZbiParserError, ZbiResult};
10use futures::prelude::*;
11use std::collections::HashMap;
12use std::str::from_utf8;
13use std::sync::Arc;
14
15pub struct Items {
16    zbi_parser: ZbiParser,
17    bootloader_files: HashMap<String, Vec<u8>>,
18}
19
20impl Items {
21    pub fn new(mut zbi_parser: ZbiParser) -> Result<Arc<Self>, Error> {
22        // Bootloader files, if they are present in the ZBI, have special layout aware processing
23        // where this service needs to parse their payload to extract the filename which is the
24        // key. All other items are just stored unprocessed.
25        let bootloader_files = match zbi_parser.try_get_item(BootloaderFile.into_raw(), None) {
26            Ok(result) => {
27                zbi_parser.release_item(BootloaderFile)?;
28                Items::parse_bootloader_items(result)?
29            }
30            Err(_) => HashMap::new(),
31        };
32
33        Ok(Arc::new(Items { zbi_parser, bootloader_files }))
34    }
35
36    pub fn parse_bootloader_items(
37        items: Vec<ZbiResult>,
38    ) -> Result<HashMap<String, Vec<u8>>, Error> {
39        let mut bootloader_result = HashMap::new();
40        for item in items {
41            // The layout of a bootloader file has the following format:
42            // | size of name |    name    |     payload     |
43            // 0              1       size of name    length of item
44            let length = item.bytes.len();
45            if length < size_of::<u8>() {
46                return Err(anyhow!(
47                    "Bootloader ZBI item is too small to contain the size of the name"
48                ));
49            }
50
51            // The bootloader items have been split into multiple byte vectors, so this offset
52            // is into an individual bootloader item starting at index 0.
53            let mut offset = 0;
54            let name_length = item.bytes[offset].try_into()?;
55            offset += size_of::<u8>();
56
57            if length < (offset + name_length) {
58                return Err(anyhow!(
59                    "Bootloader ZBI item is too small to contain the reported length of the name"
60                ));
61            }
62
63            let name = from_utf8(&item.bytes[offset..(offset + name_length)])?.to_owned();
64            offset = offset
65                .checked_add(name_length)
66                .ok_or_else(|| anyhow!("Overflow when parsing bootloader ZBI item"))?;
67
68            if bootloader_result.contains_key(&name) {
69                return Err(anyhow!("Bootloader items in ZBI have duplicate filenames: {}", name));
70            }
71
72            bootloader_result.insert(name, item.bytes[offset..].to_vec());
73        }
74
75        Ok(bootloader_result)
76    }
77
78    pub async fn serve(
79        self: Arc<Self>,
80        mut stream: fboot::ItemsRequestStream,
81    ) -> Result<(), Error> {
82        while let Some(request) = stream.try_next().await? {
83            match request {
84                fboot::ItemsRequest::Get { type_, extra, responder } => {
85                    match self.zbi_parser.try_get_last_matching_item(type_, extra) {
86                        Ok(result) => {
87                            let vmo = zx::Vmo::create(result.bytes.len().try_into()?)?;
88                            vmo.write(&result.bytes, 0)?;
89                            responder.send(Some(vmo), result.bytes.len().try_into()?)?
90                        }
91                        Err(_) => responder.send(None, 0)?,
92                    }
93                }
94                fboot::ItemsRequest::Get2 { type_, extra, responder } => {
95                    let extra = if let Some(extra) = extra { Some((*extra).n) } else { None };
96                    let item_vec = match self.zbi_parser.try_get_item(type_, extra) {
97                        Ok(vec) => vec
98                            .iter()
99                            .map(|result| -> Result<fboot::RetrievedItems, Error> {
100                                let vmo = zx::Vmo::create(result.bytes.len().try_into()?)?;
101                                vmo.write(&result.bytes, 0)?;
102                                Ok(fboot::RetrievedItems {
103                                    payload: vmo,
104                                    length: result.bytes.len().try_into()?,
105                                    extra: result.extra,
106                                })
107                            })
108                            .collect::<Result<Vec<fboot::RetrievedItems>, Error>>()
109                            .map_err(|_| zx::Status::INTERNAL.into_raw()),
110                        Err(err) => {
111                            match err {
112                                ZbiParserError::ItemNotStored { .. } => {
113                                    Err(zx::Status::NOT_SUPPORTED.into_raw())
114                                }
115                                _ => {
116                                    // Errors such as item not found are not unexpected, and so not
117                                    // propagated to the client.
118                                    Ok(vec![])
119                                }
120                            }
121                        }
122                    };
123
124                    responder.send(item_vec)?
125                }
126                fboot::ItemsRequest::GetBootloaderFile { filename, responder } => {
127                    match self.bootloader_files.get(&filename) {
128                        Some(bytes) => {
129                            let vmo = zx::Vmo::create(bytes.len().try_into()?)?;
130                            vmo.write(&bytes, 0)?;
131                            responder.send(Some(vmo))?
132                        }
133                        None => responder.send(None)?,
134                    }
135                }
136            };
137        }
138        Ok(())
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145    use fuchsia_async as fasync;
146    use fuchsia_zbi::{
147        zbi_header_t, ZbiType, ZBI_CONTAINER_MAGIC, ZBI_FLAGS_VERSION, ZBI_ITEM_MAGIC,
148        ZBI_ITEM_NO_CRC32,
149    };
150    use zerocopy::byteorder::little_endian::U32;
151    use zerocopy::IntoBytes;
152
153    const ZBI_HEADER_SIZE: usize = size_of::<zbi_header_t>();
154
155    fn serve_items(zbi_parser: ZbiParser) -> Result<fboot::ItemsProxy, Error> {
156        let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<fboot::ItemsMarker>();
157        fasync::Task::local(
158            Items::new(zbi_parser)?
159                .serve(stream)
160                .unwrap_or_else(|e| panic!("Error while serving items service: {}", e)),
161        )
162        .detach();
163        Ok(proxy)
164    }
165
166    struct ZbiBuilder {
167        zbi_bytes: Vec<u8>,
168    }
169
170    impl ZbiBuilder {
171        fn get_bootloader_item(name: &[u8], payload: &[u8]) -> Vec<u8> {
172            // The first byte of the bootloader item is the length of the bootloader item name
173            // in bytes, and following the name is the actual payload. All three sections
174            // together sum to the length reported by the header.
175            let mut result = vec![name.len().try_into().unwrap()];
176            result.extend(name);
177            result.extend(payload);
178            result
179        }
180
181        fn simple_header(zbi_type: ZbiType, extra: u32, length: u32) -> zbi_header_t {
182            zbi_header_t {
183                zbi_type: U32::new(zbi_type as u32),
184                length: U32::new(length),
185                extra: U32::new(extra),
186                flags: U32::new(ZBI_FLAGS_VERSION),
187                reserved_0: U32::new(0),
188                reserved_1: U32::new(0),
189                magic: U32::new(ZBI_ITEM_MAGIC),
190                crc32: U32::new(ZBI_ITEM_NO_CRC32),
191            }
192        }
193
194        fn add_item(mut self, data: &[u8]) -> Self {
195            self.zbi_bytes.extend(data);
196            let padding_amount = ZbiParser::align_zbi_item(data.len().try_into().unwrap()).unwrap()
197                as usize
198                - data.len();
199            if padding_amount > 0 {
200                let padding = vec![0u8; padding_amount];
201                self.zbi_bytes.extend(padding);
202            }
203            self
204        }
205
206        fn add_header(mut self, zbi_type: ZbiType, extra: u32, length: u32) -> Self {
207            self.zbi_bytes.extend(ZbiBuilder::simple_header(zbi_type, extra, length).as_bytes());
208            self
209        }
210
211        fn new() -> Self {
212            Self {
213                zbi_bytes: ZbiBuilder::simple_header(ZbiType::Container, ZBI_CONTAINER_MAGIC, 0)
214                    .as_bytes()
215                    .to_vec(),
216            }
217        }
218
219        fn calculate_item_length(mut self) -> Self {
220            let item_length =
221                U32::new(u32::try_from(self.zbi_bytes.len() - ZBI_HEADER_SIZE).unwrap());
222            let item_length_bytes = item_length.as_bytes();
223
224            let mut i = 4usize;
225            for x in item_length_bytes {
226                self.zbi_bytes[i] = *x;
227                i += 1;
228            }
229
230            self
231        }
232
233        fn generate(self) -> Result<zx::Vmo, Error> {
234            let vmo = zx::Vmo::create(self.zbi_bytes.len().try_into()?)?;
235            vmo.write(&self.zbi_bytes, 0)?;
236            Ok(vmo)
237        }
238    }
239
240    #[fuchsia::test]
241    async fn get2_untracked_item_not_found() {
242        let zbi_type = ZbiType::Crashlog;
243        let item = b"abcd";
244
245        let zbi = ZbiBuilder::new()
246            .add_header(zbi_type, 0, item.len().try_into().unwrap())
247            .add_item(item)
248            .calculate_item_length()
249            .generate()
250            .expect("failed to create ZBI");
251        let parser = ZbiParser::new(zbi)
252            .set_store_item(ZbiType::Cmdline)
253            .parse()
254            .expect("failed to parse ZBI");
255
256        let item_service = serve_items(parser).expect("failed to serve items");
257
258        // Crashlog existed in the ZBI, but the parser configuration is set to only store Cmdline
259        // items.
260        let result = item_service
261            .get2(zbi_type.into_raw(), None)
262            .await
263            .expect("failed to query item service");
264        assert_eq!(zx::Status::from_raw(result.unwrap_err()), zx::Status::NOT_SUPPORTED);
265    }
266
267    #[fuchsia::test]
268    async fn get2_multiple_items_same_type_different_extras() {
269        let zbi_type = ZbiType::Crashlog;
270
271        let item1 = b"abcd";
272        let extra1 = 123;
273
274        let item2 = b"efgh";
275        let extra2 = 456;
276
277        let zbi = ZbiBuilder::new()
278            .add_header(zbi_type, extra1, item1.len().try_into().unwrap())
279            .add_item(item1)
280            .add_header(zbi_type, extra2, item2.len().try_into().unwrap())
281            .add_item(item2)
282            .calculate_item_length()
283            .generate()
284            .expect("failed to create ZBI");
285        let parser = ZbiParser::new(zbi).parse().expect("failed to parse ZBI");
286
287        let item_service = serve_items(parser).expect("failed to serve items");
288
289        // Get both items by not specifying the extra.
290        let result = item_service
291            .get2(zbi_type.into_raw(), None)
292            .await
293            .expect("failed to query item service")
294            .expect("failed to retrieve items");
295
296        assert_eq!(result.len(), 2);
297
298        let retrieved =
299            result.iter().find(|item| item.extra == extra1).expect("failed to find item");
300        let mut bytes = vec![0; retrieved.length as usize];
301        retrieved.payload.read(&mut bytes, 0).expect("failed to read bytes");
302        assert_eq!(bytes, item1);
303        assert_eq!(retrieved.length, item1.len() as u32);
304
305        let retrieved =
306            result.iter().find(|item| item.extra == extra2).expect("failed to find item");
307        let mut bytes = vec![0; retrieved.length as usize];
308        retrieved.payload.read(&mut bytes, 0).expect("failed to read bytes");
309        assert_eq!(bytes, item2);
310        assert_eq!(retrieved.length, item2.len() as u32);
311
312        // Get a single item by specifying the extra.
313        let result = item_service
314            .get2(zbi_type.into_raw(), Some(&fidl_fuchsia_boot::Extra { n: extra2 }))
315            .await
316            .expect("failed to query item service")
317            .expect("failed to retrieve items");
318
319        assert!(result.iter().find(|item| item.extra == extra2).is_some());
320        assert!(result.iter().find(|item| item.extra == extra1).is_none());
321    }
322
323    #[fuchsia::test]
324    async fn item_not_found_no_matching_extra() {
325        // The ZBI contains the correct item, but without a matching extra.
326        let actual_extra = 54321;
327        let queried_extra = 12345;
328        let zbi_type = ZbiType::Crashlog;
329
330        let item = b"abcd";
331        let zbi = ZbiBuilder::new()
332            .add_header(zbi_type, actual_extra, item.len().try_into().unwrap())
333            .add_item(item)
334            .calculate_item_length()
335            .generate()
336            .expect("failed to create ZBI");
337        let parser = ZbiParser::new(zbi).parse().expect("failed to parse ZBI");
338
339        let item_service = serve_items(parser).expect("failed to serve items");
340        let (vmo, length) = item_service
341            .get(zbi_type as u32, queried_extra)
342            .await
343            .expect("failed to query item service");
344
345        assert!(vmo.is_none());
346        assert_eq!(length, 0);
347    }
348
349    #[fuchsia::test]
350    async fn item_not_found_no_matching_zbi_type() {
351        // The ZBI contains a different item with the matching extra, but no item with both
352        // fields matching.
353        let extra = 54321;
354        let actual_zbi_type = ZbiType::Crashlog;
355        let queried_zbi_type = ZbiType::KernelDriver;
356
357        let item = b"abcd";
358        let zbi = ZbiBuilder::new()
359            .add_header(actual_zbi_type, extra, item.len().try_into().unwrap())
360            .add_item(item)
361            .calculate_item_length()
362            .generate()
363            .expect("failed to create ZBI");
364        let parser = ZbiParser::new(zbi).parse().expect("failed to parse ZBI");
365
366        let item_service = serve_items(parser).expect("failed to serve items");
367        let (vmo, length) = item_service
368            .get(queried_zbi_type as u32, extra)
369            .await
370            .expect("failed to query item service");
371
372        assert!(vmo.is_none());
373        assert_eq!(length, 0);
374    }
375
376    #[fuchsia::test]
377    async fn get_non_bootloader_item() {
378        // Both ZBI type and extra match, returning the item parsed from the ZBI.
379        let zbi_type = ZbiType::Crashlog;
380        let extra = 12345;
381
382        let item = b"abcd";
383        let zbi = ZbiBuilder::new()
384            .add_header(zbi_type, extra, item.len().try_into().unwrap())
385            .add_item(item)
386            .calculate_item_length()
387            .generate()
388            .expect("failed to create ZBI");
389        let parser = ZbiParser::new(zbi).parse().expect("failed to parse ZBI");
390
391        let item_service = serve_items(parser).expect("failed to serve items");
392        let (vmo, length) =
393            item_service.get(zbi_type as u32, extra).await.expect("failed to query item service");
394
395        assert!(vmo.is_some());
396        let mut bytes = vec![0; item.len()];
397        vmo.unwrap().read(&mut bytes, 0).expect("failed to read bytes");
398        assert_eq!(bytes, b"abcd");
399        assert_eq!(length, item.len() as u32);
400    }
401
402    #[fuchsia::test]
403    async fn badly_formatted_bootloader_file() {
404        // Take a correct bootloader payload, and cut it in half. The first byte reporting the
405        // length of the name is now incorrect, making this an invalid entry.
406        let mut item = ZbiBuilder::get_bootloader_item(b"bootloader_name", b"");
407        item.resize(item.len() / 2, 0);
408
409        let zbi = ZbiBuilder::new()
410            .add_header(BootloaderFile, 0, item.len().try_into().unwrap())
411            .add_item(&item)
412            .calculate_item_length()
413            .generate()
414            .expect("failed to create ZBI");
415        let parser = ZbiParser::new(zbi).parse().expect("failed to parse ZBI");
416
417        let item_service = serve_items(parser);
418        assert!(item_service.is_err());
419    }
420
421    #[fuchsia::test]
422    async fn duplicate_bootloader_files() {
423        let item1 = ZbiBuilder::get_bootloader_item(b"file", b"abcd");
424        let item2 = ZbiBuilder::get_bootloader_item(b"file", b"efgh");
425
426        let zbi = ZbiBuilder::new()
427            .add_header(BootloaderFile, 0, item1.len().try_into().unwrap())
428            .add_item(&item1)
429            .add_header(BootloaderFile, 0, item2.len().try_into().unwrap())
430            .add_item(&item2)
431            .calculate_item_length()
432            .generate()
433            .expect("failed to create ZBI");
434        let parser = ZbiParser::new(zbi).parse().expect("failed to parse ZBI");
435
436        let item_service = serve_items(parser);
437        assert!(item_service.is_err());
438    }
439
440    #[fuchsia::test]
441    async fn no_matching_bootloader_file() {
442        let actual_name = b"this_is_a_name";
443        let queried_name = "this_is_NOT_a_name";
444        let item = ZbiBuilder::get_bootloader_item(actual_name, b"this_is_a_payload");
445
446        let zbi = ZbiBuilder::new()
447            .add_header(BootloaderFile, 0, item.len().try_into().unwrap())
448            .add_item(&item)
449            .calculate_item_length()
450            .generate()
451            .expect("failed to create ZBI");
452        let parser = ZbiParser::new(zbi).parse().expect("failed to parse ZBI");
453
454        let item_service = serve_items(parser).expect("failed to serve items");
455        let vmo = item_service
456            .get_bootloader_file(queried_name)
457            .await
458            .expect("failed to query item service");
459
460        assert!(vmo.is_none());
461    }
462
463    #[fuchsia::test]
464    async fn get_bootloader_files() {
465        let name1 = b"this_is_a_name";
466        let payload1 = b"this_is_a_payload";
467        let item1 = ZbiBuilder::get_bootloader_item(name1, payload1);
468
469        let name2 = b"this_is_another_name";
470        let payload2 = b"this_is_another_payload";
471        let item2 = ZbiBuilder::get_bootloader_item(name2, payload2);
472
473        let zbi = ZbiBuilder::new()
474            .add_header(BootloaderFile, 0, item1.len().try_into().unwrap())
475            .add_item(&item1)
476            .add_header(BootloaderFile, 0, item2.len().try_into().unwrap())
477            .add_item(&item2)
478            .calculate_item_length()
479            .generate()
480            .expect("failed to create ZBI");
481        let parser = ZbiParser::new(zbi).parse().expect("failed to parse ZBI");
482
483        let item_service = serve_items(parser).expect("failed to serve items");
484
485        let response = item_service
486            .get_bootloader_file(from_utf8(name1).unwrap())
487            .await
488            .expect("failed to query item service");
489
490        assert!(response.is_some());
491        let vmo = response.unwrap();
492        let length = vmo.get_content_size().unwrap().try_into().unwrap();
493        let mut bytes = vec![0; length];
494        vmo.read(&mut bytes, 0).expect("failed to read bytes");
495
496        assert_eq!(bytes, payload1);
497        assert_eq!(length, payload1.len());
498
499        let response = item_service
500            .get_bootloader_file(from_utf8(name2).unwrap())
501            .await
502            .expect("failed to query item service");
503
504        assert!(response.is_some());
505        let vmo = response.unwrap();
506        let length = vmo.get_content_size().unwrap().try_into().unwrap();
507        let mut bytes = vec![0; length];
508        vmo.read(&mut bytes, 0).expect("failed to read bytes");
509
510        assert_eq!(bytes, payload2);
511        assert_eq!(length, payload2.len());
512    }
513}