1 // Copyright 2016 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 
5 #include <bootdata/decompress.h>
6 
7 #include <limits.h>
8 #include <string.h>
9 
10 #include <zircon/boot/bootdata.h>
11 #include <zircon/compiler.h>
12 #include <zircon/syscalls.h>
13 
14 #include <lz4/lz4.h>
15 
16 // The LZ4 Frame format is used to compress a bootfs image, but we cannot use
17 // the LZ4 library's decompression functions in userboot. The following
18 // definitions are used in the reimplementation of LZ4 Frame decompression, with
19 // a few restrictions on the frame options:
20 //  - Blocks must be independent
21 //  - No block checksums
22 //  - Final content size must be included in frame header
23 //  - Max block size is 64kB
24 //
25 //  See https://github.com/lz4/lz4/blob/dev/lz4_Frame_format.md for details.
26 #define ZX_LZ4_MAGIC 0x184D2204
27 #define ZX_LZ4_VERSION (1 << 6)
28 
29 typedef struct {
30     uint8_t flag;
31     uint8_t block_desc;
32     uint64_t content_size;
33     uint8_t header_cksum;
34 } __PACKED lz4_frame_desc;
35 
36 #define ZX_LZ4_FLAG_VERSION       (1 << 6)
37 #define ZX_LZ4_FLAG_BLOCK_DEP     (1 << 5)
38 #define ZX_LZ4_FLAG_BLOCK_CKSUM   (1 << 4)
39 #define ZX_LZ4_FLAG_CONTENT_SZ    (1 << 3)
40 #define ZX_LZ4_FLAG_CONTENT_CKSUM (1 << 2)
41 #define ZX_LZ4_FLAG_RESERVED      0x03
42 
43 #define ZX_LZ4_BLOCK_MAX_MASK     (7 << 4)
44 #define ZX_LZ4_BLOCK_64KB         (4 << 4)
45 #define ZX_LZ4_BLOCK_256KB        (5 << 4)
46 #define ZX_LZ4_BLOCK_1MB          (6 << 4)
47 #define ZX_LZ4_BLOCK_4MB          (7 << 4)
48 
check_lz4_frame(const lz4_frame_desc * fd,size_t expected,const char ** err)49 static zx_status_t check_lz4_frame(const lz4_frame_desc* fd,
50                                    size_t expected, const char** err) {
51     if ((fd->flag & ZX_LZ4_FLAG_VERSION) != ZX_LZ4_VERSION) {
52         *err = "bad lz4 version for bootfs";
53         return ZX_ERR_INVALID_ARGS;
54     }
55     if ((fd->flag & ZX_LZ4_FLAG_BLOCK_DEP) == 0) {
56         *err = "bad lz4 flag (blocks must be independent)";
57         return ZX_ERR_INVALID_ARGS;
58     }
59     if (fd->flag & ZX_LZ4_FLAG_BLOCK_CKSUM) {
60         *err = "bad lz4 flag (block checksum must be disabled)";
61         return ZX_ERR_INVALID_ARGS;
62     }
63     if ((fd->flag & ZX_LZ4_FLAG_CONTENT_SZ) == 0) {
64         *err = "bad lz4 flag (content size must be included)";
65         return ZX_ERR_INVALID_ARGS;
66     }
67     if (fd->flag & ZX_LZ4_FLAG_RESERVED) {
68         *err = "bad lz4 flag (reserved bits in flg must be zero)";
69         return ZX_ERR_INVALID_ARGS;
70     }
71     if ((fd->block_desc & ZX_LZ4_BLOCK_MAX_MASK) != ZX_LZ4_BLOCK_64KB) {
72         *err = "bad lz4 flag (max block size must be 64k)";
73         return ZX_ERR_INVALID_ARGS;
74     }
75     if (fd->block_desc & ~ZX_LZ4_BLOCK_MAX_MASK) {
76         *err = "bad lz4 flag (reserved bits in bd must be zero)";
77         return ZX_ERR_INVALID_ARGS;
78     }
79     if (fd->content_size != expected) {
80         *err = "lz4 content size does not match bootdata outsize";
81         return ZX_ERR_INVALID_ARGS;
82     }
83 
84     // TODO: header checksum
85     return ZX_OK;
86 }
87 
decompress_bootfs_vmo(zx_handle_t vmar,const uint8_t * data,size_t _outsize,zx_handle_t * out,const char ** err)88 static zx_status_t decompress_bootfs_vmo(zx_handle_t vmar, const uint8_t* data,
89                                          size_t _outsize, zx_handle_t* out,
90                                          const char** err) {
91     if (*(const uint32_t*)data != ZX_LZ4_MAGIC) {
92         *err = "bad magic number for compressed bootfs";
93         return ZX_ERR_INVALID_ARGS;
94     }
95     data += sizeof(uint32_t);
96 
97     zx_status_t status = check_lz4_frame((const lz4_frame_desc*)data, _outsize, err);
98     if (status < 0)
99         return status;
100     data += sizeof(lz4_frame_desc);
101 
102     size_t outsize = (_outsize + 4095) & ~4095;
103     if (outsize < _outsize) {
104         // newsize wrapped, which means the outsize was too large
105         *err = "lz4 output size too large";
106         return ZX_ERR_NO_MEMORY;
107     }
108     zx_handle_t dst_vmo;
109     status = zx_vmo_create((uint64_t)outsize, 0, &dst_vmo);
110     if (status < 0) {
111         *err = "zx_vmo_create failed for decompressing bootfs";
112         return status;
113     }
114     zx_object_set_property(dst_vmo, ZX_PROP_NAME, "bootfs", 6);
115 
116     uintptr_t dst_addr = 0;
117     status = zx_vmar_map(vmar,
118             ZX_VM_PERM_READ|ZX_VM_PERM_WRITE,
119             0, dst_vmo, 0, outsize, &dst_addr);
120     if (status < 0) {
121         *err = "zx_vmar_map failed on bootfs vmo during decompression";
122         return status;
123     }
124 
125     size_t remaining = outsize;
126     uint8_t* dst = (uint8_t*)dst_addr;
127 
128     // Read each LZ4 block and decompress it. Block sizes are 32 bits.
129     uint32_t blocksize = *(const uint32_t*)data;
130     data += sizeof(uint32_t);
131     while (blocksize) {
132         // If the data is uncompressed, the high bit is 1.
133         if (blocksize >> 31) {
134             uint32_t actual = blocksize & 0x7fffffff;
135             memcpy(dst, data, actual);
136             dst += actual;
137             data += actual;
138             if (remaining - actual > remaining) {
139                 // Remaining wrapped around (would be negative if signed)
140                 *err = "bootdata outsize too small for lz4 decompression";
141                 return ZX_ERR_INVALID_ARGS;
142             }
143             remaining -= actual;
144         } else {
145             int dcmp = LZ4_decompress_safe((const char*)data, (char*)dst, blocksize, remaining);
146             if (dcmp < 0) {
147                 *err = "lz4 decompression failed";
148                 return ZX_ERR_BAD_STATE;
149             }
150             dst += dcmp;
151             data += blocksize;
152             if (remaining - dcmp > remaining) {
153                 // Remaining wrapped around (would be negative if signed)
154                 *err = "bootdata outsize too small for lz4 decompression";
155                 return ZX_ERR_INVALID_ARGS;
156             }
157             remaining -= dcmp;
158         }
159 
160         blocksize = *(uint32_t*)data;
161         data += sizeof(uint32_t);
162     }
163 
164     // Sanity check: verify that we didn't have more than one page leftover.
165     // The bootdata header should have specified the exact outsize needed, which
166     // we rounded up to the next full page.
167     if (remaining > 4095) {
168         *err = "bootdata size error; outsize does not match decompressed size";
169         return ZX_ERR_INVALID_ARGS;
170     }
171 
172     status = zx_vmar_unmap(vmar, dst_addr, outsize);
173     if (status < 0) {
174         *err = "zx_vmar_unmap after decompress failed";
175         return status;
176     }
177     *out = dst_vmo;
178     return ZX_OK;
179 }
180 
decompress_bootdata(zx_handle_t vmar,zx_handle_t vmo,size_t offset,size_t length,zx_handle_t * out,const char ** err)181 zx_status_t decompress_bootdata(zx_handle_t vmar, zx_handle_t vmo,
182                                 size_t offset, size_t length,
183                                 zx_handle_t* out, const char** err) {
184     *err = "none";
185 
186     if (length > SIZE_MAX) {
187         *err = "bootfs VMO too large to map";
188         return ZX_ERR_BUFFER_TOO_SMALL;
189     }
190 
191     uintptr_t addr = 0;
192     size_t aligned_offset = offset & ~(PAGE_SIZE - 1);
193     size_t align_shift = offset - aligned_offset;
194     length += align_shift;
195     zx_status_t status = zx_vmar_map(vmar, ZX_VM_PERM_READ, 0, vmo, aligned_offset, length, &addr);
196     if (status < 0) {
197         *err = "zx_vmar_map failed on bootfs vmo";
198         return status;
199     }
200     uintptr_t bootdata_addr = addr + align_shift;
201 
202     const bootdata_t* hdr = (bootdata_t*)bootdata_addr;
203     bootdata_addr += sizeof(bootdata_t);
204 
205     switch (hdr->type) {
206     case BOOTDATA_BOOTFS_BOOT:
207     case BOOTDATA_BOOTFS_SYSTEM:
208     case BOOTDATA_RAMDISK:
209         if (hdr->flags & BOOTDATA_BOOTFS_FLAG_COMPRESSED) {
210             status = decompress_bootfs_vmo(vmar, (const uint8_t*)bootdata_addr, hdr->extra, out, err);
211         }
212         break;
213     default:
214         *err = "unknown bootdata type, not attempting decompression\n";
215         status = ZX_ERR_NOT_SUPPORTED;
216         break;
217     }
218 
219     zx_status_t s = zx_vmar_unmap(vmar, addr, length);
220     if (s < 0) {
221         *err = "zx_vmar_unmap failed on bootfs vmo";
222         return s;
223     }
224 
225     return status;
226 }
227