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