xref: /DragonStub/apps/elf.c (revision 552e14d5a5945181a72f94733b02111e4c48f74e)
1 #include "elf.h"
2 #include "dragonstub/linux-efi.h"
3 #include "dragonstub/linux/align.h"
4 #include "dragonstub/printk.h"
5 #include "dragonstub/riscv64.h"
6 #include "dragonstub/types.h"
7 #include "efidef.h"
8 #include <efi.h>
9 #include <efiapi.h>
10 #include <efidevp.h>
11 #include <efilib.h>
12 #include <dragonstub/dragonstub.h>
13 #include <dragonstub/elfloader.h>
14 
15 /// @brief 校验ELF文件头
16 /// @param buf 缓冲区
17 /// @param bufsize 缓冲区大小
18 /// @return
verify_ident(const void * buf,u64 bufsize)19 static bool verify_ident(const void *buf, u64 bufsize)
20 {
21 	if (bufsize < EI_NIDENT) {
22 		// 太短,不是ELF
23 		return false;
24 	}
25 	// 检查magic number
26 	for (int i = 0; i < EI_CLASS; i++) {
27 		u8 c = *(u8 *)(buf + i);
28 		if (c != ELFMAG[i]) {
29 			// 不是ELF magic number,跳过
30 			efi_err("ELF magic number not match\n");
31 			return false;
32 		}
33 	}
34 
35 	// verify ELF Version
36 	u8 version = *(u8 *)(buf + EI_VERSION);
37 	if (version != EV_CURRENT) {
38 		efi_err("ELF version not match, expected EV_CURRENT(%d), got %d\n",
39 			EV_CURRENT, version);
40 		// 不是当前版本,跳过
41 		return false;
42 	}
43 
44 	// verify ELF Class
45 	u8 class = *(u8 *)(buf + EI_CLASS);
46 	if (class != ELFCLASS64) {
47 		efi_err("ELF class not match, expected ELFCLASS64(%d), got %d\n",
48 			ELFCLASS64, class);
49 		// 不是64位,跳过
50 		return false;
51 	}
52 
53 	return true;
54 }
55 
elf_check(const void * payload_start,u64 payload_size)56 bool elf_check(const void *payload_start, u64 payload_size)
57 {
58 	// 校验ELF文件头
59 	if (!verify_ident(payload_start, payload_size)) {
60 		return false;
61 	}
62 	// 检查架构
63 	Elf64_Ehdr *ehdr = (Elf64_Ehdr *)payload_start;
64 #ifdef CONFIG_riscv64
65 	if (ehdr->e_machine != EM_RISCV) {
66 		efi_err("ELF machine not match, expected EM_RISCV(%d), got %d\n",
67 			EM_RISCV, ehdr->e_machine);
68 		return false;
69 	}
70 #else
71 // 还没有对当前架构进行检查,抛出编译错误
72 #error "Unimplement ELF arch test for current cross compile arch"
73 #endif
74 	return true;
75 }
76 
77 /// @brief 获取ELF文件头
78 /// @param payload_start 文件起始地址
79 /// @param payload_size 文件大小
80 /// @param ehdr 返回的ELF文件头
81 /// @return
elf_get_header(const void * payload_start,u64 payload_size,Elf64_Ehdr ** ehdr)82 efi_status_t elf_get_header(const void *payload_start, u64 payload_size,
83 			    Elf64_Ehdr **ehdr)
84 {
85 	if (!verify_ident(payload_start, payload_size)) {
86 		return EFI_INVALID_PARAMETER;
87 	}
88 	*ehdr = (Elf64_Ehdr *)payload_start;
89 	return EFI_SUCCESS;
90 }
91 
print_elf_info(Elf64_Ehdr * ehdr)92 static void print_elf_info(Elf64_Ehdr *ehdr)
93 {
94 	efi_info("ELF header:\n");
95 	efi_printk("  e_type: %d\n", ehdr->e_type);
96 	efi_printk("  e_machine: %d\n", ehdr->e_machine);
97 	efi_printk("  e_version: %d\n", ehdr->e_version);
98 	efi_printk("  e_entry: %p\n", ehdr->e_entry);
99 	efi_printk("  e_phoff: %p\n", ehdr->e_phoff);
100 	efi_printk("  e_shoff: %p\n", ehdr->e_shoff);
101 	efi_printk("  e_flags: %d\n", ehdr->e_flags);
102 	efi_printk("  e_ehsize: %d\n", ehdr->e_ehsize);
103 	efi_printk("  e_phentsize: %d\n", ehdr->e_phentsize);
104 	efi_printk("  e_phnum: %d\n", ehdr->e_phnum);
105 	efi_printk("  e_shentsize: %d\n", ehdr->e_shentsize);
106 	efi_printk("  e_shnum: %d\n", ehdr->e_shnum);
107 	efi_printk("  e_shstrndx: %d\n", ehdr->e_shstrndx);
108 }
109 
parse_phdrs(const void * payload_start,u64 payload_size,const Elf64_Ehdr * ehdr,u32 * ret_segments_nr,Elf64_Phdr ** ret_phdr)110 static efi_status_t parse_phdrs(const void *payload_start, u64 payload_size,
111 				const Elf64_Ehdr *ehdr, u32 *ret_segments_nr,
112 				Elf64_Phdr **ret_phdr)
113 {
114 	if (ehdr->e_phnum == 0) {
115 		efi_err("No program header\n");
116 		return EFI_INVALID_PARAMETER;
117 	}
118 	if (ehdr->e_phentsize != sizeof(Elf64_Phdr)) {
119 		efi_err("Invalid program header size: %d, expected %d\n",
120 			ehdr->e_phentsize, sizeof(Elf64_Phdr));
121 		return EFI_INVALID_PARAMETER;
122 	}
123 
124 	u16 phnum = ehdr->e_phnum;
125 	if (phnum == PN_XNUM) {
126 		u64 shoff = ehdr->e_shoff;
127 		if (shoff == 0) {
128 			efi_err("No section header\n");
129 			return EFI_INVALID_PARAMETER;
130 		}
131 
132 		if (shoff + sizeof(Elf64_Shdr) > payload_size) {
133 			efi_err("Section header out of range\n");
134 			return EFI_INVALID_PARAMETER;
135 		}
136 
137 		Elf64_Shdr *shdr = (Elf64_Shdr *)(payload_start + shoff);
138 
139 		phnum = shdr[0].sh_info;
140 		if (phnum == 0) {
141 			efi_err("shdr[0].sh_info indicates no program header\n");
142 			return EFI_INVALID_PARAMETER;
143 		}
144 	}
145 
146 	size_t phoff = ehdr->e_phoff;
147 	size_t phsize = ehdr->e_phentsize;
148 	size_t total_size = phnum * phsize;
149 	if (phoff + total_size > payload_size) {
150 		efi_err("Program header out of range\n");
151 		return EFI_INVALID_PARAMETER;
152 	}
153 
154 	Elf64_Phdr *phdr = (Elf64_Phdr *)(payload_start + phoff);
155 
156 	*ret_segments_nr = phnum;
157 	*ret_phdr = phdr;
158 
159 	return EFI_SUCCESS;
160 }
161 
162 /*
163  * Distro versions of GRUB may ignore the BSS allocation entirely (i.e., fail
164  * to provide space, and fail to zero it). Check for this condition by double
165  * checking that the first and the last byte of the image are covered by the
166  * same EFI memory map entry.
167  */
check_image_region(u64 base,u64 size)168 static bool check_image_region(u64 base, u64 size)
169 {
170 	struct efi_boot_memmap *map;
171 	efi_status_t status;
172 	bool ret = false;
173 	u64 map_offset;
174 
175 	status = efi_get_memory_map(&map, false);
176 	if (status != EFI_SUCCESS)
177 		return false;
178 
179 	for (map_offset = 0; map_offset < map->map_size;
180 	     map_offset += map->desc_size) {
181 		efi_memory_desc_t *md = (void *)map->map + map_offset;
182 		u64 end = md->PhysicalStart + md->NumberOfPages * EFI_PAGE_SIZE;
183 
184 		/*
185 		 * Find the region that covers base, and return whether
186 		 * it covers base+size bytes.
187 		 */
188 		if (base >= md->PhysicalStart && base < end) {
189 			ret = (base + size) <= end;
190 			break;
191 		}
192 	}
193 
194 	efi_bs_call(FreePool, map);
195 
196 	return ret;
197 }
198 
199 /**
200  * efi_remap_image_all_rwx - Remap a loaded image with the appropriate permissions
201  *                   for code and data
202  *
203  * @image_base:	the base of the image in memory
204  * @alloc_size:	the size of the area in memory occupied by the image
205  *
206  * efi_remap_image() uses the EFI memory attribute protocol to remap the code
207  * region of the loaded image read-only/executable, and the remainder
208  * read-write/non-executable. The code region is assumed to start at the base
209  * of the image, and will therefore cover the PE/COFF header as well.
210  */
efi_remap_image_all_rwx(unsigned long image_base,unsigned alloc_size)211 void efi_remap_image_all_rwx(unsigned long image_base, unsigned alloc_size)
212 {
213 	efi_guid_t guid = EFI_MEMORY_ATTRIBUTE_PROTOCOL_GUID;
214 	efi_memory_attribute_protocol_t *memattr;
215 	efi_status_t status;
216 	u64 attr;
217 
218 	/*
219 	 * If the firmware implements the EFI_MEMORY_ATTRIBUTE_PROTOCOL, let's
220 	 * invoke it to remap the text/rodata region of the decompressed image
221 	 * as read-only and the data/bss region as non-executable.
222 	 */
223 	status = efi_bs_call(LocateProtocol, &guid, NULL, (void **)&memattr);
224 	if (status != EFI_SUCCESS)
225 		return;
226 
227 	// Get the current attributes for the entire region
228 	status = memattr->get_memory_attributes(memattr, image_base, alloc_size,
229 						&attr);
230 	if (status != EFI_SUCCESS) {
231 		efi_warn(
232 			"Failed to retrieve memory attributes for image region: 0x%lx\n",
233 			status);
234 		return;
235 	}
236 
237 	efi_debug("Current attributes for image region: 0x%lx\n", attr);
238 
239 	// If the entire region was already mapped as non-exec, clear the
240 	// attribute from the code region. Otherwise, set it on the data
241 	// region.
242 	if (attr & EFI_MEMORY_XP) {
243 		status = memattr->clear_memory_attributes(
244 			memattr, image_base, alloc_size, EFI_MEMORY_XP);
245 		if (status != EFI_SUCCESS)
246 			efi_warn("Failed to remap region executable\n");
247 	}
248 
249 	if (attr & EFI_MEMORY_WP) {
250 		status = memattr->clear_memory_attributes(
251 			memattr, image_base, alloc_size, EFI_MEMORY_WP);
252 		if (status != EFI_SUCCESS)
253 			efi_warn("Failed to remap region writable\n");
254 	}
255 
256 	if (attr & EFI_MEMORY_RP) {
257 		status = memattr->clear_memory_attributes(
258 			memattr, image_base, alloc_size, EFI_MEMORY_RP);
259 		if (status != EFI_SUCCESS)
260 			efi_warn("Failed to remap region readable\n");
261 	}
262 }
263 
efi_allocate_kernel_memory(const Elf64_Phdr * phdr_start,u32 phdrs_nr,u64 * ret_paddr,u64 * ret_size,u64 * ret_min_paddr,u64 * ret_max_paddr,u64 * ret_min_vaddr)264 efi_status_t efi_allocate_kernel_memory(const Elf64_Phdr *phdr_start,
265 					u32 phdrs_nr, u64 *ret_paddr,
266 					u64 *ret_size, u64 *ret_min_paddr,
267 					u64 *ret_max_paddr, u64 *ret_min_vaddr)
268 {
269 	efi_status_t status = EFI_SUCCESS;
270 	const u64 KERNEL_MEM_ALIGN = 1 << 21; // 2MB
271 
272 	const Elf64_Phdr *phdr = phdr_start;
273 
274 	u64 min_paddr = UINT64_MAX;
275 	u64 max_paddr = 0;
276 	u64 min_vaddr = UINT64_MAX;
277 
278 	for (u32 i = 0; i < phdrs_nr; ++i, ++phdr) {
279 		if (phdr->p_type != PT_LOAD) {
280 			continue;
281 		}
282 
283 		if (phdr->p_align & !EFI_PAGE_SIZE) {
284 			efi_err("ELF segment alignment should be multiple of EFI_PAGE_SIZE(%d), but got %d\n",
285 				EFI_PAGE_SIZE, phdr->p_align);
286 			return EFI_INVALID_PARAMETER;
287 		}
288 		min_paddr = min(min_paddr, (u64)phdr->p_paddr);
289 		min_vaddr = min(min_vaddr, (u64)phdr->p_vaddr);
290 		max_paddr =
291 			max(max_paddr, (u64)(phdr->p_paddr + phdr->p_memsz));
292 	}
293 
294 	if (min_paddr & (KERNEL_MEM_ALIGN - 1)) {
295 		efi_err("min_paddr should be aligned to KERNEL_MEM_ALIGN(%d), but got %p\n",
296 			KERNEL_MEM_ALIGN, min_paddr);
297 		return EFI_INVALID_PARAMETER;
298 	}
299 	u64 mem_size = ALIGN_UP(max_paddr - min_paddr, KERNEL_MEM_ALIGN);
300 
301 	status = efi_allocate_pages_aligned(mem_size, ret_paddr, UINT64_MAX,
302 					    KERNEL_MEM_ALIGN, EfiLoaderData);
303 	// status = efi_allocate_pages_exact(mem_size, paddr);
304 	if (status != EFI_SUCCESS) {
305 		efi_err("Failed to allocate pages for ELF segment: status: %d, page_size=%d, min_paddr=%p, max_paddr=%p, mem_size=%d. Maybe an OOM error or section overlaps.\n",
306 			status, KERNEL_MEM_ALIGN, ret_paddr, max_paddr,
307 			mem_size);
308 		return status;
309 	}
310 
311 	*ret_size = mem_size;
312 	*ret_min_paddr = min_paddr;
313 	*ret_max_paddr = max_paddr;
314 	*ret_min_vaddr = min_vaddr;
315 	efi_info("Allocated kernel memory: paddr=%p, mem_size= %d bytes\n",
316 		 *ret_paddr, mem_size);
317 	// zeroed the memory
318 	memset((void *)(*ret_paddr), 0, mem_size);
319 
320 	efi_remap_image_all_rwx(*ret_paddr, mem_size);
321 
322 	return EFI_SUCCESS;
323 }
324 
load_program(const void * payload_start,u64 payload_size,const Elf64_Phdr * phdr_start,u32 phdrs_nr,u64 * ret_program_mem_paddr,u64 * ret_program_mem_size,u64 * ret_min_paddr,u64 * ret_min_vaddr)325 static efi_status_t load_program(const void *payload_start, u64 payload_size,
326 				 const Elf64_Phdr *phdr_start, u32 phdrs_nr,
327 				 u64 *ret_program_mem_paddr,
328 				 u64 *ret_program_mem_size, u64 *ret_min_paddr,
329 				 u64 *ret_min_vaddr)
330 {
331 	efi_status_t status = EFI_SUCCESS;
332 
333 	u64 allocated_paddr = 0;
334 	u64 allocated_size = 0;
335 	u64 min_paddr = 0;
336 	u64 max_paddr = 0;
337 	u64 min_vaddr = 0;
338 	status = efi_allocate_kernel_memory(phdr_start, phdrs_nr,
339 					    &allocated_paddr, &allocated_size,
340 					    &min_paddr, &max_paddr, &min_vaddr);
341 
342 	if (status != EFI_SUCCESS) {
343 		efi_err("Failed to allocate kernel memory\n");
344 		return status;
345 	}
346 
347 	// 清空内存
348 	memset((void *)allocated_paddr, 0, allocated_size);
349 
350 	const Elf64_Phdr *phdr = phdr_start;
351 
352 	for (u32 i = 0; i < phdrs_nr; ++i, ++phdr) {
353 		if (phdr->p_type != PT_LOAD) {
354 			continue;
355 		}
356 
357 		if (phdr->p_align & !EFI_PAGE_SIZE) {
358 			efi_err("ELF segment alignment should be multiple of EFI_PAGE_SIZE(%d), but got %d\n",
359 				EFI_PAGE_SIZE, phdr->p_align);
360 			status = EFI_INVALID_PARAMETER;
361 			goto failed;
362 		}
363 
364 		u64 paddr = phdr->p_paddr;
365 
366 		u64 mem_size = phdr->p_memsz;
367 		u64 file_size = phdr->p_filesz;
368 		u64 file_offset = phdr->p_offset;
369 		// efi_debug(
370 		// 	"loading segment: paddr=%p, mem_size=%d, file_size=%d\n",
371 		// 	paddr, mem_size, file_size);
372 
373 		if (file_offset + file_size > payload_size) {
374 			status = EFI_INVALID_PARAMETER;
375 			goto failed;
376 		}
377 
378 		if (mem_size < file_size) {
379 			status = EFI_INVALID_PARAMETER;
380 			goto failed;
381 		}
382 
383 		if (mem_size == 0) {
384 			continue;
385 		}
386 
387 		memcpy((void *)(allocated_paddr + (paddr - min_paddr)),
388 		       payload_start + file_offset, file_size);
389 
390 		// efi_debug(
391 		// 	"segment loaded: file_offset: %p paddr=%p, mem_size=%p, file_size=%p\n",
392 		// 	file_offset, paddr, mem_size, file_size);
393 	}
394 
395 	*ret_program_mem_paddr = allocated_paddr;
396 	*ret_program_mem_size = allocated_size;
397 	*ret_min_paddr = min_paddr;
398 	*ret_min_vaddr = min_vaddr;
399 
400 	return EFI_SUCCESS;
401 failed:
402 	efi_free(allocated_size, allocated_paddr);
403 	return status;
404 }
405 
load_elf(struct payload_info * payload_info)406 efi_status_t load_elf(struct payload_info *payload_info)
407 {
408 	const void *payload_start = (void *)payload_info->payload_addr;
409 	u64 payload_size = payload_info->payload_size;
410 	Elf64_Ehdr *ehdr = NULL;
411 	efi_status_t status =
412 		elf_get_header(payload_start, payload_size, &ehdr);
413 	if (status != EFI_SUCCESS) {
414 		efi_err("Failed to get ELF header\n");
415 		return status;
416 	}
417 	ASSERT(ehdr != NULL);
418 
419 	print_elf_info(ehdr);
420 
421 	u32 phdrs_nr = 0;
422 	Elf64_Phdr *phdr_start = NULL;
423 
424 	status = parse_phdrs(payload_start, payload_size, ehdr, &phdrs_nr,
425 			     &phdr_start);
426 	if (status != EFI_SUCCESS) {
427 		efi_err("Failed to parse ELF segments\n");
428 		return status;
429 	}
430 
431 	efi_debug("program headers: %d\n", phdrs_nr);
432 
433 	u64 program_paddr = 0;
434 	u64 program_size = 0;
435 	u64 image_link_base_paddr = 0;
436 	u64 image_link_base_vaddr = 0;
437 	load_program(payload_start, payload_size, phdr_start, phdrs_nr,
438 		     &program_paddr, &program_size, &image_link_base_paddr,
439 		     &image_link_base_vaddr);
440 	payload_info->loaded_paddr = program_paddr;
441 	payload_info->loaded_size = program_size;
442 	payload_info->kernel_entry =
443 		ehdr->e_entry - image_link_base_vaddr + program_paddr;
444 
445 	efi_info("loaded_paddr: %p\n", payload_info->loaded_paddr);
446 	efi_info("loaded_size: %p\n", payload_info->loaded_size);
447 	efi_info("ehdr->e_entry: %lx\n", ehdr->e_entry);
448 	efi_info("image_link_base_paddr: %lx\n", image_link_base_paddr);
449 	efi_info("kernel_entry: %lx\n", payload_info->kernel_entry);
450 	// 处理权限问题
451 
452 	efi_remap_image_all_rwx(program_paddr, program_size);
453 	extern void _start(void);
454 	extern void _image_end(void);
455 	u64 image_size = (u64)&_image_end - (u64)&_start;
456 	efi_debug("image_size: %d\n", image_size);
457 	efi_remap_image_all_rwx((u64)&_start, (image_size + 4095) & ~4095);
458 
459 	// 添加地址到efi configuration table
460 
461 	struct dragonstub_payload_efi *tbl = NULL;
462 	status = efi_bs_call(AllocatePool, EfiLoaderData,
463 			     sizeof(struct dragonstub_payload_efi),
464 			     (void **)&tbl);
465 
466 	if (status != EFI_SUCCESS) {
467 		efi_err("Failed to allocate memory for dragonstub_payload_efi\n");
468 		return status;
469 	}
470 
471 	tbl->loaded_addr = payload_info->loaded_paddr;
472 	tbl->size = payload_info->loaded_size;
473 
474 	efi_guid_t dragonstub_payload_efi_guid =
475 		DRAGONSTUB_EFI_PAYLOAD_EFI_GUID;
476 
477 	status = efi_bs_call(InstallConfigurationTable,
478 			     &dragonstub_payload_efi_guid, tbl);
479 
480 	if (status != EFI_SUCCESS) {
481 		efi_err("Failed to install dragonstub_payload_efi\n");
482 		return status;
483 	}
484 
485 	return EFI_SUCCESS;
486 }