/src/systemd/src/basic/efivars.c
Line | Count | Source |
1 | | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | | |
3 | | #include <fcntl.h> |
4 | | #include <stdlib.h> |
5 | | #include <sys/stat.h> |
6 | | #include <sys/uio.h> |
7 | | #include <time.h> |
8 | | #include <unistd.h> |
9 | | |
10 | | #include "alloc-util.h" |
11 | | #include "chattr-util.h" |
12 | | #include "efivars.h" |
13 | | #include "fd-util.h" |
14 | | #include "io-util.h" |
15 | | #include "log.h" |
16 | | #include "memory-util.h" |
17 | | #include "stat-util.h" |
18 | | #include "string-util.h" |
19 | | #include "time-util.h" |
20 | | #include "utf8.h" |
21 | | #include "virt.h" |
22 | | |
23 | | #if ENABLE_EFI |
24 | | |
25 | | /* Reads from efivarfs sometimes fail with EINTR. Retry that many times. */ |
26 | 0 | #define EFI_N_RETRIES_NO_DELAY 20 |
27 | 0 | #define EFI_N_RETRIES_TOTAL 25 |
28 | 0 | #define EFI_RETRY_DELAY (50 * USEC_PER_MSEC) |
29 | | |
30 | | int efi_get_variable( |
31 | | const char *variable, |
32 | | uint32_t *ret_attribute, |
33 | | void **ret_value, |
34 | 0 | size_t *ret_size) { |
35 | |
|
36 | 0 | int r; |
37 | 0 | usec_t begin = 0; /* Unnecessary initialization to appease gcc */ |
38 | |
|
39 | 0 | assert(variable); |
40 | |
|
41 | 0 | const char *p = strjoina("/sys/firmware/efi/efivars/", variable); |
42 | |
|
43 | 0 | if (DEBUG_LOGGING) { |
44 | 0 | log_debug("Reading EFI variable %s.", p); |
45 | 0 | begin = now(CLOCK_MONOTONIC); |
46 | 0 | } |
47 | |
|
48 | 0 | _cleanup_close_ int fd = open(p, O_RDONLY|O_NOCTTY|O_CLOEXEC); |
49 | 0 | if (fd < 0) |
50 | 0 | return log_debug_errno(errno, "open(\"%s\") failed: %m", p); |
51 | | |
52 | 0 | uint32_t attr; |
53 | 0 | _cleanup_free_ char *buf = NULL; |
54 | 0 | ssize_t n; |
55 | | |
56 | | /* The kernel ratelimits reads from the efivarfs because EFI is inefficient, and we'll occasionally |
57 | | * fail with EINTR here. A slowdown is better than a failure for us, so retry a few times and |
58 | | * eventually fail with -EBUSY. |
59 | | * |
60 | | * See https://github.com/torvalds/linux/blob/master/fs/efivarfs/file.c#L75 and |
61 | | * https://github.com/torvalds/linux/commit/bef3efbeb897b56867e271cdbc5f8adaacaeb9cd. |
62 | | * |
63 | | * The variable may also be overwritten between the stat and read. If we find out that the new |
64 | | * contents are longer, try again. |
65 | | */ |
66 | 0 | for (unsigned try = 0;; try++) { |
67 | 0 | struct stat st; |
68 | |
|
69 | 0 | if (fstat(fd, &st) < 0) |
70 | 0 | return log_debug_errno(errno, "fstat(\"%s\") failed: %m", p); |
71 | | |
72 | 0 | r = stat_verify_regular(&st); |
73 | 0 | if (r < 0) |
74 | 0 | return log_debug_errno(r, "EFI variable '%s' is not a regular file, refusing: %m", p); |
75 | | |
76 | 0 | if (st.st_size == 0) /* for uncommitted variables, see below */ |
77 | 0 | return log_debug_errno(SYNTHETIC_ERRNO(ENOENT), "EFI variable '%s' is uncommitted", p); |
78 | 0 | if ((uint64_t) st.st_size < sizeof(attr)) |
79 | 0 | return log_debug_errno(SYNTHETIC_ERRNO(ENODATA), "EFI variable '%s' is shorter than %zu bytes, refusing.", p, sizeof(attr)); |
80 | 0 | if ((uint64_t) st.st_size > sizeof(attr) + 4 * U64_MB) |
81 | 0 | return log_debug_errno(SYNTHETIC_ERRNO(E2BIG), "EFI variable '%s' is ridiculously large, refusing.", p); |
82 | | |
83 | 0 | if (!ret_attribute && !ret_value) { |
84 | | /* No need to read anything, return the reported size. */ |
85 | 0 | n = st.st_size; |
86 | 0 | break; |
87 | 0 | } |
88 | | |
89 | | /* We want +1 for the read call, and +3 for the additional terminating bytes added below. */ |
90 | 0 | free(buf); |
91 | 0 | buf = malloc((size_t) st.st_size - sizeof(attr) + CONST_MAX(1, 3)); |
92 | 0 | if (!buf) |
93 | 0 | return -ENOMEM; |
94 | | |
95 | 0 | struct iovec iov[] = { |
96 | 0 | { &attr, sizeof(attr) }, |
97 | 0 | { buf, (size_t) st.st_size - sizeof(attr) + 1 }, |
98 | 0 | }; |
99 | |
|
100 | 0 | n = readv(fd, iov, 2); |
101 | 0 | if (n < 0) { |
102 | 0 | if (errno != EINTR) |
103 | 0 | return log_debug_errno(errno, "Reading from '%s' failed: %m", p); |
104 | | |
105 | 0 | log_debug("Reading from '%s' failed with EINTR, retrying.", p); |
106 | 0 | } else if ((size_t) n == sizeof(attr) + st.st_size + 1) |
107 | | /* We need to try again with a bigger buffer, the variable was apparently changed concurrently? */ |
108 | 0 | log_debug("EFI variable '%s' larger than expected, retrying.", p); |
109 | 0 | else { |
110 | 0 | assert((size_t) n < sizeof(attr) + st.st_size + 1); |
111 | 0 | break; |
112 | 0 | } |
113 | | |
114 | 0 | if (try >= EFI_N_RETRIES_TOTAL) |
115 | 0 | return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "Reading EFI variable '%s' failed even after %u tries, giving up.", p, try); |
116 | 0 | if (try >= EFI_N_RETRIES_NO_DELAY) |
117 | 0 | (void) usleep_safe(EFI_RETRY_DELAY); |
118 | | |
119 | | /* Start from the beginning */ |
120 | 0 | (void) lseek(fd, 0, SEEK_SET); |
121 | 0 | } |
122 | | |
123 | | /* Unfortunately kernel reports EOF if there's an inconsistency between efivarfs var list and |
124 | | * what's actually stored in firmware, c.f. #34304. A zero size env var is not allowed in EFI |
125 | | * and hence the variable doesn't really exist in the backing store as long as it is zero |
126 | | * sized, and the kernel calls this "uncommitted". Hence we translate EOF back to ENOENT |
127 | | * here, as with kernel behavior before |
128 | | * https://github.com/torvalds/linux/commit/3fab70c165795431f00ddf9be8b84ddd07bd1f8f. |
129 | | * |
130 | | * If the kernel changes behaviour (to flush dentries on resume), we can drop this at some |
131 | | * point in the future. But note that the commit is 11 years old at this point so we'll need |
132 | | * to deal with the current behaviour for a long time. |
133 | | */ |
134 | 0 | if (n == 0) |
135 | 0 | return log_debug_errno(SYNTHETIC_ERRNO(ENOENT), |
136 | 0 | "EFI variable %s is uncommitted", p); |
137 | 0 | if ((size_t) n < sizeof(attr)) |
138 | 0 | return log_debug_errno(SYNTHETIC_ERRNO(EIO), |
139 | 0 | "Read %zi bytes from EFI variable %s, expected >= %zu", n, p, sizeof(attr)); |
140 | 0 | size_t value_size = n - sizeof(attr); |
141 | |
|
142 | 0 | if (ret_attribute) |
143 | 0 | *ret_attribute = attr; |
144 | |
|
145 | 0 | if (ret_value) { |
146 | 0 | assert(buf); |
147 | | /* Always NUL-terminate (3 bytes, to properly protect UTF-16, even if truncated in |
148 | | * the middle of a character) */ |
149 | 0 | buf[value_size] = 0; |
150 | 0 | buf[value_size + 1] = 0; |
151 | 0 | buf[value_size + 2] = 0; |
152 | 0 | *ret_value = TAKE_PTR(buf); |
153 | 0 | } |
154 | |
|
155 | 0 | if (DEBUG_LOGGING) { |
156 | 0 | usec_t end = now(CLOCK_MONOTONIC); |
157 | 0 | if (end > begin + EFI_RETRY_DELAY) |
158 | 0 | log_debug("Detected slow EFI variable read access on %s: %s", |
159 | 0 | variable, FORMAT_TIMESPAN(end - begin, 1)); |
160 | 0 | } |
161 | | |
162 | | /* Note that efivarfs interestingly doesn't require ftruncate() to update an existing EFI variable |
163 | | * with a smaller value. */ |
164 | |
|
165 | 0 | if (ret_size) |
166 | 0 | *ret_size = value_size; |
167 | |
|
168 | 0 | return 0; |
169 | 0 | } |
170 | | |
171 | 0 | int efi_get_variable_string(const char *variable, char **ret) { |
172 | 0 | _cleanup_free_ void *s = NULL, *x = NULL; |
173 | 0 | size_t ss = 0; |
174 | 0 | int r; |
175 | |
|
176 | 0 | assert(variable); |
177 | |
|
178 | 0 | r = efi_get_variable(variable, NULL, &s, &ss); |
179 | 0 | if (r < 0) |
180 | 0 | return r; |
181 | | |
182 | 0 | x = utf16_to_utf8(s, ss); |
183 | 0 | if (!x) |
184 | 0 | return -ENOMEM; |
185 | | |
186 | 0 | if (ret) |
187 | 0 | *ret = TAKE_PTR(x); |
188 | |
|
189 | 0 | return 0; |
190 | 0 | } |
191 | | |
192 | 0 | int efi_get_variable_path(const char *variable, char **ret) { |
193 | 0 | int r; |
194 | |
|
195 | 0 | assert(variable); |
196 | |
|
197 | 0 | r = efi_get_variable_string(variable, ret); |
198 | 0 | if (r < 0) |
199 | 0 | return r; |
200 | | |
201 | 0 | if (ret) |
202 | 0 | efi_tilt_backslashes(*ret); |
203 | |
|
204 | 0 | return r; |
205 | 0 | } |
206 | | |
207 | 0 | static int efi_verify_variable(const char *variable, uint32_t attr, const void *value, size_t size) { |
208 | 0 | _cleanup_free_ void *buf = NULL; |
209 | 0 | size_t n; |
210 | 0 | uint32_t a; |
211 | 0 | int r; |
212 | |
|
213 | 0 | assert(variable); |
214 | 0 | assert(value || size == 0); |
215 | |
|
216 | 0 | r = efi_get_variable(variable, &a, &buf, &n); |
217 | 0 | if (r < 0) |
218 | 0 | return r; |
219 | | |
220 | 0 | return a == attr && memcmp_nn(buf, n, value, size) == 0; |
221 | 0 | } |
222 | | |
223 | 0 | int efi_set_variable(const char *variable, const void *value, size_t size) { |
224 | 0 | static const uint32_t attr = EFI_VARIABLE_NON_VOLATILE|EFI_VARIABLE_BOOTSERVICE_ACCESS|EFI_VARIABLE_RUNTIME_ACCESS; |
225 | |
|
226 | 0 | _cleanup_free_ struct var { |
227 | 0 | uint32_t attr; |
228 | 0 | char buf[]; |
229 | 0 | } _packed_ *buf = NULL; |
230 | 0 | _cleanup_close_ int fd = -EBADF; |
231 | 0 | bool saved_flags_valid = false; |
232 | 0 | unsigned saved_flags; |
233 | 0 | int r; |
234 | |
|
235 | 0 | assert(variable); |
236 | 0 | assert(value || size == 0); |
237 | | |
238 | | /* size 0 means removal, empty variable would not be enough for that */ |
239 | 0 | if (size > 0 && efi_verify_variable(variable, attr, value, size) > 0) { |
240 | 0 | log_debug("Variable '%s' is already in wanted state, skipping write.", variable); |
241 | 0 | return 0; |
242 | 0 | } |
243 | | |
244 | 0 | const char *p = strjoina("/sys/firmware/efi/efivars/", variable); |
245 | | |
246 | | /* Newer efivarfs protects variables that are not in an allow list with FS_IMMUTABLE_FL by default, |
247 | | * to protect them for accidental removal and modification. We are not changing these variables |
248 | | * accidentally however, hence let's unset the bit first. */ |
249 | |
|
250 | 0 | r = chattr_full(AT_FDCWD, p, |
251 | 0 | /* value= */ 0, |
252 | 0 | /* mask= */ FS_IMMUTABLE_FL, |
253 | 0 | /* ret_previous= */ &saved_flags, |
254 | 0 | /* ret_final= */ NULL, |
255 | 0 | /* flags= */ 0); |
256 | 0 | if (r < 0 && r != -ENOENT) |
257 | 0 | log_debug_errno(r, "Failed to drop FS_IMMUTABLE_FL flag from '%s', ignoring: %m", p); |
258 | |
|
259 | 0 | saved_flags_valid = r >= 0; |
260 | |
|
261 | 0 | if (size == 0) { |
262 | 0 | if (unlink(p) < 0) { |
263 | 0 | r = -errno; |
264 | 0 | goto finish; |
265 | 0 | } |
266 | | |
267 | 0 | return 0; |
268 | 0 | } |
269 | | |
270 | 0 | fd = open(p, O_WRONLY|O_CREAT|O_NOCTTY|O_CLOEXEC, 0644); |
271 | 0 | if (fd < 0) { |
272 | 0 | r = -errno; |
273 | 0 | goto finish; |
274 | 0 | } |
275 | | |
276 | 0 | buf = malloc(sizeof(uint32_t) + size); |
277 | 0 | if (!buf) { |
278 | 0 | r = -ENOMEM; |
279 | 0 | goto finish; |
280 | 0 | } |
281 | | |
282 | 0 | buf->attr = attr; |
283 | 0 | memcpy(buf->buf, value, size); |
284 | |
|
285 | 0 | r = loop_write(fd, buf, sizeof(uint32_t) + size); |
286 | 0 | if (r < 0) |
287 | 0 | goto finish; |
288 | | |
289 | | /* For some reason efivarfs doesn't update mtime automatically. Let's do it manually then. This is |
290 | | * useful for processes that cache EFI variables to detect when changes occurred. */ |
291 | 0 | if (futimens(fd, /* times= */ NULL) < 0) |
292 | 0 | log_debug_errno(errno, "Failed to update mtime/atime on %s, ignoring: %m", p); |
293 | |
|
294 | 0 | r = 0; |
295 | |
|
296 | 0 | finish: |
297 | 0 | if (saved_flags_valid) { |
298 | 0 | int q; |
299 | | |
300 | | /* Restore the original flags field, just in case */ |
301 | 0 | if (fd < 0) |
302 | 0 | q = chattr_path(p, saved_flags, FS_IMMUTABLE_FL); |
303 | 0 | else |
304 | 0 | q = chattr_fd(fd, saved_flags, FS_IMMUTABLE_FL); |
305 | 0 | if (q < 0) |
306 | 0 | log_debug_errno(q, "Failed to restore FS_IMMUTABLE_FL on '%s', ignoring: %m", p); |
307 | 0 | } |
308 | |
|
309 | 0 | return r; |
310 | 0 | } |
311 | | |
312 | 0 | int efi_set_variable_string(const char *variable, const char *value) { |
313 | 0 | _cleanup_free_ char16_t *u16 = NULL; |
314 | |
|
315 | 0 | u16 = utf8_to_utf16(value, SIZE_MAX); |
316 | 0 | if (!u16) |
317 | 0 | return -ENOMEM; |
318 | | |
319 | 0 | return efi_set_variable(variable, u16, (char16_strlen(u16) + 1) * sizeof(char16_t)); |
320 | 0 | } |
321 | | |
322 | | static int cache_efi_boot = -1; |
323 | | |
324 | 0 | bool set_efi_boot(bool b) { |
325 | 0 | return (cache_efi_boot = b); |
326 | 0 | } |
327 | | |
328 | 5.20k | bool is_efi_boot(void) { |
329 | 5.20k | if (cache_efi_boot >= 0) |
330 | 5.20k | return cache_efi_boot; |
331 | | |
332 | 4 | if (detect_container() > 0) |
333 | 4 | return (cache_efi_boot = false); |
334 | | |
335 | 0 | if (access("/sys/firmware/efi/", F_OK) < 0) { |
336 | 0 | if (errno != ENOENT) |
337 | 0 | log_debug_errno(errno, "Unable to test whether /sys/firmware/efi/ exists, assuming EFI not available: %m"); |
338 | 0 | return (cache_efi_boot = false); |
339 | 0 | } |
340 | | |
341 | 0 | return (cache_efi_boot = true); |
342 | 0 | } |
343 | | |
344 | 0 | static int read_flag(const char *variable) { |
345 | 0 | _cleanup_free_ void *v = NULL; |
346 | 0 | uint8_t b; |
347 | 0 | size_t s; |
348 | 0 | int r; |
349 | |
|
350 | 0 | if (!is_efi_boot()) /* If this is not an EFI boot, assume the queried flags are zero */ |
351 | 0 | return 0; |
352 | | |
353 | 0 | r = efi_get_variable(variable, NULL, &v, &s); |
354 | 0 | if (r < 0) |
355 | 0 | return r; |
356 | | |
357 | 0 | if (s != 1) |
358 | 0 | return -EINVAL; |
359 | | |
360 | 0 | b = *(uint8_t *)v; |
361 | 0 | return !!b; |
362 | 0 | } |
363 | | |
364 | 0 | bool is_efi_secure_boot(void) { |
365 | 0 | static int cache = -1; |
366 | 0 | int r; |
367 | |
|
368 | 0 | if (cache < 0) { |
369 | 0 | r = read_flag(EFI_GLOBAL_VARIABLE_STR("SecureBoot")); |
370 | 0 | if (r == -ENOENT) |
371 | 0 | cache = false; |
372 | 0 | else if (r < 0) |
373 | 0 | log_debug_errno(r, "Error reading SecureBoot EFI variable, assuming not in SecureBoot mode: %m"); |
374 | 0 | else |
375 | 0 | cache = r; |
376 | 0 | } |
377 | |
|
378 | 0 | return cache > 0; |
379 | 0 | } |
380 | | |
381 | 0 | SecureBootMode efi_get_secure_boot_mode(void) { |
382 | 0 | static SecureBootMode cache = _SECURE_BOOT_INVALID; |
383 | |
|
384 | 0 | if (cache != _SECURE_BOOT_INVALID) |
385 | 0 | return cache; |
386 | | |
387 | 0 | int secure = read_flag(EFI_GLOBAL_VARIABLE_STR("SecureBoot")); |
388 | 0 | if (secure < 0) { |
389 | 0 | if (secure != -ENOENT) |
390 | 0 | log_debug_errno(secure, "Error reading SecureBoot EFI variable, assuming not in SecureBoot mode: %m"); |
391 | |
|
392 | 0 | return (cache = SECURE_BOOT_UNSUPPORTED); |
393 | 0 | } |
394 | | |
395 | | /* We can assume false for all these if they are abscent (AuditMode and |
396 | | * DeployedMode may not exist on older firmware). */ |
397 | 0 | int audit = read_flag(EFI_GLOBAL_VARIABLE_STR("AuditMode")); |
398 | 0 | int deployed = read_flag(EFI_GLOBAL_VARIABLE_STR("DeployedMode")); |
399 | 0 | int setup = read_flag(EFI_GLOBAL_VARIABLE_STR("SetupMode")); |
400 | 0 | int moksb = read_flag(EFI_SHIMLOCK_VARIABLE_STR("MokSBStateRT")); |
401 | 0 | log_debug("Secure boot variables: SecureBoot=%d AuditMode=%d DeployedMode=%d SetupMode=%d MokSBStateRT=%d", |
402 | 0 | secure, audit, deployed, setup, moksb); |
403 | |
|
404 | 0 | return (cache = decode_secure_boot_mode(secure, audit > 0, deployed > 0, setup > 0, moksb > 0)); |
405 | 0 | } |
406 | | #endif |
407 | | |
408 | 0 | char *efi_tilt_backslashes(char *s) { |
409 | 0 | return string_replace_char(s, '\\', '/'); |
410 | 0 | } |