]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/efivars.c
Merge pull request #15934 from keszybz/docs-and-test-fix
[thirdparty/systemd.git] / src / basic / efivars.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <limits.h>
6 #include <linux/fs.h>
7 #include <stdlib.h>
8 #include <sys/stat.h>
9 #include <unistd.h>
10
11 #include "sd-id128.h"
12
13 #include "alloc-util.h"
14 #include "chattr-util.h"
15 #include "efivars.h"
16 #include "fd-util.h"
17 #include "io-util.h"
18 #include "macro.h"
19 #include "stdio-util.h"
20 #include "strv.h"
21 #include "time-util.h"
22 #include "utf8.h"
23 #include "virt.h"
24
25 #if ENABLE_EFI
26
27 /* Reads from efivarfs sometimes fail with EINTR. Retry that many times. */
28 #define EFI_N_RETRIES 5
29 #define EFI_RETRY_DELAY (50 * USEC_PER_MSEC)
30
31 char* efi_variable_path(sd_id128_t vendor, const char *name) {
32 char *p;
33
34 if (asprintf(&p,
35 "/sys/firmware/efi/efivars/%s-" SD_ID128_UUID_FORMAT_STR,
36 name, SD_ID128_FORMAT_VAL(vendor)) < 0)
37 return NULL;
38
39 return p;
40 }
41
42 int efi_get_variable(
43 sd_id128_t vendor,
44 const char *name,
45 uint32_t *ret_attribute,
46 void **ret_value,
47 size_t *ret_size) {
48
49 _cleanup_close_ int fd = -1;
50 _cleanup_free_ char *p = NULL;
51 _cleanup_free_ void *buf = NULL;
52 struct stat st;
53 usec_t begin;
54 uint32_t a;
55 ssize_t n;
56
57 assert(name);
58
59 p = efi_variable_path(vendor, name);
60 if (!p)
61 return -ENOMEM;
62
63 if (!ret_value && !ret_size && !ret_attribute) {
64 /* If caller is not interested in anything, just check if the variable exists and is
65 * readable. */
66 if (access(p, R_OK) < 0)
67 return -errno;
68
69 return 0;
70 }
71
72 if (DEBUG_LOGGING)
73 begin = now(CLOCK_MONOTONIC);
74
75 fd = open(p, O_RDONLY|O_NOCTTY|O_CLOEXEC);
76 if (fd < 0)
77 return log_debug_errno(errno, "open(\"%s\") failed: %m", p);
78
79 if (fstat(fd, &st) < 0)
80 return -errno;
81 if (st.st_size < 4)
82 return -ENODATA;
83 if (st.st_size > 4*1024*1024 + 4)
84 return -E2BIG;
85
86 if (ret_value || ret_attribute) {
87 /* The kernel ratelimits reads from the efivarfs because EFI is inefficient, and we'll
88 * occasionally fail with EINTR here. A slowdown is better than a failure for us, so
89 * retry a few times and eventually fail with -EBUSY.
90 *
91 * See https://github.com/torvalds/linux/blob/master/fs/efivarfs/file.c#L75
92 * and
93 * https://github.com/torvalds/linux/commit/bef3efbeb897b56867e271cdbc5f8adaacaeb9cd.
94 */
95 for (unsigned try = 0;; try++) {
96 n = read(fd, &a, sizeof(a));
97 if (n >= 0)
98 break;
99 log_debug_errno(errno, "read from \"%s\" failed: %m", p);
100 if (errno != EINTR)
101 return -errno;
102 if (try >= EFI_N_RETRIES)
103 return -EBUSY;
104 usleep(EFI_RETRY_DELAY);
105 }
106
107 if (n != sizeof(a))
108 return -EIO;
109 }
110
111 if (ret_value) {
112 buf = malloc(st.st_size - 4 + 2);
113 if (!buf)
114 return -ENOMEM;
115
116 n = read(fd, buf, (size_t) st.st_size - 4);
117 if (n < 0)
118 return -errno;
119 assert(n <= st.st_size - 4);
120
121 /* Always NUL terminate (2 bytes, to protect UTF-16) */
122 ((char*) buf)[n] = 0;
123 ((char*) buf)[n + 1] = 0;
124 } else
125 /* Assume that the reported size is accurate */
126 n = st.st_size - 4;
127
128 if (DEBUG_LOGGING) {
129 char ts[FORMAT_TIMESPAN_MAX];
130 usec_t end;
131
132 end = now(CLOCK_MONOTONIC);
133 if (end > begin + EFI_RETRY_DELAY)
134 log_debug("Detected slow EFI variable read access on " SD_ID128_FORMAT_STR "-%s: %s",
135 SD_ID128_FORMAT_VAL(vendor), name, format_timespan(ts, sizeof(ts), end - begin, 1));
136 }
137
138 /* Note that efivarfs interestingly doesn't require ftruncate() to update an existing EFI variable
139 * with a smaller value. */
140
141 if (ret_attribute)
142 *ret_attribute = a;
143
144 if (ret_value)
145 *ret_value = TAKE_PTR(buf);
146
147 if (ret_size)
148 *ret_size = n;
149
150 return 0;
151 }
152
153 int efi_get_variable_string(sd_id128_t vendor, const char *name, char **p) {
154 _cleanup_free_ void *s = NULL;
155 size_t ss = 0;
156 int r;
157 char *x;
158
159 r = efi_get_variable(vendor, name, NULL, &s, &ss);
160 if (r < 0)
161 return r;
162
163 x = utf16_to_utf8(s, ss);
164 if (!x)
165 return -ENOMEM;
166
167 *p = x;
168 return 0;
169 }
170
171 int efi_set_variable(
172 sd_id128_t vendor,
173 const char *name,
174 const void *value,
175 size_t size) {
176
177 struct var {
178 uint32_t attr;
179 char buf[];
180 } _packed_ * _cleanup_free_ buf = NULL;
181 _cleanup_free_ char *p = NULL;
182 _cleanup_close_ int fd = -1;
183 bool saved_flags_valid = false;
184 unsigned saved_flags;
185 int r;
186
187 assert(name);
188 assert(value || size == 0);
189
190 p = efi_variable_path(vendor, name);
191 if (!p)
192 return -ENOMEM;
193
194 /* Newer efivarfs protects variables that are not in a whitelist with FS_IMMUTABLE_FL by default, to protect
195 * them for accidental removal and modification. We are not changing these variables accidentally however,
196 * hence let's unset the bit first. */
197
198 r = chattr_path(p, 0, FS_IMMUTABLE_FL, &saved_flags);
199 if (r < 0 && r != -ENOENT)
200 log_debug_errno(r, "Failed to drop FS_IMMUTABLE_FL flag from '%s', ignoring: %m", p);
201
202 saved_flags_valid = r >= 0;
203
204 if (size == 0) {
205 if (unlink(p) < 0) {
206 r = -errno;
207 goto finish;
208 }
209
210 return 0;
211 }
212
213 fd = open(p, O_WRONLY|O_CREAT|O_NOCTTY|O_CLOEXEC, 0644);
214 if (fd < 0) {
215 r = -errno;
216 goto finish;
217 }
218
219 buf = malloc(sizeof(uint32_t) + size);
220 if (!buf) {
221 r = -ENOMEM;
222 goto finish;
223 }
224
225 buf->attr = EFI_VARIABLE_NON_VOLATILE|EFI_VARIABLE_BOOTSERVICE_ACCESS|EFI_VARIABLE_RUNTIME_ACCESS;
226 memcpy(buf->buf, value, size);
227
228 r = loop_write(fd, buf, sizeof(uint32_t) + size, false);
229 if (r < 0)
230 goto finish;
231
232 r = 0;
233
234 finish:
235 if (saved_flags_valid) {
236 int q;
237
238 /* Restore the original flags field, just in case */
239 if (fd < 0)
240 q = chattr_path(p, saved_flags, FS_IMMUTABLE_FL, NULL);
241 else
242 q = chattr_fd(fd, saved_flags, FS_IMMUTABLE_FL, NULL);
243 if (q < 0)
244 log_debug_errno(q, "Failed to restore FS_IMMUTABLE_FL on '%s', ignoring: %m", p);
245 }
246
247 return r;
248 }
249
250 int efi_set_variable_string(sd_id128_t vendor, const char *name, const char *v) {
251 _cleanup_free_ char16_t *u16 = NULL;
252
253 u16 = utf8_to_utf16(v, strlen(v));
254 if (!u16)
255 return -ENOMEM;
256
257 return efi_set_variable(vendor, name, u16, (char16_strlen(u16) + 1) * sizeof(char16_t));
258 }
259
260 bool is_efi_boot(void) {
261 static int cache = -1;
262
263 if (cache < 0) {
264 if (detect_container() > 0)
265 cache = false;
266 else
267 cache = access("/sys/firmware/efi/", F_OK) >= 0;
268 }
269
270 return cache;
271 }
272
273 static int read_flag(const char *varname) {
274 _cleanup_free_ void *v = NULL;
275 uint8_t b;
276 size_t s;
277 int r;
278
279 if (!is_efi_boot()) /* If this is not an EFI boot, assume the queried flags are zero */
280 return 0;
281
282 r = efi_get_variable(EFI_VENDOR_GLOBAL, varname, NULL, &v, &s);
283 if (r < 0)
284 return r;
285
286 if (s != 1)
287 return -EINVAL;
288
289 b = *(uint8_t *)v;
290 return !!b;
291 }
292
293 bool is_efi_secure_boot(void) {
294 static int cache = -1;
295
296 if (cache < 0)
297 cache = read_flag("SecureBoot");
298
299 return cache > 0;
300 }
301
302 bool is_efi_secure_boot_setup_mode(void) {
303 static int cache = -1;
304
305 if (cache < 0)
306 cache = read_flag("SetupMode");
307
308 return cache > 0;
309 }
310
311 int systemd_efi_options_variable(char **line) {
312 const char *e;
313 int r;
314
315 assert(line);
316
317 /* For testing purposes it is sometimes useful to be able to override this */
318 e = secure_getenv("SYSTEMD_EFI_OPTIONS");
319 if (e) {
320 char *m;
321
322 m = strdup(e);
323 if (!m)
324 return -ENOMEM;
325
326 *line = m;
327 return 0;
328 }
329
330 /* In SecureBoot mode this is probably not what you want. As your cmdline is cryptographically signed
331 * like when using Type #2 EFI Unified Kernel Images (https://systemd.io/BOOT_LOADER_SPECIFICATION/)
332 * The user's intention is then that the cmdline should not be modified. You want to make sure that
333 * the system starts up as exactly specified in the signed artifact.
334 *
335 * (NB: to make testing purposes we still check the $SYSTEMD_EFI_OPTIONS env var above, even when in
336 * SecureBoot mode.) */
337 if (is_efi_secure_boot()) {
338 _cleanup_free_ char *k;
339
340 k = efi_variable_path(EFI_VENDOR_SYSTEMD, "SystemdOptions");
341 if (!k)
342 return -ENOMEM;
343
344 /* Let's be helpful with the returned error and check if the variable exists at all. If it
345 * does, let's return a recognizable error (EPERM), and if not ENODATA. */
346
347 if (access(k, F_OK) < 0)
348 return errno == ENOENT ? -ENODATA : -errno;
349
350 return -EPERM;
351 }
352
353 r = efi_get_variable_string(EFI_VENDOR_SYSTEMD, "SystemdOptions", line);
354 if (r == -ENOENT)
355 return -ENODATA;
356
357 return r;
358 }
359 #endif