diff --git a/include/lib/zt_common.h b/include/lib/zt_common.h index 6dcfe18f..6a4014aa 100644 --- a/include/lib/zt_common.h +++ b/include/lib/zt_common.h @@ -70,6 +70,7 @@ #define __may_alias __attribute__((may_alias)) #define __section(x) __attribute__((__section__(#x))) #define __noinline __attribute__((__noinline__)) +#define __big_endian /* The Linux kernel (in stddef.h) and glibc (sys/cdefs.h) define * __always_inline. Therefore undefine it first to allow the headers * to be included first. diff --git a/zdump/Makefile b/zdump/Makefile index 0c605608..0cd61fb0 100644 --- a/zdump/Makefile +++ b/zdump/Makefile @@ -65,7 +65,7 @@ endif OBJECTS = zgetdump.o opts.o zg.o zg_error.o zg_print.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_s390mv.o dfi_s390mv_ext.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_s390mv.o dt_s390mv_ext.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'),) GLIB2_CFLAGS := $(shell pkg-config --silence-errors --cflags glib-2.0) diff --git a/zdump/df_elf.h b/zdump/df_elf.h index 2a81fdea..c3e1fc93 100644 --- a/zdump/df_elf.h +++ b/zdump/df_elf.h @@ -81,6 +81,13 @@ #define NT_S390_GS_CB 0x30b #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_LINUX "LINUX" #define NOTE_NAME_VMCOREINFO "VMCOREINFO" diff --git a/zdump/dfi.c b/zdump/dfi.c index 9f30772f..0d252a9c 100644 --- a/zdump/dfi.c +++ b/zdump/dfi.c @@ -26,6 +26,7 @@ * DFI vector - ensure that tape is the first in the list and devmem the second! */ static struct dfi *dfi_vec[] = { + /* clang-format off */ &dfi_s390tape, &dfi_devmem, &dfi_s390mv_ext, @@ -33,11 +34,13 @@ static struct dfi *dfi_vec[] = { &dfi_s390_ext, &dfi_s390, &dfi_lkcd, + &dfi_pv_elf, &dfi_elf, &dfi_kdump, &dfi_kdump_flat, &dfi_ngdump, NULL, + /* clang-format on */ }; /* @@ -168,6 +171,11 @@ struct dfi_cpu *dfi_cpu_alloc(void) return zg_alloc(sizeof(struct dfi_cpu)); } +void dfi_cpu_free(struct dfi_cpu *cpu) +{ + zg_free(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", dfi->name, rc); - if (rc == 0 || rc == -EINVAL) + if (rc == 0 || rc == -EINVAL || rc == -ENOKEY) return rc; zg_close(g.fh); i++; diff --git a/zdump/dfi.h b/zdump/dfi.h index aa8f6dd9..79fe2b49 100644 --- a/zdump/dfi.h +++ b/zdump/dfi.h @@ -147,6 +147,7 @@ void dfi_cpu_content_fac_add(int flags); struct util_list *dfi_cpu_list(void); void dfi_cpu_info_init(enum dfi_cpu_content content); struct dfi_cpu *dfi_cpu_alloc(void); +void dfi_cpu_free(struct dfi_cpu *cpu); struct dfi_cpu *dfi_cpu(unsigned int cpu_nr); void dfi_cpu_add(struct dfi_cpu *cpu); 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_ext; extern struct dfi dfi_lkcd; +extern struct dfi dfi_pv_elf; extern struct dfi dfi_elf; extern struct dfi dfi_kdump; extern struct dfi dfi_kdump_flat; diff --git a/zdump/dfi_pv_elf.c b/zdump/dfi_pv_elf.c new file mode 100644 index 00000000..e9c899d5 --- /dev/null +++ b/zdump/dfi_pv_elf.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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)", +}; diff --git a/zdump/opts.c b/zdump/opts.c index 56006dbb..f924b199 100644 --- a/zdump/opts.c +++ b/zdump/opts.c @@ -20,9 +20,11 @@ #include "opts.h" static struct option long_opts[] = { + /* clang-format off */ {"help", no_argument, NULL, 'h'}, {"version", no_argument, NULL, 'v'}, {"info", no_argument, NULL, 'i'}, + {"key", required_argument, NULL, 'k'}, {"device", no_argument, NULL, 'd'}, {"mount", no_argument, NULL, 'm'}, {"umount", no_argument, NULL, 'u'}, @@ -30,19 +32,20 @@ static struct option long_opts[] = { {"select", required_argument, NULL, 's'}, {"debug", no_argument, NULL, 'X'}, {"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 */ static const char help_text[] = - "Usage: zgetdump DUMP [-s SYS] [-f FMT] > DUMP_FILE\n" - " DUMP [-s SYS] [-f FMT] DUMP_FILE\n" - " -m DUMP [-s SYS] [-f FMT] DIR\n" - " -i DUMP [-s SYS]\n" + "Usage: zgetdump DUMP [-s SYS] [-f FMT] [-k KEY] > DUMP_FILE\n" + " DUMP [-s SYS] [-f FMT] [-k KEY] DUMP_FILE\n" + " -m DUMP [-s SYS] [-f FMT] [-k KEY] DIR\n" + " -i DUMP [-s SYS] [-k KEY]\n" " -d DUMPDEV\n" " -u DIR\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" "read. The following options are available:\n" "\n" + "-k, --key Specify the key KEY to decrypt the protected virtualization dump\n" "-m, --mount Mount DUMP to mount point DIR\n" "-u, --umount Unmount dump from mount point DIR\n" "-i, --info Print DUMP information\n" @@ -71,7 +75,7 @@ static const char help_text[] = " messages. This option is intended for debugging\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 @@ -88,6 +92,7 @@ static void init_defaults(struct options *opts) opts->prog_name = "zgetdump"; opts->action = ZG_ACTION_COPY; opts->output_path = NULL; + opts->key_path = NULL; #ifdef __s390x__ opts->fmt = "elf"; #else @@ -181,6 +186,16 @@ static void output_set(struct options *opts, const char *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 */ @@ -318,6 +333,9 @@ void opts_parse(int argc, char *argv[], struct options *opts) case 's': select_set(opts, optarg); break; + case 'k': + key_set(opts, optarg); + break; case 'X': opts->debug_specified = 1; break; diff --git a/zdump/opts.h b/zdump/opts.h index 35269cde..1b176af6 100644 --- a/zdump/opts.h +++ b/zdump/opts.h @@ -20,6 +20,7 @@ struct options { char *device; /* If `output_path == NULL` the output is written to `stdout` */ const char *output_path; + const char *key_path; char *mount_point; int fmt_specified; const char *fmt; diff --git a/zdump/pv_defs.h b/zdump/pv_defs.h new file mode 100644 index 00000000..55c5f065 --- /dev/null +++ b/zdump/pv_defs.h @@ -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 + +#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 */ diff --git a/zdump/pv_utils.c b/zdump/pv_utils.c new file mode 100644 index 00000000..3c287689 --- /dev/null +++ b/zdump/pv_utils.c @@ -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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#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; +} diff --git a/zdump/pv_utils.h b/zdump/pv_utils.h new file mode 100644 index 00000000..db7a9574 --- /dev/null +++ b/zdump/pv_utils.h @@ -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 + +#include + +#include "lib/util_log.h" +#include "libpv/crypto.h" + +#include "df_elf.h" +#include "dfi.h" +#include "pv_defs.h" +#include + +#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 */ diff --git a/zdump/zg.h b/zdump/zg.h index 8b25dcbe..647716b3 100644 --- a/zdump/zg.h +++ b/zdump/zg.h @@ -85,6 +85,7 @@ void zg_abort(const char *fmt, ...); #define ERR_EXIT(fmt, ...) zg_err_exit(fmt, ## __VA_ARGS__) #define ERR_EXIT_ERRNO(fmt, ...) zg_err_exit_errno(fmt, ## __VA_ARGS__) #define ABORT(fmt, ...) zg_abort(fmt, ## __VA_ARGS__) +#define ERR_NEWLINE "\n " void zg_stderr(const char *fmt, ...); void zg_stderr_pr(const char *fmt, ...); @@ -97,6 +98,8 @@ void zg_stdout(const char *fmt, ...); * Misc */ #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) { diff --git a/zdump/zgetdump.8 b/zdump/zgetdump.8 index 7729e8e1..6f0da889 100644 --- a/zdump/zgetdump.8 +++ b/zdump/zgetdump.8 @@ -1,19 +1,19 @@ -.\" Copyright 2018 IBM Corp. +.\" Copyright 2022 IBM Corp. .\" s390-tools is free software; you can redistribute it and/or modify .\" 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 zgetdump \- Tool for copying and converting System z dumps .SH SYNOPSIS -\fBzgetdump\fR DUMP [-s SYS] [-f FMT] > DUMP_FILE +\fBzgetdump\fR DUMP [-s SYS] [-f FMT] [-k KEY] > DUMP_FILE .br - DUMP [-s SYS] [-f FMT] DUMP_FILE + DUMP [-s SYS] [-f FMT] [-k KEY] DUMP_FILE .br - -m DUMP [-s SYS] [-f FMT] DIR + -m DUMP [-s SYS] [-f FMT] [-k KEY] DIR .br - -i DUMP [-s SYS] + -i DUMP [-s SYS] [-k KEY] .br -d DUMPDEV .br @@ -37,6 +37,10 @@ Print usage information, then exit. .BR "\-v" " or " "\-\-version" Print version information, then exit. +.TP +.BR "\-k " " or " "\-\-key " +Specify the file KEY to use as key for the decryption of a protected virtualization dump. + .TP .BR "\-m " " or " "\-\-mount " 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) .IP " -" 12 Device node for live system (/dev/mem or /dev/crash) +.IP " -" 12 +Protected virtualization dump file (e.g. /dumps/pv.elf) .PP .IP " " 8 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 Linux user space core dumps. .TP +.BR "ELF (protected virtualization dump):" +Protected virtualization vmcore dump in Executable and Linkable Format +.TP .BR "s390" This dump format is System z specific and is used for DASD and tape dumps. .TP diff --git a/zdump/zgetdump.c b/zdump/zgetdump.c index e880c058..6b213bb6 100644 --- a/zdump/zgetdump.c +++ b/zdump/zgetdump.c @@ -27,6 +27,7 @@ #include #include "lib/zt_common.h" +#include "opts.h" #include "zgetdump.h" #include "dt.h" #include "dfi.h" @@ -106,9 +107,17 @@ static void kdump_select_check(void) 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; }