/* * 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); }