• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2024, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! Image decompression support.
16 
17 // gzip [DeflateDecoder] requires heap allocation.
18 extern crate alloc;
19 
20 use crate::{gbl_print, gbl_println, GblOps};
21 use liberror::{Error, Result};
22 use lz4_flex::decompress_into;
23 use zune_inflate::DeflateDecoder;
24 
25 const LZ4_NEXT_BLOCK_FAILED_ERROR_MESSAGE: &str =
26     "Failed to handle next block of lz4-compressed kernel";
27 
28 /// Returns if the data is a gzip compressed data.
is_gzip_compressed(data: &[u8]) -> bool29 fn is_gzip_compressed(data: &[u8]) -> bool {
30     data.starts_with(b"\x1f\x8b")
31 }
32 
33 /// Returns if the data is a lz4 compressed data.
is_lz4_compressed(data: &[u8]) -> bool34 fn is_lz4_compressed(data: &[u8]) -> bool {
35     data.starts_with(b"\x02\x21\x4c\x18")
36 }
37 
38 /// To iterate over compressed blocks within lz4 structure.
39 struct LZ4BlocksIterator<'a> {
40     data: &'a [u8],
41 }
42 
43 impl<'a> LZ4BlocksIterator<'a> {
44     /// Creates a new iterator from lz4 payload.
new(data: &'a [u8]) -> Self45     fn new(data: &'a [u8]) -> Self {
46         LZ4BlocksIterator { data }
47     }
48 }
49 
50 impl<'a> Iterator for LZ4BlocksIterator<'a> {
51     type Item = Result<&'a [u8]>;
52 
next(&mut self) -> Option<Self::Item>53     fn next(&mut self) -> Option<Self::Item> {
54         if self.data.is_empty() {
55             return None;
56         }
57 
58         let Some((block_size, data)) = self.data.split_at_checked(4) else {
59             return Some(Err(Error::Other(Some(LZ4_NEXT_BLOCK_FAILED_ERROR_MESSAGE))));
60         };
61         self.data = data;
62 
63         let block_size = u32::from_le_bytes(block_size.try_into().unwrap()).try_into().unwrap();
64         // Hit end marker
65         if block_size == 0 {
66             return None;
67         }
68 
69         let Some((block_content, data)) = self.data.split_at_checked(block_size) else {
70             return Some(Err(Error::Other(Some(LZ4_NEXT_BLOCK_FAILED_ERROR_MESSAGE))));
71         };
72         self.data = data;
73 
74         Some(Ok(block_content))
75     }
76 }
77 
78 /// Decompresses lz4 `content` into `out`.
decompress_lz4(content: &[u8], out: &mut [u8]) -> Result<usize>79 fn decompress_lz4(content: &[u8], out: &mut [u8]) -> Result<usize> {
80     let blocks = LZ4BlocksIterator::new(content);
81     let mut out_pos = 0;
82 
83     for block in blocks {
84         match block {
85             Ok(block) => {
86                 out_pos += decompress_into(&block, &mut out[out_pos..])
87                     .map_err(|_| Error::Other(Some("Failed to decompress lz4 block")))?;
88             }
89             Err(e) => {
90                 return Err(e);
91             }
92         }
93     }
94 
95     Ok(out_pos)
96 }
97 
98 /// Decompresses gzip `content` into `out`.
99 ///
100 /// Dynamic allocation is used insize `decoder.decode_gzip()`.
decompress_gzip(content: &[u8], out: &mut [u8]) -> Result<usize>101 fn decompress_gzip(content: &[u8], out: &mut [u8]) -> Result<usize> {
102     let mut decoder = DeflateDecoder::new(content);
103 
104     let decompressed_data =
105         decoder.decode_gzip().map_err(|_| Error::Other(Some("Failed to decompress gzip data")))?;
106 
107     let decompressed_len = decompressed_data.len();
108     out.get_mut(..decompressed_len)
109         .ok_or(Error::BufferTooSmall(Some(decompressed_len)))?
110         .clone_from_slice(&decompressed_data);
111 
112     Ok(decompressed_len)
113 }
114 
115 /// Decompresses `kernel` into `out`.
116 ///
117 /// Supported formats: gzip, lz4, and plain (uncompressed).
118 /// If the provided `kernel` is not compressed, it will be copied into `out`
119 /// without decompression.
120 ///
121 /// Returns the size of the decompressed data copied into `out`.
decompress_kernel<'a, 'b>( ops: &mut impl GblOps<'a, 'b>, kernel: &[u8], out: &mut [u8], ) -> Result<usize>122 pub fn decompress_kernel<'a, 'b>(
123     ops: &mut impl GblOps<'a, 'b>,
124     kernel: &[u8],
125     out: &mut [u8],
126 ) -> Result<usize> {
127     if is_gzip_compressed(kernel) {
128         gbl_println!(ops, "kernel is gzip compressed");
129         let decompressed = decompress_gzip(kernel, out)?;
130         gbl_println!(ops, "kernel decompressed size: {decompressed}");
131         Ok(decompressed)
132     } else if is_lz4_compressed(kernel) {
133         gbl_println!(ops, "kernel is lz4 compressed");
134         let without_magic = &kernel[4..];
135         let decompressed = decompress_lz4(without_magic, out)?;
136         gbl_println!(ops, "kernel decompressed size: {decompressed}");
137         Ok(decompressed)
138     } else {
139         // Uncompressed case. Just copy into out.
140         out.get_mut(..kernel.len())
141             .ok_or(Error::BufferTooSmall(Some(kernel.len())))?
142             .clone_from_slice(kernel);
143         Ok(kernel.len())
144     }
145 }
146 
147 #[cfg(test)]
148 mod test {
149     use super::*;
150     use crate::ops::test::FakeGblOps;
151 
152     // Asserts byte slice equality with clear error on first mismatch.
153     // Avoids full data dump from default assert, which can be very verbose.
assert_bytes_eq(actual: &[u8], expected: &[u8])154     fn assert_bytes_eq(actual: &[u8], expected: &[u8]) {
155         assert_eq!(actual.len(), expected.len());
156 
157         for (i, (l, r)) in expected.iter().zip(actual.iter()).enumerate() {
158             assert_eq!(l, r, "Unmatched byte at index {i}")
159         }
160     }
161 
test_decompress_kernel(input: &[u8], expected_output: &[u8])162     fn test_decompress_kernel(input: &[u8], expected_output: &[u8]) {
163         let mut output_buffer = vec![0u8; input.len() * 10];
164 
165         let decompressed_len =
166             decompress_kernel(&mut FakeGblOps::default(), input, &mut output_buffer).unwrap();
167 
168         assert_bytes_eq(&output_buffer[..decompressed_len], expected_output);
169     }
170 
171     #[test]
decompress_kernel_gzip()172     fn decompress_kernel_gzip() {
173         let compressed_gzip = include_bytes!("../testdata/android/gki_boot_gz_kernel").to_vec();
174         let expected_result =
175             include_bytes!("../testdata/android/gki_boot_gz_kernel_uncompressed").to_vec();
176 
177         test_decompress_kernel(&compressed_gzip, &expected_result);
178     }
179 
180     #[test]
decompress_kernel_lz4()181     fn decompress_kernel_lz4() {
182         let compressed_gzip = include_bytes!("../testdata/android/gki_boot_lz4_kernel").to_vec();
183         let expected_result =
184             include_bytes!("../testdata/android/gki_boot_lz4_kernel_uncompressed").to_vec();
185 
186         test_decompress_kernel(&compressed_gzip, &expected_result);
187     }
188 
189     #[test]
decompress_kernel_raw()190     fn decompress_kernel_raw() {
191         let kernel = include_bytes!("../testdata/android/kernel_a.img").to_vec();
192         let expected_kernel = kernel.clone();
193 
194         test_decompress_kernel(&kernel, &expected_kernel);
195     }
196 }
197