Skip to content

Commit 0bf091a

Browse files
committed
proc-cmdline: re-implement proc_cmdline_filter_pid1_args() without using getopt_long()
If getopt_long() is called for a list of arguments and it is freed, then calling getopt_long() for another list will trigger use-after-free. The function proc_cmdline_filter_pid1_args() may be called before or during parsing program arguments (typically named as parse_argv()), hence we cannot use getopt_long() in proc_cmdline_filter_pid1_args(). Fixes #28366.
1 parent 542f99c commit 0bf091a

File tree

1 file changed

+67
-35
lines changed

1 file changed

+67
-35
lines changed

src/basic/proc-cmdline.c

Lines changed: 67 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -17,63 +17,95 @@
1717
#include "strv.h"
1818
#include "virt.h"
1919

20-
int proc_cmdline_filter_pid1_args(
21-
char **argv, /* input, may be reordered by this function. */
22-
char ***ret) {
23-
20+
int proc_cmdline_filter_pid1_args(char **argv, char ***ret) {
2421
enum {
2522
COMMON_GETOPT_ARGS,
2623
SYSTEMD_GETOPT_ARGS,
2724
SHUTDOWN_GETOPT_ARGS,
2825
};
29-
3026
static const struct option options[] = {
3127
COMMON_GETOPT_OPTIONS,
3228
SYSTEMD_GETOPT_OPTIONS,
3329
SHUTDOWN_GETOPT_OPTIONS,
34-
{}
3530
};
31+
static const char *short_options = SYSTEMD_GETOPT_SHORT_OPTIONS;
3632

37-
int saved_optind, saved_opterr, saved_optopt, argc;
38-
char *saved_optarg;
39-
char **filtered;
40-
size_t idx;
33+
_cleanup_strv_free_ char **filtered = NULL;
34+
int state, r;
4135

4236
assert(argv);
4337
assert(ret);
4438

45-
/* Backup global variables. */
46-
saved_optind = optind;
47-
saved_opterr = opterr;
48-
saved_optopt = optopt;
49-
saved_optarg = optarg;
39+
/* Currently, we do not support '-', '+', and ':' at the beginning. */
40+
assert(!IN_SET(short_options[0], '-', '+', ':'));
5041

51-
/* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
52-
* that checks for GNU extensions in optstring ('-' or '+' at the beginning). Here, we do not use
53-
* the GNU extensions, but might be used previously. Hence, we need to always reset it. */
54-
optind = 0;
42+
/* Filter out all known options. */
43+
state = no_argument;
44+
STRV_FOREACH(p, strv_skip(argv, 1)) {
45+
int prev_state = state;
46+
const char *a = *p;
5547

56-
/* Do not print an error message. */
57-
opterr = 0;
48+
/* Reset the state for the next step. */
49+
state = no_argument;
5850

59-
/* Filter out all known options. */
60-
argc = strv_length(argv);
61-
while (getopt_long(argc, argv, SYSTEMD_GETOPT_SHORT_OPTIONS, options, NULL) >= 0)
62-
;
51+
if (prev_state == required_argument ||
52+
(prev_state == optional_argument && a[0] != '-'))
53+
/* Handled as an argument of the previous option, filtering out the string. */
54+
continue;
6355

64-
idx = optind;
56+
if (a[0] != '-') {
57+
/* Not an option, accepting the string. */
58+
r = strv_extend(&filtered, a);
59+
if (r < 0)
60+
return r;
61+
continue;
62+
}
6563

66-
/* Restore global variables. */
67-
optind = saved_optind;
68-
opterr = saved_opterr;
69-
optopt = saved_optopt;
70-
optarg = saved_optarg;
64+
if (a[1] == '-') {
65+
if (a[2] == '\0') {
66+
/* "--" is specified, accepting remaining strings. */
67+
r = strv_extend_strv(&filtered, strv_skip(p, 1), /* filter_duplicates = */ false);
68+
if (r < 0)
69+
return r;
70+
break;
71+
}
7172

72-
filtered = strv_copy(strv_skip(argv, idx));
73-
if (!filtered)
74-
return -ENOMEM;
73+
/* long option, e.g. --foo */
74+
for (size_t i = 0; i < ELEMENTSOF(options); i++) {
75+
const char *q = startswith(a + 2, options[i].name);
76+
if (!q || !IN_SET(q[0], '=', '\0'))
77+
continue;
78+
79+
/* Found matching option, updating the state if necessary. */
80+
if (q[0] == '\0' && options[i].has_arg == required_argument)
81+
state = required_argument;
82+
83+
break;
84+
}
85+
continue;
86+
}
87+
88+
/* short option(s), e.g. -x or -xyz */
89+
while (a && *++a != '\0')
90+
for (const char *q = short_options; *q != '\0'; q++) {
91+
if (*q != *a)
92+
continue;
93+
94+
/* Found matching short option. */
95+
96+
if (q[1] == ':') {
97+
/* An argument is required or optional, and remaining part
98+
* is handled as argument if exists. */
99+
state = a[1] != '\0' ? no_argument :
100+
q[2] == ':' ? optional_argument : required_argument;
101+
102+
a = NULL; /* Not necessary to parse remaining part. */
103+
}
104+
break;
105+
}
106+
}
75107

76-
*ret = filtered;
108+
*ret = TAKE_PTR(filtered);
77109
return 0;
78110
}
79111

0 commit comments

Comments
 (0)