Blob Blame History Raw
From 488976e782a7307cc2df4e1a8aaea1f6dfa757dd Mon Sep 17 00:00:00 2001
From: Peter Jones <pjones@redhat.com>
Date: Mon, 17 Jun 2019 15:04:11 -0400
Subject: [PATCH 37/63] Implement 'efivar --export=foo.var'

Signed-off-by: Peter Jones <pjones@redhat.com>
---
 .gitignore                  |  17 +-
 src/Makefile                |   2 +-
 src/crc32.h                 |  19 ++
 src/efivar.c                | 362 ++++++++++++++++++++------
 src/export.c                | 502 +++++++++++++++++++++++++++++++++---
 src/gpt.c                   |  18 --
 src/include/efivar/efivar.h |   3 +
 7 files changed, 783 insertions(+), 140 deletions(-)

diff --git a/.gitignore b/.gitignore
index 5c3fd0e3f52..947d88eec42 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,23 +1,26 @@
+.*.c.P
+.*.h.P
+.*.d
 .*.sw?
 *~
 *.a
+*.env
 *.E
 *.o
+*.map
 *.pc
 *.S
 !src/guids.S
 *.so
 *.so.*
+*.spec
 *.tar.*
-.*.c.P
-.*.h.P
-.*.d
+*.var
 core.*
-*.spec
+cov-int
+vgcore.*
+scan-results/
 src/efivar
 src/efivar-static
 src/makeguids
 src/guid-symbols.c
-*.map
-cov-int
-scan-results/
diff --git a/src/Makefile b/src/Makefile
index ecbbc02e1f7..addfaa03c85 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -17,7 +17,7 @@ STATICTARGETS=$(STATICLIBTARGETS) $(STATICBINTARGETS)
 LIBEFIBOOT_SOURCES = crc32.c creator.c disk.c gpt.c loadopt.c path-helpers.c \
 		     linux.c $(wildcard linux-*.c)
 LIBEFIBOOT_OBJECTS = $(patsubst %.c,%.o,$(LIBEFIBOOT_SOURCES))
-LIBEFIVAR_SOURCES = dp.c dp-acpi.c dp-hw.c dp-media.c dp-message.c \
+LIBEFIVAR_SOURCES = crc32.c dp.c dp-acpi.c dp-hw.c dp-media.c dp-message.c \
 	efivarfs.c error.c export.c guid.c guids.S guid-symbols.c \
 	lib.c vars.c
 LIBEFIVAR_OBJECTS = $(patsubst %.S,%.o,$(patsubst %.c,%.o,$(LIBEFIVAR_SOURCES)))
diff --git a/src/crc32.h b/src/crc32.h
index b5b975a5768..4e833aafac0 100644
--- a/src/crc32.h
+++ b/src/crc32.h
@@ -29,6 +29,25 @@
 
 extern uint32_t crc32 (const void *buf, unsigned long len, uint32_t seed);
 
+/**
+ * efi_crc32() - EFI version of crc32 function
+ * @buf: buffer to calculate crc32 of
+ * @len - length of buf
+ *
+ * Description: Returns EFI-style CRC32 value for @buf
+ *
+ * This function uses the little endian Ethernet polynomial
+ * but seeds the function with ~0, and xor's with ~0 at the end.
+ * Note, the EFI Specification, v1.02, has a reference to
+ * Dr. Dobbs Journal, May 1994 (actually it's in May 1992).
+ */
+static inline uint32_t
+efi_crc32(const void *buf, unsigned long len)
+{
+	return (crc32(buf, len, ~0L) ^ ~0L);
+}
+
+
 #endif /* _CRC32_H */
 
 // vim:fenc=utf-8:tw=75:noet
diff --git a/src/efivar.c b/src/efivar.c
index 7f16ab15bab..885a9af864b 100644
--- a/src/efivar.c
+++ b/src/efivar.c
@@ -46,6 +46,8 @@ extern int optind, opterr, optopt;
 #define ACTION_LIST_GUIDS	0x08
 #define ACTION_WRITE		0x10
 #define ACTION_PRINT_DEC	0x20
+#define ACTION_IMPORT		0x40
+#define ACTION_EXPORT		0x80
 
 #define EDIT_APPEND	0
 #define EDIT_WRITE	1
@@ -173,31 +175,10 @@ bad_name:
 }
 
 static void
-show_variable(char *guid_name, int display_type)
+show_variable_data(efi_guid_t guid, const char *name, uint32_t attributes,
+		   uint8_t *data, size_t data_size,
+		   int display_type)
 {
-	efi_guid_t guid = efi_guid_empty;
-	char *name = NULL;
-	int rc;
-
-	uint8_t *data = NULL;
-	size_t data_size = 0;
-	uint32_t attributes;
-
-	parse_name(guid_name, &name, &guid);
-	if (!name || efi_guid_is_empty(&guid)) {
-		fprintf(stderr, "efivar: could not parse variable name.\n");
-		show_errors();
-		exit(1);
-	}
-
-	errno = 0;
-	rc = efi_get_variable(guid, name, &data, &data_size, &attributes);
-	if (rc < 0) {
-		fprintf(stderr, "efivar: show variable: %m\n");
-		show_errors();
-		exit(1);
-	}
-
 	if (display_type == SHOW_VERBOSE) {
 		printf("GUID: "GUID_FORMAT "\n",
 		       guid.a, guid.b, guid.c, bswap_16(guid.d),
@@ -257,6 +238,117 @@ show_variable(char *guid_name, int display_type)
 		}
 		printf("\n");
 	}
+}
+
+static void
+show_variable(char *guid_name, int display_type)
+{
+	efi_guid_t guid = efi_guid_empty;
+	char *name = NULL;
+	int rc;
+
+	uint8_t *data = NULL;
+	size_t data_size = 0;
+	uint32_t attributes;
+
+	parse_name(guid_name, &name, &guid);
+	if (!name || efi_guid_is_empty(&guid)) {
+		fprintf(stderr, "efivar: could not parse variable name.\n");
+		show_errors();
+		exit(1);
+	}
+
+	errno = 0;
+	rc = efi_get_variable(guid, name, &data, &data_size, &attributes);
+	if (rc < 0) {
+		fprintf(stderr, "efivar: show variable: %m\n");
+		show_errors();
+		exit(1);
+	}
+
+	show_variable_data(guid, name, attributes,
+			   data, data_size, display_type);
+
+	free(name);
+	if (data)
+		free(data);
+}
+
+static void
+save_variable_data(efi_variable_t *var, char *outfile, bool dmpstore)
+{
+	FILE *out = NULL;
+	ssize_t sz;
+	uint8_t *data = NULL;
+	size_t datasz = 0;
+	ssize_t (*export)(efi_variable_t *var, uint8_t *data, size_t size) =
+		dmpstore ? efi_variable_export_dmpstore : efi_variable_export;
+
+	out = fopen(outfile, "w");
+	if (!out)
+		err(1, "Could not open \"%s\" for writing", outfile);
+
+	sz = export(var, data, datasz);
+	data = calloc(sz, 1);
+	if (!data)
+		err(1, "Could not allocate memory");
+	datasz = sz;
+
+	sz = export(var, data, datasz);
+	if (sz < 0)
+		err(1, "Could not format data");
+	datasz = sz;
+
+	sz = fwrite(data, 1, datasz, out);
+	if (sz < (ssize_t)datasz)
+		err(1, "Could not write to \"%s\"", outfile);
+
+	fflush(out);
+	fclose(out);
+}
+
+static void
+save_variable(char *guid_name, char *outfile, bool dmpstore)
+{
+	efi_guid_t guid = efi_guid_empty;
+	char *name = NULL;
+	int rc;
+
+	uint8_t *data = NULL;
+	size_t data_size = 0;
+	uint32_t attributes = 7;
+	efi_variable_t *var;
+
+	parse_name(guid_name, &name, &guid);
+	if (!name || efi_guid_is_empty(&guid)) {
+		fprintf(stderr, "efivar: could not parse variable name.\n");
+		show_errors();
+		exit(1);
+	}
+
+	errno = 0;
+	rc = efi_get_variable(guid, name, &data, &data_size, &attributes);
+	if (rc < 0) {
+		fprintf(stderr, "efivar: show variable: %m\n");
+		show_errors();
+		exit(1);
+	}
+
+	var = efi_variable_alloc();
+	if (!var) {
+		fprintf(stderr, "efivar: could not allocate variable storage.\n");
+		show_errors();
+		exit(1);
+	}
+
+	efi_variable_set_name(var, (unsigned char *)name);
+	efi_variable_set_guid(var, &guid);
+	efi_variable_set_attributes(var, attributes);
+	efi_variable_set_data(var, data, data_size);
+
+	save_variable_data(var, outfile, dmpstore);
+
+	efi_variable_free(var, false);
 
 	free(name);
 	if (data)
@@ -372,16 +464,18 @@ usage(int ret)
 	FILE *out = ret == 0 ? stdout : stderr;
 	fprintf(out,
 		"Usage: %s [OPTION...]\n"
+		"  -t, --attributes=<attributes>     attributes to use on append\n"
 		"  -l, --list                        list current variables\n"
 		"  -p, --print                       print variable specified by --name\n"
+		"  -D, --dmpstore                    use DMPSTORE format when exporting\n"
 		"  -d, --print-decimal               print variable in decimal values specified\n"
 		"                                    by --name\n"
 		"  -n, --name=<guid-name>            variable to manipulate, in the form\n"
 		"                                    8be4df61-93ca-11d2-aa0d-00e098032b8c-Boot0000\n"
 		"  -a, --append                      append to variable specified by --name\n"
+		"  -f, --datafile=<file>             load or save variable contents from <file>\n"
 		"  -e, --export=<file>               export variable to <file>\n"
-		"  -f, --fromfile=<file>             use data from <file>\n"
-		"  -t, --attributes=<attributes>     attributes to use on append\n"
+		"  -i, --import=<file>               import variable from <file\n"
 		"  -L, --list-guids                  show internal guid list\n"
 		"  -w, --write                       write to variable specified by --name\n\n"
 		"Help options:\n"
@@ -398,55 +492,79 @@ int main(int argc, char *argv[])
 	int action = 0;
 	uint8_t *data = NULL;
 	size_t data_size = 0;
-	char *name = NULL;
-	char *file = NULL;
-	uint32_t attributes = 0;
-	char *sopts = "lpdn:af:t:Lw?";
-	struct option lopts[] =
-		{ {"list", no_argument, 0, 'l'},
-		  {"print", no_argument, 0, 'p'},
-		  {"print-decimal", no_argument, 0, 'd'},
-		  {"name", required_argument, 0, 'n'},
-		  {"append", no_argument, 0, 'a'},
-		  {"fromfile", required_argument, 0, 'f'},
-		  {"attributes", required_argument, 0, 't'},
-		  {"list-guids", no_argument, 0, 'L'},
-		  {"write", no_argument, 0, 'w'},
-		  {"help", no_argument, 0, '?'},
-		  {"usage", no_argument, 0, 0},
-		  {0, 0, 0, 0}
-		};
+	char *guid_name = NULL;
+	char *infile = NULL;
+	char *outfile = NULL;
+	char *datafile = NULL;
+	bool dmpstore = false;
+	int verbose = 0;
+	uint32_t attributes = EFI_VARIABLE_NON_VOLATILE
+			      | EFI_VARIABLE_BOOTSERVICE_ACCESS
+			      | EFI_VARIABLE_RUNTIME_ACCESS;
+	char *sopts = "aA:Dde:f:i:Llpn:vw?";
+	struct option lopts[] = {
+		{"append", no_argument, 0, 'a'},
+		{"attributes", required_argument, 0, 'A'},
+		{"datafile", required_argument, 0, 'f'},
+		{"dmpstore", no_argument, 0, 'D'},
+		{"export", required_argument, 0, 'e'},
+		{"help", no_argument, 0, '?'},
+		{"import", required_argument, 0, 'i'},
+		{"list", no_argument, 0, 'l'},
+		{"list-guids", no_argument, 0, 'L'},
+		{"name", required_argument, 0, 'n'},
+		{"print", no_argument, 0, 'p'},
+		{"print-decimal", no_argument, 0, 'd'},
+		{"usage", no_argument, 0, 0},
+		{"verbose", no_argument, 0, 'v'},
+		{"write", no_argument, 0, 'w'},
+		{0, 0, 0, 0}
+	};
 
 	while ((c = getopt_long(argc, argv, sopts, lopts, &i)) != -1) {
 		switch (c) {
-			case 'l':
-				action |= ACTION_LIST;
+			case 'A':
+				attributes = strtoul(optarg, NULL, 10);
+				if (errno == ERANGE || errno == EINVAL)
+					err(1,
+					    "invalid argument for -t: %s: %m\n",
+					    optarg);
 				break;
-			case 'p':
-				action |= ACTION_PRINT;
+			case 'a':
+				action |= ACTION_APPEND;
+				break;
+			case 'D':
+				dmpstore = true;
 				break;
 			case 'd':
 				action |= ACTION_PRINT_DEC;
 				break;
-			case 'n':
-				name = optarg;
-				break;
-			case 'a':
-				action |= ACTION_APPEND;
+			case 'e':
+				action |= ACTION_EXPORT;
+				outfile = optarg;
 				break;
 			case 'f':
-				file = optarg;
+				datafile = optarg;
 				break;
-			case 't':
-				attributes = strtoul(optarg, NULL, 10);
-				if (errno == ERANGE || errno == EINVAL)
-					err(1,
-					    "invalid argument for -t: %s: %m\n",
-					    optarg);
+			case 'i':
+				action |= ACTION_IMPORT;
+				infile = optarg;
 				break;
 			case 'L':
 				action |= ACTION_LIST_GUIDS;
 				break;
+			case 'l':
+				action |= ACTION_LIST;
+				break;
+			case 'n':
+				guid_name = optarg;
+				break;
+			case 'p':
+				action |= ACTION_PRINT;
+				break;
+			case 'v':
+				verbose += 1;
+				break;
 			case 'w':
 				action |= ACTION_WRITE;
 				break;
@@ -460,7 +578,9 @@ int main(int argc, char *argv[])
 		}
 	}
 
-	if (name)
+	efi_set_verbose(verbose, stderr);
+
+	if (guid_name && !outfile)
 		action |= ACTION_PRINT;
 
 	switch (action) {
@@ -468,23 +588,23 @@ int main(int argc, char *argv[])
 			list_all_variables();
 			break;
 		case ACTION_PRINT:
-			validate_name(name);
-			show_variable(name, SHOW_VERBOSE);
+			validate_name(guid_name);
+			show_variable(guid_name, SHOW_VERBOSE);
 			break;
 		case ACTION_PRINT_DEC | ACTION_PRINT:
-			validate_name(name);
-			show_variable(name, SHOW_DECIMAL);
+			validate_name(guid_name);
+			show_variable(guid_name, SHOW_DECIMAL);
 			break;
 		case ACTION_APPEND | ACTION_PRINT:
-			validate_name(name);
-			prepare_data(file, &data, &data_size);
-			edit_variable(name, data, data_size, attributes,
+			validate_name(guid_name);
+			prepare_data(infile, &data, &data_size);
+			edit_variable(guid_name, data, data_size, attributes,
 				      EDIT_APPEND);
 			break;
 		case ACTION_WRITE | ACTION_PRINT:
-			validate_name(name);
-			prepare_data(file, &data, &data_size);
-			edit_variable(name, data, data_size, attributes,
+			validate_name(guid_name);
+			prepare_data(infile, &data, &data_size);
+			edit_variable(guid_name, data, data_size, attributes,
 				      EDIT_WRITE);
 			break;
 		case ACTION_LIST_GUIDS: {
@@ -509,10 +629,108 @@ int main(int argc, char *argv[])
 					guid[i].symbol + strlen("efi_guid_"),
 					guid[i].symbol, guid[i].name);
 			}
+			break;
+					}
+		case ACTION_EXPORT:
+			if (datafile) {
+				char *name = NULL;
+				efi_guid_t guid = efi_guid_zero;
+				efi_variable_t *var;
+
+				parse_name(guid_name, &name, &guid);
+				prepare_data(datafile, &data, &data_size);
+
+				var = efi_variable_alloc();
+				if (!var)
+					err(1, "Could not allocate memory");
+
+				efi_variable_set_name(var, (unsigned char *)name);
+				efi_variable_set_guid(var, &guid);
+				efi_variable_set_attributes(var, attributes);
+				efi_variable_set_data(var, data, data_size);
+
+				save_variable_data(var, outfile, dmpstore);
+
+				efi_variable_free(var, false);
+			} else {
+				validate_name(guid_name);
+				save_variable(guid_name, outfile, dmpstore);
+			}
+			break;
+		case ACTION_IMPORT:
+		case ACTION_IMPORT | ACTION_PRINT:
+		case ACTION_IMPORT | ACTION_PRINT | ACTION_PRINT_DEC:
+			{
+				ssize_t sz;
+				efi_variable_t *var = NULL;
+				char *name;
+				efi_guid_t *guid;
+				uint64_t attributes;
+				int display_type = (action & ACTION_PRINT_DEC)
+					? SHOW_VERBOSE|SHOW_DECIMAL
+					: SHOW_VERBOSE;
+
+
+				prepare_data(infile, &data, &data_size);
+				sz = efi_variable_import(data, data_size, &var);
+				if (sz < 0)
+					err(1, "Could not import data from \"%s\"", infile);
+
+				free(data);
+				data = NULL;
+				data_size = 0;
+
+				name = (char *)efi_variable_get_name(var);
+				efi_variable_get_guid(var, &guid);
+				efi_variable_get_attributes(var, &attributes);
+				efi_variable_get_data(var, &data, &data_size);
+
+				if (datafile) {
+					FILE *out;
+					int rc;
+
+					out = fopen(datafile, "w");
+					if (!out)
+						err(1, "Could not open \"%s\" for writing",
+						    datafile);
+
+					rc = fwrite(data, data_size, 1, out);
+					if (rc < (long)data_size)
+						err(1, "Could not write to \"%s\"",
+						    datafile);
+
+					fclose(out);
+					free(guid_name);
+				}
+				if (action & ACTION_PRINT)
+					show_variable_data(*guid, name,
+						((uint32_t)(attributes & 0xffffffff)),
+						 data, data_size, display_type);
+
+				efi_variable_free(var, false);
+				break;
+			}
+		case ACTION_IMPORT | ACTION_EXPORT:
+			{
+				efi_variable_t *var = NULL;
+				ssize_t sz;
+
+				if (datafile)
+					errx(1, "--datafile cannot be used with --import and --export");
+
+				prepare_data(infile, &data, &data_size);
+				sz = efi_variable_import(data, data_size, &var);
+				if (sz < 0)
+					err(1, "Could not import data from \"%s\"", infile);
+
+				save_variable_data(var, outfile, dmpstore);
+
+				efi_variable_free(var, false);
+				break;
+			}
 		case ACTION_USAGE:
 		default:
 			usage(EXIT_FAILURE);
-		}
 	};
 
 	return 0;
diff --git a/src/export.c b/src/export.c
index 6b78412cce1..cfb021525ff 100644
--- a/src/export.c
+++ b/src/export.c
@@ -27,7 +27,7 @@
 
 #include "efivar.h"
 
-#define EFIVAR_MAGIC 0xf3df1597
+#define EFIVAR_MAGIC 0xf3df1597u
 
 #define ATTRS_UNSET 0xa5a5a5a5a5a5a5a5
 #define ATTRS_MASK 0xffffffff
@@ -50,12 +50,166 @@ struct efi_variable {
  *	uint32_t data_len;
  *	char16_t name[];
  *	uint8_t data[];
- *	uint32_t magic;
+ *	uint32_t crc32;
+ * }
+ *
+ * Unfortunately the exported structure from dmpstore is:
+ * struct {
+ *	uint32_t name_size; // in bytes
+ *	uint32_t data_size; // in bytes
+ *	char16_t name[];
+ *	efi_guid_t guid;
+ *	uint32_t attr;
+ *	unit8_t data[];
+ *	uint32_t crc32;
  * }
  */
 
-ssize_t NONNULL(1, 3) PUBLIC
-efi_variable_import(uint8_t *data, size_t size, efi_variable_t **var_out)
+#ifdef EFIVAR_BUILD_ENVIRONMENT
+#error wtf
+#endif
+
+ssize_t NONNULL(1, 3)
+efi_variable_import_dmpstore(uint8_t *data, size_t size,
+			     efi_variable_t **var_out)
+{
+	efi_variable_t var;
+	uint32_t namesz;
+	uint32_t datasz;
+	size_t min = sizeof (uint32_t)		/* name size */
+		   + sizeof (uint32_t)		/* data size */
+		   + sizeof (char16_t)		/* two bytes of name */
+		   + sizeof (efi_guid_t)	/* guid */
+		   + sizeof (uint32_t)		/* attr */
+		   + 1				/* one byte of data */
+		   + sizeof (uint32_t);		/* crc32 */
+	size_t sz = sizeof (uint32_t)		/* name size */
+		  + sizeof (uint32_t)		/* data size */
+		  + sizeof (efi_guid_t)		/* guid */
+		  + sizeof (uint32_t)		/* attr */
+		  + sizeof (uint32_t);		/* crc32 */
+	uint8_t *ptr = data;
+	uint32_t crc;
+	int saved_errno;
+
+	if (size <= min) {
+etoosmall:
+		errno = EINVAL;
+		efi_error("data size is too small for dmpstore variable (%zu < %zu)",
+			  size, min);
+		return -1;
+	}
+
+	memset(&var, 0, sizeof(var));
+
+	namesz = *(uint32_t *)ptr;
+	debug("namesz:%"PRIu32, namesz);
+	ptr += sizeof(uint32_t);
+
+	if (namesz <= 2) {
+		errno = EINVAL;
+		debug("name size (%"PRIu32") must be greater than 2", namesz);
+		return -1;
+	}
+
+	if (namesz % 2 != 0) {
+		errno = EINVAL;
+		efi_error("name size (%"PRIu32") cannot be odd", namesz);
+		return -1;
+	}
+
+	datasz = *(uint32_t *)ptr;
+	ptr += sizeof(uint32_t);
+	debug("datasz:%"PRIu32, datasz);
+
+	if (datasz == 0) {
+		errno = EINVAL;
+		efi_error("data size (%"PRIu32") must be nonzero", datasz);
+		return -1;
+	}
+
+	if (add(sz, namesz, &sz)) {
+overflow:
+		errno = EOVERFLOW;
+		efi_error("arithmetic overflow computing allocation size");
+		return -1;
+	}
+
+	if (add(sz, datasz, &min))
+		goto overflow;
+
+	if (size < min)
+		goto etoosmall;
+	size = min;
+
+	if (!(ptr[namesz - 1] == 0 && ptr[namesz -2] == 0)) {
+		errno = EINVAL;
+		efi_error("variable name is not properly terminated.");
+		return -1;
+	}
+
+	crc = efi_crc32(data, size - sizeof(uint32_t));
+	debug("efi_crc32(%p, %lu) -> 0x%"PRIx32", expected 0x%"PRIx32,
+	      data, size - sizeof(uint32_t), crc,
+	      *(uint32_t*)(data + size - sizeof(uint32_t)));
+
+	if (memcmp(data + size - sizeof(uint32_t),
+		    &crc, sizeof(uint32_t))) {
+		errno = EINVAL;
+		efi_error("crc32 did not match");
+		return -1;
+	}
+
+	var.name = ucs2_to_utf8(ptr, -1);
+	if (!var.name)
+		goto oom;
+	ptr += namesz;
+
+	var.guid = malloc(sizeof (efi_guid_t));
+	if (!var.guid)
+		goto oom;
+	memcpy(var.guid, ptr, sizeof (efi_guid_t));
+	ptr += sizeof (efi_guid_t);
+
+	var.attrs = *(uint32_t *)ptr;
+	ptr += sizeof(uint32_t);
+
+	var.data_size = datasz;
+	var.data = malloc(datasz);
+	if (!var.data) {
+		efi_error("Could not allocate %"PRIu32" bytes", datasz);
+		goto oom;
+	}
+	memcpy(var.data, ptr, datasz);
+
+	if (!*var_out) {
+		*var_out =malloc(sizeof (var));
+		if (!*var_out)
+			goto oom;
+		memcpy(*var_out, &var, sizeof (var));
+	} else {
+		return -1;
+	}
+	return size;
+oom:
+	saved_errno = errno;
+
+	if (var.guid)
+		free(var.guid);
+
+	if (var.name)
+		free(var.name);
+
+	if (var.data)
+		free(var.data);
+
+	errno = saved_errno;
+	efi_error("Could not allocate memory");
+	return -1;
+}
+
+ssize_t NONNULL(1, 3)
+efi_variable_import_efivar(uint8_t *data, size_t datasz, efi_variable_t **var_out)
 {
 	efi_variable_t var;
 	size_t min = sizeof (uint32_t) * 2	/* magic */
@@ -63,47 +217,83 @@ efi_variable_import(uint8_t *data, size_t size, efi_variable_t **var_out)
 		   + sizeof (uint64_t)		/* attr */
 		   + sizeof (efi_guid_t)	/* guid */
 		   + sizeof (uint32_t) * 2	/* name_len and data_len */
-		   + sizeof (char16_t)	/* two bytes of name */
-		   + 1;				/* one byte of data */
+		   + sizeof (char16_t)		/* two bytes of name */
+		   + 1				/* one byte of data */
+		   + 4;				/* crc32 */
+	uint32_t crc;
+	uint8_t *ptr = data;
+	uint32_t magic = EFIVAR_MAGIC;
+	int test;
+
 	errno = EINVAL;
-	if (size <= min)
+	if (datasz <= min)
 		return -1;
 
-	uint8_t *ptr = data;
-	uint32_t magic = EFIVAR_MAGIC;
-	if (memcmp(data, &magic, sizeof (uint32_t)) ||
-			memcmp(data + size - sizeof (uint32_t), &magic,
-				sizeof (uint32_t)))
+	test = memcmp(data, &magic, sizeof (uint32_t));
+	debug("test magic 0: cmp(0x%04x,0x%04x)->%d", *(uint32_t *)data, magic, test);
+	if (test) {
+		errno = EINVAL;
+		efi_error("MAGIC for file format did not match.");
 		return -1;
-	size -= sizeof (uint32_t);
+	}
+
 	ptr += sizeof (uint32_t);
 
+	debug("test version");
 	if (*(uint32_t *)ptr == 1) {
 		ptr += sizeof (uint32_t);
+		debug("version 1");
+
 		var.attrs = *(uint64_t *)ptr;
-		ptr += sizeof (uint32_t);
+		ptr += sizeof (uint64_t);
+		debug("var.attrs:0x%08"PRIx64, var.attrs);
 
 		var.guid = malloc(sizeof (efi_guid_t));
 		if (!var.guid)
 			return -1;
 		*var.guid = *(efi_guid_t *)ptr;
 		ptr += sizeof (efi_guid_t);
+		debug("var.guid:"GUID_FORMAT,
+		      var.guid->a, var.guid->b, var.guid->c,
+		      bswap_16(var.guid->d),
+		      var.guid->e[0], var.guid->e[1], var.guid->e[2],
+		      var.guid->e[3], var.guid->e[4], var.guid->e[5]);
 
 		uint32_t name_len = *(uint32_t *)ptr;
 		ptr += sizeof (uint32_t);
+		debug("name_len:%"PRIu32, name_len);
+
 		uint32_t data_len = *(uint32_t *)ptr;
 		ptr += sizeof (uint32_t);
+		debug("data_len:%"PRIu32, data_len);
+
+		min -= 3;
+		min += name_len;
+		min += data_len;
 
-		if (name_len < 1 ||
-		    name_len != ((data + size) - ptr - data_len) ||
+		if (name_len < 2 ||
+		    name_len > (datasz - data_len) ||
 		    data_len < 1 ||
-		    data_len != ((data + size) - ptr - name_len)) {
+		    data_len > (datasz - name_len)) {
 			int saved_errno = errno;
 			free(var.guid);
 			errno = saved_errno;
 			return -1;
 		}
 
+		crc = efi_crc32(data, datasz - sizeof(uint32_t));
+		debug("efi_crc32(%p, %lu) -> 0x%"PRIx32", expected 0x%"PRIx32,
+		      data, datasz - sizeof(uint32_t), crc,
+		      *(uint32_t*)(data + datasz - sizeof(uint32_t)));
+
+		if (memcmp(data + datasz - sizeof (uint32_t), &crc,
+			   sizeof (uint32_t))) {
+			free(var.guid);
+			errno = EINVAL;
+			efi_error("crc32 did not match");
+			return -1;
+		}
+
 		var.name = calloc(1, name_len + 1);
 		if (!var.name) {
 			int saved_errno = errno;
@@ -115,7 +305,8 @@ efi_variable_import(uint8_t *data, size_t size, efi_variable_t **var_out)
 		char16_t *wname = (char16_t *)ptr;
 		for (uint32_t i = 0; i < name_len; i++)
 			var.name[i] = wname[i] & 0xff;
-		ptr += name_len * 2;
+		ptr += name_len;
+		debug("name:%s", var.name);
 
 		var.data_size = data_len;
 		var.data = malloc(data_len);
@@ -143,31 +334,228 @@ efi_variable_import(uint8_t *data, size_t size, efi_variable_t **var_out)
 	} else {
 		return -1;
 	}
-	return size;
+	return min;
+}
+
+ssize_t NONNULL(1, 3) PUBLIC
+efi_variable_import(uint8_t *data, size_t size, efi_variable_t **var_out)
+{
+	ssize_t rc;
+
+	rc = efi_variable_import_efivar(data, size, var_out);
+	if (rc >= 0)
+		return rc;
+
+	rc = efi_variable_import_dmpstore(data, size, var_out);
+	return rc;
 }
 
 ssize_t NONNULL(1) PUBLIC
-efi_variable_export(efi_variable_t *var, uint8_t *data, size_t size)
+efi_variable_export_dmpstore(efi_variable_t *var, uint8_t *data, size_t datasz)
 {
-	size_t name_len = strlen((char *)var->name);
-
-	size_t needed = sizeof (uint32_t)		/* magic */
-		      + sizeof (uint32_t)		/* version */
-		      + sizeof (uint64_t)		/* attr */
-		      + sizeof (efi_guid_t)		/* guid */
-		      + sizeof (uint32_t)		/* name_len */
-		      + sizeof (uint32_t)		/* data_len */
-		      + sizeof (char16_t) * name_len	/* name */
-		      + var->data_size			/* data */
-		      + sizeof (uint32_t);		/* magic again */
-
-	if (!data || size == 0) {
+	uint32_t tmpu32;
+	ssize_t tmpssz;
+	uint32_t namesz;
+	uint32_t needed = sizeof (uint32_t)		/* name_size */
+			+ sizeof (uint32_t)		/* data_size */
+			+ 2				/* name */
+			+ sizeof (efi_guid_t)		/* guid */
+			+ sizeof (uint32_t)		/* attrs */
+			+ 1				/* data */
+			+ 4;				/* crc32 */
+	uint8_t *ptr;
+	uint32_t crc;
+
+	if (!var) {
+		errno = EINVAL;
+		efi_error("var cannot be NULL");
+		return -1;
+	}
+	if (!var->name) {
+		errno = EINVAL;
+		efi_error("var->name cannot be NULL");
+		return -1;
+	}
+	if (!var->data) {
+		errno = EINVAL;
+		efi_error("var->data cannot be NULL");
+		return -1;
+	}
+
+	debug("data: %p datasz: %zu", data, datasz);
+
+	namesz = utf8size(var->name, -1);
+	debug("sizeof(uint16_t):%zd * namesz:%"PRIu32, sizeof(uint16_t), namesz);
+	if (mul(sizeof (uint16_t), namesz, &namesz)) {
+overflow:
+		errno = EOVERFLOW;
+		efi_error("arithmetic overflow computing name size");
+		return -1;
+	}
+	debug("namesz -> %"PRIu32, namesz);
+
+	/*
+	 * Remove our stand-ins for name size and data size before we add
+	 * them back in.
+	 */
+	needed -= 3;
+
+	debug("needed:%"PRIu32" + namesz:%"PRIu32, needed, namesz);
+	if (add(needed, namesz, &needed))
+		goto overflow;
+	debug("needed -> %"PRIu32, needed);
+
+	debug("needed:%"PRIu32" + var->data_size:%zd", needed, var->data_size);
+	if (add(needed, var->data_size, &needed))
+		goto overflow;
+	debug("needed -> %"PRIu32, needed);
+
+	if (!data || datasz == 0) {
+		debug("data: %p datasz: %zd -> returning needed size %"PRIu32,
+		      data, datasz, needed);
 		return needed;
-	} else if (size < needed) {
-		return needed - size;
 	}
 
-	uint8_t *ptr = data;
+	debug("datasz:%zu needed: %"PRIu32, datasz, needed);
+	if (datasz < needed) {
+		efi_error("needed: %"PRIu32" datasz: %zd -> returning needed datasz %zu",
+			  needed, datasz, needed - datasz);
+		return needed - datasz;
+	}
+
+	ptr = data;
+
+	tmpssz = utf8_to_ucs2(ptr + 8, datasz - 8, true, var->name);
+	if (tmpssz < 0) {
+		efi_error("UTF-8 to UCS-2 conversion failed");
+		return -1;
+	}
+	tmpu32 = tmpssz;
+	tmpu32 *= sizeof(uint16_t);
+
+	debug("namesz:%"PRIu32" - tmpu32:%"PRIu32, namesz, tmpu32);
+	if (sub(namesz, tmpu32, &tmpu32))
+		goto overflow;
+	debug("tmpu32 -> %"PRIu32, tmpu32);
+
+	debug("namesz:%"PRIu32" - tmpu32:%"PRIu32, namesz, tmpu32);
+	if (sub(namesz, tmpu32, &namesz))
+		goto overflow;
+	debug("namesz -> %"PRIu32, namesz);
+
+	debug("needed:%"PRIu32" - tmpu32:%"PRIu32, needed, tmpu32);
+	if (sub(needed, tmpu32, &needed))
+		goto overflow;
+	debug("needed -> %"PRIu32, needed);
+
+	debug("datasz:%zu needed: %"PRIu32, datasz, needed);
+	if (datasz < needed) {
+		debug("needed: %"PRIu32" datasz: %zd -> returning needed datasz %"PRIu32,
+			  needed, datasz, needed);
+		return needed;
+	}
+
+	*(uint32_t *)ptr = namesz;
+	ptr += sizeof (uint32_t);
+
+	*(uint32_t *)ptr = var->data_size;
+	ptr += sizeof (uint32_t);
+
+	ptr += namesz;
+
+	memcpy(ptr, var->guid, sizeof (efi_guid_t));
+	ptr += sizeof(efi_guid_t);
+
+	*(uint32_t *)ptr = var->attrs;
+	ptr += sizeof (uint32_t);
+
+	memcpy(ptr, var->data, var->data_size);
+	ptr += var->data_size;
+
+	crc = efi_crc32(data, needed - sizeof(uint32_t));
+	debug("efi_crc32(%p, %lu) -> 0x%"PRIx32,
+	      data, needed - sizeof(uint32_t), crc);
+	*(uint32_t *)ptr = crc;
+
+	return needed;
+}
+
+ssize_t NONNULL(1) PUBLIC
+efi_variable_export(efi_variable_t *var, uint8_t *data, size_t datasz)
+{
+	uint32_t tmpu32;
+	ssize_t tmpssz;
+	uint32_t namesz;
+	uint32_t needed = sizeof (uint32_t)		/* magic */
+			+ sizeof (uint32_t)		/* version */
+			+ sizeof (uint64_t)		/* attr */
+			+ sizeof (efi_guid_t)		/* guid */
+			+ sizeof (uint32_t)		/* name_len */
+			+ sizeof (uint32_t)		/* data_len */
+			+ 2				/* name */
+			+ 1				/* data */
+			+ 4;				/* crc32 */
+	uint8_t *ptr;
+	uint32_t crc;
+
+	if (!var) {
+		errno = EINVAL;
+		efi_error("var cannot be NULL");
+		return -1;
+	}
+	if (!var->name) {
+		errno = EINVAL;
+		efi_error("var->name cannot be NULL");
+		return -1;
+	}
+	if (!var->data) {
+		errno = EINVAL;
+		efi_error("var->data cannot be NULL");
+		return -1;
+	}
+
+	debug("data: %p datasz: %zu", data, datasz);
+
+	namesz = utf8size(var->name, -1);
+	debug("sizeof(uint16_t):%zd * namesz:%"PRIu32, sizeof(uint16_t), namesz);
+	if (mul(sizeof (uint16_t), namesz, &namesz)) {
+overflow:
+		errno = EOVERFLOW;
+		efi_error("arithmetic overflow computing name size");
+		return -1;
+	}
+	debug("namesz -> %"PRIu32, namesz);
+
+	/*
+	 * Remove our stand-ins for name size and data size before we add
+	 * them back in.
+	 */
+	needed -= 3;
+
+	debug("needed:%"PRIu32" + namesz:%"PRIu32, needed, namesz);
+	if (add(needed, namesz, &needed))
+		goto overflow;
+	debug("needed -> %"PRIu32, needed);
+
+	debug("needed:%"PRIu32" + var->data_size:%zd", needed, var->data_size);
+	if (add(needed, var->data_size, &needed))
+		goto overflow;
+	debug("needed -> %"PRIu32, needed);
+
+	if (!data || datasz == 0) {
+		debug("data: %p datasz: %zd -> returning needed datasz %"PRIu32,
+		      data, datasz, needed);
+		return needed;
+	}
+
+	debug("datasz:%zu needed: %"PRIu32, datasz, needed);
+	if (datasz < needed) {
+		efi_error("needed: %"PRIu32" datasz: %zd -> returning needed datasz %zd",
+			  needed, datasz, needed - datasz);
+		return needed - datasz;
+	}
+
+	ptr = data;
 
 	*(uint32_t *)ptr = EFIVAR_MAGIC;
 	ptr += sizeof (uint32_t);
@@ -181,21 +569,51 @@ efi_variable_export(efi_variable_t *var, uint8_t *data, size_t size)
 	memcpy(ptr, var->guid, sizeof (efi_guid_t));
 	ptr += sizeof (efi_guid_t);
 
-	*(uint32_t *)ptr = (uint32_t)(sizeof (char16_t) * name_len);
+	tmpssz = utf8_to_ucs2(ptr + 8, datasz - 8, true, var->name);
+	if (tmpssz < 0) {
+		efi_error("UTF-8 to UCS-2 conversion failed");
+		return -1;
+	}
+	tmpu32 = tmpssz;
+	tmpu32 *= sizeof(uint16_t);
+
+	debug("namesz:%"PRIu32" - tmpu32:%"PRIu32, namesz, tmpu32);
+	if (sub(namesz, tmpu32, &tmpu32))
+		goto overflow;
+	debug("tmpu32 -> %"PRIu32, tmpu32);
+
+	debug("needed:%"PRIu32" - tmpu32:%"PRIu32, needed, tmpu32);
+	if (sub(needed, tmpu32, &needed))
+		goto overflow;
+	debug("needed -> %"PRIu32, needed);
+
+	debug("namesz:%"PRIu32" - tmpu32:%"PRIu32, namesz, tmpu32);
+	if (sub(namesz, tmpu32, &namesz))
+		goto overflow;
+	debug("namesz -> %"PRIu32, namesz);
+
+	debug("datasz:%zu needed: %"PRIu32, datasz, needed);
+	if (datasz < needed) {
+		efi_error("needed: %"PRIu32" datasz: %zd -> returning needed datasz %zd",
+			  needed, datasz, needed - datasz);
+		return needed - datasz;
+	}
+
+	*(uint32_t *)ptr = namesz;
 	ptr += sizeof (uint32_t);
 
 	*(uint32_t *)ptr = var->data_size;
 	ptr += sizeof (uint32_t);
 
-	for (uint32_t i = 0; i < name_len; i++) {
-		*(char16_t *)ptr = var->name[i];
-		ptr += sizeof (char16_t);
-	}
+	ptr += namesz;
 
 	memcpy(ptr, var->data, var->data_size);
 	ptr += var->data_size;
 
-	*(uint32_t *)ptr = EFIVAR_MAGIC;
+	crc = efi_crc32(data, needed - sizeof(uint32_t));
+	debug("efi_crc32(%p, %lu) -> 0x%"PRIx32,
+	      data, needed - sizeof(uint32_t), crc);
+	*(uint32_t *)ptr = crc;
 
 	return needed;
 }
diff --git a/src/gpt.c b/src/gpt.c
index aa4055b9812..8babafeb588 100644
--- a/src/gpt.c
+++ b/src/gpt.c
@@ -48,24 +48,6 @@ struct blkdev_ioctl_param {
 	char * block_contents;
 };
 
-/**
- * efi_crc32() - EFI version of crc32 function
- * @buf: buffer to calculate crc32 of
- * @len - length of buf
- *
- * Description: Returns EFI-style CRC32 value for @buf
- *
- * This function uses the little endian Ethernet polynomial
- * but seeds the function with ~0, and xor's with ~0 at the end.
- * Note, the EFI Specification, v1.02, has a reference to
- * Dr. Dobbs Journal, May 1994 (actually it's in May 1992).
- */
-static inline uint32_t
-efi_crc32(const void *buf, unsigned long len)
-{
-	return (crc32(buf, len, ~0L) ^ ~0L);
-}
-
 /**
  * is_pmbr_valid(): test Protective MBR for validity
  * @mbr: pointer to a legacy mbr structure
diff --git a/src/include/efivar/efivar.h b/src/include/efivar/efivar.h
index 729b6fe80f7..8ad14b9be57 100644
--- a/src/include/efivar/efivar.h
+++ b/src/include/efivar/efivar.h
@@ -139,6 +139,9 @@ extern ssize_t efi_variable_import(uint8_t *data, size_t size,
 extern ssize_t efi_variable_export(efi_variable_t *var, uint8_t *data,
 				size_t size)
 			__attribute__((__nonnull__ (1)));
+extern ssize_t efi_variable_export_dmpstore(efi_variable_t *var, uint8_t *data,
+				size_t size)
+			__attribute__((__nonnull__ (1)));
 
 extern efi_variable_t *efi_variable_alloc(void)
 			__attribute__((__visibility__ ("default")));
-- 
2.26.2