]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/efivars.c
meson: make user $PATH configurable
[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
24 #if ENABLE_EFI
25
26 char* efi_variable_path(sd_id128_t vendor, const char *name) {
27 char *p;
28
29 if (asprintf(&p,
30 "/sys/firmware/efi/efivars/%s-" SD_ID128_UUID_FORMAT_STR,
31 name, SD_ID128_FORMAT_VAL(vendor)) < 0)
32 return NULL;
33
34 return p;
35 }
36
37 int efi_get_variable(
38 sd_id128_t vendor,
39 const char *name,
40 uint32_t *ret_attribute,
41 void **ret_value,
42 size_t *ret_size) {
43
44 _cleanup_close_ int fd = -1;
45 _cleanup_free_ char *p = NULL;
46 _cleanup_free_ void *buf = NULL;
47 struct stat st;
48 uint32_t a;
49 ssize_t n;
50
51 assert(name);
52
53 p = efi_variable_path(vendor, name);
54 if (!p)
55 return -ENOMEM;
56
57 if (!ret_value && !ret_size && !ret_attribute) {
58 /* If caller is not interested in anything, just check if the variable exists and is readable
59 * to us. */
60 if (access(p, R_OK) < 0)
61 return -errno;
62
63 return 0;
64 }
65
66 fd = open(p, O_RDONLY|O_NOCTTY|O_CLOEXEC);
67 if (fd < 0)
68 return -errno;
69
70 if (fstat(fd, &st) < 0)
71 return -errno;
72 if (st.st_size < 4)
73 return -ENODATA;
74 if (st.st_size > 4*1024*1024 + 4)
75 return -E2BIG;
76
77 if (ret_value || ret_attribute) {
78 n = read(fd, &a, sizeof(a));
79 if (n < 0)
80 return -errno;
81 if (n != sizeof(a))
82 return -EIO;
83 }
84
85 if (ret_value) {
86 buf = malloc(st.st_size - 4 + 2);
87 if (!buf)
88 return -ENOMEM;
89
90 n = read(fd, buf, (size_t) st.st_size - 4);
91 if (n < 0)
92 return -errno;
93 if (n != st.st_size - 4)
94 return -EIO;
95
96 /* Always NUL terminate (2 bytes, to protect UTF-16) */
97 ((char*) buf)[st.st_size - 4] = 0;
98 ((char*) buf)[st.st_size - 4 + 1] = 0;
99 }
100
101 /* Note that efivarfs interestingly doesn't require ftruncate() to update an existing EFI variable
102 * with a smaller value. */
103
104 if (ret_attribute)
105 *ret_attribute = a;
106
107 if (ret_value)
108 *ret_value = TAKE_PTR(buf);
109
110 if (ret_size)
111 *ret_size = (size_t) st.st_size - 4;
112
113 return 0;
114 }
115
116 int efi_get_variable_string(sd_id128_t vendor, const char *name, char **p) {
117 _cleanup_free_ void *s = NULL;
118 size_t ss = 0;
119 int r;
120 char *x;
121
122 r = efi_get_variable(vendor, name, NULL, &s, &ss);
123 if (r < 0)
124 return r;
125
126 x = utf16_to_utf8(s, ss);
127 if (!x)
128 return -ENOMEM;
129
130 *p = x;
131 return 0;
132 }
133
134 int efi_set_variable(
135 sd_id128_t vendor,
136 const char *name,
137 const void *value,
138 size_t size) {
139
140 struct var {
141 uint32_t attr;
142 char buf[];
143 } _packed_ * _cleanup_free_ buf = NULL;
144 _cleanup_free_ char *p = NULL;
145 _cleanup_close_ int fd = -1;
146 bool saved_flags_valid = false;
147 unsigned saved_flags;
148 int r;
149
150 assert(name);
151 assert(value || size == 0);
152
153 p = efi_variable_path(vendor, name);
154 if (!p)
155 return -ENOMEM;
156
157 /* Newer efivarfs protects variables that are not in a whitelist with FS_IMMUTABLE_FL by default, to protect
158 * them for accidental removal and modification. We are not changing these variables accidentally however,
159 * hence let's unset the bit first. */
160
161 r = chattr_path(p, 0, FS_IMMUTABLE_FL, &saved_flags);
162 if (r < 0 && r != -ENOENT)
163 log_debug_errno(r, "Failed to drop FS_IMMUTABLE_FL flag from '%s', ignoring: %m", p);
164
165 saved_flags_valid = r >= 0;
166
167 if (size == 0) {
168 if (unlink(p) < 0) {
169 r = -errno;
170 goto finish;
171 }
172
173 return 0;
174 }
175
176 fd = open(p, O_WRONLY|O_CREAT|O_NOCTTY|O_CLOEXEC, 0644);
177 if (fd < 0) {
178 r = -errno;
179 goto finish;
180 }
181
182 buf = malloc(sizeof(uint32_t) + size);
183 if (!buf) {
184 r = -ENOMEM;
185 goto finish;
186 }
187
188 buf->attr = EFI_VARIABLE_NON_VOLATILE|EFI_VARIABLE_BOOTSERVICE_ACCESS|EFI_VARIABLE_RUNTIME_ACCESS;
189 memcpy(buf->buf, value, size);
190
191 r = loop_write(fd, buf, sizeof(uint32_t) + size, false);
192 if (r < 0)
193 goto finish;
194
195 r = 0;
196
197 finish:
198 if (saved_flags_valid) {
199 int q;
200
201 /* Restore the original flags field, just in case */
202 if (fd < 0)
203 q = chattr_path(p, saved_flags, FS_IMMUTABLE_FL, NULL);
204 else
205 q = chattr_fd(fd, saved_flags, FS_IMMUTABLE_FL, NULL);
206 if (q < 0)
207 log_debug_errno(q, "Failed to restore FS_IMMUTABLE_FL on '%s', ignoring: %m", p);
208 }
209
210 return r;
211 }
212
213 int efi_set_variable_string(sd_id128_t vendor, const char *name, const char *v) {
214 _cleanup_free_ char16_t *u16 = NULL;
215
216 u16 = utf8_to_utf16(v, strlen(v));
217 if (!u16)
218 return -ENOMEM;
219
220 return efi_set_variable(vendor, name, u16, (char16_strlen(u16) + 1) * sizeof(char16_t));
221 }
222
223 int efi_systemd_options_variable(char **line) {
224 const char *e;
225 int r;
226
227 assert(line);
228
229 /* For testing purposes it is sometimes useful to be able to override this */
230 e = secure_getenv("SYSTEMD_EFI_OPTIONS");
231 if (e) {
232 char *m;
233
234 m = strdup(e);
235 if (!m)
236 return -ENOMEM;
237
238 *line = m;
239 return 0;
240 }
241
242 r = efi_get_variable_string(EFI_VENDOR_SYSTEMD, "SystemdOptions", line);
243 if (r == -ENOENT)
244 return -ENODATA;
245
246 return r;
247 }
248 #endif