From 800df6bef89690f1d91a35da58b85fb391d34b07 Mon Sep 17 00:00:00 2001 From: Alexander Egorenkov Date: Mon, 1 Feb 2021 17:51:48 +0100 Subject: [PATCH] zipl/boot: fix heap OOM in stage2 of DASD dumper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For the stage2 of zipl's boot loaders only 3 heap pages are available [0x6000, 0x6000 + 0x3000]. Therefore, stage2 code needs to be very careful when and for how long it allocates a heap page. The heap OOM problem in stage2 was hidden until the commit 252be376 ("zipl/boot: fix potential heap overflow in stage2"). Before this commit, stage2 assumed that heap is 4 pages large which is apparently very wrong but get_zeroed_page() still allowed allocating an extra 4th page by overriding the memory segment following the heap area of stage2 (stage3 parameter area). Try to avoid allocating a heap page and keeping it allocated over a long period of time in the DASD dumpers. Free a heap page as soon as possible if not required anymore. And be extra careful with printf() because it always tries to allocate a free heap page. Fixes: 252be376 ("zipl/boot: fix potential heap overflow in stage2") Signed-off-by: Alexander Egorenkov Reviewed-by: Philipp Rudo Signed-off-by: Jan Höppner --- zipl/boot/eckd2dump.c | 9 ++++++--- zipl/boot/eckd2dump.h | 3 +-- zipl/boot/eckd2dump_mv.c | 27 ++++++++++++++++----------- zipl/boot/eckd2dump_sv.c | 6 +++--- zipl/boot/fba2dump.c | 15 +++++++++------ 5 files changed, 35 insertions(+), 25 deletions(-) diff --git a/zipl/boot/eckd2dump.c b/zipl/boot/eckd2dump.c index 28598adf..6e49c4cc 100644 --- a/zipl/boot/eckd2dump.c +++ b/zipl/boot/eckd2dump.c @@ -449,13 +449,14 @@ void readblock(unsigned long blk, unsigned long addr, unsigned long blk_count) * block number */ unsigned long write_dump_segment(unsigned long blk, - struct df_s390_dump_segm_hdr *segm, - unsigned long zero_page) + struct df_s390_dump_segm_hdr *segm) { - unsigned long addr, start_blk, blk_count; + unsigned long addr, start_blk, blk_count, zero_page; /* Write the dump segment header itself (1 page) */ + zero_page = get_zeroed_page(); writeblock(blk, (unsigned long)segm, m2b(PAGE_SIZE), zero_page); + free_page(zero_page); blk += m2b(PAGE_SIZE); /* Write the dump segment */ addr = segm->start; @@ -464,7 +465,9 @@ unsigned long write_dump_segment(unsigned long blk, /* Remaining blocks to write */ blk_count = m2b(segm->len) - (blk - start_blk); blk_count = MIN(blk_count, eckd_blk_max); + zero_page = get_zeroed_page(); writeblock(blk, addr, blk_count, zero_page); + free_page(zero_page); progress_print(addr); blk += blk_count; addr += b2m(blk_count); diff --git a/zipl/boot/eckd2dump.h b/zipl/boot/eckd2dump.h index 1f5dc3ed..6b630e83 100644 --- a/zipl/boot/eckd2dump.h +++ b/zipl/boot/eckd2dump.h @@ -48,7 +48,6 @@ void writeblock(unsigned long blk, unsigned long addr, unsigned long blk_count, unsigned long zero_page); void readblock(unsigned long blk, unsigned long addr, unsigned long blk_count); unsigned long write_dump_segment(unsigned long blk, - struct df_s390_dump_segm_hdr *segm, - unsigned long zero_page); + struct df_s390_dump_segm_hdr *segm); #endif /* ECKD2DUMP_H */ diff --git a/zipl/boot/eckd2dump_mv.c b/zipl/boot/eckd2dump_mv.c index 964db10e..18b1c9bc 100644 --- a/zipl/boot/eckd2dump_mv.c +++ b/zipl/boot/eckd2dump_mv.c @@ -192,10 +192,13 @@ void dt_device_enable(void) * Check for the volume timestamp and validate the dump signature * before writing a dump. */ -static void check_volume(unsigned long page) +static void check_volume(void) { struct mvdump_parm_table *mvdump_table_new; struct df_s390_hdr *hdr_new; + unsigned long page; + + page = get_zeroed_page(); /* * Check whether parameter table on dump device has a valid @@ -222,6 +225,8 @@ static void check_volume(unsigned long page) if (dump_hdr->magic != hdr_new->mvdump_sign) panic(ENOSIGN, "Wrong signature"); } + + free_page(page); } /* @@ -229,10 +234,10 @@ static void check_volume(unsigned long page) * address to write for the next volume or memory size if the dump ended * on this volume */ -static unsigned long write_volume(unsigned long addr, unsigned long page, +static unsigned long write_volume(unsigned long addr, struct df_s390_dump_segm_hdr *dump_segm) { - unsigned long free_space, blk; + unsigned long free_space, blk, page; /* * Write dump header @@ -245,7 +250,7 @@ static unsigned long write_volume(unsigned long addr, unsigned long page, * header and the end marker) */ free_space = b2m(device.blk_end - blk + 1) - b2m(2); - memset((void *) page, 0, PAGE_SIZE); + /* * Write dump data */ @@ -257,7 +262,7 @@ static unsigned long write_volume(unsigned long addr, unsigned long page, addr = find_dump_segment(addr, dump_hdr->mem_size, ROUND_DOWN(free_space, MIB), dump_segm); - blk = write_dump_segment(blk, dump_segm, page); + blk = write_dump_segment(blk, dump_segm); /* Update free space left on vol */ free_space -= dump_segm->len; /* Reserve one block for the next segment header */ @@ -267,8 +272,10 @@ static unsigned long write_volume(unsigned long addr, unsigned long page, /* Check if no more dump segments follow */ if (dump_segm->stop_marker) { /* Write end marker */ + page = get_zeroed_page(); df_s390_em_page_init(page); writeblock(blk, page, 1, 0); + free_page(page); return dump_hdr->mem_size; } /* @@ -286,19 +293,18 @@ static unsigned long write_volume(unsigned long addr, unsigned long page, void dt_dump_mem(void) { struct df_s390_dump_segm_hdr *dump_segm; - unsigned long addr, page; + unsigned long addr; dump_hdr->mvdump_sign = DF_S390_MAGIC_EXT; dump_hdr->mvdump = 1; addr = 0; total_dump_size = 0; - page = get_zeroed_page(); dump_segm = (void *)get_zeroed_page(); while (1) { printf("Dumping to: 0.%x.%04x", device.sid.ssid, device.devno); - check_volume(page); - addr = write_volume(addr, page, dump_segm); + check_volume(); + addr = write_volume(addr, dump_segm); if (addr == dump_hdr->mem_size) break; /* @@ -312,7 +318,6 @@ void dt_dump_mem(void) set_device(device.sid, DISABLED); dt_device_enable(); } - progress_print(addr); - free_page(page); free_page(__pa(dump_segm)); + progress_print(addr); } diff --git a/zipl/boot/eckd2dump_sv.c b/zipl/boot/eckd2dump_sv.c index 84e783b5..559d08eb 100644 --- a/zipl/boot/eckd2dump_sv.c +++ b/zipl/boot/eckd2dump_sv.c @@ -66,7 +66,6 @@ void dt_dump_mem(void) struct df_s390_dump_segm_hdr *dump_segm; blk = device.blk_start; - page = get_zeroed_page(); dump_segm = (void *)get_zeroed_page(); /* Write dump header */ @@ -79,18 +78,19 @@ void dt_dump_mem(void) end = dump_hdr->mem_size; while (addr < end) { addr = find_dump_segment(addr, end, 0, dump_segm); - blk = write_dump_segment(blk, dump_segm, page); + blk = write_dump_segment(blk, dump_segm); total_dump_size += dump_segm->len; if (dump_segm->stop_marker) { addr = end; break; } } + free_page(__pa(dump_segm)); progress_print(addr); /* Write end marker */ + page = get_zeroed_page(); df_s390_em_page_init(page); writeblock(blk, page, 1, 0); free_page(page); - free_page(__pa(dump_segm)); } diff --git a/zipl/boot/fba2dump.c b/zipl/boot/fba2dump.c index 100556b7..f4d7c563 100644 --- a/zipl/boot/fba2dump.c +++ b/zipl/boot/fba2dump.c @@ -141,13 +141,14 @@ static void writeblock_fba(unsigned long blk, unsigned long addr, * block number */ unsigned long write_dump_segment_fba(unsigned long blk, - struct df_s390_dump_segm_hdr *dump_segm, - unsigned long zero_page) + struct df_s390_dump_segm_hdr *dump_segm) { - unsigned long addr, start_blk, blk_count; + unsigned long addr, start_blk, blk_count, zero_page; /* Write the dump segment header itself (1 page) */ + zero_page = get_zeroed_page(); writeblock_fba(blk, __pa(dump_segm), BLK_PER_PAGE, zero_page); + free_page(zero_page); blk += BLK_PER_PAGE; /* Write the dump segment */ addr = dump_segm->start; @@ -156,7 +157,9 @@ unsigned long write_dump_segment_fba(unsigned long blk, /* Remaining blocks to write */ blk_count = m2b(dump_segm->len) - (blk - start_blk); blk_count = MIN(blk_count, BLK_PWRT); + zero_page = get_zeroed_page(); writeblock_fba(blk, addr, blk_count, zero_page); + free_page(zero_page); progress_print(addr); blk += blk_count; addr += b2m(blk_count); @@ -198,7 +201,6 @@ void dt_dump_mem(void) ccw_program_init(); blk = device.blk_start; - page = get_zeroed_page(); dump_segm = (void *)get_zeroed_page(); /* Write dump header */ @@ -211,18 +213,19 @@ void dt_dump_mem(void) end = dump_hdr->mem_size; while (addr < end) { addr = find_dump_segment(addr, end, 0, dump_segm); - blk = write_dump_segment_fba(blk, dump_segm, page); + blk = write_dump_segment_fba(blk, dump_segm); total_dump_size += dump_segm->len; if (dump_segm->stop_marker) { addr = end; break; } } + free_page(__pa(dump_segm)); progress_print(addr); /* Write end marker */ + page = get_zeroed_page(); df_s390_em_page_init(page); writeblock_fba(blk, page, 1, 0); free_page(page); - free_page(__pa(dump_segm)); }