diff options
-rw-r--r-- | configure.ac | 5 | ||||
-rw-r--r-- | etc/sandbox.conf | 20 | ||||
-rw-r--r-- | headers.h | 12 | ||||
-rw-r--r-- | src/Makefile.am | 1 | ||||
-rw-r--r-- | src/namespaces.c | 219 | ||||
-rw-r--r-- | src/options.c | 71 | ||||
-rw-r--r-- | src/sandbox.c | 2 | ||||
-rw-r--r-- | src/sandbox.h | 14 |
8 files changed, 343 insertions, 1 deletions
diff --git a/configure.ac b/configure.ac index 4c4cb20..843bb97 100644 --- a/configure.ac +++ b/configure.ac @@ -117,6 +117,7 @@ AC_CHECK_HEADERS_ONCE(m4_flatten([ memory.h pthread.h pwd.h + sched.h siginfo.h signal.h sigsegv.h @@ -132,10 +133,13 @@ AC_CHECK_HEADERS_ONCE(m4_flatten([ unistd.h utime.h sys/file.h + sys/ioctl.h sys/mman.h + sys/mount.h sys/param.h sys/ptrace.h sys/reg.h + sys/socket.h sys/stat.h sys/syscall.h sys/time.h @@ -225,6 +229,7 @@ AC_CHECK_FUNCS_ONCE(m4_flatten([ symlinkat truncate64 unlinkat + unshare utime utimensat utimes diff --git a/etc/sandbox.conf b/etc/sandbox.conf index 1d7655c..5f09ee4 100644 --- a/etc/sandbox.conf +++ b/etc/sandbox.conf @@ -29,6 +29,26 @@ # +# Namespace Section (Linux-only) +# + +# Global knob to control all namespaces. +#NAMESPACES_ENABLE="no" + +# Knobs for different types of namespaces. If the runtime doesn't support a +# particular type, it will be automatically skipped. Default to off as these +# are currently experimental. +# For more details on each type, see the namespaces(7) manpage. +#NAMESPACE_IPC_ENABLE="no" +#NAMESPACE_MNT_ENABLE="no" +#NAMESPACE_NET_ENABLE="no" +#NAMESPACE_PID_ENABLE="no" +#NAMESPACE_SYSV_ENABLE="no" +#NAMESPACE_USER_ENABLE="no" +#NAMESPACE_UTS_ENABLE="no" + + +# # ACCESS Section # @@ -53,6 +53,9 @@ #ifdef HAVE_PWD_H # include <pwd.h> #endif +#ifdef HAVE_SCHED_H +# include <sched.h> +#endif #ifdef HAVE_SIGINFO_H # include <siginfo.h> #endif @@ -96,11 +99,17 @@ #ifdef HAVE_SYS_FILE_H # include <sys/file.h> #endif +#ifdef HAVE_SYS_IOCTL_H +# include <sys/ioctl.h> +#endif #ifdef HAVE_SYS_MMAN_H # include <sys/mman.h> #else #error #endif +#ifdef HAVE_SYS_MOUNT_H +# include <sys/mount.h> +#endif #ifdef HAVE_SYS_PARAM_H # include <sys/param.h> #endif @@ -110,6 +119,9 @@ #ifdef HAVE_SYS_REG_H # include <sys/reg.h> #endif +#ifdef HAVE_SYS_SOCKET_H +# include <sys/socket.h> +#endif #ifdef HAVE_SYS_STAT_H # include <sys/stat.h> #endif diff --git a/src/Makefile.am b/src/Makefile.am index 24ffdcf..e7aeb30 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -11,6 +11,7 @@ AM_CPPFLAGS = \ sandbox_LDADD = $(top_builddir)/libsbutil/libsbutil.la $(LIBDL) sandbox_SOURCES = \ environ.c \ + namespaces.c \ options.c \ sandbox.h \ sandbox.c diff --git a/src/namespaces.c b/src/namespaces.c new file mode 100644 index 0000000..5be42f6 --- /dev/null +++ b/src/namespaces.c @@ -0,0 +1,219 @@ +/* + * Initialize various namespaces + * + * Copyright 1999-2015 Gentoo Foundation + * Licensed under the GPL-2 + */ + +#include "headers.h" +#include "sbutil.h" +#include "sandbox.h" + +#ifdef __linux__ + +#include <net/if.h> + +#ifndef HAVE_UNSHARE +# ifdef __NR_unshare +# define unshare(x) syscall(__NR_unshare, x) +# else +# define unshare(x) -1 +# endif +#endif + +#define xmount(...) sb_assert(mount(__VA_ARGS__) == 0) +#define xmkdir(...) sb_assert(mkdir(__VA_ARGS__) == 0) +#define xchmod(...) sb_assert(chmod(__VA_ARGS__) == 0) +#define xsymlink(...) sb_assert(symlink(__VA_ARGS__) == 0) + +#define xasprintf(fmt, ...) \ +({ \ + int _ret = asprintf(fmt, __VA_ARGS__); \ + if (_ret == 0) \ + sb_perr("asprintf(%s) failed", #fmt); \ + _ret; \ +}) +#define xfopen(path, ...) \ +({ \ + FILE *_ret = fopen(path, __VA_ARGS__); \ + if (_ret == 0) \ + sb_perr("fopen(%s) failed", #path); \ + _ret; \ +}) + +static void ns_user_switch(int uid, int gid, int nuid, int ngid) +{ +#ifdef CLONE_NEWUSER + FILE *fp; + char *map; + + if (uid == nuid || unshare(CLONE_NEWUSER)) + return; + + fp = xfopen("/proc/self/uid_map", "we"); + xasprintf(&map, "%i %i 1", nuid, uid); + fputs(map, fp); + fclose(fp); + free(map); + + fp = xfopen("/proc/self/setgroups", "we"); + fputs("deny", fp); + fclose(fp); + + fp = xfopen("/proc/self/gid_map", "we"); + xasprintf(&map, "%i %i 1\n", ngid, gid); + fputs(map, fp); + fclose(fp); + free(map); +#endif +} + +static void ns_net_setup(void) +{ +#ifdef CLONE_NEWNET + if (unshare(CLONE_NEWNET)) + return; + + int sock = socket(AF_LOCAL, SOCK_DGRAM|SOCK_CLOEXEC, 0); + struct ifreq ifr; + + strcpy(ifr.ifr_name, "lo"); + if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) + sb_perr("ioctl(SIOCGIFFLAGS, lo) failed"); + strcpy(ifr.ifr_name, "lo"); + ifr.ifr_flags |= IFF_UP | IFF_RUNNING; + if (ioctl(sock, SIOCSIFFLAGS, &ifr) < 0) + sb_perr("ioctl(SIOCSIFFLAGS, lo) failed"); +#endif +} + +/* Create a nice empty /dev for playing in. */ +static void ns_mount_setup(void) +{ +#ifdef CLONE_NEWNS + /* Create a new mount namespace. */ + if (unshare(CLONE_NEWNS)) + return; + + /* Mark the whole tree as private so we don't mess up the parent ns. */ + if (mount("none", "/", NULL, MS_PRIVATE | MS_REC, NULL)) + return; + + /* Create a unique /tmp dir for everyone. */ + if (mount("/tmp", "/tmp", "tmpfs", MS_NOSUID | MS_NODEV | MS_RELATIME, NULL)) + sb_ewarn("could not mount /tmp"); + + /* Mount an empty dir inside of /dev which we'll populate with bind mounts + * to the existing files in /dev. We can't just mknod ourselves because + * the kernel will deny those calls when we aren't actually root. We pick + * the /dev/shm dir as it should generally exist and we don't care about + * binding its contents. */ + if (mount("sandbox-dev", "/dev/shm", "tmpfs", MS_NOSUID | MS_NOEXEC | MS_RELATIME, "mode=0755")) + return; + + /* Now map in all the files/dirs we do want to expose. */ + int fd; +#define bind_file(node) \ + fd = open("/dev/shm/" node, O_CREAT, 0); \ + sb_assert(fd != -1); \ + close(fd); \ + xmount("/dev/" node, "/dev/shm/" node, NULL, MS_BIND, NULL) +#define bind_dir(node) \ + xmkdir("/dev/shm/" node, 0); \ + xmount("/dev/" node, "/dev/shm/" node, NULL, MS_BIND, NULL) + + bind_file("full"); + bind_file("null"); + bind_file("ptmx"); + bind_file("tty"); + bind_file("urandom"); + bind_file("zero"); + bind_dir("pts"); + + xmkdir("/dev/shm/shm", 01777); + xchmod("/dev/shm/shm", 01777); + + xsymlink("/proc/self/fd", "/dev/shm/fd"); + xsymlink("fd/0", "/dev/shm/stdin"); + xsymlink("fd/1", "/dev/shm/stdout"); + xsymlink("fd/2", "/dev/shm/stderr"); + + xchmod("/dev/shm", 0555); + + /* Now that the new root looks good, move it to /dev. */ + xmount("/dev/shm", "/dev", NULL, MS_MOVE, NULL); +#endif +} + +static pid_t ns_pid_setup(void) +{ + pid_t pid; + + if (unshare(CLONE_NEWPID) == 0) { + /* Create a child in the new pid ns. */ + pid = fork(); + if (pid == 0) { + /* Create a new mount namespace for the child. */ + sb_assert(unshare(CLONE_NEWNS) == 0); + xmount("none", "/proc", NULL, MS_PRIVATE | MS_REC, NULL); + xmount("proc", "/proc", "proc", MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_RELATIME, NULL); + } + } else { + /* At least hide other procs. */ + if (umount2("/proc", MNT_FORCE | MNT_DETACH) == 0) + xmount("proc", "/proc", "proc", MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_RELATIME, "hidepid=2"); + pid = fork(); + } + + return pid; +} + +pid_t setup_namespaces(void) +{ + /* We need to unshare namespaces independently anyways as users can + * configure kernels to have only some enabled, and if we try to do + * them all at once, we'll get EINVAL. */ + + int uid = getuid(); + int gid = getgid(); + pid_t pid; + + /* This comes first so we can do the others as non-root. */ + if (opt_use_ns_user) + ns_user_switch(uid, gid, 0, 0); + +#ifdef CLONE_NEWIPC + if (opt_use_ns_ipc) + unshare(CLONE_NEWIPC); +#endif +#ifdef CLONE_SYSVSEM + if (opt_use_ns_sysv) + unshare(CLONE_SYSVSEM); +#endif + +#ifdef CLONE_NEWUTS + if (opt_use_ns_uts && unshare(CLONE_NEWUTS) == 0) { + const char name[] = "gentoo-sandbox"; + if (sethostname(name, sizeof(name) - 1)) + /* silence gcc warning */; + } +#endif + + if (opt_use_ns_net) + ns_net_setup(); + + if (opt_use_ns_mnt) + ns_mount_setup(); + + if (opt_use_ns_mnt && opt_use_ns_pid) + pid = ns_pid_setup(); + else + pid = fork(); + + if (opt_use_ns_user) + ns_user_switch(0, 0, uid, gid); + + return pid; +} + +#endif diff --git a/src/options.c b/src/options.c index 10f937c..295ee75 100644 --- a/src/options.c +++ b/src/options.c @@ -9,6 +9,43 @@ #include "sbutil.h" #include "sandbox.h" +/* Setting to -1 will load defaults from the config file. */ +int opt_use_namespaces = -1; +int opt_use_ns_ipc = -1; +int opt_use_ns_mnt = -1; +int opt_use_ns_net = -1; +int opt_use_ns_pid = -1; +int opt_use_ns_sysv = -1; +int opt_use_ns_user = -1; +int opt_use_ns_uts = -1; + +static const struct { + const char *name; + int *opt; + int default_val; +} config_opts[] = { + /* Default these to off until they can get more testing. */ + { "NAMESPACES_ENABLE", &opt_use_namespaces, false, }, + { "NAMESPACE_IPC_ENABLE", &opt_use_ns_ipc, false, }, + { "NAMESPACE_MNT_ENABLE", &opt_use_ns_mnt, false, }, + { "NAMESPACE_NET_ENABLE", &opt_use_ns_net, false, }, + { "NAMESPACE_PID_ENABLE", &opt_use_ns_pid, false, }, + { "NAMESPACE_SYSV_ENABLE", &opt_use_ns_sysv, false, }, + { "NAMESPACE_USER_ENABLE", &opt_use_ns_user, false, }, + { "NAMESPACE_UTS_ENABLE", &opt_use_ns_uts, false, }, +}; + +static void read_config(void) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(config_opts); ++i) { + int *opt = config_opts[i].opt; + if (*opt == -1) + *opt = sb_get_cnf_bool(config_opts[i].name, config_opts[i].default_val); + } +} + static void show_version(void) { puts( @@ -36,11 +73,43 @@ static void show_version(void) #define PARSE_FLAGS "+hV" #define a_argument required_argument static struct option const long_opts[] = { + {"ns-on", no_argument, &opt_use_namespaces, true}, + {"ns-off", no_argument, &opt_use_namespaces, false}, + {"ns-ipc-on", no_argument, &opt_use_ns_ipc, true}, + {"ns-ipc-off", no_argument, &opt_use_ns_ipc, false}, + {"ns-mnt-on", no_argument, &opt_use_ns_mnt, true}, + {"ns-mnt-off", no_argument, &opt_use_ns_mnt, false}, + {"ns-net-on", no_argument, &opt_use_ns_net, true}, + {"ns-net-off", no_argument, &opt_use_ns_net, false}, + {"ns-pid-on", no_argument, &opt_use_ns_pid, true}, + {"ns-pid-off", no_argument, &opt_use_ns_pid, false}, + {"ns-sysv-on", no_argument, &opt_use_ns_sysv, true}, + {"ns-sysv-off", no_argument, &opt_use_ns_sysv, false}, + {"ns-user-on", no_argument, &opt_use_ns_user, true}, + {"ns-user-off", no_argument, &opt_use_ns_user, false}, + {"ns-uts-on", no_argument, &opt_use_ns_uts, true}, + {"ns-uts-off", no_argument, &opt_use_ns_uts, false}, {"help", no_argument, NULL, 'h'}, {"version", no_argument, NULL, 'V'}, {NULL, no_argument, NULL, 0x0} }; static const char * const opts_help[] = { + "Enable the use of namespaces", + "Disable the use of namespaces", + "Enable the use of IPC (and System V) namespaces", + "Disable the use of IPC (and System V) namespaces", + "Enable the use of mount namespaces", + "Disable the use of mount namespaces", + "Enable the use of network namespaces", + "Disable the use of network namespaces", + "Enable the use of process (pid) namespaces", + "Disable the use of process (pid) namespaces", + "Enable the use of System V namespaces", + "Disable the use of System V namespaces", + "Enable the use of user namespaces", + "Disable the use of user namespaces", + "Enable the use of UTS (hostname/uname) namespaces", + "Disable the use of UTS (hostname/uname) namespaces", "Print this help and exit", "Print version and exit", NULL @@ -113,4 +182,6 @@ void parseargs(int argc, char *argv[]) show_usage(1); } } + + read_config(); } diff --git a/src/sandbox.c b/src/sandbox.c index 15c87b2..c668ab6 100644 --- a/src/sandbox.c +++ b/src/sandbox.c @@ -160,7 +160,7 @@ static int spawn_shell(char *argv_bash[], char **env, int debug) int status = 0; int ret = 0; - child_pid = fork(); + child_pid = opt_use_namespaces ? setup_namespaces() : fork(); /* Child's process */ if (0 == child_pid) { diff --git a/src/sandbox.h b/src/sandbox.h index 4233bd6..303dac4 100644 --- a/src/sandbox.h +++ b/src/sandbox.h @@ -28,6 +28,12 @@ extern char **setup_environ(struct sandbox_info_t *sandbox_info); extern bool sb_get_cnf_bool(const char *, bool); +#ifdef __linux__ +extern pid_t setup_namespaces(void); +#else +#define setup_namespaces() fork() +#endif + #define sb_warn(fmt, args...) fprintf(stderr, "%s:%s " fmt "\n", "sandbox", __func__, ## args) #define sb_pwarn(fmt, args...) sb_warn(fmt ": %s\n", ## args, strerror(errno)) #define _sb_err(func, fmt, args...) do { sb_##func(fmt, ## args); exit(EXIT_FAILURE); } while (0) @@ -36,5 +42,13 @@ extern bool sb_get_cnf_bool(const char *, bool); /* Option parsing related code */ extern void parseargs(int argc, char *argv[]); +extern int opt_use_namespaces; +extern int opt_use_ns_ipc; +extern int opt_use_ns_mnt; +extern int opt_use_ns_net; +extern int opt_use_ns_pid; +extern int opt_use_ns_sysv; +extern int opt_use_ns_user; +extern int opt_use_ns_uts; #endif |