mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Protected VMs (PVM) are KVM VMs, where KVM can't access the VM's state like guest memory and guest registers anymore. Instead the PVMs are mostly managed by a new entity called Ultravisor (UV), which provides an API, so KVM and the PV can request management actions. PVMs are encrypted at rest and protected from hypervisor access while running. They switch from a normal operation into protected mode, so we can still use the standard boot process to load an encrypted image and then move it into protected mode. This commit adds the tool 'genprotimg'. It takes a kernel, key files, optionally an initrd, optionally a file with the kernel command line, and it generates a single, loadable image file. The image consists of a concatenation of a plain text boot loader, the encrypted components for kernel, initrd, and cmdline, and the integrity-protected PV header, containing metadata necessary for running the guest in PV mode. It's possible to use this image file as a kernel for zipl or for a direct kernel boot using QEMU. Reviewed-by: Bjoern Walk <bwalk@linux.ibm.com> Acked-by: Patrick Steuer <patrick.steuer@de.ibm.com> Reviewed-by: Claudio Imbrenda <imbrenda@linux.ibm.com> Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com> Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
821 lines
20 KiB
C
821 lines
20 KiB
C
/*
|
|
* PV image related definitions and functions
|
|
*
|
|
* Copyright IBM Corp. 2020
|
|
*
|
|
* 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 <errno.h>
|
|
#include <glib.h>
|
|
#include <openssl/evp.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "boot/stage3a.h"
|
|
#include "common.h"
|
|
#include "include/pv_crypto_def.h"
|
|
#include "include/pv_hdr_def.h"
|
|
#include "utils/align.h"
|
|
#include "utils/crypto.h"
|
|
#include "utils/file_utils.h"
|
|
|
|
#include "pv_args.h"
|
|
#include "pv_comps.h"
|
|
#include "pv_error.h"
|
|
#include "pv_hdr.h"
|
|
#include "pv_image.h"
|
|
#include "pv_ipib.h"
|
|
#include "pv_opt_item.h"
|
|
#include "pv_stage3.h"
|
|
|
|
const PvComponent *pv_img_get_stage3b_comp(const PvImage *img, GError **err)
|
|
{
|
|
const PvComponent *comp;
|
|
|
|
g_return_val_if_fail(pv_img_comps_length(img->comps) >= 1, NULL);
|
|
|
|
comp = pv_img_comps_get_nth_comp(img->comps,
|
|
pv_img_comps_length(img->comps) - 1);
|
|
if (!pv_component_is_stage3b(comp)) {
|
|
g_set_error(err, PV_ERROR, PV_ERROR_INTERNAL,
|
|
_("Failed to get 'stage3b' component"));
|
|
return NULL;
|
|
}
|
|
return comp;
|
|
}
|
|
|
|
typedef gint (*prepare_func)(PvComponent *obj, const gchar *tmp_path,
|
|
void *opaque, GError **err);
|
|
|
|
static gint pv_img_prepare_component(const PvImage *img, PvComponent *comp,
|
|
GError **err)
|
|
{
|
|
struct cipher_parms parms = { 0 };
|
|
g_autoptr(Buffer) tweak = NULL;
|
|
prepare_func func = NULL;
|
|
void *opaque = NULL;
|
|
gint rc;
|
|
|
|
if (img->pcf & PV_CFLAG_NO_DECRYPTION) {
|
|
/* we only need to align the components */
|
|
func = pv_component_align;
|
|
opaque = NULL;
|
|
} else {
|
|
const EVP_CIPHER *cipher = img->xts_cipher;
|
|
|
|
g_assert_cmpint((int)img->xts_key->size, ==,
|
|
EVP_CIPHER_key_length(cipher));
|
|
g_assert_cmpint((int)PAGE_SIZE % EVP_CIPHER_block_size(cipher),
|
|
==, 0);
|
|
g_assert_cmpint(sizeof(comp->tweak), ==,
|
|
EVP_CIPHER_iv_length(cipher));
|
|
g_assert(img->xts_key->size <= UINT_MAX);
|
|
|
|
tweak = buffer_alloc(sizeof(comp->tweak.data));
|
|
memcpy(tweak->data, comp->tweak.data, tweak->size);
|
|
func = pv_component_align_and_encrypt;
|
|
parms.cipher = cipher;
|
|
parms.key = img->xts_key;
|
|
parms.iv_or_tweak = tweak;
|
|
|
|
opaque = &parms;
|
|
}
|
|
|
|
rc = (*func)(comp, img->tmp_dir, opaque, err);
|
|
if (rc < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static Buffer *pv_img_read_key(const gchar *path, guint key_size,
|
|
GError **err)
|
|
{
|
|
g_autoptr(Buffer) tmp_ret = NULL;
|
|
Buffer *ret = NULL;
|
|
gsize bytes_read;
|
|
FILE *f = NULL;
|
|
gsize size;
|
|
|
|
if (file_size(path, &size, err) != 0)
|
|
return NULL;
|
|
|
|
if (size - key_size != 0) {
|
|
g_set_error(err, PV_ERROR, PV_CRYPTO_ERROR_INVALID_KEY_SIZE,
|
|
_("Wrong file size '%s': read %zd, expected %u"), path, size,
|
|
key_size);
|
|
return NULL;
|
|
}
|
|
|
|
f = file_open(path, "rb", err);
|
|
if (!f)
|
|
return NULL;
|
|
|
|
tmp_ret = buffer_alloc(size);
|
|
if (file_read(f, tmp_ret->data, 1, tmp_ret->size, &bytes_read, err) < 0)
|
|
goto err;
|
|
|
|
if (bytes_read - key_size != 0) {
|
|
g_set_error(err, PV_ERROR, PV_CRYPTO_ERROR_INVALID_KEY_SIZE,
|
|
_("Wrong file size '%s': read %zd, expected %u"),
|
|
path, bytes_read, key_size);
|
|
goto err;
|
|
}
|
|
|
|
ret = g_steal_pointer(&tmp_ret);
|
|
err:
|
|
if (f)
|
|
fclose(f);
|
|
return ret;
|
|
}
|
|
|
|
static EVP_PKEY *pv_img_get_cust_pub_priv_key(gint nid, GError **err)
|
|
{
|
|
return generate_ec_key(nid, err);
|
|
}
|
|
|
|
static HostKeyList *pv_img_get_host_keys(gchar **host_cert_paths,
|
|
X509_STORE *store, gint nid,
|
|
GError **err)
|
|
{
|
|
g_autoslist(EVP_PKEY) ret = NULL;
|
|
|
|
g_assert(host_cert_paths);
|
|
|
|
for (gchar **iterator = host_cert_paths; iterator != NULL && *iterator != NULL;
|
|
iterator++) {
|
|
g_autoptr(EVP_PKEY) host_key = NULL;
|
|
const gchar *path = *iterator;
|
|
|
|
g_assert(path);
|
|
|
|
host_key = read_ec_pubkey_cert(store, nid, path, err);
|
|
if (!host_key)
|
|
return NULL;
|
|
|
|
ret = g_slist_append(ret, g_steal_pointer(&host_key));
|
|
}
|
|
|
|
return g_steal_pointer(&ret);
|
|
}
|
|
|
|
static Buffer *pv_img_get_key(const EVP_CIPHER *cipher, const gchar *path,
|
|
GError **err)
|
|
{
|
|
gint key_len = EVP_CIPHER_key_length(cipher);
|
|
|
|
g_assert(key_len > 0);
|
|
|
|
if (path)
|
|
return pv_img_read_key(path, (guint)key_len, err);
|
|
|
|
return generate_aes_key((guint)key_len, err);
|
|
}
|
|
|
|
static Buffer *pv_img_get_iv(const EVP_CIPHER *cipher, const gchar *path,
|
|
GError **err)
|
|
{
|
|
gint iv_len = EVP_CIPHER_iv_length(cipher);
|
|
|
|
g_assert(iv_len > 0);
|
|
|
|
if (path)
|
|
return pv_img_read_key(path, (guint)iv_len, err);
|
|
|
|
return generate_aes_iv((guint)iv_len, err);
|
|
}
|
|
|
|
static int hex_str_toull(const gchar *nptr, uint64_t *dst,
|
|
GError **err)
|
|
{
|
|
uint64_t value;
|
|
gchar *end;
|
|
|
|
g_assert(dst);
|
|
|
|
if (!g_str_is_ascii(nptr)) {
|
|
g_set_error(err, PV_ERROR, EINVAL,
|
|
_("Invalid value: '%s'. A hexadecimal value is required, for example '0xcfe'"),
|
|
nptr);
|
|
return -1;
|
|
}
|
|
|
|
value = g_ascii_strtoull(nptr, &end, 16);
|
|
if ((value == G_MAXUINT64 && errno == ERANGE) ||
|
|
(end && *end != '\0')) {
|
|
g_set_error(err, PV_ERROR, EINVAL,
|
|
_("Invalid value: '%s'. A hexadecimal value is required, for example '0xcfe'"),
|
|
nptr);
|
|
return -1;
|
|
}
|
|
*dst = value;
|
|
return 0;
|
|
}
|
|
|
|
static gint pv_img_set_psw_addr(PvImage *img, const gchar *psw_addr_s,
|
|
GError **err)
|
|
{
|
|
if (psw_addr_s) {
|
|
uint64_t psw_addr;
|
|
|
|
if (hex_str_toull(psw_addr_s, &psw_addr, err) < 0)
|
|
return -1;
|
|
|
|
img->initial_psw.addr = psw_addr;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static gint pv_img_set_control_flags(PvImage *img, const gchar *pcf_s,
|
|
const gchar *scf_s, GError **err)
|
|
{
|
|
uint64_t flags;
|
|
|
|
if (pcf_s) {
|
|
if (hex_str_toull(pcf_s, &flags, err) < 0)
|
|
return -1;
|
|
|
|
img->pcf = flags;
|
|
}
|
|
|
|
if (scf_s) {
|
|
if (hex_str_toull(scf_s, &flags, err) < 0)
|
|
return -1;
|
|
|
|
img->scf = flags;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* read in the keys or auto-generate them */
|
|
static gint pv_img_set_keys(PvImage *img, const PvArgs *args, GError **err)
|
|
{
|
|
g_autoptr(X509_STORE) store = NULL;
|
|
|
|
g_assert(img->xts_cipher);
|
|
g_assert(img->cust_comm_cipher);
|
|
g_assert(img->gcm_cipher);
|
|
g_assert(img->nid);
|
|
|
|
img->xts_key = pv_img_get_key(img->xts_cipher, args->xts_key_path, err);
|
|
if (!img->xts_key)
|
|
return -1;
|
|
|
|
img->cust_comm_key = pv_img_get_key(img->cust_comm_cipher,
|
|
args->cust_comm_key_path, err);
|
|
if (!img->cust_comm_key)
|
|
return -1;
|
|
|
|
img->cust_root_key =
|
|
pv_img_get_key(img->gcm_cipher, args->cust_root_key_path, err);
|
|
if (!img->cust_root_key)
|
|
return -1;
|
|
|
|
img->gcm_iv = pv_img_get_iv(img->gcm_cipher, args->gcm_iv_path, err);
|
|
if (!img->gcm_iv)
|
|
return -1;
|
|
|
|
img->cust_pub_priv_key = pv_img_get_cust_pub_priv_key(img->nid, err);
|
|
if (!img->cust_pub_priv_key)
|
|
return -1;
|
|
|
|
img->host_pub_keys =
|
|
pv_img_get_host_keys(args->host_keys, store, img->nid, err);
|
|
if (!img->host_pub_keys)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void pv_img_add_host_slot(PvImage *img, PvHdrKeySlot *slot)
|
|
{
|
|
img->key_slots = g_slist_append(img->key_slots, slot);
|
|
}
|
|
|
|
static void pv_hdr_key_slot_free(PvHdrKeySlot *slot)
|
|
{
|
|
if (!slot)
|
|
return;
|
|
|
|
g_free(slot);
|
|
}
|
|
|
|
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(PvHdrKeySlot, pv_hdr_key_slot_free)
|
|
|
|
static PvHdrKeySlot *pv_hdr_key_slot_new(const EVP_CIPHER *gcm_cipher,
|
|
const Buffer *cust_root_key,
|
|
EVP_PKEY *cust_key, EVP_PKEY *host_key,
|
|
GError **err)
|
|
{
|
|
g_autoptr(PvHdrKeySlot) ret = g_new0(PvHdrKeySlot, 1);
|
|
g_autofree union ecdh_pub_key *pub = NULL;
|
|
g_autoptr(Buffer) exchange_key = NULL;
|
|
g_autoptr(Buffer) digest_key = NULL;
|
|
g_autoptr(Buffer) iv = NULL;
|
|
Buffer pub_buf;
|
|
/* No AAD data is used */
|
|
Buffer aad = { .data = NULL, .size = 0 };
|
|
/* Set the output buffers for the encrypted data and the
|
|
* generated GCM tag
|
|
*/
|
|
Buffer enc = { .data = ret->wrapped_key, .size = sizeof(ret->wrapped_key) };
|
|
Buffer tag = { .data = ret->tag, .size = sizeof(ret->tag) };
|
|
struct cipher_parms parms;
|
|
int64_t c_len = 0;
|
|
|
|
g_assert(EVP_CIPHER_iv_length(gcm_cipher) >= 0);
|
|
|
|
pub = evp_pkey_to_ecdh_pub_key(host_key, err);
|
|
if (!pub)
|
|
return NULL;
|
|
|
|
pub_buf.data = pub->data;
|
|
pub_buf.size = sizeof(*pub);
|
|
digest_key = sha256_buffer(&pub_buf, err);
|
|
if (!digest_key)
|
|
return NULL;
|
|
|
|
g_assert(digest_key->size == sizeof(ret->digest_key));
|
|
/* set `digest_key` field */
|
|
memcpy(ret->digest_key, digest_key->data, sizeof(ret->digest_key));
|
|
|
|
exchange_key = compute_exchange_key(cust_key, host_key, err);
|
|
if (!exchange_key)
|
|
return NULL;
|
|
|
|
/* initialize cipher parameters */
|
|
g_assert(exchange_key->size <= INT_MAX);
|
|
g_assert(exchange_key->size == (guint)EVP_CIPHER_key_length(gcm_cipher));
|
|
|
|
/* create zero IV */
|
|
iv = buffer_alloc((guint)EVP_CIPHER_iv_length(gcm_cipher));
|
|
parms.iv_or_tweak = iv;
|
|
parms.key = exchange_key;
|
|
parms.cipher = gcm_cipher;
|
|
|
|
/* Encrypt the customer root key that is used for the encryption
|
|
* of the PV header
|
|
*/
|
|
c_len = gcm_encrypt(cust_root_key, &aad, &parms, &enc, &tag, err);
|
|
if (c_len < 0)
|
|
return NULL;
|
|
|
|
g_assert(c_len == (int64_t)cust_root_key->size);
|
|
return g_steal_pointer(&ret);
|
|
}
|
|
|
|
static gint pv_img_set_host_slots(PvImage *img, GError **err)
|
|
{
|
|
for (GSList *iterator = img->host_pub_keys; iterator; iterator = iterator->next) {
|
|
EVP_PKEY *host_key = iterator->data;
|
|
|
|
g_assert(host_key);
|
|
|
|
PvHdrKeySlot *slot = pv_hdr_key_slot_new(img->gcm_cipher,
|
|
img->cust_root_key,
|
|
img->cust_pub_priv_key,
|
|
host_key, err);
|
|
if (!slot)
|
|
return -1;
|
|
|
|
pv_img_add_host_slot(img, slot);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static gint pv_img_set_comps_offset(PvImage *img, uint64_t offset, GError **err)
|
|
{
|
|
return pv_img_comps_set_offset(img->comps, offset, err);
|
|
}
|
|
|
|
PvImage *pv_img_new(PvArgs *args, const gchar *stage3a_path, GError **err)
|
|
{
|
|
g_autoptr(PvImage) ret = g_new0(PvImage, 1);
|
|
uint64_t offset;
|
|
|
|
g_assert(args->tmp_dir);
|
|
g_assert(stage3a_path);
|
|
|
|
if (args->no_verify)
|
|
g_warning(_("host-key document verification is disabled. Your workload is not secured."));
|
|
|
|
ret->comps = pv_img_comps_new(EVP_sha512(), EVP_sha512(), EVP_sha512(), err);
|
|
if (!ret->comps)
|
|
return NULL;
|
|
|
|
ret->cust_comm_cipher = EVP_aes_256_gcm();
|
|
ret->gcm_cipher = EVP_aes_256_gcm();
|
|
ret->initial_psw.addr = DEFAULT_INITIAL_PSW_ADDR;
|
|
ret->initial_psw.mask = DEFAULT_INITIAL_PSW_MASK;
|
|
ret->nid = NID_secp521r1;
|
|
ret->tmp_dir = g_strdup(args->tmp_dir);
|
|
ret->xts_cipher = EVP_aes_256_xts();
|
|
|
|
/* set initial PSW that will be loaded by the stage3b */
|
|
if (pv_img_set_psw_addr(ret, args->psw_addr, err) < 0)
|
|
return NULL;
|
|
|
|
/* set the control flags: PCF and SCF */
|
|
if (pv_img_set_control_flags(ret, args->pcf, args->scf, err) < 0)
|
|
return NULL;
|
|
|
|
/* read in the keys */
|
|
if (pv_img_set_keys(ret, args, err) < 0)
|
|
return NULL;
|
|
|
|
if (pv_img_set_host_slots(ret, err) < 0)
|
|
return NULL;
|
|
|
|
/* allocate enough memory for the stage3a args and load the
|
|
* stage3a template into memory and set the loader_psw
|
|
*/
|
|
if (pv_img_load_and_set_stage3a(ret, stage3a_path, err) < 0)
|
|
return NULL;
|
|
|
|
offset = PAGE_ALIGN(STAGE3A_LOAD_ADDRESS + ret->stage3a->size);
|
|
|
|
/* shift right all components by the size of stage3a loader */
|
|
if (pv_img_set_comps_offset(ret, offset, err) < 0)
|
|
return NULL;
|
|
|
|
return g_steal_pointer(&ret);
|
|
}
|
|
|
|
void pv_img_free(PvImage *img)
|
|
{
|
|
if (!img)
|
|
return;
|
|
|
|
g_slist_free_full(img->optional_items,
|
|
(GDestroyNotify)pv_opt_item_free);
|
|
g_slist_free_full(img->key_slots, (GDestroyNotify)pv_hdr_key_slot_free);
|
|
g_slist_free_full(img->host_pub_keys, (GDestroyNotify)EVP_PKEY_free);
|
|
EVP_PKEY_free(img->cust_pub_priv_key);
|
|
buffer_clear(&img->stage3a);
|
|
pv_img_comps_free(img->comps);
|
|
g_free(img->tmp_dir);
|
|
buffer_free(img->xts_key);
|
|
buffer_free(img->cust_root_key);
|
|
buffer_free(img->gcm_iv);
|
|
buffer_free(img->cust_comm_key);
|
|
g_free(img);
|
|
}
|
|
|
|
static gint pv_img_prepare_and_add_component(PvImage *img, PvComponent **comp,
|
|
GError **err)
|
|
{
|
|
g_assert(comp);
|
|
g_assert(*comp);
|
|
|
|
/* prepares the component: does the alignment and encryption
|
|
* if required
|
|
*/
|
|
if (pv_img_prepare_component(img, *comp, err) < 0)
|
|
return -1;
|
|
|
|
/* calculates the memory layout and adds the component to its
|
|
* internal list
|
|
*/
|
|
if (pv_img_comps_add_component(img->comps, comp, err) < 0)
|
|
return -1;
|
|
|
|
g_assert(!*comp);
|
|
return 0;
|
|
}
|
|
|
|
gint pv_img_add_component(PvImage *img, const PvArg *arg, GError **err)
|
|
{
|
|
g_autoptr(PvComponent) comp = NULL;
|
|
|
|
comp = pv_component_new_file(arg->type, arg->path, err);
|
|
if (!comp)
|
|
return -1;
|
|
|
|
if (pv_img_prepare_and_add_component(img, &comp, err) < 0)
|
|
return -1;
|
|
|
|
g_assert(!comp);
|
|
return 0;
|
|
}
|
|
|
|
gint pv_img_calc_pld_ald_tld_nep(const PvImage *img, Buffer **pld, Buffer **ald,
|
|
Buffer **tld, uint64_t *nep, GError **err)
|
|
{
|
|
return pv_img_comps_finalize(img->comps, pld, ald, tld, nep, err);
|
|
}
|
|
|
|
static gint pv_img_build_stage3b(PvImage *img, Buffer *stage3b, GError **err)
|
|
{
|
|
g_autofree struct stage3b_args *args = NULL;
|
|
|
|
args = pv_img_comps_get_stage3b_args(img->comps, &img->initial_psw);
|
|
if (!args) {
|
|
g_set_error(err, PV_ERROR, PV_ERROR_INTERNAL,
|
|
_("Cannot generate stage3b arguments"));
|
|
return -1;
|
|
}
|
|
|
|
build_stage3b(stage3b, args);
|
|
return 0;
|
|
}
|
|
|
|
gint pv_img_add_stage3b_comp(PvImage *img, const gchar *path, GError **err)
|
|
{
|
|
g_autoptr(PvComponent) comp = NULL;
|
|
g_autoptr(Buffer) stage3b = NULL;
|
|
|
|
stage3b = stage3b_getblob(path, err);
|
|
if (!stage3b)
|
|
return -1;
|
|
|
|
/* set the stage3b data */
|
|
if (pv_img_build_stage3b(img, stage3b, err) < 0)
|
|
return -1;
|
|
|
|
comp = pv_component_new_buf(PV_COMP_TYPE_STAGE3B, stage3b, err);
|
|
if (!comp)
|
|
return -1;
|
|
|
|
if (pv_img_prepare_and_add_component(img, &comp, err) < 0)
|
|
return -1;
|
|
|
|
g_assert(!comp);
|
|
return 0;
|
|
}
|
|
|
|
static uint32_t pv_img_get_aad_size(const PvImage *img)
|
|
{
|
|
uint32_t key_size, size = 0;
|
|
|
|
g_assert(sizeof(struct pv_hdr_head) <= UINT32_MAX);
|
|
g_assert(sizeof(struct pv_hdr_key_slot) <= UINT32_MAX);
|
|
|
|
g_assert_true(g_uint_checked_add(&size, size,
|
|
(uint32_t)sizeof(struct pv_hdr_head)));
|
|
g_assert_true(g_uint_checked_mul(&key_size,
|
|
(uint32_t)sizeof(struct pv_hdr_key_slot),
|
|
g_slist_length(img->key_slots)));
|
|
g_assert_true(g_uint_checked_add(&size, size, key_size));
|
|
return size;
|
|
}
|
|
|
|
static uint32_t pv_img_get_opt_items_size(const PvImage *img)
|
|
{
|
|
uint32_t ret = 0;
|
|
|
|
g_assert(img);
|
|
|
|
for (GSList *iterator = img->optional_items; iterator;
|
|
iterator = iterator->next) {
|
|
const struct pv_hdr_opt_item *item = iterator->data;
|
|
|
|
g_assert(item);
|
|
g_assert_true(g_uint_checked_add(&ret, ret, pv_opt_item_size(item)));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
uint32_t pv_img_get_enc_size(const PvImage *img)
|
|
{
|
|
uint32_t ret = 0;
|
|
|
|
g_assert(sizeof(struct pv_hdr_encrypted) <= UINT32_MAX);
|
|
|
|
g_assert_true(g_uint_checked_add(
|
|
&ret, ret, (uint32_t)sizeof(struct pv_hdr_encrypted)));
|
|
g_assert_true(
|
|
g_uint_checked_add(&ret, ret, pv_img_get_opt_items_size(img)));
|
|
return ret;
|
|
}
|
|
|
|
static uint32_t pv_img_get_tag_size(const PvImage *img G_GNUC_UNUSED)
|
|
{
|
|
g_assert(sizeof(((struct pv_hdr *)0)->tag) <= UINT32_MAX);
|
|
|
|
return (uint32_t)sizeof(((struct pv_hdr *)0)->tag);
|
|
}
|
|
|
|
uint32_t pv_img_get_pv_hdr_size(const PvImage *img)
|
|
{
|
|
uint32_t size = 0;
|
|
|
|
g_assert_true(
|
|
g_uint_checked_add(&size, size, pv_img_get_aad_size(img)));
|
|
g_assert_true(
|
|
g_uint_checked_add(&size, size, pv_img_get_enc_size(img)));
|
|
g_assert_true(
|
|
g_uint_checked_add(&size, size, pv_img_get_tag_size(img)));
|
|
return size;
|
|
}
|
|
|
|
static gint get_stage3a_data_size(const PvImage *img, gsize *data_size,
|
|
GError **err)
|
|
{
|
|
gsize ipib_size, hdr_size;
|
|
|
|
g_assert(data_size);
|
|
g_assert(*data_size == 0);
|
|
|
|
ipib_size = pv_ipib_get_size(pv_img_comps_length(img->comps));
|
|
if (ipib_size > PV_V1_IPIB_MAX_SIZE) {
|
|
g_set_error(err, PV_ERROR, PV_ERROR_IPIB_SIZE,
|
|
_("IPIB size is too large: '%zu' > '%zu'"),
|
|
ipib_size, PV_V1_IPIB_MAX_SIZE);
|
|
return -1;
|
|
}
|
|
|
|
hdr_size = pv_img_get_pv_hdr_size(img);
|
|
if (hdr_size > PV_V1_PV_HDR_MAX_SIZE) {
|
|
g_set_error(err, PV_ERROR, PV_ERROR_PV_HDR_SIZE,
|
|
_("PV header size is too large: '%zu' > '%zu'"),
|
|
hdr_size, PV_V1_PV_HDR_MAX_SIZE);
|
|
return -1;
|
|
}
|
|
|
|
*data_size += PAGE_ALIGN(ipib_size);
|
|
*data_size += PAGE_ALIGN(hdr_size);
|
|
return 0;
|
|
}
|
|
|
|
gint pv_img_load_and_set_stage3a(PvImage *img, const gchar *path, GError **err)
|
|
{
|
|
g_autoptr(Buffer) stage3a = NULL;
|
|
gsize bin_size, data_size = 0;
|
|
|
|
if (get_stage3a_data_size(img, &data_size, err) < 0)
|
|
return -1;
|
|
|
|
stage3a = stage3a_getblob(path, &bin_size, data_size, err);
|
|
if (!stage3a)
|
|
return -1;
|
|
|
|
img->stage3a_psw.addr = STAGE3A_ENTRY;
|
|
img->stage3a_psw.mask = DEFAULT_INITIAL_PSW_MASK;
|
|
|
|
/* set addresses and size */
|
|
img->stage3a = g_steal_pointer(&stage3a);
|
|
img->stage3a_bin_size = bin_size;
|
|
return 0;
|
|
}
|
|
|
|
/* Creates the PV IPIB and sets the stage3a arguments */
|
|
static gint pv_img_build_stage3a(Buffer *stage3a, gsize stage3a_bin_size,
|
|
GSList *comps, const Buffer *hdr, GError **err)
|
|
{
|
|
g_autofree struct ipl_parameter_block *ipib = NULL;
|
|
|
|
g_assert(stage3a);
|
|
g_assert(hdr);
|
|
|
|
ipib = pv_ipib_new(comps, hdr, err);
|
|
if (!ipib)
|
|
return -1;
|
|
|
|
if (build_stage3a(stage3a, stage3a_bin_size, hdr, ipib, err) < 0)
|
|
return -1;
|
|
|
|
g_info("%12s:\t0x%012lx (%12ld / %12ld Bytes)", "stage3a",
|
|
STAGE3A_LOAD_ADDRESS, stage3a->size, stage3a->size);
|
|
return 0;
|
|
}
|
|
|
|
/* Creates the actual PV header (serialized and AES-GCM encrypted) */
|
|
static Buffer *pv_img_create_pv_hdr(PvImage *img, GError **err)
|
|
{
|
|
g_autoptr(Buffer) hdr_buf = NULL;
|
|
g_autoptr(PvHdr) hdr = NULL;
|
|
|
|
hdr = pv_hdr_new(img, err);
|
|
if (!hdr)
|
|
return NULL;
|
|
|
|
hdr_buf = pv_hdr_serialize(hdr, img, PV_ENCRYPT, err);
|
|
if (!hdr_buf)
|
|
return NULL;
|
|
|
|
return g_steal_pointer(&hdr_buf);
|
|
}
|
|
|
|
/* No changes to the components are allowed after calling this
|
|
* function
|
|
*/
|
|
gint pv_img_finalize(PvImage *pv, const gchar *stage3b_path, GError **err)
|
|
{
|
|
g_autoptr(Buffer) hdr = NULL;
|
|
|
|
/* load stage3b template into memory and add it to the list of
|
|
* components. This must be done before calling
|
|
* `pv_img_load_and_set_stage3a`.
|
|
*/
|
|
if (pv_img_add_stage3b_comp(pv, stage3b_path, err) < 0)
|
|
return -1;
|
|
|
|
/* create the PV header */
|
|
hdr = pv_img_create_pv_hdr(pv, err);
|
|
if (!hdr)
|
|
return -1;
|
|
|
|
/* generate stage3a. At this point in time the PV header and
|
|
* the stage3b must be generated and encrypted
|
|
*/
|
|
if (pv_img_build_stage3a(pv->stage3a, pv->stage3a_bin_size,
|
|
pv_img_comps_get_comps(pv->comps), hdr, err) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static gint convert_psw_to_short_psw(const struct psw_t *psw, uint64_t *dst,
|
|
GError **err)
|
|
{
|
|
g_assert(psw);
|
|
g_assert(dst);
|
|
|
|
uint64_t psw_addr = psw->addr;
|
|
uint64_t psw_mask = psw->mask;
|
|
|
|
/* test if PSW mask can be converted */
|
|
if (psw_mask & PSW_SHORT_ADDR_MASK) {
|
|
g_set_error(err, PV_ERROR, PV_ERROR_INTERNAL,
|
|
_("Failed to convert PSW to short PSW"));
|
|
return -1;
|
|
}
|
|
|
|
/* test for bit 12 */
|
|
if (psw_mask & PSW_MASK_BIT_12) {
|
|
g_set_error(err, PV_ERROR, PV_ERROR_INTERNAL,
|
|
_("Failed to convert PSW to short PSW"));
|
|
return -1;
|
|
}
|
|
|
|
/* test if PSW addr can be converted */
|
|
if (psw_addr & ~PSW_SHORT_ADDR_MASK) {
|
|
g_set_error(err, PV_ERROR, PV_ERROR_INTERNAL,
|
|
_("Failed to convert PSW to short PSW"));
|
|
return -1;
|
|
}
|
|
|
|
*dst = psw_mask;
|
|
/* set bit 12 to 1 */
|
|
*dst |= PSW_MASK_BIT_12;
|
|
*dst |= psw_addr;
|
|
return 0;
|
|
}
|
|
|
|
static gint write_short_psw(FILE *f, struct psw_t *psw, GError **err)
|
|
{
|
|
uint64_t short_psw, short_psw_be;
|
|
|
|
if (convert_psw_to_short_psw(psw, &short_psw, err) < 0)
|
|
return -1;
|
|
|
|
short_psw_be = GUINT64_TO_BE(short_psw);
|
|
return file_write(f, &short_psw_be, 1, sizeof(short_psw_be), NULL, err);
|
|
}
|
|
|
|
gint pv_img_write(PvImage *img, const gchar *path, GError **err)
|
|
{
|
|
gint ret = -1;
|
|
FILE *f = file_open(path, "wb", err);
|
|
|
|
if (!f)
|
|
return -1;
|
|
|
|
if (write_short_psw(f, &img->stage3a_psw, err) < 0) {
|
|
g_prefix_error(err, _("Failed to write image '%s': "), path);
|
|
goto err;
|
|
}
|
|
|
|
if (seek_and_write_buffer(f, img->stage3a, STAGE3A_LOAD_ADDRESS, err) <
|
|
0) {
|
|
g_prefix_error(err, _("Failed to write image '%s': "), path);
|
|
goto err;
|
|
}
|
|
|
|
/* list is sorted by component type => by address */
|
|
for (GSList *iterator = pv_img_comps_get_comps(img->comps); iterator;
|
|
iterator = iterator->next) {
|
|
gint rc;
|
|
const PvComponent *comp = iterator->data;
|
|
|
|
rc = pv_component_write(comp, f, err);
|
|
if (rc < 0) {
|
|
g_prefix_error(err, _("Failed to write image '%s': "),
|
|
path);
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
ret = 0;
|
|
err:
|
|
if (f)
|
|
fclose(f);
|
|
return ret;
|
|
}
|