mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Verify that the size is large enough before doing the mmap. Otherwise
this can result in a SIGBUS signal if there is an attempt to access a
page that lies beyond the end of the mapped file (see `man 2 mmap`).
Fixes: 8fa1b5a00b ("zdump: dfi: add support to read Protected Virtualization dumps")
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
1012 lines
30 KiB
C
1012 lines
30 KiB
C
/*
|
|
* 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;
|
|
}
|
|
}
|
|
if (page_state & ~PV_ZERO_PAGE) {
|
|
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;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (page_state & PV_SHARED_PAGE) {
|
|
/* shared pages are not encrypted */
|
|
input = ctx->input;
|
|
if (page_state & ~PV_SHARED_PAGE) {
|
|
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;
|
|
}
|
|
} 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;
|
|
|
|
if (page_state & ~PV_ENCRYPTED_PAGE) {
|
|
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;
|
|
}
|
|
} 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 size_t file_size, const u64 offset,
|
|
const u64 size, GError **error)
|
|
{
|
|
size_t tweak_components_cnt, start_addr, min_size, 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;
|
|
}
|
|
|
|
if (G_UNLIKELY(!g_uint64_checked_add(&min_size, start_addr, mmapped_size))) {
|
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_PAGE_END_ADDR_OVERFLOW,
|
|
_("UInt overflow detected: %s: start_addr %#lx mmap_size %#lx"),
|
|
__func__, start_addr, mmapped_size);
|
|
return NULL;
|
|
}
|
|
|
|
if (file_size < min_size) {
|
|
g_set_error(error, ZDUMP_PV_UTILS_ERROR, ZDUMP_ERR_MMAP,
|
|
_("mmap failed: file too small"));
|
|
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, const size_t file_size, 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, file_size, 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;
|
|
}
|