mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
zdump: dfi: add support to read Protected Virtualization dumps
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>
This commit is contained in:
committed by
Jan Höppner
parent
e537ab902e
commit
8fa1b5a00b
@@ -70,6 +70,7 @@
|
|||||||
#define __may_alias __attribute__((may_alias))
|
#define __may_alias __attribute__((may_alias))
|
||||||
#define __section(x) __attribute__((__section__(#x)))
|
#define __section(x) __attribute__((__section__(#x)))
|
||||||
#define __noinline __attribute__((__noinline__))
|
#define __noinline __attribute__((__noinline__))
|
||||||
|
#define __big_endian
|
||||||
/* The Linux kernel (in stddef.h) and glibc (sys/cdefs.h) define
|
/* The Linux kernel (in stddef.h) and glibc (sys/cdefs.h) define
|
||||||
* __always_inline. Therefore undefine it first to allow the headers
|
* __always_inline. Therefore undefine it first to allow the headers
|
||||||
* to be included first.
|
* to be included first.
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ endif
|
|||||||
|
|
||||||
OBJECTS = zgetdump.o opts.o zg.o zg_error.o zg_print.o \
|
OBJECTS = zgetdump.o opts.o zg.o zg_error.o zg_print.o \
|
||||||
dfi.o dfi_mem_chunk.o dfi_vmcoreinfo.o \
|
dfi.o dfi_mem_chunk.o dfi_vmcoreinfo.o \
|
||||||
dfi_lkcd.o dfi_elf.o dfi_elf_common.o \
|
dfi_lkcd.o dfi_elf.o dfi_elf_common.o dfi_pv_elf.o \
|
||||||
dfi_s390.o dfi_s390_ext.o\
|
dfi_s390.o dfi_s390_ext.o\
|
||||||
dfi_s390mv.o dfi_s390mv_ext.o \
|
dfi_s390mv.o dfi_s390mv_ext.o \
|
||||||
dfi_s390tape.o dfi_kdump.o \
|
dfi_s390tape.o dfi_kdump.o \
|
||||||
@@ -75,7 +75,8 @@ OBJECTS = zgetdump.o opts.o zg.o zg_error.o zg_print.o \
|
|||||||
dt.o dt_s390sv.o dt_s390sv_ext.o \
|
dt.o dt_s390sv.o dt_s390sv_ext.o \
|
||||||
dt_s390mv.o dt_s390mv_ext.o \
|
dt_s390mv.o dt_s390mv_ext.o \
|
||||||
dt_scsi.o output.o \
|
dt_scsi.o output.o \
|
||||||
ngdump.o dt_ngdump.o dfi_ngdump.o
|
ngdump.o dt_ngdump.o dfi_ngdump.o \
|
||||||
|
pv_utils.o
|
||||||
|
|
||||||
ifneq ($(shell sh -c 'command -v pkg-config'),)
|
ifneq ($(shell sh -c 'command -v pkg-config'),)
|
||||||
GLIB2_CFLAGS := $(shell pkg-config --silence-errors --cflags glib-2.0)
|
GLIB2_CFLAGS := $(shell pkg-config --silence-errors --cflags glib-2.0)
|
||||||
|
|||||||
@@ -81,6 +81,13 @@
|
|||||||
#define NT_S390_GS_CB 0x30b
|
#define NT_S390_GS_CB 0x30b
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* S390 confidential CPU data from secure guest.
|
||||||
|
*/
|
||||||
|
#ifndef NT_S390_PV_CPU_DATA
|
||||||
|
#define NT_S390_PV_CPU_DATA 0x30e
|
||||||
|
#endif
|
||||||
|
|
||||||
#define NOTE_NAME_CORE "CORE"
|
#define NOTE_NAME_CORE "CORE"
|
||||||
#define NOTE_NAME_LINUX "LINUX"
|
#define NOTE_NAME_LINUX "LINUX"
|
||||||
#define NOTE_NAME_VMCOREINFO "VMCOREINFO"
|
#define NOTE_NAME_VMCOREINFO "VMCOREINFO"
|
||||||
|
|||||||
10
zdump/dfi.c
10
zdump/dfi.c
@@ -26,6 +26,7 @@
|
|||||||
* DFI vector - ensure that tape is the first in the list and devmem the second!
|
* DFI vector - ensure that tape is the first in the list and devmem the second!
|
||||||
*/
|
*/
|
||||||
static struct dfi *dfi_vec[] = {
|
static struct dfi *dfi_vec[] = {
|
||||||
|
/* clang-format off */
|
||||||
&dfi_s390tape,
|
&dfi_s390tape,
|
||||||
&dfi_devmem,
|
&dfi_devmem,
|
||||||
&dfi_s390mv_ext,
|
&dfi_s390mv_ext,
|
||||||
@@ -33,11 +34,13 @@ static struct dfi *dfi_vec[] = {
|
|||||||
&dfi_s390_ext,
|
&dfi_s390_ext,
|
||||||
&dfi_s390,
|
&dfi_s390,
|
||||||
&dfi_lkcd,
|
&dfi_lkcd,
|
||||||
|
&dfi_pv_elf,
|
||||||
&dfi_elf,
|
&dfi_elf,
|
||||||
&dfi_kdump,
|
&dfi_kdump,
|
||||||
&dfi_kdump_flat,
|
&dfi_kdump_flat,
|
||||||
&dfi_ngdump,
|
&dfi_ngdump,
|
||||||
NULL,
|
NULL,
|
||||||
|
/* clang-format on */
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -168,6 +171,11 @@ struct dfi_cpu *dfi_cpu_alloc(void)
|
|||||||
return zg_alloc(sizeof(struct dfi_cpu));
|
return zg_alloc(sizeof(struct dfi_cpu));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void dfi_cpu_free(struct dfi_cpu *cpu)
|
||||||
|
{
|
||||||
|
zg_free(cpu);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Add DFI CPU
|
* Add DFI CPU
|
||||||
*/
|
*/
|
||||||
@@ -780,7 +788,7 @@ int dfi_init(void)
|
|||||||
}
|
}
|
||||||
util_log_print(UTIL_LOG_DEBUG, "DFI %s returned with rc %d\n",
|
util_log_print(UTIL_LOG_DEBUG, "DFI %s returned with rc %d\n",
|
||||||
dfi->name, rc);
|
dfi->name, rc);
|
||||||
if (rc == 0 || rc == -EINVAL)
|
if (rc == 0 || rc == -EINVAL || rc == -ENOKEY)
|
||||||
return rc;
|
return rc;
|
||||||
zg_close(g.fh);
|
zg_close(g.fh);
|
||||||
i++;
|
i++;
|
||||||
|
|||||||
@@ -147,6 +147,7 @@ void dfi_cpu_content_fac_add(int flags);
|
|||||||
struct util_list *dfi_cpu_list(void);
|
struct util_list *dfi_cpu_list(void);
|
||||||
void dfi_cpu_info_init(enum dfi_cpu_content content);
|
void dfi_cpu_info_init(enum dfi_cpu_content content);
|
||||||
struct dfi_cpu *dfi_cpu_alloc(void);
|
struct dfi_cpu *dfi_cpu_alloc(void);
|
||||||
|
void dfi_cpu_free(struct dfi_cpu *cpu);
|
||||||
struct dfi_cpu *dfi_cpu(unsigned int cpu_nr);
|
struct dfi_cpu *dfi_cpu(unsigned int cpu_nr);
|
||||||
void dfi_cpu_add(struct dfi_cpu *cpu);
|
void dfi_cpu_add(struct dfi_cpu *cpu);
|
||||||
unsigned int dfi_cpu_cnt(void);
|
unsigned int dfi_cpu_cnt(void);
|
||||||
@@ -248,6 +249,7 @@ extern struct dfi dfi_s390mv_ext;
|
|||||||
extern struct dfi dfi_s390;
|
extern struct dfi dfi_s390;
|
||||||
extern struct dfi dfi_s390_ext;
|
extern struct dfi dfi_s390_ext;
|
||||||
extern struct dfi dfi_lkcd;
|
extern struct dfi dfi_lkcd;
|
||||||
|
extern struct dfi dfi_pv_elf;
|
||||||
extern struct dfi dfi_elf;
|
extern struct dfi dfi_elf;
|
||||||
extern struct dfi dfi_kdump;
|
extern struct dfi dfi_kdump;
|
||||||
extern struct dfi dfi_kdump_flat;
|
extern struct dfi dfi_kdump_flat;
|
||||||
|
|||||||
337
zdump/dfi_pv_elf.c
Normal file
337
zdump/dfi_pv_elf.c
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
/*
|
||||||
|
* 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)",
|
||||||
|
};
|
||||||
32
zdump/opts.c
32
zdump/opts.c
@@ -20,9 +20,11 @@
|
|||||||
#include "opts.h"
|
#include "opts.h"
|
||||||
|
|
||||||
static struct option long_opts[] = {
|
static struct option long_opts[] = {
|
||||||
|
/* clang-format off */
|
||||||
{"help", no_argument, NULL, 'h'},
|
{"help", no_argument, NULL, 'h'},
|
||||||
{"version", no_argument, NULL, 'v'},
|
{"version", no_argument, NULL, 'v'},
|
||||||
{"info", no_argument, NULL, 'i'},
|
{"info", no_argument, NULL, 'i'},
|
||||||
|
{"key", required_argument, NULL, 'k'},
|
||||||
{"device", no_argument, NULL, 'd'},
|
{"device", no_argument, NULL, 'd'},
|
||||||
{"mount", no_argument, NULL, 'm'},
|
{"mount", no_argument, NULL, 'm'},
|
||||||
{"umount", no_argument, NULL, 'u'},
|
{"umount", no_argument, NULL, 'u'},
|
||||||
@@ -30,19 +32,20 @@ static struct option long_opts[] = {
|
|||||||
{"select", required_argument, NULL, 's'},
|
{"select", required_argument, NULL, 's'},
|
||||||
{"debug", no_argument, NULL, 'X'},
|
{"debug", no_argument, NULL, 'X'},
|
||||||
{"verbose", no_argument, NULL, 'V'},
|
{"verbose", no_argument, NULL, 'V'},
|
||||||
{NULL, 0, NULL, 0 }
|
{NULL, 0, NULL, 0 },
|
||||||
|
/* clang-format on */
|
||||||
};
|
};
|
||||||
|
|
||||||
static const char optstr[] = "hvVidmus:f:X";
|
static const char optstr[] = "hvVidmuk:s:f:X";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Text for --help option
|
* Text for --help option
|
||||||
*/
|
*/
|
||||||
static const char help_text[] =
|
static const char help_text[] =
|
||||||
"Usage: zgetdump DUMP [-s SYS] [-f FMT] > DUMP_FILE\n"
|
"Usage: zgetdump DUMP [-s SYS] [-f FMT] [-k KEY] > DUMP_FILE\n"
|
||||||
" DUMP [-s SYS] [-f FMT] DUMP_FILE\n"
|
" DUMP [-s SYS] [-f FMT] [-k KEY] DUMP_FILE\n"
|
||||||
" -m DUMP [-s SYS] [-f FMT] DIR\n"
|
" -m DUMP [-s SYS] [-f FMT] [-k KEY] DIR\n"
|
||||||
" -i DUMP [-s SYS]\n"
|
" -i DUMP [-s SYS] [-k KEY]\n"
|
||||||
" -d DUMPDEV\n"
|
" -d DUMPDEV\n"
|
||||||
" -u DIR\n"
|
" -u DIR\n"
|
||||||
"\n"
|
"\n"
|
||||||
@@ -58,6 +61,7 @@ static const char help_text[] =
|
|||||||
"In the syntax description, DUMP specifies a dump device or dump file to be\n"
|
"In the syntax description, DUMP specifies a dump device or dump file to be\n"
|
||||||
"read. The following options are available:\n"
|
"read. The following options are available:\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
"-k, --key Specify the key KEY to decrypt the protected virtualization dump\n"
|
||||||
"-m, --mount Mount DUMP to mount point DIR\n"
|
"-m, --mount Mount DUMP to mount point DIR\n"
|
||||||
"-u, --umount Unmount dump from mount point DIR\n"
|
"-u, --umount Unmount dump from mount point DIR\n"
|
||||||
"-i, --info Print DUMP information\n"
|
"-i, --info Print DUMP information\n"
|
||||||
@@ -71,7 +75,7 @@ static const char help_text[] =
|
|||||||
" messages. This option is intended for debugging\n"
|
" messages. This option is intended for debugging\n"
|
||||||
"-h, --help Print this help, then exit\n";
|
"-h, --help Print this help, then exit\n";
|
||||||
|
|
||||||
static const char copyright_str[] = "Copyright IBM Corp. 2001, 2018";
|
static const char copyright_str[] = "Copyright IBM Corp. 2001, 2022";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Select option strings
|
* Select option strings
|
||||||
@@ -88,6 +92,7 @@ static void init_defaults(struct options *opts)
|
|||||||
opts->prog_name = "zgetdump";
|
opts->prog_name = "zgetdump";
|
||||||
opts->action = ZG_ACTION_COPY;
|
opts->action = ZG_ACTION_COPY;
|
||||||
opts->output_path = NULL;
|
opts->output_path = NULL;
|
||||||
|
opts->key_path = NULL;
|
||||||
#ifdef __s390x__
|
#ifdef __s390x__
|
||||||
opts->fmt = "elf";
|
opts->fmt = "elf";
|
||||||
#else
|
#else
|
||||||
@@ -181,6 +186,16 @@ static void output_set(struct options *opts, const char *path)
|
|||||||
opts->output_path = zg_strdup(path);
|
opts->output_path = zg_strdup(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set customer communication key (CCK)
|
||||||
|
*/
|
||||||
|
static void key_set(struct options *opts, const char *key)
|
||||||
|
{
|
||||||
|
assert(!opts->key_path);
|
||||||
|
|
||||||
|
opts->key_path = zg_strdup(key);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set FUSE debug options
|
* Set FUSE debug options
|
||||||
*/
|
*/
|
||||||
@@ -318,6 +333,9 @@ void opts_parse(int argc, char *argv[], struct options *opts)
|
|||||||
case 's':
|
case 's':
|
||||||
select_set(opts, optarg);
|
select_set(opts, optarg);
|
||||||
break;
|
break;
|
||||||
|
case 'k':
|
||||||
|
key_set(opts, optarg);
|
||||||
|
break;
|
||||||
case 'X':
|
case 'X':
|
||||||
opts->debug_specified = 1;
|
opts->debug_specified = 1;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ struct options {
|
|||||||
char *device;
|
char *device;
|
||||||
/* If `output_path == NULL` the output is written to `stdout` */
|
/* If `output_path == NULL` the output is written to `stdout` */
|
||||||
const char *output_path;
|
const char *output_path;
|
||||||
|
const char *key_path;
|
||||||
char *mount_point;
|
char *mount_point;
|
||||||
int fmt_specified;
|
int fmt_specified;
|
||||||
const char *fmt;
|
const char *fmt;
|
||||||
|
|||||||
218
zdump/pv_defs.h
Normal file
218
zdump/pv_defs.h
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
/*
|
||||||
|
* zgetdump - Tool for copying and converting System z dumps
|
||||||
|
*
|
||||||
|
* Secure execution/PV guest dump definitions.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef PV_DEFS_H
|
||||||
|
#define PV_DEFS_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "lib/zt_common.h"
|
||||||
|
|
||||||
|
/* Special ELF section names used for Protected Virtualization Dumps */
|
||||||
|
#define PV_ELF_SECTION_NAME_COMPL "pv_compl"
|
||||||
|
#define PV_ELF_SECTION_NAME_TWEAKS "pv_mem_meta"
|
||||||
|
|
||||||
|
#define PV_COMPL_DATA_VERSION_1 ((uint32_t)1)
|
||||||
|
#define PV_SEC_CPU_DATA_VERSION_1 ((uint32_t)1)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pv_tweak_nonce_t:
|
||||||
|
*
|
||||||
|
* Struct representing a tweak nonce.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
uint8_t value[16];
|
||||||
|
} pv_tweak_nonce_t __big_endian;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pv_dump_completion_aad_v1_t:
|
||||||
|
*
|
||||||
|
* Struct representing the authenticated part of a PV dump completion v1
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
uint32_t version; /* 0000 */
|
||||||
|
uint32_t len; /* 0004 */
|
||||||
|
uint64_t res_0x8; /* 0008 */
|
||||||
|
uint8_t seed[64]; /* 0016 */
|
||||||
|
uint8_t iv[12]; /* 0080 */
|
||||||
|
uint32_t res_0x92; /* 0092 */
|
||||||
|
} pv_dump_completion_aad_v1_t __big_endian;
|
||||||
|
STATIC_ASSERT(sizeof(pv_dump_completion_aad_v1_t) == 96)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pv_dump_completion_confidential_area_v1_t:
|
||||||
|
*
|
||||||
|
* Struct representing the encrypted part of a PV dump completion v1
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
pv_tweak_nonce_t nonce; /* 0096 */
|
||||||
|
union {
|
||||||
|
uint8_t key[64]; /* 0112 */
|
||||||
|
struct {
|
||||||
|
uint8_t key1[32]; /* 0112 */
|
||||||
|
uint8_t key2[32]; /* 0144 */
|
||||||
|
};
|
||||||
|
};
|
||||||
|
uint8_t res_0x176[96]; /* 0176 */
|
||||||
|
} pv_dump_completion_confidential_area_v1_t __big_endian;
|
||||||
|
STATIC_ASSERT(sizeof(pv_dump_completion_confidential_area_v1_t) == 176)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pv_dump_completion_data_v1_t
|
||||||
|
*
|
||||||
|
* Struct to interpret the data returned by the `Complete Configuration Dump` UVC.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
pv_dump_completion_aad_v1_t aad; /* 0000 */
|
||||||
|
pv_dump_completion_confidential_area_v1_t confidential_area; /* 0096 */
|
||||||
|
uint8_t tag[16]; /* 0272 */
|
||||||
|
/* 0288 */
|
||||||
|
} pv_dump_completion_data_v1_t __big_endian;
|
||||||
|
STATIC_ASSERT(sizeof(pv_dump_completion_data_v1_t) == 288)
|
||||||
|
|
||||||
|
/* Container data structures used to implement polymorphic behavior */
|
||||||
|
#define PV_STRUCT_NAME(_struct, _version) _struct##_v##_version##_t
|
||||||
|
#define PV_GET_STRUCT_DATA(_struct, _version, _container) \
|
||||||
|
_container->version != _version ? \
|
||||||
|
NULL : \
|
||||||
|
&(((PV_STRUCT_NAME(_struct, _version) *)_container)->data)
|
||||||
|
|
||||||
|
#define PV_GET_DUMP_DATA_V1(_container) PV_GET_STRUCT_DATA(pv_dump_completion, 1, _container)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
unsigned int version;
|
||||||
|
} pv_dump_completion_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
pv_dump_completion_t super;
|
||||||
|
pv_dump_completion_data_v1_t data;
|
||||||
|
} PV_STRUCT_NAME(pv_dump_completion, 1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pv_cpu_vector_register_t:
|
||||||
|
*
|
||||||
|
* Struct representing the PV CPU vector register
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
uint64_t low;
|
||||||
|
uint64_t high;
|
||||||
|
} pv_cpu_vector_register_t __big_endian;
|
||||||
|
STATIC_ASSERT(sizeof(pv_cpu_vector_register_t) == 16)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pv_cpu_dump_aad_v1_t:
|
||||||
|
*
|
||||||
|
* Struct representing the authenticated part of a PV cpu dump v1
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
uint32_t version; /* 0000 */
|
||||||
|
uint32_t len; /* 0004 */
|
||||||
|
uint8_t iv[12]; /* 0008 */
|
||||||
|
uint8_t res_0x20[12]; /* 0020 */
|
||||||
|
} pv_cpu_dump_aad_v1_t __big_endian;
|
||||||
|
STATIC_ASSERT(sizeof(pv_cpu_dump_aad_v1_t) == 32)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pv_cpu_dump_confidential_area_v1_t:
|
||||||
|
*
|
||||||
|
* Struct to interpret the Secure CPU Dump Area UVC.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
uint8_t gprs[128]; /* 0032 */
|
||||||
|
uint8_t psw[16]; /* 0160 */
|
||||||
|
uint8_t res_0x176[8]; /* 0176 */
|
||||||
|
uint32_t prefix; /* 0184 */
|
||||||
|
uint32_t fpc; /* 0188 */
|
||||||
|
uint8_t res_0x192[4]; /* 0192 */
|
||||||
|
uint32_t todpreg; /* 0196 */
|
||||||
|
uint64_t timer; /* 0200 */
|
||||||
|
uint64_t todcmp; /* 0208 */
|
||||||
|
uint8_t res_0x216[8]; /* 0216 */
|
||||||
|
uint8_t acrs[64]; /* 0224 */
|
||||||
|
uint8_t ctrs[128]; /* 0288 */
|
||||||
|
struct {
|
||||||
|
pv_cpu_vector_register_t vector_register_low[16];
|
||||||
|
uint8_t vector_register_high[256];
|
||||||
|
}; /* 0416 */
|
||||||
|
uint8_t res_0x928[512]; /* 0928 */
|
||||||
|
uint8_t zeros_2[8]; /* 1440 */
|
||||||
|
uint64_t gsd; /* 1448 */
|
||||||
|
uint64_t gssm; /* 1456 */
|
||||||
|
uint64_t gs_epl_a; /* 1464 */
|
||||||
|
uint8_t res_0x1472[64]; /* 1472 */
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
uint16_t has_osii : 1;
|
||||||
|
uint16_t reserved : 15;
|
||||||
|
};
|
||||||
|
uint16_t dump_flags; /* 1536 */
|
||||||
|
};
|
||||||
|
uint8_t res_0x1538[6]; /* 1538 */
|
||||||
|
uint8_t res_0x1544[8]; /* 1544 */
|
||||||
|
} pv_cpu_dump_confidential_area_v1_t __big_endian;
|
||||||
|
STATIC_ASSERT(sizeof(pv_cpu_dump_confidential_area_v1_t) == 1520)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pv_cpu_dump_v1_t:
|
||||||
|
*
|
||||||
|
* Struct representing a PV CPU dump v1 returned by the `Dump CPU state` UVC.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
pv_cpu_dump_aad_v1_t aad; /* 0000 */
|
||||||
|
pv_cpu_dump_confidential_area_v1_t confidential_area; /* 0032 */
|
||||||
|
uint8_t tag[16]; /* 1552 */
|
||||||
|
} pv_cpu_dump_v1_t __big_endian;
|
||||||
|
STATIC_ASSERT(sizeof(pv_cpu_dump_v1_t) == 1568)
|
||||||
|
|
||||||
|
/* PV special tweak component indicator */
|
||||||
|
#define PV_SPECIAL_INDICATOR 0xFFFFFFFFUL
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pv_special_tweak_component_layout_t:
|
||||||
|
*
|
||||||
|
* Struct representing the layout of a special tweak component.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
uint32_t indicator;
|
||||||
|
uint8_t reserved[10];
|
||||||
|
uint8_t flag_reserved1;
|
||||||
|
uint8_t flag_reserved2 : 5;
|
||||||
|
uint8_t is_mapped_page : 1;
|
||||||
|
uint8_t is_shared_page : 1;
|
||||||
|
uint8_t is_zero_page : 1;
|
||||||
|
} pv_special_tweak_component_layout_t __big_endian;
|
||||||
|
STATIC_ASSERT(sizeof(pv_special_tweak_component_layout_t) == 16)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pv_tweak_component_t:
|
||||||
|
*
|
||||||
|
* Struct representing a tweak component returned by `Dump Configuration Storage State` UVC.
|
||||||
|
*/
|
||||||
|
typedef union {
|
||||||
|
uint8_t value[16];
|
||||||
|
pv_special_tweak_component_layout_t special;
|
||||||
|
} pv_tweak_component_t __big_endian;
|
||||||
|
STATIC_ASSERT(sizeof(pv_tweak_component_t) == 16)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pv_tweak_t:
|
||||||
|
*
|
||||||
|
* Struct representing one tweak value.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
uint8_t value[16];
|
||||||
|
} pv_tweak_t;
|
||||||
|
STATIC_ASSERT(sizeof(pv_tweak_t) == 16)
|
||||||
|
|
||||||
|
STATIC_ASSERT(sizeof_field(pv_tweak_t, value) == sizeof_field(pv_tweak_component_t, value))
|
||||||
|
STATIC_ASSERT(sizeof_field(pv_tweak_t, value) == sizeof_field(pv_tweak_nonce_t, value))
|
||||||
|
|
||||||
|
#endif /* PV_DEFS_H */
|
||||||
980
zdump/pv_utils.c
Normal file
980
zdump/pv_utils.c
Normal file
@@ -0,0 +1,980 @@
|
|||||||
|
/*
|
||||||
|
* zgetdump - Tool for copying and converting System z dumps
|
||||||
|
*
|
||||||
|
* Utilities to decrypt secure execution guest dumps.
|
||||||
|
*
|
||||||
|
* Copyright IBM Corp. 2001, 2021
|
||||||
|
*
|
||||||
|
* 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 "pv_utils.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include <openssl/bio.h>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
#include <openssl/kdf.h>
|
||||||
|
#include <openssl/crypto.h>
|
||||||
|
|
||||||
|
#include "lib/zt_common.h"
|
||||||
|
#include "lib/util_log.h"
|
||||||
|
#include "libpv/crypto.h"
|
||||||
|
#include "libpv/se-hdr.h"
|
||||||
|
#include "pv_defs.h"
|
||||||
|
|
||||||
|
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(Elf64_Phdr, free)
|
||||||
|
|
||||||
|
/* Definitions for decrypting memory and deriving dump key */
|
||||||
|
#define PV_CSS_PAGESIZE 0x1000U /* Configuration storage state page size */
|
||||||
|
#define PV_DUMP_V1_HKDF_INFO "IBM Z Ultravisor Dump"
|
||||||
|
#define PV_DUMP_V1_HKDF_LEN 32
|
||||||
|
#define PV_DUMP_V1_HKDF_FUN EVP_sha512()
|
||||||
|
#define PV_DUMP_V1_CIPHER EVP_aes_256_gcm()
|
||||||
|
|
||||||
|
static gboolean u64_checked_add(u64 *res, u64 lhs, u64 rhs)
|
||||||
|
{
|
||||||
|
guint64 _res = 0;
|
||||||
|
gboolean success = g_uint64_checked_add(&_res, (guint64)lhs, (guint64)rhs);
|
||||||
|
|
||||||
|
*res = (u64)_res;
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 page_offset(u64 addr)
|
||||||
|
{
|
||||||
|
return addr % PV_CSS_PAGESIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 page_index(u64 addr)
|
||||||
|
{
|
||||||
|
return addr / PV_CSS_PAGESIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 page_start_addr(u64 page_idx, GError **error)
|
||||||
|
{
|
||||||
|
gboolean success;
|
||||||
|
uint64_t ret;
|
||||||
|
|
||||||
|
success = g_uint64_checked_mul(&ret, page_idx, PV_CSS_PAGESIZE);
|
||||||
|
if (G_UNLIKELY(!success)) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_PAGE_START_ADDR_OVERFLOW,
|
||||||
|
_("UInt overflow detected: %s: pageidx: %#llx"), __func__, page_idx);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 page_end_addr(u64 page_idx, GError **error)
|
||||||
|
{
|
||||||
|
gboolean success;
|
||||||
|
uint64_t ret;
|
||||||
|
|
||||||
|
/* (page_idx + 1) * PV_CSS_PAGESIZE - 1; */
|
||||||
|
success = g_uint64_checked_add(&ret, page_idx, 1);
|
||||||
|
success &= g_uint64_checked_mul(&ret, ret, PV_CSS_PAGESIZE);
|
||||||
|
ret -= 1;
|
||||||
|
if (G_UNLIKELY(!success)) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_PAGE_END_ADDR_OVERFLOW,
|
||||||
|
_("UInt overflow detected: %s: pageidx: %#llx"), __func__, page_idx);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pv_dump_completion_v1_free(pv_dump_completion_v1_t *cpl)
|
||||||
|
{
|
||||||
|
pv_dump_completion_free((pv_dump_completion_t *)cpl);
|
||||||
|
}
|
||||||
|
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(pv_dump_completion_v1_t, pv_dump_completion_v1_free)
|
||||||
|
|
||||||
|
void pv_dump_completion_free(pv_dump_completion_t *cpl)
|
||||||
|
{
|
||||||
|
if (!cpl)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (cpl->version == PV_COMPL_DATA_VERSION_1) {
|
||||||
|
pv_dump_completion_v1_t *cpl_v1 = (pv_dump_completion_v1_t *)cpl;
|
||||||
|
OPENSSL_cleanse(&cpl_v1->data, sizeof(cpl_v1->data));
|
||||||
|
}
|
||||||
|
g_free(cpl);
|
||||||
|
}
|
||||||
|
|
||||||
|
GBytes *pv_derive_dump_key_v1(const pv_dump_completion_data_v1_t *cpl_data, GBytes *cck,
|
||||||
|
GError **error)
|
||||||
|
{
|
||||||
|
g_autoptr(GBytes) salt = NULL, info = NULL;
|
||||||
|
size_t cck_size;
|
||||||
|
size_t exp_cck_size = sizeof_field(struct pv_hdr_encrypted, cust_comm_key);
|
||||||
|
|
||||||
|
assert(cpl_data->aad.version == PV_COMPL_DATA_VERSION_1);
|
||||||
|
|
||||||
|
cck_size = g_bytes_get_size(cck);
|
||||||
|
if (cck_size != exp_cck_size) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_WRONG_CCK_SIZE,
|
||||||
|
_("Wrong key size: expected %lu != actual %lu"), exp_cck_size,
|
||||||
|
cck_size);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
salt = g_bytes_new(&cpl_data->aad.seed, sizeof(cpl_data->aad.seed));
|
||||||
|
info = g_bytes_new(PV_DUMP_V1_HKDF_INFO, strlen(PV_DUMP_V1_HKDF_INFO));
|
||||||
|
return pv_hkdf_extract_and_expand(PV_DUMP_V1_HKDF_LEN, cck, salt, info, PV_DUMP_V1_HKDF_FUN,
|
||||||
|
error);
|
||||||
|
}
|
||||||
|
|
||||||
|
pv_dump_completion_v1_t *pv_decrypt_dump_completion_v1(const pv_dump_completion_data_v1_t *cpl_data,
|
||||||
|
GBytes *dump_key, GError **error)
|
||||||
|
{
|
||||||
|
g_autoptr(GBytes) encr = NULL, aad = NULL, tag = NULL, decr = NULL, iv = NULL;
|
||||||
|
g_autoptr(pv_dump_completion_v1_t) cpl = NULL;
|
||||||
|
PvCipherParms params;
|
||||||
|
size_t copied;
|
||||||
|
void *tmp;
|
||||||
|
|
||||||
|
assert(cpl_data->aad.version == PV_COMPL_DATA_VERSION_1);
|
||||||
|
|
||||||
|
encr = g_bytes_new(&cpl_data->confidential_area, sizeof(cpl_data->confidential_area));
|
||||||
|
aad = g_bytes_new(&cpl_data->aad, sizeof(cpl_data->aad));
|
||||||
|
tag = g_bytes_new(&cpl_data->tag, sizeof(cpl_data->tag));
|
||||||
|
iv = g_bytes_new(&cpl_data->aad.iv, sizeof(cpl_data->aad.iv));
|
||||||
|
params.cipher = PV_DUMP_V1_CIPHER;
|
||||||
|
params.key = dump_key;
|
||||||
|
params.iv = iv;
|
||||||
|
params.tag_size = g_bytes_get_size(tag);
|
||||||
|
if (pv_gcm_decrypt(encr, aad, tag, ¶ms, &decr, error) < 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
cpl = g_malloc(sizeof(*cpl));
|
||||||
|
cpl->super.version = PV_COMPL_DATA_VERSION_1;
|
||||||
|
tmp = pv_gbytes_memcpy(&cpl->data.aad, sizeof(cpl->data.aad), aad, &copied);
|
||||||
|
if (!tmp || copied != sizeof(cpl->data.aad)) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_WRONG_AAD_SIZE,
|
||||||
|
_("Wrong AAD size"));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp = pv_gbytes_memcpy(&cpl->data.confidential_area, sizeof(cpl->data.confidential_area),
|
||||||
|
decr, &copied);
|
||||||
|
if (!tmp || copied != sizeof(cpl->data.confidential_area)) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_WRONG_CONFIDENTIAL_SIZE,
|
||||||
|
_("Wrong confidential data size"));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp = pv_gbytes_memcpy(&cpl->data.tag, sizeof(cpl->data.tag), tag, &copied);
|
||||||
|
if (!tmp || copied != sizeof(cpl->data.tag)) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_WRONG_TAG_SIZE,
|
||||||
|
_("Wrong tag size"));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return g_steal_pointer(&cpl);
|
||||||
|
}
|
||||||
|
|
||||||
|
static long pv_cpu_note_data_get_version(GBytes *note, GError **error)
|
||||||
|
{
|
||||||
|
size_t size;
|
||||||
|
const uint32_t *version = g_bytes_get_data(note, &size);
|
||||||
|
STATIC_ASSERT(offsetof(pv_cpu_dump_aad_v1_t, version) == 0);
|
||||||
|
|
||||||
|
/* check whether we can dereference @version */
|
||||||
|
if (sizeof(*version) > size) {
|
||||||
|
g_set_error(
|
||||||
|
error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_WRONG_NOTE_SIZE, "%s",
|
||||||
|
_("Confidential CPU data has incorrect size. Dump probably corrupted."));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return *version;
|
||||||
|
}
|
||||||
|
|
||||||
|
dfi_cpu_t *pv_decrypt_cpu_note_data(const unsigned int expected_version, GBytes *cpu_note,
|
||||||
|
GBytes *dump_key, GError **error)
|
||||||
|
{
|
||||||
|
long version = pv_cpu_note_data_get_version(cpu_note, error);
|
||||||
|
if (version < 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (version != expected_version) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_UNSUPP_SEC_CPU_VER,
|
||||||
|
_("Wrong NT_S390_PV_CPU_DATA version (%ld)"), version);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (version) {
|
||||||
|
case PV_SEC_CPU_DATA_VERSION_1: {
|
||||||
|
const pv_cpu_dump_confidential_area_v1_t *pv_cpu;
|
||||||
|
const pv_cpu_dump_v1_t *cpu_encrypted;
|
||||||
|
size_t cpu_note_size, cpu_decrypted_size;
|
||||||
|
g_autoptr(GBytes) cpu_decrypted = NULL;
|
||||||
|
|
||||||
|
cpu_encrypted = g_bytes_get_data(cpu_note, &cpu_note_size);
|
||||||
|
if (sizeof(*cpu_encrypted) > cpu_note_size) {
|
||||||
|
g_set_error(
|
||||||
|
error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_WRONG_NOTE_SIZE,
|
||||||
|
_("Confidential CPU data has incorrect size. Dump probably corrupted."));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
cpu_decrypted = pv_decrypt_cpu_dump_area_v1(cpu_encrypted, dump_key, error);
|
||||||
|
if (!cpu_decrypted) {
|
||||||
|
g_prefix_error(
|
||||||
|
error,
|
||||||
|
_("Unable to authenticate confidential CPU data. Dump probably corrupted:" ERR_NEWLINE));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
pv_cpu = g_bytes_get_data(cpu_decrypted, &cpu_decrypted_size);
|
||||||
|
if (cpu_decrypted_size != sizeof(*pv_cpu)) {
|
||||||
|
g_set_error(
|
||||||
|
error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_WRONG_NOTE_SIZE,
|
||||||
|
_("Confidential CPU data has incorrect size. Dump probably corrupted."));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check dump flags */
|
||||||
|
if (pv_cpu->has_osii)
|
||||||
|
util_log_print(UTIL_LOG_WARN,
|
||||||
|
_("CPU state may contain partial instruction results"));
|
||||||
|
|
||||||
|
return pv_dfi_cpu_from_pv_cpu(pv_cpu);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_UNSUPP_SEC_CPU_VER,
|
||||||
|
_("Unsupported NT_S390_PV_CPU_DATA version (%ld)"), version);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GBytes *pv_decrypt_cpu_dump_area_v1(const pv_cpu_dump_v1_t *cpu_dump_area, GBytes *dump_key,
|
||||||
|
GError **error)
|
||||||
|
{
|
||||||
|
g_autoptr(GBytes) encr = NULL, aad = NULL, tag = NULL;
|
||||||
|
g_autoptr(GBytes) iv = NULL, out = NULL;
|
||||||
|
PvCipherParms params;
|
||||||
|
|
||||||
|
encr = g_bytes_new(&cpu_dump_area->confidential_area,
|
||||||
|
sizeof(cpu_dump_area->confidential_area));
|
||||||
|
aad = g_bytes_new(&cpu_dump_area->aad, sizeof(cpu_dump_area->aad));
|
||||||
|
tag = g_bytes_new(&cpu_dump_area->tag, sizeof(cpu_dump_area->tag));
|
||||||
|
iv = g_bytes_new(&cpu_dump_area->aad.iv, sizeof(cpu_dump_area->aad.iv));
|
||||||
|
params.cipher = PV_DUMP_V1_CIPHER;
|
||||||
|
params.key = dump_key;
|
||||||
|
params.iv = iv;
|
||||||
|
params.tag_size = g_bytes_get_size(tag);
|
||||||
|
if (pv_gcm_decrypt(encr, aad, tag, ¶ms, &out, error) < 0)
|
||||||
|
return NULL;
|
||||||
|
return g_steal_pointer(&out);
|
||||||
|
}
|
||||||
|
|
||||||
|
dfi_cpu_t *pv_dfi_cpu_from_pv_cpu(const pv_cpu_dump_confidential_area_v1_t *pv_cpu)
|
||||||
|
{
|
||||||
|
g_autoptr(dfi_cpu_t) ret = dfi_cpu_alloc();
|
||||||
|
|
||||||
|
STATIC_ASSERT(sizeof(ret->gprs) == sizeof(pv_cpu->gprs));
|
||||||
|
(void)memcpy(ret->gprs, pv_cpu->gprs, sizeof(ret->gprs));
|
||||||
|
STATIC_ASSERT(sizeof(ret->psw) == sizeof(pv_cpu->psw));
|
||||||
|
(void)memcpy(ret->psw, pv_cpu->psw, sizeof(ret->psw));
|
||||||
|
|
||||||
|
ret->prefix = pv_cpu->prefix;
|
||||||
|
ret->fpc = pv_cpu->fpc;
|
||||||
|
ret->todpreg = pv_cpu->todpreg;
|
||||||
|
ret->timer = pv_cpu->timer;
|
||||||
|
ret->todcmp = pv_cpu->todcmp;
|
||||||
|
|
||||||
|
STATIC_ASSERT(sizeof(ret->acrs) == sizeof(pv_cpu->acrs));
|
||||||
|
(void)memcpy(ret->acrs, pv_cpu->acrs, sizeof(ret->acrs));
|
||||||
|
|
||||||
|
STATIC_ASSERT(sizeof(ret->ctrs) == sizeof(pv_cpu->ctrs));
|
||||||
|
(void)memcpy(ret->ctrs, pv_cpu->ctrs, sizeof(ret->ctrs));
|
||||||
|
|
||||||
|
/* Copy floating point register and the high part of the first 16 vector
|
||||||
|
* register
|
||||||
|
*/
|
||||||
|
STATIC_ASSERT(ARRAY_SIZE(ret->fprs) == ARRAY_SIZE(pv_cpu->vector_register_low));
|
||||||
|
STATIC_ASSERT(ARRAY_SIZE(pv_cpu->vector_register_low) == ARRAY_SIZE(ret->vxrs_low));
|
||||||
|
dfi_cpu_content_fac_add(DFI_CPU_CONTENT_FAC_VX);
|
||||||
|
for (unsigned int i = 0; i < ARRAY_SIZE(ret->fprs); i++) {
|
||||||
|
ret->fprs[i] = pv_cpu->vector_register_low[i].low;
|
||||||
|
ret->vxrs_low[i] = pv_cpu->vector_register_low[i].high;
|
||||||
|
}
|
||||||
|
|
||||||
|
STATIC_ASSERT(sizeof(ret->vxrs_high) == sizeof(pv_cpu->vector_register_high));
|
||||||
|
(void)memcpy(ret->vxrs_high, pv_cpu->vector_register_high, sizeof(ret->vxrs_high));
|
||||||
|
|
||||||
|
/* Set guarded storage registers */
|
||||||
|
ret->reserved = 0;
|
||||||
|
ret->gsd = pv_cpu->gsd;
|
||||||
|
ret->gssm = pv_cpu->gssm;
|
||||||
|
ret->gs_epl_a = pv_cpu->gs_epl_a;
|
||||||
|
/* Add GS facility */
|
||||||
|
dfi_cpu_content_fac_add(DFI_CPU_CONTENT_FAC_GS);
|
||||||
|
|
||||||
|
/* NOTE: In the future it might be useful to store `@pv_cpu->dump_flags`
|
||||||
|
* in the `struct dfi_cpu`. Currently, we don't have any use case for
|
||||||
|
* it.
|
||||||
|
*/
|
||||||
|
|
||||||
|
return g_steal_pointer(&ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Utilities for decrypting the memory */
|
||||||
|
|
||||||
|
struct _pv_crypto_ctx {
|
||||||
|
EVP_CIPHER_CTX *cipher_ctx;
|
||||||
|
BIO *input;
|
||||||
|
BIO *filter;
|
||||||
|
/* To be allocated/deallocated using OpenSSL malloc and clear+free */
|
||||||
|
pv_tweak_nonce_t *nonce;
|
||||||
|
|
||||||
|
/* scratch area */
|
||||||
|
pv_tweak_t tweak_scratch;
|
||||||
|
};
|
||||||
|
|
||||||
|
static BIO *pv_BIO_cipher_new(const EVP_CIPHER *cipher, const unsigned char *key, size_t key_len,
|
||||||
|
enum PvCryptoMode mode, GError **error)
|
||||||
|
{
|
||||||
|
bool encrypt = mode == PV_ENCRYPT;
|
||||||
|
EVP_CIPHER_CTX *ctx = NULL;
|
||||||
|
g_autoptr(BIO) ret = NULL;
|
||||||
|
ENGINE *engine = NULL;
|
||||||
|
|
||||||
|
ret = BIO_new(BIO_f_cipher());
|
||||||
|
if (!ret) {
|
||||||
|
abort();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BIO_get_cipher_ctx(ret, &ctx) != 1) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_BIO_FAIL,
|
||||||
|
_("BIO_get_cipher_ctx failed"));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
g_assert(ctx);
|
||||||
|
|
||||||
|
if (EVP_CipherInit_ex(ctx, cipher, engine, NULL, NULL, encrypt) != 1) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_BIO_FAIL,
|
||||||
|
_("EVP_Cipher_init failed"));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check key length */
|
||||||
|
if (EVP_CIPHER_CTX_key_length(ctx) != (int)key_len) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_BIO_KEY,
|
||||||
|
_("Passed key has incorrect size: %ld != %d"), key_len,
|
||||||
|
EVP_CIPHER_key_length(cipher));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set key */
|
||||||
|
if (EVP_CipherInit_ex(ctx, NULL, NULL, key, NULL, -1) != 1) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_BIO_FAIL,
|
||||||
|
_("EVP_Cipher_init set_key failed"));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return g_steal_pointer(&ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
pv_crypto_ctx_t *pv_crypto_ctx_new(BIO *input, const unsigned char *key, size_t key_size,
|
||||||
|
const pv_tweak_nonce_t *nonce, enum PvCryptoMode mode,
|
||||||
|
GError **error)
|
||||||
|
{
|
||||||
|
g_autoptr(pv_crypto_ctx_t) ret = NULL;
|
||||||
|
g_autoptr(BIO) xts_filter = NULL;
|
||||||
|
|
||||||
|
g_assert(input);
|
||||||
|
STATIC_ASSERT(sizeof_field(pv_crypto_ctx_t, nonce) == sizeof(nonce));
|
||||||
|
|
||||||
|
ret = g_new0(pv_crypto_ctx_t, 1);
|
||||||
|
xts_filter = pv_BIO_cipher_new(EVP_aes_256_xts(), key, key_size, mode, error);
|
||||||
|
if (!xts_filter) {
|
||||||
|
g_prefix_error(error,
|
||||||
|
_("Initializing the zdump crypto context failed" ERR_NEWLINE));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BIO_get_cipher_ctx(xts_filter, &ret->cipher_ctx) != 1) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_CRYPTO_CTX,
|
||||||
|
_("Initializing the zdump crypto context failed"));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
g_assert(ret->cipher_ctx);
|
||||||
|
|
||||||
|
/* set-up BIO chain for the encryption/decryption */
|
||||||
|
ret->filter = BIO_push(g_steal_pointer(&xts_filter), input);
|
||||||
|
g_assert(ret->filter);
|
||||||
|
|
||||||
|
if (BIO_up_ref(input) != 1) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_CRYPTO_CTX,
|
||||||
|
_("Initializing the zdump crypto context failed"));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
ret->input = input;
|
||||||
|
|
||||||
|
ret->nonce = OPENSSL_malloc(sizeof(*ret->nonce));
|
||||||
|
if (!ret->nonce)
|
||||||
|
abort();
|
||||||
|
(void)memcpy(ret->nonce, nonce, sizeof(*ret->nonce));
|
||||||
|
return g_steal_pointer(&ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pv_crypto_ctx_free(pv_crypto_ctx_t *ctx)
|
||||||
|
{
|
||||||
|
if (!ctx)
|
||||||
|
return;
|
||||||
|
|
||||||
|
g_clear_pointer(&ctx->input, BIO_vfree);
|
||||||
|
g_clear_pointer(&ctx->filter, BIO_vfree);
|
||||||
|
/* It's intentional that we don't free @ctx->cipher_ctx since it's not
|
||||||
|
* owned by us, but the BIO chain */
|
||||||
|
ctx->cipher_ctx = NULL;
|
||||||
|
OPENSSL_clear_free(ctx->nonce, sizeof(*ctx->nonce));
|
||||||
|
g_free(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void calculate_tweak(const pv_tweak_component_t *tweak, const pv_tweak_nonce_t *nonce,
|
||||||
|
pv_tweak_t *out)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < ARRAY_SIZE(out->value); i++)
|
||||||
|
out->value[i] = tweak->value[i] | nonce->value[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
int pv_read_page(BIO *input, BIO *output, GError **error)
|
||||||
|
{
|
||||||
|
char data[PAGE_SIZE];
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
rc = BIO_read(input, data, ARRAY_SIZE(data));
|
||||||
|
if (rc != ARRAY_SIZE(data)) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_BIO, _("BIO_read failed"));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = BIO_write(output, data, ARRAY_SIZE(data));
|
||||||
|
if (rc != ARRAY_SIZE(data)) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_BIO, _("BIO_write failed"));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long pv_page_state(const pv_tweak_component_t *comp)
|
||||||
|
{
|
||||||
|
unsigned long ret = PV_INVAL_PAGE_STATE;
|
||||||
|
|
||||||
|
if (comp->special.indicator != PV_SPECIAL_INDICATOR)
|
||||||
|
return PV_ENCRYPTED_PAGE;
|
||||||
|
|
||||||
|
if (comp->special.flag_reserved1 || comp->special.flag_reserved2 ||
|
||||||
|
comp->special.is_zero_page + comp->special.is_shared_page +
|
||||||
|
comp->special.is_mapped_page <
|
||||||
|
1)
|
||||||
|
return PV_INVAL_PAGE_STATE;
|
||||||
|
|
||||||
|
if (comp->special.is_zero_page)
|
||||||
|
ret |= PV_ZERO_PAGE;
|
||||||
|
if (comp->special.is_shared_page)
|
||||||
|
ret |= PV_SHARED_PAGE;
|
||||||
|
if (comp->special.is_mapped_page)
|
||||||
|
ret |= PV_MAPPED_PAGE;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const unsigned char NULL_DATA[PV_CSS_PAGESIZE] = { 0x0 };
|
||||||
|
|
||||||
|
static bool pv_BIO_is_seekable(BIO *bio)
|
||||||
|
{
|
||||||
|
const int type = BIO_method_type(bio);
|
||||||
|
|
||||||
|
return type == BIO_TYPE_FD || type == BIO_TYPE_FILE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pv_BIO_seek(BIO *bio, long long offset)
|
||||||
|
{
|
||||||
|
/* The documentation of @BIO_seeks says @offset is a `int` but the
|
||||||
|
* source code actually shows it's a long. Therefore add these
|
||||||
|
* additional checks here to detect in case something changes in
|
||||||
|
* OpenSSL.
|
||||||
|
*/
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic error "-Wconversion"
|
||||||
|
#pragma GCC diagnostic error "-Wsign-conversion"
|
||||||
|
return BIO_seek(bio, offset);
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
}
|
||||||
|
|
||||||
|
static int update_tweak(pv_crypto_ctx_t *crypto_ctx, GError **error)
|
||||||
|
{
|
||||||
|
EVP_CIPHER_CTX *ctx = crypto_ctx->cipher_ctx;
|
||||||
|
u8 *tweak = crypto_ctx->tweak_scratch.value;
|
||||||
|
|
||||||
|
/* Check tweak length */
|
||||||
|
if (EVP_CIPHER_CTX_iv_length(ctx) != ARRAY_SIZE(crypto_ctx->tweak_scratch.value)) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_TWEAK,
|
||||||
|
_("Tweak has wrong size"));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* set the new tweak IV */
|
||||||
|
if (EVP_CipherInit_ex(ctx, NULL, NULL, NULL, tweak, -1) != 1) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_TWEAK,
|
||||||
|
_("Initializing tweaks failed"));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t pv_process_pglist(pv_crypto_ctx_t *ctx, BIO *output,
|
||||||
|
const pv_tweak_component_t *tweak_components, size_t tweak_components_len,
|
||||||
|
long input_off, GError **error)
|
||||||
|
{
|
||||||
|
bool is_output_seekable = pv_BIO_is_seekable(output);
|
||||||
|
long cur_in_off = input_off, cur_out_off = 0;
|
||||||
|
ssize_t page_idx;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
assert(ctx->input);
|
||||||
|
assert(ctx->filter);
|
||||||
|
assert(output);
|
||||||
|
assert(tweak_components_len <= SSIZE_MAX);
|
||||||
|
|
||||||
|
/* See https://www.openssl.org/docs/man1.1.0/man3/BIO_seek.html */
|
||||||
|
if (!pv_BIO_is_seekable(ctx->input)) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_PGLIST_BIO,
|
||||||
|
_("Input source is not seekable"));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pv_BIO_seek(ctx->input, cur_in_off) == -1) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_PGLIST_BIO,
|
||||||
|
_("BIO_seek failed"));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tweak_components_len > LONG_MAX / PV_CSS_PAGESIZE) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_PAGELIST_ELF_OFFSET_TOO_LARGE,
|
||||||
|
_("Possible long overflow detected: Try to read %li pages"),
|
||||||
|
tweak_components_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (page_idx = 0; page_idx < (ssize_t)tweak_components_len; page_idx++) {
|
||||||
|
const pv_tweak_component_t *tweak_comp = &tweak_components[page_idx];
|
||||||
|
unsigned long page_state;
|
||||||
|
BIO *input = NULL;
|
||||||
|
|
||||||
|
g_assert_nonnull(tweak_comp);
|
||||||
|
|
||||||
|
page_state = pv_page_state(tweak_comp);
|
||||||
|
if (page_state & PV_INVAL_PAGE_STATE) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_PGLIST_BIO,
|
||||||
|
_("Invalid page state"));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* g_assert(page_state & PV_MAPPED_PAGE); */
|
||||||
|
page_state &= ~PV_MAPPED_PAGE;
|
||||||
|
if (page_state & PV_ZERO_PAGE) {
|
||||||
|
/* Nothing to do here for BIO_FILE because a sparse file
|
||||||
|
* is filled with zeros by default. Therefore we can
|
||||||
|
* simply calculate the new output offset. For a
|
||||||
|
* BIO_s_mem BIO_seek doesn't work therefore we've to
|
||||||
|
* work around.
|
||||||
|
*/
|
||||||
|
|
||||||
|
cur_out_off += PV_CSS_PAGESIZE;
|
||||||
|
cur_in_off += PV_CSS_PAGESIZE;
|
||||||
|
if (G_UNLIKELY(pv_BIO_seek(ctx->input, cur_in_off) == -1)) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_PGLIST_BIO,
|
||||||
|
_("BIO_seek failed"));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (is_output_seekable) {
|
||||||
|
if (G_UNLIKELY(pv_BIO_seek(output, cur_out_off) == -1)) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR,
|
||||||
|
ZDUMP_ERR_PGLIST_BIO, _("BIO_seek failed"));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (G_UNLIKELY(
|
||||||
|
BIO_write(output, NULL_DATA, ARRAY_SIZE(NULL_DATA)) !=
|
||||||
|
ARRAY_SIZE(NULL_DATA))) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR,
|
||||||
|
ZDUMP_ERR_PGLIST_BIO, _("BIO_write failed"));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_assert(!(page_state & ~PV_ZERO_PAGE));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (page_state & PV_SHARED_PAGE) {
|
||||||
|
/* shared pages are not encrypted */
|
||||||
|
input = ctx->input;
|
||||||
|
g_assert(!(page_state & ~PV_SHARED_PAGE));
|
||||||
|
} else if (page_state & PV_ENCRYPTED_PAGE) {
|
||||||
|
input = ctx->filter;
|
||||||
|
calculate_tweak(tweak_comp, ctx->nonce, &ctx->tweak_scratch);
|
||||||
|
|
||||||
|
/* set new tweak */
|
||||||
|
if (update_tweak(ctx, error) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
g_assert(!(page_state & ~PV_ENCRYPTED_PAGE));
|
||||||
|
} else {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_PGLIST_INVAL_STATE,
|
||||||
|
_("Invalid page state. page-idx: %#lx, state: %#lx"), page_idx,
|
||||||
|
page_state);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = pv_read_page(input, output, error);
|
||||||
|
if (rc != PV_CSS_PAGESIZE)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
/* adapt the offsets */
|
||||||
|
cur_in_off += rc;
|
||||||
|
cur_out_off += rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
return page_idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pv_is_pv_elf(const Elf64_Shdr *shdrs, const unsigned int shnum, const char *shstrtab,
|
||||||
|
const size_t shstrtab_size)
|
||||||
|
{
|
||||||
|
return find_elf_shdr_by_name(shdrs, shnum, shstrtab, shstrtab_size,
|
||||||
|
PV_ELF_SECTION_NAME_COMPL) != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct _storage_state_mmap {
|
||||||
|
void *first_page_ptr;
|
||||||
|
size_t mapped_size;
|
||||||
|
pv_tweak_component_t *tweak_components;
|
||||||
|
size_t num_tweaks;
|
||||||
|
gatomicrefcount ref_count;
|
||||||
|
};
|
||||||
|
|
||||||
|
storage_state_mmap_t *storage_state_mmap_new(const int fd, const u64 offset, const u64 size,
|
||||||
|
GError **error)
|
||||||
|
{
|
||||||
|
size_t tweak_components_cnt, start_addr, in_page_offset, mmapped_size;
|
||||||
|
g_autoptr(storage_state_mmap_t) ret = NULL;
|
||||||
|
int saved_errno = 0;
|
||||||
|
u8 *ptr;
|
||||||
|
|
||||||
|
if (size == 0 || size % sizeof(pv_tweak_component_t) != 0) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_MMAP,
|
||||||
|
_("StorageState MMAP: size (%#llx) not a multiple of (%#lx)"), size,
|
||||||
|
sizeof(pv_tweak_component_t));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
start_addr = page_start_addr(page_index(offset), error);
|
||||||
|
if (*error)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
tweak_components_cnt = size / sizeof(pv_tweak_component_t);
|
||||||
|
in_page_offset = page_offset(offset);
|
||||||
|
mmapped_size = size + in_page_offset;
|
||||||
|
|
||||||
|
if (start_addr > SSIZE_MAX) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_MMAP,
|
||||||
|
_("StorageState MMAP: page start address is too large (%#lx)"),
|
||||||
|
start_addr);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
ptr = mmap(NULL, mmapped_size, PROT_READ, MAP_POPULATE | MAP_PRIVATE, fd,
|
||||||
|
(ssize_t)start_addr);
|
||||||
|
saved_errno = errno;
|
||||||
|
if (ptr == MAP_FAILED) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_MMAP, _("mmap failed: %s"),
|
||||||
|
g_strerror(saved_errno));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = g_new0(typeof(*ret), 1);
|
||||||
|
ret->first_page_ptr = ptr;
|
||||||
|
ret->tweak_components = (pv_tweak_component_t *)(ptr + in_page_offset);
|
||||||
|
ret->num_tweaks = tweak_components_cnt;
|
||||||
|
ret->mapped_size = mmapped_size;
|
||||||
|
g_atomic_ref_count_init(&ret->ref_count);
|
||||||
|
return g_steal_pointer(&ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
storage_state_mmap_t *storage_state_mmap_ref(storage_state_mmap_t *storage_state)
|
||||||
|
{
|
||||||
|
g_assert(storage_state);
|
||||||
|
g_atomic_ref_count_inc(&storage_state->ref_count);
|
||||||
|
return storage_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
void storage_state_mmap_unref(storage_state_mmap_t *storage_state)
|
||||||
|
{
|
||||||
|
if (!storage_state)
|
||||||
|
return;
|
||||||
|
if (storage_state->ref_count && !g_atomic_ref_count_dec(&storage_state->ref_count))
|
||||||
|
return;
|
||||||
|
if (storage_state->first_page_ptr) {
|
||||||
|
int rc = munmap(storage_state->first_page_ptr, storage_state->mapped_size);
|
||||||
|
if (rc != 0)
|
||||||
|
util_log_print(UTIL_LOG_WARN, _("munmap has failed"));
|
||||||
|
}
|
||||||
|
g_free(storage_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
pv_elf_ctx_t *pv_elf_ctx_new(const int fd,
|
||||||
|
const pv_dump_completion_confidential_area_v1_t *cpl_conf,
|
||||||
|
storage_state_mmap_t *storage_state_data, const u64 elf_load_offset,
|
||||||
|
GError **error)
|
||||||
|
{
|
||||||
|
g_autoptr(pv_elf_ctx_t) ret = NULL;
|
||||||
|
g_autoptr(BIO) input = NULL;
|
||||||
|
|
||||||
|
input = BIO_new_fd(fd, BIO_NOCLOSE);
|
||||||
|
if (!input) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_ELF_CTX_BIO,
|
||||||
|
_("cannot open file"));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = g_new0(typeof(*ret), 1);
|
||||||
|
ret->pv_ctx = pv_crypto_ctx_new(input, cpl_conf->key, sizeof(cpl_conf->key),
|
||||||
|
&cpl_conf->nonce, PV_DECRYPT, error);
|
||||||
|
if (!ret->pv_ctx)
|
||||||
|
return NULL;
|
||||||
|
ret->storage_state_data = storage_state_mmap_ref(storage_state_data);
|
||||||
|
ret->elf_load_off = elf_load_offset;
|
||||||
|
return g_steal_pointer(&ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pv_elf_ctx_free(pv_elf_ctx_t *p)
|
||||||
|
{
|
||||||
|
if (!p)
|
||||||
|
return;
|
||||||
|
|
||||||
|
g_clear_pointer(&p->pv_ctx, pv_crypto_ctx_free);
|
||||||
|
g_clear_pointer(&p->storage_state_data, storage_state_mmap_unref);
|
||||||
|
g_clear_pointer(&p->output, BIO_vfree);
|
||||||
|
g_free(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pv_tweak_component_t *pv_get_tweak_components(storage_state_mmap_t *storage_state_data,
|
||||||
|
u64 page_idx, u64 page_cnt)
|
||||||
|
{
|
||||||
|
u64 last_page_idx;
|
||||||
|
|
||||||
|
g_assert_cmpuint(page_cnt, >=, 1);
|
||||||
|
|
||||||
|
if (!u64_checked_add(&last_page_idx, page_idx, page_cnt - 1))
|
||||||
|
return NULL;
|
||||||
|
if (last_page_idx >= storage_state_data->num_tweaks)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return &storage_state_data->tweak_components[page_idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return version number if possible */
|
||||||
|
static long completion_data_get_version(GBytes *cpl_data, GError **error)
|
||||||
|
{
|
||||||
|
size_t size;
|
||||||
|
const uint32_t *version = g_bytes_get_data(cpl_data, &size);
|
||||||
|
STATIC_ASSERT(offsetof(pv_dump_completion_aad_v1_t, version) == 0);
|
||||||
|
|
||||||
|
/* check whether we can dereference @version */
|
||||||
|
if (sizeof(*version) > size) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_UNSUPP_COMPL_VER,
|
||||||
|
_("Unsupported " PV_ELF_SECTION_NAME_COMPL " section size (%#lx)"),
|
||||||
|
size);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return *version;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pv_process_section_data(const int fd, GBytes *completion_sec, const u64 storage_state_offset,
|
||||||
|
const size_t storage_state_size, GBytes *cck,
|
||||||
|
pv_dump_completion_t **completion_decr, GBytes **dump_key,
|
||||||
|
storage_state_mmap_t **storage_state, GError **error)
|
||||||
|
{
|
||||||
|
g_autoptr(pv_dump_completion_t) _completion_decr = NULL;
|
||||||
|
g_autoptr(storage_state_mmap_t) _storage_state_data = NULL;
|
||||||
|
g_autoptr(GBytes) _dump_key = NULL;
|
||||||
|
g_assert(completion_sec);
|
||||||
|
long version;
|
||||||
|
|
||||||
|
version = completion_data_get_version(completion_sec, error);
|
||||||
|
if (version < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
switch (version) {
|
||||||
|
case PV_COMPL_DATA_VERSION_1: {
|
||||||
|
const pv_dump_completion_data_v1_t *ccd;
|
||||||
|
size_t size;
|
||||||
|
|
||||||
|
ccd = g_bytes_get_data(completion_sec, &size);
|
||||||
|
if (sizeof(*ccd) > size) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_CORRUPTED_COMPL_DATA,
|
||||||
|
_("Corrupted completion configuration data"));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ccd->aad.len != sizeof(*ccd)) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_CORRUPTED_COMPL_DATA,
|
||||||
|
_("Incorrect completion configuration data length"));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
_dump_key = pv_derive_dump_key_v1(ccd, cck, error);
|
||||||
|
if (!_dump_key) {
|
||||||
|
g_prefix_error(error, _("Unable to derive dump key: "));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
_completion_decr = (pv_dump_completion_t *)pv_decrypt_dump_completion_v1(
|
||||||
|
ccd, _dump_key, error);
|
||||||
|
if (!_completion_decr) {
|
||||||
|
g_prefix_error(error,
|
||||||
|
_("Unable to decrypt completion configuration data: "));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
g_set_error(
|
||||||
|
error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_UNSUPP_COMPL_VER,
|
||||||
|
_("Unsupported dump completion version (%#lx) found in section " PV_ELF_SECTION_NAME_COMPL),
|
||||||
|
version);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
_storage_state_data =
|
||||||
|
storage_state_mmap_new(fd, storage_state_offset, storage_state_size, error);
|
||||||
|
if (!_storage_state_data)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
*dump_key = g_steal_pointer(&_dump_key);
|
||||||
|
*completion_decr = g_steal_pointer(&_completion_decr);
|
||||||
|
*storage_state = g_steal_pointer(&_storage_state_data);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pv_elf_read(const pv_elf_ctx_t *elf_ctx, const u64 start_addr, void *dst, const u64 size,
|
||||||
|
GError **error)
|
||||||
|
{
|
||||||
|
u64 pglist_size, pglist_start_idx, pglist_end_idx, pglist_num_pages;
|
||||||
|
const pv_tweak_component_t *pglist_tweak_components;
|
||||||
|
u64 pglist_start_addr, pglist_end_addr, pglist_elf_off, page_off;
|
||||||
|
const unsigned char *data = NULL;
|
||||||
|
g_autoptr(BIO) output = NULL;
|
||||||
|
gssize num_processed_pages;
|
||||||
|
long data_size;
|
||||||
|
u64 end_addr;
|
||||||
|
|
||||||
|
/* nothing to do then */
|
||||||
|
if (size == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
end_addr = start_addr + size - 1;
|
||||||
|
if (end_addr < start_addr) {
|
||||||
|
g_set_error(
|
||||||
|
error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_ELF_READ_END_ADDR_OVERFLOW,
|
||||||
|
_("UInt Overflow during reading memory detected. start_addr %p, size: %#llx"),
|
||||||
|
(void *)start_addr, size);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
page_off = page_offset(start_addr);
|
||||||
|
pglist_start_idx = page_index(start_addr);
|
||||||
|
pglist_start_addr = page_start_addr(pglist_start_idx, error);
|
||||||
|
/* Could never happen here */
|
||||||
|
if (*error)
|
||||||
|
return -1;
|
||||||
|
pglist_end_idx = page_index(end_addr);
|
||||||
|
pglist_end_addr = page_end_addr(pglist_end_idx, error);
|
||||||
|
if (*error)
|
||||||
|
return -1;
|
||||||
|
pglist_size = pglist_end_addr - pglist_start_addr + 1;
|
||||||
|
pglist_num_pages = pglist_end_idx - pglist_start_idx + 1;
|
||||||
|
|
||||||
|
g_assert(IS_ALIGNED(pglist_start_addr, PV_CSS_PAGESIZE));
|
||||||
|
g_assert(IS_ALIGNED(pglist_end_addr + 1, PV_CSS_PAGESIZE));
|
||||||
|
g_assert(IS_ALIGNED(pglist_size, PV_CSS_PAGESIZE));
|
||||||
|
/* must be true, is max UINT64_T/0x1000 + 1 < SSIZE_MAX */
|
||||||
|
g_assert(pglist_num_pages <= SSIZE_MAX);
|
||||||
|
g_assert(pglist_num_pages > 0);
|
||||||
|
|
||||||
|
pglist_tweak_components = pv_get_tweak_components(elf_ctx->storage_state_data,
|
||||||
|
pglist_start_idx, pglist_num_pages);
|
||||||
|
if (!pglist_tweak_components) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_ELF_READ,
|
||||||
|
_("Page tweaks were not found idx %llu num %llu"), pglist_start_idx,
|
||||||
|
pglist_num_pages);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
output = BIO_new(BIO_s_mem());
|
||||||
|
if (!output)
|
||||||
|
abort();
|
||||||
|
|
||||||
|
if (!u64_checked_add(&pglist_elf_off, elf_ctx->elf_load_off, pglist_start_addr)) {
|
||||||
|
g_set_error(
|
||||||
|
error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_PAGELIST_ELF_OFFSET_TOO_LARGE,
|
||||||
|
_("UInt overflow detected: ELF load offset %#llx page start address %#llx"),
|
||||||
|
elf_ctx->elf_load_off, pglist_start_addr);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pglist_elf_off > LONG_MAX) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_PAGELIST_ELF_OFFSET_TOO_LARGE,
|
||||||
|
_("ELF load offset is too large"));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Process the pages - in this case do the decryption */
|
||||||
|
num_processed_pages = pv_process_pglist(elf_ctx->pv_ctx, output, pglist_tweak_components,
|
||||||
|
pglist_num_pages, (long)pglist_elf_off, error);
|
||||||
|
if (num_processed_pages < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (num_processed_pages != (gssize)pglist_num_pages) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_ELF_READ,
|
||||||
|
_("Processed page count isn't correct"));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BIO_flush(output) != 1) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_ELF_READ, _("BIO_flush failed"));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
data_size = BIO_get_mem_data(output, &data);
|
||||||
|
if (data_size < 0 || !data) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_ELF_READ,
|
||||||
|
_("BIO_get_mem_data failed"));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((u64)data_size != pglist_size) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_ELF_READ,
|
||||||
|
_("Decrypting memory failed"));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NOTE previous assertions/overflow checks assure that this can never happen.
|
||||||
|
* We keep this to be extra sure and protect the following memcpy from
|
||||||
|
* malicious copying.
|
||||||
|
*/
|
||||||
|
if (page_off + size > (u64)data_size) {
|
||||||
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_ELF_READ, _("%s: Illegal state"),
|
||||||
|
__func__);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)memcpy(dst, data + page_off, size);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
347
zdump/pv_utils.h
Normal file
347
zdump/pv_utils.h
Normal file
@@ -0,0 +1,347 @@
|
|||||||
|
/*
|
||||||
|
* zgetdump - Tool for copying and converting System z dumps
|
||||||
|
*
|
||||||
|
* Utilities to decrypt secure execution guest dumps.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef PV_UTILS_H
|
||||||
|
#define PV_UTILS_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
|
||||||
|
#include "lib/util_log.h"
|
||||||
|
#include "libpv/crypto.h"
|
||||||
|
|
||||||
|
#include "df_elf.h"
|
||||||
|
#include "dfi.h"
|
||||||
|
#include "pv_defs.h"
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define PV_INVAL_PAGE_STATE 0UL
|
||||||
|
#define PV_ENCRYPTED_PAGE (1UL << 0)
|
||||||
|
#define PV_SHARED_PAGE (1UL << 1)
|
||||||
|
#define PV_ZERO_PAGE (1UL << 2)
|
||||||
|
#define PV_MAPPED_PAGE (1UL << 3)
|
||||||
|
|
||||||
|
#define FUNC_NAME_FLAT_STRUCT_CLEANSE_FREE(type) type##_flat_struct_cleanse_free
|
||||||
|
#define DEFINE_FLAT_STRUCT_CLEANSE_FREE(type, free_func) \
|
||||||
|
static void FUNC_NAME_FLAT_STRUCT_CLEANSE_FREE(type)(type * obj) \
|
||||||
|
{ \
|
||||||
|
if (!obj) \
|
||||||
|
return; \
|
||||||
|
OPENSSL_cleanse(obj, sizeof(type)); \
|
||||||
|
free_func(obj); \
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The typedef is required to be able to define a autoptr cleanup
|
||||||
|
* function using glib2.
|
||||||
|
*/
|
||||||
|
typedef struct dfi_cpu dfi_cpu_t;
|
||||||
|
|
||||||
|
DEFINE_FLAT_STRUCT_CLEANSE_FREE(dfi_cpu_t, dfi_cpu_free)
|
||||||
|
DEFINE_FLAT_STRUCT_CLEANSE_FREE(pv_dump_completion_data_v1_t, g_free)
|
||||||
|
DEFINE_FLAT_STRUCT_CLEANSE_FREE(pv_dump_completion_confidential_area_v1_t, g_free)
|
||||||
|
|
||||||
|
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(dfi_cpu_t, FUNC_NAME_FLAT_STRUCT_CLEANSE_FREE(dfi_cpu_t))
|
||||||
|
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(
|
||||||
|
pv_dump_completion_data_v1_t,
|
||||||
|
FUNC_NAME_FLAT_STRUCT_CLEANSE_FREE(pv_dump_completion_data_v1_t))
|
||||||
|
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(
|
||||||
|
pv_dump_completion_confidential_area_v1_t,
|
||||||
|
FUNC_NAME_FLAT_STRUCT_CLEANSE_FREE(pv_dump_completion_confidential_area_v1_t))
|
||||||
|
|
||||||
|
typedef struct _pv_crypto_ctx pv_crypto_ctx_t;
|
||||||
|
typedef struct _storage_state_mmap storage_state_mmap_t;
|
||||||
|
struct _pv_elf_ctx {
|
||||||
|
BIO *output;
|
||||||
|
u64 elf_load_off;
|
||||||
|
pv_crypto_ctx_t *pv_ctx;
|
||||||
|
storage_state_mmap_t *storage_state_data;
|
||||||
|
};
|
||||||
|
typedef struct _pv_elf_ctx pv_elf_ctx_t;
|
||||||
|
|
||||||
|
void pv_dump_completion_free(pv_dump_completion_t *cpl);
|
||||||
|
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(pv_dump_completion_t, pv_dump_completion_free)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pv_derive_dump_key_v1:
|
||||||
|
* @cpl_data: v1 dump completion data
|
||||||
|
* @cck: Customer communication key
|
||||||
|
* @error: return location for a #GError
|
||||||
|
*
|
||||||
|
* Derives an 32 byte AES-256-GCM key using the key derivation function HDKF
|
||||||
|
* together with the customer communication key (CCK) @cck as input keying
|
||||||
|
* material, and the seed from the completion configuration dump @cpl_data as salt.
|
||||||
|
*
|
||||||
|
* Returns: 32 byte AES-256-GCM key on success, %NULL if an error occurred
|
||||||
|
*/
|
||||||
|
GBytes *pv_derive_dump_key_v1(const pv_dump_completion_data_v1_t *cpl_data, GBytes *cck,
|
||||||
|
GError **error);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pv_decrypt_dump_completion_v1:
|
||||||
|
* @cpl_data: v1 dump completion data
|
||||||
|
* @dump_key: Dump key
|
||||||
|
* @error: return location for a #GError
|
||||||
|
*
|
||||||
|
* Decrypts and authenticates the completion configuration dump data @cpl_data
|
||||||
|
* using the AES-256-GCM @dump_key.
|
||||||
|
*
|
||||||
|
* Returns: Decrypted confidential area of the completion configuration dump on success,
|
||||||
|
* %NULL if an error occurred
|
||||||
|
*/
|
||||||
|
pv_dump_completion_v1_t *pv_decrypt_dump_completion_v1(const pv_dump_completion_data_v1_t *cpl_data,
|
||||||
|
GBytes *dump_key, GError **error);
|
||||||
|
|
||||||
|
dfi_cpu_t *pv_decrypt_cpu_note_data(const unsigned int expected_version, GBytes *cpu_note,
|
||||||
|
GBytes *dump_key, GError **error);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pv_decrypt_cpu_dump_area_v1:
|
||||||
|
* @cpu_dump_area: a v1 CPU dump area
|
||||||
|
* @dump_key: Dump key
|
||||||
|
* @error: return location for a #GError
|
||||||
|
*
|
||||||
|
* Decrypts and authenticates a secure CPU dump using AES-256-GCM with specified
|
||||||
|
* dump_key.
|
||||||
|
*
|
||||||
|
* Returns: Decrypted part of the CPU dump on success, %NULL if an error occurred
|
||||||
|
*/
|
||||||
|
GBytes *pv_decrypt_cpu_dump_area_v1(const pv_cpu_dump_v1_t *cpu_dump_area, GBytes *dump_key,
|
||||||
|
GError **error);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pv_dfi_cpu_from_pv_cpu:
|
||||||
|
* @pv_cpu: PV CPU data
|
||||||
|
*
|
||||||
|
* Convert PV CPU data to internal dfi_cpu struct.
|
||||||
|
*
|
||||||
|
* Returns: #dfi_cpu struct
|
||||||
|
*/
|
||||||
|
dfi_cpu_t *pv_dfi_cpu_from_pv_cpu(const pv_cpu_dump_confidential_area_v1_t *pv_cpu);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pv_crypto_ctx_new:
|
||||||
|
* @input: a #BIO input
|
||||||
|
* @key: (array length=key_size): byte data of the key
|
||||||
|
* @key_size: size of @key in bytes
|
||||||
|
* @nonce: The PV tweak nonce
|
||||||
|
* @mode: encrypt or decrypt mode
|
||||||
|
* @error: return location for a #GError
|
||||||
|
*
|
||||||
|
* Create a PV crypto context using @input as the input, @key as the cipher key
|
||||||
|
* and @nonce as the nonce for deriving the tweaks.
|
||||||
|
*
|
||||||
|
* Returns: a new #pv_crypto_ctx_t
|
||||||
|
*/
|
||||||
|
pv_crypto_ctx_t *pv_crypto_ctx_new(BIO *input, const unsigned char *key, size_t key_size,
|
||||||
|
const pv_tweak_nonce_t *nonce, enum PvCryptoMode mode,
|
||||||
|
GError **error);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pv_crypto_ctx_free:
|
||||||
|
* @ctx: a #pv_crypto_ctx_t
|
||||||
|
*
|
||||||
|
* Free all resources and the memory allocated for the #pv_crypto_ctx_t
|
||||||
|
*/
|
||||||
|
void pv_crypto_ctx_free(pv_crypto_ctx_t *ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pv_process_pglist:
|
||||||
|
* @ctx: a #pv_crypto_ctx_t context
|
||||||
|
* @output: Output
|
||||||
|
* @tweaks_components: (array length=tweaks_len) (element-type pv_tweak_component_t): tweaks
|
||||||
|
* components to be used for the decryption
|
||||||
|
* @tweaks_components_len: the length of @tweaks and the length of the page list to be processed
|
||||||
|
* @input_off: the file offset of the encrypted memory passed in @ctx to decrypt
|
||||||
|
* @error: return location for a #GError
|
||||||
|
*
|
||||||
|
* Decrypts a page block (e.g. 256 pages).
|
||||||
|
*
|
||||||
|
* Returns: number of processed pages on success, -1 if an error occurred
|
||||||
|
*/
|
||||||
|
ssize_t pv_process_pglist(pv_crypto_ctx_t *ctx, BIO *output,
|
||||||
|
const pv_tweak_component_t *tweak_components, size_t tweak_components_len,
|
||||||
|
long input_off, GError **error);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* calculate_tweak:
|
||||||
|
*
|
||||||
|
* calculates the bitwise or of tweak component and nonce.
|
||||||
|
*/
|
||||||
|
void calculate_tweak(const pv_tweak_component_t *tweak, const pv_tweak_nonce_t *nonce,
|
||||||
|
pv_tweak_t *out);
|
||||||
|
/**
|
||||||
|
* pv_read_page:
|
||||||
|
* @output: Input
|
||||||
|
* @output: Output
|
||||||
|
*
|
||||||
|
* Read one page data from @input and write it to @output.
|
||||||
|
*
|
||||||
|
* Returns: The number of bytes read for success, -1 if an error occurred
|
||||||
|
*/
|
||||||
|
int pv_read_page(BIO *input, BIO *output, GError **error);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pv_page_state:
|
||||||
|
*
|
||||||
|
* Get the page state of the tweak component.
|
||||||
|
*/
|
||||||
|
unsigned long pv_page_state(const pv_tweak_component_t *comp);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pv_get_tweak_components:
|
||||||
|
* @storage_state_data: input data
|
||||||
|
* @page_idx: Page index of the fist page
|
||||||
|
* @page_cnt: number of tweak components that can be at least accessed
|
||||||
|
*
|
||||||
|
* get a pointer to the page states of the given pages.
|
||||||
|
*
|
||||||
|
* Returns: pointer to page states or NULL in case of an error
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const pv_tweak_component_t *pv_get_tweak_components(storage_state_mmap_t *storage_state_data,
|
||||||
|
u64 page_idx, u64 page_cnt);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pv_elf_ctx_new:
|
||||||
|
* fd: file descriptor of elf file
|
||||||
|
* @completion_confidential_decr: content of ection pv_meta size must be
|
||||||
|
* sizeof(pv_dump_completion_confidential_area_t)
|
||||||
|
* @storage_state_data: mmap to pv_mem_meta
|
||||||
|
* @load_offset: offset of LOAD segment in fd
|
||||||
|
* @error: return value for GError
|
||||||
|
*
|
||||||
|
* creates a new elf context for processing the elf file
|
||||||
|
*
|
||||||
|
* Returns: new elf context or NULL in case of error
|
||||||
|
*/
|
||||||
|
pv_elf_ctx_t *
|
||||||
|
pv_elf_ctx_new(const int fd,
|
||||||
|
const pv_dump_completion_confidential_area_v1_t *completion_confidential_decr,
|
||||||
|
storage_state_mmap_t *storage_state_data, const u64 load_offset, GError **error);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pv_info_free:
|
||||||
|
* frees the elf context
|
||||||
|
*/
|
||||||
|
void pv_elf_ctx_free(pv_elf_ctx_t *p);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pv_is_pv_elf:
|
||||||
|
* @shdrs: list of section headers
|
||||||
|
* @shnum: size of shdrs list
|
||||||
|
* @shstrtab: content of shstrtab (section hdr string table)
|
||||||
|
* @shstrtab_size: size of shstrtab
|
||||||
|
*
|
||||||
|
* Returns: true in case it is a PV ELF vmcore dump, false otherwise.
|
||||||
|
*/
|
||||||
|
bool pv_is_pv_elf(const Elf64_Shdr *shdrs, const unsigned int shnum, const char *shstrtab,
|
||||||
|
const size_t shstrtab_size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pv_process_section_data:
|
||||||
|
*
|
||||||
|
* Uses the ELF sections data to derive dump key, using the provided
|
||||||
|
* customer-communication-key. It copies the decrypted completion
|
||||||
|
* data into @completion_decr and mmaps storage state tweaks to storage
|
||||||
|
* state data.
|
||||||
|
*
|
||||||
|
* The caller is responsible for cleanup and overwriting.
|
||||||
|
*
|
||||||
|
* Returns: 0 in case of success, -1 otherwise.
|
||||||
|
*/
|
||||||
|
int pv_process_section_data(const int fd, GBytes *completion_sec, const u64 storage_state_offset,
|
||||||
|
const size_t storage_state_size, GBytes *cck,
|
||||||
|
pv_dump_completion_t **completion_decr, GBytes **dump_key,
|
||||||
|
storage_state_mmap_t **storage_state_data, GError **error);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pv_elf_read:
|
||||||
|
*
|
||||||
|
* elf_ctx: elf context
|
||||||
|
* @start_addr: first address to read from
|
||||||
|
* @dst: target
|
||||||
|
* @size: num bytes to read
|
||||||
|
* @error: return value for GError
|
||||||
|
*
|
||||||
|
* Read size bytes from start addr in elf_ctx and save it into dest.
|
||||||
|
* Will decrypt pages if defined by page state.
|
||||||
|
*
|
||||||
|
* Returns: 0 in case of success, -1 otherwise.
|
||||||
|
*/
|
||||||
|
int pv_elf_read(const pv_elf_ctx_t *elf_ctx, const u64 start_addr, void *dst, const u64 size,
|
||||||
|
GError **error);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* storage_state_mmap_new:
|
||||||
|
* @fd: file descriptor for which to create the mapping
|
||||||
|
* @offset: offset in fd to pv_mem_meta section
|
||||||
|
* @size: size of mapping (and pv_mem_meta section)
|
||||||
|
* @error: return value for GError
|
||||||
|
*
|
||||||
|
* Returns: new storage_state_mmap context
|
||||||
|
*/
|
||||||
|
storage_state_mmap_t *storage_state_mmap_new(const int fd, const u64 offset, const u64 size,
|
||||||
|
GError **error);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* storage_state_ref:
|
||||||
|
*
|
||||||
|
* increase the reference counter by one.
|
||||||
|
*
|
||||||
|
* Returns: pointer to storage_state
|
||||||
|
*/
|
||||||
|
storage_state_mmap_t *storage_state_mmap_ref(storage_state_mmap_t *storage_state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* storage_state_unref:
|
||||||
|
*
|
||||||
|
* Decreases the reference counter by one.
|
||||||
|
* If the counter is now zero storage_state will be unmapped and freed.
|
||||||
|
* If the munmap fails, an error will be logged.
|
||||||
|
*/
|
||||||
|
void storage_state_mmap_unref(storage_state_mmap_t *storage_state);
|
||||||
|
|
||||||
|
#define ZDUMP_PV_UTILS_ERROR g_quark_from_static_string("zdump-pv-utils")
|
||||||
|
typedef enum {
|
||||||
|
ZDUMP_ERR_BIO,
|
||||||
|
ZDUMP_ERR_BIO_FAIL,
|
||||||
|
ZDUMP_ERR_BIO_KEY,
|
||||||
|
ZDUMP_ERR_CORRUPTED_COMPL_DATA,
|
||||||
|
ZDUMP_ERR_CORRUPTED_NOTE,
|
||||||
|
ZDUMP_ERR_CRYPTO_CTX,
|
||||||
|
ZDUMP_ERR_ELF_CTX,
|
||||||
|
ZDUMP_ERR_ELF_CTX_BIO,
|
||||||
|
ZDUMP_ERR_ELF_READ,
|
||||||
|
ZDUMP_ERR_ELF_READ_END_ADDR_OVERFLOW,
|
||||||
|
ZDUMP_ERR_MMAP,
|
||||||
|
ZDUMP_ERR_PAGELIST_ELF_OFFSET_TOO_LARGE,
|
||||||
|
ZDUMP_ERR_PAGE_END_ADDR_OVERFLOW,
|
||||||
|
ZDUMP_ERR_PAGE_START_ADDR_OVERFLOW,
|
||||||
|
ZDUMP_ERR_PGLIST_BIO,
|
||||||
|
ZDUMP_ERR_PGLIST_INVAL,
|
||||||
|
ZDUMP_ERR_PGLIST_INVAL_STATE,
|
||||||
|
ZDUMP_ERR_PROC_SECT,
|
||||||
|
ZDUMP_ERR_TWEAK,
|
||||||
|
ZDUMP_ERR_UNSUPP_COMPL_VER,
|
||||||
|
ZDUMP_ERR_UNSUPP_SEC_CPU_VER,
|
||||||
|
ZDUMP_ERR_WRONG_AAD_SIZE,
|
||||||
|
ZDUMP_ERR_WRONG_CCK_SIZE,
|
||||||
|
ZDUMP_ERR_WRONG_CONFIDENTIAL_SIZE,
|
||||||
|
ZDUMP_ERR_WRONG_NOTE_SIZE,
|
||||||
|
ZDUMP_ERR_WRONG_TAG_SIZE,
|
||||||
|
} zdump_pv_utils_error_e;
|
||||||
|
|
||||||
|
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(pv_crypto_ctx_t, pv_crypto_ctx_free)
|
||||||
|
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(pv_elf_ctx_t, pv_elf_ctx_free)
|
||||||
|
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(storage_state_mmap_t, storage_state_mmap_unref)
|
||||||
|
|
||||||
|
#endif /* PV_UTILS_H */
|
||||||
@@ -85,6 +85,7 @@ void zg_abort(const char *fmt, ...);
|
|||||||
#define ERR_EXIT(fmt, ...) zg_err_exit(fmt, ## __VA_ARGS__)
|
#define ERR_EXIT(fmt, ...) zg_err_exit(fmt, ## __VA_ARGS__)
|
||||||
#define ERR_EXIT_ERRNO(fmt, ...) zg_err_exit_errno(fmt, ## __VA_ARGS__)
|
#define ERR_EXIT_ERRNO(fmt, ...) zg_err_exit_errno(fmt, ## __VA_ARGS__)
|
||||||
#define ABORT(fmt, ...) zg_abort(fmt, ## __VA_ARGS__)
|
#define ABORT(fmt, ...) zg_abort(fmt, ## __VA_ARGS__)
|
||||||
|
#define ERR_NEWLINE "\n "
|
||||||
|
|
||||||
void zg_stderr(const char *fmt, ...);
|
void zg_stderr(const char *fmt, ...);
|
||||||
void zg_stderr_pr(const char *fmt, ...);
|
void zg_stderr_pr(const char *fmt, ...);
|
||||||
@@ -97,6 +98,8 @@ void zg_stdout(const char *fmt, ...);
|
|||||||
* Misc
|
* Misc
|
||||||
*/
|
*/
|
||||||
#define PAGE_ALIGN(addr) ALIGN(addr, PAGE_SIZE)
|
#define PAGE_ALIGN(addr) ALIGN(addr, PAGE_SIZE)
|
||||||
|
#define IS_ALIGNED(addr, size) (!((addr) & (size - 1)))
|
||||||
|
#define IS_PAGE_ALIGNED(addr) IS_ALIGNED((unsigned long)(addr), PAGE_SIZE)
|
||||||
|
|
||||||
static inline u32 zg_csum_partial(const void *buf, int len, u32 sum)
|
static inline u32 zg_csum_partial(const void *buf, int len, u32 sum)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
.\" Copyright 2018 IBM Corp.
|
.\" Copyright 2022 IBM Corp.
|
||||||
.\" s390-tools is free software; you can redistribute it and/or modify
|
.\" s390-tools is free software; you can redistribute it and/or modify
|
||||||
.\" it under the terms of the MIT license. See LICENSE for details.
|
.\" it under the terms of the MIT license. See LICENSE for details.
|
||||||
.\"
|
.\"
|
||||||
.TH ZGETDUMP 8 "April 2012" "s390-tools"
|
.TH ZGETDUMP 8 "October 2022" "s390-tools"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
zgetdump \- Tool for copying and converting System z dumps
|
zgetdump \- Tool for copying and converting System z dumps
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
|
|
||||||
\fBzgetdump\fR DUMP [-s SYS] [-f FMT] > DUMP_FILE
|
\fBzgetdump\fR DUMP [-s SYS] [-f FMT] [-k KEY] > DUMP_FILE
|
||||||
.br
|
.br
|
||||||
DUMP [-s SYS] [-f FMT] DUMP_FILE
|
DUMP [-s SYS] [-f FMT] [-k KEY] DUMP_FILE
|
||||||
.br
|
.br
|
||||||
-m DUMP [-s SYS] [-f FMT] DIR
|
-m DUMP [-s SYS] [-f FMT] [-k KEY] DIR
|
||||||
.br
|
.br
|
||||||
-i DUMP [-s SYS]
|
-i DUMP [-s SYS] [-k KEY]
|
||||||
.br
|
.br
|
||||||
-d DUMPDEV
|
-d DUMPDEV
|
||||||
.br
|
.br
|
||||||
@@ -37,6 +37,10 @@ Print usage information, then exit.
|
|||||||
.BR "\-v" " or " "\-\-version"
|
.BR "\-v" " or " "\-\-version"
|
||||||
Print version information, then exit.
|
Print version information, then exit.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BR "\-k <KEY>" " or " "\-\-key <KEY>"
|
||||||
|
Specify the file KEY to use as key for the decryption of a protected virtualization dump.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BR "\-m <DUMP> <DIR>" " or " "\-\-mount <DUMP> <DIR>"
|
.BR "\-m <DUMP> <DIR>" " or " "\-\-mount <DUMP> <DIR>"
|
||||||
Mount the source dump DUMP to mount point DIR and generate a virtual target
|
Mount the source dump DUMP to mount point DIR and generate a virtual target
|
||||||
@@ -115,6 +119,8 @@ NVMe partition device node (e.g. /dev/nvme0n1p1)
|
|||||||
Tape device node (e.g. /dev/ntibm0)
|
Tape device node (e.g. /dev/ntibm0)
|
||||||
.IP " -" 12
|
.IP " -" 12
|
||||||
Device node for live system (/dev/mem or /dev/crash)
|
Device node for live system (/dev/mem or /dev/crash)
|
||||||
|
.IP " -" 12
|
||||||
|
Protected virtualization dump file (e.g. /dumps/pv.elf)
|
||||||
.PP
|
.PP
|
||||||
.IP " " 8
|
.IP " " 8
|
||||||
Note: For DASD multi-volume dump it is sufficient to specify only one of the
|
Note: For DASD multi-volume dump it is sufficient to specify only one of the
|
||||||
@@ -176,6 +182,9 @@ target and source dump:
|
|||||||
Executable and Linkable Format core dump. This dump format is also used for
|
Executable and Linkable Format core dump. This dump format is also used for
|
||||||
Linux user space core dumps.
|
Linux user space core dumps.
|
||||||
.TP
|
.TP
|
||||||
|
.BR "ELF (protected virtualization dump):"
|
||||||
|
Protected virtualization vmcore dump in Executable and Linkable Format
|
||||||
|
.TP
|
||||||
.BR "s390"
|
.BR "s390"
|
||||||
This dump format is System z specific and is used for DASD and tape dumps.
|
This dump format is System z specific and is used for DASD and tape dumps.
|
||||||
.TP
|
.TP
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "lib/zt_common.h"
|
#include "lib/zt_common.h"
|
||||||
|
#include "opts.h"
|
||||||
#include "zgetdump.h"
|
#include "zgetdump.h"
|
||||||
#include "dt.h"
|
#include "dt.h"
|
||||||
#include "dfi.h"
|
#include "dfi.h"
|
||||||
@@ -106,9 +107,17 @@ static void kdump_select_check(void)
|
|||||||
ERR_EXIT("%s", msg);
|
ERR_EXIT("%s", msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int dfi_init_error(int UNUSED(rc))
|
static int dfi_init_error(int rc)
|
||||||
{
|
{
|
||||||
STDERR("Dump cannot be processed (is not complete)\n");
|
if (rc == -ENOKEY) {
|
||||||
|
if (g.opts.key_path)
|
||||||
|
ERR("Cannot read key: '%s'", g.opts.key_path);
|
||||||
|
else
|
||||||
|
ERR("No key for dump decryption provided.");
|
||||||
|
opts_print_usage(g.opts.prog_name);
|
||||||
|
} else {
|
||||||
|
STDERR("Dump cannot be processed (is not complete)\n");
|
||||||
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user