Files
s390-tools/zdump/dfi_pv_elf.c
Marc Hartmayer f4ed0b0ec6 zdump: check size before mmap'ing
Verify that the size is large enough before doing the mmap. Otherwise
this can result in a SIGBUS signal if there is an attempt to access a
page that lies beyond the end of the mapped file (see `man 2 mmap`).

Fixes: 8fa1b5a00b ("zdump: dfi: add support to read Protected Virtualization dumps")
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2022-11-29 17:03:57 +01:00

338 lines
10 KiB
C

/*
* zgetdump - Tool for copying and converting System z dumps
*
* PV ELF core dump input format
*
* Copyright IBM Corp. 2022
*
* 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 <error.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <unistd.h>
#include <elf.h>
#include <errno.h>
#include <glib.h>
#include <openssl/bio.h>
#include <openssl/crypto.h>
#include "lib/util_log.h"
#include "libpv/glib-helper.h"
#include "pv_defs.h"
#include "pv_utils.h"
#include "zgetdump.h"
#include "zg.h"
#include "df_elf.h"
#include "dfi.h"
#include "dfi_mem_chunk.h"
#include "dfi_elf_common.h"
#include "opts.h"
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(Elf64_Phdr, free);
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(Elf64_Shdr, free);
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(Elf64_Ehdr, free);
/**
* read_elf_section_data_as_gbytes:
* @fh: (not nullable): open input file
* @shdr: (not nullable): section header of the section to read
*
* Try to read section data and return it as #GBytes.
*
* Returns: #GBytes on success, %NULL if an error occurred
*/
static GBytes *read_elf_section_data_as_gbytes(const struct zg_fh *fh, const Elf64_Shdr *shdr)
{
unsigned char *data;
size_t size;
data = read_elf_section_data(fh, shdr, &size);
if (!data)
return NULL;
return g_bytes_new_with_free_func(data, size, free, data);
}
/**
* dfi_pv_elf_mem_chunk_read_fn:
* @chunk: (not nullable): memory chunk to read from
* @chunk_off: offset in the memory chunk to read from
* @dst: (not nullable): destination buffer
* @size: size in bytes to read
*
* Memory chunk callback that attempts to read and decrypt up to @size
* bytes encrypted memory from @chunk.
*/
static void dfi_pv_elf_mem_chunk_read_fn(struct dfi_mem_chunk *chunk, u64 chunk_off, void *dst,
u64 size)
{
const pv_elf_ctx_t *ctx = chunk->data;
g_autoptr(GError) error = NULL;
g_assert_nonnull(ctx);
if (pv_elf_read(ctx, chunk_off, dst, size, &error) < 0)
ERR_EXIT(_("Reading encrypted memory failed:" ERR_NEWLINE "%s"), error->message);
}
/**
* nt_s390_pv_cpu_data_read:
* @fh: (not nullable): open input ELF file
* @note_hdr: (not nullable): note header of the note to read
* @version: expected PV CPU dump version
* @dump_key: (not nullable): key to be used to decrypt data stored in the note
* @error: return location for a #GError
*
* Read and decrypt PV CPU data of the note using @dump_key.
*
* Returns: #dfi_cpu_t struct on success, %NULL if an error occurred.
*/
static dfi_cpu_t *nt_s390_pv_cpu_data_read(const struct zg_fh *fh, const Elf64_Nhdr *note_hdr,
const unsigned int version, GBytes *dump_key,
GError **error)
{
const size_t note_descsz = note_hdr->n_descsz;
g_autofree uint8_t *note_data = NULL;
g_autoptr(GBytes) note = NULL;
g_assert(dump_key);
note_data = g_malloc(note_descsz);
if (nt_read(fh, note_hdr, note_data, note_descsz) < 0) {
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_CORRUPTED_NOTE,
_("Unable to read confidential CPU data. Dump probably corrupted."));
return NULL;
}
note = g_bytes_new_take(g_steal_pointer(&note_data), note_descsz);
return pv_decrypt_cpu_note_data(version, note, dump_key, error);
}
/**
* pt_notes_add:
* @fh: (not nullable): open input file
* @phdr: (not nullable): program header of the note segment to add
* @version: expected PV CPU dump version
* @dump_key: (not nullable): key to be used to decrypt data stored in the notes
* @error: return location for a #GError
*
* Read all ELF notes for @phdr.
*
* Returns: %0 on success, -EINVAL if an error occurred.
*/
static int pt_notes_add(const struct zg_fh *fh, const Elf64_Phdr *phdr, const unsigned int version,
GBytes *dump_key, GError **error)
{
if (phdr->p_offset > OFF_T_MAX) {
g_set_error(
error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_CORRUPTED_NOTE,
_("Confidential CPU data has too large offset. Dump probably corrupted."));
return -EINVAL;
}
zg_seek(fh, (off_t)phdr->p_offset, ZG_CHECK);
/* Cast to `size_t` is safe because we're using `ZG_CHECK` here. This
* means an error in `zg_tell` leads to an exit and therefore `zg_tell`
* cannot return an negative value.
*/
while ((size_t)zg_tell(fh, ZG_CHECK) - phdr->p_offset < phdr->p_filesz) {
g_autoptr(dfi_cpu_t) cpu_current = NULL;
Elf64_Nhdr note;
ssize_t rc;
rc = zg_read(fh, &note, sizeof(note), ZG_CHECK_ERR);
if (rc != sizeof(note)) {
g_set_error(
error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_CORRUPTED_NOTE,
_("Confidential CPU data could not be read. Dump probably corrupted."));
return -EINVAL;
}
switch (note.n_type) {
case NT_S390_PV_CPU_DATA:
cpu_current = nt_s390_pv_cpu_data_read(fh, &note, version, dump_key, error);
if (!cpu_current)
return -EINVAL;
dfi_cpu_add(g_steal_pointer(&cpu_current));
break;
default:
util_log_print(UTIL_LOG_WARN, _("Unknown ELF-Note %#x"), note.n_type);
__attribute__((fallthrough));
case NT_PRSTATUS:
case NT_FPREGSET:
case NT_S390_TIMER:
case NT_S390_TODCMP:
case NT_S390_TODPREG:
case NT_S390_CTRS:
case NT_S390_PREFIX:
case NT_S390_VXRS_LOW:
case NT_S390_VXRS_HIGH:
case NT_S390_GS_CB:
/* In case of PV ignore all these note types */
nt_skip(fh, &note);
break;
}
}
return 0;
}
/**
* dfi_pv_elf_init:
*
* Initialize Protected Virtualization ELF input dump format for @g.fh.
*
* Returns: %0 on success, -ENODEV in case it's not a PV ELF dump and
* -EINVAL in case of an error.
*/
static int dfi_pv_elf_init(void)
{
const Elf64_Shdr *completion_shdr = NULL, *storage_state_shdr = NULL;
g_autoptr(GBytes) key = NULL, dump_key = NULL, completion_sdata = NULL;
g_autoptr(storage_state_mmap_t) storage_state_data = NULL;
const pv_dump_completion_v1_t *cpl_conf_v1_decr = NULL;
g_autoptr(pv_dump_completion_t) cpl_conf_decr = NULL;
const char *key_path = g.opts.key_path;
g_autoptr(Elf64_Phdr) phdrs = NULL;
g_autoptr(Elf64_Shdr) shdrs = NULL;
g_autoptr(Elf64_Ehdr) ehdr = NULL;
g_autofree char *shstrtab = NULL;
g_autoptr(GError) error = NULL;
unsigned int cpu_dump_version;
const struct zg_fh *fh = g.fh;
unsigned int shnum, phnum;
bool load_found = false;
size_t shstrtab_size;
unsigned int i;
util_log_print(UTIL_LOG_DEBUG, _("DFI %s initialization\n"), dfi_pv_elf.name);
ehdr = read_elf_hdr(fh);
if (!ehdr)
return -ENODEV;
if (ehdr_check_s390x(ehdr) < 0)
return -ENODEV;
shdrs = read_elf_shdrs(fh, ehdr, &shnum);
if (!shdrs)
return -ENODEV;
shstrtab = read_elf_shstrtab(fh, ehdr, shdrs, shnum, &shstrtab_size);
if (!shstrtab)
return -ENODEV;
if (!pv_is_pv_elf(shdrs, shnum, shstrtab, shstrtab_size))
return -ENODEV;
df_elf_ensure_s390x();
dfi_arch_set(DFI_ARCH_64);
dfi_cpu_info_init(DFI_CPU_CONTENT_ALL);
/* Try to read the customer communication key */
if (!key_path)
return -ENOKEY;
util_log_print(UTIL_LOG_DEBUG, _("Key path: %s\n"), key_path);
key = pv_file_get_content_as_secure_bytes(key_path);
if (!key)
return -ENOKEY;
/* Find PV completion configuration data and storage state data sections */
completion_shdr = find_elf_shdr_by_name(shdrs, shnum, shstrtab, shstrtab_size,
PV_ELF_SECTION_NAME_COMPL);
if (!completion_shdr)
return -EINVAL;
storage_state_shdr = find_elf_shdr_by_name(shdrs, shnum, shstrtab, shstrtab_size,
PV_ELF_SECTION_NAME_TWEAKS);
if (!storage_state_shdr)
return -EINVAL;
/* Read the PV completion configuration section data */
completion_sdata = read_elf_section_data_as_gbytes(fh, completion_shdr);
if (!completion_sdata)
return -EINVAL;
/* Derive and store the dump key in `@dump_key`. This key is used to AES-GCM decrypt the
* completion configuration data and store it in `@cpl_conf_decr`. In addition, mmap the
* configuration storage state area and use the `tweak_nonce`.
*/
if (pv_process_section_data(fh->fh, fh->sb.st_size, completion_sdata,
storage_state_shdr->sh_offset, storage_state_shdr->sh_size, key,
&cpl_conf_decr, &dump_key, &storage_state_data, &error) < 0) {
ERR(_("Unable to read decryption information:" ERR_NEWLINE "%s."), error->message);
return -EINVAL;
}
g_assert_nonnull(cpl_conf_decr);
g_assert_cmpuint(cpl_conf_decr->version, ==, PV_COMPL_DATA_VERSION_1);
cpl_conf_v1_decr = (pv_dump_completion_v1_t *)cpl_conf_decr;
cpu_dump_version = PV_SEC_CPU_DATA_VERSION_1;
phdrs = read_elf_phdrs(fh, ehdr, &phnum);
util_log_print(UTIL_LOG_DEBUG, _("DFI %s e_phnum %u\n"), dfi_pv_elf.name, phnum);
for (i = 0; i < phnum; i++) {
const Elf64_Phdr *phdr = &phdrs[i];
util_log_print(UTIL_LOG_DEBUG, _("DFI %s p_type[%d] 0x%lx\n"), dfi_pv_elf.name, i,
phdr->p_type);
switch (phdr->p_type) {
case PT_LOAD: {
/* Initialize callback data for the `dfi_pv_elf_mem_chunk_read_fn` function
*/
g_autoptr(pv_elf_ctx_t) elf_ctx =
pv_elf_ctx_new(fh->fh, &cpl_conf_v1_decr->data.confidential_area,
storage_state_data, phdr->p_offset, &error);
if (!elf_ctx) {
ERR(_("Reading dump failed:" ERR_NEWLINE "%s"), error->message);
return -EINVAL;
}
if (load_found) {
ERR(_("Reading dump failed:" ERR_NEWLINE
"Multiple PT_LOAD segments are not supported."));
return -EINVAL;
}
if (pt_load_add(fh, phdr, (void **)&elf_ctx, dfi_pv_elf_mem_chunk_read_fn,
(dfi_mem_chunk_free_fn)pv_elf_ctx_free) < 0)
return -EINVAL;
load_found = true;
break;
}
case PT_NOTE:
/* Add CPU information (decrypts the CPU data) */
if (pt_notes_add(fh, phdr, cpu_dump_version, dump_key, &error)) {
ERR(_("Reading dump failed:" ERR_NEWLINE "%s"), error->message);
return -EINVAL;
}
break;
default:
util_log_print(UTIL_LOG_WARN, _("Unknown ELF-PHDR type %#x"), phdr->p_type);
break;
}
}
dfi_attr_version_set(cpl_conf_decr->version);
return 0;
}
static void dfi_pv_elf_cleanup(void)
{
/* nothing to do here */
}
/*
* PV ELF DFI operations
*/
struct dfi dfi_pv_elf = {
.exit = dfi_pv_elf_cleanup,
.feat_bits = DFI_FEAT_COPY | DFI_FEAT_SEEK,
.init = dfi_pv_elf_init,
.name = "ELF (protected virtualization dump)",
};