1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
|
From 68daa04654acbe1bbaa17ebfc23c371b39e69c6b Mon Sep 17 00:00:00 2001
From: Ilias Apalodimas <ilias.apalodimas@linaro.org>
Date: Wed, 18 Jun 2025 22:37:04 +0300
Subject: [PATCH] efivarfs: Update a file variable store On SetVariable RT
Embedded boards have hardware limitations when storing and managing EFI
variables. Some hardware comes with an eMMC & an RPMB partition which they
use to store the EFI variables securely. However, the vast majority of
boards (using U-Boot), stores the EFI variables in a file in the ESP.
This has a few limitations
- UEFI secure boot cannot be enabled as it can be very easily
overridden
- SetVariable at runtime is impossible to support
Distros and capsule updates on-disk do rely on the that service though
and U-Boot does implement a workaround.
U-Boot enables SetVariableRT in the RTPROP table and creates a memory backend,
so the linux kernel can naturally read and write variables via the efivarfs
filesystem. Those reads and writes end up in memory though. So they are visible
while the OS is live and are lost in the event of a reboot.
At the same time it also creates two EFI RO variables.
RTStorageVolatile -- Holds the filename the variables are stored relative to
the ESP
VarToFile -- Holds a binary dump of all the EFI variables that should be
preserved (BS, NV, RT).
By using these two variables we can persist the changes after reboots by
doing
dd if=/sys/firmware/efi/efivars/VarToFile-b2ac5fc9-92b7-4acd-aeac-11e818c3130c of=/boot/efi/ubootefi.var skip=4 bs=1
So let's plug this functionality into the efivafs backend and enable it
automatically if those variables are detected.
Signed-off-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
---
src/efivarfs.c | 157 +++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 153 insertions(+), 4 deletions(-)
diff --git a/src/efivarfs.c b/src/efivarfs.c
index 034d6c1..2dea252 100644
--- a/src/efivarfs.c
+++ b/src/efivarfs.c
@@ -28,6 +28,24 @@
# define EFIVARFS_MAGIC 0xde5e81e4
#endif
+/*
+ * RTStorageVolatile-b2ac5fc9-92b7-4acd-aeac-11e818c3130c holds the name of
+ * the file we need to update relative to the ESP
+ */
+#define NAME_RTSV "RTStorageVolatile"
+/*
+ * Namespace of the special EFI variables pointing to the file and data we
+ * need to update
+ */
+#define GUID_FILE_STORE_VARS \
+ EFI_GUID(0xB2AC5FC9,0x92B7,0x4ACD,0xAEAC,0x11,0xE8,0x18,0xC3,0x13,0x0C)
+
+static const char *esp_paths[] = {
+ "/boot/efi/",
+ "/boot/",
+ "/efi/"
+};
+
static char const default_efivarfs_path[] = "/sys/firmware/efi/efivars/";
static char *efivarfs_path;
@@ -64,6 +82,137 @@ fini_efivarfs_path(void)
}
}
+static int
+get_esp_filepath(const char *filename, char *filepath, size_t sz)
+{
+ size_t num_paths = sizeof(esp_paths) / sizeof(esp_paths[0]);
+ size_t rc;
+
+ for (size_t i = 0; i < num_paths; ++i) {
+ struct stat buffer;
+
+ rc = snprintf(filepath, sz, "%s%s", esp_paths[i], filename);
+ if (rc >= sz) {
+ fprintf(stderr, "Error: Filepath too big. Max allowed %ld\n", sz);
+ return -1;
+ }
+ if (!stat(filepath, &buffer))
+ return 0;
+ }
+
+ return -1;
+}
+
+static int
+get_esp_filename(char *filename, size_t sz)
+{
+ size_t size;
+ uint32_t attr;
+ uint8_t *data = NULL;
+ int rc = 0;
+
+ rc = efi_get_variable(GUID_FILE_STORE_VARS, NAME_RTSV, &data, &size, &attr);
+ if (rc < 0)
+ /*
+ * Return an error here so we can bail out and not try to
+ * write the file
+ */
+ return rc;
+
+ if (size > sz) {
+ fprintf(stderr, "Error: Filename too big. Max allowed %ld\n", sz);
+ free(data);
+ return -1;
+ }
+
+ memcpy(filename, data, sz);
+ free(data);
+
+ return 0;
+}
+
+#define make_efivarfs_path(str, guid, name) ({ \
+ asprintf(str, "%s%s-" GUID_FORMAT, get_efivarfs_path(), \
+ name, GUID_FORMAT_ARGS(&(guid))); \
+ })
+
+static void
+write_file(const char *filepath) {
+ size_t bytes_read;
+ unsigned char buffer[1024];
+ FILE *output_file = NULL;
+ FILE *var2file = NULL;
+ bool fail = false;
+ char *path;
+ int rc;
+
+ rc = make_efivarfs_path(&path, GUID_FILE_STORE_VARS, "VarToFile");
+ if (rc < 0) {
+ efi_error("make_efivarfs_path failed");
+ exit(1);
+ }
+
+ var2file = fopen(path, "rb");
+ if (!var2file) {
+ fprintf(stderr, "Error: Could not open file '%s'\n", path);
+ goto err;
+ }
+
+ output_file = fopen(filepath, "wb");
+ if (!output_file) {
+ fprintf(stderr, "Error: Could not open file '%s'\n", filepath);
+ goto err;
+ }
+
+ if (fread(buffer, 1, 4, var2file) < 4) {
+ fprintf(stderr, "Error: Could not skip first 4 bytes or '%s' file is too small\n", filepath);
+ fail = true;
+ goto err;
+ }
+
+ while ((bytes_read = fread(buffer, 1, sizeof(buffer), var2file)) > 0) {
+ size_t total_written = 0;
+ while (total_written < bytes_read) {
+ size_t written = fwrite(buffer + total_written, 1, bytes_read - total_written, output_file);
+ if (!written) {
+ fprintf(stderr, "Error: Could not write data to ESP '%s' file\n", filepath);
+ fail = true;
+ goto err;
+ }
+ total_written += written;
+ }
+ }
+
+err:
+ if (path)
+ free(path);
+ if (var2file)
+ fclose(var2file);
+ if (output_file)
+ fclose(output_file);
+
+ if (fail)
+ exit(1);
+}
+
+static void
+efi_update_var_file(void)
+{
+ int rc = 0;
+ char filename[PATH_MAX / 4] = { 0 };
+ char filepath[PATH_MAX] = { 0 };
+
+ rc = get_esp_filename(filename, sizeof(filename));
+ if (rc < 0)
+ return;
+
+ rc = get_esp_filepath(filename, filepath, sizeof(filepath));
+ if (!rc)
+ write_file(filepath);
+ else
+ fprintf(stderr, "Error: '%s' file not found in ESP partition. EFI variable changes won't persist reboots\n", filename);
+}
+
static int
efivarfs_probe(void)
{
@@ -94,10 +243,6 @@ efivarfs_probe(void)
return 0;
}
-#define make_efivarfs_path(str, guid, name) ({ \
- asprintf(str, "%s%s-" GUID_FORMAT, get_efivarfs_path(), \
- name, GUID_FORMAT_ARGS(&(guid))); \
- })
static int
efivarfs_set_fd_immutable(int fd, int immutable)
@@ -312,6 +457,8 @@ efivarfs_del_variable(efi_guid_t guid, const char *name)
if (rc < 0)
efi_error("unlink failed");
+ efi_update_var_file();
+
__typeof__(errno) errno_value = errno;
free(path);
errno = errno_value;
@@ -442,6 +589,8 @@ efivarfs_set_variable(efi_guid_t guid, const char *name, const uint8_t *data,
goto err;
}
+ efi_update_var_file();
+
/* we're done */
ret = 0;
--
2.43.0
|