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