Introduce "no_fs" and "no_new_fd" options.

no_fs is a simple way to take away all
FS access, without constructing path_policies etc.

no_new_fd disallows opening any new
file descriptors
This commit is contained in:
Albert S. 2021-08-09 20:29:18 +02:00
bovenliggende 57238b535c
commit 4a4d551e75
2 gewijzigde bestanden met toevoegingen van 204 en 6 verwijderingen

142
qssb.h
Bestand weergeven

@ -29,6 +29,8 @@
#include <sys/mount.h>
#include <sys/prctl.h>
#include <sys/random.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
@ -158,6 +160,29 @@ static int default_blacklisted_syscals[] = {
-1
};
/* TODO: Check for completion
* Known blacklisting problem (catch up game, etc.)
*
* However, we use it to enhance "no_fs" policy, which does not solely rely
* on seccomp anyway */
static int fs_access_syscalls[] = {
QSSB_SYS(chdir),
QSSB_SYS(truncate),
QSSB_SYS(stat),
QSSB_SYS(flock),
QSSB_SYS(chmod),
QSSB_SYS(chown),
QSSB_SYS(setxattr),
QSSB_SYS(utime),
QSSB_SYS(ioctl),
QSSB_SYS(fcntl),
QSSB_SYS(access),
QSSB_SYS(open),
QSSB_SYS(openat),
QSSB_SYS(unlink),
-1
};
struct qssb_path_policy
{
const char *path;
@ -173,6 +198,8 @@ struct qssb_policy
int preserve_cwd;
int not_dumpable;
int no_new_privs;
int no_fs;
int no_new_fds;
int namespace_options;
/* Bind mounts all paths in path_policies into the chroot and applies
non-landlock policies */
@ -197,6 +224,8 @@ struct qssb_policy *qssb_init_policy()
result->drop_caps = 1;
result->not_dumpable = 1;
result->no_new_privs = 1;
result->no_fs = 0;
result->no_new_fds = 0;
result->namespace_options = QSSB_UNSHARE_MOUNT | QSSB_UNSHARE_USER;
result->chdir_path = NULL;
result->mount_path_policies_to_chroot = 0;
@ -737,16 +766,101 @@ static int check_policy_sanity(struct qssb_policy *policy)
}
}
if(policy->path_policies != NULL && policy->mount_path_policies_to_chroot != 1)
if(policy->path_policies != NULL)
{
#if HAVE_LANDLOCK != 1
QSSB_LOG_ERROR("Path policies cannot be enforced! System needs landlock support or set mount_path_policies_to_chroot = 1\n");
if(policy->mount_path_policies_to_chroot != 1)
{
#if HAVE_LANDLOCK != 1
QSSB_LOG_ERROR("Path policies cannot be enforced! System needs landlock support or set mount_path_policies_to_chroot = 1\n");
return -1;
#endif
}
if(policy->no_fs == 1)
{
QSSB_LOG_ERROR("If path_policies are specified, no_fs cannot be set to 1");
return -1;
#endif
}
}
return 0;
}
static void close_file_fds()
{
long max_files = sysconf(_SC_OPEN_MAX);
for(long i = 3; i <= max_files; i++)
{
close((int)i);
}
}
/* Takes away file system access from the process
*
* We use this when "no_fs" is given in the policy.
*
* This is useful for restricted subprocesses that do some computational work
* and do not require filesystem access
*
* @returns: 0 on success, < 0 on error
*/
static int enable_no_fs(struct qssb_policy *policy)
{
close_file_fds();
if(chdir("/proc/self/fdinfo") != 0)
{
QSSB_LOG_ERROR("Failed to change to safe directory: %s\n", strerror(errno));
return -1;
}
if(chroot(".") != 0)
{
QSSB_LOG_ERROR("Failed to chroot into safe directory: %s\n", strerror(errno));
return -1;
}
if(chdir("/") != 0)
{
QSSB_LOG_ERROR("Failed to chdir into safe directory inside chroot: %s\n", strerror(errno));
return -1;
}
if(policy->whitelisted_syscalls == NULL)
{
if(policy->blacklisted_syscalls == NULL)
{
policy->blacklisted_syscalls = fs_access_syscalls;
}
else
{
size_t nbl = 0;
size_t nfs = sizeof(fs_access_syscalls)/sizeof(fs_access_syscalls[0]);
int *tmp = policy->blacklisted_syscalls;
while(*tmp != -1)
{
++nbl;
++tmp;
}
size_t n = (nbl + nfs) + 1;
tmp = (int *) calloc(n, sizeof(int));
int *tmp_begin = tmp;
if(tmp == NULL)
{
QSSB_LOG_ERROR("Failed to expand blacklisted syscall array\n");
return -1;
}
memcpy(tmp, policy->blacklisted_syscalls, nbl*sizeof(int));
tmp+=nbl;
memcpy(tmp, fs_access_syscalls, nfs*sizeof(int));
tmp+=(nfs+1);
*tmp = -1;
policy->blacklisted_syscalls = tmp_begin;
}
}
return 0;
}
/* Enables the specified qssb_policy.
*
* This function is not atomic (and can't be). This means some
@ -827,7 +941,6 @@ int qssb_enable_policy(struct qssb_policy *policy)
}
}
#endif
if(policy->chdir_path == NULL)
{
policy->chdir_path = "/";
@ -839,6 +952,25 @@ int qssb_enable_policy(struct qssb_policy *policy)
return -1;
}
if(policy->no_fs)
{
if(enable_no_fs(policy) != 0)
{
QSSB_LOG_ERROR("Failed to take away filesystem access of process\n");
return -1;
}
}
if(policy->no_new_fds)
{
const struct rlimit nofile = {0, 0};
if (setrlimit(RLIMIT_NOFILE, &nofile) == -1)
{
QSSB_LOG_ERROR("setrlimit: Failed to set rlimit: %s\n", strerror(errno));
return -1;
}
}
if(policy->drop_caps)
{
if(drop_caps() < 0)

68
test.c
Bestand weergeven

@ -1,5 +1,10 @@
#include "qssb.h"
#include <stdbool.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/socket.h>
int test_default_main(int argc, char *argv[])
{
struct qssb_policy *policy = qssb_init_policy();
@ -69,6 +74,65 @@ int test_landlock_deny_write(int argc, char *argv[])
return 1;
}
int test_nofs(int argc, char *argv[])
{
struct qssb_policy *policy = qssb_init_policy();
policy->no_fs = 1;
int ret = qssb_enable_policy(policy);
if(ret != 0)
{
fprintf(stderr, "Failed to activate nofs sandbox\n");
return -1;
}
int s = socket(AF_INET,SOCK_STREAM,0);
if(s == -1)
{
fprintf(stderr, "Failed to open socket but this was not requested by policy\n");
return 0;
}
/* Expect seccomp to take care of this */
if(open("/test", O_CREAT | O_WRONLY) >= 0)
{
fprintf(stderr, "Failed: Do not expect write access\n");
return -1;
}
return 0;
}
int test_no_new_fds(int argc, char *argv[])
{
struct qssb_policy *policy = qssb_init_policy();
policy->no_new_fds = 1;
int ret = qssb_enable_policy(policy);
if(ret != 0)
{
fprintf(stderr, "Failed to activate no_new_fd sandbox\n");
return -1;
}
if(open("/tmp/test", O_CREAT | O_WRONLY) >= 0)
{
fprintf(stderr, "Failed: Could open new file descriptor\n");
return -1;
}
int s = socket(AF_INET,SOCK_STREAM,0);
if(s >= 0)
{
fprintf(stderr, "Failed: socket got opened but policy denied\n");
return -1;
}
return 0;
}
struct dispatcher
{
char *name;
@ -81,7 +145,9 @@ struct dispatcher dispatchers[] = {
{ "seccomp-blacklisted", &test_seccomp_blacklisted, false },
{ "seccomp-blacklisted-permitted", &test_seccomp_blacklisted_call_permitted, true },
{ "landlock", &test_landlock, true },
{ "landlock-deny-write", &test_landlock_deny_write, true }
{ "landlock-deny-write", &test_landlock_deny_write, true },
{ "no_fs", &test_nofs, false},
{ "no_new_fds", &test_no_new_fds, true}
};
int main(int argc, char *argv[])