genprotimg: introduce new tool for the creation of PV images

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>
This commit is contained in:
Marc Hartmayer
2020-03-02 13:17:01 +01:00
committed by Jan Höppner
parent 2c106425e8
commit 65b9fc442c
38 changed files with 4965 additions and 2 deletions
+405
View File
@@ -0,0 +1,405 @@
/*
* PV arguments 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 <glib.h>
#include <glib/gprintf.h>
#include "common.h"
#include "pv_comp.h"
#include "pv_error.h"
#include "pv_args.h"
static gchar summary[] =
"Use genprotimg to create a protected virtualization kernel image file,\n"
"which can be loaded using zipl or QEMU.";
static gint pv_arg_compare(gconstpointer arg_1, gconstpointer arg_2)
{
g_assert(arg_1);
g_assert(arg_2);
PvComponentType a = ((PvArg *)arg_1)->type;
PvComponentType b = ((PvArg *)arg_2)->type;
if (a < b)
return -1;
if (a == b)
return 0;
return 1;
}
static gint pv_arg_has_type(gconstpointer arg, gconstpointer type)
{
const PvArg *c = arg;
const PvComponentType *t = type;
g_assert(arg);
if (c->type == *t)
return 0;
if (c->type < *t)
return -1;
return 1;
}
static gint pv_args_set_defaults(PvArgs *args, GError **err G_GNUC_UNUSED)
{
if (!args->psw_addr)
args->psw_addr =
g_strdup_printf("0x%lx", DEFAULT_INITIAL_PSW_ADDR);
return 0;
}
static gint pv_args_validate_options(PvArgs *args, GError **err)
{
PvComponentType KERNEL = PV_COMP_TYPE_KERNEL;
if (args->unused_values->len > 0) {
g_autofree gchar *unused = NULL;
for (gsize i = args->unused_values->len; i > 0; i--) {
g_autofree gchar *tmp = unused;
unused = g_strjoin(" ", g_ptr_array_index(args->unused_values, i - 1),
tmp,
NULL);
}
g_set_error(err, PV_PARSE_ERROR, PR_PARSE_ERROR_INVALID_ARGUMENT,
_("Unrecognized arguments: '%s'.\nUse 'genprotimg --help' for more information"),
unused);
return -1;
}
if (!args->output_path) {
g_set_error(err, PV_PARSE_ERROR, PR_PARSE_ERROR_MISSING_ARGUMENT,
_("Option '--output' is required.\nUse 'genprotimg --help' for more information"));
return -1;
}
if (!g_slist_find_custom(args->comps, &KERNEL, pv_arg_has_type)) {
g_set_error(err, PV_PARSE_ERROR, PR_PARSE_ERROR_MISSING_ARGUMENT,
_("Option '--image' is required.\nUse 'genprotimg --help' for more information"));
return -1;
}
if (!args->host_keys || g_strv_length(args->host_keys) == 0) {
g_set_error(err, PV_PARSE_ERROR, PR_PARSE_ERROR_MISSING_ARGUMENT,
_("Option '--host-key-document' is required.\nUse 'genprotimg --help' for more information"));
return -1;
}
if (!args->no_verify) {
g_set_error(err, PV_PARSE_ERROR, PR_PARSE_ERROR_MISSING_ARGUMENT,
_("Use the option '--no-verify' as the verification support is not available yet."));
return -1;
}
return 0;
}
static gboolean cb_add_component(const gchar *option, const gchar *value,
PvArgs *args, GError **err)
{
PvArg *comp = NULL;
gint type = -1;
if (g_str_equal(option, "-i") || g_str_equal(option, "--image"))
type = PV_COMP_TYPE_KERNEL;
if (g_str_equal(option, "-r") || g_str_equal(option, "--ramdisk"))
type = PV_COMP_TYPE_INITRD;
if (g_str_equal(option, "-p") || g_str_equal(option, "--parmfile"))
type = PV_COMP_TYPE_CMDLINE;
if (type < 0) {
g_set_error(err, PV_PARSE_ERROR, PV_PARSE_ERROR_SYNTAX,
_("Invalid option '%s': "), option);
return FALSE;
}
if (g_slist_find_custom(args->comps, &type, pv_arg_has_type)) {
g_set_error(err, PV_PARSE_ERROR, PV_PARSE_ERROR_SYNTAX,
_("Multiple values for option '%s'"), option);
return FALSE;
}
comp = pv_arg_new((PvComponentType)type, value);
args->comps = g_slist_insert_sorted(args->comps, comp, pv_arg_compare);
return TRUE;
}
static gboolean cb_set_string_option(const gchar *option, const gchar *value,
PvArgs *args, GError **err)
{
gchar **args_option = NULL;
if (g_str_equal(option, "-o") || g_str_equal(option, "--output"))
args_option = &args->output_path;
if (g_str_equal(option, "--x-comp-key"))
args_option = &args->xts_key_path;
if (g_str_equal(option, "--x-comm-key"))
args_option = &args->cust_comm_key_path;
if (g_str_equal(option, "--x-header-key"))
args_option = &args->cust_root_key_path;
if (g_str_equal(option, "--x-pcf"))
args_option = &args->pcf;
if (g_str_equal(option, "--x-psw"))
args_option = &args->psw_addr;
if (g_str_equal(option, "--x-scf"))
args_option = &args->scf;
if (!args_option) {
g_set_error(err, PV_PARSE_ERROR, PV_PARSE_ERROR_SYNTAX,
_("Invalid option '%s': "), option);
return FALSE;
}
if (*args_option) {
g_set_error(err, PV_PARSE_ERROR, PV_PARSE_ERROR_SYNTAX,
_("Multiple values for option '%s'"), option);
return FALSE;
}
*args_option = g_strdup(value);
return TRUE;
}
static gboolean cb_set_log_level(const gchar *option G_GNUC_UNUSED,
const gchar *value G_GNUC_UNUSED, PvArgs *args,
GError **err G_GNUC_UNUSED)
{
args->log_level++;
return TRUE;
}
static gboolean cb_remaining_values(const gchar *option G_GNUC_UNUSED,
const gchar *value, PvArgs *args,
GError **err G_GNUC_UNUSED)
{
g_ptr_array_add(args->unused_values, g_strdup(value));
return TRUE;
}
#define INDENT " "
gint pv_args_parse_options(PvArgs *args, gint *argc, gchar **argv[],
GError **err)
{
g_autoptr(GOptionContext) context = NULL;
gboolean print_version = FALSE;
GOptionGroup *group, *x_group;
g_autofree gchar *psw_desc = g_strdup_printf(
_("Load from the specified hexadecimal ADDRESS.\n" INDENT
"Optional; default: '0x%lx'."),
DEFAULT_INITIAL_PSW_ADDR);
GOptionEntry entries[] = {
{ .long_name = "host-key-document",
.short_name = 'k',
.flags = G_OPTION_FLAG_NONE,
.arg = G_OPTION_ARG_FILENAME_ARRAY,
.arg_data = &args->host_keys,
.description =
_("FILE specifies a host-key document. At least\n" INDENT
"one is required."),
.arg_description = _("FILE") },
{ .long_name = "output",
.short_name = 'o',
.flags = G_OPTION_FLAG_FILENAME,
.arg = G_OPTION_ARG_CALLBACK,
.arg_data = cb_set_string_option,
.description = _("Set FILE as the output file."),
.arg_description = _("FILE") },
{ .long_name = "image",
.short_name = 'i',
.flags = G_OPTION_FLAG_FILENAME,
.arg = G_OPTION_ARG_CALLBACK,
.arg_data = cb_add_component,
.description = _("Use IMAGE as the Linux kernel image."),
.arg_description = _("IMAGE") },
{ .long_name = "ramdisk",
.short_name = 'r',
.flags = G_OPTION_FLAG_OPTIONAL_ARG | G_OPTION_FLAG_FILENAME,
.arg = G_OPTION_ARG_CALLBACK,
.arg_data = cb_add_component,
.description = _("Use RAMDISK as the initial RAM disk\n" INDENT
"(optional)."),
.arg_description = _("RAMDISK") },
{ .long_name = "parmfile",
.short_name = 'p',
.flags = G_OPTION_FLAG_OPTIONAL_ARG | G_OPTION_FLAG_FILENAME,
.arg = G_OPTION_ARG_CALLBACK,
.arg_data = cb_add_component,
.description = _("Use the kernel parameters stored in PARMFILE\n" INDENT
"(optional)."),
.arg_description = _("PARMFILE") },
{ .long_name = "no-verify",
.short_name = 0,
.flags = G_OPTION_FLAG_NONE,
.arg = G_OPTION_ARG_NONE,
.arg_data = &args->no_verify,
.description = _("Disable the host-key document verification\n" INDENT
"(optional)."),
.arg_description = NULL },
{ .long_name = "verbose",
.short_name = 'V',
.flags = G_OPTION_FLAG_NO_ARG,
.arg = G_OPTION_ARG_CALLBACK,
.arg_data = cb_set_log_level,
.description = _("Provide more detailed output (optional)."),
.arg_description = NULL },
{ .long_name = "version",
.short_name = 'v',
.flags = G_OPTION_FLAG_NONE,
.arg = G_OPTION_ARG_NONE,
.arg_data = &print_version,
.description = _("Print the version and exit."),
.arg_description = NULL },
{ .long_name = G_OPTION_REMAINING,
.short_name = 0,
.flags = 0,
.arg = G_OPTION_ARG_CALLBACK,
.arg_data = cb_remaining_values,
.description = NULL,
.arg_description = NULL },
{ 0 },
};
GOptionEntry x_entries[] = {
{ .long_name = "x-comm-key",
.short_name = 0,
.flags = G_OPTION_FLAG_FILENAME,
.arg = G_OPTION_ARG_CALLBACK,
.arg_data = cb_set_string_option,
.description = _(
"Use FILE as the customer communication key.\n" INDENT
"Optional; default: auto-generated."),
.arg_description = _("FILE") },
{ .long_name = "x-comp-key",
.short_name = 0,
.flags = G_OPTION_FLAG_FILENAME,
.arg = G_OPTION_ARG_CALLBACK,
.arg_data = cb_set_string_option,
.description = _(
"Use FILE as the AES 256-bit XTS key\n" INDENT
"that is used for the component encryption.\n" INDENT
"Optional; default: auto-generated."),
.arg_description = _("FILE") },
{ .long_name = "x-header-key",
.short_name = 0,
.flags = G_OPTION_FLAG_FILENAME,
.arg = G_OPTION_ARG_CALLBACK,
.arg_data = cb_set_string_option,
.description = _(
"Use FILE as the AES 256-bit GCM header key\n" INDENT
"that protects the PV header.\n" INDENT
"Optional; default: auto-generated."),
.arg_description = _("FILE") },
{ .long_name = "x-pcf",
.short_name = 0,
.flags = G_OPTION_FLAG_NONE,
.arg = G_OPTION_ARG_CALLBACK,
.arg_data = cb_set_string_option,
.description =
_("Specify the plaintext control flags\n" INDENT
"as a hexadecimal value.\n" INDENT
"Optional; default: '0x0'."),
.arg_description = _("VALUE") },
{ .long_name = "x-psw",
.short_name = 0,
.flags = G_OPTION_FLAG_NONE,
.arg = G_OPTION_ARG_CALLBACK,
.arg_data = cb_set_string_option,
.description = psw_desc,
.arg_description = _("ADDRESS") },
{ .long_name = "x-scf",
.short_name = 0,
.flags = G_OPTION_FLAG_NONE,
.arg = G_OPTION_ARG_CALLBACK,
.arg_data = cb_set_string_option,
.description = _("Specify the secret control flags\n" INDENT
"as a hexadecimal value.\n" INDENT
"Optional; default: '0x0'."),
.arg_description = _("VALUE") },
{ 0 },
};
context = g_option_context_new(
_("- Create a protected virtualization image"));
g_option_context_set_summary(context, _(summary));
group = g_option_group_new(GETTEXT_PACKAGE, _("Application Options:"),
_("Show help options"), args, NULL);
g_option_group_add_entries(group, entries);
g_option_context_set_main_group(context, group);
x_group = g_option_group_new("experimental", _("Experimental Options:"),
_("Show experimental options"), args, NULL);
g_option_group_add_entries(x_group, x_entries);
g_option_context_add_group(context, x_group);
if (!g_option_context_parse(context, argc, argv, err))
return -1;
if (print_version) {
g_printf(_("%s version %s\n"), tool_name, RELEASE_STRING);
g_printf("%s\n", copyright_notice);
exit(EXIT_SUCCESS);
}
if (pv_args_set_defaults(args, err) < 0)
return -1;
return pv_args_validate_options(args, err);
}
PvArgs *pv_args_new(void)
{
g_autoptr(PvArgs) args = g_new0(PvArgs, 1);
args->unused_values = g_ptr_array_new_with_free_func(g_free);
return g_steal_pointer(&args);
}
void pv_args_free(PvArgs *args)
{
if (!args)
return;
g_free(args->pcf);
g_free(args->scf);
g_free(args->psw_addr);
g_free(args->cust_root_key_path);
g_free(args->cust_comm_key_path);
g_free(args->gcm_iv_path);
g_strfreev(args->host_keys);
g_free(args->xts_key_path);
g_slist_free_full(args->comps, (GDestroyNotify)pv_arg_free);
g_ptr_array_free(args->unused_values, TRUE);
g_free(args->output_path);
g_free(args->tmp_dir);
g_free(args);
}
void pv_arg_free(PvArg *arg)
{
if (!arg)
return;
g_free(arg->path);
g_free(arg);
}
PvArg *pv_arg_new(PvComponentType type, const gchar *path)
{
g_autoptr(PvArg) ret = g_new0(struct pv_arg, 1);
ret->type = type;
ret->path = g_strdup(path);
return g_steal_pointer(&ret);
}
+53
View File
@@ -0,0 +1,53 @@
/*
* PV arguments 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.
*/
#ifndef PV_ARGS_H
#define PV_ARGS_H
#include <glib.h>
#include "pv_comp.h"
typedef struct pv_arg {
PvComponentType type;
gchar *path;
} PvArg;
PvArg *pv_arg_new(PvComponentType type, const gchar *path);
void pv_arg_free(PvArg *arg);
typedef struct {
gint log_level;
gint no_verify;
gchar *pcf;
gchar *scf;
gchar *psw_addr; /* PSW address which will be used for the start of
* the actual component (e.g. Linux kernel)
*/
gchar *cust_root_key_path;
gchar *cust_comm_key_path;
gchar *gcm_iv_path;
gchar **host_keys;
gchar *xts_key_path;
GSList *comps;
gchar *output_path;
gchar *tmp_dir;
GPtrArray *unused_values;
} PvArgs;
PvArgs *pv_args_new(void);
void pv_args_free(PvArgs *args);
gint pv_args_parse_options(PvArgs *args, gint *argc, gchar **argv[],
GError **err);
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(PvArg, pv_arg_free)
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(PvArgs, pv_args_free)
#endif
+446
View File
@@ -0,0 +1,446 @@
/*
* PV component 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 <glib.h>
#include <glib/gtypes.h>
#include <openssl/bn.h>
#include <openssl/evp.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "boot/s390.h"
#include "common.h"
#include "utils/align.h"
#include "utils/buffer.h"
#include "utils/crypto.h"
#include "utils/file_utils.h"
#include "pv_comp.h"
#include "pv_error.h"
static void comp_file_free(CompFile *comp)
{
if (!comp)
return;
g_free(comp->path);
g_free(comp);
}
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(CompFile, comp_file_free)
static PvComponent *pv_component_new(PvComponentType type, gsize size,
PvComponentDataType d_type, void **data,
GError **err)
{
g_autoptr(PvComponent) ret = g_new0(PvComponent, 1);
g_assert(type >= 0 && type <= UINT16_MAX);
ret->type = (int)type;
ret->d_type = (int)d_type;
ret->data = g_steal_pointer(data);
ret->orig_size = size;
if (generate_tweak(&ret->tweak, (uint16_t)type, err) < 0)
return NULL;
return g_steal_pointer(&ret);
}
PvComponent *pv_component_new_file(PvComponentType type, const gchar *path,
GError **err)
{
g_autoptr(CompFile) file = g_new0(CompFile, 1);
gsize size;
gint rc;
g_assert(path != NULL);
rc = file_size(path, &size, err);
if (rc < 0)
return NULL;
file->path = g_strdup(path);
file->size = size;
return pv_component_new(type, size, DATA_FILE, (void **)&file, err);
}
PvComponent *pv_component_new_buf(PvComponentType type, const Buffer *buf,
GError **err)
{
g_assert(buf);
g_autoptr(Buffer) dup_buf = buffer_dup(buf, FALSE);
return pv_component_new(type, buf->size, DATA_BUFFER, (void **)&dup_buf,
err);
}
void pv_component_free(PvComponent *component)
{
if (!component)
return;
switch ((PvComponentDataType)component->d_type) {
case DATA_BUFFER:
buffer_clear(&component->buf);
break;
case DATA_FILE:
comp_file_free(component->file);
break;
}
g_free(component);
}
gint pv_component_type(const PvComponent *component)
{
return component->type;
}
const gchar *pv_component_name(const PvComponent *component)
{
gint type = pv_component_type(component);
switch ((PvComponentType)type) {
case PV_COMP_TYPE_KERNEL:
return "kernel";
case PV_COMP_TYPE_INITRD:
return "ramdisk";
case PV_COMP_TYPE_CMDLINE:
return "parmline";
case PV_COMP_TYPE_STAGE3B:
return "stage3b";
}
g_assert_not_reached();
}
uint64_t pv_component_size(const PvComponent *component)
{
switch ((PvComponentDataType)component->d_type) {
case DATA_BUFFER:
return component->buf->size;
case DATA_FILE:
return component->file->size;
}
g_assert_not_reached();
}
uint64_t pv_component_get_src_addr(const PvComponent *component)
{
return component->src_addr;
}
uint64_t pv_component_get_orig_size(const PvComponent *component)
{
return component->orig_size;
}
uint64_t pv_component_get_tweak_prefix(const PvComponent *component)
{
return GUINT64_FROM_BE(component->tweak.cmp_idx.data);
}
gboolean pv_component_is_stage3b(const PvComponent *component)
{
return pv_component_type(component) == PV_COMP_TYPE_STAGE3B;
}
gint pv_component_align_and_encrypt(PvComponent *component, const gchar *tmp_path,
void *opaque, GError **err)
{
struct cipher_parms *parms = opaque;
switch ((PvComponentDataType)component->d_type) {
case DATA_BUFFER: {
g_autoptr(Buffer) enc_buf = NULL;
if (!(IS_PAGE_ALIGNED(pv_component_size(component)))) {
g_autoptr(Buffer) new = NULL;
/* create a page aligned copy */
new = buffer_dup(component->buf, TRUE);
buffer_clear(&component->buf);
component->buf = g_steal_pointer(&new);
}
enc_buf = encrypt_buf(parms, component->buf, err);
if (!enc_buf)
return -1;
buffer_clear(&component->buf);
component->buf = g_steal_pointer(&enc_buf);
return 0;
}
case DATA_FILE: {
const gchar *comp_name = pv_component_name(component);
gchar *path_in = component->file->path;
g_autofree gchar *path_out = NULL;
gsize orig_size;
gsize prep_size;
g_assert(path_in);
path_out = g_build_filename(tmp_path, comp_name, NULL);
if (encrypt_file(parms, path_in, path_out, &orig_size,
&prep_size, err) < 0)
return -1;
if (component->orig_size != orig_size) {
g_set_error(err, G_FILE_ERROR, PV_ERROR_INTERNAL,
_("File has changed during the preparation '%s'"),
path_out);
return -1;
}
g_free(component->file->path);
component->file->size = prep_size;
component->file->path = g_steal_pointer(&path_out);
return 0;
}
}
g_assert_not_reached();
}
/* Page align the size of the component */
gint pv_component_align(PvComponent *component, const gchar *tmp_path,
void *opaque G_GNUC_UNUSED, GError **err)
{
if (IS_PAGE_ALIGNED(pv_component_size(component)))
return 0;
switch (component->d_type) {
case DATA_BUFFER: {
g_autoptr(Buffer) buf = NULL;
buf = buffer_dup(component->buf, TRUE);
buffer_clear(&component->buf);
component->buf = g_steal_pointer(&buf);
return 0;
} break;
case DATA_FILE: {
const gchar *comp_name = pv_component_name(component);
g_autofree gchar *path_out =
g_build_filename(tmp_path, comp_name, NULL);
gchar *path_in = component->file->path;
gsize size_out;
if (pad_file_right(path_out, path_in, &size_out, PAGE_SIZE,
err) < 0)
return -1;
g_free(component->file->path);
component->file->path = g_steal_pointer(&path_out);
component->file->size = size_out;
return 0;
} break;
}
g_assert_not_reached();
}
/* Convert uint64_t address to byte array */
static void uint64_to_uint8_buf(uint8_t dst[8], uint64_t addr)
{
uint8_t *p = (uint8_t *)&addr;
g_assert(dst);
for (gint i = 0; i < 8; i++) {
/* cppcheck-suppress objectIndex */
dst[i] = p[i];
}
}
int64_t pv_component_update_ald(const PvComponent *comp, EVP_MD_CTX *ctx,
GError **err)
{
uint64_t addr = pv_component_get_src_addr(comp);
uint64_t size = pv_component_size(comp);
uint64_t cur = addr;
int64_t nep = 0;
g_assert(IS_PAGE_ALIGNED(size) && size != 0);
do {
uint64_t cur_be = GUINT64_TO_BE(cur);
uint8_t addr_buf[8];
uint64_to_uint8_buf(addr_buf, cur_be);
if (EVP_DigestUpdate(ctx, addr_buf, sizeof(addr_buf)) != 1) {
g_set_error(err, PV_CRYPTO_ERROR,
PV_CRYPTO_ERROR_INTERNAL,
_("EVP_DigestUpdate failed"));
return -1;
}
cur += PAGE_SIZE;
nep++;
} while (cur < addr + size);
return nep;
}
int64_t pv_component_update_pld(const PvComponent *comp, EVP_MD_CTX *ctx,
GError **err)
{
uint64_t size = pv_component_size(comp);
int64_t nep = 0;
g_assert(IS_PAGE_ALIGNED(size) && size != 0);
switch (comp->d_type) {
case DATA_BUFFER: {
const Buffer *buf = comp->buf;
g_assert(buf->size <= INT64_MAX);
g_assert(buf->size == size);
if (EVP_DigestUpdate(ctx, buf->data, buf->size) != 1) {
g_set_error(err, PV_CRYPTO_ERROR,
PV_CRYPTO_ERROR_INTERNAL,
_("EVP_DigestUpdate failed"));
return -1;
}
nep = (int64_t)(buf->size / PAGE_SIZE);
break;
}
case DATA_FILE: {
const gchar *in_path = comp->file->path;
guchar in_buf[PAGE_SIZE];
gsize num_bytes_read_total = 0;
gsize num_bytes_read = 0;
FILE *f_in;
f_in = file_open(in_path, "rb", err);
if (!f_in)
return -1;
do {
/* Read data in blocks. Update the digest
* context each read.
*/
if (file_read(f_in, in_buf, sizeof(*in_buf),
sizeof(in_buf), &num_bytes_read,
err) < 0) {
fclose(f_in);
return -1;
}
num_bytes_read_total += num_bytes_read;
if (EVP_DigestUpdate(ctx, in_buf, sizeof(in_buf)) != 1) {
g_set_error(err, PV_CRYPTO_ERROR,
PV_CRYPTO_ERROR_INTERNAL,
_("EVP_DigestUpdate failed"));
fclose(f_in);
return -1;
}
nep++;
} while (num_bytes_read_total < pv_component_size(comp) &&
num_bytes_read != 0);
if (num_bytes_read_total != pv_component_size(comp)) {
g_set_error(err, G_FILE_ERROR, PV_ERROR_INTERNAL,
_("'%s' has changed during the preparation"),
in_path);
fclose(f_in);
return -1;
}
fclose(f_in);
break;
}
default:
g_assert_not_reached();
}
return nep;
}
int64_t pv_component_update_tld(const PvComponent *comp, EVP_MD_CTX *ctx,
GError **err)
{
uint64_t size = pv_component_size(comp);
const union tweak *tweak = &comp->tweak;
g_autoptr(BIGNUM) tweak_num = NULL;
int64_t nep = 0;
g_assert(IS_PAGE_ALIGNED(size) && size != 0);
tweak_num = BN_bin2bn(tweak->data, sizeof(tweak->data), NULL);
if (!tweak_num) {
g_set_error(err, PV_CRYPTO_ERROR,
PV_CRYPTO_ERROR_INTERNAL,
_("BN_bin2bn failed"));
}
for (uint64_t cur = 0; cur < size; cur += PAGE_SIZE) {
guchar tmp[sizeof(tweak->data)] = { 0 };
g_assert(BN_num_bytes(tweak_num) >= 0);
g_assert(sizeof(tmp) - (guint)BN_num_bytes(tweak_num) > 0);
if (BN_bn2binpad(tweak_num, tmp, sizeof(tmp)) < 0) {
g_set_error(err, PV_CRYPTO_ERROR,
PV_CRYPTO_ERROR_INTERNAL,
_("BN_bn2binpad failed"));
}
if (EVP_DigestUpdate(ctx, tmp, sizeof(tmp)) != 1) {
g_set_error(err, PV_CRYPTO_ERROR,
PV_CRYPTO_ERROR_INTERNAL,
_("EVP_DigestUpdate failed"));
return -1;
}
/* calculate new tweak value */
if (BN_add_word(tweak_num, PAGE_SIZE) != 1) {
g_set_error(err, PV_CRYPTO_ERROR,
PV_CRYPTO_ERROR_INTERNAL,
_("BN_add_word failed"));
}
nep++;
}
return nep;
}
gint pv_component_write(const PvComponent *component, FILE *f, GError **err)
{
uint64_t offset = pv_component_get_src_addr(component);
g_assert(f);
switch (component->d_type) {
case DATA_BUFFER: {
const Buffer *buf = component->buf;
if (seek_and_write_buffer(f, buf, offset, err) < 0)
return -1;
return 0;
}
case DATA_FILE: {
const CompFile *file = component->file;
if (seek_and_write_file(f, file, offset, err) < 0)
return -1;
return 0;
}
}
g_assert_not_reached();
}
+78
View File
@@ -0,0 +1,78 @@
/*
* PV component 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.
*/
#ifndef PV_COMP_H
#define PV_COMP_H
#include <glib.h>
#include <openssl/evp.h>
#include <stdint.h>
#include "utils/crypto.h"
/* The order of this enum also implicitly defines the order of the
* components within the PV image!
*/
typedef enum {
PV_COMP_TYPE_KERNEL = 0,
PV_COMP_TYPE_CMDLINE = 1,
PV_COMP_TYPE_INITRD = 2,
PV_COMP_TYPE_STAGE3B = 3,
} PvComponentType;
typedef enum {
DATA_FILE = 0,
DATA_BUFFER,
} PvComponentDataType;
typedef struct comp_file {
gchar *path;
gsize size;
} CompFile;
typedef struct {
gint type; /* PvComponentType */
gint d_type; /* PvComponentDataType */
union {
struct comp_file *file;
Buffer *buf;
void *data;
};
uint64_t src_addr;
uint64_t orig_size;
union tweak tweak; /* used for the AES XTS encryption */
} PvComponent;
PvComponent *pv_component_new_file(PvComponentType type, const gchar *path,
GError **err);
PvComponent *pv_component_new_buf(PvComponentType type, const Buffer *buf,
GError **err);
void pv_component_free(PvComponent *component);
gint pv_component_type(const PvComponent *component);
const gchar *pv_component_name(const PvComponent *component);
uint64_t pv_component_size(const PvComponent *component);
uint64_t pv_component_get_src_addr(const PvComponent *component);
uint64_t pv_component_get_orig_size(const PvComponent *component);
uint64_t pv_component_get_tweak_prefix(const PvComponent *component);
gboolean pv_component_is_stage3b(const PvComponent *component);
gint pv_component_align_and_encrypt(PvComponent *component, const gchar *tmp_path,
void *opaque, GError **err);
gint pv_component_align(PvComponent *component, const gchar *tmp_path,
void *opaque G_GNUC_UNUSED, GError **err);
int64_t pv_component_update_pld(const PvComponent *comp, EVP_MD_CTX *ctx,
GError **err);
int64_t pv_component_update_ald(const PvComponent *comp, EVP_MD_CTX *ctx,
GError **err);
int64_t pv_component_update_tld(const PvComponent *comp, EVP_MD_CTX *ctx,
GError **err);
gint pv_component_write(const PvComponent *component, FILE *f, GError **err);
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(PvComponent, pv_component_free)
#endif
+252
View File
@@ -0,0 +1,252 @@
/*
* PV components 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 <glib.h>
#include <glib/gtypes.h>
#include <openssl/evp.h>
#include <stdint.h>
#include "boot/s390.h"
#include "boot/stage3b.h"
#include "common.h"
#include "utils/align.h"
#include "utils/crypto.h"
#include "pv_comp.h"
#include "pv_comps.h"
#include "pv_error.h"
#include "pv_stage3.h"
struct _pv_img_comps {
gboolean finalized;
uint64_t next_src;
uint64_t nep;
EVP_MD_CTX *ald; /* context used for the hash of the addresses */
EVP_MD_CTX *pld; /* context used for the hash of the pages content */
EVP_MD_CTX *tld; /* context used for the hash of the tweaks */
GSList *comps; /* elements sorted by component type */
};
void pv_img_comps_free(PvImgComps *comps)
{
if (!comps)
return;
EVP_MD_CTX_free(comps->ald);
EVP_MD_CTX_free(comps->pld);
EVP_MD_CTX_free(comps->tld);
g_slist_free_full(comps->comps, (GDestroyNotify)pv_component_free);
g_free(comps);
}
PvImgComps *pv_img_comps_new(const EVP_MD *ald_md, const EVP_MD *pld_md,
const EVP_MD *tld_md, GError **err)
{
g_autoptr(PvImgComps) ret = g_new0(PvImgComps, 1);
ret->ald = digest_ctx_new(ald_md, err);
if (!ret->ald)
return NULL;
ret->pld = digest_ctx_new(pld_md, err);
if (!ret->pld)
return NULL;
ret->tld = digest_ctx_new(tld_md, err);
if (!ret->tld)
return NULL;
return g_steal_pointer(&ret);
}
guint pv_img_comps_length(const PvImgComps *comps)
{
return g_slist_length(comps->comps);
}
/* Update hashes and nep */
/* Returns 0 in case of success and -1 in case of a failure */
static gint pv_img_comps_hash_comp(PvImgComps *comps, const PvComponent *comp,
GError **err)
{
int64_t nep_1 = 0;
int64_t nep_2 = 0;
int64_t nep_3 = 0;
/* update pld */
nep_1 = pv_component_update_pld(comp, comps->pld, err);
if (nep_1 < 0)
return -1;
/* update ald */
nep_2 = pv_component_update_ald(comp, comps->ald, err);
if (nep_2 < 0)
return -1;
/* update tld */
nep_3 = pv_component_update_tld(comp, comps->tld, err);
if (nep_3 < 0)
return -1;
g_assert(nep_1 == nep_2);
g_assert(nep_2 == nep_3);
/* update comps->nep */
g_assert_true(g_uint64_checked_add(&comps->nep, comps->nep,
(uint64_t)nep_1));
return 0;
}
gint pv_img_comps_add_component(PvImgComps *comps, PvComponent **comp,
GError **err)
{
g_assert(comp);
g_assert(*comp);
g_assert(comps);
g_assert(IS_PAGE_ALIGNED(comps->next_src));
uint64_t src_addr = comps->next_src;
uint64_t src_size = pv_component_size(*comp)
? PAGE_ALIGN(pv_component_size(*comp))
: PAGE_SIZE;
if (comps->finalized) {
g_set_error(err, PV_COMPONENT_ERROR, PV_COMPONENT_ERROR_FINALIZED,
_("Failed to add component, image is already finalized"));
return -1;
}
/* set the address of the component in the memory layout */
(*comp)->src_addr = src_addr;
g_info("%12s:\t0x%012lx (%12ld / %12ld Bytes)",
pv_component_name(*comp), pv_component_get_src_addr(*comp),
pv_component_size(*comp), pv_component_get_orig_size(*comp));
/* append the component and pass the responsibility of @comp
* to @comps
*/
comps->comps = g_slist_append(comps->comps, g_steal_pointer(comp));
comps->next_src += src_size;
g_assert(IS_PAGE_ALIGNED(comps->next_src));
g_assert(!*comp);
return 0;
}
struct stage3b_args *pv_img_comps_get_stage3b_args(const PvImgComps *comps,
struct psw_t *psw)
{
g_autofree struct stage3b_args *ret = g_new0(struct stage3b_args, 1);
for (GSList *iterator = comps->comps; iterator; iterator = iterator->next) {
const PvComponent *img_comp = iterator->data;
uint64_t src_addr, dst_size;
g_assert(img_comp);
src_addr = pv_component_get_src_addr(img_comp);
dst_size = pv_component_get_orig_size(img_comp);
g_assert(dst_size <= pv_component_size(img_comp));
switch ((PvComponentType)pv_component_type(img_comp)) {
case PV_COMP_TYPE_KERNEL:
memblob_init(&ret->kernel, src_addr, dst_size);
break;
case PV_COMP_TYPE_CMDLINE:
memblob_init(&ret->cmdline, src_addr, dst_size);
break;
case PV_COMP_TYPE_INITRD:
memblob_init(&ret->initrd, src_addr, dst_size);
break;
case PV_COMP_TYPE_STAGE3B:
/* nothing needs to be done since it is the
* stage3b itself
*/
break;
default:
g_assert_not_reached();
break;
}
}
/* for `stage3b_args` big-endian format must be used */
ret->psw.mask = GUINT64_TO_BE(psw->mask);
ret->psw.addr = GUINT64_TO_BE(psw->addr);
return g_steal_pointer(&ret);
}
gint pv_img_comps_set_offset(PvImgComps *comps, gsize offset, GError **err)
{
g_assert(IS_PAGE_ALIGNED(comps->next_src));
if (!IS_PAGE_ALIGNED(offset)) {
g_set_error(err, PV_IMAGE_ERROR, PV_IMAGE_ERROR_OFFSET,
_("Offset must be page aligned"));
return -1;
}
if (pv_img_comps_length(comps) > 0) {
g_set_error(err, PV_IMAGE_ERROR, PV_IMAGE_ERROR_OFFSET,
_("Offset cannot be changed after a component was added"));
return -1;
}
comps->next_src += offset;
g_assert(IS_PAGE_ALIGNED(comps->next_src));
return 0;
}
GSList *pv_img_comps_get_comps(const PvImgComps *comps)
{
return comps->comps;
}
gint pv_img_comps_finalize(PvImgComps *comps, Buffer **pld_digest,
Buffer **ald_digest, Buffer **tld_digest,
uint64_t *nep, GError **err)
{
g_autoptr(Buffer) tmp_pld_digest = NULL;
g_autoptr(Buffer) tmp_ald_digest = NULL;
g_autoptr(Buffer) tmp_tld_digest = NULL;
comps->finalized = TRUE;
for (GSList *iterator = comps->comps; iterator; iterator = iterator->next) {
const PvComponent *comp = iterator->data;
/* update hashes and nep */
if (pv_img_comps_hash_comp(comps, comp, err) < 0)
return -1;
}
tmp_pld_digest = digest_ctx_finalize(comps->pld, err);
if (!tmp_pld_digest)
return -1;
tmp_ald_digest = digest_ctx_finalize(comps->ald, err);
if (!tmp_ald_digest)
return -1;
tmp_tld_digest = digest_ctx_finalize(comps->tld, err);
if (!tmp_tld_digest)
return -1;
*pld_digest = g_steal_pointer(&tmp_pld_digest);
*ald_digest = g_steal_pointer(&tmp_ald_digest);
*tld_digest = g_steal_pointer(&tmp_tld_digest);
*nep = comps->nep;
return 0;
}
PvComponent *pv_img_comps_get_nth_comp(PvImgComps *comps, guint n)
{
return g_slist_nth_data(comps->comps, n);
}
+42
View File
@@ -0,0 +1,42 @@
/*
* PV components 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.
*/
#ifndef PV_COMPS_H
#define PV_COMPS_H
#include <glib.h>
#include <openssl/evp.h>
#include <stdint.h>
#include "boot/s390.h"
#include "boot/stage3b.h"
#include "utils/buffer.h"
#include "pv_comp.h"
typedef struct _pv_img_comps PvImgComps;
PvImgComps *pv_img_comps_new(const EVP_MD *ald_md, const EVP_MD *pld_md,
const EVP_MD *tld_md, GError **err);
guint pv_img_comps_length(const PvImgComps *comps);
GSList *pv_img_comps_get_comps(const PvImgComps *comps);
struct stage3b_args *pv_img_comps_get_stage3b_args(const PvImgComps *comps,
struct psw_t *psw);
gint pv_img_comps_add_component(PvImgComps *comps, PvComponent **comp,
GError **err);
PvComponent *pv_img_comps_get_nth_comp(PvImgComps *comps, guint n);
gint pv_img_comps_set_offset(PvImgComps *comps, gsize offset, GError **err);
gint pv_img_comps_finalize(PvImgComps *comps, Buffer **pld_digest,
Buffer **ald_digest, Buffer **tld_digest,
uint64_t *nep, GError **err);
void pv_img_comps_free(PvImgComps *comps);
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(PvImgComps, pv_img_comps_free)
#endif
+37
View File
@@ -0,0 +1,37 @@
/*
* PV error related 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 <glib.h>
#include "pv_error.h"
GQuark pv_error_quark(void)
{
return g_quark_from_static_string("pv-error-quark");
}
GQuark pv_crypto_error_quark(void)
{
return g_quark_from_static_string("pv-crypto-error-quark");
}
GQuark pv_component_error_quark(void)
{
return g_quark_from_static_string("pv-component-error-quark");
}
GQuark pv_image_error_quark(void)
{
return g_quark_from_static_string("pv-image-error-quark");
}
GQuark pv_parse_error_quark(void)
{
return g_quark_from_static_string("pv-parse-error-quark");
}
+62
View File
@@ -0,0 +1,62 @@
/*
* PV error 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.
*/
#ifndef PV_ERROR_H
#define PV_ERROR_H
#include <glib.h>
GQuark pv_error_quark(void);
GQuark pv_parse_error_quark(void);
GQuark pv_component_error_quark(void);
GQuark pv_crypto_error_quark(void);
GQuark pv_image_error_quark(void);
#define PV_ERROR pv_error_quark()
#define PV_PARSE_ERROR pv_parse_error_quark()
#define PV_CRYPTO_ERROR pv_crypto_error_quark()
#define PV_COMPONENT_ERROR pv_component_error_quark()
#define PV_IMAGE_ERROR pv_image_error_quark()
typedef enum {
PV_ERROR_IPIB_SIZE,
PV_ERROR_PV_HDR_SIZE,
PV_ERROR_INTERNAL,
} PvErrors;
typedef enum {
PV_PARSE_ERROR_OK = 0,
PV_PARSE_ERROR_SYNTAX,
PR_PARSE_ERROR_INVALID_ARGUMENT,
PR_PARSE_ERROR_MISSING_ARGUMENT,
} PvParseErrors;
typedef enum {
PV_COMPONENT_ERROR_UNALIGNED,
PV_COMPONENT_ERROR_FINALIZED,
} PvComponentErrors;
typedef enum {
PV_IMAGE_ERROR_OFFSET,
PV_IMAGE_ERROR_FINALIZED,
} PvImageErrors;
typedef enum {
PV_CRYPTO_ERROR_VERIFICATION,
PV_CRYPTO_ERROR_INIT,
PV_CRYPTO_ERROR_READ_CERTIFICATE,
PV_CRYPTO_ERROR_INTERNAL,
PV_CRYPTO_ERROR_DERIVE,
PV_CRYPTO_ERROR_KEYGENERATION,
PV_CRYPTO_ERROR_RANDOMIZATION,
PV_CRYPTO_ERROR_INVALID_PARM,
PV_CRYPTO_ERROR_INVALID_KEY_SIZE,
} PvCryptoErrors;
#endif
+293
View File
@@ -0,0 +1,293 @@
/*
* PV header related 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 <glib.h>
#include <glib/gtypes.h>
#include <openssl/aes.h>
#include <openssl/evp.h>
#include <stdint.h>
#include <string.h>
#include "boot/s390.h"
#include "include/pv_crypto_def.h"
#include "utils/buffer.h"
#include "utils/crypto.h"
#include "pv_comp.h"
#include "pv_hdr.h"
#include "pv_image.h"
void pv_hdr_free(PvHdr *hdr)
{
if (!hdr)
return;
g_free(hdr->optional_items);
g_free(hdr->encrypted);
g_free(hdr->slots);
g_free(hdr);
}
uint32_t pv_hdr_size(const PvHdr *hdr)
{
return GUINT32_FROM_BE(hdr->head.phs);
}
gboolean pv_hdr_uses_encryption(const PvHdr *hdr)
{
return !(GUINT64_FROM_BE(hdr->head.pcf) & PV_CFLAG_NO_DECRYPTION);
}
uint64_t pv_hdr_enc_size(const PvHdr *hdr)
{
return GUINT64_FROM_BE(hdr->head.sea);
}
uint32_t pv_hdr_enc_size_casted(const PvHdr *hdr)
{
uint64_t size = pv_hdr_enc_size(hdr);
if (size > UINT32_MAX)
g_abort();
return (uint32_t)size;
}
static guint pv_hdr_tag_size(const PvHdr *hdr)
{
return sizeof(hdr->tag);
}
uint32_t pv_hdr_aad_size(const PvHdr *hdr)
{
return pv_hdr_size(hdr) - pv_hdr_enc_size_casted(hdr) -
pv_hdr_tag_size(hdr);
}
uint64_t pv_hdr_get_nks(const PvHdr *hdr)
{
return GUINT64_FROM_BE(hdr->head.nks);
}
/* In-place modification of ``buf`` */
static gint pv_hdr_encrypt(const PvHdr *hdr, const PvImage *img, Buffer *buf,
GError **err)
{
uint32_t hdr_len = pv_hdr_size(hdr);
uint32_t aad_len = pv_hdr_aad_size(hdr);
guint tag_len = pv_hdr_tag_size(hdr);
uint32_t enc_len = pv_hdr_enc_size_casted(hdr);
const Buffer aad_part = { .data = buf->data, .size = aad_len };
Buffer enc_part = { .data = (uint8_t *)buf->data + aad_len,
.size = enc_len };
Buffer tag_part = { .data = (uint8_t *)buf->data + hdr_len - tag_len,
.size = tag_len };
struct cipher_parms parms;
int64_t c_len;
g_assert(aad_part.size + enc_part.size + tag_part.size == buf->size);
g_assert(img->cust_root_key->size <= INT_MAX);
g_assert(img->gcm_iv->size <= INT_MAX);
g_assert(EVP_CIPHER_key_length(img->gcm_cipher) ==
(int)img->cust_root_key->size);
g_assert(EVP_CIPHER_iv_length(img->gcm_cipher) == (int)img->gcm_iv->size);
parms.key = img->cust_root_key;
parms.iv_or_tweak = img->gcm_iv;
parms.cipher = img->gcm_cipher;
/* in-place encryption */
c_len = gcm_encrypt(&enc_part, &aad_part, &parms, &enc_part, &tag_part, err);
if (c_len < 0)
return -1;
g_assert(c_len == enc_len);
return 0;
}
/* Initializes the unencrypted, but integrity protected part of the PV
* header
*/
static gint pv_hdr_aad_init(PvHdr *hdr, const PvImage *img, GError **err)
{
g_autofree union ecdh_pub_key *cust_pub_key = NULL;
struct pv_hdr_key_slot *hdr_slot = hdr->slots;
struct pv_hdr_head *head = &hdr->head;
g_autoptr(Buffer) pld = NULL;
g_autoptr(Buffer) ald = NULL;
g_autoptr(Buffer) tld = NULL;
uint64_t nep = 0;
g_assert(sizeof(head->iv) == img->gcm_iv->size);
g_assert(sizeof(head->cust_pub_key) == sizeof(*cust_pub_key));
cust_pub_key = evp_pkey_to_ecdh_pub_key(img->cust_pub_priv_key, err);
if (!cust_pub_key)
return -1;
head->magic = GUINT64_TO_BE(PV_MAGIC_NUMBER);
head->version = GUINT32_TO_BE(PV_VERSION_1);
/* ``phs`` is already set so we can skip it here */
memcpy(head->iv, img->gcm_iv->data, sizeof(head->iv));
/* ``nks`` is already set so we can skip it here */
/* ``sea`` is already set so we can skip it here */
head->pcf = GUINT64_TO_BE(img->pcf);
memcpy(head->cust_pub_key.data, cust_pub_key,
sizeof(head->cust_pub_key));
if (pv_img_calc_pld_ald_tld_nep(img, &pld, &ald, &tld, &nep, err) < 0)
return -1;
g_assert(sizeof(head->pld) == pld->size);
g_assert(sizeof(head->ald) == ald->size);
g_assert(sizeof(head->tld) == tld->size);
head->nep = GUINT64_TO_BE(nep);
memcpy(head->pld, pld->data, sizeof(head->pld));
memcpy(head->ald, ald->data, sizeof(head->ald));
memcpy(head->tld, tld->data, sizeof(head->tld));
/* set the key slots */
for (GSList *iterator = img->key_slots; iterator; iterator = iterator->next) {
const PvHdrKeySlot *slot = iterator->data;
g_assert(slot);
/* the memory for the slots is pre-allocated so we
* have not to allocate and since PvHdrKeySlot is
* stored in the big-edian format we can simply use
* memcpy.
*/
memcpy(hdr_slot++, slot, sizeof(*slot));
}
return 0;
}
/* Initializes the encrypted and also integrity protected part of the
* PV header
*/
static gint pv_hdr_enc_init(PvHdr *hdr, const PvImage *img, GError **err)
{
struct pv_hdr_encrypted *enc = hdr->encrypted;
const PvComponent *stage3b;
struct psw_t psw;
g_assert(sizeof(enc->img_enc_key_1) + sizeof(enc->img_enc_key_2) ==
EVP_CIPHER_key_length(img->xts_cipher));
g_assert(sizeof(enc->cust_comm_key) == img->cust_comm_key->size);
g_assert(img->xts_key->size ==
(guint)EVP_CIPHER_key_length(img->xts_cipher));
stage3b = pv_img_get_stage3b_comp(img, err);
if (!stage3b)
return -1;
memcpy(enc->cust_comm_key, img->cust_comm_key->data,
sizeof(enc->cust_comm_key));
memcpy(enc->img_enc_key_1, img->xts_key->data,
sizeof(enc->img_enc_key_1));
memcpy(enc->img_enc_key_2,
(uint8_t *)img->xts_key->data + sizeof(enc->img_enc_key_1),
sizeof(enc->img_enc_key_2));
/* Setup program check handler */
psw.mask = GUINT64_TO_BE(DEFAULT_INITIAL_PSW_MASK);
psw.addr = GUINT64_TO_BE(pv_component_get_src_addr(stage3b));
enc->psw = psw;
enc->scf = GUINT64_TO_BE(img->scf);
enc->noi = GUINT32_TO_BE(g_slist_length(img->optional_items));
/* set the optional items */
for (GSList *iterator = img->optional_items; iterator;
iterator = iterator->next) {
const struct pv_hdr_opt_item *item = iterator->data;
g_assert(item);
/* not supported in the first version */
g_assert_not_reached();
}
return 0;
}
PvHdr *pv_hdr_new(const PvImage *img, GError **err)
{
uint32_t noi = g_slist_length(img->optional_items);
uint32_t hdr_size = pv_img_get_pv_hdr_size(img);
gsize nks = g_slist_length(img->key_slots);
uint32_t sea = pv_img_get_enc_size(img);
g_autoptr(PvHdr) ret = NULL;
g_assert(nks > 0);
/* must be a multiple of AES block size */
g_assert(sea % AES_BLOCK_SIZE == 0);
g_assert(sea >= sizeof(struct pv_hdr_encrypted));
ret = g_new0(PvHdr, 1);
ret->slots = g_new0(struct pv_hdr_key_slot, nks);
ret->head.phs = GUINT32_TO_BE(hdr_size);
ret->head.nks = GUINT64_TO_BE(nks);
ret->head.sea = GUINT64_TO_BE(sea);
ret->encrypted = g_new0(struct pv_hdr_encrypted, 1);
ret->optional_items = g_malloc0(sea - sizeof(struct pv_hdr_encrypted));
ret->encrypted->noi = GUINT32_TO_BE(noi);
if (pv_hdr_aad_init(ret, img, err) < 0)
return NULL;
if (pv_hdr_enc_init(ret, img, err) < 0)
return NULL;
return g_steal_pointer(&ret);
}
static void pv_hdr_memcpy(const PvHdr *hdr, const Buffer *dst)
{
uint64_t nks = pv_hdr_get_nks(hdr);
uint8_t *data;
g_assert(dst->size == pv_hdr_size(hdr));
g_assert(pv_hdr_enc_size_casted(hdr) >= sizeof(*hdr->encrypted));
data = memcpy(dst->data, &hdr->head, sizeof(hdr->head));
data = memcpy(data + sizeof(hdr->head), hdr->slots,
sizeof(struct pv_hdr_key_slot) * nks);
data = memcpy(data + sizeof(struct pv_hdr_key_slot) * nks,
hdr->encrypted, sizeof(*hdr->encrypted));
if (pv_hdr_enc_size_casted(hdr) - sizeof(*hdr->encrypted) > 0) {
(void)memcpy(data + sizeof(*hdr->encrypted),
hdr->optional_items,
pv_hdr_enc_size_casted(hdr) - sizeof(*hdr->encrypted));
}
}
Buffer *pv_hdr_serialize(const PvHdr *hdr, const PvImage *img,
enum PvCryptoMode mode, GError **err)
{
uint32_t hdr_size = pv_hdr_size(hdr);
g_autoptr(Buffer) ret = NULL;
ret = buffer_alloc(hdr_size);
pv_hdr_memcpy(hdr, ret);
if (mode == PV_ENCRYPT) {
/* The buffer @ret is modified in-place */
if (pv_hdr_encrypt(hdr, img, ret, err) < 0)
return NULL;
} else {
/* Simply copy the tag */
memcpy((uint8_t *)ret->data + hdr_size - pv_hdr_tag_size(hdr),
hdr->tag, pv_hdr_tag_size(hdr));
}
return g_steal_pointer(&ret);
}
+36
View File
@@ -0,0 +1,36 @@
/*
* PV header related 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.
*/
#ifndef PV_HDR_H
#define PV_HDR_H
#include <glib.h>
#include <stdint.h>
#include "boot/s390.h"
#include "include/pv_hdr_def.h"
#include "utils/crypto.h"
#include "utils/buffer.h"
#include "pv_image.h"
PvHdr *pv_hdr_new(const PvImage *img, GError **err);
void pv_hdr_free(PvHdr *hdr);
G_GNUC_UNUSED gboolean pv_hdr_uses_encryption(const PvHdr *hdr);
Buffer *pv_hdr_serialize(const PvHdr *hdr, const PvImage *img,
enum PvCryptoMode mode, GError **err);
uint32_t pv_hdr_size(const PvHdr *hdr);
uint32_t pv_hdr_aad_size(const PvHdr *hdr);
uint64_t pv_hdr_enc_size(const PvHdr *hdr);
uint32_t pv_hdr_enc_size_casted(const PvHdr *hdr);
uint64_t pv_hdr_get_nks(const PvHdr *hdr);
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(PvHdr, pv_hdr_free)
#endif
+820
View File
@@ -0,0 +1,820 @@
/*
* 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;
}
+68
View File
@@ -0,0 +1,68 @@
/*
* 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.
*/
#ifndef PV_IMAGE_H
#define PV_IMAGE_H
#include <glib.h>
#include <glib/gtypes.h>
#include <openssl/evp.h>
#include <stdint.h>
#include "boot/s390.h"
#include "utils/buffer.h"
#include "pv_args.h"
#include "pv_comp.h"
#include "pv_comps.h"
#include "pv_stage3.h"
typedef struct {
gchar *tmp_dir; /* directory used for temporary files */
Buffer *stage3a; /* stage3a containing IPIB and PV header */
gsize stage3a_bin_size; /* size of stage3a.bin */
struct psw_t stage3a_psw; /* (short) PSW that is written to
* location 0 of the created image
*/
struct psw_t initial_psw; /* PSW loaded by stage3b */
EVP_PKEY *cust_pub_priv_key; /* customer private/public key */
GSList *host_pub_keys; /* public host keys */
gint nid; /* Elliptic Curve used for the key derivation */
/* keys and cipher used for the AES-GCM encryption */
Buffer *cust_root_key;
Buffer *gcm_iv;
const EVP_CIPHER *gcm_cipher;
/* Information for the IPIB and PV header */
uint64_t pcf;
uint64_t scf;
Buffer *cust_comm_key;
const EVP_CIPHER *cust_comm_cipher;
Buffer *xts_key;
const EVP_CIPHER *xts_cipher;
GSList *key_slots;
GSList *optional_items;
PvImgComps *comps;
} PvImage;
PvImage *pv_img_new(PvArgs *args, const gchar *stage3a_path, GError **err);
void pv_img_free(PvImage *img);
gint pv_img_add_component(PvImage *img, const PvArg *arg, GError **err);
gint pv_img_finalize(PvImage *img, const gchar *stage3b_path, GError **err);
gint pv_img_calc_pld_ald_tld_nep(const PvImage *img, Buffer **pld, Buffer **ald,
Buffer **tld, uint64_t *nep, GError **err);
gint pv_img_load_and_set_stage3a(PvImage *img, const gchar *path, GError **err);
const PvComponent *pv_img_get_stage3b_comp(const PvImage *img, GError **err);
gint pv_img_add_stage3b_comp(PvImage *img, const gchar *path, GError **err);
uint32_t pv_img_get_enc_size(const PvImage *img);
uint32_t pv_img_get_pv_hdr_size(const PvImage *img);
gint pv_img_write(PvImage *img, const gchar *path, GError **err);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(PvImage, pv_img_free)
#endif
+128
View File
@@ -0,0 +1,128 @@
/*
* PV IPIB 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 <glib.h>
#include <glib/gtypes.h>
#include <stdint.h>
#include <stdio.h>
#include "boot/ipl.h"
#include "boot/s390.h"
#include "common.h"
#include "include/pv_hdr_def.h"
#include "lib/zt_common.h"
#include "utils/align.h"
#include "utils/buffer.h"
#include "pv_comp.h"
#include "pv_error.h"
#include "pv_ipib.h"
uint64_t pv_ipib_get_size(uint32_t num_comp)
{
gsize ipib_size = sizeof(struct ipl_pl_hdr) +
sizeof(struct ipl_pb0_pv) +
num_comp * sizeof(struct ipl_pb0_pv_comp);
/* the minimal size is one page */
return MAX(ipib_size, PAGE_SIZE);
}
static gint pv_ipib_init(IplParameterBlock *ipib, GSList *comps,
const Buffer *hdr)
{
g_assert(sizeof(struct ipl_pl_hdr) <= UINT32_MAX);
g_assert(sizeof(struct ipl_pb0_pv_comp) <= UINT32_MAX);
g_assert(sizeof(struct ipl_pb0_pv) <= UINT32_MAX);
g_assert(ipib);
guint comps_length = g_slist_length(comps);
uint32_t ipl_pl_hdr_size = (uint32_t)sizeof(struct ipl_pl_hdr);
struct ipl_pb0_pv *pv = &ipib->pv;
uint32_t ipib_comps_size;
uint32_t blk0_len;
uint32_t ipib_size;
gsize i;
g_assert_true(
g_uint_checked_mul(&ipib_comps_size, comps_length,
(uint32_t)sizeof(struct ipl_pb0_pv_comp)));
g_assert_true(g_uint_checked_add(&blk0_len, (uint32_t)sizeof(*pv),
ipib_comps_size));
g_assert(ipl_pl_hdr_size + blk0_len <= PAGE_SIZE);
ipib_size = MAX(ipl_pl_hdr_size + blk0_len, (uint32_t)PAGE_SIZE);
g_assert(pv_ipib_get_size(comps_length) == ipib_size);
pv->pbt = IPL_TYPE_PV;
pv->len = GUINT32_TO_BE(blk0_len);
pv->num_comp = GUINT32_TO_BE(comps_length);
/* both values will be overwritten during the IPL process by
* the stage3a loader
*/
pv->pv_hdr_addr = GUINT64_TO_BE(0x0);
pv->pv_hdr_size = GUINT64_TO_BE(hdr->size);
ipib->hdr.len = GUINT32_TO_BE(ipib_size);
ipib->hdr.version = IPL_PARM_BLOCK_VERSION;
i = 0;
for (GSList *iterator = comps; iterator; iterator = iterator->next, i++) {
const PvComponent *comp = iterator->data;
uint64_t comp_addr, comp_size;
g_assert(comp);
comp_addr = pv_component_get_src_addr(comp);
comp_size = pv_component_size(comp);
g_assert(IS_PAGE_ALIGNED(comp_size));
pv->components[i].addr = GUINT64_TO_BE(comp_addr);
pv->components[i].len = GUINT64_TO_BE(comp_size);
pv->components[i].tweak_pref =
GUINT64_TO_BE(pv_component_get_tweak_prefix(comp));
if (i > 0) {
/* tweak prefixes of the components must grow
* strictly monotonous
*/
g_assert(GUINT64_FROM_BE(pv->components[i].tweak_pref) >
GUINT64_FROM_BE(pv->components[i - 1].tweak_pref));
}
}
return 0;
}
IplParameterBlock *pv_ipib_new(GSList *comps, const Buffer *hdr, GError **err)
{
uint64_t ipib_size = pv_ipib_get_size(g_slist_length(comps));
g_autoptr(IplParameterBlock) ret = NULL;
if (ipib_size > PV_V1_IPIB_MAX_SIZE) {
g_set_error(err, PV_ERROR, PV_ERROR_IPIB_SIZE,
_("IPIB size is too large: %lu < %lu"), ipib_size,
PAGE_SIZE);
return NULL;
}
ret = g_malloc0(ipib_size);
if (pv_ipib_init(ret, comps, hdr) < 0)
return NULL;
return g_steal_pointer(&ret);
}
void pv_ipib_free(IplParameterBlock *ipib)
{
if (!ipib)
return;
g_free(ipib);
}
+27
View File
@@ -0,0 +1,27 @@
/*
* PV IPIB 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.
*/
#ifndef PV_IPIB_H
#define PV_IPIB_H
#include <glib.h>
#include <stdint.h>
#include "boot/ipl.h"
#include "utils/buffer.h"
typedef struct ipl_parameter_block IplParameterBlock;
uint64_t pv_ipib_get_size(uint32_t num_comp);
IplParameterBlock *pv_ipib_new(GSList *comps, const Buffer *hdr, GError **err);
void pv_ipib_free(IplParameterBlock *ipib);
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(IplParameterBlock, pv_ipib_free)
#endif
+26
View File
@@ -0,0 +1,26 @@
/*
* PV optional item 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 <glib.h>
#include "pv_opt_item.h"
uint32_t pv_opt_item_size(const struct pv_hdr_opt_item *item G_GNUC_UNUSED)
{
/* not implemented yet */
g_assert_not_reached();
}
void pv_opt_item_free(struct pv_hdr_opt_item *item)
{
if (!item)
return;
g_free(item);
}
+20
View File
@@ -0,0 +1,20 @@
/*
* PV optional item 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.
*/
#ifndef PV_OPT_ITEM_H
#define PV_OPT_ITEM_H
#include <stdint.h>
#include "include/pv_hdr_def.h"
uint32_t pv_opt_item_size(const struct pv_hdr_opt_item *item);
void pv_opt_item_free(struct pv_hdr_opt_item *item);
#endif
+164
View File
@@ -0,0 +1,164 @@
/*
* PV stage3 loader 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 <glib.h>
#include <stdint.h>
#include <string.h>
#include "boot/ipl.h"
#include "boot/stage3a.h"
#include "boot/stage3b.h"
#include "common.h"
#include "utils/align.h"
#include "pv_error.h"
#include "pv_stage3.h"
#define STAGE3A_ARGS(data_ptr, loader_size) \
((struct stage3a_args *)((uint64_t)data_ptr + loader_size - \
sizeof(struct stage3a_args)))
static Buffer *loader_getblob(const gchar *filename, gsize *loader_size,
gsize args_size, gsize data_size,
gboolean data_aligned, GError **err)
{
g_autoptr(GMappedFile) mapped_file = NULL;
g_autoptr(Buffer) ret = NULL;
gsize size, tmp_loader_size;
gchar *loader_data;
g_assert(loader_size);
mapped_file = g_mapped_file_new(filename, FALSE, err);
if (!mapped_file)
return NULL;
loader_data = g_mapped_file_get_contents(mapped_file);
if (!loader_data) {
g_set_error(err, G_FILE_ERROR, G_FILE_ERROR_BADF,
_("File '%s' is empty"), filename);
return NULL;
}
tmp_loader_size = g_mapped_file_get_length(mapped_file);
if (tmp_loader_size < args_size) {
g_set_error(err, G_FILE_ERROR, G_FILE_ERROR_BADF,
_("File size less than expected: %lu < %ln"),
tmp_loader_size, loader_size);
return NULL;
}
/* For example, the PV header and IPIB data must be page
* aligned.
*/
size = (data_aligned ? PAGE_ALIGN(tmp_loader_size) : tmp_loader_size) +
data_size;
ret = buffer_alloc(size);
/* copy the loader "template" */
memcpy(ret->data, loader_data, tmp_loader_size);
/* reset our dummy data (offsets and length) to zeros */
memset((uint8_t *)ret->data + tmp_loader_size - args_size, 0,
args_size);
*loader_size = tmp_loader_size;
return g_steal_pointer(&ret);
}
Buffer *stage3a_getblob(const gchar *filename, gsize *loader_size,
gsize data_size, GError **err)
{
return loader_getblob(filename, loader_size,
sizeof(struct stage3a_args), data_size, TRUE,
err);
}
/* For the memory layout see stage3a.lds */
/* Set the right offsets and sizes in the stage3a template + add
* the IPIB block with the PV header
*/
static gint stage3a_set_data(Buffer *loader, gsize loader_size,
const Buffer *hdr, struct ipl_parameter_block *ipib,
GError **err)
{
uint32_t ipib_size = GUINT32_FROM_BE(ipib->hdr.len);
gsize args_size = sizeof(struct stage3a_args);
uint32_t hdr_size = (uint32_t)hdr->size;
uint64_t args_addr, next_data_addr;
if (hdr->size > UINT32_MAX) {
g_set_error(err, PV_ERROR, PV_ERROR_INTERNAL,
_("Invalid header size: %zu"), hdr->size);
return -1;
}
/* we assume here that the loader ``stage3a`` is loaded page
* aligned in the guest
*/
args_addr = (uint64_t)loader->data + loader_size - args_size;
/* therefore `next_data_addr` is also page aligned */
next_data_addr = (uint64_t)loader->data + PAGE_ALIGN(loader_size);
/* copy IPIB data */
memcpy((void *)next_data_addr, ipib, ipib_size);
/* set IPIB offset in relation to the stage3a arguments */
STAGE3A_ARGS(loader->data, loader_size)->ipib_offs =
GUINT64_TO_BE(next_data_addr - args_addr);
next_data_addr = next_data_addr + PAGE_ALIGN(ipib_size);
/* copy PV header */
memcpy((void *)next_data_addr, hdr->data, hdr_size);
/* set PV header size and offset in relation to the stage3a
* arguments
*/
STAGE3A_ARGS(loader->data, loader_size)->hdr_offs =
GUINT64_TO_BE(next_data_addr - args_addr);
STAGE3A_ARGS(loader->data, loader_size)->hdr_size = GUINT64_TO_BE(hdr_size);
return 0;
}
gint build_stage3a(Buffer *loader, gsize loader_size, const Buffer *hdr,
struct ipl_parameter_block *ipib, GError **err)
{
return stage3a_set_data(loader, loader_size, hdr, ipib, err);
}
Buffer *stage3b_getblob(const gchar *filename, GError **err)
{
g_autoptr(Buffer) ret = NULL;
gsize rb_size;
ret = loader_getblob(filename, &rb_size, sizeof(struct stage3b_args), 0,
FALSE, err);
if (!ret)
return NULL;
g_assert(ret->size == rb_size);
return g_steal_pointer(&ret);
}
void build_stage3b(Buffer *stage3b, const struct stage3b_args *args)
{
g_assert(stage3b->size > sizeof(*args));
/* at the end of the stage3b there are the stage3b args
* positioned
*/
memcpy((uint8_t *)stage3b->data + stage3b->size - sizeof(*args), args,
sizeof(*args));
}
void memblob_init(struct memblob *arg, uint64_t src, uint64_t size)
{
arg->src = GUINT64_TO_BE(src);
arg->size = GUINT64_TO_BE(size);
}
+30
View File
@@ -0,0 +1,30 @@
/*
* PV stage3 loader 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.
*/
#ifndef PV_STAGE3_H
#define PV_STAGE3_H
#include <glib.h>
#include <glib/gtypes.h>
#include <stdint.h>
#include "boot/ipl.h"
#include "boot/s390.h"
#include "boot/stage3b.h"
#include "utils/buffer.h"
Buffer *stage3a_getblob(const gchar *filename, gsize *loader_size,
gsize data_size, GError **err);
gint build_stage3a(Buffer *dc, gsize dc_size, const Buffer *hdr,
struct ipl_parameter_block *ipib, GError **err);
Buffer *stage3b_getblob(const gchar *filename, GError **err);
void build_stage3b(Buffer *stage3b, const struct stage3b_args *args);
void memblob_init(struct memblob *arg, uint64_t src, uint64_t size);
#endif