Systemd/src/test/test-user-util.c
Lennart Poettering 7a8867abfa user-util: rework how we validate user names
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
2020-04-08 17:11:20 +02:00

391 lines
18 KiB
C

/* SPDX-License-Identifier: LGPL-2.1+ */
#include "alloc-util.h"
#include "format-util.h"
#include "libcrypt-util.h"
#include "log.h"
#include "macro.h"
#include "memory-util.h"
#include "path-util.h"
#include "string-util.h"
#include "user-util.h"
static void test_uid_to_name_one(uid_t uid, const char *name) {
_cleanup_free_ char *t = NULL;
log_info("/* %s("UID_FMT", \"%s\") */", __func__, uid, name);
assert_se(t = uid_to_name(uid));
if (!synthesize_nobody() && streq(name, NOBODY_USER_NAME)) {
log_info("(skipping detailed tests because nobody is not synthesized)");
return;
}
assert_se(streq_ptr(t, name));
}
static void test_gid_to_name_one(gid_t gid, const char *name) {
_cleanup_free_ char *t = NULL;
log_info("/* %s("GID_FMT", \"%s\") */", __func__, gid, name);
assert_se(t = gid_to_name(gid));
if (!synthesize_nobody() && streq(name, NOBODY_GROUP_NAME)) {
log_info("(skipping detailed tests because nobody is not synthesized)");
return;
}
assert_se(streq_ptr(t, name));
}
static void test_parse_uid(void) {
int r;
uid_t uid;
log_info("/* %s */", __func__);
r = parse_uid("100", &uid);
assert_se(r == 0);
assert_se(uid == 100);
r = parse_uid("65535", &uid);
assert_se(r == -ENXIO);
r = parse_uid("asdsdas", &uid);
assert_se(r == -EINVAL);
}
static void test_uid_ptr(void) {
log_info("/* %s */", __func__);
assert_se(UID_TO_PTR(0) != NULL);
assert_se(UID_TO_PTR(1000) != NULL);
assert_se(PTR_TO_UID(UID_TO_PTR(0)) == 0);
assert_se(PTR_TO_UID(UID_TO_PTR(1000)) == 1000);
}
static void test_valid_user_group_name_relaxed(void) {
log_info("/* %s */", __func__);
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, 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_numeric_relaxed(void) {
log_info("/* %s */", __func__);
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_numeric(void) {
log_info("/* %s */", __func__);
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) {
log_info("/* %s */", __func__);
assert_se(!valid_gecos(NULL));
assert_se(valid_gecos(""));
assert_se(valid_gecos("test"));
assert_se(valid_gecos("Ümläüt"));
assert_se(!valid_gecos("In\nvalid"));
assert_se(!valid_gecos("In:valid"));
}
static void test_valid_home(void) {
log_info("/* %s */", __func__);
assert_se(!valid_home(NULL));
assert_se(!valid_home(""));
assert_se(!valid_home("."));
assert_se(!valid_home("/home/.."));
assert_se(!valid_home("/home/../"));
assert_se(!valid_home("/home\n/foo"));
assert_se(!valid_home("./piep"));
assert_se(!valid_home("piep"));
assert_se(!valid_home("/home/user:lennart"));
assert_se(valid_home("/"));
assert_se(valid_home("/home"));
assert_se(valid_home("/home/foo"));
}
static void test_get_user_creds_one(const char *id, const char *name, uid_t uid, gid_t gid, const char *home, const char *shell) {
const char *rhome = NULL;
const char *rshell = NULL;
uid_t ruid = UID_INVALID;
gid_t rgid = GID_INVALID;
int r;
log_info("/* %s(\"%s\", \"%s\", "UID_FMT", "GID_FMT", \"%s\", \"%s\") */",
__func__, id, name, uid, gid, home, shell);
r = get_user_creds(&id, &ruid, &rgid, &rhome, &rshell, 0);
log_info_errno(r, "got \"%s\", "UID_FMT", "GID_FMT", \"%s\", \"%s\": %m",
id, ruid, rgid, strnull(rhome), strnull(rshell));
if (!synthesize_nobody() && streq(name, NOBODY_USER_NAME)) {
log_info("(skipping detailed tests because nobody is not synthesized)");
return;
}
assert_se(r == 0);
assert_se(streq_ptr(id, name));
assert_se(ruid == uid);
assert_se(rgid == gid);
assert_se(path_equal(rhome, home));
assert_se(path_equal(rshell, shell));
}
static void test_get_group_creds_one(const char *id, const char *name, gid_t gid) {
gid_t rgid = GID_INVALID;
int r;
log_info("/* %s(\"%s\", \"%s\", "GID_FMT") */", __func__, id, name, gid);
r = get_group_creds(&id, &rgid, 0);
log_info_errno(r, "got \"%s\", "GID_FMT": %m", id, rgid);
if (!synthesize_nobody() && streq(name, NOBODY_GROUP_NAME)) {
log_info("(skipping detailed tests because nobody is not synthesized)");
return;
}
assert_se(r == 0);
assert_se(streq_ptr(id, name));
assert_se(rgid == gid);
}
static void test_make_salt(void) {
log_info("/* %s */", __func__);
_cleanup_free_ char *s, *t;
assert_se(make_salt(&s) == 0);
log_info("got %s", s);
assert_se(make_salt(&t) == 0);
log_info("got %s", t);
assert(!streq(s, t));
}
static void test_in_gid(void) {
assert(in_gid(getgid()) >= 0);
assert(in_gid(getegid()) >= 0); assert(in_gid(TTY_GID) == 0); /* The TTY gid is for owning ttys, it would be really really weird if we were in it. */
}
static void test_gid_lists_ops(void) {
static const gid_t l1[] = { 5, 10, 15, 20, 25};
static const gid_t l2[] = { 1, 2, 3, 15, 20, 25};
static const gid_t l3[] = { 5, 10, 15, 20, 25, 26, 27};
static const gid_t l4[] = { 25, 26, 20, 15, 5, 27, 10};
static const gid_t result1[] = {1, 2, 3, 5, 10, 15, 20, 25, 26, 27};
static const gid_t result2[] = {5, 10, 15, 20, 25, 26, 27};
_cleanup_free_ gid_t *gids = NULL;
_cleanup_free_ gid_t *res1 = NULL;
_cleanup_free_ gid_t *res2 = NULL;
_cleanup_free_ gid_t *res3 = NULL;
_cleanup_free_ gid_t *res4 = NULL;
int nresult;
nresult = merge_gid_lists(l2, ELEMENTSOF(l2), l3, ELEMENTSOF(l3), &res1);
assert_se(nresult >= 0);
assert_se(memcmp_nn(res1, nresult, result1, ELEMENTSOF(result1)) == 0);
nresult = merge_gid_lists(NULL, 0, l2, ELEMENTSOF(l2), &res2);
assert_se(nresult >= 0);
assert_se(memcmp_nn(res2, nresult, l2, ELEMENTSOF(l2)) == 0);
nresult = merge_gid_lists(l1, ELEMENTSOF(l1), l1, ELEMENTSOF(l1), &res3);
assert_se(nresult >= 0);
assert_se(memcmp_nn(l1, ELEMENTSOF(l1), res3, nresult) == 0);
nresult = merge_gid_lists(l1, ELEMENTSOF(l1), l4, ELEMENTSOF(l4), &res4);
assert_se(nresult >= 0);
assert_se(memcmp_nn(result2, ELEMENTSOF(result2), res4, nresult) == 0);
nresult = getgroups_alloc(&gids);
assert_se(nresult >= 0 || nresult == -EINVAL || nresult == -ENOMEM);
assert_se(gids);
}
int main(int argc, char *argv[]) {
test_uid_to_name_one(0, "root");
test_uid_to_name_one(UID_NOBODY, NOBODY_USER_NAME);
test_uid_to_name_one(0xFFFF, "65535");
test_uid_to_name_one(0xFFFFFFFF, "4294967295");
test_gid_to_name_one(0, "root");
test_gid_to_name_one(GID_NOBODY, NOBODY_GROUP_NAME);
test_gid_to_name_one(TTY_GID, "tty");
test_gid_to_name_one(0xFFFF, "65535");
test_gid_to_name_one(0xFFFFFFFF, "4294967295");
test_get_user_creds_one("root", "root", 0, 0, "/root", "/bin/sh");
test_get_user_creds_one("0", "root", 0, 0, "/root", "/bin/sh");
test_get_user_creds_one(NOBODY_USER_NAME, NOBODY_USER_NAME, UID_NOBODY, GID_NOBODY, "/", NOLOGIN);
test_get_user_creds_one("65534", NOBODY_USER_NAME, UID_NOBODY, GID_NOBODY, "/", NOLOGIN);
test_get_group_creds_one("root", "root", 0);
test_get_group_creds_one("0", "root", 0);
test_get_group_creds_one(NOBODY_GROUP_NAME, NOBODY_GROUP_NAME, GID_NOBODY);
test_get_group_creds_one("65534", NOBODY_GROUP_NAME, GID_NOBODY);
test_parse_uid();
test_uid_ptr();
test_valid_user_group_name_relaxed();
test_valid_user_group_name();
test_valid_user_group_name_or_numeric_relaxed();
test_valid_user_group_name_or_numeric();
test_valid_gecos();
test_valid_home();
test_make_salt();
test_in_gid();
test_gid_lists_ops();
return 0;
}