From nobody Wed Mar 5 18:01:27 2025 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1741092858; cv=none; d=zohomail.com; s=zohoarc; b=iCYT+oq4JR8Hos1KJqca/3+3p5Ik4K1VTvONijRL3vEj5zab8MS9E5KcgOsGpYnJL5UtJq2sBccc7MgqwISptjWJ8AF+I9FlOArs3A1JqkrfxKHtqDYlh/CZORC/sy8ByV7xidtSy75SSigYMvtrdDXtGOzdumjM4hXKhvDsVt0= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1741092858; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=eSA53h4Tq1ld+EUgLayekMHRu0q1zKVUKG7i9WfYbL0=; b=TVSw3tBgTWGERErwTz5cpE77HPuTVbm6uvQmKe/T199xMi9pCDNS6GBf68HxE5CAFe1I8fncxni1sr//CE1iryzliEz+iUH7NDUQWV8pSIMQHFdQvIHRkAqZrqoG5nU/qtiBhI4fSii3tnEhXhB+Q4Im4rzUiCYfyA1+S/ptC8s= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1741092858485599.8764059796961; Tue, 4 Mar 2025 04:54:18 -0800 (PST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1tpRmR-0003dk-14; Tue, 04 Mar 2025 07:53:51 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1tpRhu-0004wl-Fc for qemu-devel@nongnu.org; Tue, 04 Mar 2025 07:49:13 -0500 Received: from us-smtp-delivery-124.mimecast.com ([170.10.129.124]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1tpRhr-0007VM-9S for qemu-devel@nongnu.org; Tue, 04 Mar 2025 07:49:09 -0500 Received: from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-593-R3YktBr-NUe4Q2yq612kWw-1; Tue, 04 Mar 2025 07:48:48 -0500 Received: from mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.40]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id E2F051800873; Tue, 4 Mar 2025 12:48:46 +0000 (UTC) Received: from sirius.home.kraxel.org (unknown [10.44.32.122]) by mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id C565219560AA; Tue, 4 Mar 2025 12:48:45 +0000 (UTC) Received: by sirius.home.kraxel.org (Postfix, from userid 1000) id 2631818003BB; Tue, 04 Mar 2025 13:48:16 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741092545; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=eSA53h4Tq1ld+EUgLayekMHRu0q1zKVUKG7i9WfYbL0=; b=fXFfBe1gLaz2JfJKjTBoQmY7MRyqnTW/H5sSER+kqwae0K/mpektldxF7QRuLR9RQNVxC+ xejJ4DMlu9QDyBakrNTQ4v8O8HqPEWEPD3Hpqyu2I0zrMWywfjTI6oy8tMvZFFAcCkfNJe kYQfHeDayFdP7VOwQEeW6y0cBdM1RUI= X-MC-Unique: R3YktBr-NUe4Q2yq612kWw-1 X-Mimecast-MFC-AGG-ID: R3YktBr-NUe4Q2yq612kWw_1741092527 From: Gerd Hoffmann To: qemu-devel@nongnu.org Cc: Richard Henderson , Marcel Apfelbaum , "Michael S. Tsirkin" , Eric Blake , Paolo Bonzini , Gerd Hoffmann , Peter Maydell , qemu-arm@nongnu.org, Michael Roth , Markus Armbruster , Eduardo Habkost , =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= , =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= , =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= Subject: [PULL 14/24] hw/uefi: add var-service-json.c + qapi for NV vars. Date: Tue, 4 Mar 2025 13:48:02 +0100 Message-ID: <20250304124815.591749-15-kraxel@redhat.com> In-Reply-To: <20250304124815.591749-1-kraxel@redhat.com> References: <20250304124815.591749-1-kraxel@redhat.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.0 on 10.30.177.40 Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=170.10.129.124; envelope-from=kraxel@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.001, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H5=0.001, RCVD_IN_MSPIKE_WL=0.001, RCVD_IN_VALIDITY_RPBL_BLOCKED=0.001, RCVD_IN_VALIDITY_SAFE_BLOCKED=0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=unavailable autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @redhat.com) X-ZM-MESSAGEID: 1741092860562019000 Content-Type: text/plain; charset="utf-8" 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 Signed-off-by: Gerd Hoffmann Message-ID: <20250225163031.1409078-15-kraxel@redhat.com> [ incremental fix squashed in ] Message-ID: --- hw/uefi/var-service-json.c | 243 +++++++++++++++++++++++++++++++++++++ qapi/meson.build | 1 + qapi/qapi-schema.json | 1 + qapi/uefi.json | 64 ++++++++++ 4 files changed, 309 insertions(+) create mode 100644 hw/uefi/var-service-json.c create mode 100644 qapi/uefi.json diff --git a/hw/uefi/var-service-json.c b/hw/uefi/var-service-json.c new file mode 100644 index 000000000000..761082c11fc1 --- /dev/null +++ b/hw/uefi/var-service-json.c @@ -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[] =3D { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', + }; + uint8_t *src =3D data; + char *dest; + size_t i; + + dest =3D g_malloc(len * 2 + 1); + for (i =3D 0; i < len * 2;) { + dest[i++] =3D hex[*src >> 4]; + dest[i++] =3D hex[*src & 15]; + src++; + } + dest[i++] =3D 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 =3D g_new0(UefiVarStore, 1); + vs->version =3D 2; + tail =3D &vs->variables; + + QTAILQ_FOREACH(var, &uv->variables, next) { + if (!(var->attributes & EFI_VARIABLE_NON_VOLATILE)) { + continue; + } + + v =3D g_new0(UefiVariable, 1); + be =3D qemu_uuid_bswap(var->guid); + v->guid =3D qemu_uuid_unparse_strdup(&be); + v->name =3D uefi_ucs2_to_ascii(var->name, var->name_size); + v->attr =3D var->attributes; + + v->data =3D generate_hexstr(var->data, var->data_size); + + if (var->attributes & + EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) { + v->time =3D generate_hexstr(&var->time, sizeof(var->time)); + if (var->digest && var->digest_size) { + v->digest =3D generate_hexstr(var->digest, var->digest_siz= e); + } + } + + 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 =3D dest; + size_t i; + + for (i =3D 0; i < len; i +=3D 2) { + *(data++) =3D + 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 =3D vs->variables; item !=3D NULL; item =3D item->next) { + v =3D item->value; + + var =3D g_new0(uefi_variable, 1); + var->attributes =3D v->attr; + qemu_uuid_parse(v->guid, &be); + var->guid =3D qemu_uuid_bswap(be); + + len =3D strlen(v->name); + var->name_size =3D len * 2 + 2; + var->name =3D g_malloc(var->name_size); + for (i =3D 0; i <=3D len; i++) { + var->name[i] =3D v->name[i]; + } + + len =3D strlen(v->data); + var->data_size =3D len / 2; + var->data =3D data =3D g_malloc(var->data_size); + parse_hexstr(var->data, v->data, len); + + if (v->time && strlen(v->time) =3D=3D 32) { + parse_hexstr(&var->time, v->time, 32); + } + + if (v->digest) { + len =3D strlen(v->digest); + var->digest_size =3D len / 2; + var->digest =3D 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 =3D uefi_vars_to_qapi(uv); + QObject *qobj =3D NULL; + Visitor *v; + GString *gstr; + + v =3D 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 =3D 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 =3D qemu_create(uv->jsonfile, O_RDWR, 0666, errp); + } +} + +void uefi_vars_json_save(uefi_vars_state *uv) +{ + GString *gstr; + int rc; + + if (uv->jsonfd =3D=3D -1) { + return; + } + + gstr =3D uefi_vars_to_json(uv); + + lseek(uv->jsonfd, 0, SEEK_SET); + rc =3D ftruncate(uv->jsonfd, 0); + if (rc !=3D 0) { + warn_report("%s: ftruncate error", __func__); + } + rc =3D write(uv->jsonfd, gstr->str, gstr->len); + if (rc !=3D 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 =3D=3D -1) { + return; + } + + len =3D lseek(uv->jsonfd, 0, SEEK_END); + if (len =3D=3D 0) { + return; + } + + str =3D g_malloc(len + 1); + lseek(uv->jsonfd, 0, SEEK_SET); + rc =3D read(uv->jsonfd, str, len); + if (rc !=3D len) { + warn_report("%s: read error", __func__); + } + str[len] =3D 0; + + qobj =3D qobject_from_json(str, errp); + v =3D 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); +} diff --git a/qapi/meson.build b/qapi/meson.build index e7bc54e5d047..eadde4db307f 100644 --- a/qapi/meson.build +++ b/qapi/meson.build @@ -65,6 +65,7 @@ if have_system 'pci', 'rocker', 'tpm', + 'uefi', ] endif if have_system or have_tools diff --git a/qapi/qapi-schema.json b/qapi/qapi-schema.json index b1581988e4eb..2877aff73d0c 100644 --- a/qapi/qapi-schema.json +++ b/qapi/qapi-schema.json @@ -81,3 +81,4 @@ { 'include': 'vfio.json' } { 'include': 'cryptodev.json' } { 'include': 'cxl.json' } +{ 'include': 'uefi.json' } diff --git a/qapi/uefi.json b/qapi/uefi.json new file mode 100644 index 000000000000..bdfcabe1df4d --- /dev/null +++ b/qapi/uefi.json @@ -0,0 +1,64 @@ +# -*- Mode: Python -*- +# vim: filetype=3Dpython +# + +## +# =3D 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' ] }} --=20 2.48.1