From patchwork Thu Dec 15 12:38:10 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Evgeniy Baskov X-Patchwork-Id: 635278 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id ADBBAC4708D for ; Thu, 15 Dec 2022 12:43:06 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229994AbiLOMnF (ORCPT ); Thu, 15 Dec 2022 07:43:05 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:58210 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229810AbiLOMma (ORCPT ); Thu, 15 Dec 2022 07:42:30 -0500 Received: from mail.ispras.ru (mail.ispras.ru [83.149.199.84]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id A0E076157; Thu, 15 Dec 2022 04:42:27 -0800 (PST) Received: from localhost.localdomain (unknown [83.149.199.65]) by mail.ispras.ru (Postfix) with ESMTPSA id BFA0F419E9FF; Thu, 15 Dec 2022 12:42:25 +0000 (UTC) DKIM-Filter: OpenDKIM Filter v2.11.0 mail.ispras.ru BFA0F419E9FF DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=ispras.ru; s=default; t=1671108145; bh=EAbn3Rhsf+S7OSuQie81F1FreXLK1ulF2aNbcqPjOe4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=JxoQ3E4aUoGFvRxdS408noJm/dtCXGZOTUvhcPabuS9xzRxqlbhnRt/D+JrA3+kj5 Vum5MFIlfVgcs7Zny5kNdHbLue7aSxI6ge/qJcYpfMsfZV9ZpuX7vLJKt6GBMbjye6 ChaYoO+ciPJeUVRps9WfoK4NfXiwteHmqLMAdBBA= From: Evgeniy Baskov To: Ard Biesheuvel Cc: Evgeniy Baskov , Borislav Petkov , Andy Lutomirski , Dave Hansen , Ingo Molnar , Peter Zijlstra , Thomas Gleixner , Alexey Khoroshilov , Peter Jones , "Limonciello, Mario" , joeyli , lvc-project@linuxtesting.org, x86@kernel.org, linux-efi@vger.kernel.org, linux-kernel@vger.kernel.org, linux-hardening@vger.kernel.org Subject: [PATCH v4 19/26] x86/build: Cleanup tools/build.c Date: Thu, 15 Dec 2022 15:38:10 +0300 Message-Id: X-Mailer: git-send-email 2.37.4 In-Reply-To: References: MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-efi@vger.kernel.org Use newer C standard. Since kernel requires C99 compiler now, we can make use of the new features to make the core more readable. Use mmap() for reading files also to make things simpler. Replace most magic numbers with defines. Should have no functional changes. This is done in preparation for the next changes that makes generated PE header more spec compliant. Tested-by: Mario Limonciello Tested-by: Peter Jones Signed-off-by: Evgeniy Baskov --- arch/x86/boot/tools/build.c | 387 +++++++++++++++++++++++------------- 1 file changed, 245 insertions(+), 142 deletions(-) diff --git a/arch/x86/boot/tools/build.c b/arch/x86/boot/tools/build.c index bd247692b701..fbc5315af032 100644 --- a/arch/x86/boot/tools/build.c +++ b/arch/x86/boot/tools/build.c @@ -25,20 +25,21 @@ * Substantially overhauled by H. Peter Anvin, April 2007 */ +#include +#include +#include #include -#include #include -#include -#include +#include +#include #include +#include #include -#include -#include + #include +#include -typedef unsigned char u8; -typedef unsigned short u16; -typedef unsigned int u32; +#define round_up(x, n) (((x) + (n) - 1) & ~((n) - 1)) #define DEFAULT_MAJOR_ROOT 0 #define DEFAULT_MINOR_ROOT 0 @@ -48,8 +49,13 @@ typedef unsigned int u32; #define SETUP_SECT_MIN 5 #define SETUP_SECT_MAX 64 +#define PARAGRAPH_SIZE 16 +#define SECTOR_SIZE 512 +#define FILE_ALIGNMENT 512 +#define SECTION_ALIGNMENT 4096 + /* This must be large enough to hold the entire setup */ -u8 buf[SETUP_SECT_MAX*512]; +uint8_t buf[SETUP_SECT_MAX*SECTOR_SIZE]; #define PECOFF_RELOC_RESERVE 0x20 @@ -59,6 +65,52 @@ u8 buf[SETUP_SECT_MAX*512]; #define PECOFF_COMPAT_RESERVE 0x0 #endif +#define RELOC_SECTION_SIZE 10 + +/* PE header has different format depending on the architecture */ +#ifdef CONFIG_X86_64 +typedef struct pe32plus_opt_hdr pe_opt_hdr; +#else +typedef struct pe32_opt_hdr pe_opt_hdr; +#endif + +static inline struct pe_hdr *get_pe_header(uint8_t *buf) +{ + uint32_t pe_offset = get_unaligned_le32(buf+MZ_HEADER_PEADDR_OFFSET); + return (struct pe_hdr *)(buf + pe_offset); +} + +static inline pe_opt_hdr *get_pe_opt_header(uint8_t *buf) +{ + return (pe_opt_hdr *)(get_pe_header(buf) + 1); +} + +static inline struct section_header *get_sections(uint8_t *buf) +{ + pe_opt_hdr *hdr = get_pe_opt_header(buf); + uint32_t n_data_dirs = get_unaligned_le32(&hdr->data_dirs); + uint8_t *sections = (uint8_t *)(hdr + 1) + n_data_dirs*sizeof(struct data_dirent); + return (struct section_header *)sections; +} + +static inline struct data_directory *get_data_dirs(uint8_t *buf) +{ + pe_opt_hdr *hdr = get_pe_opt_header(buf); + return (struct data_directory *)(hdr + 1); +} + +#ifdef CONFIG_EFI_DXE_MEM_ATTRIBUTES +#define SCN_RW (IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE | IMAGE_SCN_ALIGN_4096BYTES) +#define SCN_RX (IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_ALIGN_4096BYTES) +#define SCN_RO (IMAGE_SCN_MEM_READ | IMAGE_SCN_ALIGN_4096BYTES) +#else +/* With memory protection disabled all sections are RWX */ +#define SCN_RW (IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE | \ + IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_ALIGN_4096BYTES) +#define SCN_RX SCN_RW +#define SCN_RO SCN_RW +#endif + static unsigned long efi32_stub_entry; static unsigned long efi64_stub_entry; static unsigned long efi_pe_entry; @@ -70,7 +122,7 @@ static unsigned long _end; /*----------------------------------------------------------------------*/ -static const u32 crctab32[] = { +static const uint32_t crctab32[] = { 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, @@ -125,12 +177,12 @@ static const u32 crctab32[] = { 0x2d02ef8d }; -static u32 partial_crc32_one(u8 c, u32 crc) +static uint32_t partial_crc32_one(uint8_t c, uint32_t crc) { return crctab32[(crc ^ c) & 0xff] ^ (crc >> 8); } -static u32 partial_crc32(const u8 *s, int len, u32 crc) +static uint32_t partial_crc32(const uint8_t *s, int len, uint32_t crc) { while (len--) crc = partial_crc32_one(*s++, crc); @@ -152,57 +204,106 @@ static void usage(void) die("Usage: build setup system zoffset.h image"); } +static void *map_file(const char *path, size_t *psize) +{ + struct stat statbuf; + size_t size; + void *addr; + int fd; + + fd = open(path, O_RDONLY); + if (fd < 0) + die("Unable to open `%s': %m", path); + if (fstat(fd, &statbuf)) + die("Unable to stat `%s': %m", path); + + size = statbuf.st_size; + /* + * Map one byte more, to allow adding null-terminator + * for text files. + */ + addr = mmap(NULL, size + 1, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); + if (addr == MAP_FAILED) + die("Unable to mmap '%s': %m", path); + + close(fd); + + *psize = size; + return addr; +} + +static void unmap_file(void *addr, size_t size) +{ + munmap(addr, size + 1); +} + +static void *map_output_file(const char *path, size_t size) +{ + void *addr; + int fd; + + fd = open(path, O_RDWR | O_CREAT, 0660); + if (fd < 0) + die("Unable to create `%s': %m", path); + + if (ftruncate(fd, size)) + die("Unable to resize `%s': %m", path); + + addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (addr == MAP_FAILED) + die("Unable to mmap '%s': %m", path); + + return addr; +} + #ifdef CONFIG_EFI_STUB -static void update_pecoff_section_header_fields(char *section_name, u32 vma, u32 size, u32 datasz, u32 offset) +static void update_pecoff_section_header_fields(char *section_name, uint32_t vma, + uint32_t size, uint32_t datasz, + uint32_t offset) { unsigned int pe_header; unsigned short num_sections; - u8 *section; + struct section_header *section; - pe_header = get_unaligned_le32(&buf[0x3c]); - num_sections = get_unaligned_le16(&buf[pe_header + 6]); - -#ifdef CONFIG_X86_32 - section = &buf[pe_header + 0xa8]; -#else - section = &buf[pe_header + 0xb8]; -#endif + struct pe_hdr *hdr = get_pe_header(buf); + num_sections = get_unaligned_le16(&hdr->sections); + section = get_sections(buf); while (num_sections > 0) { - if (strncmp((char*)section, section_name, 8) == 0) { + if (strncmp(section->name, section_name, 8) == 0) { /* section header size field */ - put_unaligned_le32(size, section + 0x8); + put_unaligned_le32(size, §ion->virtual_size); /* section header vma field */ - put_unaligned_le32(vma, section + 0xc); + put_unaligned_le32(vma, §ion->virtual_address); /* section header 'size of initialised data' field */ - put_unaligned_le32(datasz, section + 0x10); + put_unaligned_le32(datasz, §ion->raw_data_size); /* section header 'file offset' field */ - put_unaligned_le32(offset, section + 0x14); + put_unaligned_le32(offset, §ion->data_addr); break; } - section += 0x28; + section++; num_sections--; } } -static void update_pecoff_section_header(char *section_name, u32 offset, u32 size) +static void update_pecoff_section_header(char *section_name, uint32_t offset, uint32_t size) { update_pecoff_section_header_fields(section_name, offset, size, size, offset); } static void update_pecoff_setup_and_reloc(unsigned int size) { - u32 setup_offset = 0x200; - u32 reloc_offset = size - PECOFF_RELOC_RESERVE - PECOFF_COMPAT_RESERVE; + uint32_t setup_offset = SECTOR_SIZE; + uint32_t reloc_offset = size - PECOFF_RELOC_RESERVE - PECOFF_COMPAT_RESERVE; #ifdef CONFIG_EFI_MIXED - u32 compat_offset = reloc_offset + PECOFF_RELOC_RESERVE; + uint32_t compat_offset = reloc_offset + PECOFF_RELOC_RESERVE; #endif - u32 setup_size = reloc_offset - setup_offset; + uint32_t setup_size = reloc_offset - setup_offset; update_pecoff_section_header(".setup", setup_offset, setup_size); update_pecoff_section_header(".reloc", reloc_offset, PECOFF_RELOC_RESERVE); @@ -211,8 +312,8 @@ static void update_pecoff_setup_and_reloc(unsigned int size) * Modify .reloc section contents with a single entry. The * relocation is applied to offset 10 of the relocation section. */ - put_unaligned_le32(reloc_offset + 10, &buf[reloc_offset]); - put_unaligned_le32(10, &buf[reloc_offset + 4]); + put_unaligned_le32(reloc_offset + RELOC_SECTION_SIZE, &buf[reloc_offset]); + put_unaligned_le32(RELOC_SECTION_SIZE, &buf[reloc_offset + 4]); #ifdef CONFIG_EFI_MIXED update_pecoff_section_header(".compat", compat_offset, PECOFF_COMPAT_RESERVE); @@ -224,19 +325,17 @@ static void update_pecoff_setup_and_reloc(unsigned int size) */ buf[compat_offset] = 0x1; buf[compat_offset + 1] = 0x8; - put_unaligned_le16(0x14c, &buf[compat_offset + 2]); + put_unaligned_le16(IMAGE_FILE_MACHINE_I386, &buf[compat_offset + 2]); put_unaligned_le32(efi32_pe_entry + size, &buf[compat_offset + 4]); #endif } -static void update_pecoff_text(unsigned int text_start, unsigned int file_sz, +static unsigned int update_pecoff_sections(unsigned int text_start, unsigned int text_sz, unsigned int init_sz) { - unsigned int pe_header; - unsigned int text_sz = file_sz - text_start; + unsigned int file_sz = text_start + text_sz; unsigned int bss_sz = init_sz - file_sz; - - pe_header = get_unaligned_le32(&buf[0x3c]); + pe_opt_hdr *hdr = get_pe_opt_header(buf); /* * The PE/COFF loader may load the image at an address which is @@ -254,18 +353,20 @@ static void update_pecoff_text(unsigned int text_start, unsigned int file_sz, * Size of code: Subtract the size of the first sector (512 bytes) * which includes the header. */ - put_unaligned_le32(file_sz - 512 + bss_sz, &buf[pe_header + 0x1c]); + put_unaligned_le32(file_sz - SECTOR_SIZE + bss_sz, &hdr->text_size); /* Size of image */ - put_unaligned_le32(init_sz, &buf[pe_header + 0x50]); + put_unaligned_le32(init_sz, &hdr->image_size); /* * Address of entry point for PE/COFF executable */ - put_unaligned_le32(text_start + efi_pe_entry, &buf[pe_header + 0x28]); + put_unaligned_le32(text_start + efi_pe_entry, &hdr->entry_point); update_pecoff_section_header_fields(".text", text_start, text_sz + bss_sz, text_sz, text_start); + + return text_start + file_sz; } static int reserve_pecoff_reloc_section(int c) @@ -275,7 +376,7 @@ static int reserve_pecoff_reloc_section(int c) return PECOFF_RELOC_RESERVE; } -static void efi_stub_defaults(void) +static void efi_stub_update_defaults(void) { /* Defaults for old kernel */ #ifdef CONFIG_X86_32 @@ -298,7 +399,7 @@ static void efi_stub_entry_update(void) #ifdef CONFIG_EFI_MIXED if (efi32_stub_entry != addr) - die("32-bit and 64-bit EFI entry points do not match\n"); + die("32-bit and 64-bit EFI entry points do not match"); #endif #endif put_unaligned_le32(addr, &buf[0x264]); @@ -310,7 +411,7 @@ static inline void update_pecoff_setup_and_reloc(unsigned int size) {} static inline void update_pecoff_text(unsigned int text_start, unsigned int file_sz, unsigned int init_sz) {} -static inline void efi_stub_defaults(void) {} +static inline void efi_stub_update_defaults(void) {} static inline void efi_stub_entry_update(void) {} static inline int reserve_pecoff_reloc_section(int c) @@ -338,20 +439,15 @@ static int reserve_pecoff_compat_section(int c) static void parse_zoffset(char *fname) { - FILE *file; - char *p; - int c; + size_t size; + char *data, *p; - file = fopen(fname, "r"); - if (!file) - die("Unable to open `%s': %m", fname); - c = fread(buf, 1, sizeof(buf) - 1, file); - if (ferror(file)) - die("read-error on `zoffset.h'"); - fclose(file); - buf[c] = 0; + data = map_file(fname, &size); - p = (char *)buf; + /* We can do that, since we mapped one byte more */ + data[size] = 0; + + p = (char *)data; while (p && *p) { PARSE_ZOFS(p, efi32_stub_entry); @@ -367,82 +463,99 @@ static void parse_zoffset(char *fname) while (p && (*p == '\r' || *p == '\n')) p++; } + + unmap_file(data, size); } -int main(int argc, char ** argv) +static unsigned int read_setup(char *path) { - unsigned int i, sz, setup_sectors, init_sz; - int c; - u32 sys_size; - struct stat sb; - FILE *file, *dest; - int fd; - void *kernel; - u32 crc = 0xffffffffUL; - - efi_stub_defaults(); - - if (argc != 5) - usage(); - parse_zoffset(argv[3]); - - dest = fopen(argv[4], "w"); - if (!dest) - die("Unable to write `%s': %m", argv[4]); + FILE *file; + unsigned int setup_size, file_size; /* Copy the setup code */ - file = fopen(argv[1], "r"); + file = fopen(path, "r"); if (!file) - die("Unable to open `%s': %m", argv[1]); - c = fread(buf, 1, sizeof(buf), file); + die("Unable to open `%s': %m", path); + + file_size = fread(buf, 1, sizeof(buf), file); if (ferror(file)) die("read-error on `setup'"); - if (c < 1024) + + if (file_size < 2 * SECTOR_SIZE) die("The setup must be at least 1024 bytes"); - if (get_unaligned_le16(&buf[510]) != 0xAA55) + + if (get_unaligned_le16(&buf[SECTOR_SIZE - 2]) != 0xAA55) die("Boot block hasn't got boot flag (0xAA55)"); + fclose(file); - c += reserve_pecoff_compat_section(c); - c += reserve_pecoff_reloc_section(c); + /* Reserve space for PE sections */ + file_size += reserve_pecoff_compat_section(file_size); + file_size += reserve_pecoff_reloc_section(file_size); /* Pad unused space with zeros */ - setup_sectors = (c + 511) / 512; - if (setup_sectors < SETUP_SECT_MIN) - setup_sectors = SETUP_SECT_MIN; - i = setup_sectors*512; - memset(buf+c, 0, i-c); - update_pecoff_setup_and_reloc(i); + setup_size = round_up(file_size, SECTOR_SIZE); + + if (setup_size < SETUP_SECT_MIN * SECTOR_SIZE) + setup_size = SETUP_SECT_MIN * SECTOR_SIZE; + + /* + * Global buffer is already initialised + * to 0, but just in case, zero out padding. + */ + + memset(buf + file_size, 0, setup_size - file_size); + + return setup_size; +} + +int main(int argc, char **argv) +{ + size_t kern_file_size; + unsigned int setup_size; + unsigned int setup_sectors; + unsigned int init_size; + unsigned int total_size; + unsigned int kern_size; + void *kernel; + uint32_t crc = 0xffffffffUL; + uint8_t *output; + + if (argc != 5) + usage(); + + efi_stub_update_defaults(); + parse_zoffset(argv[3]); + + setup_size = read_setup(argv[1]); + + setup_sectors = setup_size/SECTOR_SIZE; /* Set the default root device */ put_unaligned_le16(DEFAULT_ROOT_DEV, &buf[508]); - /* Open and stat the kernel file */ - fd = open(argv[2], O_RDONLY); - if (fd < 0) - die("Unable to open `%s': %m", argv[2]); - if (fstat(fd, &sb)) - die("Unable to stat `%s': %m", argv[2]); - sz = sb.st_size; - kernel = mmap(NULL, sz, PROT_READ, MAP_SHARED, fd, 0); - if (kernel == MAP_FAILED) - die("Unable to mmap '%s': %m", argv[2]); - /* Number of 16-byte paragraphs, including space for a 4-byte CRC */ - sys_size = (sz + 15 + 4) / 16; + /* Map kernel file to memory */ + kernel = map_file(argv[2], &kern_file_size); + #ifdef CONFIG_EFI_STUB - /* - * COFF requires minimum 32-byte alignment of sections, and - * adding a signature is problematic without that alignment. - */ - sys_size = (sys_size + 1) & ~1; + /* PE specification require 512-byte minimum section file alignment */ + kern_size = round_up(kern_file_size + 4, SECTOR_SIZE); + update_pecoff_setup_and_reloc(setup_size); +#else + /* Number of 16-byte paragraphs, including space for a 4-byte CRC */ + kern_size = round_up(kern_file_size + 4, PARAGRAPH_SIZE); #endif /* Patch the setup code with the appropriate size parameters */ - buf[0x1f1] = setup_sectors-1; - put_unaligned_le32(sys_size, &buf[0x1f4]); + buf[0x1f1] = setup_sectors - 1; + put_unaligned_le32(kern_size/PARAGRAPH_SIZE, &buf[0x1f4]); + + /* Update kernel_info offset. */ + put_unaligned_le32(kernel_info, &buf[0x268]); + + init_size = get_unaligned_le32(&buf[0x260]); - init_sz = get_unaligned_le32(&buf[0x260]); #ifdef CONFIG_EFI_STUB /* * The decompression buffer will start at ImageBase. When relocating @@ -458,45 +571,35 @@ int main(int argc, char ** argv) * For future-proofing, increase init_sz if necessary. */ - if (init_sz - _end < i + _ehead) { - init_sz = (i + _ehead + _end + 4095) & ~4095; - put_unaligned_le32(init_sz, &buf[0x260]); + if (init_size - _end < setup_size + _ehead) { + init_size = round_up(setup_size + _ehead + _end, SECTION_ALIGNMENT); + put_unaligned_le32(init_size, &buf[0x260]); } -#endif - update_pecoff_text(setup_sectors * 512, i + (sys_size * 16), init_sz); - efi_stub_entry_update(); - - /* Update kernel_info offset. */ - put_unaligned_le32(kernel_info, &buf[0x268]); + total_size = update_pecoff_sections(setup_size, kern_size, init_size); - crc = partial_crc32(buf, i, crc); - if (fwrite(buf, 1, i, dest) != i) - die("Writing setup failed"); + efi_stub_entry_update(); +#else + (void)init_size; + total_size = setup_size + kern_size; +#endif - /* Copy the kernel code */ - crc = partial_crc32(kernel, sz, crc); - if (fwrite(kernel, 1, sz, dest) != sz) - die("Writing kernel failed"); + output = map_output_file(argv[4], total_size); - /* Add padding leaving 4 bytes for the checksum */ - while (sz++ < (sys_size*16) - 4) { - crc = partial_crc32_one('\0', crc); - if (fwrite("\0", 1, 1, dest) != 1) - die("Writing padding failed"); - } + memcpy(output, buf, setup_size); + memcpy(output + setup_size, kernel, kern_file_size); + memset(output + setup_size + kern_file_size, 0, kern_size - kern_file_size); - /* Write the CRC */ - put_unaligned_le32(crc, buf); - if (fwrite(buf, 1, 4, dest) != 4) - die("Writing CRC failed"); + /* Calculate and write kernel checksum. */ + crc = partial_crc32(output, total_size - 4, crc); + put_unaligned_le32(crc, &output[total_size - 4]); - /* Catch any delayed write failures */ - if (fclose(dest)) - die("Writing image failed"); + /* Catch any delayed write failures. */ + if (munmap(output, total_size) < 0) + die("Writing kernel failed"); - close(fd); + unmap_file(kernel, kern_file_size); - /* Everything is OK */ + /* Everything is OK. */ return 0; }