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>
This commit is contained in:
Sergii Dmytruk
2023-11-17 19:31:20 +02:00
committed by Martin L Roth
parent 7a51acfbe9
commit 04bd965143
21 changed files with 1730 additions and 0 deletions

1
util/smmstoretool/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
smmstoretool

View File

@@ -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)

180
util/smmstoretool/data.c Normal file
View File

@@ -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;
}

26
util/smmstoretool/data.h Normal file
View File

@@ -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__

View File

@@ -0,0 +1 @@
Offline SMMSTORE variable modification tool `C`

175
util/smmstoretool/fv.c Normal file
View File

@@ -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;
}

19
util/smmstoretool/fv.h Normal file
View File

@@ -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__

89
util/smmstoretool/guids.c Normal file
View File

@@ -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;
}

25
util/smmstoretool/guids.h Normal file
View File

@@ -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__

487
util/smmstoretool/main.c Normal file
View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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__

View File

@@ -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__

111
util/smmstoretool/utils.c Normal file
View File

@@ -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));
}

29
util/smmstoretool/utils.h Normal file
View File

@@ -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__

232
util/smmstoretool/vs.c Normal file
View File

@@ -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);
}
}

44
util/smmstoretool/vs.h Normal file
View File

@@ -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__