xref: /aosp_15_r20/external/coreboot/util/cbfstool/cbfs-mkpayload.c (revision b9411a12aaaa7e1e6a6fb7c5e057f44ee179a49c)
1 /* SPDX-License-Identifier: GPL-2.0-only */
2 
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <commonlib/endian.h>
7 
8 #include "elfparsing.h"
9 #include "common.h"
10 #include "cbfs.h"
11 #include "fv.h"
12 #include "coff.h"
13 #include "fdt.h"
14 
15 /* serialize the seg array into the buffer.
16  * The buffer is assumed to be large enough.
17  */
xdr_segs(struct buffer * output,struct cbfs_payload_segment * segs,int nseg)18 void xdr_segs(struct buffer *output,
19 	struct cbfs_payload_segment *segs, int nseg)
20 {
21 	struct buffer outheader;
22 	int i;
23 
24 	outheader.data = output->data;
25 	outheader.size = 0;
26 
27 	for(i = 0; i < nseg; i++){
28 		xdr_be.put32(&outheader, segs[i].type);
29 		xdr_be.put32(&outheader, segs[i].compression);
30 		xdr_be.put32(&outheader, segs[i].offset);
31 		xdr_be.put64(&outheader, segs[i].load_addr);
32 		xdr_be.put32(&outheader, segs[i].len);
33 		xdr_be.put32(&outheader, segs[i].mem_len);
34 	}
35 }
36 
xdr_get_seg(struct cbfs_payload_segment * out,struct cbfs_payload_segment * in)37 void xdr_get_seg(struct cbfs_payload_segment *out,
38 		struct cbfs_payload_segment *in)
39 {
40 	struct buffer inheader;
41 
42 	inheader.data = (void *)in;
43 	inheader.size = sizeof(*in);
44 
45 	out->type = xdr_be.get32(&inheader);
46 	out->compression = xdr_be.get32(&inheader);
47 	out->offset = xdr_be.get32(&inheader);
48 	out->load_addr = xdr_be.get64(&inheader);
49 	out->len = xdr_be.get32(&inheader);
50 	out->mem_len = xdr_be.get32(&inheader);
51 }
52 
parse_elf_to_payload(const struct buffer * input,struct buffer * output,enum cbfs_compression algo)53 int parse_elf_to_payload(const struct buffer *input, struct buffer *output,
54 			 enum cbfs_compression algo)
55 {
56 	Elf64_Phdr *phdr;
57 	Elf64_Ehdr ehdr;
58 	char *header;
59 	int headers;
60 	int segments = 1;
61 	int isize = 0, osize = 0;
62 	int doffset = 0;
63 	struct cbfs_payload_segment *segs = NULL;
64 	int i;
65 	int ret = 0;
66 
67 	comp_func_ptr compress = compression_function(algo);
68 	if (!compress)
69 		return -1;
70 
71 	if (elf_headers(input, &ehdr, &phdr, NULL) < 0)
72 		return -1;
73 
74 	DEBUG("start: parse_elf_to_payload\n");
75 	headers = ehdr.e_phnum;
76 	header = input->data;
77 
78 	/* Count the number of segment headers - we only care about PT_LOAD
79 	   headers, because that's what we're actually going to load */
80 	for (i = 0; i < headers; i++) {
81 		if (phdr[i].p_type != PT_LOAD)
82 			continue;
83 
84 		/* Empty segments are never interesting */
85 		if (phdr[i].p_memsz == 0)
86 			continue;
87 
88 		isize += phdr[i].p_filesz;
89 
90 		segments++;
91 	}
92 	/* Allocate and initialize the segment header array */
93 	segs = calloc(segments, sizeof(*segs));
94 	if (segs == NULL) {
95 		ret = -1;
96 		goto out;
97 	}
98 	/* Allocate a block of memory to store the data in */
99 	if (buffer_create(output, (segments * sizeof(*segs)) + isize,
100 			  input->name) != 0) {
101 		ret = -1;
102 		goto out;
103 	}
104 	memset(output->data, 0, output->size);
105 
106 	doffset = (segments * sizeof(*segs));
107 
108 	/* set up for output marshaling. This is a bit
109 	 * tricky as we are marshaling the headers at the front,
110 	 * and the data starting after the headers. We need to convert
111 	 * the headers to the right format but the data
112 	 * passes through unchanged. Unlike most XDR code,
113 	 * we are doing these two concurrently. The doffset is
114 	 * used to compute the address for the raw data, and the
115 	 * outheader is used to marshal the headers. To make it simpler
116 	 * for The Reader, we set up the headers in a separate array,
117 	 * then marshal them all at once to the output.
118 	 */
119 	segments = 0;
120 
121 	for (i = 0; i < headers; i++) {
122 		if (phdr[i].p_type != PT_LOAD)
123 			continue;
124 		if (phdr[i].p_memsz == 0)
125 			continue;
126 		if (phdr[i].p_filesz == 0) {
127 			segs[segments].type = PAYLOAD_SEGMENT_BSS;
128 			segs[segments].load_addr = phdr[i].p_paddr;
129 			segs[segments].mem_len = phdr[i].p_memsz;
130 			segs[segments].offset = doffset;
131 
132 			segments++;
133 			continue;
134 		}
135 
136 		if (phdr[i].p_flags & PF_X)
137 			segs[segments].type = PAYLOAD_SEGMENT_CODE;
138 		else
139 			segs[segments].type = PAYLOAD_SEGMENT_DATA;
140 		segs[segments].load_addr = phdr[i].p_paddr;
141 		segs[segments].mem_len = phdr[i].p_memsz;
142 		segs[segments].offset = doffset;
143 
144 		/* If the compression failed or made the section is larger,
145 		   use the original stuff */
146 
147 		int len;
148 		if (compress((char *)&header[phdr[i].p_offset],
149 			     phdr[i].p_filesz, output->data + doffset, &len) ||
150 		    (unsigned int)len > phdr[i].p_filesz) {
151 			WARN("Compression failed or would make the data bigger "
152 			     "- disabled.\n");
153 			segs[segments].compression = 0;
154 			segs[segments].len = phdr[i].p_filesz;
155 			memcpy(output->data + doffset,
156 			       &header[phdr[i].p_offset], phdr[i].p_filesz);
157 		} else {
158 			segs[segments].compression = algo;
159 			segs[segments].len = len;
160 		}
161 
162 		doffset += segs[segments].len;
163 		osize += segs[segments].len;
164 
165 		segments++;
166 	}
167 
168 	segs[segments].type = PAYLOAD_SEGMENT_ENTRY;
169 	segs[segments++].load_addr = ehdr.e_entry;
170 
171 	output->size = (segments * sizeof(*segs)) + osize;
172 	xdr_segs(output, segs, segments);
173 
174 out:
175 	if (segs) free(segs);
176 	if (phdr) free(phdr);
177 	return ret;
178 }
179 
parse_flat_binary_to_payload(const struct buffer * input,struct buffer * output,uint64_t loadaddress,uint64_t entrypoint,enum cbfs_compression algo)180 int parse_flat_binary_to_payload(const struct buffer *input,
181 				 struct buffer *output,
182 				 uint64_t loadaddress,
183 				 uint64_t entrypoint,
184 				 enum cbfs_compression algo)
185 {
186 	comp_func_ptr compress;
187 	struct cbfs_payload_segment segs[2] = { {0} };
188 	int doffset, len = 0;
189 
190 	compress = compression_function(algo);
191 	if (!compress)
192 		return -1;
193 
194 	DEBUG("start: parse_flat_binary_to_payload\n");
195 	if (buffer_create(output, (sizeof(segs) + input->size),
196 			  input->name) != 0)
197 		return -1;
198 	memset(output->data, 0, output->size);
199 
200 	doffset = (2 * sizeof(*segs));
201 
202 	/* Prepare code segment */
203 	segs[0].type = PAYLOAD_SEGMENT_CODE;
204 	segs[0].load_addr = loadaddress;
205 	segs[0].mem_len = input->size;
206 	segs[0].offset = doffset;
207 
208 	if (!compress(input->data, input->size, output->data + doffset, &len) &&
209 	    (unsigned int)len < input->size) {
210 		segs[0].compression = algo;
211 		segs[0].len = len;
212 	} else {
213 		WARN("Compression failed or would make the data bigger "
214 		     "- disabled.\n");
215 		segs[0].compression = 0;
216 		segs[0].len = input->size;
217 		memcpy(output->data + doffset, input->data, input->size);
218 	}
219 
220 	/* prepare entry point segment */
221 	segs[1].type = PAYLOAD_SEGMENT_ENTRY;
222 	segs[1].load_addr = entrypoint;
223 	output->size = doffset + segs[0].len;
224 	xdr_segs(output, segs, 2);
225 	return 0;
226 }
227 
parse_fv_to_payload(const struct buffer * input,struct buffer * output,enum cbfs_compression algo)228 int parse_fv_to_payload(const struct buffer *input, struct buffer *output,
229 			enum cbfs_compression algo)
230 {
231 	comp_func_ptr compress;
232 	struct cbfs_payload_segment segs[2] = { {0} };
233 	int doffset, len = 0;
234 	firmware_volume_header_t *fv;
235 	firmware_volume_ext_header_t *fvh_ext;
236 	ffs_file_header_t *fh;
237 	common_section_header_t *cs;
238 	dos_header_t *dh;
239 	coff_header_t *ch;
240 	int dh_offset;
241 
242 	uint32_t loadaddress = 0;
243 	uint32_t entrypoint = 0;
244 
245 	compress = compression_function(algo);
246 	if (!compress)
247 		return -1;
248 
249 	DEBUG("start: parse_fv_to_payload\n");
250 
251 	fv = (firmware_volume_header_t *)input->data;
252 	if (fv->signature != FV_SIGNATURE) {
253 		INFO("Not a UEFI firmware volume.\n");
254 		return -1;
255 	}
256 
257 	fh = (ffs_file_header_t *)(input->data + fv->header_length);
258 	if (fv->ext_header_offs != 0) {
259 		fvh_ext = (firmware_volume_ext_header_t *)((uintptr_t)fv + fv->ext_header_offs);
260 		fh = (ffs_file_header_t *)((uintptr_t)fvh_ext + fvh_ext->ext_header_size);
261 		fh = (ffs_file_header_t *)(((uintptr_t)fh + 7) & ~7);
262 	}
263 
264 	while (fh->file_type == FILETYPE_PAD) {
265 		unsigned long offset = (fh->size[2] << 16) | (fh->size[1] << 8) | fh->size[0];
266 		DEBUG("skipping %lu bytes of FV padding\n", offset);
267 		fh = (ffs_file_header_t *)(((uintptr_t)fh) + offset);
268 	}
269 	if (fh->file_type != FILETYPE_SEC) {
270 		ERROR("Not a usable UEFI firmware volume.\n");
271 		INFO("First file in first FV not a SEC core.\n");
272 		return -1;
273 	}
274 
275 	cs = (common_section_header_t *)&fh[1];
276 	while (cs->section_type == SECTION_RAW) {
277 		unsigned long offset = (cs->size[2] << 16) | (cs->size[1] << 8) | cs->size[0];
278 		DEBUG("skipping %lu bytes of section padding\n", offset);
279 		cs = (common_section_header_t *)(((uintptr_t)cs) + offset);
280 	}
281 	if (cs->section_type != SECTION_PE32) {
282 		ERROR("Not a usable UEFI firmware volume.\n");
283 		INFO("Section type not PE32.\n");
284 		return -1;
285 	}
286 
287 	dh = (dos_header_t *)&cs[1];
288 	if (dh->signature != DOS_MAGIC) {
289 		ERROR("Not a usable UEFI firmware volume.\n");
290 		INFO("DOS header signature wrong.\n");
291 		return -1;
292 	}
293 
294 	dh_offset = (unsigned long)dh - (unsigned long)input->data;
295 	DEBUG("dos header offset = %x\n", dh_offset);
296 
297 	ch = (coff_header_t *)(((uintptr_t)dh)+dh->e_lfanew);
298 
299 	if (ch->machine == MACHINE_TYPE_X86) {
300 		pe_opt_header_32_t *ph;
301 		ph = (pe_opt_header_32_t *)&ch[1];
302 		if (ph->signature != PE_HDR_32_MAGIC) {
303 			WARN("PE header signature incorrect.\n");
304 			return -1;
305 		}
306 		DEBUG("image base %x\n", ph->image_addr);
307 		DEBUG("entry point %x\n", ph->entry_point);
308 
309 		loadaddress = ph->image_addr - dh_offset;
310 		entrypoint = ph->image_addr + ph->entry_point;
311 	} else if (ch->machine == MACHINE_TYPE_X64 || ch->machine == MACHINE_TYPE_ARM64) {
312 		pe_opt_header_64_t *ph;
313 		ph = (pe_opt_header_64_t *)&ch[1];
314 		if (ph->signature != PE_HDR_64_MAGIC) {
315 			WARN("PE header signature incorrect.\n");
316 			return -1;
317 		}
318 		DEBUG("image base %lx\n", (unsigned long)ph->image_addr);
319 		DEBUG("entry point %x\n", ph->entry_point);
320 
321 		loadaddress = ph->image_addr - dh_offset;
322 		entrypoint = ph->image_addr + ph->entry_point;
323 	} else {
324 		ERROR("Machine type not x86, x64, or arm64.\n");
325 		return -1;
326 	}
327 
328 	if (buffer_create(output, (sizeof(segs) + input->size),
329 			  input->name) != 0)
330 		return -1;
331 
332 	memset(output->data, 0, output->size);
333 
334 	doffset = (sizeof(segs));
335 
336 	/* Prepare code segment */
337 	segs[0].type = PAYLOAD_SEGMENT_CODE;
338 	segs[0].load_addr = loadaddress;
339 	segs[0].mem_len = input->size;
340 	segs[0].offset = doffset;
341 
342 	if (!compress(input->data, input->size, output->data + doffset, &len) &&
343 	    (unsigned int)len < input->size) {
344 		segs[0].compression = algo;
345 		segs[0].len = len;
346 	} else {
347 		WARN("Compression failed or would make the data bigger "
348 		     "- disabled.\n");
349 		segs[0].compression = 0;
350 		segs[0].len = input->size;
351 		memcpy(output->data + doffset, input->data, input->size);
352 	}
353 
354 	/* prepare entry point segment */
355 	segs[1].type = PAYLOAD_SEGMENT_ENTRY;
356 	segs[1].load_addr = entrypoint;
357 	output->size = doffset + segs[0].len;
358 	xdr_segs(output, segs, 2);
359 	return 0;
360 
361 }
362 
parse_fit_to_payload(const struct buffer * input,struct buffer * output,enum cbfs_compression algo)363 int parse_fit_to_payload(const struct buffer *input, struct buffer *output,
364 			 enum cbfs_compression algo)
365 {
366 	struct fdt_header *fdt_h;
367 
368 	DEBUG("start: parse_fit_to_payload\n");
369 
370 	fdt_h = buffer_get(input);
371 	if (read_be32(&fdt_h->magic) != FDT_HEADER_MAGIC) {
372 		INFO("Not a FIT payload.\n");
373 		return -1;
374 	}
375 
376 	/**
377 	 * For developers:
378 	 * Compress the kernel binary you're sourcing in your its-script
379 	 * manually with LZ4 or LZMA and add 'compression = "lz4"' or "lzma" to
380 	 * the kernel@1 node in the its-script before assembling the image with
381 	 * mkimage.
382 	 */
383 	if (algo != CBFS_COMPRESS_NONE) {
384 		ERROR("FIT images don't support whole-image compression,"
385 		      " compress the kernel component instead!\n");
386 		return -1;
387 	}
388 
389 	if (buffer_create(output, buffer_size(input), input->name) != 0)
390 		return -1;
391 
392 	memcpy(buffer_get(output), buffer_get(input), buffer_size(input));
393 
394 	DEBUG("done\n");
395 
396 	return 0;
397 }
398