mirror of
https://github.com/systemd/systemd
synced 2024-09-06 16:56:43 +00:00
Use "dollar-single-quotes" to escape shell-sensitive strings
Also called "ANSI-C Quoting" in info:(bash) ANSI-C Quoting. The escaping rules are a POSIX proposal, and are described in http://austingroupbugs.net/view.php?id=249. There's a lot of back-and-forth on the details of escaping of control characters, but we'll be only using a small subset of the syntax that is common to all proposals and is widely supported. Unfortunately dash and fish and maybe some other shells do not support it (see the man page patch for a list). This allows environment variables to be safely exported using show-environment and imported into the shell. Shells which do not support this syntax will have to do something like export $(systemctl show-environment|grep -v '=\$') or whatever is appropriate in their case. I think csh and fish do not support the A=B syntax anyway, so the change is moot for them. Fixes #5536. v2: - also escape newlines (which currently disallowed in shell values, so this doesn't really matter), and tabs (as $'\t'), and ! (as $'!'). This way quoted output can be included directly in both interactive and noninteractive bash.
This commit is contained in:
parent
42d3bf86bb
commit
804ee07c13
|
@ -1495,11 +1495,26 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
|
|||
<term><command>show-environment</command></term>
|
||||
|
||||
<listitem>
|
||||
<para>Dump the systemd manager environment block. The
|
||||
environment block will be dumped in straight-forward form
|
||||
suitable for sourcing into a shell script. This environment
|
||||
block will be passed to all processes the manager
|
||||
spawns.</para>
|
||||
<para>Dump the systemd manager environment block. This is the environment
|
||||
block that is passed to all processes the manager spawns. The environment
|
||||
block will be dumped in straight-forward form suitable for sourcing into
|
||||
most shells. If no special characters or whitespace is present in the variable
|
||||
values, no escaping is performed, and the assignments have the form
|
||||
<literal>VARIABLE=value</literal>. If whitespace or characters which have
|
||||
special meaning to the shell are present, dollar-single-quote escaping is
|
||||
used, and assignments have the form <literal>VARIABLE=$'value'</literal>.
|
||||
This syntax is known to be supported by
|
||||
<citerefentry project='die-net'><refentrytitle>bash</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry project='die-net'><refentrytitle>zsh</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry project='die-net'><refentrytitle>ksh</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
and
|
||||
<citerefentry project='die-net'><refentrytitle>busybox</refentrytitle><manvolnum>1</manvolnum></citerefentry>'s
|
||||
<citerefentry project='die-net'><refentrytitle>ash</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
but not
|
||||
<citerefentry project='die-net'><refentrytitle>dash</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||
or
|
||||
<citerefentry project='die-net'><refentrytitle>fish</refentrytitle><manvolnum>1</manvolnum></citerefentry>.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
|
|
|
@ -441,10 +441,16 @@ char *octescape(const char *s, size_t len) {
|
|||
|
||||
}
|
||||
|
||||
static char *strcpy_backslash_escaped(char *t, const char *s, const char *bad) {
|
||||
static char *strcpy_backslash_escaped(char *t, const char *s, const char *bad, bool escape_tab_nl) {
|
||||
assert(bad);
|
||||
|
||||
for (; *s; s++) {
|
||||
if (escape_tab_nl && IN_SET(*s, '\n', '\t')) {
|
||||
*(t++) = '\\';
|
||||
*(t++) = *s == '\n' ? 'n' : 't';
|
||||
continue;
|
||||
}
|
||||
|
||||
if (*s == '\\' || strchr(bad, *s))
|
||||
*(t++) = '\\';
|
||||
|
||||
|
@ -461,20 +467,21 @@ char *shell_escape(const char *s, const char *bad) {
|
|||
if (!r)
|
||||
return NULL;
|
||||
|
||||
t = strcpy_backslash_escaped(r, s, bad);
|
||||
t = strcpy_backslash_escaped(r, s, bad, false);
|
||||
*t = 0;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
char *shell_maybe_quote(const char *s) {
|
||||
char* shell_maybe_quote(const char *s, EscapeStyle style) {
|
||||
const char *p;
|
||||
char *r, *t;
|
||||
|
||||
assert(s);
|
||||
|
||||
/* Encloses a string in double quotes if necessary to make it
|
||||
* OK as shell string. */
|
||||
/* Encloses a string in quotes if necessary to make it OK as a shell
|
||||
* string. Note that we treat benign UTF-8 characters as needing
|
||||
* escaping too, but that should be OK. */
|
||||
|
||||
for (p = s; *p; p++)
|
||||
if (*p <= ' ' ||
|
||||
|
@ -485,17 +492,30 @@ char *shell_maybe_quote(const char *s) {
|
|||
if (!*p)
|
||||
return strdup(s);
|
||||
|
||||
r = new(char, 1+strlen(s)*2+1+1);
|
||||
r = new(char, (style == ESCAPE_POSIX) + 1 + strlen(s)*2 + 1 + 1);
|
||||
if (!r)
|
||||
return NULL;
|
||||
|
||||
t = r;
|
||||
*(t++) = '"';
|
||||
if (style == ESCAPE_BACKSLASH)
|
||||
*(t++) = '"';
|
||||
else if (style == ESCAPE_POSIX) {
|
||||
*(t++) = '$';
|
||||
*(t++) = '\'';
|
||||
} else
|
||||
assert_not_reached("Bad EscapeStyle");
|
||||
|
||||
t = mempcpy(t, s, p - s);
|
||||
|
||||
t = strcpy_backslash_escaped(t, p, SHELL_NEED_ESCAPE);
|
||||
if (style == ESCAPE_BACKSLASH)
|
||||
t = strcpy_backslash_escaped(t, p, SHELL_NEED_ESCAPE, false);
|
||||
else
|
||||
t = strcpy_backslash_escaped(t, p, SHELL_NEED_ESCAPE_POSIX, true);
|
||||
|
||||
*(t++)= '"';
|
||||
if (style == ESCAPE_BACKSLASH)
|
||||
*(t++) = '"';
|
||||
else
|
||||
*(t++) = '\'';
|
||||
*t = 0;
|
||||
|
||||
return r;
|
||||
|
|
|
@ -31,13 +31,30 @@
|
|||
/* What characters are special in the shell? */
|
||||
/* must be escaped outside and inside double-quotes */
|
||||
#define SHELL_NEED_ESCAPE "\"\\`$"
|
||||
/* can be escaped or double-quoted */
|
||||
#define SHELL_NEED_QUOTES SHELL_NEED_ESCAPE GLOB_CHARS "'()<>|&;"
|
||||
|
||||
/* Those that can be escaped or double-quoted.
|
||||
*
|
||||
* Stricly speaking, ! does not need to be escaped, except in interactive
|
||||
* mode, but let's be extra nice to the user and quote ! in case this
|
||||
* output is ever used in interactive mode. */
|
||||
#define SHELL_NEED_QUOTES SHELL_NEED_ESCAPE GLOB_CHARS "'()<>|&;!"
|
||||
|
||||
/* Note that we assume control characters would need to be escaped too in
|
||||
* addition to the "special" characters listed here, if they appear in the
|
||||
* string. Current users disallow control characters. Also '"' shall not
|
||||
* be escaped.
|
||||
*/
|
||||
#define SHELL_NEED_ESCAPE_POSIX "\\\'"
|
||||
|
||||
typedef enum UnescapeFlags {
|
||||
UNESCAPE_RELAX = 1,
|
||||
} UnescapeFlags;
|
||||
|
||||
typedef enum EscapeStyle {
|
||||
ESCAPE_BACKSLASH = 1,
|
||||
ESCAPE_POSIX = 2,
|
||||
} EscapeStyle;
|
||||
|
||||
char *cescape(const char *s);
|
||||
char *cescape_length(const char *s, size_t n);
|
||||
size_t cescape_char(char c, char *buf);
|
||||
|
@ -51,4 +68,4 @@ char *xescape(const char *s, const char *bad);
|
|||
char *octescape(const char *s, size_t len);
|
||||
|
||||
char *shell_escape(const char *s, const char *bad);
|
||||
char *shell_maybe_quote(const char *s);
|
||||
char* shell_maybe_quote(const char *s, EscapeStyle style);
|
||||
|
|
|
@ -741,7 +741,7 @@ static void job_print_status_message(Unit *u, JobType t, JobResult result) {
|
|||
if (t == JOB_START && result == JOB_FAILED) {
|
||||
_cleanup_free_ char *quoted;
|
||||
|
||||
quoted = shell_maybe_quote(u->id);
|
||||
quoted = shell_maybe_quote(u->id, ESCAPE_BACKSLASH);
|
||||
manager_status_printf(u->manager, STATUS_TYPE_NORMAL, NULL, "See 'systemctl status %s' for details.", strna(quoted));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ static int load_and_print(void) {
|
|||
t = strchr(*i, '=');
|
||||
assert(t);
|
||||
|
||||
q = shell_maybe_quote(t + 1);
|
||||
q = shell_maybe_quote(t + 1, ESCAPE_BACKSLASH);
|
||||
if (!q)
|
||||
return log_oom();
|
||||
|
||||
|
|
|
@ -860,7 +860,7 @@ static void log_job_error_with_service_result(const char* service, const char *r
|
|||
|
||||
assert(service);
|
||||
|
||||
service_shell_quoted = shell_maybe_quote(service);
|
||||
service_shell_quoted = shell_maybe_quote(service, ESCAPE_BACKSLASH);
|
||||
|
||||
if (extra_args) {
|
||||
_cleanup_free_ char *t;
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
#include "dropin.h"
|
||||
#include "efivars.h"
|
||||
#include "env-util.h"
|
||||
#include "escape.h"
|
||||
#include "exit-status.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
|
@ -5573,6 +5574,24 @@ static int reset_failed(int argc, char *argv[], void *userdata) {
|
|||
return r;
|
||||
}
|
||||
|
||||
static int print_variable(const char *s) {
|
||||
const char *sep;
|
||||
_cleanup_free_ char *esc = NULL;
|
||||
|
||||
sep = strchr(s, '=');
|
||||
if (!sep) {
|
||||
log_error("Invalid environment block");
|
||||
return -EUCLEAN;
|
||||
}
|
||||
|
||||
esc = shell_maybe_quote(sep + 1, ESCAPE_POSIX);
|
||||
if (!esc)
|
||||
return log_oom();
|
||||
|
||||
printf("%.*s=%s\n", (int)(sep-s), s, esc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int show_environment(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
|
||||
|
@ -5602,8 +5621,11 @@ static int show_environment(int argc, char *argv[], void *userdata) {
|
|||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
|
||||
while ((r = sd_bus_message_read_basic(reply, SD_BUS_TYPE_STRING, &text)) > 0)
|
||||
puts(text);
|
||||
while ((r = sd_bus_message_read_basic(reply, SD_BUS_TYPE_STRING, &text)) > 0) {
|
||||
r = print_variable(text);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
|
||||
|
|
|
@ -86,25 +86,52 @@ static void test_shell_escape(void) {
|
|||
test_shell_escape_one("foo:bar,baz", ",:", "foo\\:bar\\,baz");
|
||||
}
|
||||
|
||||
static void test_shell_maybe_quote_one(const char *s, const char *expected) {
|
||||
_cleanup_free_ char *r;
|
||||
static void test_shell_maybe_quote_one(const char *s,
|
||||
EscapeStyle style,
|
||||
const char *expected) {
|
||||
_cleanup_free_ char *ret = NULL;
|
||||
|
||||
assert_se(r = shell_maybe_quote(s));
|
||||
assert_se(streq(r, expected));
|
||||
assert_se(ret = shell_maybe_quote(s, style));
|
||||
log_debug("[%s] → [%s] (%s)", s, ret, expected);
|
||||
assert_se(streq(ret, expected));
|
||||
}
|
||||
|
||||
static void test_shell_maybe_quote(void) {
|
||||
|
||||
test_shell_maybe_quote_one("", "");
|
||||
test_shell_maybe_quote_one("\\", "\"\\\\\"");
|
||||
test_shell_maybe_quote_one("\"", "\"\\\"\"");
|
||||
test_shell_maybe_quote_one("foobar", "foobar");
|
||||
test_shell_maybe_quote_one("foo bar", "\"foo bar\"");
|
||||
test_shell_maybe_quote_one("foo \"bar\" waldo", "\"foo \\\"bar\\\" waldo\"");
|
||||
test_shell_maybe_quote_one("foo$bar", "\"foo\\$bar\"");
|
||||
test_shell_maybe_quote_one("", ESCAPE_BACKSLASH, "");
|
||||
test_shell_maybe_quote_one("", ESCAPE_POSIX, "");
|
||||
test_shell_maybe_quote_one("\\", ESCAPE_BACKSLASH, "\"\\\\\"");
|
||||
test_shell_maybe_quote_one("\\", ESCAPE_POSIX, "$'\\\\'");
|
||||
test_shell_maybe_quote_one("\"", ESCAPE_BACKSLASH, "\"\\\"\"");
|
||||
test_shell_maybe_quote_one("\"", ESCAPE_POSIX, "$'\"'");
|
||||
test_shell_maybe_quote_one("foobar", ESCAPE_BACKSLASH, "foobar");
|
||||
test_shell_maybe_quote_one("foobar", ESCAPE_POSIX, "foobar");
|
||||
test_shell_maybe_quote_one("foo bar", ESCAPE_BACKSLASH, "\"foo bar\"");
|
||||
test_shell_maybe_quote_one("foo bar", ESCAPE_POSIX, "$'foo bar'");
|
||||
test_shell_maybe_quote_one("foo\tbar", ESCAPE_BACKSLASH, "\"foo\tbar\"");
|
||||
test_shell_maybe_quote_one("foo\tbar", ESCAPE_POSIX, "$'foo\\tbar'");
|
||||
test_shell_maybe_quote_one("foo\nbar", ESCAPE_BACKSLASH, "\"foo\nbar\"");
|
||||
test_shell_maybe_quote_one("foo\nbar", ESCAPE_POSIX, "$'foo\\nbar'");
|
||||
test_shell_maybe_quote_one("foo \"bar\" waldo", ESCAPE_BACKSLASH, "\"foo \\\"bar\\\" waldo\"");
|
||||
test_shell_maybe_quote_one("foo \"bar\" waldo", ESCAPE_POSIX, "$'foo \"bar\" waldo'");
|
||||
test_shell_maybe_quote_one("foo$bar", ESCAPE_BACKSLASH, "\"foo\\$bar\"");
|
||||
test_shell_maybe_quote_one("foo$bar", ESCAPE_POSIX, "$'foo$bar'");
|
||||
|
||||
/* Note that current users disallow control characters, so this "test"
|
||||
* is here merely to establish current behaviour. If control characters
|
||||
* were allowed, they should be quoted, i.e. \001 should become \\001. */
|
||||
test_shell_maybe_quote_one("a\nb\001", ESCAPE_BACKSLASH, "\"a\nb\001\"");
|
||||
test_shell_maybe_quote_one("a\nb\001", ESCAPE_POSIX, "$'a\\nb\001'");
|
||||
|
||||
test_shell_maybe_quote_one("foo!bar", ESCAPE_BACKSLASH, "\"foo!bar\"");
|
||||
test_shell_maybe_quote_one("foo!bar", ESCAPE_POSIX, "$'foo!bar'");
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
log_set_max_level(LOG_DEBUG);
|
||||
log_parse_environment();
|
||||
log_open();
|
||||
|
||||
test_cescape();
|
||||
test_cunescape();
|
||||
test_shell_escape();
|
||||
|
|
Loading…
Reference in a new issue