summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergii Dmytruk <sergii.dmytruk@3mdeb.com>2023-11-17 19:31:20 +0200
committerMartin L Roth <gaumless@gmail.com>2024-03-09 23:22:55 +0000
commit04bd9651435843ce4b03c9717f2965fe344fe5cc (patch)
tree0193502b26818c738206a60862c689485d533a50
parent7a51acfbe91c7f9d01837103341526abb6ea46f4 (diff)
util: add smmstoretool for editing SMMSTORE
Offline SMMSTORE variable modification tool. Can be used to pre-configure ROM image or debug EFI state stored in a dump. Change-Id: I6c1c06f1d0c39c13b5be76a3070f09b715aca6e0 Signed-off-by: Sergii Dmytruk <sergii.dmytruk@3mdeb.com> Reviewed-on: https://review.coreboot.org/c/coreboot/+/79080 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Michał Żygowski <michal.zygowski@3mdeb.com> Reviewed-by: Arthur Heymans <arthur@aheymans.xyz>
-rw-r--r--Documentation/util.md2
-rw-r--r--Documentation/util/smmstoretool/index.md126
-rw-r--r--util/README.md1
-rw-r--r--util/smmstoretool/.gitignore1
-rw-r--r--util/smmstoretool/Makefile56
-rw-r--r--util/smmstoretool/data.c180
-rw-r--r--util/smmstoretool/data.h26
-rw-r--r--util/smmstoretool/description.md1
-rw-r--r--util/smmstoretool/fv.c175
-rw-r--r--util/smmstoretool/fv.h19
-rw-r--r--util/smmstoretool/guids.c89
-rw-r--r--util/smmstoretool/guids.h25
-rw-r--r--util/smmstoretool/main.c487
-rw-r--r--util/smmstoretool/storage.c73
-rw-r--r--util/smmstoretool/storage.h22
-rw-r--r--util/smmstoretool/udk2017.h30
-rw-r--r--util/smmstoretool/utils.c111
-rw-r--r--util/smmstoretool/utils.h29
-rw-r--r--util/smmstoretool/vs.c232
-rw-r--r--util/smmstoretool/vs.h44
-rw-r--r--util/util_readme/post_util.md1
21 files changed, 1730 insertions, 0 deletions
diff --git a/Documentation/util.md b/Documentation/util.md
index b4ebc92263..fe460d9155 100644
--- a/Documentation/util.md
+++ b/Documentation/util.md
@@ -144,6 +144,7 @@ from the local git repository for auditing or release `Bash`
Does not show variants. `Shell`
* _ucode_h_to_bin.sh_ - Microcode conversion tool `Bash`
* _update_submodules_ - Check all submodules for updates `Bash`
+* __smmstoretool__ - Offline SMMSTORE variable modification tool `C`
* __spdtool__ - Dumps SPD ROMs from a given blob to separate files
using known patterns and reserved bits. Useful for analysing firmware
that holds SPDs on boards that have soldered down DRAM. `python`
@@ -171,6 +172,7 @@ the documentation `Bash`
* [cbfstool](util/cbfstool/index.md)
* [ifdtool](util/ifdtool/index.md)
* [intelp2m](util/intelp2m/index.md)
+* [smmstoretool](util/smmstoretool/index.md)
## Generated documentation
diff --git a/Documentation/util/smmstoretool/index.md b/Documentation/util/smmstoretool/index.md
new file mode 100644
index 0000000000..f7af60579d
--- /dev/null
+++ b/Documentation/util/smmstoretool/index.md
@@ -0,0 +1,126 @@
+# smmstoretool
+
+Offline SMMSTORE variable modification tool.
+
+## Operation
+
+### Storage initialization
+
+If SMMSTORE presence isn't detected and an update operation is requested, the
+store spanning the whole file is created automatically. Size of the store file
+must be a multiple of 64 KiB (block size in version 2 of SMMSTORE protocol),
+the variable storage itself will be 64 KiB in size. That's the way EDK2 makes
+use of it.
+
+Unlike online editing which mostly appends new variable entries each storage
+update with this tool drops all deleted or incomplete entries.
+
+### Unicode
+
+There is no actual support for it. ASCII bytes (or UTF-8 bytes if that was
+passed in) is just extended to 16 bits. And Unicode chars that are larger than
+8 bit are turned into `?`. Need UTF-8 to/from UTF-16 conversion functions for
+proper support.
+
+## Help
+
+Start with:
+
+```
+$ smmstoretool -h
+Usage: smmstoretool smm-store-file sub-command
+ smmstoretool -h|--help
+
+Sub-commands:
+ * get - display current value of a variable
+ * guids - show GUID to alias mapping
+ * help - provide built-in help
+ * list - list variables present in the store
+ * remove - remove a variable from the store
+ * set - add or updates a variable in the store
+```
+
+Then run `smmstoretool rom help sub-command-name` to get more details.
+
+## Data types
+
+EFI variables in the storage don't have an associated data type and it needs to
+be specified on reading/writing variables. Several basic types that correspond
+to typical configuration values are supported:
+
+ * `bool` (`true`, `false`)
+ * `uint8` (0-255)
+ * `uint16` (0-65535)
+ * `uint32` (0-4294967295)
+ * `ascii` (NUL-terminated)
+ * `unicode` (widened and NUL-terminated)
+ * `raw` (output only; raw bytes on output)
+
+## Examples
+
+`SMMSTORE` is the name of a file containing SMMSTORE data.
+
+### Variable listing
+
+```
+$ smmstoretool SMMSTORE list
+dasharo :NetworkBoot (1 byte)
+c076ec0c-7028-4399-a07271ee5c448b9f:CustomMode (1 byte)
+d9bee56e-75dc-49d9-b4d7b534210f637a:certdb (4 bytes)
+9073e4e0-60ec-4b6e-99034c223c260f3c:VendorKeysNv (1 byte)
+6339d487-26ba-424b-9a5d687e25d740bc:TCG2_DEVICE_DETECTION (1 byte)
+6339d487-26ba-424b-9a5d687e25d740bc:TCG2_CONFIGURATION (1 byte)
+6339d487-26ba-424b-9a5d687e25d740bc:TCG2_VERSION (16 bytes)
+global :Boot0000 (66 bytes)
+global :Timeout (2 bytes)
+global :PlatformLang (3 bytes)
+global :Lang (4 bytes)
+global :Key0000 (14 bytes)
+global :Boot0001 (102 bytes)
+global :Key0001 (14 bytes)
+04b37fe8-f6ae-480b-bdd537d98c5e89aa:VarErrorFlag (1 byte)
+dasharo :Type1UUID (16 bytes)
+dasharo :Type2SN (10 bytes)
+global :Boot0002 (90 bytes)
+global :BootOrder (8 bytes)
+global :Boot0003 (76 bytes)
+...
+```
+
+### Variable reading
+
+```
+$ smmstoretool SMMSTORE get -g dasharo -n UsbDriverStack -t bool
+false
+```
+
+### Variable writing
+
+```
+$ smmstoretool SMMSTORE set -g dasharo -n UsbDriverStack -t bool -v true
+```
+
+### Variable deletion
+
+```
+$ smmstoretool SMMSTORE remove -g dasharo -n NetworkBoot
+```
+
+### Real-world usage
+
+If one edits a newly generated Dasharo `coreboot.rom`:
+
+```bash
+cbfstool coreboot.rom read -r SMMSTORE -f SMMSTORE
+smmstoretool SMMSTORE set -g dasharo -n NetworkBoot -t bool -v true
+cbfstool coreboot.rom write -r SMMSTORE -f SMMSTORE
+```
+
+On the first boot, "Network Boot" setting will already be enabled.
+
+Can also read SMMSTORE from a flash and examine some of its contents for
+debugging purposes without adding new logging code or powering on the hardware:
+
+```bash
+smmstoretool SMMSTORE get -g global -n BootOrder -t raw | hexdump -C
+```
diff --git a/util/README.md b/util/README.md
index 51c4068a6c..ec1c6bf7ce 100644
--- a/util/README.md
+++ b/util/README.md
@@ -133,6 +133,7 @@ from the local git repository for auditing or release `Bash`
Does not show variants. `Shell`
* _ucode_h_to_bin.sh_ - Microcode conversion tool `Bash`
* _update_submodules_ - Check all submodules for updates `Bash`
+* __smmstoretool__ - Offline SMMSTORE variable modification tool `C`
* __spdtool__ - Dumps SPD ROMs from a given blob to separate files
using known patterns and reserved bits. Useful for analysing firmware
that holds SPDs on boards that have soldered down DRAM. `python`
diff --git a/util/smmstoretool/.gitignore b/util/smmstoretool/.gitignore
new file mode 100644
index 0000000000..0cc355ef3e
--- /dev/null
+++ b/util/smmstoretool/.gitignore
@@ -0,0 +1 @@
+smmstoretool
diff --git a/util/smmstoretool/Makefile b/util/smmstoretool/Makefile
new file mode 100644
index 0000000000..78b77caa2d
--- /dev/null
+++ b/util/smmstoretool/Makefile
@@ -0,0 +1,56 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+PRG := smmstoretool
+TOP ?= $(abspath ../..)
+ROOT := $(TOP)/src
+MDE := $(ROOT)/vendorcode/intel/edk2/UDK2017/MdePkg/Include/
+
+CC ?= $(CROSS_COMPILE)gcc
+HOSTCC ?= $(CC)
+INSTALL ?= /usr/bin/env install
+PREFIX ?= /usr/local
+
+HOSTCFLAGS ?= $(CFLAGS)
+HOSTCFLAGS += -Wall -Wextra -MMD -MP -O3
+HOSTCFLAGS += -I $(ROOT)/commonlib/bsd/include
+HOSTCFLAGS += -I $(ROOT)/vendorcode/intel/edk2/
+HOSTCFLAGS += -I $(MDE)
+
+HOSTLDFLAGS ?= $(LDFLAGS)
+
+MACHINE := $(shell uname -m)
+ifeq ($(MACHINE),x86_64)
+ HOSTCFLAGS += -I $(MDE)/X64
+else ifeq ($(patsubst i%86,Ia32,$(MACHINE)),Ia32)
+ HOSTCFLAGS += -I $(MDE)/Ia32
+else
+ $(error Unsupported machine: '$(MACHINE)')
+endif
+
+SRC := data.c fv.c guids.c main.c storage.c utils.c vs.c
+
+OBJ := $(SRC:.c=.o)
+DEP := $(SRC:.c=.o.d)
+
+.PHONY: all debug clean install
+
+all: $(PRG)
+
+debug: HOSTCFLAGS += -O0 -g
+debug: HOSTLDFLAGS += -g
+debug: all
+
+install: $(PRG)
+ $(INSTALL) -d $(DESTDIR)$(PREFIX)/bin/
+ $(INSTALL) $^ $(DESTDIR)$(PREFIX)/bin/
+
+clean:
+ -$(RM) $(PRG) $(OBJ) $(DEP)
+
+$(PRG): $(OBJ)
+ $(HOSTCC) -o $@ $^ $(HOSTLDFLAGS)
+
+%.o: %.c
+ $(HOSTCC) $(HOSTCFLAGS) -c -o $@ -MF $@.d $<
+
+-include $(DEP)
diff --git a/util/smmstoretool/data.c b/util/smmstoretool/data.c
new file mode 100644
index 0000000000..778bf8a6cb
--- /dev/null
+++ b/util/smmstoretool/data.c
@@ -0,0 +1,180 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "data.h"
+
+#include <ctype.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "utils.h"
+
+void print_data(const uint8_t data[], size_t data_size, enum data_type type)
+{
+ if (data_size == 0)
+ return;
+
+ switch (type) {
+ case DATA_TYPE_BOOL:
+ bool value = false;
+ for (size_t i = 0; i < data_size; ++i) {
+ if (data[i] != 0) {
+ value = true;
+ break;
+ }
+ }
+ printf("%s\n", value ? "true" : "false");
+ break;
+ case DATA_TYPE_UINT8:
+ if (data_size != 1) {
+ fprintf(stderr,
+ "warning: expected size of 1, got %zu\n",
+ data_size);
+ }
+
+ if (data_size >= 1)
+ printf("%u\n", *(uint8_t *)data);
+ break;
+ case DATA_TYPE_UINT16:
+ if (data_size != 2) {
+ fprintf(stderr,
+ "warning: expected size of 2, got %zu\n",
+ data_size);
+ }
+
+ if (data_size >= 2)
+ printf("%u\n", *(uint16_t *)data);
+ break;
+ case DATA_TYPE_UINT32:
+ if (data_size != 4) {
+ fprintf(stderr,
+ "warning: expected size of 4, got %zu\n",
+ data_size);
+ }
+
+ if (data_size >= 4)
+ printf("%u\n", *(uint32_t *)data);
+ break;
+ case DATA_TYPE_ASCII:
+ for (size_t i = 0; i < data_size; ++i) {
+ char c = data[i];
+ if (isprint(c))
+ printf("%c", c);
+ }
+ printf("\n");
+ break;
+ case DATA_TYPE_UNICODE:
+ char *chars = to_chars((const CHAR16 *)data, data_size);
+ printf("%s\n", chars);
+ free(chars);
+ break;
+ case DATA_TYPE_RAW:
+ fwrite(data, 1, data_size, stdout);
+ break;
+ }
+}
+
+static uint64_t parse_uint(const char source[],
+ const char type[],
+ unsigned long long max)
+{
+ char *end;
+ unsigned long long uint = strtoull(source, &end, /*base=*/0);
+ if (*end != '\0') {
+ fprintf(stderr, "Trailing characters in \"%s\": %s\n",
+ source, end);
+ return UINT64_MAX;
+ }
+ if (uint > max) {
+ fprintf(stderr, "Invalid %s value: %llu\n", type, uint);
+ return UINT64_MAX;
+ }
+
+ return uint;
+}
+
+void *make_data(const char source[], size_t *data_size, enum data_type type)
+{
+ switch (type) {
+ void *data;
+ bool boolean;
+ unsigned long long uint;
+
+ case DATA_TYPE_BOOL:
+ if (str_eq(source, "true")) {
+ boolean = true;
+ } else if (str_eq(source, "false")) {
+ boolean = false;
+ } else {
+ fprintf(stderr, "Invalid boolean value: \"%s\"\n",
+ source);
+ return NULL;
+ }
+
+ *data_size = 1;
+ data = xmalloc(*data_size);
+ *(uint8_t *)data = boolean;
+ return data;
+ case DATA_TYPE_UINT8:
+ uint = parse_uint(source, "uint8", UINT8_MAX);
+ if (uint == UINT64_MAX)
+ return NULL;
+
+ *data_size = 1;
+ data = xmalloc(*data_size);
+ *(uint8_t *)data = uint;
+ return data;
+ case DATA_TYPE_UINT16:
+ uint = parse_uint(source, "uint16", UINT16_MAX);
+ if (uint == UINT64_MAX)
+ return NULL;
+
+ *data_size = 2;
+ data = xmalloc(*data_size);
+ *(uint16_t *)data = uint;
+ return data;
+ case DATA_TYPE_UINT32:
+ uint = parse_uint(source, "uint32", UINT32_MAX);
+ if (uint == UINT64_MAX)
+ return NULL;
+
+ *data_size = 4;
+ data = xmalloc(*data_size);
+ *(uint32_t *)data = uint;
+ return data;
+ case DATA_TYPE_ASCII:
+ *data_size = strlen(source) + 1;
+ return strdup(source);
+ case DATA_TYPE_UNICODE:
+ return to_uchars(source, data_size);
+ case DATA_TYPE_RAW:
+ fprintf(stderr, "Raw data type is output only\n");
+ return NULL;
+ }
+
+ return NULL;
+}
+
+bool parse_data_type(const char str[], enum data_type *type)
+{
+ if (str_eq(str, "bool"))
+ *type = DATA_TYPE_BOOL;
+ else if (str_eq(str, "uint8"))
+ *type = DATA_TYPE_UINT8;
+ else if (str_eq(str, "uint16"))
+ *type = DATA_TYPE_UINT16;
+ else if (str_eq(str, "uint32"))
+ *type = DATA_TYPE_UINT32;
+ else if (str_eq(str, "ascii"))
+ *type = DATA_TYPE_ASCII;
+ else if (str_eq(str, "unicode"))
+ *type = DATA_TYPE_UNICODE;
+ else if (str_eq(str, "raw"))
+ *type = DATA_TYPE_RAW;
+ else
+ return false;
+
+ return true;
+}
diff --git a/util/smmstoretool/data.h b/util/smmstoretool/data.h
new file mode 100644
index 0000000000..13bd7bc4f4
--- /dev/null
+++ b/util/smmstoretool/data.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef SMMSTORETOOL__DATA_H__
+#define SMMSTORETOOL__DATA_H__
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+enum data_type {
+ DATA_TYPE_BOOL,
+ DATA_TYPE_UINT8,
+ DATA_TYPE_UINT16,
+ DATA_TYPE_UINT32,
+ DATA_TYPE_ASCII,
+ DATA_TYPE_UNICODE,
+ DATA_TYPE_RAW,
+};
+
+void print_data(const uint8_t data[], size_t data_size, enum data_type type);
+
+void *make_data(const char source[], size_t *data_size, enum data_type type);
+
+bool parse_data_type(const char str[], enum data_type *type);
+
+#endif // SMMSTORETOOL__DATA_H__
diff --git a/util/smmstoretool/description.md b/util/smmstoretool/description.md
new file mode 100644
index 0000000000..c6f493f8cb
--- /dev/null
+++ b/util/smmstoretool/description.md
@@ -0,0 +1 @@
+Offline SMMSTORE variable modification tool `C`
diff --git a/util/smmstoretool/fv.c b/util/smmstoretool/fv.c
new file mode 100644
index 0000000000..c9d342e162
--- /dev/null
+++ b/util/smmstoretool/fv.c
@@ -0,0 +1,175 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "fv.h"
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "udk2017.h"
+
+// The same as in `smmstore.h` header, which isn't in `commonlib`.
+#define SMM_BLOCK_SIZE (64*1024)
+
+static const EFI_GUID EfiVariableGuid = EFI_VARIABLE_GUID;
+
+static const EFI_GUID EfiAuthenticatedVariableGuid =
+ EFI_AUTHENTICATED_VARIABLE_GUID;
+
+static const EFI_GUID EfiSystemNvDataFvGuid = {
+ 0xfff12b8d, 0x7696, 0x4c8b,
+ { 0xa9, 0x85, 0x27, 0x47, 0x07, 0x5b, 0x4f, 0x50 }
+};
+
+static uint16_t calc_checksum(const uint16_t *hdr, size_t size)
+{
+ assert(size % 2 == 0 && "Header can't have odd length.");
+
+ uint16_t checksum = 0;
+ for (size_t i = 0; i < size / 2; ++i)
+ checksum += hdr[i];
+ return checksum;
+}
+
+bool
+fv_init(struct mem_range_t fv)
+{
+ if (fv.length % SMM_BLOCK_SIZE != 0) {
+ fprintf(stderr,
+ "Firmware Volume size is not a multiple of 64KiB\n");
+ return false;
+ }
+
+ memset(fv.start, 0xff, fv.length);
+
+ const EFI_FIRMWARE_VOLUME_HEADER vol_hdr = {
+ .FileSystemGuid = EfiSystemNvDataFvGuid,
+ .FvLength = fv.length,
+ .Signature = EFI_FVH_SIGNATURE,
+ .Attributes = EFI_FVB2_READ_ENABLED_CAP
+ | EFI_FVB2_READ_STATUS
+ | EFI_FVB2_WRITE_ENABLED_CAP
+ | EFI_FVB2_WRITE_STATUS
+ | EFI_FVB2_STICKY_WRITE
+ | EFI_FVB2_MEMORY_MAPPED
+ | EFI_FVB2_ERASE_POLARITY,
+ .HeaderLength = sizeof(vol_hdr)
+ + sizeof(EFI_FV_BLOCK_MAP_ENTRY),
+ .Revision = EFI_FVH_REVISION,
+ .BlockMap[0] = {
+ .NumBlocks = fv.length / SMM_BLOCK_SIZE,
+ .Length = SMM_BLOCK_SIZE,
+ },
+ };
+
+ EFI_FIRMWARE_VOLUME_HEADER *vol_hdr_dst = (void *)fv.start;
+ *vol_hdr_dst = vol_hdr;
+ vol_hdr_dst->BlockMap[1].NumBlocks = 0;
+ vol_hdr_dst->BlockMap[1].Length = 0;
+
+ vol_hdr_dst->Checksum =
+ ~calc_checksum((const void *)vol_hdr_dst, vol_hdr.HeaderLength);
+ ++vol_hdr_dst->Checksum;
+
+ const VARIABLE_STORE_HEADER var_store_hdr = {
+ // Authentication-related fields will be filled with 0xff.
+ .Signature = EfiAuthenticatedVariableGuid,
+ // Actual size of the storage is block size, the rest is
+ // Fault Tolerant Write (FTW) space and the FTW spare space.
+ .Size = SMM_BLOCK_SIZE - vol_hdr.HeaderLength,
+ .Format = VARIABLE_STORE_FORMATTED,
+ .State = VARIABLE_STORE_HEALTHY,
+ };
+
+ VARIABLE_STORE_HEADER *var_store_hdr_dst =
+ (void *)(fv.start + vol_hdr.HeaderLength);
+ *var_store_hdr_dst = var_store_hdr;
+
+ return true;
+}
+
+static bool guid_eq(const EFI_GUID *lhs, const EFI_GUID *rhs)
+{
+ return memcmp(lhs, rhs, sizeof(*lhs)) == 0;
+}
+
+static bool check_fw_vol_hdr(const EFI_FIRMWARE_VOLUME_HEADER *hdr,
+ size_t max_size)
+{
+ if (hdr->Revision != EFI_FVH_REVISION ||
+ hdr->Signature != EFI_FVH_SIGNATURE ||
+ hdr->FvLength > max_size ||
+ hdr->HeaderLength > max_size ||
+ hdr->HeaderLength % 2 != 0) {
+ fprintf(stderr, "No firmware volume header present\n");
+ return false;
+ }
+
+ if (!guid_eq(&hdr->FileSystemGuid, &EfiSystemNvDataFvGuid)) {
+ fprintf(stderr, "Firmware volume GUID non-compatible\n");
+ return false;
+ }
+
+ uint16_t checksum = calc_checksum((const void *)hdr, hdr->HeaderLength);
+ if (checksum != 0) {
+ fprintf(stderr,
+ "Firmware Volume checksum is non-zero: 0x%04X\n",
+ checksum);
+ return false;
+ }
+
+ return true;
+}
+
+static bool check_var_store_hdr(const VARIABLE_STORE_HEADER *hdr,
+ size_t max_size,
+ bool *auth_vars)
+{
+ *auth_vars = guid_eq(&hdr->Signature, &EfiAuthenticatedVariableGuid);
+ if (!*auth_vars && !guid_eq(&hdr->Signature, &EfiVariableGuid)) {
+ fprintf(stderr, "Variable store has unexpected GUID\n");
+ return false;
+ }
+
+ if (hdr->Size > max_size) {
+ fprintf(stderr, "Variable store size is too large: %zu > %zu\n",
+ (size_t)hdr->Size, max_size);
+ return false;
+ }
+
+ if (hdr->Format != VARIABLE_STORE_FORMATTED) {
+ fprintf(stderr, "Variable store is not formatted\n");
+ return false;
+ }
+
+ if (hdr->State != VARIABLE_STORE_HEALTHY) {
+ fprintf(stderr, "Variable store is not in a healthy state\n");
+ return false;
+ }
+
+ return true;
+}
+
+bool
+fv_parse(struct mem_range_t fv, struct mem_range_t *var_store, bool *auth_vars)
+{
+ const EFI_FIRMWARE_VOLUME_HEADER *vol_hdr = (void *)fv.start;
+ if (!check_fw_vol_hdr(vol_hdr, fv.length)) {
+ fprintf(stderr, "No valid firmware volume was found\n");
+ return false;
+ }
+
+ uint8_t *fw_vol_data = fv.start + vol_hdr->HeaderLength;
+ size_t volume_size = fv.length - vol_hdr->HeaderLength;
+ const VARIABLE_STORE_HEADER *var_store_hdr = (void *)fw_vol_data;
+ if (!check_var_store_hdr(var_store_hdr, volume_size, auth_vars)) {
+ fprintf(stderr, "No valid variable store was found");
+ return false;
+ }
+
+ var_store->start = fw_vol_data + sizeof(*var_store_hdr);
+ var_store->length = volume_size - sizeof(*var_store_hdr);
+ return true;
+}
diff --git a/util/smmstoretool/fv.h b/util/smmstoretool/fv.h
new file mode 100644
index 0000000000..b28db4cbf2
--- /dev/null
+++ b/util/smmstoretool/fv.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef SMMSTORETOOL__FV_H__
+#define SMMSTORETOOL__FV_H__
+
+#include <stdbool.h>
+
+#include "utils.h"
+
+// Firmware volume is what's stored in SMMSTORE region of CBFS. It wraps
+// variable store.
+
+bool fv_init(struct mem_range_t fv);
+
+bool fv_parse(struct mem_range_t fv,
+ struct mem_range_t *var_store,
+ bool *auth_vars);
+
+#endif // SMMSTORETOOL__FV_H__
diff --git a/util/smmstoretool/guids.c b/util/smmstoretool/guids.c
new file mode 100644
index 0000000000..4ba92b9798
--- /dev/null
+++ b/util/smmstoretool/guids.c
@@ -0,0 +1,89 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "guids.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include <commonlib/bsd/helpers.h>
+
+#include "udk2017.h"
+#include "utils.h"
+
+const struct guid_alias_t known_guids[] = {
+ {
+ "coreboot",
+ {
+ 0xceae4c1d, 0x335b, 0x4685,
+ { 0xa4, 0xa0, 0xfc, 0x4a, 0x94, 0xee, 0xa0, 0x85 }
+ },
+ },
+ {
+ "dasharo",
+ {
+ 0xd15b327e, 0xff2d, 0x4fc1,
+ { 0xab, 0xf6, 0xc1, 0x2b, 0xd0, 0x8c, 0x13, 0x59 }
+ },
+ },
+ {
+ "global",
+ {
+ 0x8be4df61, 0x93ca, 0x11d2,
+ { 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c }
+ },
+ },
+ {
+ "microsoft",
+ {
+ 0x77fa9abd, 0x0359, 0x4d32,
+ { 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b }
+ },
+ },
+};
+
+const int known_guid_count = ARRAY_SIZE(known_guids);
+
+char *format_guid(const EFI_GUID *guid, bool use_alias)
+{
+ if (use_alias) {
+ for (int i = 0; i < known_guid_count; ++i) {
+ const struct guid_alias_t *known_guid = &known_guids[i];
+ if (memcmp(&known_guid->guid, guid, sizeof(*guid)) == 0)
+ return strdup(known_guid->alias);
+ }
+ }
+
+ char *str = xmalloc(GUID_LEN + 1);
+ snprintf(str, GUID_LEN + 1,
+ "%08x-%04x-%04x-%02x%02x%02x%02x%02x%02x%02x%02x",
+ guid->Data1, guid->Data2, guid->Data3,
+ guid->Data4[0], guid->Data4[1],
+ guid->Data4[2], guid->Data4[3],
+ guid->Data4[4], guid->Data4[5],
+ guid->Data4[6], guid->Data4[7]);
+ return str;
+}
+
+bool parse_guid(const char str[], EFI_GUID *guid)
+{
+ for (int i = 0; i < known_guid_count; ++i) {
+ const struct guid_alias_t *known_guid = &known_guids[i];
+ if (str_eq(known_guid->alias, str)) {
+ *guid = known_guid->guid;
+ return true;
+ }
+ }
+
+ if (strlen(str) != GUID_LEN)
+ return false;
+
+ int n = sscanf(str,
+ "%08x-%04hx-%04hx-"
+ "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx",
+ &guid->Data1, &guid->Data2, &guid->Data3,
+ &guid->Data4[0], &guid->Data4[1],
+ &guid->Data4[2], &guid->Data4[3],
+ &guid->Data4[4], &guid->Data4[5],
+ &guid->Data4[6], &guid->Data4[7]);
+ return n == 11;
+}
diff --git a/util/smmstoretool/guids.h b/util/smmstoretool/guids.h
new file mode 100644
index 0000000000..b4dad1257a
--- /dev/null
+++ b/util/smmstoretool/guids.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef SMMSTORETOOL__GUIDS_H__
+#define SMMSTORETOOL__GUIDS_H__
+
+#include <stdbool.h>
+
+#include "udk2017.h"
+
+#define GUID_LEN 35
+
+struct guid_alias_t {
+ const char *alias;
+ EFI_GUID guid;
+};
+
+extern const struct guid_alias_t known_guids[];
+
+extern const int known_guid_count;
+
+char *format_guid(const EFI_GUID *guid, bool use_alias);
+
+bool parse_guid(const char str[], EFI_GUID *guid);
+
+#endif // SMMSTORETOOL__GUIDS_H__
diff --git a/util/smmstoretool/main.c b/util/smmstoretool/main.c
new file mode 100644
index 0000000000..eff5d9c02b
--- /dev/null
+++ b/util/smmstoretool/main.c
@@ -0,0 +1,487 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <unistd.h>
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <commonlib/bsd/helpers.h>
+
+#include "data.h"
+#include "guids.h"
+#include "storage.h"
+#include "udk2017.h"
+#include "vs.h"
+
+struct subcommand_t {
+ const char *name;
+ const char *description;
+ void (*print_help)(FILE *f, const struct subcommand_t *info);
+ int (*process)(int argc, char *argv[], const char store_file[]);
+};
+
+static void help_get(FILE *f, const struct subcommand_t *info);
+static void help_guids(FILE *f, const struct subcommand_t *info);
+static void help_help(FILE *f, const struct subcommand_t *info);
+static void help_list(FILE *f, const struct subcommand_t *info);
+static void help_remove(FILE *f, const struct subcommand_t *info);
+static void help_set(FILE *f, const struct subcommand_t *info);
+static int process_get(int argc, char *argv[], const char store_file[]);
+static int process_guids(int argc, char *argv[], const char store_file[]);
+static int process_help(int argc, char *argv[], const char store_file[]);
+static int process_list(int argc, char *argv[], const char store_file[]);
+static int process_remove(int argc, char *argv[], const char store_file[]);
+static int process_set(int argc, char *argv[], const char store_file[]);
+
+static const struct subcommand_t sub_commands[] = {
+ {
+ .name = "get",
+ .description = "display current value of a variable",
+ .print_help = &help_get,
+ .process = &process_get,
+ },
+ {
+ .name = "guids",
+ .description = "show GUID to alias mapping",
+ .print_help = &help_guids,
+ .process = &process_guids,
+ },
+ {
+ .name = "help",
+ .description = "provide built-in help",
+ .print_help = &help_help,
+ .process = &process_help,
+ },
+ {
+ .name = "list",
+ .description = "list variables present in the store",
+ .print_help = &help_list,
+ .process = &process_list,
+ },
+ {
+ .name = "remove",
+ .description = "remove a variable from the store",
+ .print_help = &help_remove,
+ .process = &process_remove,
+ },
+ {
+ .name = "set",
+ .description = "add or updates a variable in the store",
+ .print_help = &help_set,
+ .process = &process_set,
+ },
+};
+
+static const int sub_command_count = ARRAY_SIZE(sub_commands);
+
+static const char *USAGE_FMT = "Usage: %s smm-store-file sub-command\n"
+ " %s -h|--help\n";
+
+static const char *program_name;
+
+static void print_program_usage(void)
+{
+ fprintf(stderr, USAGE_FMT, program_name, program_name);
+ exit(EXIT_FAILURE);
+}
+
+static void print_sub_command_usage(const char sub_command[])
+{
+ fprintf(stderr, "\n");
+ fprintf(stderr, USAGE_FMT, program_name, program_name);
+ fprintf(stderr, "\n");
+
+ for (int i = 0; i < sub_command_count; ++i) {
+ const struct subcommand_t *cmd = &sub_commands[i];
+ if (!str_eq(cmd->name, sub_command))
+ continue;
+
+ cmd->print_help(stderr, cmd);
+ break;
+ }
+
+ exit(EXIT_FAILURE);
+}
+
+static void print_help(void)
+{
+ printf(USAGE_FMT, program_name, program_name);
+
+ printf("\n");
+ printf("Sub-commands:\n");
+ for (int i = 0; i < sub_command_count; ++i) {
+ const struct subcommand_t *cmd = &sub_commands[i];
+ printf(" * %-6s - %s\n", cmd->name, cmd->description);
+ }
+}
+
+static void print_types(FILE *f)
+{
+ fprintf(f, "Types and their values:\n");
+ fprintf(f, " * bool (true, false)\n");
+ fprintf(f, " * uint8 (0-255)\n");
+ fprintf(f, " * ascii (NUL-terminated)\n");
+ fprintf(f, " * unicode (widened and NUL-terminated)\n");
+ fprintf(f, " * raw (output only; raw bytes on output)\n");
+}
+
+static void help_set(FILE *f, const struct subcommand_t *info)
+{
+ fprintf(f, "Create or update a variable:\n");
+ fprintf(f, " %s smm-store-file %s \\\n", program_name, info->name);
+ fprintf(f, " -g vendor-guid \\\n");
+ fprintf(f, " -n variable-name \\\n");
+ fprintf(f, " -t variable-type \\\n");
+ fprintf(f, " -v value\n");
+ fprintf(f, "\n");
+ print_types(f);
+}
+
+static int process_set(int argc, char *argv[], const char store_file[])
+{
+ const char *name = NULL;
+ const char *value = NULL;
+ const char *type_str = NULL;
+ const char *guid_str = NULL;
+ int opt;
+ while ((opt = getopt(argc, argv, "n:t:v:g:")) != -1) {
+ switch (opt) {
+ case 'n':
+ name = optarg;
+ break;
+ case 't':
+ type_str = optarg;
+ break;
+ case 'v':
+ value = optarg;
+ break;
+ case 'g':
+ guid_str = optarg;
+ break;
+
+ case '?': /* parsing error */
+ print_sub_command_usage(argv[0]);
+ }
+ }
+
+ if (argv[optind] != NULL) {
+ fprintf(stderr, "First unexpected positional argument: %s\n",
+ argv[optind]);
+ print_sub_command_usage(argv[0]);
+ }
+
+ if (name == NULL || value == NULL || type_str == NULL ||
+ guid_str == NULL) {
+ fprintf(stderr, "All options are required\n");
+ print_sub_command_usage(argv[0]);
+ }
+
+ if (name[0] == '\0') {
+ fprintf(stderr, "Variable name can't be empty\n");
+ print_sub_command_usage(argv[0]);
+ }
+
+ EFI_GUID guid;
+ if (!parse_guid(guid_str, &guid)) {
+ fprintf(stderr, "Failed to parse GUID: %s\n", guid_str);
+ return EXIT_FAILURE;
+ }
+
+ enum data_type type;
+ if (!parse_data_type(type_str, &type)) {
+ fprintf(stderr, "Failed to parse type: %s\n", type_str);
+ return EXIT_FAILURE;
+ }
+
+ size_t data_size;
+ void *data = make_data(value, &data_size, type);
+ if (data == NULL) {
+ fprintf(stderr, "Failed to parse value \"%s\" as %s\n",
+ value, type_str);
+ return EXIT_FAILURE;
+ }
+
+ struct storage_t storage;
+ if (!storage_open(store_file, &storage, /*rw=*/true)) {
+ free(data);
+ return EXIT_FAILURE;
+ }
+
+ struct var_t *var = vs_find(&storage.vs, name, &guid);
+ if (var == NULL) {
+ var = vs_new_var(&storage.vs);
+ var->name = to_uchars(name, &var->name_size);
+ var->data = data;
+ var->data_size = data_size;
+ var->guid = guid;
+ } else {
+ free(var->data);
+ var->data = data;
+ var->data_size = data_size;
+ }
+
+ return storage_write_back(&storage) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+static void help_list(FILE *f, const struct subcommand_t *info)
+{
+ fprintf(f, "List variables in the store:\n");
+ fprintf(f, " %s smm-store-file %s\n", program_name, info->name);
+}
+
+static int process_list(int argc, char *argv[], const char store_file[])
+{
+ if (argc != 1) {
+ fprintf(stderr, "Invalid invocation\n");
+ print_sub_command_usage(argv[0]);
+ }
+
+ struct storage_t storage;
+ if (!storage_open(store_file, &storage, /*rw=*/false))
+ return EXIT_FAILURE;
+
+ for (struct var_t *v = storage.vs.vars; v != NULL; v = v->next) {
+ char *name = to_chars(v->name, v->name_size);
+ char *guid = format_guid(&v->guid, /*use_alias=*/true);
+
+ printf("%-*s:%s (%zu %s)\n", GUID_LEN, guid, name, v->data_size,
+ v->data_size == 1 ? "byte" : "bytes");
+
+ free(name);
+ free(guid);
+ }
+
+ storage_drop(&storage);
+ return EXIT_SUCCESS;
+}
+
+static void help_get(FILE *f, const struct subcommand_t *info)
+{
+ fprintf(f, "Read variable's value:\n");
+ fprintf(f, " %s smm-store-file %s \\\n", program_name, info->name);
+ fprintf(f, " -g vendor-guid \\\n");
+ fprintf(f, " -n variable-name \\\n");
+ fprintf(f, " -t variable-type\n");
+ fprintf(f, "\n");
+ print_types(f);
+}
+
+static int process_get(int argc, char *argv[], const char store_file[])
+{
+ const char *name = NULL;
+ const char *type_str = NULL;
+ const char *guid_str = NULL;
+ int opt;
+ while ((opt = getopt(argc, argv, "n:g:t:")) != -1) {
+ switch (opt) {
+ case 'n':
+ name = optarg;
+ break;
+ case 'g':
+ guid_str = optarg;
+ break;
+ case 't':
+ type_str = optarg;
+ break;
+
+ case '?': /* parsing error */
+ print_sub_command_usage(argv[0]);
+ }
+ }
+
+ if (name == NULL || type_str == NULL || guid_str == NULL) {
+ fprintf(stderr, "All options are required\n");
+ print_sub_command_usage(argv[0]);
+ }
+
+ EFI_GUID guid;
+ if (!parse_guid(guid_str, &guid)) {
+ fprintf(stderr, "Failed to parse GUID: %s\n", guid_str);
+ return EXIT_FAILURE;
+ }
+
+ enum data_type type;
+ if (!parse_data_type(type_str, &type)) {
+ fprintf(stderr, "Failed to parse type: %s\n", type_str);
+ return EXIT_FAILURE;
+ }
+
+ struct storage_t storage;
+ if (!storage_open(store_file, &storage, /*rw=*/false))
+ return EXIT_FAILURE;
+
+ int result = EXIT_SUCCESS;
+
+ struct var_t *var = vs_find(&storage.vs, name, &guid);
+ if (var == NULL) {
+ result = EXIT_FAILURE;
+ fprintf(stderr, "Couldn't find variable \"%s:%s\"\n",
+ guid_str, name);
+ } else if (var->data_size == 0) {
+ fprintf(stderr, "There is no data to show.\n");
+ result = EXIT_FAILURE;
+ } else {
+ print_data(var->data, var->data_size, type);
+ }
+
+ storage_drop(&storage);
+ return result;
+}
+
+static void help_help(FILE *f, const struct subcommand_t *info)
+{
+ fprintf(f, "Display generic help:\n");
+ fprintf(f, " %s smm-store-file %s\n", program_name, info->name);
+ fprintf(f, "\n");
+ fprintf(f, "Display sub-command help:\n");
+ fprintf(f, " %s smm-store-file %s sub-command\n",
+ program_name, info->name);
+}
+
+static int process_help(int argc, char *argv[], const char store_file[])
+{
+ (void)store_file;
+
+ if (argc == 1) {
+ print_help();
+ return EXIT_SUCCESS;
+ }
+
+ if (argc != 2) {
+ fprintf(stderr, "Invalid invocation\n");
+ print_sub_command_usage(argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ const char *sub_command = argv[1];
+
+ for (int i = 0; i < sub_command_count; ++i) {
+ const struct subcommand_t *cmd = &sub_commands[i];
+ if (!str_eq(cmd->name, sub_command))
+ continue;
+
+ cmd->print_help(stdout, cmd);
+ return EXIT_SUCCESS;
+ }
+
+ fprintf(stderr, "Unknown sub-command: %s\n", sub_command);
+ print_help();
+ return EXIT_FAILURE;
+}
+
+static void help_remove(FILE *f, const struct subcommand_t *info)
+{
+ fprintf(f, "Remove a variable:\n");
+ fprintf(f, " %s smm-store-file %s \\\n", program_name, info->name);
+ fprintf(f, " -g vendor-guid \\\n");
+ fprintf(f, " -n variable-name\n");
+}
+
+static int process_remove(int argc, char *argv[], const char store_file[])
+{
+ const char *name = NULL;
+ const char *guid_str = NULL;
+ int opt;
+ while ((opt = getopt(argc, argv, "n:g:")) != -1) {
+ switch (opt) {
+ case 'n':
+ name = optarg;
+ break;
+ case 'g':
+ guid_str = optarg;
+ break;
+
+ case '?': /* parsing error */
+ print_sub_command_usage(argv[0]);
+ }
+ }
+
+ if (name == NULL || guid_str == NULL) {
+ fprintf(stderr, "All options are required\n");
+ print_sub_command_usage(argv[0]);
+ }
+
+ EFI_GUID guid;
+ if (!parse_guid(guid_str, &guid)) {
+ fprintf(stderr, "Failed to parse GUID: %s\n", guid_str);
+ return EXIT_FAILURE;
+ }
+
+ struct storage_t storage;
+ if (!storage_open(store_file, &storage, /*rw=*/true))
+ return EXIT_FAILURE;
+
+ int result = EXIT_SUCCESS;
+
+ struct var_t *var = vs_find(&storage.vs, name, &guid);
+ if (var == NULL) {
+ result = EXIT_FAILURE;
+ fprintf(stderr, "Couldn't find variable \"%s:%s\"\n",
+ guid_str, name);
+ } else {
+ vs_delete(&storage.vs, var);
+ }
+
+ storage_write_back(&storage);
+ return result;
+}
+
+static void help_guids(FILE *f, const struct subcommand_t *info)
+{
+ fprintf(f, "List recognized GUIDS:\n");
+ fprintf(f, " %s smm-store-file %s\n", program_name, info->name);
+}
+
+static int process_guids(int argc, char *argv[], const char store_file[])
+{
+ (void)store_file;
+
+ if (argc != 1) {
+ fprintf(stderr, "Invalid invocation\n");
+ print_sub_command_usage(argv[0]);
+ }
+
+ for (int i = 0; i < known_guid_count; ++i) {
+ char *guid = format_guid(&known_guids[i].guid,
+ /*use_alias=*/false);
+ printf("%-10s -> %s\n", known_guids[i].alias, guid);
+ free(guid);
+ }
+ return EXIT_SUCCESS;
+}
+
+int main(int argc, char *argv[])
+{
+ program_name = argv[0];
+
+ if (argc > 1 && (str_eq(argv[1], "-h") || str_eq(argv[1], "--help"))) {
+ print_help();
+ exit(EXIT_SUCCESS);
+ }
+
+ if (argc < 3)
+ print_program_usage();
+
+ const char *store_file = argv[1];
+ const char *sub_command = argv[2];
+
+ int sub_command_argc = argc - 2;
+ char **sub_command_argv = argv + 2;
+
+ for (int i = 0; i < sub_command_count; ++i) {
+ const struct subcommand_t *cmd = &sub_commands[i];
+ if (!str_eq(cmd->name, sub_command))
+ continue;
+
+ return cmd->process(sub_command_argc,
+ sub_command_argv,
+ store_file);
+ }
+
+ fprintf(stderr, "Unknown sub-command: %s\n", sub_command);
+ print_help();
+ return EXIT_FAILURE;
+}
diff --git a/util/smmstoretool/storage.c b/util/smmstoretool/storage.c
new file mode 100644
index 0000000000..abc6840b67
--- /dev/null
+++ b/util/smmstoretool/storage.c
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "storage.h"
+
+#include <assert.h>
+#include <stdio.h>
+
+#include "fv.h"
+#include "utils.h"
+
+bool storage_open(const char store_file[], struct storage_t *storage, bool rw)
+{
+ storage->rw = rw;
+
+ storage->file = map_file(store_file, rw);
+ if (storage->file.start == NULL) {
+ fprintf(stderr, "Failed to load smm-store-file \"%s\"\n",
+ store_file);
+ return false;
+ }
+
+ bool auth_vars;
+ if (!fv_parse(storage->file, &storage->store_area, &auth_vars)) {
+ if (!rw) {
+ fprintf(stderr,
+ "Failed to find variable store in \"%s\"\n",
+ store_file);
+ goto error;
+ }
+
+ if (!fv_init(storage->file)) {
+ fprintf(stderr,
+ "Failed to create variable store in \"%s\"\n",
+ store_file);
+ goto error;
+ }
+
+ if (!fv_parse(storage->file, &storage->store_area, &auth_vars)) {
+ fprintf(stderr,
+ "Failed to parse newly formatted store in \"%s\"\n",
+ store_file);
+ goto error;
+ }
+
+ fprintf(stderr,
+ "Successfully created variable store in \"%s\"\n",
+ store_file);
+ }
+
+ storage->vs = vs_load(storage->store_area, auth_vars);
+ return true;
+
+error:
+ unmap_file(storage->file);
+ return false;
+}
+
+bool storage_write_back(struct storage_t *storage)
+{
+ assert(storage->rw && "Only RW storage can be updated.");
+
+ bool success = vs_store(&storage->vs, storage->store_area);
+ if (!success)
+ fprintf(stderr, "Failed to update variable store\n");
+ storage_drop(storage);
+ return success;
+}
+
+void storage_drop(struct storage_t *storage)
+{
+ unmap_file(storage->file);
+ vs_free(&storage->vs);
+}
diff --git a/util/smmstoretool/storage.h b/util/smmstoretool/storage.h
new file mode 100644
index 0000000000..eb5cb08fc5
--- /dev/null
+++ b/util/smmstoretool/storage.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef SMMSTORETOOL__STORAGE_H__
+#define SMMSTORETOOL__STORAGE_H__
+
+#include "vs.h"
+#include "utils.h"
+
+struct storage_t {
+ bool rw;
+ struct mem_range_t file;
+ struct mem_range_t store_area;
+ struct var_store_t vs;
+};
+
+bool storage_open(const char store_file[], struct storage_t *storage, bool rw);
+
+bool storage_write_back(struct storage_t *storage);
+
+void storage_drop(struct storage_t *storage);
+
+#endif // SMMSTORETOOL__STORAGE_H__
diff --git a/util/smmstoretool/udk2017.h b/util/smmstoretool/udk2017.h
new file mode 100644
index 0000000000..5a4286a634
--- /dev/null
+++ b/util/smmstoretool/udk2017.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef SMMSTORETOOL__UDK2017_H__
+#define SMMSTORETOOL__UDK2017_H__
+
+#include <UDK2017/MdePkg/Include/Uefi/UefiBaseType.h>
+#include <UDK2017/MdePkg/Include/Uefi/UefiMultiPhase.h>
+#include <UDK2017/MdePkg/Include/Pi/PiFirmwareVolume.h>
+#include <UDK2017/MdeModulePkg/Include/Guid/VariableFormat.h>
+
+/*
+ * ProcessorBind.h contains `#pragma GCC visibility push(hidden)` guarded by an
+ * identical condition, but there is no corresponding `pop` pragma. This can
+ * cause trouble for code following headers above including libc headers because
+ * linker suddenly considers declarations from them (e.g., `strcmp()`) to be
+ * hidden.
+ *
+ * In order to address this situation all UDK2017 headers used by this tool
+ * must be listed above and included indirectly through this header which
+ * restores default visibility.
+ *
+ * Mind that this issue appears only if the following conditions are satisfied
+ * and not all toolchains are configured to build position-independent code by
+ * default (as if -fpic or -fpie appears on compilation command-line).
+ */
+#if defined(__GNUC__) && defined(__pic__) && !defined(USING_LTO)
+#pragma GCC visibility pop
+#endif
+
+#endif // SMMSTORETOOL__UDK2017_H__
diff --git a/util/smmstoretool/utils.c b/util/smmstoretool/utils.c
new file mode 100644
index 0000000000..9f554e0501
--- /dev/null
+++ b/util/smmstoretool/utils.c
@@ -0,0 +1,111 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "utils.h"
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+void *xmalloc(size_t size)
+{
+ void *p = malloc(size);
+ if (p == NULL) {
+ fprintf(stderr, "Failed to allocate memory\n");
+ abort();
+ }
+ return p;
+}
+
+char *to_chars(const CHAR16 uchars[], size_t size)
+{
+ char *chars = xmalloc(size / 2 + 1);
+
+ const CHAR16 *from = uchars;
+ char *to = chars;
+ while (*from != 0) {
+ CHAR16 uc = *from++;
+ if (uc < CHAR_MAX)
+ *to++ = uc;
+ else
+ *to++ = '?';
+ }
+
+ // In case there was no terminating NUL.
+ if (to != chars && to[-1] != '\0')
+ *to = '\0';
+
+ return chars;
+}
+
+CHAR16 *to_uchars(const char chars[], size_t *size)
+{
+ *size = (strlen(chars) + 1) * 2;
+ CHAR16 *uchars = xmalloc(*size);
+
+ const char *from = chars;
+ CHAR16 *to = uchars;
+ while (*from != '\0')
+ *to++ = *from++;
+ *to = 0;
+
+ return uchars;
+}
+
+bool str_eq(const char lhs[], const char rhs[])
+{
+ return strcmp(lhs, rhs) == 0;
+}
+
+struct mem_range_t map_file(const char path[], bool rw)
+{
+ struct mem_range_t range = {0};
+
+ int open_flags = rw ? O_RDWR : O_RDONLY;
+ int mmap_flags = rw ? PROT_READ | PROT_WRITE : PROT_READ;
+
+ int fd = open(path, open_flags);
+ if (fd == -1) {
+ fprintf(stderr, "Failed to open(): %s\n", strerror(errno));
+ return range;
+ }
+
+ struct stat stat_buf;
+ if (fstat(fd, &stat_buf) != 0) {
+ (void)close(fd);
+ fprintf(stderr, "Failed to fstat(): %s\n", strerror(errno));
+ return range;
+ }
+
+ if (stat_buf.st_size == 0) {
+ (void)close(fd);
+ fprintf(stderr, "Can't map an empty \"%s\" file\n", path);
+ return range;
+ }
+
+ uint8_t *mem = mmap(/*addr=*/NULL, stat_buf.st_size, mmap_flags,
+ MAP_SHARED | MAP_POPULATE, fd, /*offset=*/0);
+ (void)close(fd);
+ if (mem == MAP_FAILED) {
+ fprintf(stderr, "Failed to mmap(): %s\n", strerror(errno));
+ return range;
+ }
+
+ range.start = mem;
+ range.length = stat_buf.st_size;
+ return range;
+}
+
+void unmap_file(struct mem_range_t store)
+{
+ if (munmap(store.start, store.length) != 0)
+ fprintf(stderr, "Failed to munmap(): %s\n", strerror(errno));
+}
diff --git a/util/smmstoretool/utils.h b/util/smmstoretool/utils.h
new file mode 100644
index 0000000000..a1cefc1748
--- /dev/null
+++ b/util/smmstoretool/utils.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef SMMSTORETOOL__UTILS_H__
+#define SMMSTORETOOL__UTILS_H__
+
+#include <stddef.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "udk2017.h"
+
+struct mem_range_t {
+ uint8_t *start;
+ size_t length;
+};
+
+void *xmalloc(size_t size);
+
+char *to_chars(const CHAR16 uchars[], size_t size);
+
+CHAR16 *to_uchars(const char chars[], size_t *size);
+
+bool str_eq(const char lhs[], const char rhs[]);
+
+struct mem_range_t map_file(const char path[], bool rw);
+
+void unmap_file(struct mem_range_t store);
+
+#endif // SMMSTORETOOL__UTILS_H__
diff --git a/util/smmstoretool/vs.c b/util/smmstoretool/vs.c
new file mode 100644
index 0000000000..a1167ca2de
--- /dev/null
+++ b/util/smmstoretool/vs.c
@@ -0,0 +1,232 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "vs.h"
+
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "udk2017.h"
+#include "utils.h"
+
+static size_t get_var_hdr_size(bool auth_vars)
+{
+ if (auth_vars)
+ return sizeof(AUTHENTICATED_VARIABLE_HEADER);
+ return sizeof(VARIABLE_HEADER);
+}
+
+struct var_store_t vs_load(struct mem_range_t vs_data, bool auth_vars)
+{
+ uint8_t *var_hdr = vs_data.start;
+
+ struct var_store_t vs = {
+ .auth_vars = auth_vars,
+ .vars = NULL,
+ };
+
+ struct var_t *last_var = NULL;
+
+ const size_t var_hdr_size = get_var_hdr_size(auth_vars);
+ while (var_hdr + var_hdr_size < vs_data.start + vs_data.length) {
+ uint16_t start_id;
+ uint8_t state;
+ struct var_t var = {0};
+ uint8_t *var_data = var_hdr;
+
+ if (auth_vars) {
+ const AUTHENTICATED_VARIABLE_HEADER *auth_hdr =
+ (void *)var_data;
+
+ start_id = auth_hdr->StartId;
+ state = auth_hdr->State;
+
+ var.reserved = auth_hdr->Reserved;
+ var.attrs = auth_hdr->Attributes;
+ var.name_size = auth_hdr->NameSize;
+ var.data_size = auth_hdr->DataSize;
+ var.guid = auth_hdr->VendorGuid;
+ } else {
+ const VARIABLE_HEADER *no_auth_hdr = (void *)var_data;
+
+ start_id = no_auth_hdr->StartId;
+ state = no_auth_hdr->State;
+
+ var.reserved = no_auth_hdr->Reserved;
+ var.attrs = no_auth_hdr->Attributes;
+ var.name_size = no_auth_hdr->NameSize;
+ var.data_size = no_auth_hdr->DataSize;
+ var.guid = no_auth_hdr->VendorGuid;
+ }
+
+ var_hdr += HEADER_ALIGN(var_hdr_size +
+ var.name_size +
+ var.data_size);
+
+ if (start_id != VARIABLE_DATA)
+ break;
+
+ if (state != VAR_ADDED)
+ continue;
+
+ if (var.data_size == UINT32_MAX ||
+ var.name_size == UINT32_MAX ||
+ var.attrs == UINT32_MAX)
+ continue;
+
+ CHAR16 *name = (void *)(var_data + var_hdr_size);
+ var.name = xmalloc(var.name_size);
+ memcpy(var.name, name, var.name_size);
+
+ uint8_t *data =
+ (void *)(var_data + var_hdr_size + var.name_size);
+ var.data = xmalloc(var.data_size);
+ memcpy(var.data, data, var.data_size);
+
+ struct var_t *var_node = xmalloc(sizeof(*var_node));
+ *var_node = var;
+ if (last_var != NULL)
+ last_var->next = var_node;
+ else if (vs.vars == NULL)
+ vs.vars = var_node;
+ last_var = var_node;
+ }
+
+ return vs;
+}
+
+static void store_var(const struct var_t *var, bool auth_vars, uint8_t *data)
+{
+ if (auth_vars) {
+ AUTHENTICATED_VARIABLE_HEADER hdr;
+ memset(&hdr, 0xff, sizeof(hdr));
+
+ hdr.StartId = VARIABLE_DATA;
+ hdr.State = VAR_ADDED;
+ hdr.Reserved = var->reserved;
+ hdr.Attributes = var->attrs;
+ hdr.VendorGuid = var->guid;
+ hdr.NameSize = var->name_size;
+ hdr.DataSize = var->data_size;
+
+ memcpy(data, &hdr, sizeof(hdr));
+ data += sizeof(hdr);
+ } else {
+ VARIABLE_HEADER hdr;
+ memset(&hdr, 0xff, sizeof(hdr));
+
+ hdr.StartId = VARIABLE_DATA;
+ hdr.State = VAR_ADDED;
+ hdr.Reserved = var->reserved;
+ hdr.Attributes = var->attrs;
+ hdr.VendorGuid = var->guid;
+ hdr.NameSize = var->name_size;
+ hdr.DataSize = var->data_size;
+
+ memcpy(data, &hdr, sizeof(hdr));
+ data += sizeof(hdr);
+ }
+
+ memcpy(data, var->name, var->name_size);
+ memcpy(data + var->name_size, var->data, var->data_size);
+}
+
+bool vs_store(struct var_store_t *vs, struct mem_range_t vs_data)
+{
+ uint8_t *out_data = vs_data.start;
+
+ const size_t var_hdr_size = get_var_hdr_size(vs->auth_vars);
+ for (struct var_t *var = vs->vars; var != NULL; var = var->next) {
+ const size_t var_size =
+ var_hdr_size + var->name_size + var->data_size;
+ if (out_data + var_size > vs_data.start + vs_data.length) {
+ fprintf(stderr,
+ "Not enough space to serialize Variable Store.\n");
+ return false;
+ }
+
+ store_var(var, vs->auth_vars, out_data);
+ out_data += HEADER_ALIGN(var_size);
+ }
+
+ // The rest is "uninitialized".
+ memset(out_data, 0xff, vs_data.length - (out_data - vs_data.start));
+
+ return true;
+}
+
+struct var_t *vs_new_var(struct var_store_t *vs)
+{
+ struct var_t *new_var = xmalloc(sizeof(*new_var));
+
+ memset(new_var, 0, sizeof(*new_var));
+ new_var->attrs = EFI_VARIABLE_NON_VOLATILE
+ | EFI_VARIABLE_BOOTSERVICE_ACCESS
+ | EFI_VARIABLE_RUNTIME_ACCESS;
+
+ struct var_t *var = vs->vars;
+ if (var == NULL) {
+ vs->vars = new_var;
+ } else {
+ while (var->next != NULL)
+ var = var->next;
+ var->next = new_var;
+ }
+
+ return new_var;
+}
+
+struct var_t *vs_find(struct var_store_t *vs,
+ const char name[],
+ const EFI_GUID *guid)
+{
+ size_t name_size;
+ CHAR16 *uchar_name = to_uchars(name, &name_size);
+
+ struct var_t *var;
+ for (var = vs->vars; var != NULL; var = var->next) {
+ if (var->name_size != name_size)
+ continue;
+ if (memcmp(var->name, uchar_name, name_size) != 0)
+ continue;
+ if (memcmp(&var->guid, guid, sizeof(*guid)) != 0)
+ continue;
+ break;
+ }
+
+ free(uchar_name);
+ return var;
+}
+
+static void free_var(struct var_t *var)
+{
+ free(var->name);
+ free(var->data);
+ free(var);
+}
+
+void vs_delete(struct var_store_t *vs, struct var_t *var)
+{
+ if (vs->vars == var) {
+ vs->vars = var->next;
+ free_var(var);
+ return;
+ }
+
+ for (struct var_t *v = vs->vars; v != NULL; v = v->next) {
+ if (v->next == var) {
+ v->next = var->next;
+ free_var(var);
+ return;
+ }
+ }
+}
+
+void vs_free(struct var_store_t *vs)
+{
+ for (struct var_t *next, *var = vs->vars; var != NULL; var = next) {
+ next = var->next;
+ free_var(var);
+ }
+}
diff --git a/util/smmstoretool/vs.h b/util/smmstoretool/vs.h
new file mode 100644
index 0000000000..2ac9e6c8b2
--- /dev/null
+++ b/util/smmstoretool/vs.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef SMMSTORETOOL__VS_H__
+#define SMMSTORETOOL__VS_H__
+
+#include <stdbool.h>
+
+#include "udk2017.h"
+#include "utils.h"
+
+// Variable store is part of firmware volume. This unit doesn't deal with its
+// header only with data that follows.
+
+struct var_t {
+ uint8_t reserved;
+ uint32_t attrs;
+ EFI_GUID guid;
+ CHAR16 *name;
+ size_t name_size; // in bytes
+ uint8_t *data;
+ size_t data_size; // in bytes
+ struct var_t *next;
+};
+
+struct var_store_t {
+ struct var_t *vars;
+ bool auth_vars;
+};
+
+struct var_store_t vs_load(struct mem_range_t vs_data, bool auth_vars);
+
+bool vs_store(struct var_store_t *vs, struct mem_range_t vs_data);
+
+struct var_t *vs_new_var(struct var_store_t *vs);
+
+struct var_t *vs_find(struct var_store_t *vs,
+ const char name[],
+ const EFI_GUID *guid);
+
+void vs_delete(struct var_store_t *vs, struct var_t *var);
+
+void vs_free(struct var_store_t *vs);
+
+#endif // SMMSTORETOOL__VS_H__
diff --git a/util/util_readme/post_util.md b/util/util_readme/post_util.md
index 78519ad14c..892dc8a766 100644
--- a/util/util_readme/post_util.md
+++ b/util/util_readme/post_util.md
@@ -4,6 +4,7 @@
* [cbfstool](util/cbfstool/index.md)
* [ifdtool](util/ifdtool/index.md)
* [intelp2m](util/intelp2m/index.md)
+* [smmstoretool](util/smmstoretool/index.md)
## Generated documentation