Files
s390-tools/zdump/dfi_elf.c
Alexander Egorenkov cb09223a56 zdump/dfi_elf: Fix stack buffer overflow in nt_read()
Sanity check ELF notes descriptor size before reading it. This prevents
stack buffer overflows in case a dump contains invalid ELF notes.
Instead of reading a note's descriptor into a temporary buffer on stack,
read it directly into the buffer given to nt_read() but also provide
a maximum length of the given buffer to nt_read() in order to prevent
overflows.

This problem was found with valgrind and AFL fuzzing + ASAN.

AFL +  ASAN findings:

[root@t83lp49 s390-tools]# ./zdump/zgetdump -iVVVVV ~/zgetdump-fuzzing/findings/crashes/id\:000008\,sig\:06\,src\:000007\,op\:arith8\,pos\:67\,val\:+3
TRACE: DFI initialization
DEBUG: DFI trying s390tape
DEBUG: DFI s390tape returned with rc -19
DEBUG: DFI trying devmem
DEBUG: DFI devmem returned with rc -19
DEBUG: DFI trying s390mv_ext
DEBUG: DFI s390mv_ext returned with rc -19
DEBUG: DFI trying s390mv
DEBUG: DFI s390mv returned with rc -19
DEBUG: DFI trying s390_ext
DEBUG: DFI S390 extended initialization
DEBUG: DFI s390_ext returned with rc -19
DEBUG: DFI trying s390
DEBUG: DFI S390 initialization
DEBUG: DFI s390 returned with rc -19
DEBUG: DFI trying lkcd
DEBUG: DFI lkcd returned with rc -19
DEBUG: DFI trying elf
DEBUG: DFI ELF initialization
DEBUG: DFI ELF e_phnum 11
DEBUG: DFI ELF p_type[0] 0x4
DEBUG: DFI ELF n_type 0x0
AddressSanitizer:DEADLYSIGNAL
=================================================================
==208548==ERROR: AddressSanitizer: stack-overflow on address 0x03ffef05d000 (pc 0x0000010051b0 bp 0x03fff107dc40 sp 0x03ffef05dac8 T0)
    #0 0x10051b0  (/root/s390-tools/zdump/zgetdump+0x10051b0)

SUMMARY: AddressSanitizer: stack-overflow (/root/s390-tools/zdump/zgetdump+0x10051b0)
==208548==ABORTING

valgrind findings:

	==56423== Source and destination overlap in memcpy(0x4a86d38, 0x4a875e0, 4096)
	==56423==    at 0x4839F86: memcpy (in /usr/lib/s390x-linux-gnu/valgrind/vgpreload_memcheck-s390x-linux.so)
	==56423==    by 0x114253: memcpy (string_fortified.h:34)
	==56423==    by 0x114253: nt_read (dfi_elf.c:95)
	==56423==    by 0x1145FF: nt_s390_prefix_read (dfi_elf.c:195)
	==56423==    by 0x1145FF: pt_notes_add (dfi_elf.c:259)
	==56423==    by 0x1145FF: dfi_elf_init (dfi_elf.c:326)
	==56423==    by 0x112A57: dfi_init (dfi.c:1212)
	==56423==    by 0x10D663: do_dump_info (zgetdump.c:127)
	==56423==    by 0x10D663: main (zgetdump.c:182)
	==56423==
	==56423== Invalid write of size 8
	==56423==    at 0x4839E28: memcpy (in /usr/lib/s390x-linux-gnu/valgrind/vgpreload_memcheck-s390x-linux.so)
	==56423==    by 0x114253: memcpy (string_fortified.h:34)
	==56423==    by 0x114253: nt_read (dfi_elf.c:95)
	==56423==    by 0x1145FF: nt_s390_prefix_read (dfi_elf.c:195)
	==56423==    by 0x1145FF: pt_notes_add (dfi_elf.c:259)
	==56423==    by 0x1145FF: dfi_elf_init (dfi_elf.c:326)
	==56423==    by 0x112A57: dfi_init (dfi.c:1212)
	==56423==    by 0x10D663: do_dump_info (zgetdump.c:127)
	==56423==    by 0x10D663: main (zgetdump.c:182)
	==56423==  Address 0x4a86ee0 is 0 bytes after a block of size 912 alloc'd
	==56423==    at 0x483675E: calloc (in /usr/lib/s390x-linux-gnu/valgrind/vgpreload_memcheck-s390x-linux.so)
	==56423==    by 0x10E71D: zg_alloc (zg.c:93)
	==56423==    by 0x114793: nt_prstatus_read (dfi_elf.c:123)
	==56423==    by 0x114793: pt_notes_add (dfi_elf.c:234)
	==56423==    by 0x114793: dfi_elf_init (dfi_elf.c:326)
	==56423==    by 0x112A57: dfi_init (dfi.c:1212)
	==56423==    by 0x10D663: do_dump_info (zgetdump.c:127)
	==56423==    by 0x10D663: main (zgetdump.c:182)

Signed-off-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Suggested-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Reported-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2021-10-01 14:59:56 +02:00

352 lines
8.2 KiB
C

/*
* zgetdump - Tool for copying and converting System z dumps
*
* ELF core dump input format
*
* Copyright IBM Corp. 2001, 2018
*
* 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 <assert.h>
#include <elf.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "lib/util_libc.h"
#include "lib/util_log.h"
#include "zgetdump.h"
/*
* Read memory for given memory chunk
*/
static void dfi_elf_mem_chunk_read_fn(struct dfi_mem_chunk *mem_chunk, u64 off,
void *buf, u64 cnt)
{
u64 elf_load_off = *((u64 *) mem_chunk->data);
zg_seek(g.fh, elf_load_off + off, ZG_CHECK);
zg_read(g.fh, buf, cnt, ZG_CHECK);
}
/*
* Add load (memory chunk) to DFI dump
*/
static int pt_load_add(Elf64_Phdr *phdr)
{
u64 *off_ptr;
util_log_print(UTIL_LOG_DEBUG,
"DFI ELF p_paddr 0x%016lx p_vaddr 0x%016lx p_offset 0x%016lx p_filesz 0x%016lx p_memsz 0x%016lx\n",
phdr->p_paddr, phdr->p_vaddr, phdr->p_offset,
phdr->p_filesz, phdr->p_memsz);
if (phdr->p_paddr != phdr->p_vaddr) {
phdr->p_paddr = phdr->p_vaddr;
STDERR("Dump file \"%s\" is a user space core dump\n",
g.opts.device);
}
if (phdr->p_memsz == 0)
return -EINVAL;
if (phdr->p_offset + phdr->p_filesz > zg_size(g.fh))
return -EINVAL;
if (phdr->p_filesz > phdr->p_memsz)
return -EINVAL;
if (phdr->p_filesz > 0) {
off_ptr = zg_alloc(sizeof(*off_ptr));
*off_ptr = phdr->p_offset;
dfi_mem_chunk_add(phdr->p_paddr, phdr->p_filesz, off_ptr,
dfi_elf_mem_chunk_read_fn, zg_free);
}
if (phdr->p_memsz - phdr->p_filesz > 0) {
/* Add zero memory chunk */
dfi_mem_chunk_add(phdr->p_paddr + phdr->p_filesz,
phdr->p_memsz - phdr->p_filesz, NULL,
dfi_mem_chunk_read_zero, NULL);
}
return 0;
}
/*
* Read note
*/
static int nt_read(const Elf64_Nhdr *note, void *buf, size_t buf_len)
{
ssize_t nread;
/* We cannot read more than the current note provides */
if (note->n_descsz < buf_len)
return -EINVAL;
/* Skip note's name and position file at note's descriptor */
zg_seek_cur(g.fh, ROUNDUP(note->n_namesz, 4), ZG_CHECK);
/* Read note's descriptor */
nread = zg_read(g.fh, buf, buf_len, ZG_CHECK_ERR);
if (nread < 0 || (size_t)nread != buf_len)
return -EINVAL;
/* Skip the rest of note's descriptor until the next note */
zg_seek_cur(g.fh, ROUNDUP(note->n_descsz, 4) - buf_len, ZG_CHECK);
return 0;
}
/*
* Skip note
*/
static void nt_skip(const Elf64_Nhdr *note)
{
/* Skip note's name + descriptor and position file at the next note */
zg_seek_cur(g.fh,
ROUNDUP(note->n_namesz, 4) + ROUNDUP(note->n_descsz, 4),
ZG_CHECK);
}
/*
* Ensure that CPU is already defined by prstatus note
*/
static void check_cpu(struct dfi_cpu *cpu, const char *note_str)
{
if (cpu)
return;
ERR_EXIT("Invalid ELF dump (%s before prstatus found)", note_str);
}
/*
* Read prstatus note and return new DFI CPU
*/
static struct dfi_cpu *nt_prstatus_read(Elf64_Nhdr *note)
{
struct dfi_cpu *cpu = dfi_cpu_alloc();
struct nt_prstatus_64 nt_prstatus;
if (nt_read(note, &nt_prstatus, sizeof(nt_prstatus)))
return NULL;
memcpy(cpu->gprs, &nt_prstatus.gprs, sizeof(cpu->gprs));
memcpy(cpu->psw, &nt_prstatus.psw, sizeof(cpu->psw));
memcpy(cpu->acrs, &nt_prstatus.acrs, sizeof(cpu->acrs));
dfi_cpu_add(cpu);
return cpu;
}
/*
* Read fpregset note
*/
static int nt_fpregset_read(struct dfi_cpu *cpu, Elf64_Nhdr *note)
{
struct nt_fpregset_64 nt_fpregset;
check_cpu(cpu, "FPREGSET");
if (nt_read(note, &nt_fpregset, sizeof(nt_fpregset)))
return -EINVAL;
memcpy(&cpu->fpc, &nt_fpregset.fpc, sizeof(cpu->fpc));
memcpy(cpu->fprs, &nt_fpregset.fprs, sizeof(cpu->fprs));
return 0;
}
/*
* Read s390 timer note
*/
static int nt_s390_timer_read(struct dfi_cpu *cpu, Elf64_Nhdr *note)
{
check_cpu(cpu, "S390_TIMER");
return nt_read(note, &cpu->timer, sizeof(cpu->timer));
}
/*
* Read s390 todcmp note
*/
static int nt_s390_todcmp_read(struct dfi_cpu *cpu, Elf64_Nhdr *note)
{
check_cpu(cpu, "S390_TODCMP");
return nt_read(note, &cpu->todcmp, sizeof(cpu->todcmp));
}
/*
* Read s390 todpreg note
*/
static int nt_s390_todpreg_read(struct dfi_cpu *cpu, Elf64_Nhdr *note)
{
check_cpu(cpu, "S390_TODPREG");
return nt_read(note, &cpu->todpreg, sizeof(cpu->todpreg));
}
/*
* Read s390 ctrs note
*/
static int nt_s390_ctrs_read(struct dfi_cpu *cpu, Elf64_Nhdr *note)
{
check_cpu(cpu, "S390_CTRS");
return nt_read(note, &cpu->ctrs, sizeof(cpu->ctrs));
}
/*
* Read s390 prefix note
*/
static int nt_s390_prefix_read(struct dfi_cpu *cpu, Elf64_Nhdr *note)
{
check_cpu(cpu, "S390_PREFIX");
return nt_read(note, &cpu->prefix, sizeof(cpu->prefix));
}
/*
* Read s390 vxrs_low note
*/
static int nt_s390_vxrs_low_read(struct dfi_cpu *cpu, Elf64_Nhdr *note)
{
check_cpu(cpu, "S390_VXRS_LOW");
return nt_read(note, &cpu->vxrs_low, sizeof(cpu->vxrs_low));
}
/*
* Read s390 vxrs_high note
*/
static int nt_s390_vxrs_high_read(struct dfi_cpu *cpu, Elf64_Nhdr *note)
{
check_cpu(cpu, "S390_VXRS_HIGH");
return nt_read(note, &cpu->vxrs_high, sizeof(cpu->vxrs_high));
}
/*
* Add all notes for notes phdr
*/
static int pt_notes_add(Elf64_Phdr *phdr)
{
struct dfi_cpu *cpu_current = NULL;
u64 notes_start_off;
Elf64_Nhdr note;
int rc;
zg_seek(g.fh, phdr->p_offset, ZG_CHECK);
notes_start_off = zg_tell(g.fh, ZG_CHECK);
while (zg_tell(g.fh, ZG_CHECK) - notes_start_off < phdr->p_filesz) {
rc = zg_read(g.fh, &note, sizeof(note), ZG_CHECK_ERR);
if (rc != sizeof(note))
return -EINVAL;
util_log_print(UTIL_LOG_DEBUG, "DFI ELF n_type 0x%x\n",
note.n_type);
switch (note.n_type) {
case NT_PRSTATUS:
cpu_current = nt_prstatus_read(&note);
if (!cpu_current)
return -EINVAL;
break;
case NT_FPREGSET:
if (nt_fpregset_read(cpu_current, &note))
return -EINVAL;
break;
case NT_S390_TIMER:
if (nt_s390_timer_read(cpu_current, &note))
return -EINVAL;
break;
case NT_S390_TODCMP:
if (nt_s390_todcmp_read(cpu_current, &note))
return -EINVAL;
break;
case NT_S390_TODPREG:
if (nt_s390_todpreg_read(cpu_current, &note))
return -EINVAL;
break;
case NT_S390_CTRS:
if (nt_s390_ctrs_read(cpu_current, &note))
return -EINVAL;
break;
case NT_S390_PREFIX:
if (nt_s390_prefix_read(cpu_current, &note))
return -EINVAL;
break;
case NT_S390_VXRS_LOW:
if (nt_s390_vxrs_low_read(cpu_current, &note))
return -EINVAL;
dfi_cpu_content_fac_add(DFI_CPU_CONTENT_FAC_VX);
break;
case NT_S390_VXRS_HIGH:
if (nt_s390_vxrs_high_read(cpu_current, &note))
return -EINVAL;
dfi_cpu_content_fac_add(DFI_CPU_CONTENT_FAC_VX);
break;
default:
nt_skip(&note);
break;
}
}
return 0;
}
/*
* Read ELF header
*/
static int read_elf_hdr(Elf64_Ehdr *ehdr)
{
if (zg_size(g.fh) < sizeof(*ehdr))
return -ENODEV;
zg_read(g.fh, ehdr, sizeof(*ehdr), ZG_CHECK);
if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG) != 0)
return -ENODEV;
if (ehdr->e_type != ET_CORE)
return -ENODEV;
if (ehdr->e_machine != EM_S390 || ehdr->e_ident[EI_CLASS] != ELFCLASS64)
ERR_EXIT("Only s390x (64 bit) core dump files are supported");
return 0;
}
/*
* Initialize ELF input dump format
*/
static int dfi_elf_init(void)
{
Elf64_Ehdr ehdr;
Elf64_Phdr *phdr;
int i;
util_log_print(UTIL_LOG_DEBUG, "DFI ELF initialization\n");
if (read_elf_hdr(&ehdr) != 0)
return -ENODEV;
df_elf_ensure_s390x();
dfi_arch_set(DFI_ARCH_64);
dfi_cpu_info_init(DFI_CPU_CONTENT_ALL);
phdr = util_malloc(sizeof(*phdr) * ehdr.e_phnum);
zg_seek(g.fh, ehdr.e_phoff, ZG_CHECK);
zg_read(g.fh, phdr, sizeof(*phdr) * ehdr.e_phnum, ZG_CHECK);
util_log_print(UTIL_LOG_DEBUG, "DFI ELF e_phnum %u\n", ehdr.e_phnum);
for (i = 0; i < ehdr.e_phnum; i++) {
util_log_print(UTIL_LOG_DEBUG, "DFI ELF p_type[%d] 0x%lx\n",
i, phdr[i].p_type);
switch (phdr[i].p_type) {
case PT_LOAD:
if (pt_load_add(&phdr[i])) {
free(phdr);
return -EINVAL;
}
break;
case PT_NOTE:
if (pt_notes_add(&phdr[i])) {
free(phdr);
return -EINVAL;
}
break;
default:
break;
}
}
free(phdr);
dfi_attr_version_set(ehdr.e_ident[EI_VERSION]);
return 0;
}
/*
* ELF DFI operations
*/
struct dfi dfi_elf = {
.name = "elf",
.init = dfi_elf_init,
.feat_bits = DFI_FEAT_COPY | DFI_FEAT_SEEK,
};