Browse Source
Define qapi schema for the uefi variable store state. Use it and the generated visitor helper functions to store persistent (EFI_VARIABLE_NON_VOLATILE) variables in JSON format on disk. Acked-by: Markus Armbruster <armbru@redhat.com> Signed-off-by: Gerd Hoffmann <kraxel@redhat.com> Message-ID: <20250225163031.1409078-15-kraxel@redhat.com> [ incremental fix squashed in ] Message-ID: <pji24p6oag7cn2rovus7rquo7q2c6tokuquobfro2sqorky7vu@tk7cxud6jw7f>pull/291/head
4 changed files with 309 additions and 0 deletions
@ -0,0 +1,243 @@ |
|||
/*
|
|||
* SPDX-License-Identifier: GPL-2.0-or-later |
|||
* |
|||
* uefi vars device - serialize non-volatile varstore from/to json, |
|||
* using qapi |
|||
* |
|||
* tools which can read/write these json files: |
|||
* - https://gitlab.com/kraxel/virt-firmware
|
|||
* - https://github.com/awslabs/python-uefivars
|
|||
*/ |
|||
#include "qemu/osdep.h" |
|||
#include "qemu/cutils.h" |
|||
#include "qemu/error-report.h" |
|||
#include "system/dma.h" |
|||
|
|||
#include "hw/uefi/var-service.h" |
|||
|
|||
#include "qobject/qobject.h" |
|||
#include "qobject/qjson.h" |
|||
|
|||
#include "qapi/dealloc-visitor.h" |
|||
#include "qapi/qobject-input-visitor.h" |
|||
#include "qapi/qobject-output-visitor.h" |
|||
#include "qapi/qapi-types-uefi.h" |
|||
#include "qapi/qapi-visit-uefi.h" |
|||
|
|||
static char *generate_hexstr(void *data, size_t len) |
|||
{ |
|||
static const char hex[] = { |
|||
'0', '1', '2', '3', '4', '5', '6', '7', |
|||
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f', |
|||
}; |
|||
uint8_t *src = data; |
|||
char *dest; |
|||
size_t i; |
|||
|
|||
dest = g_malloc(len * 2 + 1); |
|||
for (i = 0; i < len * 2;) { |
|||
dest[i++] = hex[*src >> 4]; |
|||
dest[i++] = hex[*src & 15]; |
|||
src++; |
|||
} |
|||
dest[i++] = 0; |
|||
|
|||
return dest; |
|||
} |
|||
|
|||
static UefiVarStore *uefi_vars_to_qapi(uefi_vars_state *uv) |
|||
{ |
|||
UefiVarStore *vs; |
|||
UefiVariableList **tail; |
|||
UefiVariable *v; |
|||
QemuUUID be; |
|||
uefi_variable *var; |
|||
|
|||
vs = g_new0(UefiVarStore, 1); |
|||
vs->version = 2; |
|||
tail = &vs->variables; |
|||
|
|||
QTAILQ_FOREACH(var, &uv->variables, next) { |
|||
if (!(var->attributes & EFI_VARIABLE_NON_VOLATILE)) { |
|||
continue; |
|||
} |
|||
|
|||
v = g_new0(UefiVariable, 1); |
|||
be = qemu_uuid_bswap(var->guid); |
|||
v->guid = qemu_uuid_unparse_strdup(&be); |
|||
v->name = uefi_ucs2_to_ascii(var->name, var->name_size); |
|||
v->attr = var->attributes; |
|||
|
|||
v->data = generate_hexstr(var->data, var->data_size); |
|||
|
|||
if (var->attributes & |
|||
EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) { |
|||
v->time = generate_hexstr(&var->time, sizeof(var->time)); |
|||
if (var->digest && var->digest_size) { |
|||
v->digest = generate_hexstr(var->digest, var->digest_size); |
|||
} |
|||
} |
|||
|
|||
QAPI_LIST_APPEND(tail, v); |
|||
} |
|||
return vs; |
|||
} |
|||
|
|||
static unsigned parse_hexchar(char c) |
|||
{ |
|||
switch (c) { |
|||
case '0' ... '9': return c - '0'; |
|||
case 'a' ... 'f': return c - 'a' + 0xa; |
|||
case 'A' ... 'F': return c - 'A' + 0xA; |
|||
default: return 0; |
|||
} |
|||
} |
|||
|
|||
static void parse_hexstr(void *dest, char *src, int len) |
|||
{ |
|||
uint8_t *data = dest; |
|||
size_t i; |
|||
|
|||
for (i = 0; i < len; i += 2) { |
|||
*(data++) = |
|||
parse_hexchar(src[i]) << 4 | |
|||
parse_hexchar(src[i + 1]); |
|||
} |
|||
} |
|||
|
|||
static void uefi_vars_from_qapi(uefi_vars_state *uv, UefiVarStore *vs) |
|||
{ |
|||
UefiVariableList *item; |
|||
UefiVariable *v; |
|||
QemuUUID be; |
|||
uefi_variable *var; |
|||
uint8_t *data; |
|||
size_t i, len; |
|||
|
|||
for (item = vs->variables; item != NULL; item = item->next) { |
|||
v = item->value; |
|||
|
|||
var = g_new0(uefi_variable, 1); |
|||
var->attributes = v->attr; |
|||
qemu_uuid_parse(v->guid, &be); |
|||
var->guid = qemu_uuid_bswap(be); |
|||
|
|||
len = strlen(v->name); |
|||
var->name_size = len * 2 + 2; |
|||
var->name = g_malloc(var->name_size); |
|||
for (i = 0; i <= len; i++) { |
|||
var->name[i] = v->name[i]; |
|||
} |
|||
|
|||
len = strlen(v->data); |
|||
var->data_size = len / 2; |
|||
var->data = data = g_malloc(var->data_size); |
|||
parse_hexstr(var->data, v->data, len); |
|||
|
|||
if (v->time && strlen(v->time) == 32) { |
|||
parse_hexstr(&var->time, v->time, 32); |
|||
} |
|||
|
|||
if (v->digest) { |
|||
len = strlen(v->digest); |
|||
var->digest_size = len / 2; |
|||
var->digest = g_malloc(var->digest_size); |
|||
parse_hexstr(var->digest, v->digest, len); |
|||
} |
|||
|
|||
QTAILQ_INSERT_TAIL(&uv->variables, var, next); |
|||
} |
|||
} |
|||
|
|||
static GString *uefi_vars_to_json(uefi_vars_state *uv) |
|||
{ |
|||
UefiVarStore *vs = uefi_vars_to_qapi(uv); |
|||
QObject *qobj = NULL; |
|||
Visitor *v; |
|||
GString *gstr; |
|||
|
|||
v = qobject_output_visitor_new(&qobj); |
|||
if (visit_type_UefiVarStore(v, NULL, &vs, NULL)) { |
|||
visit_complete(v, &qobj); |
|||
} |
|||
visit_free(v); |
|||
qapi_free_UefiVarStore(vs); |
|||
|
|||
gstr = qobject_to_json_pretty(qobj, true); |
|||
qobject_unref(qobj); |
|||
|
|||
return gstr; |
|||
} |
|||
|
|||
void uefi_vars_json_init(uefi_vars_state *uv, Error **errp) |
|||
{ |
|||
if (uv->jsonfile) { |
|||
uv->jsonfd = qemu_create(uv->jsonfile, O_RDWR, 0666, errp); |
|||
} |
|||
} |
|||
|
|||
void uefi_vars_json_save(uefi_vars_state *uv) |
|||
{ |
|||
GString *gstr; |
|||
int rc; |
|||
|
|||
if (uv->jsonfd == -1) { |
|||
return; |
|||
} |
|||
|
|||
gstr = uefi_vars_to_json(uv); |
|||
|
|||
lseek(uv->jsonfd, 0, SEEK_SET); |
|||
rc = ftruncate(uv->jsonfd, 0); |
|||
if (rc != 0) { |
|||
warn_report("%s: ftruncate error", __func__); |
|||
} |
|||
rc = write(uv->jsonfd, gstr->str, gstr->len); |
|||
if (rc != gstr->len) { |
|||
warn_report("%s: write error", __func__); |
|||
} |
|||
fsync(uv->jsonfd); |
|||
|
|||
g_string_free(gstr, true); |
|||
} |
|||
|
|||
void uefi_vars_json_load(uefi_vars_state *uv, Error **errp) |
|||
{ |
|||
UefiVarStore *vs; |
|||
QObject *qobj; |
|||
Visitor *v; |
|||
char *str; |
|||
size_t len; |
|||
int rc; |
|||
|
|||
if (uv->jsonfd == -1) { |
|||
return; |
|||
} |
|||
|
|||
len = lseek(uv->jsonfd, 0, SEEK_END); |
|||
if (len == 0) { |
|||
return; |
|||
} |
|||
|
|||
str = g_malloc(len + 1); |
|||
lseek(uv->jsonfd, 0, SEEK_SET); |
|||
rc = read(uv->jsonfd, str, len); |
|||
if (rc != len) { |
|||
warn_report("%s: read error", __func__); |
|||
} |
|||
str[len] = 0; |
|||
|
|||
qobj = qobject_from_json(str, errp); |
|||
v = qobject_input_visitor_new(qobj); |
|||
visit_type_UefiVarStore(v, NULL, &vs, errp); |
|||
visit_free(v); |
|||
|
|||
if (!(*errp)) { |
|||
uefi_vars_from_qapi(uv, vs); |
|||
uefi_vars_update_storage(uv); |
|||
} |
|||
|
|||
qapi_free_UefiVarStore(vs); |
|||
qobject_unref(qobj); |
|||
g_free(str); |
|||
} |
|||
@ -0,0 +1,64 @@ |
|||
# -*- Mode: Python -*- |
|||
# vim: filetype=python |
|||
# |
|||
|
|||
## |
|||
# = UEFI Variable Store |
|||
# |
|||
# The qemu efi variable store implementation (hw/uefi/) uses this to |
|||
# store non-volatile variables in json format on disk. |
|||
# |
|||
# This is an existing format already supported by (at least) two other |
|||
# projects, specifically https://gitlab.com/kraxel/virt-firmware and |
|||
# https://github.com/awslabs/python-uefivars. |
|||
## |
|||
|
|||
## |
|||
# @UefiVariable: |
|||
# |
|||
# UEFI Variable. Check the UEFI specifification for more detailed |
|||
# information on the fields. |
|||
# |
|||
# @guid: variable namespace GUID |
|||
# |
|||
# @name: variable name, in UTF-8 encoding. |
|||
# |
|||
# @attr: variable attributes. |
|||
# |
|||
# @data: variable value, encoded as hex string. |
|||
# |
|||
# @time: variable modification time. EFI_TIME struct, encoded as hex |
|||
# string. Used only for authenticated variables, where the |
|||
# EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS attribute bit |
|||
# is set. |
|||
# |
|||
# @digest: variable certificate digest. Used to verify the signature |
|||
# of updates for authenticated variables. UEFI has two kinds of |
|||
# authenticated variables. The secure boot variables ('PK', |
|||
# 'KEK', 'db' and 'dbx') have hard coded signature checking rules. |
|||
# For other authenticated variables the firmware stores a digest |
|||
# of the signing certificate at variable creation time, and any |
|||
# updates must be signed with the same certificate. |
|||
# |
|||
# Since: 10.0 |
|||
## |
|||
{ 'struct' : 'UefiVariable', |
|||
'data' : { 'guid' : 'str', |
|||
'name' : 'str', |
|||
'attr' : 'int', |
|||
'data' : 'str', |
|||
'*time' : 'str', |
|||
'*digest' : 'str'}} |
|||
|
|||
## |
|||
# @UefiVarStore: |
|||
# |
|||
# @version: currently always 2 |
|||
# |
|||
# @variables: list of UEFI variables |
|||
# |
|||
# Since: 10.0 |
|||
## |
|||
{ 'struct' : 'UefiVarStore', |
|||
'data' : { 'version' : 'int', |
|||
'variables' : [ 'UefiVariable' ] }} |
|||
Loading…
Reference in new issue