]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/proc-cmdline.c
proc-cmdline: fix return value clobbering in proc_cmdline_get_key()
[thirdparty/systemd.git] / src / basic / proc-cmdline.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #include <stdbool.h>
4 #include <stddef.h>
5
6 #include "alloc-util.h"
7 #include "efivars.h"
8 #include "extract-word.h"
9 #include "fileio.h"
10 #include "macro.h"
11 #include "parse-util.h"
12 #include "proc-cmdline.h"
13 #include "process-util.h"
14 #include "special.h"
15 #include "string-util.h"
16 #include "util.h"
17 #include "virt.h"
18
19 int proc_cmdline(char **ret) {
20 const char *e;
21 assert(ret);
22
23 /* For testing purposes it is sometimes useful to be able to override what we consider /proc/cmdline to be */
24 e = secure_getenv("SYSTEMD_PROC_CMDLINE");
25 if (e) {
26 char *m;
27
28 m = strdup(e);
29 if (!m)
30 return -ENOMEM;
31
32 *ret = m;
33 return 0;
34 }
35
36 if (detect_container() > 0)
37 return get_process_cmdline(1, SIZE_MAX, 0, ret);
38 else
39 return read_one_line_file("/proc/cmdline", ret);
40 }
41
42 static int proc_cmdline_extract_first(const char **p, char **ret_word, ProcCmdlineFlags flags) {
43 const char *q = *p;
44 int r;
45
46 for (;;) {
47 _cleanup_free_ char *word = NULL;
48 const char *c;
49
50 r = extract_first_word(&q, &word, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX);
51 if (r < 0)
52 return r;
53 if (r == 0)
54 break;
55
56 /* Filter out arguments that are intended only for the initrd */
57 c = startswith(word, "rd.");
58 if (c) {
59 if (!in_initrd())
60 continue;
61
62 if (FLAGS_SET(flags, PROC_CMDLINE_STRIP_RD_PREFIX)) {
63 r = free_and_strdup(&word, c);
64 if (r < 0)
65 return r;
66 }
67
68 } else if (FLAGS_SET(flags, PROC_CMDLINE_RD_STRICT) && in_initrd())
69 continue; /* And optionally filter out arguments that are intended only for the host */
70
71 *p = q;
72 *ret_word = TAKE_PTR(word);
73 return 1;
74 }
75
76 *p = q;
77 *ret_word = NULL;
78 return 0;
79 }
80
81 int proc_cmdline_parse_given(const char *line, proc_cmdline_parse_t parse_item, void *data, ProcCmdlineFlags flags) {
82 const char *p;
83 int r;
84
85 assert(parse_item);
86
87 /* The PROC_CMDLINE_VALUE_OPTIONAL flag doesn't really make sense for proc_cmdline_parse(), let's make this
88 * clear. */
89 assert(!FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL));
90
91 p = line;
92 for (;;) {
93 _cleanup_free_ char *word = NULL;
94 char *value;
95
96 r = proc_cmdline_extract_first(&p, &word, flags);
97 if (r < 0)
98 return r;
99 if (r == 0)
100 break;
101
102 value = strchr(word, '=');
103 if (value)
104 *(value++) = 0;
105
106 r = parse_item(word, value, data);
107 if (r < 0)
108 return r;
109 }
110
111 return 0;
112 }
113
114 int proc_cmdline_parse(proc_cmdline_parse_t parse_item, void *data, ProcCmdlineFlags flags) {
115 _cleanup_free_ char *line = NULL;
116 int r;
117
118 assert(parse_item);
119
120 /* We parse the EFI variable first, because later settings have higher priority. */
121
122 r = systemd_efi_options_variable(&line);
123 if (r < 0 && r != -ENODATA)
124 log_debug_errno(r, "Failed to get SystemdOptions EFI variable, ignoring: %m");
125
126 r = proc_cmdline_parse_given(line, parse_item, data, flags);
127 if (r < 0)
128 return r;
129
130 line = mfree(line);
131 r = proc_cmdline(&line);
132 if (r < 0)
133 return r;
134
135 return proc_cmdline_parse_given(line, parse_item, data, flags);
136 }
137
138 static bool relaxed_equal_char(char a, char b) {
139 return a == b ||
140 (a == '_' && b == '-') ||
141 (a == '-' && b == '_');
142 }
143
144 char *proc_cmdline_key_startswith(const char *s, const char *prefix) {
145 assert(s);
146 assert(prefix);
147
148 /* Much like startswith(), but considers "-" and "_" the same */
149
150 for (; *prefix != 0; s++, prefix++)
151 if (!relaxed_equal_char(*s, *prefix))
152 return NULL;
153
154 return (char*) s;
155 }
156
157 bool proc_cmdline_key_streq(const char *x, const char *y) {
158 assert(x);
159 assert(y);
160
161 /* Much like streq(), but considers "-" and "_" the same */
162
163 for (; *x != 0 || *y != 0; x++, y++)
164 if (!relaxed_equal_char(*x, *y))
165 return false;
166
167 return true;
168 }
169
170 static int cmdline_get_key(const char *line, const char *key, ProcCmdlineFlags flags, char **ret_value) {
171 _cleanup_free_ char *ret = NULL;
172 bool found = false;
173 const char *p;
174 int r;
175
176 assert(line);
177 assert(key);
178
179 p = line;
180 for (;;) {
181 _cleanup_free_ char *word = NULL;
182
183 r = proc_cmdline_extract_first(&p, &word, flags);
184 if (r < 0)
185 return r;
186 if (r == 0)
187 break;
188
189 if (ret_value) {
190 const char *e;
191
192 e = proc_cmdline_key_startswith(word, key);
193 if (!e)
194 continue;
195
196 if (*e == '=') {
197 r = free_and_strdup(&ret, e+1);
198 if (r < 0)
199 return r;
200
201 found = true;
202
203 } else if (*e == 0 && FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL))
204 found = true;
205
206 } else {
207 if (streq(word, key)) {
208 found = true;
209 break; /* we found what we were looking for */
210 }
211 }
212 }
213
214 if (ret_value)
215 *ret_value = TAKE_PTR(ret);
216
217 return found;
218 }
219
220 int proc_cmdline_get_key(const char *key, ProcCmdlineFlags flags, char **ret_value) {
221 _cleanup_free_ char *line = NULL, *v = NULL;
222 int r;
223
224 /* Looks for a specific key on the kernel command line and (with lower priority) the EFI variable.
225 * Supports three modes:
226 *
227 * a) The "ret_value" parameter is used. In this case a parameter beginning with the "key" string followed by
228 * "=" is searched for, and the value following it is returned in "ret_value".
229 *
230 * b) as above, but the PROC_CMDLINE_VALUE_OPTIONAL flag is set. In this case if the key is found as a separate
231 * word (i.e. not followed by "=" but instead by whitespace or the end of the command line), then this is
232 * also accepted, and "value" is returned as NULL.
233 *
234 * c) The "ret_value" parameter is NULL. In this case a search for the exact "key" parameter is performed.
235 *
236 * In all three cases, > 0 is returned if the key is found, 0 if not. */
237
238 if (isempty(key))
239 return -EINVAL;
240
241 if (FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL) && !ret_value)
242 return -EINVAL;
243
244 r = proc_cmdline(&line);
245 if (r < 0)
246 return r;
247
248 r = cmdline_get_key(line, key, flags, ret_value ? &v : NULL);
249 if (r < 0)
250 return r;
251 if (r > 0) {
252 if (ret_value)
253 *ret_value = TAKE_PTR(v);
254
255 return r;
256 }
257
258 line = mfree(line);
259 r = systemd_efi_options_variable(&line);
260 if (r == -ENODATA) {
261 if (ret_value)
262 *ret_value = NULL;
263
264 return false; /* Not found */
265 }
266 if (r < 0)
267 return r;
268
269 return cmdline_get_key(line, key, flags, ret_value);
270 }
271
272 int proc_cmdline_get_bool(const char *key, bool *ret) {
273 _cleanup_free_ char *v = NULL;
274 int r;
275
276 assert(ret);
277
278 r = proc_cmdline_get_key(key, PROC_CMDLINE_VALUE_OPTIONAL, &v);
279 if (r < 0)
280 return r;
281 if (r == 0) { /* key not specified at all */
282 *ret = false;
283 return 0;
284 }
285
286 if (v) { /* key with parameter passed */
287 r = parse_boolean(v);
288 if (r < 0)
289 return r;
290 *ret = r;
291 } else /* key without parameter passed */
292 *ret = true;
293
294 return 1;
295 }
296
297 int proc_cmdline_get_key_many_internal(ProcCmdlineFlags flags, ...) {
298 _cleanup_free_ char *line = NULL;
299 const char *p;
300 va_list ap;
301 int r, ret = 0;
302
303 /* The PROC_CMDLINE_VALUE_OPTIONAL flag doesn't really make sense for proc_cmdline_get_key_many(), let's make
304 * this clear. */
305 assert(!FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL));
306
307 /* This call may clobber arguments on failure! */
308
309 r = proc_cmdline(&line);
310 if (r < 0)
311 return r;
312
313 p = line;
314 for (;;) {
315 _cleanup_free_ char *word = NULL;
316
317 r = proc_cmdline_extract_first(&p, &word, flags);
318 if (r < 0)
319 return r;
320 if (r == 0)
321 break;
322
323 va_start(ap, flags);
324
325 for (;;) {
326 char **v;
327 const char *k, *e;
328
329 k = va_arg(ap, const char*);
330 if (!k)
331 break;
332
333 assert_se(v = va_arg(ap, char**));
334
335 e = proc_cmdline_key_startswith(word, k);
336 if (e && *e == '=') {
337 r = free_and_strdup(v, e + 1);
338 if (r < 0) {
339 va_end(ap);
340 return r;
341 }
342
343 ret++;
344 }
345 }
346
347 va_end(ap);
348 }
349
350 return ret;
351 }