Merge pull request #26887 from yuwata/proc-cmdline-filter-arguments

proc-cmdline: filter PID1 arguments on container
This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2023-04-07 10:55:30 +02:00
committed by GitHub
18 changed files with 395 additions and 200 deletions
+75
View File
@@ -0,0 +1,75 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <getopt.h>
#define SYSTEMD_GETOPT_SHORT_OPTIONS "hDbsz:"
#define COMMON_GETOPT_ARGS \
ARG_LOG_LEVEL = 0x100, \
ARG_LOG_TARGET, \
ARG_LOG_COLOR, \
ARG_LOG_LOCATION, \
ARG_LOG_TIME
#define SYSTEMD_GETOPT_ARGS \
ARG_UNIT, \
ARG_SYSTEM, \
ARG_USER, \
ARG_TEST, \
ARG_NO_PAGER, \
ARG_VERSION, \
ARG_DUMP_CONFIGURATION_ITEMS, \
ARG_DUMP_BUS_PROPERTIES, \
ARG_BUS_INTROSPECT, \
ARG_DUMP_CORE, \
ARG_CRASH_CHVT, \
ARG_CRASH_SHELL, \
ARG_CRASH_REBOOT, \
ARG_CONFIRM_SPAWN, \
ARG_SHOW_STATUS, \
ARG_DESERIALIZE, \
ARG_SWITCHED_ROOT, \
ARG_DEFAULT_STD_OUTPUT, \
ARG_DEFAULT_STD_ERROR, \
ARG_MACHINE_ID, \
ARG_SERVICE_WATCHDOGS
#define SHUTDOWN_GETOPT_ARGS \
ARG_EXIT_CODE, \
ARG_TIMEOUT
#define COMMON_GETOPT_OPTIONS \
{ "log-level", required_argument, NULL, ARG_LOG_LEVEL }, \
{ "log-target", required_argument, NULL, ARG_LOG_TARGET }, \
{ "log-color", optional_argument, NULL, ARG_LOG_COLOR }, \
{ "log-location", optional_argument, NULL, ARG_LOG_LOCATION }, \
{ "log-time", optional_argument, NULL, ARG_LOG_TIME }
#define SYSTEMD_GETOPT_OPTIONS \
{ "unit", required_argument, NULL, ARG_UNIT }, \
{ "system", no_argument, NULL, ARG_SYSTEM }, \
{ "user", no_argument, NULL, ARG_USER }, \
{ "test", no_argument, NULL, ARG_TEST }, \
{ "no-pager", no_argument, NULL, ARG_NO_PAGER }, \
{ "help", no_argument, NULL, 'h' }, \
{ "version", no_argument, NULL, ARG_VERSION }, \
{ "dump-configuration-items", no_argument, NULL, ARG_DUMP_CONFIGURATION_ITEMS }, \
{ "dump-bus-properties", no_argument, NULL, ARG_DUMP_BUS_PROPERTIES }, \
{ "bus-introspect", required_argument, NULL, ARG_BUS_INTROSPECT }, \
{ "dump-core", optional_argument, NULL, ARG_DUMP_CORE }, \
{ "crash-chvt", required_argument, NULL, ARG_CRASH_CHVT }, \
{ "crash-shell", optional_argument, NULL, ARG_CRASH_SHELL }, \
{ "crash-reboot", optional_argument, NULL, ARG_CRASH_REBOOT }, \
{ "confirm-spawn", optional_argument, NULL, ARG_CONFIRM_SPAWN }, \
{ "show-status", optional_argument, NULL, ARG_SHOW_STATUS }, \
{ "deserialize", required_argument, NULL, ARG_DESERIALIZE }, \
{ "switched-root", no_argument, NULL, ARG_SWITCHED_ROOT }, \
{ "default-standard-output", required_argument, NULL, ARG_DEFAULT_STD_OUTPUT, }, \
{ "default-standard-error", required_argument, NULL, ARG_DEFAULT_STD_ERROR, }, \
{ "machine-id", required_argument, NULL, ARG_MACHINE_ID }, \
{ "service-watchdogs", required_argument, NULL, ARG_SERVICE_WATCHDOGS }
#define SHUTDOWN_GETOPT_OPTIONS \
{ "exit-code", required_argument, NULL, ARG_EXIT_CODE }, \
{ "timeout", required_argument, NULL, ARG_TIMEOUT }
+209 -120
View File
@@ -7,15 +7,76 @@
#include "efivars.h"
#include "extract-word.h"
#include "fileio.h"
#include "getopt-defs.h"
#include "initrd-util.h"
#include "macro.h"
#include "parse-util.h"
#include "proc-cmdline.h"
#include "process-util.h"
#include "special.h"
#include "string-util.h"
#include "strv.h"
#include "virt.h"
int proc_cmdline_filter_pid1_args(
char **argv, /* input, may be reordered by this function. */
char ***ret) {
enum {
COMMON_GETOPT_ARGS,
SYSTEMD_GETOPT_ARGS,
SHUTDOWN_GETOPT_ARGS,
};
static const struct option options[] = {
COMMON_GETOPT_OPTIONS,
SYSTEMD_GETOPT_OPTIONS,
SHUTDOWN_GETOPT_OPTIONS,
{}
};
int saved_optind, saved_opterr, saved_optopt, argc;
char *saved_optarg;
char **filtered;
size_t idx;
assert(argv);
assert(ret);
/* Backup global variables. */
saved_optind = optind;
saved_opterr = opterr;
saved_optopt = optopt;
saved_optarg = optarg;
/* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
* that checks for GNU extensions in optstring ('-' or '+' at the beginning). Here, we do not use
* the GNU extensions, but might be used previously. Hence, we need to always reset it. */
optind = 0;
/* Do not print an error message. */
opterr = 0;
/* Filter out all known options. */
argc = strv_length(argv);
while (getopt_long(argc, argv, SYSTEMD_GETOPT_SHORT_OPTIONS, options, NULL) >= 0)
;
idx = optind;
/* Restore global variables. */
optind = saved_optind;
opterr = saved_opterr;
optopt = saved_optopt;
optarg = saved_optarg;
filtered = strv_copy(strv_skip(argv, idx));
if (!filtered)
return -ENOMEM;
*ret = filtered;
return 0;
}
int proc_cmdline(char **ret) {
const char *e;
@@ -40,71 +101,86 @@ int proc_cmdline(char **ret) {
return read_one_line_file("/proc/cmdline", ret);
}
static int proc_cmdline_extract_first(const char **p, char **ret_word, ProcCmdlineFlags flags) {
const char *q = *p;
static int proc_cmdline_strv_internal(char ***ret, bool filter_pid1_args) {
const char *e;
int r;
for (;;) {
_cleanup_free_ char *word = NULL;
const char *c;
assert(ret);
r = extract_first_word(&q, &word, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
/* For testing purposes it is sometimes useful to be able to override what we consider /proc/cmdline to be */
e = secure_getenv("SYSTEMD_PROC_CMDLINE");
if (e)
return strv_split_full(ret, e, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
if (detect_container() > 0) {
_cleanup_strv_free_ char **args = NULL;
r = get_process_cmdline_strv(1, /* flags = */ 0, &args);
if (r < 0)
return r;
if (r == 0)
break;
/* Filter out arguments that are intended only for the initrd */
c = startswith(word, "rd.");
if (c) {
if (!in_initrd())
continue;
if (filter_pid1_args)
return proc_cmdline_filter_pid1_args(args, ret);
if (FLAGS_SET(flags, PROC_CMDLINE_STRIP_RD_PREFIX)) {
r = free_and_strdup(&word, c);
if (r < 0)
return r;
}
*ret = TAKE_PTR(args);
return 0;
} else if (FLAGS_SET(flags, PROC_CMDLINE_RD_STRICT) && in_initrd())
continue; /* And optionally filter out arguments that are intended only for the host */
} else {
_cleanup_free_ char *s = NULL;
*p = q;
*ret_word = TAKE_PTR(word);
return 1;
r = read_one_line_file("/proc/cmdline", &s);
if (r < 0)
return r;
return strv_split_full(ret, s, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
}
*p = q;
*ret_word = NULL;
return 0;
}
static int proc_cmdline_parse_given(const char *line, proc_cmdline_parse_t parse_item, void *data, ProcCmdlineFlags flags) {
const char *p;
int proc_cmdline_strv(char ***ret) {
return proc_cmdline_strv_internal(ret, /* filter_pid1_args = */ false);
}
static char *mangle_word(const char *word, ProcCmdlineFlags flags) {
char *c;
c = startswith(word, "rd.");
if (c) {
/* Filter out arguments that are intended only for the initrd */
if (!in_initrd())
return NULL;
if (FLAGS_SET(flags, PROC_CMDLINE_STRIP_RD_PREFIX))
return c;
} else if (FLAGS_SET(flags, PROC_CMDLINE_RD_STRICT) && in_initrd())
/* And optionally filter out arguments that are intended only for the host */
return NULL;
return (char*) word;
}
static int proc_cmdline_parse_strv(char **args, proc_cmdline_parse_t parse_item, void *data, ProcCmdlineFlags flags) {
int r;
assert(parse_item);
/* The PROC_CMDLINE_VALUE_OPTIONAL flag doesn't really make sense for proc_cmdline_parse(), let's make this
* clear. */
/* The PROC_CMDLINE_VALUE_OPTIONAL flag doesn't really make sense for proc_cmdline_parse(), let's
* make this clear. */
assert(!FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL));
p = line;
for (;;) {
_cleanup_free_ char *word = NULL;
char *value;
STRV_FOREACH(word, args) {
char *key, *value;
r = proc_cmdline_extract_first(&p, &word, flags);
if (r < 0)
return r;
if (r == 0)
break;
key = mangle_word(*word, flags);
if (!key)
continue;
value = strchr(word, '=');
value = strchr(key, '=');
if (value)
*(value++) = 0;
*(value++) = '\0';
r = parse_item(word, value, data);
r = parse_item(key, value, data);
if (r < 0)
return r;
}
@@ -113,7 +189,7 @@ static int proc_cmdline_parse_given(const char *line, proc_cmdline_parse_t parse
}
int proc_cmdline_parse(proc_cmdline_parse_t parse_item, void *data, ProcCmdlineFlags flags) {
_cleanup_free_ char *line = NULL;
_cleanup_strv_free_ char **args = NULL;
int r;
assert(parse_item);
@@ -121,24 +197,30 @@ int proc_cmdline_parse(proc_cmdline_parse_t parse_item, void *data, ProcCmdlineF
/* We parse the EFI variable first, because later settings have higher priority. */
if (!FLAGS_SET(flags, PROC_CMDLINE_IGNORE_EFI_OPTIONS)) {
_cleanup_free_ char *line = NULL;
r = systemd_efi_options_variable(&line);
if (r < 0) {
if (r != -ENODATA)
log_debug_errno(r, "Failed to get SystemdOptions EFI variable, ignoring: %m");
} else {
r = proc_cmdline_parse_given(line, parse_item, data, flags);
r = strv_split_full(&args, line, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
if (r < 0)
return r;
line = mfree(line);
r = proc_cmdline_parse_strv(args, parse_item, data, flags);
if (r < 0)
return r;
args = strv_free(args);
}
}
r = proc_cmdline(&line);
r = proc_cmdline_strv_internal(&args, /* filter_pid1_args = */ true);
if (r < 0)
return r;
return proc_cmdline_parse_given(line, parse_item, data, flags);
return proc_cmdline_parse_strv(args, parse_item, data, flags);
}
static bool relaxed_equal_char(char a, char b) {
@@ -173,24 +255,19 @@ bool proc_cmdline_key_streq(const char *x, const char *y) {
return true;
}
static int cmdline_get_key(const char *line, const char *key, ProcCmdlineFlags flags, char **ret_value) {
static int cmdline_get_key(char **args, const char *key, ProcCmdlineFlags flags, char **ret_value) {
_cleanup_free_ char *v = NULL;
bool found = false;
const char *p;
int r;
assert(line);
assert(key);
p = line;
for (;;) {
_cleanup_free_ char *word = NULL;
STRV_FOREACH(p, args) {
const char *word;
r = proc_cmdline_extract_first(&p, &word, flags);
if (r < 0)
return r;
if (r == 0)
break;
word = mangle_word(*p, flags);
if (!word)
continue;
if (ret_value) {
const char *e;
@@ -224,6 +301,7 @@ static int cmdline_get_key(const char *line, const char *key, ProcCmdlineFlags f
}
int proc_cmdline_get_key(const char *key, ProcCmdlineFlags flags, char **ret_value) {
_cleanup_strv_free_ char **args = NULL;
_cleanup_free_ char *line = NULL, *v = NULL;
int r;
@@ -247,14 +325,14 @@ int proc_cmdline_get_key(const char *key, ProcCmdlineFlags flags, char **ret_val
if (FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL) && !ret_value)
return -EINVAL;
r = proc_cmdline(&line);
r = proc_cmdline_strv_internal(&args, /* filter_pid1_args = */ true);
if (r < 0)
return r;
if (FLAGS_SET(flags, PROC_CMDLINE_IGNORE_EFI_OPTIONS)) /* Shortcut */
return cmdline_get_key(line, key, flags, ret_value);
return cmdline_get_key(args, key, flags, ret_value);
r = cmdline_get_key(line, key, flags, ret_value ? &v : NULL);
r = cmdline_get_key(args, key, flags, ret_value ? &v : NULL);
if (r < 0)
return r;
if (r > 0) {
@@ -264,7 +342,6 @@ int proc_cmdline_get_key(const char *key, ProcCmdlineFlags flags, char **ret_val
return r;
}
line = mfree(line);
r = systemd_efi_options_variable(&line);
if (r == -ENODATA) {
if (ret_value)
@@ -275,7 +352,12 @@ int proc_cmdline_get_key(const char *key, ProcCmdlineFlags flags, char **ret_val
if (r < 0)
return r;
return cmdline_get_key(line, key, flags, ret_value);
args = strv_free(args);
r = strv_split_full(&args, line, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
if (r < 0)
return r;
return cmdline_get_key(args, key, flags, ret_value);
}
int proc_cmdline_get_bool(const char *key, bool *ret) {
@@ -303,13 +385,45 @@ int proc_cmdline_get_bool(const char *key, bool *ret) {
return 1;
}
int proc_cmdline_get_key_many_internal(ProcCmdlineFlags flags, ...) {
_cleanup_free_ char *line = NULL;
bool processing_efi = true;
const char *p;
va_list ap;
static int cmdline_get_key_ap(ProcCmdlineFlags flags, char* const* args, va_list ap) {
int r, ret = 0;
for (;;) {
char **v;
const char *k, *e;
k = va_arg(ap, const char*);
if (!k)
break;
assert_se(v = va_arg(ap, char**));
STRV_FOREACH(p, args) {
const char *word;
word = mangle_word(*p, flags);
if (!word)
continue;
e = proc_cmdline_key_startswith(word, k);
if (e && *e == '=') {
r = free_and_strdup(v, e + 1);
if (r < 0)
return r;
ret++;
}
}
}
return ret;
}
int proc_cmdline_get_key_many_internal(ProcCmdlineFlags flags, ...) {
_cleanup_strv_free_ char **args = NULL;
int r, ret = 0;
va_list ap;
/* The PROC_CMDLINE_VALUE_OPTIONAL flag doesn't really make sense for proc_cmdline_get_key_many(), let's make
* this clear. */
assert(!FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL));
@@ -317,61 +431,36 @@ int proc_cmdline_get_key_many_internal(ProcCmdlineFlags flags, ...) {
/* This call may clobber arguments on failure! */
if (!FLAGS_SET(flags, PROC_CMDLINE_IGNORE_EFI_OPTIONS)) {
_cleanup_free_ char *line = NULL;
r = systemd_efi_options_variable(&line);
if (r < 0 && r != -ENODATA)
log_debug_errno(r, "Failed to get SystemdOptions EFI variable, ignoring: %m");
if (r >= 0) {
r = strv_split_full(&args, line, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
if (r < 0)
return r;
va_start(ap, flags);
r = cmdline_get_key_ap(flags, args, ap);
va_end(ap);
if (r < 0)
return r;
ret = r;
args = strv_free(args);
}
}
p = line;
for (;;) {
_cleanup_free_ char *word = NULL;
r = proc_cmdline_strv(&args);
if (r < 0)
return r;
r = proc_cmdline_extract_first(&p, &word, flags);
if (r < 0)
return r;
if (r == 0) {
/* We finished with this command line. If this was the EFI one, then let's proceed with the regular one */
if (processing_efi) {
processing_efi = false;
va_start(ap, flags);
r = cmdline_get_key_ap(flags, args, ap);
va_end(ap);
if (r < 0)
return r;
line = mfree(line);
r = proc_cmdline(&line);
if (r < 0)
return r;
p = line;
continue;
}
break;
}
va_start(ap, flags);
for (;;) {
char **v;
const char *k, *e;
k = va_arg(ap, const char*);
if (!k)
break;
assert_se(v = va_arg(ap, char**));
e = proc_cmdline_key_startswith(word, k);
if (e && *e == '=') {
r = free_and_strdup(v, e + 1);
if (r < 0) {
va_end(ap);
return r;
}
ret++;
}
}
va_end(ap);
}
return ret;
return ret + r;
}
+3
View File
@@ -14,7 +14,10 @@ typedef enum ProcCmdlineFlags {
typedef int (*proc_cmdline_parse_t)(const char *key, const char *value, void *data);
int proc_cmdline_filter_pid1_args(char **argv, char ***ret);
int proc_cmdline(char **ret);
int proc_cmdline_strv(char ***ret);
int proc_cmdline_parse(const proc_cmdline_parse_t parse, void *userdata, ProcCmdlineFlags flags);