From 9c9e3cf238c09ef02c5dd99ec477122bc19db95d Mon Sep 17 00:00:00 2001 From: yangmingtaip Date: Wed, 16 Feb 2022 11:53:41 +0800 Subject: [PATCH] CVE-2020-13776 --- ...il-Allow-names-starting-with-a-digit.patch | 105 +++ ...-order-of-checks-in-valid_user_group.patch | 39 + ...il-rework-how-we-validate-user-names.patch | 793 ++++++++++++++++++ ...r-document-explaining-our-rules-on-u.patch | 191 +++++ ...e-new-USER_NAMES-document-everywhere.patch | 58 ++ ...ntry-for-SD_MESSAGE_UNSAFE_USER_NAME.patch | 45 + ...always-use-base-10-for-user-group-nu.patch | 73 ++ ...imes-it-is-useful-to-check-if-a-stri.patch | 151 ++++ ...76-basic-parse-util-add-safe_atoux64.patch | 132 +++ ...allow-tweaking-how-to-parse-integers.patch | 155 ++++ ...il-allow-0-as-alternative-to-0-and-0.patch | 61 ++ ...return-parameter-optional-in-safe_at.patch | 32 + ...te-parse_mode-on-top-of-safe_atou_fu.patch | 60 ++ ...6-user-util-be-stricter-in-parse_uid.patch | 79 ++ ...parse-integers-prefixed-with-0b-and-.patch | 165 ++++ systemd.spec | 21 +- 16 files changed, 2159 insertions(+), 1 deletion(-) create mode 100644 backport-0001-CVE-2020-13776-user-util-Allow-names-starting-with-a-digit.patch create mode 100644 backport-0002-CVE-2020-13776-user-util-switch-order-of-checks-in-valid_user_group.patch create mode 100644 backport-0003-CVE-2020-13776-user-util-rework-how-we-validate-user-names.patch create mode 100644 backport-0004-CVE-2020-13776-docs-add-a-longer-document-explaining-our-rules-on-u.patch create mode 100644 backport-0005-CVE-2020-13776-docs-hook-up-the-new-USER_NAMES-document-everywhere.patch create mode 100644 backport-0006-CVE-2020-13776-catalog-add-entry-for-SD_MESSAGE_UNSAFE_USER_NAME.patch create mode 100644 backport-0007-CVE-2020-13776-basic-user-util-always-use-base-10-for-user-group-nu.patch create mode 100644 backport-0008-CVE-2020-13776-parse-util-sometimes-it-is-useful-to-check-if-a-stri.patch create mode 100644 backport-0009-CVE-2020-13776-basic-parse-util-add-safe_atoux64.patch create mode 100644 backport-0010-CVE-2020-13776-parse-util-allow-tweaking-how-to-parse-integers.patch create mode 100644 backport-0011-CVE-2020-13776-parse-util-allow-0-as-alternative-to-0-and-0.patch create mode 100644 backport-0012-CVE-2020-13776-parse-util-make-return-parameter-optional-in-safe_at.patch create mode 100644 backport-0013-CVE-2020-13776-parse-util-rewrite-parse_mode-on-top-of-safe_atou_fu.patch create mode 100644 backport-0014-CVE-2020-13776-user-util-be-stricter-in-parse_uid.patch create mode 100644 backport-0015-CVE-2020-13776-parse-util-also-parse-integers-prefixed-with-0b-and-.patch diff --git a/backport-0001-CVE-2020-13776-user-util-Allow-names-starting-with-a-digit.patch b/backport-0001-CVE-2020-13776-user-util-Allow-names-starting-with-a-digit.patch new file mode 100644 index 0000000..9350477 --- /dev/null +++ b/backport-0001-CVE-2020-13776-user-util-Allow-names-starting-with-a-digit.patch @@ -0,0 +1,105 @@ +From 93c23c9297e48e594785e0bb9c51504aae5fbe3e Mon Sep 17 00:00:00 2001 +From: Balint Reczey +Date: Wed, 18 Mar 2020 18:29:02 +0100 +Subject: [PATCH] user-util: Allow names starting with a digit + +In 1a29610f5fa1bcb2eeb37d2c6b79d8d1a6dbb865 the change inadvertedly +disabled names with digit as the first character. This follow-up change +allows a digit as the first character in compat mode. + +Fixes: #15141 +Reference: https://github.com/systemd/systemd/commit/93c23c9297e48e594785e0bb9c51504aae5fbe3e +Conflict: NA +--- + src/basic/user-util.c | 20 +++++++++++++++++--- + src/test/test-user-util.c | 4 ++-- + 2 files changed, 19 insertions(+), 5 deletions(-) + +diff --git a/src/basic/user-util.c b/src/basic/user-util.c +index a491f5505e..e998a46e72 100644 +--- a/src/basic/user-util.c ++++ b/src/basic/user-util.c +@@ -701,16 +701,18 @@ int take_etc_passwd_lock(const char *root) { + bool valid_user_group_name_full(const char *u, bool strict) { + const char *i; + long sz; ++ bool warned = false; + + /* Checks if the specified name is a valid user/group name. Also see POSIX IEEE Std 1003.1-2008, 2016 Edition, + * 3.437. We are a bit stricter here however. Specifically we deviate from POSIX rules: + * + * - We require that names fit into the appropriate utmp field + * - We don't allow empty user names +- * - No dots or digits in the first character ++ * - No dots in the first character + * + * If strict==true, additionally: + * - We don't allow any dots (this conflicts with chown syntax which permits dots as user/group name separator) ++ * - We don't allow a digit as the first character + * + * Note that other systems are even more restrictive, and don't permit underscores or uppercase characters. + */ +@@ -720,17 +722,26 @@ bool valid_user_group_name_full(const char *u, bool strict) { + + if (!(u[0] >= 'a' && u[0] <= 'z') && + !(u[0] >= 'A' && u[0] <= 'Z') && ++ !(u[0] >= '0' && u[0] <= '9' && !strict) && + u[0] != '_') + return false; + +- bool warned = false; ++ bool only_digits_seen = u[0] >= '0' && u[0] <= '9'; ++ ++ if (only_digits_seen) { ++ log_warning("User or group name \"%s\" starts with a digit, accepting for compatibility.", u); ++ warned = true; ++ } + + for (i = u+1; *i; i++) { + if (((*i >= 'a' && *i <= 'z') || + (*i >= 'A' && *i <= 'Z') || + (*i >= '0' && *i <= '9') || +- IN_SET(*i, '_', '-'))) ++ IN_SET(*i, '_', '-'))) { ++ if (!(*i >= '0' && *i <= '9')) ++ only_digits_seen = false; + continue; ++ } + + if (*i == '.' && !strict) { + if (!warned) { +@@ -744,6 +755,9 @@ bool valid_user_group_name_full(const char *u, bool strict) { + return false; + } + ++ if (only_digits_seen) ++ return false; ++ + sz = sysconf(_SC_LOGIN_NAME_MAX); + assert_se(sz > 0); + +diff --git a/src/test/test-user-util.c b/src/test/test-user-util.c +index 084a584876..11c01e5189 100644 +--- a/src/test/test-user-util.c ++++ b/src/test/test-user-util.c +@@ -96,7 +96,7 @@ static void test_valid_user_group_name_compat(void) { + assert_se(valid_user_group_name_compat("eff.")); + + assert_se(valid_user_group_name_compat("some5")); +- assert_se(!valid_user_group_name_compat("5some")); ++ assert_se(valid_user_group_name_compat("5some")); + assert_se(valid_user_group_name_compat("INNER5NUMBER")); + } + +@@ -166,7 +166,7 @@ static void test_valid_user_group_name_or_id_compat(void) { + assert_se(valid_user_group_name_or_id_compat("kk-k")); + + assert_se(valid_user_group_name_or_id_compat("some5")); +- assert_se(!valid_user_group_name_or_id_compat("5some")); ++ assert_se(valid_user_group_name_or_id_compat("5some")); + assert_se(valid_user_group_name_or_id_compat("INNER5NUMBER")); + } + +-- +2.23.0 + diff --git a/backport-0002-CVE-2020-13776-user-util-switch-order-of-checks-in-valid_user_group.patch b/backport-0002-CVE-2020-13776-user-util-switch-order-of-checks-in-valid_user_group.patch new file mode 100644 index 0000000..e1c041a --- /dev/null +++ b/backport-0002-CVE-2020-13776-user-util-switch-order-of-checks-in-valid_user_group.patch @@ -0,0 +1,39 @@ +From a85daa0dfb3eb03be9845760e90e54b9af8fb00e Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Mon, 30 Mar 2020 21:46:01 +0200 +Subject: [PATCH] user-util: switch order of checks in + valid_user_group_name_or_id_full() + +When we are supposed to accept numeric UIDs formatted as string, then +let's check that first, before passing things on to +valid_user_group_name_full(), since that might log about, and not the +other way round. + +See: #15201 +Follow-up for: 93c23c9297e48e594785e0bb9c51504aae5fbe3e +Reference: https://github.com/systemd/systemd/commit/a85daa0dfb3eb03be9845760e90e54b9af8fb00e +Conflict: NA +--- + src/basic/user-util.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/basic/user-util.c b/src/basic/user-util.c +index e998a46e72..1510fc96ef 100644 +--- a/src/basic/user-util.c ++++ b/src/basic/user-util.c +@@ -778,10 +778,10 @@ bool valid_user_group_name_or_id_full(const char *u, bool strict) { + if (isempty(u)) + return false; + +- if (valid_user_group_name_full(u, strict)) ++ if (parse_uid(u, NULL) >= 0) + return true; + +- return parse_uid(u, NULL) >= 0; ++ return valid_user_group_name_full(u, strict); + } + + bool valid_gecos(const char *d) { +-- +2.23.0 + diff --git a/backport-0003-CVE-2020-13776-user-util-rework-how-we-validate-user-names.patch b/backport-0003-CVE-2020-13776-user-util-rework-how-we-validate-user-names.patch new file mode 100644 index 0000000..56f70c1 --- /dev/null +++ b/backport-0003-CVE-2020-13776-user-util-rework-how-we-validate-user-names.patch @@ -0,0 +1,793 @@ +From 7a8867abfab10e5bbca10590ec2aa40c5b27d8fb Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Sat, 4 Apr 2020 12:23:02 +0200 +Subject: [PATCH] user-util: rework how we validate user names +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This reworks the user validation infrastructure. There are now two +modes. In regular mode we are strict and test against a strict set of +valid chars. And in "relaxed" mode we just filter out some really +obvious, dangerous stuff. i.e. strict is whitelisting what is OK, but +"relaxed" is blacklisting what is really not OK. + +The idea is that we use strict mode whenver we allocate a new user +(i.e. in sysusers.d or homed), while "relaxed" mode is when we process +users registered elsewhere, (i.e. userdb, logind, …) + +The requirements on user name validity vary wildly. SSSD thinks its fine +to embedd "@" for example, while the suggested NAME_REGEX field on +Debian does not even allow uppercase chars… + +This effectively liberaralizes a lot what we expect from usernames. + +The code that warns about questionnable user names is now optional and +only used at places such as unit file parsing, so that it doesn't show +up on every userdb query, but only when processing configuration files +that know better. + +Fixes: #15149 #15090 +Reference: https://github.com/systemd/systemd/commit/7a8867abfab10e5bbca10590ec2aa40c5b27d8fb +Conflict: Remove unneeded file and change the context. +--- + src/basic/user-util.c | 185 +++++++++++++---------- + src/basic/user-util.h | 21 +-- + src/core/dbus-execute.c | 6 +- + src/core/dbus-manager.c | 2 +- + src/core/dbus-socket.c | 4 +- + src/core/dbus-util.c | 7 +- + src/core/dbus-util.h | 2 +- + src/core/dynamic-user.c | 2 +- + src/core/load-fragment.c | 4 +- + src/core/unit.c | 2 +- + src/nss-systemd/nss-systemd.c | 6 +- + src/systemd/sd-messages.h | 3 + + src/sysusers/sysusers.c | 4 +- + src/test/test-user-util.c | 271 ++++++++++++++++++---------------- + 14 files changed, 287 insertions(+), 232 deletions(-) + +diff --git a/src/basic/user-util.c b/src/basic/user-util.c +index 1510fc96ef..2e3580017d 100644 +--- a/src/basic/user-util.c ++++ b/src/basic/user-util.c +@@ -10,6 +10,8 @@ + #include + #include + ++#include "sd-messages.h" ++ + #include "alloc-util.h" + #include "errno-util.h" + #include "fd-util.h" +@@ -698,90 +701,123 @@ int take_etc_passwd_lock(const char *root) { + return fd; + } + +-bool valid_user_group_name_full(const char *u, bool strict) { ++bool valid_user_group_name(const char *u, ValidUserFlags flags) { + const char *i; +- long sz; +- bool warned = false; + +- /* Checks if the specified name is a valid user/group name. Also see POSIX IEEE Std 1003.1-2008, 2016 Edition, +- * 3.437. We are a bit stricter here however. Specifically we deviate from POSIX rules: +- * +- * - We require that names fit into the appropriate utmp field +- * - We don't allow empty user names +- * - No dots in the first character +- * +- * If strict==true, additionally: +- * - We don't allow any dots (this conflicts with chown syntax which permits dots as user/group name separator) +- * - We don't allow a digit as the first character ++ /* Checks if the specified name is a valid user/group name. There are two flavours of this call: ++ * strict mode is the default which is POSIX plus some extra rules; and relaxed mode where we accept ++ * pretty much everything except the really worst offending names. + * +- * Note that other systems are even more restrictive, and don't permit underscores or uppercase characters. +- */ ++ * Whenever we synthesize users ourselves we should use the strict mode. But when we process users ++ * created by other stuff, let's be more liberal. */ + +- if (isempty(u)) ++ if (isempty(u)) /* An empty user name is never valid */ + return false; + +- if (!(u[0] >= 'a' && u[0] <= 'z') && +- !(u[0] >= 'A' && u[0] <= 'Z') && +- !(u[0] >= '0' && u[0] <= '9' && !strict) && +- u[0] != '_') +- return false; +- +- bool only_digits_seen = u[0] >= '0' && u[0] <= '9'; ++ if (parse_uid(u, NULL) >= 0) /* Something that parses as numeric UID string is valid exactly when the ++ * flag for it is set */ ++ return FLAGS_SET(flags, VALID_USER_ALLOW_NUMERIC); ++ ++ if (FLAGS_SET(flags, VALID_USER_RELAX)) { ++ ++ /* In relaxed mode we just check very superficially. Apparently SSSD and other stuff is ++ * extremely liberal (way too liberal if you ask me, even inserting "@" in user names, which ++ * is bound to cause problems for example when used with an MTA), hence only filter the most ++ * obvious cases, or where things would result in an invalid entry if such a user name would ++ * show up in /etc/passwd (or equivalent getent output). ++ * ++ * Note that we stepped far out of POSIX territory here. It's not our fault though, but ++ * SSSD's, Samba's and everybody else who ignored POSIX on this. (I mean, I am happy to step ++ * outside of POSIX' bounds any day, but I must say in this case I probably wouldn't ++ * have...) */ ++ ++ if (startswith(u, " ") || endswith(u, " ")) /* At least expect whitespace padding is removed ++ * at front and back (accept in the middle, since ++ * that's apparently a thing on Windows). Note ++ * that this also blocks usernames consisting of ++ * whitespace only. */ ++ return false; + +- if (only_digits_seen) { +- log_warning("User or group name \"%s\" starts with a digit, accepting for compatibility.", u); +- warned = true; +- } ++ if (!utf8_is_valid(u)) /* We want to synthesize JSON from this, hence insist on UTF-8 */ ++ return false; + +- for (i = u+1; *i; i++) { +- if (((*i >= 'a' && *i <= 'z') || +- (*i >= 'A' && *i <= 'Z') || +- (*i >= '0' && *i <= '9') || +- IN_SET(*i, '_', '-'))) { +- if (!(*i >= '0' && *i <= '9')) +- only_digits_seen = false; +- continue; +- } +- +- if (*i == '.' && !strict) { +- if (!warned) { +- log_warning("Bad user or group name \"%s\", accepting for compatibility.", u); +- warned = true; +- } +- +- continue; +- } ++ if (string_has_cc(u, NULL)) /* CC characters are just dangerous (and \n in particular is the ++ * record separator in /etc/passwd), so we can't allow that. */ ++ return false; + +- return false; +- } ++ if (strpbrk(u, ":/")) /* Colons are the field separator in /etc/passwd, we can't allow ++ * that. Slashes are special to file systems paths and user names ++ * typically show up in the file system as home directories, hence ++ * don't allow slashes. */ ++ return false; + +- if (only_digits_seen) +- return false; ++ if (in_charset(u, "0123456789")) /* Don't allow fully numeric strings, they might be confused ++ * with with UIDs (note that this test is more broad than ++ * the parse_uid() test above, as it will cover more than ++ * the 32bit range, and it will detect 65535 (which is in ++ * invalid UID, even though in the unsigned 32 bit range) */ ++ return false; + +- sz = sysconf(_SC_LOGIN_NAME_MAX); +- assert_se(sz > 0); ++ if (u[0] == '-' && in_charset(u + 1, "0123456789")) /* Don't allow negative fully numeric ++ * strings either. After all some people ++ * write 65535 as -1 (even though that's ++ * not even true on 32bit uid_t ++ * anyway) */ ++ return false; + +- if ((size_t) (i-u) > (size_t) sz) +- return false; ++ if (dot_or_dot_dot(u)) /* User names typically become home directory names, and these two are ++ * special in that context, don't allow that. */ ++ return false; + +- if ((size_t) (i-u) > UT_NAMESIZE - 1) +- return false; ++ /* Compare with strict result and warn if result doesn't match */ ++ if (FLAGS_SET(flags, VALID_USER_WARN) && !valid_user_group_name(u, 0)) ++ log_struct(LOG_NOTICE, ++ "MESSAGE=Accepting user/group name '%s', which does not match strict user/group name rules.", u, ++ "USER_GROUP_NAME=%s", u, ++ "MESSAGE_ID=" SD_MESSAGE_UNSAFE_USER_NAME_STR); + +- return true; +-} ++ /* Note that we make no restrictions on the length in relaxed mode! */ ++ } else { ++ long sz; ++ size_t l; ++ ++ /* Also see POSIX IEEE Std 1003.1-2008, 2016 Edition, 3.437. We are a bit stricter here ++ * however. Specifically we deviate from POSIX rules: ++ * ++ * - We don't allow empty user names (see above) ++ * - We require that names fit into the appropriate utmp field ++ * - We don't allow any dots (this conflicts with chown syntax which permits dots as user/group name separator) ++ * - We don't allow dashes or digit as the first character ++ * ++ * Note that other systems are even more restrictive, and don't permit underscores or uppercase characters. ++ */ ++ ++ if (!(u[0] >= 'a' && u[0] <= 'z') && ++ !(u[0] >= 'A' && u[0] <= 'Z') && ++ u[0] != '_') ++ return false; + +-bool valid_user_group_name_or_id_full(const char *u, bool strict) { ++ for (i = u+1; *i; i++) ++ if (!(*i >= 'a' && *i <= 'z') && ++ !(*i >= 'A' && *i <= 'Z') && ++ !(*i >= '0' && *i <= '9') && ++ !IN_SET(*i, '_', '-')) ++ return false; + +- /* 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. */ ++ l = i - u; + +- if (isempty(u)) +- return false; ++ sz = sysconf(_SC_LOGIN_NAME_MAX); ++ assert_se(sz > 0); + +- if (parse_uid(u, NULL) >= 0) +- return true; ++ if (l > (size_t) sz) ++ return false; ++ if (l > FILENAME_MAX) ++ return false; ++ if (l > UT_NAMESIZE - 1) ++ return false; ++ } + +- return valid_user_group_name_full(u, strict); ++ return true; + } + + bool valid_gecos(const char *d) { +diff --git a/src/basic/user-util.h b/src/basic/user-util.h +index 6796ac42c1..1f267d21a3 100644 +--- a/src/basic/user-util.h ++++ b/src/basic/user-util.h +@@ -97,20 +97,13 @@ static inline bool userns_supported(void) { + return access("/proc/self/uid_map", F_OK) >= 0; + } + +-bool valid_user_group_name_full(const char *u, bool strict); +-bool valid_user_group_name_or_id_full(const char *u, bool strict); +-static inline bool valid_user_group_name(const char *u) { +- return valid_user_group_name_full(u, true); +-} +-static inline bool valid_user_group_name_or_id(const char *u) { +- return valid_user_group_name_or_id_full(u, true); +-} +-static inline bool valid_user_group_name_compat(const char *u) { +- return valid_user_group_name_full(u, false); +-} +-static inline bool valid_user_group_name_or_id_compat(const char *u) { +- return valid_user_group_name_or_id_full(u, false); +-} ++typedef enum ValidUserFlags { ++ VALID_USER_RELAX = 1 << 0, ++ VALID_USER_WARN = 1 << 1, ++ VALID_USER_ALLOW_NUMERIC = 1 << 2, ++} ValidUserFlags; ++ ++bool valid_user_group_name(const char *u, ValidUserFlags flags); + bool valid_gecos(const char *d); + bool valid_home(const char *p); + +diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c +index 5696a60ba8..93857436b4 100644 +--- a/src/core/dbus-execute.c ++++ b/src/core/dbus-execute.c +@@ -1204,10 +1204,10 @@ int bus_exec_context_set_transient_property( + flags |= UNIT_PRIVATE; + + if (streq(name, "User")) +- return bus_set_transient_user_compat(u, name, &c->user, message, flags, error); ++ return bus_set_transient_user_relaxed(u, name, &c->user, message, flags, error); + + if (streq(name, "Group")) +- return bus_set_transient_user_compat(u, name, &c->group, message, flags, error); ++ return bus_set_transient_user_relaxed(u, name, &c->group, message, flags, error); + + if (streq(name, "TTYPath")) + return bus_set_transient_path(u, name, &c->tty_path, message, flags, error); +@@ -1392,7 +1392,7 @@ int bus_exec_context_set_transient_property( + return r; + + STRV_FOREACH(p, l) +- if (!isempty(*p) && !valid_user_group_name_or_id_compat(*p)) ++ if (!isempty(*p) && !valid_user_group_name(*p, VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX|VALID_USER_WARN)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Invalid supplementary group names"); + +diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c +index 60f55aef5f..ef4bb316cc 100644 +--- a/src/core/dbus-manager.c ++++ b/src/core/dbus-manager.c +@@ -1643,7 +1643,7 @@ static int method_lookup_dynamic_user_by_name(sd_bus_message *message, void *use + + if (!MANAGER_IS_SYSTEM(m)) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Dynamic users are only supported in the system instance."); +- if (!valid_user_group_name(name)) ++ if (!valid_user_group_name(name, VALID_USER_RELAX)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User name invalid: %s", name); + + r = dynamic_user_lookup_name(m, name, &uid); +diff --git a/src/core/dbus-socket.c b/src/core/dbus-socket.c +index c8253c7940..ad7b41a95b 100644 +--- a/src/core/dbus-socket.c ++++ b/src/core/dbus-socket.c +@@ -278,10 +278,10 @@ static int bus_socket_set_transient_property( + return bus_set_transient_fdname(u, name, &s->fdname, message, flags, error); + + if (streq(name, "SocketUser")) +- return bus_set_transient_user_compat(u, name, &s->user, message, flags, error); ++ return bus_set_transient_user_relaxed(u, name, &s->user, message, flags, error); + + if (streq(name, "SocketGroup")) +- return bus_set_transient_user_compat(u, name, &s->group, message, flags, error); ++ return bus_set_transient_user_relaxed(u, name, &s->group, message, flags, error); + + if (streq(name, "BindIPv6Only")) + return bus_set_transient_bind_ipv6_only(u, name, &s->bind_ipv6_only, message, flags, error); +diff --git a/src/core/dbus-util.c b/src/core/dbus-util.c +index 7862beaacb..951450e53d 100644 +--- a/src/core/dbus-util.c ++++ b/src/core/dbus-util.c +@@ -30,7 +30,12 @@ int bus_property_get_triggered_unit( + + BUS_DEFINE_SET_TRANSIENT(mode_t, "u", uint32_t, mode_t, "%040o"); + BUS_DEFINE_SET_TRANSIENT(unsigned, "u", uint32_t, unsigned, "%" PRIu32); +-BUS_DEFINE_SET_TRANSIENT_STRING_WITH_CHECK(user_compat, valid_user_group_name_or_id_compat); ++ ++static inline bool valid_user_group_name_or_id_relaxed(const char *u) { ++ return valid_user_group_name(u, VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX); ++} ++ ++BUS_DEFINE_SET_TRANSIENT_STRING_WITH_CHECK(user_relaxed, valid_user_group_name_or_id_relaxed); + BUS_DEFINE_SET_TRANSIENT_STRING_WITH_CHECK(path, path_is_absolute); + + int bus_set_transient_string( +diff --git a/src/core/dbus-util.h b/src/core/dbus-util.h +index ec8c245fff..654ceb5279 100644 +--- a/src/core/dbus-util.h ++++ b/src/core/dbus-util.h +@@ -236,7 +236,7 @@ int bus_property_get_triggered_unit(sd_bus *bus, const char *path, const char *i + + int bus_set_transient_mode_t(Unit *u, const char *name, mode_t *p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error); + int bus_set_transient_unsigned(Unit *u, const char *name, unsigned *p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error); +-int bus_set_transient_user_compat(Unit *u, const char *name, char **p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error); ++int bus_set_transient_user_relaxed(Unit *u, const char *name, char **p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error); + int bus_set_transient_path(Unit *u, const char *name, char **p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error); + int bus_set_transient_string(Unit *u, const char *name, char **p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error); + int bus_set_transient_bool(Unit *u, const char *name, bool *p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error); +diff --git a/src/core/dynamic-user.c b/src/core/dynamic-user.c +index f1819b36bc..58b37925bf 100644 +--- a/src/core/dynamic-user.c ++++ b/src/core/dynamic-user.c +@@ -116,7 +116,7 @@ static int dynamic_user_acquire(Manager *m, const char *name, DynamicUser** ret) + return 0; + } + +- if (!valid_user_group_name_or_id(name)) ++ if (!valid_user_group_name(name, VALID_USER_ALLOW_NUMERIC)) + return -EINVAL; + + if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, storage_socket) < 0) +diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c +index 646364eb89..f3fe73535e 100644 +--- a/src/core/load-fragment.c ++++ b/src/core/load-fragment.c +@@ -2068,7 +2068,7 @@ int config_parse_user_group_compat( + return -ENOEXEC; + } + +- if (!valid_user_group_name_or_id_compat(k)) { ++ if (!valid_user_group_name(k, VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX|VALID_USER_WARN)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid user/group name or numeric ID: %s", k); + return -ENOEXEC; + } +@@ -2122,7 +2122,7 @@ int config_parse_user_group_strv_compat( + return -ENOEXEC; + } + +- if (!valid_user_group_name_or_id_compat(k)) { ++ if (!valid_user_group_name(k, VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX|VALID_USER_WARN)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid user/group name or numeric ID: %s", k); + return -ENOEXEC; + } +diff --git a/src/core/unit.c b/src/core/unit.c +index 96e1a6c320..95d574d8d4 100644 +--- a/src/core/unit.c ++++ b/src/core/unit.c +@@ -4287,7 +4287,7 @@ static int user_from_unit_name(Unit *u, char **ret) { + if (r < 0) + return r; + +- if (valid_user_group_name(n)) { ++ if (valid_user_group_name(n, 0)) { + *ret = TAKE_PTR(n); + return 0; + } +diff --git a/src/nss-systemd/nss-systemd.c b/src/nss-systemd/nss-systemd.c +index 8ef1cd5..25d034a 100644 +--- a/src/nss-systemd/nss-systemd.c ++++ b/src/nss-systemd/nss-systemd.c +@@ -124,7 +124,7 @@ static int direct_lookup_uid(uid_t uid, char **ret) { + r = readlink_malloc(path, &s); + if (r < 0) + return r; +- if (!valid_user_group_name(s)) { /* extra safety check */ ++ if (!valid_user_group_name(s, VALID_USER_RELAX)) { /* extra safety check */ + free(s); + return -EINVAL; + } +@@ -154,7 +154,7 @@ enum nss_status _nss_systemd_getpwnam_r( + + /* If the username is not valid, then we don't know it. Ideally libc would filter these for us anyway. We don't + * generate EINVAL here, because it isn't really out business to complain about invalid user names. */ +- if (!valid_user_group_name(name)) ++ if (!valid_user_group_name(name, VALID_USER_RELAX)) + return NSS_STATUS_NOTFOUND; + + /* Synthesize entries for the root and nobody users, in case they are missing in /etc/passwd */ +@@ -357,7 +357,7 @@ enum nss_status _nss_systemd_getgrnam_r( + assert(name); + assert(gr); + +- if (!valid_user_group_name(name)) ++ if (!valid_user_group_name(name, VALID_USER_RELAX)) + return NSS_STATUS_NOTFOUND; + + /* Synthesize records for root and nobody, in case they are missing form /etc/group */ +diff --git a/src/systemd/sd-messages.h b/src/systemd/sd-messages.h +index e8d26ab71b..162b650e64 100644 +--- a/src/systemd/sd-messages.h ++++ b/src/systemd/sd-messages.h +@@ -158,6 +158,9 @@ _SD_BEGIN_DECLARATIONS; + #define SD_MESSAGE_DNSSEC_DOWNGRADE SD_ID128_MAKE(36,db,2d,fa,5a,90,45,e1,bd,4a,f5,f9,3e,1c,f0,57) + #define SD_MESSAGE_DNSSEC_DOWNGRADE_STR SD_ID128_MAKE_STR(36,db,2d,fa,5a,90,45,e1,bd,4a,f5,f9,3e,1c,f0,57) + ++#define SD_MESSAGE_UNSAFE_USER_NAME SD_ID128_MAKE(b6,1f,da,c6,12,e9,4b,91,82,28,5b,99,88,43,06,1f) ++#define SD_MESSAGE_UNSAFE_USER_NAME_STR SD_ID128_MAKE_STR(b6,1f,da,c6,12,e9,4b,91,82,28,5b,99,88,43,06,1f) ++ + _SD_END_DECLARATIONS; + + #endif +diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c +index 08a2df7..3e3da47 100644 +--- a/src/sysusers/sysusers.c ++++ b/src/sysusers/sysusers.c +@@ -1417,7 +1417,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) { + if (r < 0) + log_error_errno(r, "[%s:%u] Failed to replace specifiers: %s", fname, line, name); + +- if (!valid_user_group_name(resolved_name)) ++ if (!valid_user_group_name(resolved_name, 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "[%s:%u] '%s' is not a valid user or group name.", + fname, line, resolved_name); +@@ -1520,7 +1520,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) { + "[%s:%u] Lines of type 'm' require a group name in the third field.", + fname, line); + +- if (!valid_user_group_name(resolved_id)) ++ if (!valid_user_group_name(resolved_id, 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "[%s:%u] '%s' is not a valid user or group name.", + fname, line, resolved_id); +diff --git a/src/test/test-user-util.c b/src/test/test-user-util.c +index 11c01e5189..a0e1495186 100644 +--- a/src/test/test-user-util.c ++++ b/src/test/test-user-util.c +@@ -63,144 +63,163 @@ static void test_uid_ptr(void) { + assert_se(PTR_TO_UID(UID_TO_PTR(1000)) == 1000); + } + +-static void test_valid_user_group_name_compat(void) { ++static void test_valid_user_group_name_relaxed(void) { + log_info("/* %s */", __func__); + +- assert_se(!valid_user_group_name_compat(NULL)); +- assert_se(!valid_user_group_name_compat("")); +- assert_se(!valid_user_group_name_compat("1")); +- assert_se(!valid_user_group_name_compat("65535")); +- assert_se(!valid_user_group_name_compat("-1")); +- assert_se(!valid_user_group_name_compat("-kkk")); +- assert_se(!valid_user_group_name_compat("rööt")); +- assert_se(!valid_user_group_name_compat(".")); +- assert_se(!valid_user_group_name_compat(".eff")); +- assert_se(!valid_user_group_name_compat("foo\nbar")); +- assert_se(!valid_user_group_name_compat("0123456789012345678901234567890123456789")); +- assert_se(!valid_user_group_name_or_id_compat("aaa:bbb")); +- assert_se(!valid_user_group_name_compat(".")); +- assert_se(!valid_user_group_name_compat(".1")); +- assert_se(!valid_user_group_name_compat(".65535")); +- assert_se(!valid_user_group_name_compat(".-1")); +- assert_se(!valid_user_group_name_compat(".-kkk")); +- assert_se(!valid_user_group_name_compat(".rööt")); +- assert_se(!valid_user_group_name_or_id_compat(".aaa:bbb")); +- +- assert_se(valid_user_group_name_compat("root")); +- assert_se(valid_user_group_name_compat("lennart")); +- assert_se(valid_user_group_name_compat("LENNART")); +- assert_se(valid_user_group_name_compat("_kkk")); +- assert_se(valid_user_group_name_compat("kkk-")); +- assert_se(valid_user_group_name_compat("kk-k")); +- assert_se(valid_user_group_name_compat("eff.eff")); +- assert_se(valid_user_group_name_compat("eff.")); +- +- assert_se(valid_user_group_name_compat("some5")); +- assert_se(valid_user_group_name_compat("5some")); +- assert_se(valid_user_group_name_compat("INNER5NUMBER")); ++ assert_se(!valid_user_group_name(NULL, VALID_USER_RELAX)); ++ assert_se(!valid_user_group_name("", VALID_USER_RELAX)); ++ assert_se(!valid_user_group_name("1", VALID_USER_RELAX)); ++ assert_se(!valid_user_group_name("65535", VALID_USER_RELAX)); ++ assert_se(!valid_user_group_name("-1", VALID_USER_RELAX)); ++ assert_se(!valid_user_group_name("foo\nbar", VALID_USER_RELAX)); ++ assert_se(!valid_user_group_name("0123456789012345678901234567890123456789", VALID_USER_RELAX)); ++ assert_se(!valid_user_group_name("aaa:bbb", VALID_USER_RELAX|VALID_USER_ALLOW_NUMERIC)); ++ assert_se(!valid_user_group_name(".aaa:bbb", VALID_USER_RELAX|VALID_USER_ALLOW_NUMERIC)); ++ assert_se(!valid_user_group_name(".", VALID_USER_RELAX)); ++ assert_se(!valid_user_group_name("..", VALID_USER_RELAX)); ++ ++ assert_se(valid_user_group_name("root", VALID_USER_RELAX)); ++ assert_se(valid_user_group_name("lennart", VALID_USER_RELAX)); ++ assert_se(valid_user_group_name("LENNART", VALID_USER_RELAX)); ++ assert_se(valid_user_group_name("_kkk", VALID_USER_RELAX)); ++ assert_se(valid_user_group_name("kkk-", VALID_USER_RELAX)); ++ assert_se(valid_user_group_name("kk-k", VALID_USER_RELAX)); ++ assert_se(valid_user_group_name("eff.eff", VALID_USER_RELAX)); ++ assert_se(valid_user_group_name("eff.", VALID_USER_RELAX)); ++ assert_se(valid_user_group_name("-kkk", VALID_USER_RELAX)); ++ assert_se(valid_user_group_name("rööt", VALID_USER_RELAX)); ++ assert_se(valid_user_group_name(".eff", VALID_USER_RELAX)); ++ assert_se(valid_user_group_name(".1", VALID_USER_RELAX)); ++ assert_se(valid_user_group_name(".65535", VALID_USER_RELAX)); ++ assert_se(valid_user_group_name(".-1", VALID_USER_RELAX)); ++ assert_se(valid_user_group_name(".-kkk", VALID_USER_RELAX)); ++ assert_se(valid_user_group_name(".rööt", VALID_USER_RELAX)); ++ assert_se(valid_user_group_name("...", VALID_USER_RELAX)); ++ ++ assert_se(valid_user_group_name("some5", VALID_USER_RELAX)); ++ assert_se(valid_user_group_name("5some", VALID_USER_RELAX)); ++ assert_se(valid_user_group_name("INNER5NUMBER", VALID_USER_RELAX)); ++ ++ assert_se(valid_user_group_name("piff.paff@ad.domain.example", VALID_USER_RELAX)); ++ assert_se(valid_user_group_name("Dāvis", VALID_USER_RELAX)); + } + + static void test_valid_user_group_name(void) { + log_info("/* %s */", __func__); + +- 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")); +- 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(".")); +- 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_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("eff.eff")); +- assert_se(!valid_user_group_name("eff.")); +- +- assert_se(valid_user_group_name("some5")); +- assert_se(!valid_user_group_name("5some")); +- assert_se(valid_user_group_name("INNER5NUMBER")); ++ assert_se(!valid_user_group_name(NULL, 0)); ++ assert_se(!valid_user_group_name("", 0)); ++ assert_se(!valid_user_group_name("1", 0)); ++ assert_se(!valid_user_group_name("65535", 0)); ++ assert_se(!valid_user_group_name("-1", 0)); ++ assert_se(!valid_user_group_name("-kkk", 0)); ++ assert_se(!valid_user_group_name("rööt", 0)); ++ assert_se(!valid_user_group_name(".", 0)); ++ assert_se(!valid_user_group_name(".eff", 0)); ++ assert_se(!valid_user_group_name("foo\nbar", 0)); ++ assert_se(!valid_user_group_name("0123456789012345678901234567890123456789", 0)); ++ assert_se(!valid_user_group_name("aaa:bbb", VALID_USER_ALLOW_NUMERIC)); ++ assert_se(!valid_user_group_name(".", 0)); ++ assert_se(!valid_user_group_name("..", 0)); ++ assert_se(!valid_user_group_name("...", 0)); ++ assert_se(!valid_user_group_name(".1", 0)); ++ assert_se(!valid_user_group_name(".65535", 0)); ++ assert_se(!valid_user_group_name(".-1", 0)); ++ assert_se(!valid_user_group_name(".-kkk", 0)); ++ assert_se(!valid_user_group_name(".rööt", 0)); ++ assert_se(!valid_user_group_name(".aaa:bbb", VALID_USER_ALLOW_NUMERIC)); ++ ++ assert_se(valid_user_group_name("root", 0)); ++ assert_se(valid_user_group_name("lennart", 0)); ++ assert_se(valid_user_group_name("LENNART", 0)); ++ assert_se(valid_user_group_name("_kkk", 0)); ++ assert_se(valid_user_group_name("kkk-", 0)); ++ assert_se(valid_user_group_name("kk-k", 0)); ++ assert_se(!valid_user_group_name("eff.eff", 0)); ++ assert_se(!valid_user_group_name("eff.", 0)); ++ ++ assert_se(valid_user_group_name("some5", 0)); ++ assert_se(!valid_user_group_name("5some", 0)); ++ assert_se(valid_user_group_name("INNER5NUMBER", 0)); ++ ++ assert_se(!valid_user_group_name("piff.paff@ad.domain.example", 0)); ++ assert_se(!valid_user_group_name("Dāvis", 0)); + } + +-static void test_valid_user_group_name_or_id_compat(void) { ++static void test_valid_user_group_name_or_numeric_relaxed(void) { + log_info("/* %s */", __func__); + +- assert_se(!valid_user_group_name_or_id_compat(NULL)); +- assert_se(!valid_user_group_name_or_id_compat("")); +- assert_se(valid_user_group_name_or_id_compat("0")); +- assert_se(valid_user_group_name_or_id_compat("1")); +- assert_se(valid_user_group_name_or_id_compat("65534")); +- assert_se(!valid_user_group_name_or_id_compat("65535")); +- assert_se(valid_user_group_name_or_id_compat("65536")); +- assert_se(!valid_user_group_name_or_id_compat("-1")); +- assert_se(!valid_user_group_name_or_id_compat("-kkk")); +- assert_se(!valid_user_group_name_or_id_compat("rööt")); +- assert_se(!valid_user_group_name_or_id_compat(".")); +- assert_se(!valid_user_group_name_or_id_compat(".eff")); +- assert_se(valid_user_group_name_or_id_compat("eff.eff")); +- assert_se(valid_user_group_name_or_id_compat("eff.")); +- assert_se(!valid_user_group_name_or_id_compat("foo\nbar")); +- assert_se(!valid_user_group_name_or_id_compat("0123456789012345678901234567890123456789")); +- assert_se(!valid_user_group_name_or_id_compat("aaa:bbb")); +- +- assert_se(valid_user_group_name_or_id_compat("root")); +- assert_se(valid_user_group_name_or_id_compat("lennart")); +- assert_se(valid_user_group_name_or_id_compat("LENNART")); +- assert_se(valid_user_group_name_or_id_compat("_kkk")); +- assert_se(valid_user_group_name_or_id_compat("kkk-")); +- assert_se(valid_user_group_name_or_id_compat("kk-k")); +- +- assert_se(valid_user_group_name_or_id_compat("some5")); +- assert_se(valid_user_group_name_or_id_compat("5some")); +- assert_se(valid_user_group_name_or_id_compat("INNER5NUMBER")); ++ assert_se(!valid_user_group_name(NULL, VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); ++ assert_se(!valid_user_group_name("", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); ++ assert_se(valid_user_group_name("0", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); ++ assert_se(valid_user_group_name("1", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); ++ assert_se(valid_user_group_name("65534", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); ++ assert_se(!valid_user_group_name("65535", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); ++ assert_se(valid_user_group_name("65536", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); ++ assert_se(!valid_user_group_name("-1", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); ++ assert_se(!valid_user_group_name("foo\nbar", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); ++ assert_se(!valid_user_group_name("0123456789012345678901234567890123456789", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); ++ assert_se(!valid_user_group_name("aaa:bbb", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); ++ assert_se(!valid_user_group_name(".", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); ++ assert_se(!valid_user_group_name("..", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); ++ ++ assert_se(valid_user_group_name("root", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); ++ assert_se(valid_user_group_name("lennart", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); ++ assert_se(valid_user_group_name("LENNART", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); ++ assert_se(valid_user_group_name("_kkk", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); ++ assert_se(valid_user_group_name("kkk-", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); ++ assert_se(valid_user_group_name("kk-k", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); ++ assert_se(valid_user_group_name("-kkk", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); ++ assert_se(valid_user_group_name("rööt", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); ++ assert_se(valid_user_group_name(".eff", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); ++ assert_se(valid_user_group_name("eff.eff", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); ++ assert_se(valid_user_group_name("eff.", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); ++ assert_se(valid_user_group_name("...", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); ++ ++ assert_se(valid_user_group_name("some5", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); ++ assert_se(valid_user_group_name("5some", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); ++ assert_se(valid_user_group_name("INNER5NUMBER", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); ++ ++ assert_se(valid_user_group_name("piff.paff@ad.domain.example", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); ++ assert_se(valid_user_group_name("Dāvis", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); + } + +-static void test_valid_user_group_name_or_id(void) { ++static void test_valid_user_group_name_or_numeric(void) { + log_info("/* %s */", __func__); + +- 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")); +- assert_se(!valid_user_group_name_or_id("eff.eff")); +- assert_se(!valid_user_group_name_or_id("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")); ++ assert_se(!valid_user_group_name(NULL, VALID_USER_ALLOW_NUMERIC)); ++ assert_se(!valid_user_group_name("", VALID_USER_ALLOW_NUMERIC)); ++ assert_se(valid_user_group_name("0", VALID_USER_ALLOW_NUMERIC)); ++ assert_se(valid_user_group_name("1", VALID_USER_ALLOW_NUMERIC)); ++ assert_se(valid_user_group_name("65534", VALID_USER_ALLOW_NUMERIC)); ++ assert_se(!valid_user_group_name("65535", VALID_USER_ALLOW_NUMERIC)); ++ assert_se(valid_user_group_name("65536", VALID_USER_ALLOW_NUMERIC)); ++ assert_se(!valid_user_group_name("-1", VALID_USER_ALLOW_NUMERIC)); ++ assert_se(!valid_user_group_name("-kkk", VALID_USER_ALLOW_NUMERIC)); ++ assert_se(!valid_user_group_name("rööt", VALID_USER_ALLOW_NUMERIC)); ++ assert_se(!valid_user_group_name(".", VALID_USER_ALLOW_NUMERIC)); ++ assert_se(!valid_user_group_name("..", VALID_USER_ALLOW_NUMERIC)); ++ assert_se(!valid_user_group_name("...", VALID_USER_ALLOW_NUMERIC)); ++ assert_se(!valid_user_group_name(".eff", VALID_USER_ALLOW_NUMERIC)); ++ assert_se(!valid_user_group_name("eff.eff", VALID_USER_ALLOW_NUMERIC)); ++ assert_se(!valid_user_group_name("eff.", VALID_USER_ALLOW_NUMERIC)); ++ assert_se(!valid_user_group_name("foo\nbar", VALID_USER_ALLOW_NUMERIC)); ++ assert_se(!valid_user_group_name("0123456789012345678901234567890123456789", VALID_USER_ALLOW_NUMERIC)); ++ assert_se(!valid_user_group_name("aaa:bbb", VALID_USER_ALLOW_NUMERIC)); ++ ++ assert_se(valid_user_group_name("root", VALID_USER_ALLOW_NUMERIC)); ++ assert_se(valid_user_group_name("lennart", VALID_USER_ALLOW_NUMERIC)); ++ assert_se(valid_user_group_name("LENNART", VALID_USER_ALLOW_NUMERIC)); ++ assert_se(valid_user_group_name("_kkk", VALID_USER_ALLOW_NUMERIC)); ++ assert_se(valid_user_group_name("kkk-", VALID_USER_ALLOW_NUMERIC)); ++ assert_se(valid_user_group_name("kk-k", VALID_USER_ALLOW_NUMERIC)); ++ ++ assert_se(valid_user_group_name("some5", VALID_USER_ALLOW_NUMERIC)); ++ assert_se(!valid_user_group_name("5some", VALID_USER_ALLOW_NUMERIC)); ++ assert_se(valid_user_group_name("INNER5NUMBER", VALID_USER_ALLOW_NUMERIC)); ++ ++ assert_se(!valid_user_group_name("piff.paff@ad.domain.example", VALID_USER_ALLOW_NUMERIC)); ++ assert_se(!valid_user_group_name("Dāvis", VALID_USER_ALLOW_NUMERIC)); + } + + static void test_valid_gecos(void) { +@@ -355,10 +374,10 @@ int main(int argc, char *argv[]) { + test_parse_uid(); + test_uid_ptr(); + +- test_valid_user_group_name_compat(); ++ test_valid_user_group_name_relaxed(); + test_valid_user_group_name(); +- test_valid_user_group_name_or_id_compat(); +- test_valid_user_group_name_or_id(); ++ test_valid_user_group_name_or_numeric_relaxed(); ++ test_valid_user_group_name_or_numeric(); + test_valid_gecos(); + test_valid_home(); + +-- +2.23.0 + diff --git a/backport-0004-CVE-2020-13776-docs-add-a-longer-document-explaining-our-rules-on-u.patch b/backport-0004-CVE-2020-13776-docs-add-a-longer-document-explaining-our-rules-on-u.patch new file mode 100644 index 0000000..340cac1 --- /dev/null +++ b/backport-0004-CVE-2020-13776-docs-add-a-longer-document-explaining-our-rules-on-u.patch @@ -0,0 +1,191 @@ +From cafed7b32cdac13024c4093b7942a49ee8602dcf Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Tue, 7 Apr 2020 10:38:39 +0200 +Subject: [PATCH] docs: add a longer document explaining our rules on + user/group names + +Reference: https://github.com/systemd/systemd/commit/cafed7b32cdac13024c4093b7942a49ee8602dcf +Conflict: NA +--- + docs/USER_NAMES.md | 169 +++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 169 insertions(+) + create mode 100644 docs/USER_NAMES.md + +diff --git a/docs/USER_NAMES.md b/docs/USER_NAMES.md +new file mode 100644 +index 0000000000..ccbb0a360d +--- /dev/null ++++ b/docs/USER_NAMES.md +@@ -0,0 +1,169 @@ ++-- ++title: User/Group Name Syntax ++category: Concepts ++layout: default ++--- ++ ++# User/Group Name Syntax ++ ++The precise set of allowed user and group names on Linux systems is weakly ++defined. Depending on the distribution a different set of requirements and ++restrictions on the syntax of user/group names are enforced — on some ++distributions the accepted syntax is even configurable by the administrator. In ++the interest of interoperability systemd enforces different rules when ++processing users/group defined by other subsystems and when defining users/groups ++itself, following the principle of "Be conservative in what you send, be ++liberal in what you accept". Also in the interest of interoperability systemd ++will enforce the same rules everywhere and not make them configurable or ++distribution dependent. The precise rules are described below. ++ ++Generally, the same rules apply for user as for group names. ++ ++## Other Systems ++ ++* On POSIX the set of [valid user ++ names](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_437) ++ is defined as [lower and upper case ASCII letters, digits, period, ++ underscore, and ++ hyphen](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_282), ++ with the restriction that hyphen is now allowed as first character of the ++ user name. Interestingly no size limit is declared, i.e. in neither ++ direction, meaning that strictly speaking according to POSIX both the empty ++ string is a valid user name as well as a string of gigabytes in length. ++ ++* Debian/Ubuntu based systems enforce the regular expression ++ `^[a-z][-a-z0-9]*$`, i.e. only lower case ASCII letters, digits and ++ hyphens. As first character only lowercase ASCII letters are allowed. This ++ regular expression is configurable by the administrator at runtime ++ though. This rule enforces a minimum length of one character but no maximum ++ length. ++ ++* Upstream shadow-utils enforces the regular expression ++ `^[a-z_][a-z0-9_-]*[$]$`, i.e. is similar to the Debian/Ubuntu rule, but ++ allows underscores and hyphens, but the latter not as first character. Also, ++ an optional trailing dollar character is permitted. ++ ++* Fedora/Red Hat based systems enforce the regular expression of ++ `^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,30}[a-zA-Z0-9_.$-]?$`, i.e. a size limit of ++ 32 characters, with upper and lower case letters, digits, underscores, ++ hyphens and periods. No hyphen as first character though, and the last ++ character may be a dollar character. On top of that, `.` and `..` are not ++ allowed as user/group names. ++ ++* sssd is known to generate user names with embedded `@` and white-space ++ characters, as well as non-ASCII (i.e. UTF-8) user/group names. ++ ++* winbindd is known to generate user/group names with embedded `\` and ++ white-space characters, as well as non-ASCII (i.e. UTF-8) user/group names. ++ ++Other operating systems enforce different rules; in this documentation we'll ++focus on Linux systems only however, hence those are out of scope. That said, ++software like Samba is frequently deployed on Linux for providing compatibility ++with Windows systems; on such systems it might be wise to stick to user/group ++names also valid according to Windows rules. ++ ++## Rules systemd enforces ++ ++Distilled from the above, below are the rules systemd enforces on user/group ++names. An additional, common rule between both modes listed below is that empty ++strings are not valid user/group names. ++ ++Philosophically, the strict mode described below enforces a white-list of what's ++allowed and prohibits everything else, while the relaxed mode described below ++implements a blacklist of what's not allowed and permits everything else. ++ ++### Strict mode ++ ++Strict user/group name syntax is enforced whenever a systemd component is used ++to register a user or group in the system, for example a system user/group ++using ++[`systemd-sysusers.service`](https://www.freedesktop.org/software/systemd/man/systemd-sysusers.html) ++or a regular user with ++[`systemd-homed.service`](https://www.freedesktop.org/software/systemd/man/systemd-homed.html). ++ ++In strict mode, only uppercase and lowercase characters are allowed, as well as ++digits, underscores and hyphens. The first character may not be a digit or ++hyphen. A size limit is enforced: the minimum of `sysconf(_SC_LOGIN_NAME_MAX)` ++(typically 256 on Linux; rationale: this is how POSIX suggests to detect the ++limit), `UT_NAMESIZE-1` (typically 31 on Linux; rationale: names longer than ++this cannot correctly appear in `utmp`/`wtmp` and create ambiguity with login ++accounting) and `FILENAME_MAX` (4096 on Linux; rationale: user names typically ++appear in directory names, i.e. the home directory), thus MIN(256, 31, 4096) = ++31. ++ ++Note that these rules are both more strict and more relaxed than all of the ++rules enforced by other systems listed above. A user/group name conforming to ++systemd's strict rules will not necessarily pass a test by the rules enforced ++by these other subsystems. ++ ++Written as regular expression the above is: `^[a-zA-Z_][a-zA-Z0-9_-]{0,30}$` ++ ++### Relaxed mode ++ ++Relaxed user/group name syntax is enforced whenever a systemd component accepts ++and makes use of user/group names registered by other (non-systemd) ++components of the system, for example in ++[`systemd-logind.service`](https://www.freedesktop.org/software/systemd/man/systemd-logind.html). ++ ++Relaxed syntax is also enforced by the `User=` setting in service unit files, ++i.e. for system services used for running services. Since these users may be ++registered by a variety of tools relaxed mode is used, but since the primary ++purpose of these users is to run a system service and thus a job for systemd a ++warning is shown if the specified user name does not qualify by the strict ++rules above. ++ ++* No embedded NUL bytes (rationale: handling in C must be possible and ++ straight-forward) ++ ++* No names consisting fully of digits (rationale: avoid confusion with numeric ++ UID/GID specifications) ++ ++* Similar, no names consisting of an initial hyphen and otherwise entirely made ++ up of digits (rationale: avoid confusion with negative, numeric UID/GID ++ specifications, e.g. `-1`) ++ ++* No strings that do not qualify as valid UTF-8 (rationale: we want to be able ++ to embed these strings in JSON, with permits only valid UTF-8 in its strings; ++ user names using other character sets, such as JIS/Shift-JIS will cause ++ validation errors) ++ ++* No control characters (i.e. characters in ASCII range 1…31; rationale: they ++ tend to have special meaning when output on a terminal in other contexts, ++ moreover the newline character — as a specific control character — is used as ++ record separator in `/etc/passwd`, and hence it's crucial to avoid ++ ambiguities here) ++ ++* No colon characters (rationale: it is used as field separator in `/etc/passwd`) ++ ++* The two strings `.` and `..` are not permitted, as these have special meaning ++ in file system paths, and user names are frequently included in file system ++ paths, in particular for the purpose of home directories. ++ ++* Similar, no slashes, as these have special meaning in file system paths ++ ++* No leading or trailing white-space is permitted; and hence no user/group names ++ consisting of white-space only either (rationale: this typically indicates ++ parsing errors, and creates confusion since not visible on screen) ++ ++Note that these relaxed rules are implied by the strict rules above, i.e. all ++user/group names accepted by the strict rules are also accepted by the relaxed ++rules, but not vice versa. ++ ++Note that this relaxed mode does not refuse a couple of very questionable ++syntaxes. For example it permits a leading or embedded period. A leading period ++is problematic because the matching home directory would typically be hidden ++from the user's/administrator's view. An embedded period is problematic since ++it creates ambiguity in traditional `chown` syntax (which is still accepted ++today) that uses it to separate user and group names in the command's ++parameter: without consulting the user/group databases it is not possible to ++determine if a `chown` invocation would change just the owning user or both the ++owning user and group. It also allows embeddeding `@` (which is confusing to ++MTAs). ++ ++## Common Core ++ ++Combining all rules listed above, user/group names that shall be considered ++valid in all systemd contexts and on all Linux systems should match the ++following regular expression (at least according to our understanding): ++ ++`^[a-z][a-z0-9-]{0,30}$` +-- +2.23.0 + diff --git a/backport-0005-CVE-2020-13776-docs-hook-up-the-new-USER_NAMES-document-everywhere.patch b/backport-0005-CVE-2020-13776-docs-hook-up-the-new-USER_NAMES-document-everywhere.patch new file mode 100644 index 0000000..bc14f5f --- /dev/null +++ b/backport-0005-CVE-2020-13776-docs-hook-up-the-new-USER_NAMES-document-everywhere.patch @@ -0,0 +1,58 @@ +From 887a8fa341d9b24a7c9cd3f1fce328f8e43a1b4f Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Tue, 7 Apr 2020 11:04:59 +0200 +Subject: [PATCH] docs: hook up the new USER_NAMES document everywhere + +(Also correct the set of names we accept in User=, which was forgotten +to be updated in ae480f0b09aec815b64579bb1828ea935d8ee236. + +Reference: https://github.com/systemd/systemd/commit/887a8fa341d9b24a7c9cd3f1fce328f8e43a1b4f +Conflict: Remove unneeded file changes. +--- + man/systemd.exec.xml | 15 +++++++++------ + man/sysusers.d.xml | 3 +++ + 2 files changed, 12 insertions(+), 6 deletions(-) + +diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml +index a52d8fa..bf9b030 100644 +--- a/man/systemd.exec.xml ++++ b/man/systemd.exec.xml +@@ -186,12 +186,15 @@ + is set, the default group of the user is used. This setting does not affect commands whose command line is + prefixed with +. + +- Note that restrictions on the user/group name syntax are enforced: the specified name must consist only +- of the characters a-z, A-Z, 0-9, _ and -, except for the first character +- which must be one of a-z, A-Z or _ (i.e. numbers and - are not permitted +- as first character). The user/group name must have at least one character, and at most 31. These restrictions +- are enforced in order to avoid ambiguities and to ensure user/group names and unit files remain portable among +- Linux systems. ++ Note that this enforces only weak restrictions on the user/group name syntax, but will generate ++ warnings in many cases where user/group names do not adhere to the following rules: the specified ++ name should consist only of the characters a-z, A-Z, 0-9, _ and ++ -, except for the first character which must be one of a-z, A-Z and ++ _ (i.e. digits and - are not permitted as first character). The ++ user/group name must have at least one character, and at most 31. These restrictions are made in ++ order to avoid ambiguities and to ensure user/group names and unit files remain portable among Linux ++ systems. For further details on the names accepted and the names warned about see User/Group Name Syntax. + + When used in conjunction with DynamicUser= the user/group name specified is + dynamically allocated at the time the service is started, and released at the time the service is stopped — +diff --git a/man/sysusers.d.xml b/man/sysusers.d.xml +index e47d36c..840da44 100644 +--- a/man/sysusers.d.xml ++++ b/man/sysusers.d.xml +@@ -143,6 +143,9 @@ u root 0 "Superuser" /root /bin/zsh_ (i.e. numbers and - are not permitted as first character). The + user/group name must have at least one character, and at most 31. + ++ For further details about the syntax of user/group names, see User/Group Name Syntax. ++ + It is strongly recommended to pick user and group names that are unlikely to clash with normal users + created by the administrator. A good scheme to guarantee this is by prefixing all system and group names with the + underscore, and avoiding too generic names. +-- +2.23.0 + diff --git a/backport-0006-CVE-2020-13776-catalog-add-entry-for-SD_MESSAGE_UNSAFE_USER_NAME.patch b/backport-0006-CVE-2020-13776-catalog-add-entry-for-SD_MESSAGE_UNSAFE_USER_NAME.patch new file mode 100644 index 0000000..af047c9 --- /dev/null +++ b/backport-0006-CVE-2020-13776-catalog-add-entry-for-SD_MESSAGE_UNSAFE_USER_NAME.patch @@ -0,0 +1,45 @@ +From ad313ec33bb367624c25c9264994d6e43b8a7e2e Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Tue, 7 Apr 2020 11:15:49 +0200 +Subject: [PATCH] catalog: add entry for SD_MESSAGE_UNSAFE_USER_NAME + +Reference: https://github.com/systemd/systemd/commit/ad313ec33bb367624c25c9264994d6e43b8a7e2e +Conflict: NA +--- + catalog/systemd.catalog.in | 23 +++++++++++++++++++++++ + 1 file changed, 23 insertions(+) + +diff --git a/catalog/systemd.catalog.in b/catalog/systemd.catalog.in +index db275d7c50..3342d59422 100644 +--- a/catalog/systemd.catalog.in ++++ b/catalog/systemd.catalog.in +@@ -412,3 +412,26 @@ as the best process to terminate and has been forcibly terminated by the + kernel. + + Note that the memory pressure might or might not have been caused by @UNIT@. ++ ++-- b61fdac612e94b9182285b998843061f ++Subject: Accepting user/group name @USER_GROUP_NAME@, which does not match strict user/group name rules. ++Defined-By: systemd ++Support: %SUPPORT_URL% ++ ++The user/group name @USER_GROUP_NAME@ has been specified, which is accepted ++according the relaxed user/group name rules, but does not qualify under the ++strict rules. ++ ++The strict user/group name rules written as regular expression are: ++ ++^[a-zA-Z_][a-zA-Z0-9_-]{0,30}$ ++ ++The relaxed user/group name rules accept all names, except for the empty ++string; names containing NUL bytes, control characters, colon or slash ++characters; names not valid UTF-8; names with leading or trailing whitespace; ++the strings "." or ".."; fully numeric strings, or strings beginning in a ++hyphen and otherwise fully numeric. ++ ++For further details on strict and relaxed user/group name rules, see: ++ ++https://systemd.io/USER_NAMES +-- +2.23.0 + diff --git a/backport-0007-CVE-2020-13776-basic-user-util-always-use-base-10-for-user-group-nu.patch b/backport-0007-CVE-2020-13776-basic-user-util-always-use-base-10-for-user-group-nu.patch new file mode 100644 index 0000000..0e2bce2 --- /dev/null +++ b/backport-0007-CVE-2020-13776-basic-user-util-always-use-base-10-for-user-group-nu.patch @@ -0,0 +1,73 @@ +From 156a5fd297b61bce31630d7a52c15614bf784843 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Sun, 31 May 2020 18:21:09 +0200 +Subject: [PATCH] basic/user-util: always use base 10 for user/group numbers + +We would parse numbers with base prefixes as user identifiers. For example, +"0x2b3bfa0" would be interpreted as UID==45334432 and "01750" would be +interpreted as UID==1000. This parsing was used also in cases where either a +user/group name or number may be specified. This means that names like +0x2b3bfa0 would be ambiguous: they are a valid user name according to our +documented relaxed rules, but they would also be parsed as numeric uids. + +This behaviour is definitely not expected by users, since tools generally only +accept decimal numbers (e.g. id, getent passwd), while other tools only accept +user names and thus will interpret such strings as user names without even +attempting to convert them to numbers (su, ssh). So let's follow suit and only +accept numbers in decimal notation. Effectively this means that we will reject +such strings as a username/uid/groupname/gid where strict mode is used, and try +to look up a user/group with such a name in relaxed mode. + +Since the function changed is fairly low-level and fairly widely used, this +affects multiple tools: loginctl show-user/enable-linger/disable-linger foo', +the third argument in sysusers.d, fourth and fifth arguments in tmpfiles.d, +etc. + +Fixes #15985. +Reference: https://github.com/systemd/systemd/commit/156a5fd297b61bce31630d7a52c15614bf784843 +Conflict: NA +--- + src/basic/user-util.c | 2 +- + src/test/test-user-util.c | 10 ++++++++++ + 2 files changed, 11 insertions(+), 1 deletion(-) + +diff --git a/src/basic/user-util.c b/src/basic/user-util.c +index 2e3580017d..2db8ef6abf 100644 +--- a/src/basic/user-util.c ++++ b/src/basic/user-util.c +@@ -49,7 +49,7 @@ int parse_uid(const char *s, uid_t *ret) { + assert(s); + + assert_cc(sizeof(uid_t) == sizeof(uint32_t)); +- r = safe_atou32(s, &uid); ++ r = safe_atou32_full(s, 10, &uid); + if (r < 0) + return r; + +diff --git a/src/test/test-user-util.c b/src/test/test-user-util.c +index a0e1495186..3165232fef 100644 +--- a/src/test/test-user-util.c ++++ b/src/test/test-user-util.c +@@ -48,9 +48,19 @@ static void test_parse_uid(void) { + + r = parse_uid("65535", &uid); + assert_se(r == -ENXIO); ++ assert_se(uid == 100); ++ ++ r = parse_uid("0x1234", &uid); ++ assert_se(r == -EINVAL); ++ assert_se(uid == 100); ++ ++ r = parse_uid("01234", &uid); ++ assert_se(r == 0); ++ assert_se(uid == 1234); + + r = parse_uid("asdsdas", &uid); + assert_se(r == -EINVAL); ++ assert_se(uid == 1234); + } + + static void test_uid_ptr(void) { +-- +2.23.0 + diff --git a/backport-0008-CVE-2020-13776-parse-util-sometimes-it-is-useful-to-check-if-a-stri.patch b/backport-0008-CVE-2020-13776-parse-util-sometimes-it-is-useful-to-check-if-a-stri.patch new file mode 100644 index 0000000..cceb3cf --- /dev/null +++ b/backport-0008-CVE-2020-13776-parse-util-sometimes-it-is-useful-to-check-if-a-stri.patch @@ -0,0 +1,151 @@ +From 22810041c2200fe72b0e0c985d0e404f8b80f9e2 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Thu, 14 Nov 2019 14:49:40 +0100 +Subject: [PATCH] parse-util: sometimes it is useful to check if a string is a + valid integer, but not actually parse it + +Reference: https://github.com/systemd/systemd/commit/22810041c2200fe72b0e0c985d0e404f8b80f9e2 +Conflict: NA +--- + src/basic/parse-util.c | 34 ++++++++++++++++++++-------------- + 1 file changed, 20 insertions(+), 14 deletions(-) + +diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c +index aec6099c9c..b81db04989 100644 +--- a/src/basic/parse-util.c ++++ b/src/basic/parse-util.c +@@ -365,7 +365,6 @@ int safe_atou_full(const char *s, unsigned base, unsigned *ret_u) { + unsigned long l; + + assert(s); +- assert(ret_u); + assert(base <= 16); + + /* strtoul() is happy to parse negative values, and silently +@@ -389,7 +388,9 @@ int safe_atou_full(const char *s, unsigned base, unsigned *ret_u) { + if ((unsigned long) (unsigned) l != l) + return -ERANGE; + +- *ret_u = (unsigned) l; ++ if (ret_u) ++ *ret_u = (unsigned) l; ++ + return 0; + } + +@@ -398,7 +399,6 @@ int safe_atoi(const char *s, int *ret_i) { + long l; + + assert(s); +- assert(ret_i); + + errno = 0; + l = strtol(s, &x, 0); +@@ -409,7 +409,9 @@ int safe_atoi(const char *s, int *ret_i) { + if ((long) (int) l != l) + return -ERANGE; + +- *ret_i = (int) l; ++ if (ret_i) ++ *ret_i = (int) l; ++ + return 0; + } + +@@ -418,7 +420,6 @@ int safe_atollu(const char *s, long long unsigned *ret_llu) { + unsigned long long l; + + assert(s); +- assert(ret_llu); + + s += strspn(s, WHITESPACE); + +@@ -431,7 +432,9 @@ int safe_atollu(const char *s, long long unsigned *ret_llu) { + if (*s == '-') + return -ERANGE; + +- *ret_llu = l; ++ if (ret_llu) ++ *ret_llu = l; ++ + return 0; + } + +@@ -440,7 +443,6 @@ int safe_atolli(const char *s, long long int *ret_lli) { + long long l; + + assert(s); +- assert(ret_lli); + + errno = 0; + l = strtoll(s, &x, 0); +@@ -449,7 +451,9 @@ int safe_atolli(const char *s, long long int *ret_lli) { + if (!x || x == s || *x != 0) + return -EINVAL; + +- *ret_lli = l; ++ if (ret_lli) ++ *ret_lli = l; ++ + return 0; + } + +@@ -458,7 +462,6 @@ int safe_atou8(const char *s, uint8_t *ret) { + unsigned long l; + + assert(s); +- assert(ret); + + s += strspn(s, WHITESPACE); + +@@ -473,7 +476,8 @@ int safe_atou8(const char *s, uint8_t *ret) { + if ((unsigned long) (uint8_t) l != l) + return -ERANGE; + +- *ret = (uint8_t) l; ++ if (ret) ++ *ret = (uint8_t) l; + return 0; + } + +@@ -507,7 +511,6 @@ int safe_atoi16(const char *s, int16_t *ret) { + long l; + + assert(s); +- assert(ret); + + errno = 0; + l = strtol(s, &x, 0); +@@ -518,7 +521,9 @@ int safe_atoi16(const char *s, int16_t *ret) { + if ((long) (int16_t) l != l) + return -ERANGE; + +- *ret = (int16_t) l; ++ if (ret) ++ *ret = (int16_t) l; ++ + return 0; + } + +@@ -528,7 +533,6 @@ int safe_atod(const char *s, double *ret_d) { + double d = 0; + + assert(s); +- assert(ret_d); + + loc = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0); + if (loc == (locale_t) 0) +@@ -541,7 +545,9 @@ int safe_atod(const char *s, double *ret_d) { + if (!x || x == s || *x != 0) + return -EINVAL; + +- *ret_d = (double) d; ++ if (ret_d) ++ *ret_d = (double) d; ++ + return 0; + } + +-- +2.23.0 + diff --git a/backport-0009-CVE-2020-13776-basic-parse-util-add-safe_atoux64.patch b/backport-0009-CVE-2020-13776-basic-parse-util-add-safe_atoux64.patch new file mode 100644 index 0000000..5dc0591 --- /dev/null +++ b/backport-0009-CVE-2020-13776-basic-parse-util-add-safe_atoux64.patch @@ -0,0 +1,132 @@ +From ce51632a357d347737bf40d3817df331cd8874cb Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Thu, 9 Apr 2020 11:18:26 +0200 +Subject: [PATCH] basic/parse-util: add safe_atoux64() + +Reference: https://github.com/systemd/systemd/commit/ce51632a357d347737bf40d3817df331cd8874cb +Conflict: NA +--- + src/basic/parse-util.c | 4 ++-- + src/basic/parse-util.h | 12 +++++++++++- + src/test/test-parse-util.c | 39 ++++++++++++++++++++++++++++++++++++++ + 3 files changed, 52 insertions(+), 3 deletions(-) + +diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c +index e0094b0f37..8de5cd5c56 100644 +--- a/src/basic/parse-util.c ++++ b/src/basic/parse-util.c +@@ -395,7 +395,7 @@ int safe_atoi(const char *s, int *ret_i) { + return 0; + } + +-int safe_atollu(const char *s, long long unsigned *ret_llu) { ++int safe_atollu_full(const char *s, unsigned base, long long unsigned *ret_llu) { + char *x = NULL; + unsigned long long l; + +@@ -404,7 +404,7 @@ int safe_atollu(const char *s, long long unsigned *ret_llu) { + s += strspn(s, WHITESPACE); + + errno = 0; +- l = strtoull(s, &x, 0); ++ l = strtoull(s, &x, base); + if (errno > 0) + return -errno; + if (!x || x == s || *x != 0) +diff --git a/src/basic/parse-util.h b/src/basic/parse-util.h +index 36d76ba576..970bdefbf0 100644 +--- a/src/basic/parse-util.h ++++ b/src/basic/parse-util.h +@@ -28,7 +28,6 @@ static inline int safe_atou(const char *s, unsigned *ret_u) { + } + + int safe_atoi(const char *s, int *ret_i); +-int safe_atollu(const char *s, unsigned long long *ret_u); + int safe_atolli(const char *s, long long int *ret_i); + + int safe_atou8(const char *s, uint8_t *ret); +@@ -59,6 +58,12 @@ static inline int safe_atoi32(const char *s, int32_t *ret_i) { + return safe_atoi(s, (int*) ret_i); + } + ++int safe_atollu_full(const char *s, unsigned base, long long unsigned *ret_llu); ++ ++static inline int safe_atollu(const char *s, long long unsigned *ret_llu) { ++ return safe_atollu_full(s, 0, ret_llu); ++} ++ + static inline int safe_atou64(const char *s, uint64_t *ret_u) { + assert_cc(sizeof(uint64_t) == sizeof(unsigned long long)); + return safe_atollu(s, (unsigned long long*) ret_u); +@@ -69,6 +74,11 @@ static inline int safe_atoi64(const char *s, int64_t *ret_i) { + return safe_atolli(s, (long long int*) ret_i); + } + ++static inline int safe_atoux64(const char *s, uint64_t *ret) { ++ assert_cc(sizeof(int64_t) == sizeof(long long unsigned)); ++ return safe_atollu_full(s, 16, (long long unsigned*) ret); ++} ++ + #if LONG_MAX == INT_MAX + static inline int safe_atolu(const char *s, unsigned long *ret_u) { + assert_cc(sizeof(unsigned long) == sizeof(unsigned)); +diff --git a/src/test/test-parse-util.c b/src/test/test-parse-util.c +index d732f402f0..1627bc747d 100644 +--- a/src/test/test-parse-util.c ++++ b/src/test/test-parse-util.c +@@ -561,6 +561,44 @@ static void test_safe_atoi64(void) { + assert_se(r == -EINVAL); + } + ++static void test_safe_atoux64(void) { ++ int r; ++ uint64_t l; ++ ++ r = safe_atoux64("12345", &l); ++ assert_se(r == 0); ++ assert_se(l == 0x12345); ++ ++ r = safe_atoux64(" 12345", &l); ++ assert_se(r == 0); ++ assert_se(l == 0x12345); ++ ++ r = safe_atoux64("0x12345", &l); ++ assert_se(r == 0); ++ assert_se(l == 0x12345); ++ ++ r = safe_atoux64("18446744073709551617", &l); ++ assert_se(r == -ERANGE); ++ ++ r = safe_atoux64("-1", &l); ++ assert_se(r == -ERANGE); ++ ++ r = safe_atoux64(" -1", &l); ++ assert_se(r == -ERANGE); ++ ++ r = safe_atoux64("junk", &l); ++ assert_se(r == -EINVAL); ++ ++ r = safe_atoux64("123x", &l); ++ assert_se(r == -EINVAL); ++ ++ r = safe_atoux64("12.3", &l); ++ assert_se(r == -EINVAL); ++ ++ r = safe_atoux64("", &l); ++ assert_se(r == -EINVAL); ++} ++ + static void test_safe_atod(void) { + int r; + double d; +@@ -838,6 +876,7 @@ int main(int argc, char *argv[]) { + test_safe_atoux16(); + test_safe_atou64(); + test_safe_atoi64(); ++ test_safe_atoux64(); + test_safe_atod(); + test_parse_percent(); + test_parse_percent_unbounded(); +-- +2.23.0 + diff --git a/backport-0010-CVE-2020-13776-parse-util-allow-tweaking-how-to-parse-integers.patch b/backport-0010-CVE-2020-13776-parse-util-allow-tweaking-how-to-parse-integers.patch new file mode 100644 index 0000000..ae3b68a --- /dev/null +++ b/backport-0010-CVE-2020-13776-parse-util-allow-tweaking-how-to-parse-integers.patch @@ -0,0 +1,155 @@ +From 707e93aff8f358f8a62117e54b857530d6594e4b Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Mon, 1 Jun 2020 17:06:19 +0200 +Subject: [PATCH] parse-util: allow tweaking how to parse integers + +This allows disabling a few alternative ways to decode integers +formatted as strings, for safety reasons. + +See: #15991 +Reference: https://github.com/systemd/systemd/commit/707e93aff8f358f8a62117e54b857530d6594e4b +Conflict: Add inline function safe_atou32_full. +--- + src/basic/parse-util.c | 65 +++++++++++++++++++++++++++++++++--------- + src/basic/parse-util.h | 13 ++++++++- + 2 files changed, 64 insertions(+), 14 deletions(-) + +diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c +index 59f8a31cec..15818958e4 100644 +--- a/src/basic/parse-util.c ++++ b/src/basic/parse-util.c +@@ -359,20 +359,35 @@ int safe_atou_full(const char *s, unsigned base, unsigned *ret_u) { + unsigned long l; + + assert(s); +- assert(base <= 16); ++ assert(SAFE_ATO_MASK_FLAGS(base) <= 16); + +- /* strtoul() is happy to parse negative values, and silently +- * converts them to unsigned values without generating an +- * error. We want a clean error, hence let's look for the "-" +- * prefix on our own, and generate an error. But let's do so +- * only after strtoul() validated that the string is clean +- * otherwise, so that we return EINVAL preferably over +- * ERANGE. */ ++ /* strtoul() is happy to parse negative values, and silently converts them to unsigned values without ++ * generating an error. We want a clean error, hence let's look for the "-" prefix on our own, and ++ * generate an error. But let's do so only after strtoul() validated that the string is clean ++ * otherwise, so that we return EINVAL preferably over ERANGE. */ ++ ++ if (FLAGS_SET(base, SAFE_ATO_REFUSE_LEADING_WHITESPACE) && ++ strchr(WHITESPACE, s[0])) ++ return -EINVAL; + + s += strspn(s, WHITESPACE); + ++ if (FLAGS_SET(base, SAFE_ATO_REFUSE_PLUS_MINUS) && ++ IN_SET(s[0], '+', '-')) ++ return -EINVAL; /* Note that we check the "-" prefix again a second time below, but return a ++ * different error. I.e. if the SAFE_ATO_REFUSE_PLUS_MINUS flag is set we ++ * blanket refuse +/- prefixed integers, while if it is missing we'll just ++ * return ERANGE, because the string actually parses correctly, but doesn't ++ * fit in the return type. */ ++ ++ if (FLAGS_SET(base, SAFE_ATO_REFUSE_LEADING_ZERO) && ++ s[0] == '0' && !streq(s, "0")) ++ return -EINVAL; /* This is particularly useful to avoid ambiguities between C's octal ++ * notation and assumed-to-be-decimal integers with a leading zero. */ ++ + errno = 0; +- l = strtoul(s, &x, base); ++ l = strtoul(s, &x, SAFE_ATO_MASK_FLAGS(base) /* Let's mask off the flags bits so that only the actual ++ * base is left */); + if (errno > 0) + return -errno; + if (!x || x == s || *x != 0) +@@ -414,11 +429,24 @@ int safe_atollu_full(const char *s, unsigned base, long long unsigned *ret_llu) + unsigned long long l; + + assert(s); ++ assert(SAFE_ATO_MASK_FLAGS(base) <= 16); ++ ++ if (FLAGS_SET(base, SAFE_ATO_REFUSE_LEADING_WHITESPACE) && ++ strchr(WHITESPACE, s[0])) ++ return -EINVAL; + + s += strspn(s, WHITESPACE); + ++ if (FLAGS_SET(base, SAFE_ATO_REFUSE_PLUS_MINUS) && ++ IN_SET(s[0], '+', '-')) ++ return -EINVAL; ++ ++ if (FLAGS_SET(base, SAFE_ATO_REFUSE_LEADING_ZERO) && ++ s[0] == '0' && s[1] != 0) ++ return -EINVAL; ++ + errno = 0; +- l = strtoull(s, &x, base); ++ l = strtoull(s, &x, SAFE_ATO_MASK_FLAGS(base)); + if (errno > 0) + return -errno; + if (!x || x == s || *x != 0) +@@ -480,13 +508,24 @@ int safe_atou16_full(const char *s, unsigned base, uint16_t *ret) { + unsigned long l; + + assert(s); +- assert(ret); +- assert(base <= 16); ++ assert(SAFE_ATO_MASK_FLAGS(base) <= 16); ++ ++ if (FLAGS_SET(base, SAFE_ATO_REFUSE_LEADING_WHITESPACE) && ++ strchr(WHITESPACE, s[0])) ++ return -EINVAL; + + s += strspn(s, WHITESPACE); + ++ if (FLAGS_SET(base, SAFE_ATO_REFUSE_PLUS_MINUS) && ++ IN_SET(s[0], '+', '-')) ++ return -EINVAL; ++ ++ if (FLAGS_SET(base, SAFE_ATO_REFUSE_LEADING_ZERO) && ++ s[0] == '0' && s[1] != 0) ++ return -EINVAL; ++ + errno = 0; +- l = strtoul(s, &x, base); ++ l = strtoul(s, &x, SAFE_ATO_MASK_FLAGS(base)); + if (errno > 0) + return -errno; + if (!x || x == s || *x != 0) +diff --git a/src/basic/parse-util.h b/src/basic/parse-util.h +index 84a2c8a..ae7fca7 100644 +--- a/src/basic/parse-util.h ++++ b/src/basic/parse-util.h +@@ -22,6 +22,12 @@ int parse_range(const char *t, unsigned *lower, unsigned *upper); + int parse_errno(const char *t); + int parse_syscall_and_errno(const char *in, char **name, int *error); + ++#define SAFE_ATO_REFUSE_PLUS_MINUS (1U << 30) ++#define SAFE_ATO_REFUSE_LEADING_ZERO (1U << 29) ++#define SAFE_ATO_REFUSE_LEADING_WHITESPACE (1U << 28) ++#define SAFE_ATO_ALL_FLAGS (SAFE_ATO_REFUSE_PLUS_MINUS|SAFE_ATO_REFUSE_LEADING_ZERO|SAFE_ATO_REFUSE_LEADING_WHITESPACE) ++#define SAFE_ATO_MASK_FLAGS(base) ((base) & ~SAFE_ATO_ALL_FLAGS) ++ + int safe_atou_full(const char *s, unsigned base, unsigned *ret_u); + + static inline int safe_atou(const char *s, unsigned *ret_u) { +@@ -45,9 +51,14 @@ static inline int safe_atoux16(const char *s, uint16_t *ret) { + + int safe_atoi16(const char *s, int16_t *ret); + ++static inline int safe_atou32_full(const char *s, unsigned base, uint32_t *ret_u) { ++ assert_cc(sizeof(uint32_t) == sizeof(unsigned)); ++ return safe_atou_full(s, base, (unsigned*) ret_u); ++} ++ + static inline int safe_atou32(const char *s, uint32_t *ret_u) { + assert_cc(sizeof(uint32_t) == sizeof(unsigned)); +- return safe_atou(s, (unsigned*) ret_u); ++ return safe_atou32_full(s, 0, (unsigned*) ret_u); + } + + static inline int safe_atoi32(const char *s, int32_t *ret_i) { +-- +2.23.0 + diff --git a/backport-0011-CVE-2020-13776-parse-util-allow-0-as-alternative-to-0-and-0.patch b/backport-0011-CVE-2020-13776-parse-util-allow-0-as-alternative-to-0-and-0.patch new file mode 100644 index 0000000..e314d2f --- /dev/null +++ b/backport-0011-CVE-2020-13776-parse-util-allow-0-as-alternative-to-0-and-0.patch @@ -0,0 +1,61 @@ +From c78eefc13562a8fc0c22c00a6d3001af89860258 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Mon, 1 Jun 2020 17:08:38 +0200 +Subject: [PATCH] parse-util: allow '-0' as alternative to '0' and '+0' + +Let's allow "-0" as alternative to "+0" and "0" when parsing integers, +unless the new SAFE_ATO_REFUSE_PLUS_MINUS flag is specified. + +In cases where allowing the +/- syntax shall not be allowed +SAFE_ATO_REFUSE_PLUS_MINUS is the right flag to use, but this also means +that -0 as only negative integer that fits into an unsigned value should +be acceptable if the flag is not specified. +Reference: https://github.com/systemd/systemd/commit/c78eefc13562a8fc0c22c00a6d3001af89860258 +Conflict: NA +--- + src/basic/parse-util.c | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c +index 15818958e4..7344dc4311 100644 +--- a/src/basic/parse-util.c ++++ b/src/basic/parse-util.c +@@ -392,7 +392,7 @@ int safe_atou_full(const char *s, unsigned base, unsigned *ret_u) { + return -errno; + if (!x || x == s || *x != 0) + return -EINVAL; +- if (s[0] == '-') ++ if (l != 0 && s[0] == '-') + return -ERANGE; + if ((unsigned long) (unsigned) l != l) + return -ERANGE; +@@ -451,7 +451,7 @@ int safe_atollu_full(const char *s, unsigned base, long long unsigned *ret_llu) + return -errno; + if (!x || x == s || *x != 0) + return -EINVAL; +- if (*s == '-') ++ if (l != 0 && s[0] == '-') + return -ERANGE; + + if (ret_llu) +@@ -493,7 +493,7 @@ int safe_atou8(const char *s, uint8_t *ret) { + return -errno; + if (!x || x == s || *x != 0) + return -EINVAL; +- if (s[0] == '-') ++ if (l != 0 && s[0] == '-') + return -ERANGE; + if ((unsigned long) (uint8_t) l != l) + return -ERANGE; +@@ -530,7 +530,7 @@ int safe_atou16_full(const char *s, unsigned base, uint16_t *ret) { + return -errno; + if (!x || x == s || *x != 0) + return -EINVAL; +- if (s[0] == '-') ++ if (l != 0 && s[0] == '-') + return -ERANGE; + if ((unsigned long) (uint16_t) l != l) + return -ERANGE; +-- +2.23.0 + diff --git a/backport-0012-CVE-2020-13776-parse-util-make-return-parameter-optional-in-safe_at.patch b/backport-0012-CVE-2020-13776-parse-util-make-return-parameter-optional-in-safe_at.patch new file mode 100644 index 0000000..95b5179 --- /dev/null +++ b/backport-0012-CVE-2020-13776-parse-util-make-return-parameter-optional-in-safe_at.patch @@ -0,0 +1,32 @@ +From aa85e4d3cef8ca8436e480bce9fa4ce72876b636 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Mon, 1 Jun 2020 17:10:27 +0200 +Subject: [PATCH] parse-util: make return parameter optional in + safe_atou16_full() + +All other safe_atoXYZ_full() functions have the parameter optional, +let's make it optoinal here, too. +Reference: https://github.com/systemd/systemd/commit/aa85e4d3cef8ca8436e480bce9fa4ce72876b636 +Conflict: NA +--- + src/basic/parse-util.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c +index 7344dc4311..c58f2cdda1 100644 +--- a/src/basic/parse-util.c ++++ b/src/basic/parse-util.c +@@ -535,7 +535,9 @@ int safe_atou16_full(const char *s, unsigned base, uint16_t *ret) { + if ((unsigned long) (uint16_t) l != l) + return -ERANGE; + +- *ret = (uint16_t) l; ++ if (ret) ++ *ret = (uint16_t) l; ++ + return 0; + } + +-- +2.23.0 + diff --git a/backport-0013-CVE-2020-13776-parse-util-rewrite-parse_mode-on-top-of-safe_atou_fu.patch b/backport-0013-CVE-2020-13776-parse-util-rewrite-parse_mode-on-top-of-safe_atou_fu.patch new file mode 100644 index 0000000..69a2df1 --- /dev/null +++ b/backport-0013-CVE-2020-13776-parse-util-rewrite-parse_mode-on-top-of-safe_atou_fu.patch @@ -0,0 +1,60 @@ +From c44702a8bd8cc8b7f2f1df21db9308d9af7dda5b Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Mon, 1 Jun 2020 17:16:04 +0200 +Subject: [PATCH] parse-util: rewrite parse_mode() on top of safe_atou_full() + +Parsing is hard, hence let's use our own careful wrappers wherever +possible. +Reference: https://github.com/systemd/systemd/commit/c44702a8bd8cc8b7f2f1df21db9308d9af7dda5b +Conflict: NA +--- + src/basic/parse-util.c | 28 +++++++++++++--------------- + 1 file changed, 13 insertions(+), 15 deletions(-) + +diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c +index c58f2cdda1..0f6d24f590 100644 +--- a/src/basic/parse-util.c ++++ b/src/basic/parse-util.c +@@ -70,26 +70,24 @@ int parse_pid(const char *s, pid_t* ret_pid) { + } + + int parse_mode(const char *s, mode_t *ret) { +- char *x; +- long l; ++ unsigned m; ++ int r; + + assert(s); +- assert(ret); + +- s += strspn(s, WHITESPACE); +- if (s[0] == '-') +- return -ERANGE; +- +- errno = 0; +- l = strtol(s, &x, 8); +- if (errno > 0) +- return -errno; +- if (!x || x == s || *x != 0) +- return -EINVAL; +- if (l < 0 || l > 07777) ++ r = safe_atou_full(s, 8 | ++ SAFE_ATO_REFUSE_PLUS_MINUS, /* Leading '+' or even '-' char? that's just weird, ++ * refuse. User might have wanted to add mode flags or ++ * so, but this parser doesn't allow that, so let's ++ * better be safe. */ ++ &m); ++ if (r < 0) ++ return r; ++ if (m > 07777) + return -ERANGE; + +- *ret = (mode_t) l; ++ if (ret) ++ *ret = m; + return 0; + } + +-- +2.23.0 + diff --git a/backport-0014-CVE-2020-13776-user-util-be-stricter-in-parse_uid.patch b/backport-0014-CVE-2020-13776-user-util-be-stricter-in-parse_uid.patch new file mode 100644 index 0000000..4101808 --- /dev/null +++ b/backport-0014-CVE-2020-13776-user-util-be-stricter-in-parse_uid.patch @@ -0,0 +1,79 @@ +From f5979b63cc305ba217dfd174b1bf0583bcf75a73 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Mon, 1 Jun 2020 17:16:46 +0200 +Subject: [PATCH] user-util: be stricter in parse_uid() + +Let's refuse "+" and "-" prefixed UIDs. Let's refuse whitespace-prefixed +UIDS, Let's refuse zero-prefixed UIDs. Let's be safe than sorry. +Reference: https://github.com/systemd/systemd/commit/f5979b63cc305ba217dfd174b1bf0583bcf75a73 +Conflict: NA +--- + src/basic/user-util.c | 10 +++++++++- + src/test/test-user-util.c | 26 +++++++++++++++++++++++--- + 2 files changed, 32 insertions(+), 4 deletions(-) + +diff --git a/src/basic/user-util.c b/src/basic/user-util.c +index 2db8ef6abf..4d087b1d3e 100644 +--- a/src/basic/user-util.c ++++ b/src/basic/user-util.c +@@ -49,7 +49,15 @@ int parse_uid(const char *s, uid_t *ret) { + assert(s); + + assert_cc(sizeof(uid_t) == sizeof(uint32_t)); +- r = safe_atou32_full(s, 10, &uid); ++ ++ /* We are very strict when parsing UIDs, and prohibit +/- as prefix, leading zero as prefix, and ++ * whitespace. We do this, since this call is often used in a context where we parse things as UID ++ * first, and if that doesn't work we fall back to NSS. Thus we really want to make sure that UIDs ++ * are parsed as UIDs only if they really really look like UIDs. */ ++ r = safe_atou32_full(s, 10 ++ | SAFE_ATO_REFUSE_PLUS_MINUS ++ | SAFE_ATO_REFUSE_LEADING_ZERO ++ | SAFE_ATO_REFUSE_LEADING_WHITESPACE, &uid); + if (r < 0) + return r; + +diff --git a/src/test/test-user-util.c b/src/test/test-user-util.c +index 3165232fef..7d2efc8c59 100644 +--- a/src/test/test-user-util.c ++++ b/src/test/test-user-util.c +@@ -54,13 +54,33 @@ static void test_parse_uid(void) { + assert_se(r == -EINVAL); + assert_se(uid == 100); + ++ r = parse_uid("+1234", &uid); ++ assert_se(r == -EINVAL); ++ assert_se(uid == 100); ++ ++ r = parse_uid("-1234", &uid); ++ assert_se(r == -EINVAL); ++ assert_se(uid == 100); ++ ++ r = parse_uid(" 1234", &uid); ++ assert_se(r == -EINVAL); ++ assert_se(uid == 100); ++ + r = parse_uid("01234", &uid); +- assert_se(r == 0); +- assert_se(uid == 1234); ++ assert_se(r == -EINVAL); ++ assert_se(uid == 100); ++ ++ r = parse_uid("-0", &uid); ++ assert_se(r == -EINVAL); ++ assert_se(uid == 100); ++ ++ r = parse_uid("+0", &uid); ++ assert_se(r == -EINVAL); ++ assert_se(uid == 100); + + r = parse_uid("asdsdas", &uid); + assert_se(r == -EINVAL); +- assert_se(uid == 1234); ++ assert_se(uid == 100); + } + + static void test_uid_ptr(void) { +-- +2.23.0 + diff --git a/backport-0015-CVE-2020-13776-parse-util-also-parse-integers-prefixed-with-0b-and-.patch b/backport-0015-CVE-2020-13776-parse-util-also-parse-integers-prefixed-with-0b-and-.patch new file mode 100644 index 0000000..9ded598 --- /dev/null +++ b/backport-0015-CVE-2020-13776-parse-util-also-parse-integers-prefixed-with-0b-and-.patch @@ -0,0 +1,165 @@ +From fc80cabcf584a8b486bdff5be0c074fec4059cdc Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Mon, 1 Jun 2020 17:31:51 +0200 +Subject: [PATCH] parse-util: also parse integers prefixed with 0b and 0o + +Let's adopt Python 3 style 0b and 0x syntaxes, because it makes a ton of +sense, in particular in bitmask settings. +Reference: https://github.com/systemd/systemd/commit/fc80cabcf584a8b486bdff5be0c074fec4059cdc +Conflict: Also include strv.h. +--- + src/basic/parse-util.c | 57 ++++++++++++++++++++++++++++++++++++++---- + 1 file changed, 52 insertions(+), 5 deletions(-) + +diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c +index b4c7f0d..bf56410 100644 +--- a/src/basic/parse-util.c ++++ b/src/basic/parse-util.c +@@ -20,6 +20,7 @@ + #include "process-util.h" + #include "stat-util.h" + #include "string-util.h" ++#include "strv.h" + + int parse_boolean(const char *v) { + if (!v) +@@ -360,6 +361,32 @@ int parse_syscall_and_errno(const char *in, char **name, int *error) { + return 0; + } + ++static const char *mangle_base(const char *s, unsigned *base) { ++ const char *k; ++ ++ assert(s); ++ assert(base); ++ ++ /* Base already explicitly specified, then don't do anything. */ ++ if (SAFE_ATO_MASK_FLAGS(*base) != 0) ++ return s; ++ ++ /* Support Python 3 style "0b" and 0x" prefixes, because they truly make sense, much more than C's "0" prefix for octal. */ ++ k = STARTSWITH_SET(s, "0b", "0B"); ++ if (k) { ++ *base = 2 | (*base & SAFE_ATO_ALL_FLAGS); ++ return k; ++ } ++ ++ k = STARTSWITH_SET(s, "0o", "0O"); ++ if (k) { ++ *base = 8 | (*base & SAFE_ATO_ALL_FLAGS); ++ return k; ++ } ++ ++ return s; ++} ++ + int safe_atou_full(const char *s, unsigned base, unsigned *ret_u) { + char *x = NULL; + unsigned long l; +@@ -391,6 +418,8 @@ int safe_atou_full(const char *s, unsigned base, unsigned *ret_u) { + return -EINVAL; /* This is particularly useful to avoid ambiguities between C's octal + * notation and assumed-to-be-decimal integers with a leading zero. */ + ++ s = mangle_base(s, &base); ++ + errno = 0; + l = strtoul(s, &x, SAFE_ATO_MASK_FLAGS(base) /* Let's mask off the flags bits so that only the actual + * base is left */); +@@ -410,13 +439,17 @@ int safe_atou_full(const char *s, unsigned base, unsigned *ret_u) { + } + + int safe_atoi(const char *s, int *ret_i) { ++ unsigned base = 0; + char *x = NULL; + long l; + + assert(s); + ++ s += strspn(s, WHITESPACE); ++ s = mangle_base(s, &base); ++ + errno = 0; +- l = strtol(s, &x, 0); ++ l = strtol(s, &x, base); + if (errno > 0) + return -errno; + if (!x || x == s || *x != 0) +@@ -451,6 +484,8 @@ int safe_atollu_full(const char *s, unsigned base, long long unsigned *ret_llu) + s[0] == '0' && s[1] != 0) + return -EINVAL; + ++ s = mangle_base(s, &base); ++ + errno = 0; + l = strtoull(s, &x, SAFE_ATO_MASK_FLAGS(base)); + if (errno > 0) +@@ -467,13 +502,17 @@ int safe_atollu_full(const char *s, unsigned base, long long unsigned *ret_llu) + } + + int safe_atolli(const char *s, long long int *ret_lli) { ++ unsigned base = 0; + char *x = NULL; + long long l; + + assert(s); + ++ s += strspn(s, WHITESPACE); ++ s = mangle_base(s, &base); ++ + errno = 0; +- l = strtoll(s, &x, 0); ++ l = strtoll(s, &x, base); + if (errno > 0) + return -errno; + if (!x || x == s || *x != 0) +@@ -486,15 +525,17 @@ int safe_atolli(const char *s, long long int *ret_lli) { + } + + int safe_atou8(const char *s, uint8_t *ret) { +- char *x = NULL; ++ unsigned base = 0; + unsigned long l; ++ char *x = NULL; + + assert(s); + + s += strspn(s, WHITESPACE); ++ s = mangle_base(s, &base); + + errno = 0; +- l = strtoul(s, &x, 0); ++ l = strtoul(s, &x, base); + if (errno > 0) + return -errno; + if (!x || x == s || *x != 0) +@@ -530,6 +571,8 @@ int safe_atou16_full(const char *s, unsigned base, uint16_t *ret) { + s[0] == '0' && s[1] != 0) + return -EINVAL; + ++ s = mangle_base(s, &base); ++ + errno = 0; + l = strtoul(s, &x, SAFE_ATO_MASK_FLAGS(base)); + if (errno > 0) +@@ -548,13 +591,17 @@ int safe_atou16_full(const char *s, unsigned base, uint16_t *ret) { + } + + int safe_atoi16(const char *s, int16_t *ret) { ++ unsigned base = 0; + char *x = NULL; + long l; + + assert(s); + ++ s += strspn(s, WHITESPACE); ++ s = mangle_base(s, &base); ++ + errno = 0; +- l = strtol(s, &x, 0); ++ l = strtol(s, &x, base); + if (errno > 0) + return -errno; + if (!x || x == s || *x != 0) +-- +2.23.0 + diff --git a/systemd.spec b/systemd.spec index 870b3b0..81a3ac8 100644 --- a/systemd.spec +++ b/systemd.spec @@ -16,7 +16,7 @@ Name: systemd Url: https://www.freedesktop.org/wiki/Software/systemd Version: 243 -Release: 53 +Release: 54 License: MIT and LGPLv2+ and GPLv2+ Summary: System and Service Manager @@ -156,6 +156,22 @@ Patch0108: backport-udevadm-fix-tag-match-help-description.patch Patch0109: backport-fix-ConditionDirectoryNotEmpty-when-it-comes-to-a-No.patch Patch0110: backport-fix-ConditionPathIsReadWrite-when-path-does-not-exis.patch Patch0111: backport-fix-DirectoryNotEmpty-when-it-comes-to-a-Non-directo.patch +Patch0112: backport-0001-CVE-2020-13776-user-util-Allow-names-starting-with-a-digit.patch +Patch0113: backport-0002-CVE-2020-13776-user-util-switch-order-of-checks-in-valid_user_group.patch +Patch0114: backport-0003-CVE-2020-13776-user-util-rework-how-we-validate-user-names.patch +Patch0115: backport-0004-CVE-2020-13776-docs-add-a-longer-document-explaining-our-rules-on-u.patch +Patch0116: backport-0005-CVE-2020-13776-docs-hook-up-the-new-USER_NAMES-document-everywhere.patch +Patch0117: backport-0006-CVE-2020-13776-catalog-add-entry-for-SD_MESSAGE_UNSAFE_USER_NAME.patch +Patch0118: backport-0007-CVE-2020-13776-basic-user-util-always-use-base-10-for-user-group-nu.patch +Patch0119: backport-0008-CVE-2020-13776-parse-util-sometimes-it-is-useful-to-check-if-a-stri.patch +Patch0120: backport-0009-CVE-2020-13776-basic-parse-util-add-safe_atoux64.patch +Patch0121: backport-0010-CVE-2020-13776-parse-util-allow-tweaking-how-to-parse-integers.patch +Patch0122: backport-0011-CVE-2020-13776-parse-util-allow-0-as-alternative-to-0-and-0.patch +Patch0123: backport-0012-CVE-2020-13776-parse-util-make-return-parameter-optional-in-safe_at.patch +Patch0124: backport-0013-CVE-2020-13776-parse-util-rewrite-parse_mode-on-top-of-safe_atou_fu.patch +Patch0125: backport-0014-CVE-2020-13776-user-util-be-stricter-in-parse_uid.patch +Patch0126: backport-0015-CVE-2020-13776-parse-util-also-parse-integers-prefixed-with-0b-and-.patch + #openEuler Patch9002: 1509-fix-journal-file-descriptors-leak-problems.patch @@ -1544,6 +1560,9 @@ fi %exclude /usr/share/man/man3/* %changelog +* Wed Feb 16 2021 yangmingtai - 243-54 +- fix CVE-2020-13776 + * Wed Jan 26 2021 yangmingtai - 243-53 - fix ConditionDirectoryNotEmpty,ConditionPathIsReadWrite and DirectoryNotEmpty