new file mode 100644
@@ -0,0 +1,856 @@
+If a user is created with a strictly-speaking invalid name such as '0day' and a
+unit created to run as that user, systemd rejects the username and runs the unit
+as root.
+
+CVE: CVE-2017-1000082
+Upstream-Status: Backport
+Signed-off-by: Ross Burton <ross.burton@intel.com>
+
+From e0c4eb1435d50cb3797cf94100d4886dc2022bce Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Thu, 14 Jul 2016 12:23:39 +0200
+Subject: [PATCH 1/3] sysusers: move various user credential validity checks to
+ src/basic/
+
+This way we can reuse them for validating User=/Group= settings in unit files
+(to be added in a later commit).
+
+Also, add some tests for them.
+---
+ src/basic/user-util.c | 93 +++++++++++++++++++++++++++++++++++++++++++++++
+ src/basic/user-util.h | 5 +++
+ src/sysusers/sysusers.c | 75 --------------------------------------
+ src/test/test-user-util.c | 87 ++++++++++++++++++++++++++++++++++++++++++++
+ 4 files changed, 185 insertions(+), 75 deletions(-)
+
+diff --git a/src/basic/user-util.c b/src/basic/user-util.c
+index f65ca3eda..c85b5c6a8 100644
+--- a/src/basic/user-util.c
++++ b/src/basic/user-util.c
+@@ -29,6 +29,7 @@
+ #include <string.h>
+ #include <sys/stat.h>
+ #include <unistd.h>
++#include <utmp.h>
+
+ #include "missing.h"
+ #include "alloc-util.h"
+@@ -39,6 +40,7 @@
+ #include "path-util.h"
+ #include "string-util.h"
+ #include "user-util.h"
++#include "utf8.h"
+
+ bool uid_is_valid(uid_t uid) {
+
+@@ -479,3 +481,94 @@ int take_etc_passwd_lock(const char *root) {
+
+ return fd;
+ }
++
++bool valid_user_group_name(const char *u) {
++ const char *i;
++ long sz;
++
++ /* Checks if the specified name is a valid user/group name. */
++
++ if (isempty(u))
++ return false;
++
++ if (!(u[0] >= 'a' && u[0] <= 'z') &&
++ !(u[0] >= 'A' && u[0] <= 'Z') &&
++ u[0] != '_')
++ return false;
++
++ for (i = u+1; *i; i++) {
++ if (!(*i >= 'a' && *i <= 'z') &&
++ !(*i >= 'A' && *i <= 'Z') &&
++ !(*i >= '0' && *i <= '9') &&
++ *i != '_' &&
++ *i != '-')
++ return false;
++ }
++
++ sz = sysconf(_SC_LOGIN_NAME_MAX);
++ assert_se(sz > 0);
++
++ if ((size_t) (i-u) > (size_t) sz)
++ return false;
++
++ if ((size_t) (i-u) > UT_NAMESIZE - 1)
++ return false;
++
++ return true;
++}
++
++bool valid_user_group_name_or_id(const char *u) {
++
++ /* Similar as above, but is also fine with numeric UID/GID specifications, as long as they are in the right
++ * range, and not the invalid user ids. */
++
++ if (isempty(u))
++ return false;
++
++ if (valid_user_group_name(u))
++ return true;
++
++ return parse_uid(u, NULL) >= 0;
++}
++
++bool valid_gecos(const char *d) {
++
++ if (!d)
++ return false;
++
++ if (!utf8_is_valid(d))
++ return false;
++
++ if (string_has_cc(d, NULL))
++ return false;
++
++ /* Colons are used as field separators, and hence not OK */
++ if (strchr(d, ':'))
++ return false;
++
++ return true;
++}
++
++bool valid_home(const char *p) {
++
++ if (isempty(p))
++ return false;
++
++ if (!utf8_is_valid(p))
++ return false;
++
++ if (string_has_cc(p, NULL))
++ return false;
++
++ if (!path_is_absolute(p))
++ return false;
++
++ if (!path_is_safe(p))
++ return false;
++
++ /* Colons are used as field separators, and hence not OK */
++ if (strchr(p, ':'))
++ return false;
++
++ return true;
++}
+diff --git a/src/basic/user-util.h b/src/basic/user-util.h
+index 8026eca3f..36f71fb00 100644
+--- a/src/basic/user-util.h
++++ b/src/basic/user-util.h
+@@ -68,3 +68,8 @@ int take_etc_passwd_lock(const char *root);
+ static inline bool userns_supported(void) {
+ return access("/proc/self/uid_map", F_OK) >= 0;
+ }
++
++bool valid_user_group_name(const char *u);
++bool valid_user_group_name_or_id(const char *u);
++bool valid_gecos(const char *d);
++bool valid_home(const char *p);
+diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c
+index 4377f1b91..df3b7de30 100644
+--- a/src/sysusers/sysusers.c
++++ b/src/sysusers/sysusers.c
+@@ -1299,81 +1299,6 @@ static bool item_equal(Item *a, Item *b) {
+ return true;
+ }
+
+-static bool valid_user_group_name(const char *u) {
+- const char *i;
+- long sz;
+-
+- if (isempty(u))
+- return false;
+-
+- if (!(u[0] >= 'a' && u[0] <= 'z') &&
+- !(u[0] >= 'A' && u[0] <= 'Z') &&
+- u[0] != '_')
+- return false;
+-
+- for (i = u+1; *i; i++) {
+- if (!(*i >= 'a' && *i <= 'z') &&
+- !(*i >= 'A' && *i <= 'Z') &&
+- !(*i >= '0' && *i <= '9') &&
+- *i != '_' &&
+- *i != '-')
+- return false;
+- }
+-
+- sz = sysconf(_SC_LOGIN_NAME_MAX);
+- assert_se(sz > 0);
+-
+- if ((size_t) (i-u) > (size_t) sz)
+- return false;
+-
+- if ((size_t) (i-u) > UT_NAMESIZE - 1)
+- return false;
+-
+- return true;
+-}
+-
+-static bool valid_gecos(const char *d) {
+-
+- if (!d)
+- return false;
+-
+- if (!utf8_is_valid(d))
+- return false;
+-
+- if (string_has_cc(d, NULL))
+- return false;
+-
+- /* Colons are used as field separators, and hence not OK */
+- if (strchr(d, ':'))
+- return false;
+-
+- return true;
+-}
+-
+-static bool valid_home(const char *p) {
+-
+- if (isempty(p))
+- return false;
+-
+- if (!utf8_is_valid(p))
+- return false;
+-
+- if (string_has_cc(p, NULL))
+- return false;
+-
+- if (!path_is_absolute(p))
+- return false;
+-
+- if (!path_is_safe(p))
+- return false;
+-
+- /* Colons are used as field separators, and hence not OK */
+- if (strchr(p, ':'))
+- return false;
+-
+- return true;
+-}
+-
+ static int parse_line(const char *fname, unsigned line, const char *buffer) {
+
+ static const Specifier specifier_table[] = {
+diff --git a/src/test/test-user-util.c b/src/test/test-user-util.c
+index 8d1ec19f1..2a344a9f9 100644
+--- a/src/test/test-user-util.c
++++ b/src/test/test-user-util.c
+@@ -61,6 +61,88 @@ static void test_uid_ptr(void) {
+ assert_se(PTR_TO_UID(UID_TO_PTR(1000)) == 1000);
+ }
+
++static void test_valid_user_group_name(void) {
++ assert_se(!valid_user_group_name(NULL));
++ assert_se(!valid_user_group_name(""));
++ assert_se(!valid_user_group_name("1"));
++ assert_se(!valid_user_group_name("65535"));
++ assert_se(!valid_user_group_name("-1"));
++ assert_se(!valid_user_group_name("-kkk"));
++ assert_se(!valid_user_group_name("rööt"));
++ assert_se(!valid_user_group_name("."));
++ assert_se(!valid_user_group_name("eff.eff"));
++ assert_se(!valid_user_group_name("foo\nbar"));
++ assert_se(!valid_user_group_name("0123456789012345678901234567890123456789"));
++ assert_se(!valid_user_group_name_or_id("aaa:bbb"));
++
++ assert_se(valid_user_group_name("root"));
++ assert_se(valid_user_group_name("lennart"));
++ assert_se(valid_user_group_name("LENNART"));
++ assert_se(valid_user_group_name("_kkk"));
++ assert_se(valid_user_group_name("kkk-"));
++ assert_se(valid_user_group_name("kk-k"));
++
++ assert_se(valid_user_group_name("some5"));
++ assert_se(!valid_user_group_name("5some"));
++ assert_se(valid_user_group_name("INNER5NUMBER"));
++}
++
++static void test_valid_user_group_name_or_id(void) {
++ assert_se(!valid_user_group_name_or_id(NULL));
++ assert_se(!valid_user_group_name_or_id(""));
++ assert_se(valid_user_group_name_or_id("0"));
++ assert_se(valid_user_group_name_or_id("1"));
++ assert_se(valid_user_group_name_or_id("65534"));
++ assert_se(!valid_user_group_name_or_id("65535"));
++ assert_se(valid_user_group_name_or_id("65536"));
++ assert_se(!valid_user_group_name_or_id("-1"));
++ assert_se(!valid_user_group_name_or_id("-kkk"));
++ assert_se(!valid_user_group_name_or_id("rööt"));
++ assert_se(!valid_user_group_name_or_id("."));
++ assert_se(!valid_user_group_name_or_id("eff.eff"));
++ assert_se(!valid_user_group_name_or_id("foo\nbar"));
++ assert_se(!valid_user_group_name_or_id("0123456789012345678901234567890123456789"));
++ assert_se(!valid_user_group_name_or_id("aaa:bbb"));
++
++ assert_se(valid_user_group_name_or_id("root"));
++ assert_se(valid_user_group_name_or_id("lennart"));
++ assert_se(valid_user_group_name_or_id("LENNART"));
++ assert_se(valid_user_group_name_or_id("_kkk"));
++ assert_se(valid_user_group_name_or_id("kkk-"));
++ assert_se(valid_user_group_name_or_id("kk-k"));
++
++ assert_se(valid_user_group_name_or_id("some5"));
++ assert_se(!valid_user_group_name_or_id("5some"));
++ assert_se(valid_user_group_name_or_id("INNER5NUMBER"));
++}
++
++static void test_valid_gecos(void) {
++
++ assert_se(!valid_gecos(NULL));
++ assert_se(valid_gecos(""));
++ assert_se(valid_gecos("test"));
++ assert_se(valid_gecos("Ümläüt"));
++ assert_se(!valid_gecos("In\nvalid"));
++ assert_se(!valid_gecos("In:valid"));
++}
++
++static void test_valid_home(void) {
++
++ assert_se(!valid_home(NULL));
++ assert_se(!valid_home(""));
++ assert_se(!valid_home("."));
++ assert_se(!valid_home("/home/.."));
++ assert_se(!valid_home("/home/../"));
++ assert_se(!valid_home("/home\n/foo"));
++ assert_se(!valid_home("./piep"));
++ assert_se(!valid_home("piep"));
++ assert_se(!valid_home("/home/user:lennart"));
++
++ assert_se(valid_home("/"));
++ assert_se(valid_home("/home"));
++ assert_se(valid_home("/home/foo"));
++}
++
+ int main(int argc, char*argv[]) {
+
+ test_uid_to_name_one(0, "root");
+@@ -75,5 +157,10 @@ int main(int argc, char*argv[]) {
+ test_parse_uid();
+ test_uid_ptr();
+
++ test_valid_user_group_name();
++ test_valid_user_group_name_or_id();
++ test_valid_gecos();
++ test_valid_home();
++
+ return 0;
+ }
+--
+2.11.0
+
+
+From 1affacaaf6eff93e53563a644567cc5c3930cb28 Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Thu, 14 Jul 2016 12:28:06 +0200
+Subject: [PATCH 2/3] core: be stricter when parsing User=/Group= fields
+
+Let's verify the validity of the syntax of the user/group names set.
+---
+ src/core/load-fragment-gperf.gperf.m4 | 10 +--
+ src/core/load-fragment.c | 118 ++++++++++++++++++++++++++++++++++
+ src/core/load-fragment.h | 2 +
+ 3 files changed, 125 insertions(+), 5 deletions(-)
+
+diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
+index 819341898..110089696 100644
+--- a/src/core/load-fragment-gperf.gperf.m4
++++ b/src/core/load-fragment-gperf.gperf.m4
+@@ -19,9 +19,9 @@ m4_dnl Define the context options only once
+ m4_define(`EXEC_CONTEXT_CONFIG_ITEMS',
+ `$1.WorkingDirectory, config_parse_working_directory, 0, offsetof($1, exec_context)
+ $1.RootDirectory, config_parse_unit_path_printf, 0, offsetof($1, exec_context.root_directory)
+-$1.User, config_parse_unit_string_printf, 0, offsetof($1, exec_context.user)
+-$1.Group, config_parse_unit_string_printf, 0, offsetof($1, exec_context.group)
+-$1.SupplementaryGroups, config_parse_strv, 0, offsetof($1, exec_context.supplementary_groups)
++$1.User, config_parse_user_group, 0, offsetof($1, exec_context.user)
++$1.Group, config_parse_user_group, 0, offsetof($1, exec_context.group)
++$1.SupplementaryGroups, config_parse_user_group_strv, 0, offsetof($1, exec_context.supplementary_groups)
+ $1.Nice, config_parse_exec_nice, 0, offsetof($1, exec_context)
+ $1.OOMScoreAdjust, config_parse_exec_oom_score_adjust, 0, offsetof($1, exec_context)
+ $1.IOSchedulingClass, config_parse_exec_io_class, 0, offsetof($1, exec_context)
+@@ -275,8 +275,8 @@ Socket.ExecStartPost, config_parse_exec, SOCKET_EXEC
+ Socket.ExecStopPre, config_parse_exec, SOCKET_EXEC_STOP_PRE, offsetof(Socket, exec_command)
+ Socket.ExecStopPost, config_parse_exec, SOCKET_EXEC_STOP_POST, offsetof(Socket, exec_command)
+ Socket.TimeoutSec, config_parse_sec, 0, offsetof(Socket, timeout_usec)
+-Socket.SocketUser, config_parse_unit_string_printf, 0, offsetof(Socket, user)
+-Socket.SocketGroup, config_parse_unit_string_printf, 0, offsetof(Socket, group)
++Socket.SocketUser, config_parse_user_group, 0, offsetof(Socket, user)
++Socket.SocketGroup, config_parse_user_group, 0, offsetof(Socket, group)
+ Socket.SocketMode, config_parse_mode, 0, offsetof(Socket, socket_mode)
+ Socket.DirectoryMode, config_parse_mode, 0, offsetof(Socket, directory_mode)
+ Socket.Accept, config_parse_bool, 0, offsetof(Socket, accept)
+diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
+index 86b4fb071..f43781803 100644
+--- a/src/core/load-fragment.c
++++ b/src/core/load-fragment.c
+@@ -64,6 +64,7 @@
+ #include "unit-name.h"
+ #include "unit-printf.h"
+ #include "unit.h"
++#include "user-util.h"
+ #include "utf8.h"
+ #include "web-util.h"
+
+@@ -1758,6 +1759,123 @@ int config_parse_sec_fix_0(
+ return 0;
+ }
+
++int config_parse_user_group(
++ const char *unit,
++ const char *filename,
++ unsigned line,
++ const char *section,
++ unsigned section_line,
++ const char *lvalue,
++ int ltype,
++ const char *rvalue,
++ void *data,
++ void *userdata) {
++
++ char **user = data, *n;
++ Unit *u = userdata;
++ int r;
++
++ assert(filename);
++ assert(lvalue);
++ assert(rvalue);
++ assert(u);
++
++ if (isempty(rvalue))
++ n = NULL;
++ else {
++ _cleanup_free_ char *k = NULL;
++
++ r = unit_full_printf(u, rvalue, &k);
++ if (r < 0) {
++ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers in %s, ignoring: %m", rvalue);
++ return 0;
++ }
++
++ if (!valid_user_group_name_or_id(k)) {
++ log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid user/group name or numeric ID, ignoring: %s", k);
++ return 0;
++ }
++
++ n = k;
++ k = NULL;
++ }
++
++ free(*user);
++ *user = n;
++
++ return 0;
++}
++
++int config_parse_user_group_strv(
++ const char *unit,
++ const char *filename,
++ unsigned line,
++ const char *section,
++ unsigned section_line,
++ const char *lvalue,
++ int ltype,
++ const char *rvalue,
++ void *data,
++ void *userdata) {
++
++ char ***users = data;
++ Unit *u = userdata;
++ const char *p;
++ int r;
++
++ assert(filename);
++ assert(lvalue);
++ assert(rvalue);
++ assert(u);
++
++ if (isempty(rvalue)) {
++ char **empty;
++
++ empty = new0(char*, 1);
++ if (!empty)
++ return log_oom();
++
++ strv_free(*users);
++ *users = empty;
++
++ return 0;
++ }
++
++ p = rvalue;
++ for (;;) {
++ _cleanup_free_ char *word = NULL, *k = NULL;
++
++ r = extract_first_word(&p, &word, WHITESPACE, 0);
++ if (r == 0)
++ break;
++ if (r == -ENOMEM)
++ return log_oom();
++ if (r < 0) {
++ log_syntax(unit, LOG_ERR, filename, line, r, "Invalid syntax, ignoring: %s", rvalue);
++ break;
++ }
++
++ r = unit_full_printf(u, word, &k);
++ if (r < 0) {
++ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers in %s, ignoring: %m", word);
++ continue;
++ }
++
++ if (!valid_user_group_name_or_id(k)) {
++ log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid user/group name or numeric ID, ignoring: %s", k);
++ continue;
++ }
++
++ r = strv_push(users, k);
++ if (r < 0)
++ return log_oom();
++
++ k = NULL;
++ }
++
++ return 0;
++}
++
+ int config_parse_busname_service(
+ const char *unit,
+ const char *filename,
+diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h
+index b36a2e3a0..213bce55a 100644
+--- a/src/core/load-fragment.h
++++ b/src/core/load-fragment.h
+@@ -111,6 +111,8 @@ int config_parse_exec_utmp_mode(const char *unit, const char *filename, unsigned
+ int config_parse_working_directory(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+ int config_parse_fdname(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+ int config_parse_sec_fix_0(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
++int config_parse_user_group(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
++int config_parse_user_group_strv(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+
+ /* gperf prototypes */
+ const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, unsigned length);
+--
+2.11.0
+
+
+From 97e0456384ed5c930394062d340237ea6130ece0 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
+Date: Thu, 6 Jul 2017 13:28:19 -0400
+Subject: [PATCH 3/3] core/load-fragment: refuse units with errors in certain
+ directives
+
+If an error is encountered in any of the Exec* lines, WorkingDirectory,
+SELinuxContext, ApparmorProfile, SmackProcessLabel, Service (in .socket
+units), User, or Group, refuse to load the unit. If the config stanza
+has support, ignore the failure if '-' is present.
+
+For those configuration directives, even if we started the unit, it's
+pretty likely that it'll do something unexpected (like write files
+in a wrong place, or with a wrong context, or run with wrong permissions,
+etc). It seems better to refuse to start the unit and have the admin
+clean up the configuration without giving the service a chance to mess
+up stuff.
+
+Note that all "security" options that restrict what the unit can do
+(Capabilities, AmbientCapabilities, Restrict*, SystemCallFilter, Limit*,
+PrivateDevices, Protect*, etc) are _not_ treated like this. Such options are
+only supplementary, and are not always available depending on the architecture
+and compilation options, so unit authors have to make sure that the service
+runs correctly without them anyway.
+
+Fixes #6237, #6277.
+
+Signed-off-by: Ross Burton <ross.burton@intel.com>
+---
+ src/core/load-fragment.c | 101 ++++++++++++++++++++++++++++------------------
+ src/test/test-unit-file.c | 14 +++----
+ 2 files changed, 69 insertions(+), 46 deletions(-)
+
+diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
+index f43781803..b1fb1d407 100644
+--- a/src/core/load-fragment.c
++++ b/src/core/load-fragment.c
+@@ -626,20 +626,28 @@ int config_parse_exec(
+
+ if (isempty(f)) {
+ /* First word is either "-" or "@" with no command. */
+- log_syntax(unit, LOG_ERR, filename, line, 0, "Empty path in command line, ignoring: \"%s\"", rvalue);
+- return 0;
++ log_syntax(unit, LOG_ERR, filename, line, 0,
++ "Empty path in command line%s: \"%s\"",
++ ignore ? ", ignoring" : "", rvalue);
++ return ignore ? 0 : -ENOEXEC;
+ }
+ if (!string_is_safe(f)) {
+- log_syntax(unit, LOG_ERR, filename, line, 0, "Executable path contains special characters, ignoring: %s", rvalue);
+- return 0;
++ log_syntax(unit, LOG_ERR, filename, line, 0,
++ "Executable path contains special characters%s: %s",
++ ignore ? ", ignoring" : "", rvalue);
++ return ignore ? 0 : -ENOEXEC;
+ }
+ if (!path_is_absolute(f)) {
+- log_syntax(unit, LOG_ERR, filename, line, 0, "Executable path is not absolute, ignoring: %s", rvalue);
+- return 0;
++ log_syntax(unit, LOG_ERR, filename, line, 0,
++ "Executable path is not absolute%s: %s",
++ ignore ? ", ignoring" : "", rvalue);
++ return ignore ? 0 : -ENOEXEC;
+ }
+ if (endswith(f, "/")) {
+- log_syntax(unit, LOG_ERR, filename, line, 0, "Executable path specifies a directory, ignoring: %s", rvalue);
+- return 0;
++ log_syntax(unit, LOG_ERR, filename, line, 0,
++ "Executable path specifies a directory%s: %s",
++ ignore ? ", ignoring" : "", rvalue);
++ return ignore ? 0 : -ENOEXEC;
+ }
+
+ if (f == firstword) {
+@@ -695,7 +703,7 @@ int config_parse_exec(
+ if (r == 0)
+ break;
+ else if (r < 0)
+- return 0;
++ return ignore ? 0 : -ENOEXEC;
+
+ if (!GREEDY_REALLOC(n, nbufsize, nlen + 2))
+ return log_oom();
+@@ -705,8 +713,10 @@ int config_parse_exec(
+ }
+
+ if (!n || !n[0]) {
+- log_syntax(unit, LOG_ERR, filename, line, 0, "Empty executable name or zeroeth argument, ignoring: %s", rvalue);
+- return 0;
++ log_syntax(unit, LOG_ERR, filename, line, 0,
++ "Empty executable name or zeroeth argument%s: %s",
++ ignore ? ", ignoring" : "", rvalue);
++ return ignore ? 0 : -ENOEXEC;
+ }
+
+ nce = new0(ExecCommand, 1);
+@@ -1214,8 +1224,10 @@ int config_parse_exec_selinux_context(
+
+ r = unit_name_printf(u, rvalue, &k);
+ if (r < 0) {
+- log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m");
+- return 0;
++ log_syntax(unit, LOG_ERR, filename, line, r,
++ "Failed to resolve specifiers%s: %m",
++ ignore ? ", ignoring" : "");
++ return ignore ? 0 : -ENOEXEC;
+ }
+
+ free(c->selinux_context);
+@@ -1262,8 +1274,10 @@ int config_parse_exec_apparmor_profile(
+
+ r = unit_name_printf(u, rvalue, &k);
+ if (r < 0) {
+- log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m");
+- return 0;
++ log_syntax(unit, LOG_ERR, filename, line, r,
++ "Failed to resolve specifiers%s: %m",
++ ignore ? ", ignoring" : "");
++ return ignore ? 0 : -ENOEXEC;
+ }
+
+ free(c->apparmor_profile);
+@@ -1310,8 +1324,10 @@ int config_parse_exec_smack_process_label(
+
+ r = unit_name_printf(u, rvalue, &k);
+ if (r < 0) {
+- log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m");
+- return 0;
++ log_syntax(unit, LOG_ERR, filename, line, r,
++ "Failed to resolve specifiers%s: %m",
++ ignore ? ", ignoring" : "");
++ return ignore ? 0 : -ENOEXEC;
+ }
+
+ free(c->smack_process_label);
+@@ -1520,19 +1536,19 @@ int config_parse_socket_service(
+
+ r = unit_name_printf(UNIT(s), rvalue, &p);
+ if (r < 0) {
+- log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %s", rvalue);
+- return 0;
++ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers: %s", rvalue);
++ return -ENOEXEC;
+ }
+
+ if (!endswith(p, ".service")) {
+- log_syntax(unit, LOG_ERR, filename, line, 0, "Unit must be of type service, ignoring: %s", rvalue);
+- return 0;
++ log_syntax(unit, LOG_ERR, filename, line, 0, "Unit must be of type service: %s", rvalue);
++ return -ENOEXEC;
+ }
+
+ r = manager_load_unit(UNIT(s)->manager, p, NULL, &error, &x);
+ if (r < 0) {
+- log_syntax(unit, LOG_ERR, filename, line, r, "Failed to load unit %s, ignoring: %s", rvalue, bus_error_message(&error, r));
+- return 0;
++ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to load unit %s: %s", rvalue, bus_error_message(&error, r));
++ return -ENOEXEC;
+ }
+
+ unit_ref_set(&s->service, x);
+@@ -1787,13 +1803,13 @@ int config_parse_user_group(
+
+ r = unit_full_printf(u, rvalue, &k);
+ if (r < 0) {
+- log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers in %s, ignoring: %m", rvalue);
+- return 0;
++ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers in %s: %m", rvalue);
++ return -ENOEXEC;
+ }
+
+ if (!valid_user_group_name_or_id(k)) {
+- log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid user/group name or numeric ID, ignoring: %s", k);
+- return 0;
++ log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid user/group name or numeric ID: %s", k);
++ return -ENOEXEC;
+ }
+
+ n = k;
+@@ -1851,19 +1867,19 @@ int config_parse_user_group_strv(
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+- log_syntax(unit, LOG_ERR, filename, line, r, "Invalid syntax, ignoring: %s", rvalue);
+- break;
++ log_syntax(unit, LOG_ERR, filename, line, r, "Invalid syntax: %s", rvalue);
++ return -ENOEXEC;
+ }
+
+ r = unit_full_printf(u, word, &k);
+ if (r < 0) {
+- log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers in %s, ignoring: %m", word);
+- continue;
++ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers in %s: %m", word);
++ return -ENOEXEC;
+ }
+
+ if (!valid_user_group_name_or_id(k)) {
+- log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid user/group name or numeric ID, ignoring: %s", k);
+- continue;
++ log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid user/group name or numeric ID: %s", k);
++ return -ENOEXEC;
+ }
+
+ r = strv_push(users, k);
+@@ -2022,20 +2038,24 @@ int config_parse_working_directory(
+
+ r = unit_full_printf(u, rvalue, &k);
+ if (r < 0) {
+- log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers in working directory path '%s', ignoring: %m", rvalue);
+- return 0;
++ log_syntax(unit, LOG_ERR, filename, line, r,
++ "Failed to resolve unit specifiers in working directory path '%s'%s: %m",
++ rvalue, missing_ok ? ", ignoring" : "");
++ return missing_ok ? 0 : -ENOEXEC;
+ }
+
+ path_kill_slashes(k);
+
+ if (!utf8_is_valid(k)) {
+ log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, rvalue);
+- return 0;
++ return missing_ok ? 0 : -ENOEXEC;
+ }
+
+ if (!path_is_absolute(k)) {
+- log_syntax(unit, LOG_ERR, filename, line, 0, "Working directory path '%s' is not absolute, ignoring.", rvalue);
+- return 0;
++ log_syntax(unit, LOG_ERR, filename, line, 0,
++ "Working directory path '%s' is not absolute%s.",
++ rvalue, missing_ok ? ", ignoring" : "");
++ return missing_ok ? 0 : -ENOEXEC;
+ }
+
+ free(c->working_directory);
+@@ -4043,8 +4063,11 @@ int unit_load_fragment(Unit *u) {
+ return r;
+
+ r = load_from_path(u, k);
+- if (r < 0)
++ if (r < 0) {
++ if (r == -ENOEXEC)
++ log_unit_notice(u, "Unit configuration has fatal error, unit will not be started.");
+ return r;
++ }
+
+ if (u->load_state == UNIT_STUB) {
+ SET_FOREACH(t, u->names, i) {
+diff --git a/src/test/test-unit-file.c b/src/test/test-unit-file.c
+index ade0ff2a6..fe1969570 100644
+--- a/src/test/test-unit-file.c
++++ b/src/test/test-unit-file.c
+@@ -146,7 +146,7 @@ static void test_config_parse_exec(void) {
+ r = config_parse_exec(NULL, "fake", 4, "section", 1,
+ "LValue", 0, "/RValue/ argv0 r1",
+ &c, u);
+- assert_se(r == 0);
++ assert_se(r == -ENOEXEC);
+ assert_se(c1->command_next == NULL);
+
+ log_info("/* honour_argv0 */");
+@@ -161,7 +161,7 @@ static void test_config_parse_exec(void) {
+ r = config_parse_exec(NULL, "fake", 3, "section", 1,
+ "LValue", 0, "@/RValue",
+ &c, u);
+- assert_se(r == 0);
++ assert_se(r == -ENOEXEC);
+ assert_se(c1->command_next == NULL);
+
+ log_info("/* no command, whitespace only, reset */");
+@@ -220,7 +220,7 @@ static void test_config_parse_exec(void) {
+ "-@/RValue argv0 r1 ; ; "
+ "/goo/goo boo",
+ &c, u);
+- assert_se(r >= 0);
++ assert_se(r == -ENOEXEC);
+ c1 = c1->command_next;
+ check_execcommand(c1, "/RValue", "argv0", "r1", NULL, true);
+
+@@ -374,7 +374,7 @@ static void test_config_parse_exec(void) {
+ r = config_parse_exec(NULL, "fake", 4, "section", 1,
+ "LValue", 0, path,
+ &c, u);
+- assert_se(r == 0);
++ assert_se(r == -ENOEXEC);
+ assert_se(c1->command_next == NULL);
+ }
+
+@@ -401,21 +401,21 @@ static void test_config_parse_exec(void) {
+ r = config_parse_exec(NULL, "fake", 4, "section", 1,
+ "LValue", 0, "/path\\",
+ &c, u);
+- assert_se(r == 0);
++ assert_se(r == -ENOEXEC);
+ assert_se(c1->command_next == NULL);
+
+ log_info("/* missing ending ' */");
+ r = config_parse_exec(NULL, "fake", 4, "section", 1,
+ "LValue", 0, "/path 'foo",
+ &c, u);
+- assert_se(r == 0);
++ assert_se(r == -ENOEXEC);
+ assert_se(c1->command_next == NULL);
+
+ log_info("/* missing ending ' with trailing backslash */");
+ r = config_parse_exec(NULL, "fake", 4, "section", 1,
+ "LValue", 0, "/path 'foo\\",
+ &c, u);
+- assert_se(r == 0);
++ assert_se(r == -ENOEXEC);
+ assert_se(c1->command_next == NULL);
+
+ log_info("/* invalid space between modifiers */");
+--
+2.11.0
+
@@ -36,6 +36,7 @@ SRC_URI += " \
file://0022-socket-util-don-t-fail-if-libc-doesn-t-support-IDN.patch \
file://udev-re-enable-mount-propagation-for-udevd.patch \
file://CVE-2016-7795.patch \
+ file://validate-user.patch \
"
SRC_URI_append_libc-uclibc = "\
file://0002-units-Prefer-getty-to-agetty-in-console-setup-system.patch \
If a unit has a statement such as User=0day where the username exists but is strictly speaking invalid, the unit will be started as the root user instead. Backport a patch from upstream to mitigate this by refusing to start units such as this. Signed-off-by: Ross Burton <ross.burton@intel.com> --- .../systemd/systemd/validate-user.patch | 856 +++++++++++++++++++++ meta/recipes-core/systemd/systemd_230.bb | 1 + 2 files changed, 857 insertions(+) create mode 100644 meta/recipes-core/systemd/systemd/validate-user.patch