• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

systemd / systemd / 20151578145

11 Dec 2025 05:38AM UTC coverage: 72.698% (-0.02%) from 72.713%
20151578145

push

github

web-flow
core: gracefully skip unknown policy designators in RootImagePolicy et al (#40060)

Usually we gracefully ignore unknown configuration parameters, so that
service files can be written by upstreams and used across a variegated
range of distributions with various versions of systemd, to avoid
forcing users to the minimum common denominator and only adding settings
that are supported by the oldest distro supported.

Image policies do not behave like this, and any unknown partition or
policy designator causes the whole unit to fail to parse and a hard
error.

Change it so that parsing RootImagePolicy and friends via unit file or
D-Bus logs but otherwise ignores unknown specifiers, like other options
do.

This allows us to add new specifiers in the future, and users to adopt
them immediately.

Follow-up for d452335aa

44 of 49 new or added lines in 7 files covered. (89.8%)

297 existing lines in 37 files now uncovered.

309479 of 425707 relevant lines covered (72.7%)

1150153.67 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.89
/src/basic/efivars.c
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
#define EFI_N_RETRIES_NO_DELAY 20
27
#define EFI_N_RETRIES_TOTAL 25
28
#define EFI_RETRY_DELAY (50 * USEC_PER_MSEC)
29

30
int efi_get_variable(
731✔
31
                const char *variable,
32
                uint32_t *ret_attribute,
33
                void **ret_value,
34
                size_t *ret_size) {
35

36
        int r;
731✔
37
        usec_t begin = 0; /* Unnecessary initialization to appease gcc */
731✔
38

39
        assert(variable);
731✔
40

41
        const char *p = strjoina("/sys/firmware/efi/efivars/", variable);
3,655✔
42

43
        if (DEBUG_LOGGING) {
731✔
44
                log_debug("Reading EFI variable %s.", p);
461✔
45
                begin = now(CLOCK_MONOTONIC);
461✔
46
        }
47

48
        _cleanup_close_ int fd = open(p, O_RDONLY|O_NOCTTY|O_CLOEXEC);
1,462✔
49
        if (fd < 0)
731✔
50
                return log_debug_errno(errno, "open(\"%s\") failed: %m", p);
199✔
51

52
        uint32_t attr;
53
        _cleanup_free_ char *buf = NULL;
532✔
54
        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
        for (unsigned try = 0;; try++) {
×
67
                struct stat st;
532✔
68

69
                if (fstat(fd, &st) < 0)
532✔
70
                        return log_debug_errno(errno, "fstat(\"%s\") failed: %m", p);
×
71

72
                r = stat_verify_regular(&st);
532✔
73
                if (r < 0)
532✔
74
                        return log_debug_errno(r, "EFI variable '%s' is not a regular file, refusing: %m", p);
×
75

76
                if (st.st_size == 0) /* for uncommitted variables, see below */
532✔
77
                        return log_debug_errno(SYNTHETIC_ERRNO(ENOENT), "EFI variable '%s' is uncommitted", p);
×
78
                if ((uint64_t) st.st_size < sizeof(attr))
532✔
79
                        return log_debug_errno(SYNTHETIC_ERRNO(ENODATA), "EFI variable '%s' is shorter than %zu bytes, refusing.", p, sizeof(attr));
×
80
                if ((uint64_t) st.st_size > sizeof(attr) + 4 * U64_MB)
532✔
81
                        return log_debug_errno(SYNTHETIC_ERRNO(E2BIG), "EFI variable '%s' is ridiculously large, refusing.", p);
×
82

83
                if (!ret_attribute && !ret_value) {
532✔
84
                        /* No need to read anything, return the reported size. */
85
                        n = st.st_size;
86
                        break;
532✔
87
                }
88

89
                /* We want +1 for the read call, and +3 for the additional terminating bytes added below. */
90
                free(buf);
525✔
91
                buf = malloc((size_t) st.st_size - sizeof(attr) + CONST_MAX(1, 3));
525✔
92
                if (!buf)
525✔
93
                        return -ENOMEM;
94

95
                struct iovec iov[] = {
525✔
96
                        { &attr, sizeof(attr)                           },
97
                        { buf,   (size_t) st.st_size - sizeof(attr) + 1 },
525✔
98
                };
99

100
                n = readv(fd, iov, 2);
525✔
101
                if (n < 0) {
525✔
102
                        if (errno != EINTR)
×
103
                                return log_debug_errno(errno, "Reading from '%s' failed: %m", p);
×
104

105
                        log_debug("Reading from '%s' failed with EINTR, retrying.", p);
×
106
                } else if ((size_t) n == sizeof(attr) + st.st_size + 1)
525✔
107
                        /* We need to try again with a bigger buffer, the variable was apparently changed concurrently? */
108
                        log_debug("EFI variable '%s' larger than expected, retrying.", p);
×
109
                else {
110
                        assert((size_t) n < sizeof(attr) + st.st_size + 1);
525✔
111
                        break;
112
                }
113

114
                if (try >= EFI_N_RETRIES_TOTAL)
×
115
                        return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "Reading EFI variable '%s' failed even after %u tries, giving up.", p, try);
×
116
                if (try >= EFI_N_RETRIES_NO_DELAY)
×
117
                        (void) usleep_safe(EFI_RETRY_DELAY);
×
118

119
                /* Start from the beginning */
120
                (void) lseek(fd, 0, SEEK_SET);
×
121
        }
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
        if (n == 0)
532✔
135
                return log_debug_errno(SYNTHETIC_ERRNO(ENOENT),
×
136
                                       "EFI variable %s is uncommitted", p);
137
        if ((size_t) n < sizeof(attr))
532✔
138
                return log_debug_errno(SYNTHETIC_ERRNO(EIO),
×
139
                                       "Read %zi bytes from EFI variable %s, expected >= %zu", n, p, sizeof(attr));
140
        size_t value_size = n - sizeof(attr);
532✔
141

142
        if (ret_attribute)
532✔
143
                *ret_attribute = attr;
8✔
144

145
        if (ret_value) {
532✔
146
                assert(buf);
525✔
147
                /* Always NUL-terminate (3 bytes, to properly protect UTF-16, even if truncated in
148
                 * the middle of a character) */
149
                buf[value_size] = 0;
525✔
150
                buf[value_size + 1] = 0;
525✔
151
                buf[value_size + 2] = 0;
525✔
152
                *ret_value = TAKE_PTR(buf);
525✔
153
        }
154

155
        if (DEBUG_LOGGING) {
532✔
156
                usec_t end = now(CLOCK_MONOTONIC);
353✔
157
                if (end > begin + EFI_RETRY_DELAY)
353✔
UNCOV
158
                        log_debug("Detected slow EFI variable read access on %s: %s",
×
159
                                  variable, FORMAT_TIMESPAN(end - begin, 1));
160
        }
161

162
        /* Note that efivarfs interestingly doesn't require ftruncate() to update an existing EFI variable
163
         * with a smaller value. */
164

165
        if (ret_size)
532✔
166
                *ret_size = value_size;
532✔
167

168
        return 0;
169
}
170

171
int efi_get_variable_string(const char *variable, char **ret) {
491✔
172
        _cleanup_free_ void *s = NULL, *x = NULL;
491✔
173
        size_t ss = 0;
491✔
174
        int r;
491✔
175

176
        assert(variable);
491✔
177

178
        r = efi_get_variable(variable, NULL, &s, &ss);
491✔
179
        if (r < 0)
491✔
180
                return r;
181

182
        x = utf16_to_utf8(s, ss);
350✔
183
        if (!x)
350✔
184
                return -ENOMEM;
185

186
        if (ret)
350✔
187
                *ret = TAKE_PTR(x);
350✔
188

189
        return 0;
190
}
191

192
int efi_get_variable_path(const char *variable, char **ret) {
30✔
193
        int r;
30✔
194

195
        assert(variable);
30✔
196

197
        r = efi_get_variable_string(variable, ret);
30✔
198
        if (r < 0)
30✔
199
                return r;
200

201
        if (ret)
30✔
202
                efi_tilt_backslashes(*ret);
30✔
203

204
        return r;
205
}
206

207
static int efi_verify_variable(const char *variable, uint32_t attr, const void *value, size_t size) {
22✔
208
        _cleanup_free_ void *buf = NULL;
22✔
209
        size_t n;
22✔
210
        uint32_t a;
22✔
211
        int r;
22✔
212

213
        assert(variable);
22✔
214
        assert(value || size == 0);
22✔
215

216
        r = efi_get_variable(variable, &a, &buf, &n);
22✔
217
        if (r < 0)
22✔
218
                return r;
219

220
        return a == attr && memcmp_nn(buf, n, value, size) == 0;
8✔
221
}
222

223
int efi_set_variable(const char *variable, const void *value, size_t size) {
49✔
224
        static const uint32_t attr = EFI_VARIABLE_NON_VOLATILE|EFI_VARIABLE_BOOTSERVICE_ACCESS|EFI_VARIABLE_RUNTIME_ACCESS;
49✔
225

226
        _cleanup_free_ struct var {
49✔
227
                uint32_t attr;
228
                char buf[];
229
        } _packed_ *buf = NULL;
49✔
230
        _cleanup_close_ int fd = -EBADF;
49✔
231
        bool saved_flags_valid = false;
49✔
232
        unsigned saved_flags;
49✔
233
        int r;
49✔
234

235
        assert(variable);
49✔
236
        assert(value || size == 0);
49✔
237

238
        /* size 0 means removal, empty variable would not be enough for that */
239
        if (size > 0 && efi_verify_variable(variable, attr, value, size) > 0) {
49✔
240
                log_debug("Variable '%s' is already in wanted state, skipping write.", variable);
×
241
                return 0;
×
242
        }
243

244
        const char *p = strjoina("/sys/firmware/efi/efivars/", variable);
245✔
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
        r = chattr_full(AT_FDCWD, p,
49✔
251
                        /* value = */ 0,
252
                        /* mask = */ FS_IMMUTABLE_FL,
253
                        /* ret_previous = */ &saved_flags,
254
                        /* ret_final = */ NULL,
255
                        /* flags = */ 0);
256
        if (r < 0 && r != -ENOENT)
49✔
257
                log_debug_errno(r, "Failed to drop FS_IMMUTABLE_FL flag from '%s', ignoring: %m", p);
×
258

259
        saved_flags_valid = r >= 0;
49✔
260

261
        if (size == 0) {
49✔
262
                if (unlink(p) < 0) {
27✔
263
                        r = -errno;
21✔
264
                        goto finish;
21✔
265
                }
266

267
                return 0;
268
        }
269

270
        fd = open(p, O_WRONLY|O_CREAT|O_NOCTTY|O_CLOEXEC, 0644);
22✔
271
        if (fd < 0) {
22✔
272
                r = -errno;
×
273
                goto finish;
×
274
        }
275

276
        buf = malloc(sizeof(uint32_t) + size);
22✔
277
        if (!buf) {
22✔
278
                r = -ENOMEM;
×
279
                goto finish;
×
280
        }
281

282
        buf->attr = attr;
22✔
283
        memcpy(buf->buf, value, size);
22✔
284

285
        r = loop_write(fd, buf, sizeof(uint32_t) + size);
22✔
286
        if (r < 0)
22✔
287
                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
        if (futimens(fd, /* times = */ NULL) < 0)
22✔
292
                log_debug_errno(errno, "Failed to update mtime/atime on %s, ignoring: %m", p);
11✔
293

294
        r = 0;
295

296
finish:
43✔
297
        if (saved_flags_valid) {
43✔
298
                int q;
8✔
299

300
                /* Restore the original flags field, just in case */
301
                if (fd < 0)
8✔
302
                        q = chattr_path(p, saved_flags, FS_IMMUTABLE_FL);
×
303
                else
304
                        q = chattr_fd(fd, saved_flags, FS_IMMUTABLE_FL);
8✔
305
                if (q < 0)
8✔
306
                        log_debug_errno(q, "Failed to restore FS_IMMUTABLE_FL on '%s', ignoring: %m", p);
49✔
307
        }
308

309
        return r;
310
}
311

312
int efi_set_variable_string(const char *variable, const char *value) {
×
313
        _cleanup_free_ char16_t *u16 = NULL;
×
314

315
        u16 = utf8_to_utf16(value, SIZE_MAX);
×
316
        if (!u16)
×
317
                return -ENOMEM;
318

319
        return efi_set_variable(variable, u16, (char16_strlen(u16) + 1) * sizeof(char16_t));
×
320
}
321

322
bool is_efi_boot(void) {
785✔
323
        static int cache = -1;
785✔
324

325
        if (cache < 0) {
785✔
326
                if (detect_container() > 0)
354✔
327
                        cache = false;
54✔
328
                else {
329
                        cache = access("/sys/firmware/efi/", F_OK) >= 0;
300✔
330
                        if (!cache && errno != ENOENT)
300✔
331
                                log_debug_errno(errno, "Unable to test whether /sys/firmware/efi/ exists, assuming EFI not available: %m");
×
332
                }
333
        }
334

335
        return cache;
785✔
336
}
337

338
static int read_flag(const char *variable) {
62✔
339
        _cleanup_free_ void *v = NULL;
62✔
340
        uint8_t b;
62✔
341
        size_t s;
62✔
342
        int r;
62✔
343

344
        if (!is_efi_boot()) /* If this is not an EFI boot, assume the queried flags are zero */
62✔
345
                return 0;
346

347
        r = efi_get_variable(variable, NULL, &v, &s);
60✔
348
        if (r < 0)
60✔
349
                return r;
350

351
        if (s != 1)
24✔
352
                return -EINVAL;
353

354
        b = *(uint8_t *)v;
24✔
355
        return !!b;
24✔
356
}
357

358
bool is_efi_secure_boot(void) {
4✔
359
        static int cache = -1;
4✔
360
        int r;
4✔
361

362
        if (cache < 0) {
4✔
363
                r = read_flag(EFI_GLOBAL_VARIABLE_STR("SecureBoot"));
2✔
364
                if (r == -ENOENT)
2✔
365
                        cache = false;
×
366
                else if (r < 0)
2✔
367
                        log_debug_errno(r, "Error reading SecureBoot EFI variable, assuming not in SecureBoot mode: %m");
×
368
                else
369
                        cache = r;
2✔
370
        }
371

372
        return cache > 0;
4✔
373
}
374

375
SecureBootMode efi_get_secure_boot_mode(void) {
12✔
376
        static SecureBootMode cache = _SECURE_BOOT_INVALID;
12✔
377

378
        if (cache != _SECURE_BOOT_INVALID)
12✔
379
                return cache;
380

381
        int secure = read_flag(EFI_GLOBAL_VARIABLE_STR("SecureBoot"));
12✔
382
        if (secure < 0) {
12✔
383
                if (secure != -ENOENT)
×
384
                        log_debug_errno(secure, "Error reading SecureBoot EFI variable, assuming not in SecureBoot mode: %m");
×
385

386
                return (cache = SECURE_BOOT_UNSUPPORTED);
×
387
        }
388

389
        /* We can assume false for all these if they are abscent (AuditMode and
390
         * DeployedMode may not exist on older firmware). */
391
        int audit    = read_flag(EFI_GLOBAL_VARIABLE_STR("AuditMode"));
12✔
392
        int deployed = read_flag(EFI_GLOBAL_VARIABLE_STR("DeployedMode"));
12✔
393
        int setup    = read_flag(EFI_GLOBAL_VARIABLE_STR("SetupMode"));
12✔
394
        int moksb    = read_flag(EFI_SHIMLOCK_VARIABLE_STR("MokSBStateRT"));
12✔
395
        log_debug("Secure boot variables: SecureBoot=%d AuditMode=%d DeployedMode=%d SetupMode=%d MokSBStateRT=%d",
12✔
396
                  secure, audit, deployed, setup, moksb);
397

398
        return (cache = decode_secure_boot_mode(secure, audit > 0, deployed > 0, setup > 0, moksb > 0));
12✔
399
}
400
#endif
401

402
char *efi_tilt_backslashes(char *s) {
40✔
403
        return string_replace_char(s, '\\', '/');
40✔
404
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc