mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
mem_init_flex() allocates the page header index array sized for ADDR_TO_IDX(mem_end) + 1 entries, but never checked that each page record's address falls within [0, mem_end). A crafted LKCD page record with addr >= mem_end writes 8 bytes beyond the allocation. Fix by rejecting any page record whose address is > mem_end - PAGE_SIZE with ERR_EXIT(), immediately after the end-of-dump marker check. Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com> Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
363 lines
8.1 KiB
C
363 lines
8.1 KiB
C
/*
|
|
* zgetdump - Tool for copying and converting System z dumps
|
|
*
|
|
* LKCD dump input format
|
|
*
|
|
* Copyright IBM Corp. 2001, 2017
|
|
*
|
|
* s390-tools is free software; you can redistribute it and/or modify
|
|
* it under the terms of the MIT license. See LICENSE for details.
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <zlib.h>
|
|
|
|
#include "zgetdump.h"
|
|
#include "zg.h"
|
|
#include "df_lkcd.h"
|
|
#include "dfi.h"
|
|
#include "dfi_mem_chunk.h"
|
|
|
|
#define MEM_HOLE_SIZE_MIN (1024 * 1024)
|
|
#define IDX_KIB 64 /* One index entry per IDX_KIB */
|
|
#define IDX_TO_ADDR(idx) (idx * IDX_KIB * 1024)
|
|
#define ADDR_TO_IDX(addr) (addr / (1024 * IDX_KIB))
|
|
|
|
/*
|
|
* File local static data
|
|
*/
|
|
static struct {
|
|
u64 *pg_hdr_idx;
|
|
u64 page_last;
|
|
struct df_lkcd_hdr hdr;
|
|
struct df_lkcd_hdr_asm hdr_asm;
|
|
int dump_full;
|
|
} l;
|
|
|
|
/*
|
|
* Read LKCD page buffer, either compressed or uncompressed
|
|
*/
|
|
static void read_page_buf(struct df_lkcd_pg_hdr *pg_hdr, void *buf)
|
|
{
|
|
unsigned long size = PAGE_SIZE;
|
|
unsigned char cbuf[PAGE_SIZE];
|
|
|
|
if (pg_hdr->size > PAGE_SIZE)
|
|
ERR_EXIT("Dump file inconsistent, LKCD page size too large (%u)",
|
|
pg_hdr->size);
|
|
switch (pg_hdr->flags) {
|
|
case DF_LKCD_DH_RAW:
|
|
if (pg_hdr->size == 0) {
|
|
/* No data in file: treat as zero page */
|
|
memset(buf, 0, PAGE_SIZE);
|
|
break;
|
|
}
|
|
if (pg_hdr->size == PAGE_SIZE) {
|
|
zg_read(g.fh, buf, pg_hdr->size, ZG_CHECK);
|
|
break;
|
|
}
|
|
ERR_EXIT("Dump file inconsistent, LKCD page size invalid: %u", pg_hdr->size);
|
|
break;
|
|
case DF_LKCD_DH_COMPRESSED:
|
|
zg_read(g.fh, cbuf, pg_hdr->size, ZG_CHECK);
|
|
if (uncompress(buf, &size, cbuf, pg_hdr->size) != Z_OK)
|
|
ERR_EXIT("Dump file inconsistent, LKCD page decompression failed");
|
|
if (size != PAGE_SIZE)
|
|
ERR_EXIT("Dump file inconsistent, LKCD decompressed page size invalid: %u",
|
|
size);
|
|
break;
|
|
default:
|
|
ERR_EXIT("Unsupported page flags: %x at addr %Lx",
|
|
pg_hdr->flags, pg_hdr->addr);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Read next LKCD page from current file position
|
|
*
|
|
* If we find the page, we copy the page content. If the page is not present
|
|
* we copy zeroes and skip it. If the page address is not yet reached, we just
|
|
* skip it.
|
|
*/
|
|
static int read_next_page(u64 addr, void *buf)
|
|
{
|
|
struct df_lkcd_pg_hdr pg_hdr;
|
|
|
|
zg_read(g.fh, &pg_hdr, sizeof(pg_hdr), ZG_CHECK);
|
|
l.page_last = pg_hdr.addr / PAGE_SIZE;
|
|
if (pg_hdr.addr == addr) {
|
|
read_page_buf(&pg_hdr, buf);
|
|
return 0;
|
|
}
|
|
if (pg_hdr.addr > addr) {
|
|
memset(buf, 0, PAGE_SIZE);
|
|
zg_seek_cur(g.fh, pg_hdr.size, ZG_CHECK);
|
|
return 0;
|
|
}
|
|
zg_seek_cur(g.fh, pg_hdr.size, ZG_CHECK);
|
|
return -ENODEV;
|
|
}
|
|
|
|
/*
|
|
* Read LKCD dump page for flex dump
|
|
*
|
|
* If the page after the last read page should be read, we just read
|
|
* the next one. Otherwise we seek to the beginning of the page cluster
|
|
* of the page index and search the page there.
|
|
*/
|
|
static void read_page_flex(u64 pg_num, void *buf)
|
|
{
|
|
u64 addr = pg_num * PAGE_SIZE;
|
|
|
|
if (l.pg_hdr_idx[ADDR_TO_IDX(addr)] == 0)
|
|
ABORT("Dump page index broken");
|
|
|
|
if (l.page_last == pg_num - 1) {
|
|
read_next_page(addr, buf);
|
|
return;
|
|
}
|
|
|
|
zg_seek(g.fh, l.pg_hdr_idx[ADDR_TO_IDX(addr)], ZG_CHECK);
|
|
do {
|
|
if (read_next_page(addr, buf) == 0)
|
|
break;
|
|
} while (1);
|
|
}
|
|
|
|
/*
|
|
* Read lkcd page for full dump
|
|
*/
|
|
static void read_page_full(u64 pg_num, void *buf)
|
|
{
|
|
zg_seek(g.fh, DF_LKCD_HDR_SIZE + pg_num * DF_LKCD_UCP_SIZE +
|
|
sizeof(struct df_lkcd_pg_hdr), ZG_CHECK);
|
|
zg_read(g.fh, buf, PAGE_SIZE, ZG_CHECK);
|
|
}
|
|
|
|
/*
|
|
* Read lkcd page
|
|
*/
|
|
static void read_page(u64 pg_num, void *buf)
|
|
{
|
|
if (l.dump_full)
|
|
read_page_full(pg_num, buf);
|
|
else
|
|
read_page_flex(pg_num, buf);
|
|
}
|
|
|
|
/*
|
|
* LKCD mem chunk read callback
|
|
*/
|
|
static void dfi_lkcd_mem_chunk_read_fn(struct dfi_mem_chunk *mem_chunk, u64 off,
|
|
void *buf, u64 cnt)
|
|
{
|
|
u64 copied = 0, size, pg_nr, addr = off + mem_chunk->start;
|
|
char pg_buf[PAGE_SIZE];
|
|
unsigned int pg_off;
|
|
|
|
while (copied != cnt) {
|
|
pg_nr = (addr + copied) / PAGE_SIZE;
|
|
pg_off = (addr + copied) % PAGE_SIZE;
|
|
size = MIN(cnt - copied, PAGE_SIZE - pg_off);
|
|
read_page(pg_nr, pg_buf);
|
|
memcpy(buf + copied, &pg_buf[pg_off], size);
|
|
copied += size;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Did we find the end of the LCKD dump?
|
|
*/
|
|
static int dump_end(u64 addr, struct df_lkcd_pg_hdr *pg_hdr)
|
|
{
|
|
if (addr == pg_hdr->addr) {
|
|
/*
|
|
* This is a workaroud for a bug in vmconvert,
|
|
* where instaed of the end marker the last
|
|
* page was written twice. Sorry for that...
|
|
*/
|
|
return 1;
|
|
}
|
|
if (pg_hdr->addr == 0 && pg_hdr->size == 4 && pg_hdr->flags == 0) {
|
|
/*
|
|
* zfcpdump bug (wrong end marker)
|
|
*/
|
|
return 1;
|
|
}
|
|
if (pg_hdr->flags == DF_LKCD_DH_END)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Init memory chunks for full dump
|
|
*
|
|
* Full dump: It is not compressed and it does not have any memory holes.
|
|
*/
|
|
static int mem_init_full(void)
|
|
{
|
|
dfi_mem_chunk_add(0, l.hdr.mem_end, NULL, dfi_lkcd_mem_chunk_read_fn,
|
|
NULL);
|
|
l.dump_full = 1;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Init memory chunks for flex dump
|
|
*
|
|
* Flex dump: It is compressed and/or it has memory holes.
|
|
*/
|
|
static int mem_init_flex(void)
|
|
{
|
|
u64 addr = U64_MAX, idx = 0, mem_chunk_start = 0, rc;
|
|
struct df_lkcd_pg_hdr pg_hdr;
|
|
int dump_incomplete = 0;
|
|
|
|
l.pg_hdr_idx = zg_alloc(sizeof(u64) * (ADDR_TO_IDX(l.hdr.mem_end) + 1));
|
|
zg_seek(g.fh, DF_LKCD_HDR_SIZE, ZG_CHECK_NONE);
|
|
zg_progress_init("Analyzing dump", l.hdr.mem_end);
|
|
do {
|
|
rc = zg_read(g.fh, &pg_hdr, sizeof(pg_hdr), ZG_CHECK_ERR);
|
|
if (rc != sizeof(pg_hdr)) {
|
|
dump_incomplete = 1;
|
|
break;
|
|
}
|
|
if (dump_end(addr, &pg_hdr))
|
|
break;
|
|
if (pg_hdr.addr + PAGE_SIZE > l.hdr.mem_end)
|
|
ERR_EXIT("Dump file inconsistent, LKCD page address out of range (0x%llx)",
|
|
pg_hdr.addr);
|
|
if (pg_hdr.addr - addr > MEM_HOLE_SIZE_MIN) {
|
|
dfi_mem_chunk_add(mem_chunk_start,
|
|
addr + PAGE_SIZE - mem_chunk_start,
|
|
NULL, dfi_lkcd_mem_chunk_read_fn,
|
|
NULL);
|
|
mem_chunk_start = pg_hdr.addr;
|
|
}
|
|
addr = pg_hdr.addr;
|
|
zg_progress(addr);
|
|
if (addr >= IDX_TO_ADDR(idx)) {
|
|
idx = ADDR_TO_IDX(addr);
|
|
l.pg_hdr_idx[idx] = zg_tell(g.fh, ZG_CHECK) -
|
|
sizeof(pg_hdr);
|
|
idx++;
|
|
}
|
|
zg_seek_cur(g.fh, pg_hdr.size, ZG_CHECK);
|
|
} while (1);
|
|
|
|
if (addr != mem_chunk_start) {
|
|
dfi_mem_chunk_add(mem_chunk_start,
|
|
l.hdr.mem_end - mem_chunk_start,
|
|
NULL, dfi_lkcd_mem_chunk_read_fn, NULL);
|
|
}
|
|
zg_progress(l.hdr.mem_end);
|
|
if (g.opts.action != ZG_ACTION_MOUNT)
|
|
fprintf(stderr, "\n");
|
|
if (dump_incomplete)
|
|
return -EINVAL;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Do we have a full dump?
|
|
*/
|
|
static int is_full_dump(void)
|
|
{
|
|
u64 full_size;
|
|
int pages;
|
|
|
|
if (l.hdr.dump_compress != DF_LKCD_COMPRESS_NONE)
|
|
return 0;
|
|
pages = l.hdr.mem_end / PAGE_SIZE;
|
|
full_size = DF_LKCD_HDR_SIZE + pages * DF_LKCD_UCP_SIZE +
|
|
sizeof(struct df_lkcd_pg_hdr);
|
|
if (zg_size(g.fh) != full_size)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Init memory chunks
|
|
*/
|
|
static int mem_init(void)
|
|
{
|
|
if (is_full_dump())
|
|
return mem_init_full();
|
|
else
|
|
return mem_init_flex();
|
|
}
|
|
|
|
/*
|
|
* Initialize CPU information
|
|
*/
|
|
static int cpu_init(void)
|
|
{
|
|
unsigned int i;
|
|
int rc;
|
|
|
|
if (l.hdr_asm.magic != DF_LKCD_MAGIC_ASM) {
|
|
/* Old LKCD dump without asm header */
|
|
dfi_cpu_info_init(DFI_CPU_CONTENT_NONE);
|
|
return 0;
|
|
}
|
|
|
|
dfi_cpu_info_init(DFI_CPU_CONTENT_ALL);
|
|
for (i = 0; i < l.hdr_asm.cpu_cnt; i++) {
|
|
rc = dfi_cpu_add_from_lc(l.hdr_asm.lc_vec[i]);
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Read LKCD dump header and dump asm header
|
|
*/
|
|
static int read_lkcd_hdr(void)
|
|
{
|
|
if (zg_size(g.fh) < DF_LKCD_HDR_SIZE)
|
|
return -ENODEV;
|
|
|
|
/* Read dump header */
|
|
zg_read(g.fh, &l.hdr, sizeof(l.hdr), ZG_CHECK);
|
|
|
|
if (l.hdr.magic != DF_LKCD_MAGIC)
|
|
return -ENODEV;
|
|
|
|
/* Read asm header */
|
|
zg_seek(g.fh, l.hdr.hdr_size, ZG_CHECK);
|
|
zg_read(g.fh, &l.hdr_asm, sizeof(l.hdr_asm), ZG_CHECK);
|
|
if (strncmp(l.hdr.utsname_machine, "s390x", sizeof("s390x")) != 0)
|
|
ERR_EXIT("Dump architecture \"%s\" is not supported",
|
|
l.hdr.utsname_machine);
|
|
if (l.hdr_asm.magic == DF_LKCD_MAGIC_ASM)
|
|
dfi_attr_real_cpu_cnt_set(l.hdr_asm.real_cpu_cnt);
|
|
dfi_attr_version_set(l.hdr.version);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Initialize LKCD DFI
|
|
*/
|
|
static int dfi_lkcd_init(void)
|
|
{
|
|
if (read_lkcd_hdr() != 0)
|
|
return -ENODEV;
|
|
if (mem_init() != 0)
|
|
return -EINVAL;
|
|
if (cpu_init() != 0)
|
|
return -EINVAL;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* LKCD DFI operations
|
|
*/
|
|
struct dfi dfi_lkcd = {
|
|
.name = "lkcd",
|
|
.init = dfi_lkcd_init,
|
|
.feat_bits = DFI_FEAT_COPY | DFI_FEAT_SEEK,
|
|
};
|