mirror of
https://github.com/systemd/systemd.git
synced 2025-03-09 12:58:26 +03:00
test: Gracefully handle running within user namespace with single user
Unprivileged users often make themselves root by unsharing a user namespace and then mapping their current user to root which does not require privileges. Let's make sure our tests don't fail in such an environment by adding checks where required to see if we're not running in a user namespace with only a single user.
This commit is contained in:
parent
d098b8df6e
commit
ef31767ed7
@ -29,6 +29,7 @@
|
|||||||
#include "strv.h"
|
#include "strv.h"
|
||||||
#include "tests.h"
|
#include "tests.h"
|
||||||
#include "tmpfile-util.h"
|
#include "tmpfile-util.h"
|
||||||
|
#include "uid-range.h"
|
||||||
|
|
||||||
char* setup_fake_runtime_dir(void) {
|
char* setup_fake_runtime_dir(void) {
|
||||||
char t[] = "/tmp/fake-xdg-runtime-XXXXXX", *p;
|
char t[] = "/tmp/fake-xdg-runtime-XXXXXX", *p;
|
||||||
@ -166,6 +167,24 @@ bool have_namespaces(void) {
|
|||||||
assert_not_reached();
|
assert_not_reached();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool userns_has_single_user(void) {
|
||||||
|
_cleanup_(uid_range_freep) UIDRange *uidrange = NULL, *gidrange = NULL;
|
||||||
|
|
||||||
|
/* Check if we're in a user namespace with only a single user mapped in. We special case this
|
||||||
|
* scenario in a few tests because it's the only kind of namespace that can be created unprivileged
|
||||||
|
* and as such happens more often than not, so we make sure to deal with it so that all tests pass
|
||||||
|
* in such environments. */
|
||||||
|
|
||||||
|
if (uid_range_load_userns(NULL, UID_RANGE_USERNS_INSIDE, &uidrange) < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (uid_range_load_userns(NULL, GID_RANGE_USERNS_INSIDE, &gidrange) < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return uidrange->n_entries == 1 && uidrange->entries[0].nr == 1 &&
|
||||||
|
gidrange->n_entries == 1 && gidrange->entries[0].nr == 1;
|
||||||
|
}
|
||||||
|
|
||||||
bool can_memlock(void) {
|
bool can_memlock(void) {
|
||||||
/* Let's see if we can mlock() a larger blob of memory. BPF programs are charged against
|
/* Let's see if we can mlock() a larger blob of memory. BPF programs are charged against
|
||||||
* RLIMIT_MEMLOCK, hence let's first make sure we can lock memory at all, and skip the test if we
|
* RLIMIT_MEMLOCK, hence let's first make sure we can lock memory at all, and skip the test if we
|
||||||
|
@ -76,6 +76,7 @@ void test_setup_logging(int level);
|
|||||||
int write_tmpfile(char *pattern, const char *contents);
|
int write_tmpfile(char *pattern, const char *contents);
|
||||||
|
|
||||||
bool have_namespaces(void);
|
bool have_namespaces(void);
|
||||||
|
bool userns_has_single_user(void);
|
||||||
|
|
||||||
/* We use the small but non-trivial limit here */
|
/* We use the small but non-trivial limit here */
|
||||||
#define CAN_MEMLOCK_SIZE (512 * 1024U)
|
#define CAN_MEMLOCK_SIZE (512 * 1024U)
|
||||||
|
@ -41,7 +41,7 @@ TEST_RET(add_acls_for_user) {
|
|||||||
cmd = strjoina("getfacl -p ", fn);
|
cmd = strjoina("getfacl -p ", fn);
|
||||||
assert_se(system(cmd) == 0);
|
assert_se(system(cmd) == 0);
|
||||||
|
|
||||||
if (getuid() == 0) {
|
if (getuid() == 0 && !userns_has_single_user()) {
|
||||||
const char *nobody = NOBODY_USER_NAME;
|
const char *nobody = NOBODY_USER_NAME;
|
||||||
r = get_user_creds(&nobody, &uid, NULL, NULL, NULL, 0);
|
r = get_user_creds(&nobody, &uid, NULL, NULL, NULL, 0);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
|
@ -318,10 +318,13 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
show_capabilities();
|
show_capabilities();
|
||||||
|
|
||||||
test_drop_privileges();
|
if (!userns_has_single_user())
|
||||||
|
test_drop_privileges();
|
||||||
|
|
||||||
test_update_inherited_set();
|
test_update_inherited_set();
|
||||||
|
|
||||||
fork_test(test_have_effective_cap);
|
if (!userns_has_single_user())
|
||||||
|
fork_test(test_have_effective_cap);
|
||||||
|
|
||||||
if (run_ambient)
|
if (run_ambient)
|
||||||
fork_test(test_apply_ambient_caps);
|
fork_test(test_apply_ambient_caps);
|
||||||
|
@ -183,7 +183,7 @@ TEST(chase) {
|
|||||||
|
|
||||||
/* Paths underneath the "root" with different UIDs while using CHASE_SAFE */
|
/* Paths underneath the "root" with different UIDs while using CHASE_SAFE */
|
||||||
|
|
||||||
if (geteuid() == 0) {
|
if (geteuid() == 0 && !userns_has_single_user()) {
|
||||||
p = strjoina(temp, "/user");
|
p = strjoina(temp, "/user");
|
||||||
ASSERT_OK(mkdir(p, 0755));
|
ASSERT_OK(mkdir(p, 0755));
|
||||||
ASSERT_OK(chown(p, UID_NOBODY, GID_NOBODY));
|
ASSERT_OK(chown(p, UID_NOBODY, GID_NOBODY));
|
||||||
@ -313,7 +313,7 @@ TEST(chase) {
|
|||||||
r = chase(p, NULL, 0, &result, NULL);
|
r = chase(p, NULL, 0, &result, NULL);
|
||||||
assert_se(r == -ENOENT);
|
assert_se(r == -ENOENT);
|
||||||
|
|
||||||
if (geteuid() == 0) {
|
if (geteuid() == 0 && !userns_has_single_user()) {
|
||||||
p = strjoina(temp, "/priv1");
|
p = strjoina(temp, "/priv1");
|
||||||
ASSERT_OK(mkdir(p, 0755));
|
ASSERT_OK(mkdir(p, 0755));
|
||||||
|
|
||||||
|
@ -153,8 +153,8 @@ TEST(chown_recursive) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int intro(void) {
|
static int intro(void) {
|
||||||
if (geteuid() != 0)
|
if (geteuid() != 0 || userns_has_single_user())
|
||||||
return log_tests_skipped("not running as root");
|
return log_tests_skipped("not running as root or in userns with single user");
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
@ -1003,6 +1003,13 @@ TEST(condition_test_group) {
|
|||||||
condition_free(condition);
|
condition_free(condition);
|
||||||
free(gid);
|
free(gid);
|
||||||
|
|
||||||
|
/* In an unprivileged user namespace with the current user mapped to root, all the auxiliary groups
|
||||||
|
* of the user will be mapped to the nobody group, which means the user in the user namespace is in
|
||||||
|
* both the root and the nobody group, meaning the next test can't work, so let's skip it in that
|
||||||
|
* case. */
|
||||||
|
if (in_group(NOBODY_GROUP_NAME) && in_group("root"))
|
||||||
|
return (void) log_tests_skipped("user is in both root and nobody group");
|
||||||
|
|
||||||
groupname = (char*)(getegid() == 0 ? NOBODY_GROUP_NAME : "root");
|
groupname = (char*)(getegid() == 0 ? NOBODY_GROUP_NAME : "root");
|
||||||
condition = condition_new(CONDITION_GROUP, groupname, false, false);
|
condition = condition_new(CONDITION_GROUP, groupname, false, false);
|
||||||
assert_se(condition);
|
assert_se(condition);
|
||||||
|
@ -368,8 +368,8 @@ TEST(chmod_and_chown) {
|
|||||||
struct stat st;
|
struct stat st;
|
||||||
const char *p;
|
const char *p;
|
||||||
|
|
||||||
if (geteuid() != 0)
|
if (geteuid() != 0 || userns_has_single_user())
|
||||||
return;
|
return (void) log_tests_skipped("not running as root or in userns with single user");
|
||||||
|
|
||||||
BLOCK_WITH_UMASK(0000);
|
BLOCK_WITH_UMASK(0000);
|
||||||
|
|
||||||
|
@ -89,6 +89,9 @@ static void test_rm_rf_chmod_inner(void) {
|
|||||||
TEST(rm_rf_chmod) {
|
TEST(rm_rf_chmod) {
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
|
if (getuid() == 0 && userns_has_single_user())
|
||||||
|
return (void) log_tests_skipped("running as root or in userns with single user");
|
||||||
|
|
||||||
if (getuid() == 0) {
|
if (getuid() == 0) {
|
||||||
/* This test only works unpriv (as only then the access mask for the owning user matters),
|
/* This test only works unpriv (as only then the access mask for the owning user matters),
|
||||||
* hence drop privs here */
|
* hence drop privs here */
|
||||||
|
@ -170,7 +170,7 @@ TEST(getpeercred_getpeergroups) {
|
|||||||
struct ucred ucred;
|
struct ucred ucred;
|
||||||
int pair[2] = EBADF_PAIR;
|
int pair[2] = EBADF_PAIR;
|
||||||
|
|
||||||
if (geteuid() == 0) {
|
if (geteuid() == 0 && !userns_has_single_user()) {
|
||||||
test_uid = 1;
|
test_uid = 1;
|
||||||
test_gid = 2;
|
test_gid = 2;
|
||||||
test_gids = (gid_t*) gids;
|
test_gids = (gid_t*) gids;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user