From e764f460c457ab2a6000acb5f2eb7169866ce192 Mon Sep 17 00:00:00 2001 From: Stefan Haberland Date: Mon, 15 Apr 2019 18:08:43 +0200 Subject: [PATCH] zipl: Do not strip kernel image IPL header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The binary Linux kernel image is built to be loaded to memory address 0x0 but the first 64 kbyte contain an IPL header that is not used for disk IPL. zIPL strips away this IPL header when writing IPL records to disk, loads the remaining data to memory address 0x10000 and uses the memory area below that for its own boot loader code. The Secure Boot firmware feature checks the integrity of an installed image during IPL using a checksum that was generated for the full image. Since the checksum becomes invalid if the IPL header is removed, zIPL must be changed to write the full image to disk. This patch modifies the zIPL logic to no longer strip away the IPL header. Instead the full image is loaded to a higher memory address and relocated by the stage 3 boot loader code to its final location. Signed-off-by: Stefan Haberland Reviewed-by: Peter Oberparleiter Signed-off-by: Jan Höppner --- zipl/boot/libc.c | 20 ++++++++++++++++++++ zipl/boot/libc.h | 1 + zipl/boot/stage3.c | 16 +++++++++++++++- zipl/boot/stage3.h | 4 ++++ zipl/boot/stage3.lds | 5 +++++ zipl/include/boot.h | 6 +++++- zipl/include/zipl.h | 1 - zipl/src/boot.c | 5 ++++- zipl/src/bootmap.c | 19 +++++++++++++++---- zipl/src/job.c | 2 +- 10 files changed, 70 insertions(+), 9 deletions(-) diff --git a/zipl/boot/libc.c b/zipl/boot/libc.c index f86f62ac..5944c4e2 100644 --- a/zipl/boot/libc.c +++ b/zipl/boot/libc.c @@ -58,6 +58,26 @@ void *memcpy(void *dest, const void *src, unsigned long n) return dest; } +/* + * Move @n bytes of memory from @src to @dest. The memory regions may overlap. + */ +void *memmove(void *dest, const void *src, unsigned long n) +{ + const char *s = src; + char *d = dest; + + if (s < d) { + d += n; + s += n; + while (n--) + *--d = *--s; + } else { + while (n--) + *d++ = *s++; + } + return dest; +} + /* * Copy string */ diff --git a/zipl/boot/libc.h b/zipl/boot/libc.h index b05a1169..44097fa4 100644 --- a/zipl/boot/libc.h +++ b/zipl/boot/libc.h @@ -49,6 +49,7 @@ typedef unsigned char uint8_t; void printf(const char *, ...); void sprintf(char *, const char *, ...); void *memcpy(void *, const void *, unsigned long); +void *memmove(void *, const void *, unsigned long); void *memset(void *, int c, unsigned long); char *strcat(char *, const char *); int strncmp(const char *, const char *, unsigned long); diff --git a/zipl/boot/stage3.c b/zipl/boot/stage3.c index 109acd17..b803b0b9 100644 --- a/zipl/boot/stage3.c +++ b/zipl/boot/stage3.c @@ -14,13 +14,15 @@ #include "stage3.h" /* - * 48 Byte dummy space for external symbols + * 64 Byte dummy space for external symbols * _parm_addr; address of parmline * _initrd_addr; address of initrd * _initrd_len; length of initrd * _load_psw; load psw of kernel * _extra_parm; use extra parm line mechanism? * stage3_flags; flags (e.g. STAGE3_FLAG_KDUMP) + * _image_len; length of kernel + * _image_addr; target address of kernel * * needed to blow up the binary and leave room */ @@ -39,6 +41,10 @@ __attribute__ ((section(".text.dummy"))) void _dummy(void) ".long 0x00000000\n" ".long 0x00000000\n" ".long 0x00000000\n" + ".long 0x00000000\n" + ".long 0x00000000\n" + ".long 0x00000000\n" + ".long 0x00000000\n" ); } @@ -228,6 +234,14 @@ void start(void) unsigned char *command_line = (unsigned char *)COMMAND_LINE; unsigned int begin = 0, end = 0, length = 0; + /* + * Relocate the kernel image to its actual load address while stripping + * away the kernel IPL header to not overwrite the stage3 loader. + */ + memmove((void *)_image_addr, + (void *)_image_addr + KERNEL_HEADER_SIZE, + _image_len - KERNEL_HEADER_SIZE); + /* store subchannel ID into low core and into new kernel space */ subchannel_id = S390_lowcore.subchannel_id; *(unsigned int *)__LC_IPLDEV = subchannel_id; diff --git a/zipl/boot/stage3.h b/zipl/boot/stage3.h index cdc0d778..9bc4f860 100644 --- a/zipl/boot/stage3.h +++ b/zipl/boot/stage3.h @@ -27,6 +27,8 @@ #define STAGE3_FLAG_SCSI 0x0001000000000000ULL #define STAGE3_FLAG_KDUMP 0x0002000000000000ULL +#define KERNEL_HEADER_SIZE 65536 + #define UNSPECIFIED_ADDRESS -1ULL extern unsigned long long _parm_addr; /* address of parmline */ @@ -35,6 +37,8 @@ extern unsigned long long _initrd_len; /* length of initrd */ extern unsigned long long _load_psw; /* load psw of kernel */ extern unsigned long long _extra_parm; /* use extra parm line mechanism? */ extern unsigned long long stage3_flags; /* flags (e.g. STAGE3_FLAG_KDUMP) */ +extern unsigned long long _image_len; /* length of kernel */ +extern unsigned long long _image_addr; /* target address of kernel */ extern void kdump_stage3(); #endif /* STAGE3_H */ diff --git a/zipl/boot/stage3.lds b/zipl/boot/stage3.lds index 638fb25a..7beafb03 100644 --- a/zipl/boot/stage3.lds +++ b/zipl/boot/stage3.lds @@ -23,6 +23,11 @@ SECTIONS _extra_parm = .; . = 0xa028; stage3_flags =.; + . = 0xa030; + _image_len = .; + . = 0xa038; + _image_addr = .; + . = 0xa000; .text.dummy : { *(.text.dummy) } diff --git a/zipl/include/boot.h b/zipl/include/boot.h index f21ab598..8412a65b 100644 --- a/zipl/include/boot.h +++ b/zipl/include/boot.h @@ -248,6 +248,9 @@ struct boot_stage3_params { uint64_t load_psw; uint64_t extra_parm; uint16_t flags; + uint16_t reserved[3]; + uint64_t image_len; + uint64_t image_addr; } __attribute__ ((packed)); #define STAGE3_FLAG_SCSI 0x0001 @@ -308,7 +311,8 @@ int boot_get_eckd_stage2(void** data, size_t* size, struct job_data* job); size_t get_stage3_size(); int boot_get_stage3(void** buffer, size_t* bytecount, address_t parm_addr, address_t initrd_addr, size_t initrd_len, - address_t image_addr, int extra_parm, uint16_t flags); + address_t image_addr, int extra_parm, uint16_t flags, + size_t image_len); int boot_get_tape_ipl(void** data, size_t* size, address_t parm_addr, address_t initrd_addr, address_t image_addr); int boot_get_tape_dump(void** data, size_t* size, uint64_t mem); diff --git a/zipl/include/zipl.h b/zipl/include/zipl.h index 770801e5..5cd7cb4e 100644 --- a/zipl/include/zipl.h +++ b/zipl/include/zipl.h @@ -35,7 +35,6 @@ #define PSW_LOAD 0x0008000080000000LL #define PSW_DISABLED_WAIT 0x000a000000000000LL -#define KERNEL_HEADER_SIZE 65536 #define BOOTMAP_FILENAME "bootmap" #define BOOTMAP_TEMPLATE_FILENAME "bootmap_temp.XXXXXX" diff --git a/zipl/src/boot.c b/zipl/src/boot.c index f5dbf839..f4290fe2 100644 --- a/zipl/src/boot.c +++ b/zipl/src/boot.c @@ -81,7 +81,7 @@ get_stage3_size() int boot_get_stage3(void** buffer, size_t* bytecount, address_t parm_addr, address_t initrd_addr, size_t initrd_len, address_t image_addr, - int extra_parm, uint16_t flags) + int extra_parm, uint16_t flags, size_t image_len) { struct boot_stage3_params params; void* data; @@ -95,6 +95,7 @@ boot_get_stage3(void** buffer, size_t* bytecount, address_t parm_addr, data = misc_malloc(DATA_SIZE(stage3)); if (data == NULL) return -1; + memset(data, 0, DATA_SIZE(stage3)); /* Prepare params section */ params.parm_addr = (uint64_t) parm_addr; params.initrd_addr = (uint64_t) initrd_addr; @@ -102,6 +103,8 @@ boot_get_stage3(void** buffer, size_t* bytecount, address_t parm_addr, params.load_psw = (uint64_t)(image_addr | PSW_LOAD); params.extra_parm = (uint64_t) extra_parm; params.flags = flags; + params.image_len = (uint64_t) image_len; + params.image_addr = (uint64_t) image_addr; /* Initialize buffer */ memcpy(data, DATA_ADDR(stage3), DATA_SIZE(stage3)); memcpy(data, ¶ms, sizeof(struct boot_stage3_params)); diff --git a/zipl/src/bootmap.c b/zipl/src/bootmap.c index 1b71b037..1243b0e0 100644 --- a/zipl/src/bootmap.c +++ b/zipl/src/bootmap.c @@ -409,7 +409,6 @@ print_components(const char *name[], struct component_loc *loc, int num) } } - static int add_ipl_program(int fd, struct job_ipl_data* ipl, disk_blockptr_t* program, int verbose, int add_files, component_header_type type, @@ -424,6 +423,7 @@ add_ipl_program(int fd, struct job_ipl_data* ipl, disk_blockptr_t* program, struct component_loc comp_loc[4]; int rc; int offset, flags = 0; + size_t ramdisk_size, image_size; memset(comp_loc, 0, sizeof(comp_loc)); table = misc_malloc(info->phy_block_size); @@ -456,18 +456,29 @@ add_ipl_program(int fd, struct job_ipl_data* ipl, disk_blockptr_t* program, return -1; } } + ramdisk_size = stats.st_size; if (info->type == disk_type_scsi) flags |= STAGE3_FLAG_SCSI; if (ipl->is_kdump) flags |= STAGE3_FLAG_KDUMP; + /* Get kernel file size */ + if (stat(ipl->image, &stats)) { + error_reason(strerror(errno)); + error_text("Could not get information for file '%s'", + ipl->image); + free(table); + return -1; + } + image_size = stats.st_size; + /* Add stage 3 loader to bootmap */ rc = boot_get_stage3(&stage3, &stage3_size, ipl->parm_addr, - ipl->ramdisk_addr, (size_t) stats.st_size, + ipl->ramdisk_addr, ramdisk_size, ipl->is_kdump ? ipl->image_addr + 0x10 : ipl->image_addr, (info->type == disk_type_scsi) ? 0 : 1, - flags); + flags, image_size); if (rc) { free(table); return rc; @@ -487,7 +498,7 @@ add_ipl_program(int fd, struct job_ipl_data* ipl, disk_blockptr_t* program, printf(" kernel image......: %s\n", ipl->image); } rc = add_component_file(fd, ipl->image, ipl->image_addr, - KERNEL_HEADER_SIZE, VOID_ADD(table, offset), + 0, VOID_ADD(table, offset), add_files, info, target, &comp_loc[0]); if (rc) { error_text("Could not add image file '%s'", ipl->image); diff --git a/zipl/src/job.c b/zipl/src/job.c index 22d2549b..bdd1744c 100644 --- a/zipl/src/job.c +++ b/zipl/src/job.c @@ -511,7 +511,7 @@ get_ipl_components(struct job_ipl_data *ipl, struct component_loc **clp, /* Fill in component data */ num = 0; rc = set_cl_element(&cl[num++], "kernel image", ipl->image, - &ipl->image_addr, 0, 0x10000, + &ipl->image_addr, 0, 0, MAXIMUM_PHYSICAL_BLOCKSIZE); if (rc) goto error;