diff --git a/NEWS b/NEWS
index 77b38f5d6c..de3d28d668 100644
--- a/NEWS
+++ b/NEWS
@@ -7,6 +7,13 @@ CHANGES WITH 236 in spe:
numdummies=0, preventing the kernel from automatically creating
dummy0. All dummy interfaces must now be explicitly created.
+ * Unknown specifiers are now rejected. This applies to units and
+ tmpfiles.d configuration. Any percent characters that are followed by
+ a letter or digit that are not supposed to be interpreted as the
+ beginning of a specifier should be escaped by doubling ("%%").
+ (So "size=5%" is still accepted, as well as "size=5%,foo=bar", but
+ not "LABEL=x%y%z" since %y and %z are not valid specifiers today.)
+
* systemd-resolved now maintains a new dynamic
/run/systemd/resolve/stub-resolv.conf compatibility file. It is
recommended to make /etc/resolv.conf a symlink to it. This file
diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml
index 9c40562e08..06a7bbd3ba 100644
--- a/man/systemd.unit.xml
+++ b/man/systemd.unit.xml
@@ -1297,7 +1297,8 @@
Many settings resolve specifiers which may be used to write
generic unit files referring to runtime or unit parameters that
- are replaced when the unit files are loaded. The following
+ are replaced when the unit files are loaded. Specifiers must be known
+ and resolvable for the setting to be valid. The following
specifiers are understood:
diff --git a/man/tmpfiles.d.xml b/man/tmpfiles.d.xml
index d983984fd6..642a124eac 100644
--- a/man/tmpfiles.d.xml
+++ b/man/tmpfiles.d.xml
@@ -594,6 +594,7 @@ r! /tmp/.X[0-9]*-lock
Specifiers
Specifiers can be used in the "path" and "argument" fields.
+ An unknown or unresolvable specifier is treated as invalid configuration.
The following expansions are understood:
Specifiers available
diff --git a/src/shared/specifier.c b/src/shared/specifier.c
index 9bef890346..9034cc20d7 100644
--- a/src/shared/specifier.c
+++ b/src/shared/specifier.c
@@ -41,6 +41,10 @@
*
*/
+/* Any ASCII character or digit: our pool of potential specifiers,
+ * and "%" used for escaping. */
+#define POSSIBLE_SPECIFIERS ALPHANUMERICAL "%"
+
int specifier_printf(const char *text, const Specifier table[], void *userdata, char **_ret) {
char *ret, *t;
const char *f;
@@ -97,7 +101,10 @@ int specifier_printf(const char *text, const Specifier table[], void *userdata,
ret = n;
t = n + j + k;
- } else {
+ } else if (strchr(POSSIBLE_SPECIFIERS, *f))
+ /* Oops, an unknown specifier. */
+ return -EBADSLT;
+ else {
*(t++) = '%';
*(t++) = *f;
}
diff --git a/src/test/test-systemd-tmpfiles.py b/src/test/test-systemd-tmpfiles.py
index 0f45bd4f76..75d8fd3657 100755
--- a/src/test/test-systemd-tmpfiles.py
+++ b/src/test/test-systemd-tmpfiles.py
@@ -32,10 +32,10 @@ if __name__ == '__main__':
test_invalid_line('f++ /too/many/plusses')
test_invalid_line('f+!+ /too/many/plusses')
test_invalid_line('f!+! /too/many/bangs')
- #test_invalid_line('w /unresolved/argument - - - - "%Y"')
- #test_invalid_line('w /unresolved/argument/sandwich - - - - "%v%Y%v"')
- #test_invalid_line('w /unresolved/filename/%Y - - - - "whatever"')
- #test_invalid_line('w /unresolved/filename/sandwich/%v%Y%v - - - - "whatever"')
+ test_invalid_line('w /unresolved/argument - - - - "%Y"')
+ test_invalid_line('w /unresolved/argument/sandwich - - - - "%v%Y%v"')
+ test_invalid_line('w /unresolved/filename/%Y - - - - "whatever"')
+ test_invalid_line('w /unresolved/filename/sandwich/%v%Y%v - - - - "whatever"')
test_invalid_line('w - - - - - "no file specfied"')
test_invalid_line('C - - - - - "no file specfied"')
test_invalid_line('C non/absolute/path - - - - -')
diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c
index e0d96d612d..4e615288da 100644
--- a/src/tmpfiles/tmpfiles.c
+++ b/src/tmpfiles/tmpfiles.c
@@ -1867,7 +1867,8 @@ static int parse_line(const char *fname, unsigned line, const char *buffer, bool
&age,
NULL);
if (r < 0) {
- if (r == -EINVAL) /* invalid quoting and such */
+ if (IN_SET(r, -EINVAL, -EBADSLT))
+ /* invalid quoting and such or an unknown specifier */
*invalid_config = true;
return log_error_errno(r, "[%s:%u] Failed to parse line: %m", fname, line);
}
@@ -1916,8 +1917,8 @@ static int parse_line(const char *fname, unsigned line, const char *buffer, bool
if (r == -ENOKEY)
return log_unresolvable_specifier(fname, line);
if (r < 0) {
- /* ENOMEM is the only return value left after ENOKEY,
- * so *invalid_config should not be set. */
+ if (IN_SET(r, -EINVAL, -EBADSLT))
+ *invalid_config = true;
return log_error_errno(r, "[%s:%u] Failed to replace specifiers: %s", fname, line, path);
}
@@ -2028,7 +2029,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer, bool
return -EBADMSG;
}
r = parse_attribute_from_arg(&i);
- if (r == -EINVAL)
+ if (IN_SET(r, -EINVAL, -EBADSLT))
*invalid_config = true;
if (r < 0)
return r;
@@ -2054,9 +2055,12 @@ static int parse_line(const char *fname, unsigned line, const char *buffer, bool
r = specifier_expansion_from_arg(&i);
if (r == -ENOKEY)
return log_unresolvable_specifier(fname, line);
- if (r < 0)
+ if (r < 0) {
+ if (IN_SET(r, -EINVAL, -EBADSLT))
+ *invalid_config = true;
return log_error_errno(r, "[%s:%u] Failed to substitute specifiers in argument: %m",
fname, line);
+ }
if (arg_root) {
char *p;