Compare commits

...

6 Commits

Author SHA1 Message Date
44b9a17bec Allow specifying uid/gid to map in user namespace 2022-12-27 13:25:12 +01:00
f662398ac3 test: test_launch_get(): Fix typo and remove redundant call 2022-12-27 13:14:39 +01:00
7b859d0aed exile_launch_get(): Remove redundant seek 2022-12-26 18:36:17 +01:00
5cd0a36ced test.sh: Fix regression causing status code to be lost
The changes in 01c5cbf701
did not take into account that "tee" would change the exit code.

Use a protable alternative to &>> now.
2022-12-26 18:29:32 +01:00
618f223491 enter_namespaces(): Fix uid/gid mapping
This was not caught before because a test was missing, fprintf() without ferror()
didn't help, and calling code did not depend on uid maps so far.

Add tests.
2022-12-26 18:23:34 +01:00
01c5cbf701 test.sh: Make it more portable 2022-12-20 10:50:42 +01:00
4 changed files with 200 additions and 48 deletions

74
exile.c
View File

@ -625,6 +625,8 @@ struct exile_policy *exile_init_policy()
result->not_dumpable = 1;
result->no_new_privs = 1;
result->namespace_options = EXILE_UNSHARE_MOUNT | EXILE_UNSHARE_USER;
result->namespace_uid = 0;
result->namespace_gid = 0;
return result;
}
@ -938,10 +940,15 @@ void exile_free_policy(struct exile_policy *ctxt)
}
/* Enters the specified namespaces */
static int enter_namespaces(int namespace_options)
static int enter_namespaces(int namespace_options, uid_t namespace_uid, gid_t namespace_gid)
{
if(namespace_options & EXILE_UNSHARE_USER)
{
uid_t current_uid = getuid();
gid_t current_gid = getgid();
char buf[1024] = {0};
int ret = unshare(CLONE_NEWUSER);
if(ret == -1)
{
@ -949,47 +956,51 @@ static int enter_namespaces(int namespace_options)
return ret;
}
uid_t current_uid = getuid();
gid_t current_gid = getgid();
int fd = open("/proc/self/setgroups", O_WRONLY);
if(fd == -1)
{
EXILE_LOG_ERROR("Failed to open /proc/self/setgroups for writing");
return -1;
}
int writesize = snprintf(buf, sizeof(buf), "deny");
int writeret = write(fd, buf, writesize);
if(writeret < 0 || writeret < writesize)
{
EXILE_LOG_ERROR("Failed to write to /proc/self/setgroups: %i (%s)\n", writeret, strerror(errno));
return -1;
}
close(fd);
FILE *fp = fopen("/proc/self/setgroups", "w");
if(fp == NULL)
fd = open("/proc/self/uid_map", O_WRONLY);
if(fd == -1)
{
EXILE_LOG_ERROR("fopen failed while trying to deny setgroups\n");
EXILE_LOG_ERROR("Failed to open /proc/self/uid_map for writing");
return -1;
}
if(fprintf(fp, "deny") < 0)
writesize = snprintf(buf, sizeof(buf), "%u %u 1\n", namespace_uid, current_uid);
writeret = write(fd, buf, writesize);
if(writeret < 0 || writeret < writesize)
{
EXILE_LOG_ERROR("fprintf failed while trying to write setgroups\n");
EXILE_LOG_ERROR("Failed to write to /proc/self/uid_map: %i (%s)\n", writeret, strerror(errno));
return -1;
}
fclose(fp);
close(fd);
fp = fopen("/proc/self/uid_map", "w");
if(fp == NULL)
{
EXILE_LOG_ERROR("fopen failed while trying to write uid_map\n");
return -1;
}
if(fprintf(fp, "0 %i", current_uid) < 0)
{
EXILE_LOG_ERROR("fprintf failed while trying to write uid_map\n");
return -1;
}
fclose(fp);
fp = fopen("/proc/self/gid_map", "w");
if(fp == NULL)
fd = open("/proc/self/gid_map", O_WRONLY);
if(fd == -1)
{
EXILE_LOG_ERROR("fopen failed while trying to write gid_map\n");
EXILE_LOG_ERROR("Failed to open /proc/self/gid_map for writing");
return -1;
}
if(fprintf(fp, "0 %i", current_gid) < 0)
writesize = snprintf(buf, sizeof(buf), "%u %u 1\n", namespace_gid, current_gid);
writeret = write(fd, buf, writesize);
if(writeret < 0 || writeret < writesize)
{
EXILE_LOG_ERROR("fprintf failed while trying to write gid_map\n");
EXILE_LOG_ERROR("Failed to write to /proc/self/gid_map: %i (%s)\n", writeret, strerror(errno));
return -1;
}
fclose(fp);
close(fd);
}
if(namespace_options & EXILE_UNSHARE_MOUNT)
@ -1532,7 +1543,7 @@ int exile_enable_policy(struct exile_policy *policy)
close_file_fds();
}
if(enter_namespaces(policy->namespace_options) < 0)
if(enter_namespaces(policy->namespace_options, policy->namespace_uid, policy->namespace_gid) < 0)
{
EXILE_LOG_ERROR("Error while trying to enter namespaces\n");
return -1;
@ -1879,13 +1890,6 @@ char *exile_launch_get(struct exile_launch_params *launch_params, size_t *n)
}
}
fclose(stream);
int seek = fseek(stream, 0, SEEK_SET);
if(seek == -1)
{
EXILE_LOG_ERROR("fseek failed\n");
close(launch_result.read_fd);
return NULL;
}
close(launch_result.read_fd);
*n = size;
return result;

View File

@ -375,6 +375,9 @@ struct exile_policy
uint64_t vow_promises;
uid_t namespace_uid;
gid_t namespace_gid;
/* Do not manually add policies here, use exile_append_path_policies() */
struct exile_path_policy *path_policies;
struct exile_path_policy **path_policies_tail;

148
test.c
View File

@ -618,9 +618,9 @@ int test_launch_get()
size_t n = 0;
char *content = exile_launch_get(&params, &n);
unsigned int len = strlen(LAUNCH_GET_TEST_STR);
if(n != strlen(LAUNCH_GET_TEST_STR))
if(n != len)
{
LOG("Lenght does does not match: %lu vs %u\n", n, len);
LOG("Lenght does not match: %lu vs %u\n", n, len);
return 1;
}
if(strcmp(content, LAUNCH_GET_TEST_STR) != 0)
@ -661,6 +661,146 @@ int test_clone3_nosys()
return 0;
}
int do_test_nsuidmap(const char *path, const char *firstfield, const char *secondfield, const char *thirdfield)
{
char *line = NULL;
size_t n = 0;
FILE *fp = fopen(path, "r");
int ret = getdelim(&line, &n, ' ', fp);
while(ret != -1 && strlen(line) == 1 && *line == ' ')
ret = getdelim(&line, &n, ' ', fp);
if(ret == -1)
{
LOG("getdelim() failed to read a line from %s\n", path);
return 1;
}
line[ret-1] = '\0';
if(strcmp(line, firstfield) != 0)
{
LOG("Invalid value for first entry in map: Expected: %s, was: %s\n", firstfield, line);
return 1;
}
ret = getdelim(&line, &n, ' ', fp);
while(ret != -1 && strlen(line) == 1 && *line == ' ')
ret = getdelim(&line, &n, ' ', fp);
if(ret == -1)
{
LOG("getdelim() failed to read a line from map\n");
return 1;
}
line[ret-1] = '\0';
if(strcmp(line, secondfield) != 0)
{
LOG("Invalid value for second entry in map: Expected: %s, was: %s\n", secondfield, line);
return 1;
}
ret = getdelim(&line, &n, ' ', fp);
while(ret != -1 && strlen(line) == 1 && *line == ' ')
ret = getdelim(&line, &n, ' ', fp);
if(ret == -1)
{
LOG("getdelim() failed to read a line from uid_map\n");
return 1;
}
line[ret-1] = '\0';
if(strcmp(line, thirdfield) != 0)
{
LOG("Invalid value for second entry in map: Expected: %s, was: %s\n", thirdfield, line);
return 1;
}
fclose(fp);
return 0;
}
int test_unshare_user()
{
char uidstr[64];
snprintf(uidstr, sizeof(uidstr), "%u", getuid());
char gidstr[64];
snprintf(gidstr, sizeof(gidstr), "%u", getgid());
struct exile_policy *policy = exile_init_policy();
policy->namespace_options = EXILE_UNSHARE_USER;
xexile_enable_policy(policy);
if(do_test_nsuidmap("/proc/self/uid_map", "0", uidstr, "1") != 0)
{
LOG("/proc/self/uid_map failed\n");
return 1;
}
if(do_test_nsuidmap("/proc/self/gid_map", "0", gidstr, "1") != 0)
{
LOG("/proc/self/gid_map failed\n");
return 1;
}
FILE *fp = fopen("/proc/self/setgroups", "r");
char buffer[4096] = { 0 };
fread(buffer, sizeof(buffer), 1, fp);
fclose(fp);
if(strcmp(buffer, "deny\n") != 0)
{
LOG("/proc/self/setgroups does not contain 'deny'\n");
return 1;
}
return 0;
}
int test_unshare_user_own_uid()
{
uid_t uid = getuid();
gid_t gid = getgid();
char uidstr[64];
snprintf(uidstr, sizeof(uidstr), "%u", uid);
char gidstr[64];
snprintf(gidstr, sizeof(gidstr), "%u", gid);
struct exile_policy *policy = exile_init_policy();
policy->namespace_options = EXILE_UNSHARE_USER;
policy->namespace_gid = gid;
policy->namespace_uid = uid;
xexile_enable_policy(policy);
if(do_test_nsuidmap("/proc/self/uid_map", uidstr, uidstr, "1") != 0)
{
LOG("/proc/self/uid_map failed\n");
return 1;
}
if(do_test_nsuidmap("/proc/self/gid_map", gidstr, gidstr, "1") != 0)
{
LOG("/proc/self/gid_map failed\n");
return 1;
}
FILE *fp = fopen("/proc/self/setgroups", "r");
char buffer[4096] = { 0 };
fread(buffer, sizeof(buffer), 1, fp);
fclose(fp);
if(strcmp(buffer, "deny\n") != 0)
{
LOG("/proc/self/setgroups does not contain 'deny'\n");
return 1;
}
return 0;
}
struct dispatcher
{
char *name;
@ -689,6 +829,10 @@ struct dispatcher dispatchers[] = {
{ "launch-get", &test_launch_get},
{ "vow_from_str", &test_vows_from_str},
{ "clone3_nosys", &test_clone3_nosys},
{ "unshare-user", &test_unshare_user},
{ "unshare-user-own-uid", &test_unshare_user_own_uid},
};
int main(int argc, char *argv[])

23
test.sh
View File

@ -8,41 +8,41 @@ COUNT_SUCCEEDED=0
COUNT_FAILED=0
COUNT_SKIPPED=0
function print_fail()
print_fail()
{
echo -e "${RED}$@${NC}" 1>&2
printf "${RED}$@${NC}\n" 1>&2
}
function print_success()
print_success()
{
echo -e "${GREEN}$@${NC}"
printf "${GREEN}$@${NC}\n"
}
function print_skipped()
print_skipped()
{
echo -e "${YELLOW}$@${NC}"
printf "${YELLOW}$@${NC}\n"
}
function runtest_fail()
runtest_fail()
{
print_fail "failed"
COUNT_FAILED=$(($COUNT_FAILED+1))
}
function runtest_success()
runtest_success()
{
print_success "ok"
COUNT_SUCCEEDED=$((COUNT_SUCCEEDED+1))
}
function runtest_skipped()
runtest_skipped()
{
print_skipped "skipped"
COUNT_SKIPPED=$((COUNT_SKIPPED+1))
}
function runtest()
runtest()
{
testbin="$1"
testname="$2"
@ -52,7 +52,8 @@ function runtest()
echo -n "Running $testname... "
#exit $? to suppress shell message like "./test.sh: line 18: pid Bad system call"
(./$testbin "$testname" || exit $?) &>> "${test_log_file}"
(./$testbin "$testname" || exit $?) >> "${test_log_file}" 2>&1
ret=$?
SUCCESS="no"
if [ $ret -eq 0 ] ; then