File: 0002-efivarfs-Update-a-file-variable-store-On-SetVariable.patch

package info (click to toggle)
efivar 39-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 4,264 kB
  • sloc: ansic: 14,990; makefile: 528; sh: 21
file content (240 lines) | stat: -rw-r--r-- 6,386 bytes parent folder | download
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