/* * 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 #include #include #include #include #include #include #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 PvBuffer *buf, GError **err) { g_assert(buf); g_autoptr(PvBuffer) dup_buf = pv_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: pv_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(PvBuffer) enc_buf = NULL; if (!(IS_PAGE_ALIGNED(pv_component_size(component)))) { g_autoptr(PvBuffer) new = NULL; /* create a page aligned copy */ new = pv_buffer_dup(component->buf, TRUE); pv_buffer_clear(&component->buf); component->buf = g_steal_pointer(&new); } enc_buf = encrypt_buf(parms, component->buf, err); if (!enc_buf) return -1; pv_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(PvBuffer) buf = NULL; buf = pv_buffer_dup(component->buf, TRUE); pv_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(); } 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); if (EVP_DigestUpdate(ctx, &cur_be, sizeof(cur_be)) != 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 PvBuffer *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")); return -1; } 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")); return -1; } 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")); return -1; } 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 PvBuffer *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(); }