Skip to content

Commit

Permalink
kexec: Introduce UKI image parser
Browse files Browse the repository at this point in the history
A UKI image is a PE file that consists of several sections, typically
including: .text, .data, .linux, .initrd, .cmdline, and others.

The kernel image is stored in the .linux section, which is one of the
formats currently recognized by kexec-tools. Therefore, the UKI parser
can be used to strip away the UKI layer, allowing the other parser to
continue the procession of the kernel image.

Signed-off-by: Pingfan Liu <[email protected]>
Signed-off-by: Simon Horman <[email protected]>
  • Loading branch information
Pingfan Liu authored and horms committed Oct 21, 2024
1 parent 14fde16 commit 0c72c44
Show file tree
Hide file tree
Showing 5 changed files with 232 additions and 0 deletions.
1 change: 1 addition & 0 deletions include/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ dist += include/Makefile \
include/x86/mb_header.h \
include/x86/multiboot2.h \
include/elf.h \
include/pe.h \
include/image.h \
include/unused.h \
include/boot/linuxbios_tables.h \
Expand Down
104 changes: 104 additions & 0 deletions include/pe.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Extract from linux kernel include/linux/pe.h
*/

#ifndef __PE_H__
#define __PE_H__

struct pe_hdr {
uint32_t magic; /* PE magic */
uint16_t machine; /* machine type */
uint16_t sections; /* number of sections */
uint32_t timestamp; /* time_t */
uint32_t symbol_table; /* symbol table offset */
uint32_t symbols; /* number of symbols */
uint16_t opt_hdr_size; /* size of optional header */
uint16_t flags; /* flags */
};

/* the fact that pe32 isn't padded where pe32+ is 64-bit means union won't
* work right. vomit. */
struct pe32_opt_hdr {
/* "standard" header */
uint16_t magic; /* file type */
uint8_t ld_major; /* linker major version */
uint8_t ld_minor; /* linker minor version */
uint32_t text_size; /* size of text section(s) */
uint32_t data_size; /* size of data section(s) */
uint32_t bss_size; /* size of bss section(s) */
uint32_t entry_point; /* file offset of entry point */
uint32_t code_base; /* relative code addr in ram */
uint32_t data_base; /* relative data addr in ram */
/* "windows" header */
uint32_t image_base; /* preferred load address */
uint32_t section_align; /* alignment in bytes */
uint32_t file_align; /* file alignment in bytes */
uint16_t os_major; /* major OS version */
uint16_t os_minor; /* minor OS version */
uint16_t image_major; /* major image version */
uint16_t image_minor; /* minor image version */
uint16_t subsys_major; /* major subsystem version */
uint16_t subsys_minor; /* minor subsystem version */
uint32_t win32_version; /* reserved, must be 0 */
uint32_t image_size; /* image size */
uint32_t header_size; /* header size rounded up to
file_align */
uint32_t csum; /* checksum */
uint16_t subsys; /* subsystem */
uint16_t dll_flags; /* more flags! */
uint32_t stack_size_req;/* amt of stack requested */
uint32_t stack_size; /* amt of stack required */
uint32_t heap_size_req; /* amt of heap requested */
uint32_t heap_size; /* amt of heap required */
uint32_t loader_flags; /* reserved, must be 0 */
uint32_t data_dirs; /* number of data dir entries */
};

struct pe32plus_opt_hdr {
uint16_t magic; /* file type */
uint8_t ld_major; /* linker major version */
uint8_t ld_minor; /* linker minor version */
uint32_t text_size; /* size of text section(s) */
uint32_t data_size; /* size of data section(s) */
uint32_t bss_size; /* size of bss section(s) */
uint32_t entry_point; /* file offset of entry point */
uint32_t code_base; /* relative code addr in ram */
/* "windows" header */
uint64_t image_base; /* preferred load address */
uint32_t section_align; /* alignment in bytes */
uint32_t file_align; /* file alignment in bytes */
uint16_t os_major; /* major OS version */
uint16_t os_minor; /* minor OS version */
uint16_t image_major; /* major image version */
uint16_t image_minor; /* minor image version */
uint16_t subsys_major; /* major subsystem version */
uint16_t subsys_minor; /* minor subsystem version */
uint32_t win32_version; /* reserved, must be 0 */
uint32_t image_size; /* image size */
uint32_t header_size; /* header size rounded up to
file_align */
uint32_t csum; /* checksum */
uint16_t subsys; /* subsystem */
uint16_t dll_flags; /* more flags! */
uint64_t stack_size_req;/* amt of stack requested */
uint64_t stack_size; /* amt of stack required */
uint64_t heap_size_req; /* amt of heap requested */
uint64_t heap_size; /* amt of heap required */
uint32_t loader_flags; /* reserved, must be 0 */
uint32_t data_dirs; /* number of data dir entries */
};

struct section_header {
char name[8]; /* name or "/12\0" string tbl offset */
uint32_t virtual_size; /* size of loaded section in ram */
uint32_t virtual_address; /* relative virtual address */
uint32_t raw_data_size; /* size of the section */
uint32_t data_addr; /* file pointer to first page of sec */
uint32_t relocs; /* file pointer to relocation entries */
uint32_t line_numbers; /* line numbers! */
uint16_t num_relocs; /* number of relocations */
uint16_t num_lin_numbers; /* srsly. */
uint32_t flags;
};

#endif
1 change: 1 addition & 0 deletions kexec/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ KEXEC_SRCS_base += kexec/kexec-elf-core.c
KEXEC_SRCS_base += kexec/kexec-elf-rel.c
KEXEC_SRCS_base += kexec/kexec-elf-boot.c
KEXEC_SRCS_base += kexec/kexec-pe-zboot.c
KEXEC_SRCS_base += kexec/kexec-uki.c
KEXEC_SRCS_base += kexec/kexec-iomem.c
KEXEC_SRCS_base += kexec/firmware_memmap.c
KEXEC_SRCS_base += kexec/crashdump.c
Expand Down
122 changes: 122 additions & 0 deletions kexec/kexec-uki.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#include <limits.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <pe.h>
#include "kexec.h"

#define UKI_LINUX_SECTION ".linux"
#define UKI_INITRD_SECTION ".initrd"
#define UKI_CMDLINE_SECTION ".cmdline"
#define UKI_DTB_SECTION ".dtb"

#define FILENAME_UKI_INITRD "/tmp/InitrdXXXXXX"

static int embeded_linux_format_index = -1;

/*
* Return -1 if not PE, else offset of the PE header
*/
static int get_pehdr_offset(const char *buf)
{
int pe_hdr_offset;

pe_hdr_offset = *((int *)(buf + 0x3c));
buf += pe_hdr_offset;
if (!!memcmp(buf, "PE\0\0", 4)) {
printf("Not a PE file\n");
return -1;
}

return pe_hdr_offset;
}

int uki_image_probe(const char *file_buf, off_t buf_sz)
{
struct pe_hdr *pe_hdr;
struct pe32plus_opt_hdr *opt_hdr;
struct section_header *sect_hdr;
int pe_hdr_offset, section_nr, linux_sz = -1;
char *pe_part_buf, *linux_src;
char *initrd_fname = NULL;
int initrd_fd = -1;

pe_hdr_offset = get_pehdr_offset(file_buf);
pe_part_buf = (char *)file_buf + pe_hdr_offset;
pe_hdr = (struct pe_hdr *)pe_part_buf;
if (pe_hdr->opt_hdr_size == 0) {
printf("ERR: optional header is missing\n");
return -1;
}
section_nr = pe_hdr->sections;
opt_hdr = (struct pe32plus_opt_hdr *)(pe_part_buf + sizeof(struct pe_hdr));
sect_hdr = (struct section_header *)((char *)opt_hdr + pe_hdr->opt_hdr_size);

for (int i = 0; i < section_nr; i++) {
if (!strcmp(sect_hdr->name, UKI_LINUX_SECTION)) {
/* data_addr is relative to the whole file */
linux_src = (char *)file_buf + sect_hdr->data_addr;
linux_sz = sect_hdr->raw_data_size;

} else if (!strcmp(sect_hdr->name, UKI_INITRD_SECTION)) {
if (!(initrd_fname = strdup(FILENAME_UKI_INITRD))) {
dbgprintf("%s: Can't duplicate strings\n", __func__);
goto next;
}

if ((initrd_fd = mkstemp(initrd_fname)) < 0) {
dbgprintf("%s: Can't open file %s\n", __func__, initrd_fname);
goto next;
}

if (write(initrd_fd, (char *)file_buf + sect_hdr->data_addr,
sect_hdr->raw_data_size) != sect_hdr->raw_data_size) {
dbgprintf("%s: Can't write the compressed file %s\n",
__func__, initrd_fname);
goto next;
} else {
implicit_initrd_fd = open(initrd_fname, O_RDONLY);
close(initrd_fd);
}
}
next:
sect_hdr++;
}

if (linux_sz == -1) {
printf("ERR: can not find .linux section\n");
return -1;
}
/*
* After stripping the UKI coat, the real kernel format can be handled now.
*/
for (int i = 0; i < file_types; i++) {
/* kernel_fd will be created by probe */
if (file_type[i].probe != uki_image_probe &&
file_type[i].probe(linux_src, linux_sz) >= 0) {
embeded_linux_format_index = i;
break;
}
}
if (embeded_linux_format_index < 0) {
printf("Can not recognize the kernel format in .linux section\n");
return -1;
}
return 0;
}

int uki_image_load(int argc, char **argv, const char *buf, off_t len,
struct kexec_info *info)
{
return file_type[embeded_linux_format_index].load(argc, argv, buf, len, info);
}

void uki_image_usage(void)
{
printf(
" An UKI image.\n");
}
4 changes: 4 additions & 0 deletions kexec/kexec.h
Original file line number Diff line number Diff line change
Expand Up @@ -357,4 +357,8 @@ static inline void ultoa(unsigned long val, char *str)
str[pos] = 0;
}

extern int uki_image_probe(const char *file_buf, off_t buf_sz);
extern int uki_image_load(int argc, char **argv, const char *buf, off_t len,
struct kexec_info *info);
extern void uki_image_usage(void);
#endif /* KEXEC_H */

0 comments on commit 0c72c44

Please sign in to comment.