Σύγκριση υποβολών

7 Υποβολές

Συγγραφέας SHA1 Μήνυμα Ημερομηνία
4cfdead5d0 no_fs: Use landlock if possible
This is not 100% the same, but good enough and more importantly,
does not require unsharing user/mount namespace and the chroot call.
2024-05-26 20:12:20 +02:00
bbc8193ea9 Handle newer landlock ABI versions for filesystem isolation 2024-05-26 20:03:20 +02:00
c9fdeb4a1d enter_namespaces(): Add missing newline at error messages 2024-05-26 19:31:14 +02:00
3732524bfa exile_init_policy(): Don't unshare network namespaces by default
This no longer works on some distros (e. g. Ubuntu 24.04) which
move (back) to restrict unprivileged user namespaces, and is
not required when Landlock is available, which is more and more
a given, thankfully.
2024-05-26 19:28:02 +02:00
4059c1a093 landlock_prepare_ruleset(): zero-init landlock structs
'landlock_ruleset_attr' used to only have a single member. Meanwhile,
depending on linux/headers version, others may be present. So zero-init
the struct, as otherwise we might get 'Invalid argument' return codes,
as those we do not explicitly initialize might contain garbage values.
2024-05-24 13:25:10 +02:00
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
3 αρχεία άλλαξαν με 149 προσθήκες και 37 διαγραφές

135
exile.c

@ -621,10 +621,12 @@ struct exile_policy *exile_init_policy()
{ {
return NULL; return NULL;
} }
result->drop_caps = 1; result->drop_caps = 0;
result->not_dumpable = 1; result->not_dumpable = 1;
result->no_new_privs = 1; result->no_new_privs = 1;
result->namespace_options = EXILE_UNSHARE_MOUNT | EXILE_UNSHARE_USER; result->namespace_options = EXILE_UNSHARE_AUTOMATIC;
result->namespace_uid = 0;
result->namespace_gid = 0;
return result; return result;
} }
@ -938,7 +940,7 @@ void exile_free_policy(struct exile_policy *ctxt)
} }
/* Enters the specified namespaces */ /* 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) if(namespace_options & EXILE_UNSHARE_USER)
{ {
@ -957,7 +959,7 @@ static int enter_namespaces(int namespace_options)
int fd = open("/proc/self/setgroups", O_WRONLY); int fd = open("/proc/self/setgroups", O_WRONLY);
if(fd == -1) if(fd == -1)
{ {
EXILE_LOG_ERROR("Failed to open /proc/self/setgroups for writing"); EXILE_LOG_ERROR("Failed to open /proc/self/setgroups for writing\n");
return -1; return -1;
} }
int writesize = snprintf(buf, sizeof(buf), "deny"); int writesize = snprintf(buf, sizeof(buf), "deny");
@ -972,10 +974,10 @@ static int enter_namespaces(int namespace_options)
fd = open("/proc/self/uid_map", O_WRONLY); fd = open("/proc/self/uid_map", O_WRONLY);
if(fd == -1) if(fd == -1)
{ {
EXILE_LOG_ERROR("Failed to open /proc/self/uid_map for writing"); EXILE_LOG_ERROR("Failed to open /proc/self/uid_map for writing\n");
return -1; return -1;
} }
writesize = snprintf(buf, sizeof(buf), "0 %u 1\n", current_uid); writesize = snprintf(buf, sizeof(buf), "%u %u 1\n", namespace_uid, current_uid);
writeret = write(fd, buf, writesize); writeret = write(fd, buf, writesize);
if(writeret < 0 || writeret < writesize) if(writeret < 0 || writeret < writesize)
{ {
@ -988,10 +990,10 @@ static int enter_namespaces(int namespace_options)
fd = open("/proc/self/gid_map", O_WRONLY); fd = open("/proc/self/gid_map", O_WRONLY);
if(fd == -1) if(fd == -1)
{ {
EXILE_LOG_ERROR("Failed to open /proc/self/gid_map for writing"); EXILE_LOG_ERROR("Failed to open /proc/self/gid_map for writing\n");
return -1; return -1;
} }
writesize = snprintf(buf, sizeof(buf), "0 %u 1\n", current_gid); writesize = snprintf(buf, sizeof(buf), "%u %u 1\n", namespace_gid, current_gid);
writeret = write(fd, buf, writesize); writeret = write(fd, buf, writesize);
if(writeret < 0 || writeret < writesize) if(writeret < 0 || writeret < writesize)
{ {
@ -1227,6 +1229,9 @@ static unsigned int exile_flags_to_landlock(unsigned int flags, int statmode)
if(flags & EXILE_FS_ALLOW_ALL_WRITE) if(flags & EXILE_FS_ALLOW_ALL_WRITE)
{ {
result |= LANDLOCK_ACCESS_FS_WRITE_FILE; result |= LANDLOCK_ACCESS_FS_WRITE_FILE;
#ifdef LANDLOCK_ACCESS_FS_TRUNCATE
result |= LANDLOCK_ACCESS_FS_TRUNCATE;
#endif
if(S_ISDIR(statmode)) if(S_ISDIR(statmode))
{ {
result |= LANDLOCK_ACCESS_FS_REMOVE_DIR; result |= LANDLOCK_ACCESS_FS_REMOVE_DIR;
@ -1236,6 +1241,9 @@ static unsigned int exile_flags_to_landlock(unsigned int flags, int statmode)
result |= LANDLOCK_ACCESS_FS_MAKE_REG; result |= LANDLOCK_ACCESS_FS_MAKE_REG;
result |= LANDLOCK_ACCESS_FS_MAKE_SOCK; result |= LANDLOCK_ACCESS_FS_MAKE_SOCK;
result |= LANDLOCK_ACCESS_FS_MAKE_SYM; result |= LANDLOCK_ACCESS_FS_MAKE_SYM;
#ifdef LANDLOCK_ACCESS_FS_REFER
result |= LANDLOCK_ACCESS_FS_REFER;
#endif
} }
} }
if(flags & EXILE_FS_ALLOW_EXEC) if(flags & EXILE_FS_ALLOW_EXEC)
@ -1302,15 +1310,42 @@ static unsigned int exile_flags_to_landlock(unsigned int flags, int statmode)
return result; return result;
} }
/* Sets maximum values for the handled access fs... */
static int landlock_set_max_handled_access(struct landlock_ruleset_attr *ruleset)
{
int abi = landlock_create_ruleset(NULL, 0,
LANDLOCK_CREATE_RULESET_VERSION);
if(abi < 0)
{
EXILE_LOG_ERROR("Can't determine landlock ABI version\n");
return -1;
}
ruleset->handled_access_net = 0;
if(abi == 1)
{
ruleset->handled_access_fs = ((LANDLOCK_ACCESS_FS_MAKE_SYM << 1) - 1);
}
if(abi == 2)
{
ruleset->handled_access_fs = ((LANDLOCK_ACCESS_FS_REFER << 1) - 1);
}
if(abi >= 3)
{
ruleset->handled_access_fs = ((LANDLOCK_ACCESS_FS_TRUNCATE << 1) - 1);
/* TODO: think about net */
}
return 0;
}
static int landlock_prepare_ruleset(struct exile_path_policy *policies) static int landlock_prepare_ruleset(struct exile_path_policy *policies)
{ {
int ruleset_fd = -1; int ruleset_fd = -1;
struct landlock_ruleset_attr ruleset_attr; struct landlock_ruleset_attr ruleset_attr = {0};
/* We here want the maximum possible ruleset, so set the var to the max possible bitmask. if(landlock_set_max_handled_access(&ruleset_attr) != 0)
Stolen/Adapted from: [linux src]/security/landlock/limits.h {
*/ return -1;
ruleset_attr.handled_access_fs = ((LANDLOCK_ACCESS_FS_MAKE_SYM << 1) - 1); }
ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
if (ruleset_fd < 0) if (ruleset_fd < 0)
{ {
@ -1320,7 +1355,7 @@ static int landlock_prepare_ruleset(struct exile_path_policy *policies)
struct exile_path_policy *policy = policies; struct exile_path_policy *policy = policies;
while(policy != NULL) while(policy != NULL)
{ {
struct landlock_path_beneath_attr path_beneath; struct landlock_path_beneath_attr path_beneath = {0};
path_beneath.parent_fd = open(policy->path, O_PATH | O_CLOEXEC); path_beneath.parent_fd = open(policy->path, O_PATH | O_CLOEXEC);
if(path_beneath.parent_fd < 0) if(path_beneath.parent_fd < 0)
{ {
@ -1337,6 +1372,13 @@ static int landlock_prepare_ruleset(struct exile_path_policy *policies)
return ret; return ret;
} }
path_beneath.allowed_access = exile_flags_to_landlock(policy->policy, sb.st_mode); path_beneath.allowed_access = exile_flags_to_landlock(policy->policy, sb.st_mode);
/* Required, so the .allowed_access fits .handled_access_fs of the ruleset.
* Needed for backwards compatibility, e. g. new binary compiled with new headers,
executed on a kernel with an older ABI version which does not have some constant defined...
*/
path_beneath.allowed_access &= ruleset_attr.handled_access_fs;
ret = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, &path_beneath, 0); ret = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, &path_beneath, 0);
if(ret) if(ret)
{ {
@ -1490,6 +1532,30 @@ static int enable_no_fs(struct exile_policy *policy)
{ {
close_file_fds(); close_file_fds();
if(exile_landlock_is_available())
{
struct landlock_ruleset_attr ruleset_attr = {0};
if(landlock_set_max_handled_access(&ruleset_attr) != 0)
{
return -1;
}
int ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
if (ruleset_fd < 0)
{
EXILE_LOG_ERROR("Failed to create landlock ruleset\n");
return -1;
}
int ret = landlock_restrict_self(ruleset_fd, 0);
if(ret != 0)
{
EXILE_LOG_ERROR("Failed to enable no_fs with landlock: %s\n", strerror(errno));
close(ruleset_fd);
return -1;
}
close(ret);
return 0;
}
if(chdir("/proc/self/fdinfo") != 0) if(chdir("/proc/self/fdinfo") != 0)
{ {
EXILE_LOG_ERROR("Failed to change to safe directory: %s\n", strerror(errno)); EXILE_LOG_ERROR("Failed to change to safe directory: %s\n", strerror(errno));
@ -1541,7 +1607,7 @@ int exile_enable_policy(struct exile_policy *policy)
close_file_fds(); 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"); EXILE_LOG_ERROR("Error while trying to enter namespaces\n");
return -1; return -1;
@ -1624,14 +1690,6 @@ int exile_enable_policy(struct exile_policy *policy)
} }
#endif #endif
if(policy->no_fs)
{
if(enable_no_fs(policy) != 0)
{
EXILE_LOG_ERROR("Failed to take away filesystem access of process\n");
return -1;
}
}
if(policy->no_new_fds) if(policy->no_new_fds)
{ {
@ -1643,15 +1701,6 @@ int exile_enable_policy(struct exile_policy *policy)
} }
} }
if(policy->drop_caps)
{
if(drop_caps() < 0)
{
EXILE_LOG_ERROR("failed to drop capabilities\n");
return -1;
}
}
if(policy->not_dumpable) if(policy->not_dumpable)
{ {
if(prctl(PR_SET_DUMPABLE, 0) == -1) if(prctl(PR_SET_DUMPABLE, 0) == -1)
@ -1670,6 +1719,15 @@ int exile_enable_policy(struct exile_policy *policy)
} }
} }
if(policy->no_fs)
{
if(enable_no_fs(policy) != 0)
{
EXILE_LOG_ERROR("Failed to take away filesystem access of process\n");
return -1;
}
}
#if HAVE_LANDLOCK == 1 #if HAVE_LANDLOCK == 1
if (can_use_landlock && policy->path_policies != NULL && landlock_restrict_self(landlock_ruleset_fd, 0) != 0) if (can_use_landlock && policy->path_policies != NULL && landlock_restrict_self(landlock_ruleset_fd, 0) != 0)
{ {
@ -1690,12 +1748,19 @@ int exile_enable_policy(struct exile_policy *policy)
} }
} }
if(policy->drop_caps)
{
if(drop_caps() < 0)
{
EXILE_LOG_ERROR("failed to drop capabilities\n");
return -1;
}
}
if(policy->syscall_policies != NULL) if(policy->syscall_policies != NULL)
{ {
return exile_enable_syscall_policy(policy); return exile_enable_syscall_policy(policy);
} }
return 0; return 0;
} }

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

48
test.c

@ -618,9 +618,9 @@ int test_launch_get()
size_t n = 0; size_t n = 0;
char *content = exile_launch_get(&params, &n); char *content = exile_launch_get(&params, &n);
unsigned int len = strlen(LAUNCH_GET_TEST_STR); 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; return 1;
} }
if(strcmp(content, LAUNCH_GET_TEST_STR) != 0) if(strcmp(content, LAUNCH_GET_TEST_STR) != 0)
@ -755,8 +755,50 @@ int test_unshare_user()
} }
return 0; 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 struct dispatcher
@ -788,6 +830,8 @@ struct dispatcher dispatchers[] = {
{ "vow_from_str", &test_vows_from_str}, { "vow_from_str", &test_vows_from_str},
{ "clone3_nosys", &test_clone3_nosys}, { "clone3_nosys", &test_clone3_nosys},
{ "unshare-user", &test_unshare_user}, { "unshare-user", &test_unshare_user},
{ "unshare-user-own-uid", &test_unshare_user_own_uid},
}; };