mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Sometimes dumping a virtual machine from the outside is the only way to get the data that is needed. This can be the case if a dumping mechanism like kdump hasn't been configured or data needs to be fetched at a specific point. Dumping a protected guest from the outside without help from FW/HW doesn't yield sufficient data to be useful. Hence we have introduced Protected Virtualization (PV) dump support - also named confidential dump support. The confidential dump support works by integrating the firmware into the dump process. New Ultravisor calls (UVC) are used to initiate the dump process, dump CPU data, dump memory state and lastly complete the dump process. The guest's data is fully encrypted and can only be decrypted by the entity that owns the customer communication key (CCK) for the dumped guest. The output format is very similar the normal s390 vmcore ELF format, it's only enriched by new sections where the returned data from the UVC "Complete Configuration Dump" and the UVC "Dump Configuration Storage State" is stored. The encrypted CPU data is stored in a new note type `NT_S390_PV_CPU_DATA`. The old note types do still exists but without any confidential data stored. The memory data is stored in the LOAD segment as usual but for PV dumps it's fully AES-XTS encrypted. This commit adds support for reading/decrypting PV guest dumps to zgetdump by introducing a new DFI input module (`dfi_pv_elf.c`). For specifying the customer communication key a new command line option `--key` is added. 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>
338 lines
10 KiB
C
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(¬e_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, ¬e, 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, ¬e, 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, ¬e);
|
|
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, 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)",
|
|
};
|