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

systemd / systemd / 23825567702

31 Mar 2026 12:42PM UTC coverage: 72.404% (+0.006%) from 72.398%
23825567702

push

github

daandemeyer
terminal-util: fix boot hang from ANSI terminal size queries

Since v257, terminal_fix_size() is called during early boot via
console_setup() → reset_dev_console_fd() to query terminal dimensions
via ANSI escape sequences. This has caused intermittent boot hangs
where the system gets stuck with a blinking cursor and requires a
keypress to continue (see systemd/systemd#35499).

The function tries CSI 18 first, then falls back to DSR if that fails.
Previously, each method independently opened a non-blocking fd, disabled
echo/icanon, ran its query, restored termios, and closed its fd. This
created two problems:

1. Echo window between CSI 18 and DSR fallback: After CSI 18 times out
   and restores termios (re-enabling ECHO and ICANON), there is a brief
   window before DSR disables them again. If the terminal's CSI 18
   response arrives during this window, it is echoed back to the
   terminal — where the terminal interprets \e[8;rows;cols t as a
   "resize text area" command — and the response bytes land in the
   canonical line buffer as stale input that can confuse the DSR
   response parser.

2. Cursor left at bottom-right on DSR timeout: The DSR method worked by
   sending two DSR queries — one to save the cursor position, then
   moving the cursor to (32766,32766) and sending another to read the
   clamped position. If neither response was received (timeout), the
   cursor restore was skipped (conditional on saved_row > 0), leaving
   the cursor at the bottom-right corner of the terminal. The
   subsequent terminal_reset_ansi_seq() then moved it to the beginning
   of the last line via \e[1G, making boot output appear at the bottom
   of the screen — giving the appearance of a hang even when the system
   was still booting.

This commit fixes both issues:

- terminal_fix_size() now opens the non-blocking fd and configures
  termios once for both query methods, so echo stays disabled for the
  entire CSI 18 → DSR fallback sequence with no gap. tcflu... (continued)

22 of 57 new or added lines in 3 files covered. (38.6%)

834 existing lines in 52 files now uncovered.

318485 of 439872 relevant lines covered (72.4%)

1162379.76 hits per line

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

81.25
/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(
825✔
31
                const char *variable,
32
                uint32_t *ret_attribute,
33
                void **ret_value,
34
                size_t *ret_size) {
35

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

39
        assert(variable);
825✔
40

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

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

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

52
        uint32_t attr;
53
        _cleanup_free_ char *buf = NULL;
592✔
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;
592✔
68

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

72
                r = stat_verify_regular(&st);
592✔
73
                if (r < 0)
592✔
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 */
592✔
77
                        return log_debug_errno(SYNTHETIC_ERRNO(ENOENT), "EFI variable '%s' is uncommitted", p);
×
78
                if ((uint64_t) st.st_size < sizeof(attr))
592✔
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)
592✔
81
                        return log_debug_errno(SYNTHETIC_ERRNO(E2BIG), "EFI variable '%s' is ridiculously large, refusing.", p);
×
82

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

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

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

100
                n = readv(fd, iov, 2);
585✔
101
                if (n < 0) {
585✔
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)
585✔
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);
585✔
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)
592✔
135
                return log_debug_errno(SYNTHETIC_ERRNO(ENOENT),
×
136
                                       "EFI variable %s is uncommitted", p);
137
        if ((size_t) n < sizeof(attr))
592✔
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);
592✔
141

142
        if (ret_attribute)
592✔
143
                *ret_attribute = attr;
14✔
144

145
        if (ret_value) {
592✔
146
                assert(buf);
585✔
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;
585✔
150
                buf[value_size + 1] = 0;
585✔
151
                buf[value_size + 2] = 0;
585✔
152
                *ret_value = TAKE_PTR(buf);
585✔
153
        }
154

155
        if (DEBUG_LOGGING) {
592✔
156
                usec_t end = now(CLOCK_MONOTONIC);
410✔
157
                if (end > begin + EFI_RETRY_DELAY)
410✔
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)
592✔
166
                *ret_size = value_size;
592✔
167

168
        return 0;
169
}
170

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

176
        assert(variable);
528✔
177

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

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

186
        if (ret)
356✔
187
                *ret = TAKE_PTR(x);
356✔
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) {
31✔
208
        _cleanup_free_ void *buf = NULL;
31✔
209
        size_t n;
31✔
210
        uint32_t a;
31✔
211
        int r;
31✔
212

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

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

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

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

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

235
        assert(variable);
58✔
236
        assert(value || size == 0);
58✔
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) {
58✔
240
                log_debug("Variable '%s' is already in wanted state, skipping write.", variable);
6✔
241
                return 0;
6✔
242
        }
243

244
        const char *p = strjoina("/sys/firmware/efi/efivars/", variable);
260✔
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,
52✔
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)
52✔
257
                log_debug_errno(r, "Failed to drop FS_IMMUTABLE_FL flag from '%s', ignoring: %m", p);
×
258

259
        saved_flags_valid = r >= 0;
52✔
260

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

267
                return 0;
268
        }
269

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

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

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

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

294
        r = 0;
295

296
finish:
49✔
297
        if (saved_flags_valid) {
49✔
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);
58✔
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
static int cache_efi_boot = -1;
323

324
bool set_efi_boot(bool b) {
29✔
325
        return (cache_efi_boot = b);
29✔
326
}
327

328
bool is_efi_boot(void) {
1,008✔
329
        if (cache_efi_boot >= 0)
1,008✔
330
                return cache_efi_boot;
477✔
331

332
        if (detect_container() > 0)
531✔
333
                return (cache_efi_boot = false);
109✔
334

335
        if (access("/sys/firmware/efi/", F_OK) < 0) {
422✔
336
                if (errno != ENOENT)
205✔
337
                        log_debug_errno(errno, "Unable to test whether /sys/firmware/efi/ exists, assuming EFI not available: %m");
×
338
                return (cache_efi_boot = false);
205✔
339
        }
340

341
        return (cache_efi_boot = true);
217✔
342
}
343

344
static int read_flag(const char *variable) {
62✔
345
        _cleanup_free_ void *v = NULL;
62✔
346
        uint8_t b;
62✔
347
        size_t s;
62✔
348
        int r;
62✔
349

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

353
        r = efi_get_variable(variable, NULL, &v, &s);
60✔
354
        if (r < 0)
60✔
355
                return r;
356

357
        if (s != 1)
24✔
358
                return -EINVAL;
359

360
        b = *(uint8_t *)v;
24✔
361
        return !!b;
24✔
362
}
363

364
bool is_efi_secure_boot(void) {
4✔
365
        static int cache = -1;
4✔
366
        int r;
4✔
367

368
        if (cache < 0) {
4✔
369
                r = read_flag(EFI_GLOBAL_VARIABLE_STR("SecureBoot"));
2✔
370
                if (r == -ENOENT)
2✔
371
                        cache = false;
×
372
                else if (r < 0)
2✔
373
                        log_debug_errno(r, "Error reading SecureBoot EFI variable, assuming not in SecureBoot mode: %m");
×
374
                else
375
                        cache = r;
2✔
376
        }
377

378
        return cache > 0;
4✔
379
}
380

381
SecureBootMode efi_get_secure_boot_mode(void) {
12✔
382
        static SecureBootMode cache = _SECURE_BOOT_INVALID;
12✔
383

384
        if (cache != _SECURE_BOOT_INVALID)
12✔
385
                return cache;
386

387
        int secure = read_flag(EFI_GLOBAL_VARIABLE_STR("SecureBoot"));
12✔
388
        if (secure < 0) {
12✔
389
                if (secure != -ENOENT)
×
390
                        log_debug_errno(secure, "Error reading SecureBoot EFI variable, assuming not in SecureBoot mode: %m");
×
391

392
                return (cache = SECURE_BOOT_UNSUPPORTED);
×
393
        }
394

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

404
        return (cache = decode_secure_boot_mode(secure, audit > 0, deployed > 0, setup > 0, moksb > 0));
12✔
405
}
406
#endif
407

408
char *efi_tilt_backslashes(char *s) {
64✔
409
        return string_replace_char(s, '\\', '/');
64✔
410
}
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