diff options
author | Sergii Dmytruk <sergii.dmytruk@3mdeb.com> | 2023-11-17 19:31:20 +0200 |
---|---|---|
committer | Martin L Roth <gaumless@gmail.com> | 2024-03-09 23:22:55 +0000 |
commit | 04bd9651435843ce4b03c9717f2965fe344fe5cc (patch) | |
tree | 0193502b26818c738206a60862c689485d533a50 | |
parent | 7a51acfbe91c7f9d01837103341526abb6ea46f4 (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.md | 2 | ||||
-rw-r--r-- | Documentation/util/smmstoretool/index.md | 126 | ||||
-rw-r--r-- | util/README.md | 1 | ||||
-rw-r--r-- | util/smmstoretool/.gitignore | 1 | ||||
-rw-r--r-- | util/smmstoretool/Makefile | 56 | ||||
-rw-r--r-- | util/smmstoretool/data.c | 180 | ||||
-rw-r--r-- | util/smmstoretool/data.h | 26 | ||||
-rw-r--r-- | util/smmstoretool/description.md | 1 | ||||
-rw-r--r-- | util/smmstoretool/fv.c | 175 | ||||
-rw-r--r-- | util/smmstoretool/fv.h | 19 | ||||
-rw-r--r-- | util/smmstoretool/guids.c | 89 | ||||
-rw-r--r-- | util/smmstoretool/guids.h | 25 | ||||
-rw-r--r-- | util/smmstoretool/main.c | 487 | ||||
-rw-r--r-- | util/smmstoretool/storage.c | 73 | ||||
-rw-r--r-- | util/smmstoretool/storage.h | 22 | ||||
-rw-r--r-- | util/smmstoretool/udk2017.h | 30 | ||||
-rw-r--r-- | util/smmstoretool/utils.c | 111 | ||||
-rw-r--r-- | util/smmstoretool/utils.h | 29 | ||||
-rw-r--r-- | util/smmstoretool/vs.c | 232 | ||||
-rw-r--r-- | util/smmstoretool/vs.h | 44 | ||||
-rw-r--r-- | util/util_readme/post_util.md | 1 |
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 |