chiark / gitweb /
nspawn: bind mount /dev/nul to /proc/kmsg, so that the container can't muck with...
[elogind.git] / src / nspawn.c
index bead9eea2b67a8042e90ee76ad879e1cae915bde..685b4d4e50d4b5c588ebd8374d028b2cb83b046b 100644 (file)
@@ -6,16 +6,16 @@
   Copyright 2010 Lennart Poettering
 
   systemd is free software; you can redistribute it and/or modify it
-  under the terms of the GNU General Public License as published by
-  the Free Software Foundation; either version 2 of the License, or
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
   (at your option) any later version.
 
   systemd is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-  General Public License for more details.
+  Lesser General Public License for more details.
 
-  You should have received a copy of the GNU General Public License
+  You should have received a copy of the GNU Lesser General Public License
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
 #include <termios.h>
 #include <sys/signalfd.h>
 #include <grp.h>
+#include <linux/fs.h>
+
+#include <systemd/sd-daemon.h>
 
 #include "log.h"
 #include "util.h"
+#include "mkdir.h"
+#include "audit.h"
 #include "missing.h"
 #include "cgroup-util.h"
-#include "sd-daemon.h"
 #include "strv.h"
+#include "loopback-setup.h"
 
 static char *arg_directory = NULL;
 static char *arg_user = NULL;
+static char **arg_controllers = NULL;
+static bool arg_private_network = false;
 
 static int help(void) {
 
         printf("%s [OPTIONS...] [PATH] [ARGUMENTS...]\n\n"
                "Spawn a minimal namespace container for debugging, testing and building.\n\n"
-               "  -h --help            Show this help\n"
-               "  -D --directory=NAME  Root directory for the container\n"
-               "  -u --user=USER       Run the command under specified user or uid\n",
+               "  -h --help             Show this help\n"
+               "  -D --directory=NAME   Root directory for the container\n"
+               "  -u --user=USER        Run the command under specified user or uid\n"
+               "  -C --controllers=LIST Put the container in specified comma-separated cgroup hierarchies\n"
+               "     --private-network  Disable network in container\n",
                program_invocation_short_name);
 
         return 0;
@@ -62,11 +71,17 @@ static int help(void) {
 
 static int parse_argv(int argc, char *argv[]) {
 
+        enum {
+                ARG_PRIVATE_NETWORK = 0x100
+        };
+
         static const struct option options[] = {
-                { "help",      no_argument,       NULL, 'h' },
-                { "directory", required_argument, NULL, 'D' },
-                { "user",      optional_argument, NULL, 'u' },
-                { NULL,        0,                 NULL, 0   }
+                { "help",            no_argument,       NULL, 'h'                 },
+                { "directory",       required_argument, NULL, 'D'                 },
+                { "user",            required_argument, NULL, 'u'                 },
+                { "controllers",     required_argument, NULL, 'C'                 },
+                { "private-network", no_argument,       NULL, ARG_PRIVATE_NETWORK },
+                { NULL,              0,                 NULL, 0                   }
         };
 
         int c;
@@ -74,7 +89,7 @@ static int parse_argv(int argc, char *argv[]) {
         assert(argc >= 0);
         assert(argv);
 
-        while ((c = getopt_long(argc, argv, "+hD:u:", options, NULL)) >= 0) {
+        while ((c = getopt_long(argc, argv, "+hD:u:C:", options, NULL)) >= 0) {
 
                 switch (c) {
 
@@ -100,6 +115,21 @@ static int parse_argv(int argc, char *argv[]) {
 
                         break;
 
+                case 'C':
+                        strv_free(arg_controllers);
+                        arg_controllers = strv_split(optarg, ",");
+                        if (!arg_controllers) {
+                                log_error("Failed to split controllers list.");
+                                return -ENOMEM;
+                        }
+                        strv_uniq(arg_controllers);
+
+                        break;
+
+                case ARG_PRIVATE_NETWORK:
+                        arg_private_network = true;
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -124,15 +154,17 @@ static int mount_all(const char *dest) {
         } MountPoint;
 
         static const MountPoint mount_table[] = {
-                { "proc",      "/proc",     "proc",      NULL,        MS_NOSUID|MS_NOEXEC|MS_NODEV, true },
-                { "/proc/sys", "/proc/sys", "bind",      NULL,        MS_BIND, true },                      /* Bind mount first */
-                { "/proc/sys", "/proc/sys", "bind",      NULL,        MS_BIND|MS_RDONLY|MS_REMOUNT, true }, /* Then, make it r/o */
-                { "sysfs",     "/sys",      "sysfs",     NULL,        MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_RDONLY, true },
-                { "tmpfs",     "/dev",      "tmpfs",     "mode=755",  MS_NOSUID, true },
-                { "/dev/pts",  "/dev/pts",  "bind",      NULL,        MS_BIND, true },
-                { "tmpfs",     "/run",      "tmpfs",     "mode=755",  MS_NOSUID|MS_NODEV, true },
+                { "proc",      "/proc",     "proc",  NULL,       MS_NOSUID|MS_NOEXEC|MS_NODEV, true  },
+                { "/proc/sys", "/proc/sys", "bind",  NULL,       MS_BIND, true                       },   /* Bind mount first */
+                { "/proc/sys", "/proc/sys", "bind",  NULL,       MS_BIND|MS_RDONLY|MS_REMOUNT, true  },   /* Then, make it r/o */
+                { "/sys",      "/sys",      "bind",  NULL,       MS_BIND,                      true  },   /* Bind mount first */
+                { "/sys",      "/sys",      "bind",  NULL,       MS_BIND|MS_RDONLY|MS_REMOUNT, true  },   /* Then, make it r/o */
+                { "tmpfs",     "/dev",      "tmpfs", "mode=755", MS_NOSUID|MS_STRICTATIME,     true  },
+                { "/dev/pts",  "/dev/pts",  "bind",  NULL,       MS_BIND,                      true  },
+                { "tmpfs",     "/run",      "tmpfs", "mode=755", MS_NOSUID|MS_NODEV|MS_STRICTATIME, true  },
 #ifdef HAVE_SELINUX
-                { "selinux",   "/selinux",  "selinuxfs", NULL,        MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_RDONLY, false },
+                { "/sys/fs/selinux", "/sys/fs/selinux", "bind", NULL, MS_BIND,                      false },  /* Bind mount first */
+                { "/sys/fs/selinux", "/sys/fs/selinux", "bind", NULL, MS_BIND|MS_RDONLY|MS_REMOUNT, false },  /* Then, make it r/o */
 #endif
         };
 
@@ -152,7 +184,8 @@ static int mount_all(const char *dest) {
                         break;
                 }
 
-                if ((t = path_is_mount_point(where)) < 0) {
+                t = path_is_mount_point(where, false);
+                if (t < 0) {
                         log_error("Failed to detect whether %s is a mount point: %s", where, strerror(-t));
                         free(where);
 
@@ -181,9 +214,24 @@ static int mount_all(const char *dest) {
         }
 
         /* Fix the timezone, if possible */
-        if (asprintf(&where, "%s/%s", dest, "/etc/localtime") >= 0) {
-                mount("/etc/localtime", where, "bind", MS_BIND, NULL);
-                mount("/etc/localtime", where, "bind", MS_BIND|MS_REMOUNT|MS_RDONLY, NULL);
+        if (asprintf(&where, "%s/etc/localtime", dest) >= 0) {
+
+                if (mount("/etc/localtime", where, "bind", MS_BIND, NULL) >= 0)
+                        mount("/etc/localtime", where, "bind", MS_BIND|MS_REMOUNT|MS_RDONLY, NULL);
+
+                free(where);
+        }
+
+        if (asprintf(&where, "%s/etc/timezone", dest) >= 0) {
+
+                if (mount("/etc/timezone", where, "bind", MS_BIND, NULL) >= 0)
+                        mount("/etc/timezone", where, "bind", MS_BIND|MS_REMOUNT|MS_RDONLY, NULL);
+
+                free(where);
+        }
+
+        if (asprintf(&where, "%s/proc/kmsg", dest) >= 0) {
+                mount("/dev/null", where, "bind", MS_BIND, NULL);
                 free(where);
         }
 
@@ -312,7 +360,6 @@ static int copy_devnodes(const char *dest, const char *console) {
         }
 
 finish:
-
         umask(u);
 
         return r;
@@ -345,7 +392,7 @@ static int drop_capabilities(void) {
 
         unsigned long l;
 
-        for (l = 0; l <= MAX(63LU, (unsigned long) CAP_LAST_CAP); l++) {
+        for (l = 0; l <= cap_last_cap(); l++) {
                 unsigned i;
 
                 for (i = 0; i < ELEMENTSOF(retain); i++)
@@ -356,12 +403,6 @@ static int drop_capabilities(void) {
                         continue;
 
                 if (prctl(PR_CAPBSET_DROP, l) < 0) {
-
-                        /* If this capability is not known, EINVAL
-                         * will be returned, let's ignore this. */
-                        if (errno == EINVAL)
-                                break;
-
                         log_error("PR_CAPBSET_DROP failed: %m");
                         return -errno;
                 }
@@ -384,11 +425,9 @@ static int is_os_tree(const char *path) {
         return r < 0 ? 0 : 1;
 }
 
-#define BUFFER_SIZE 1024
-
 static int process_pty(int master, sigset_t *mask) {
 
-        char in_buffer[BUFFER_SIZE], out_buffer[BUFFER_SIZE];
+        char in_buffer[LINE_MAX], out_buffer[LINE_MAX];
         size_t in_buffer_full = 0, out_buffer_full = 0;
         struct epoll_event stdin_ev, stdout_ev, master_ev, signal_ev;
         bool stdin_readable = false, stdout_writable = false, master_readable = false, master_writable = false;
@@ -509,9 +548,9 @@ static int process_pty(int master, sigset_t *mask) {
                        (master_readable && out_buffer_full <= 0) ||
                        (stdout_writable && out_buffer_full > 0)) {
 
-                        if (stdin_readable && in_buffer_full < BUFFER_SIZE) {
+                        if (stdin_readable && in_buffer_full < LINE_MAX) {
 
-                                if ((k = read(STDIN_FILENO, in_buffer + in_buffer_full, BUFFER_SIZE - in_buffer_full)) < 0) {
+                                if ((k = read(STDIN_FILENO, in_buffer + in_buffer_full, LINE_MAX - in_buffer_full)) < 0) {
 
                                         if (errno == EAGAIN || errno == EPIPE || errno == ECONNRESET || errno == EIO)
                                                 stdin_readable = false;
@@ -543,9 +582,9 @@ static int process_pty(int master, sigset_t *mask) {
                                 }
                         }
 
-                        if (master_readable && out_buffer_full < BUFFER_SIZE) {
+                        if (master_readable && out_buffer_full < LINE_MAX) {
 
-                                if ((k = read(master, out_buffer + out_buffer_full, BUFFER_SIZE - out_buffer_full)) < 0) {
+                                if ((k = read(master, out_buffer + out_buffer_full, LINE_MAX - out_buffer_full)) < 0) {
 
                                         if (errno == EAGAIN || errno == EPIPE || errno == ECONNRESET || errno == EIO)
                                                 master_readable = false;
@@ -593,6 +632,7 @@ int main(int argc, char *argv[]) {
         pid_t pid = 0;
         int r = EXIT_FAILURE, k;
         char *oldcg = NULL, *newcg = NULL;
+        char **controller = NULL;
         int master = -1;
         const char *console = NULL;
         struct termios saved_attr, raw_attr;
@@ -652,11 +692,18 @@ int main(int argc, char *argv[]) {
                 goto finish;
         }
 
-        if ((k = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, newcg, 0)) < 0)  {
+        k = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, newcg, 0);
+        if (k < 0)  {
                 log_error("Failed to create cgroup: %s", strerror(-k));
                 goto finish;
         }
 
+        STRV_FOREACH(controller,arg_controllers) {
+                k = cg_create_and_attach(*controller, newcg, 0);
+                if (k < 0)
+                        log_warning("Failed to create cgroup in controller %s: %s", *controller, strerror(-k));
+        }
+
         if ((master = posix_openpt(O_RDWR|O_NOCTTY|O_CLOEXEC|O_NDELAY)) < 0) {
                 log_error("Failed to acquire pseudo tty: %m");
                 goto finish;
@@ -697,8 +744,13 @@ int main(int argc, char *argv[]) {
         sigset_add_many(&mask, SIGCHLD, SIGWINCH, SIGTERM, SIGINT, -1);
         assert_se(sigprocmask(SIG_BLOCK, &mask, NULL) == 0);
 
-        if ((pid = syscall(__NR_clone, SIGCHLD|CLONE_NEWIPC|CLONE_NEWNS|CLONE_NEWPID|CLONE_NEWUTS, NULL)) < 0) {
-                log_error("clone() failed: %m");
+        pid = syscall(__NR_clone, SIGCHLD|CLONE_NEWIPC|CLONE_NEWNS|CLONE_NEWPID|CLONE_NEWUTS|(arg_private_network ? CLONE_NEWNET : 0), NULL);
+        if (pid < 0) {
+                if (errno == EINVAL)
+                        log_error("clone() failed, do you have namespace support enabled in your kernel? (You need UTS, IPC, PID and NET namespacing built in): %m");
+                else
+                        log_error("clone() failed: %m");
+
                 goto finish;
         }
 
@@ -711,6 +763,7 @@ int main(int argc, char *argv[]) {
                 gid_t gid = (gid_t) -1;
                 const char *envp[] = {
                         "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
+                        "container=systemd-nspawn", /* LXC sets container=lxc, so follow the scheme here */
                         NULL, /* TERM */
                         NULL, /* HOME */
                         NULL, /* USER */
@@ -718,7 +771,7 @@ int main(int argc, char *argv[]) {
                         NULL
                 };
 
-                envp[1] = strv_find_prefix(environ, "TERM=");
+                envp[2] = strv_find_prefix(environ, "TERM=");
 
                 close_nointr_nofail(master);
 
@@ -739,6 +792,10 @@ int main(int argc, char *argv[]) {
                 if (prctl(PR_SET_PDEATHSIG, SIGKILL) < 0)
                         goto child_fail;
 
+                /* Mark / as private, in case somebody marked it shared */
+                if (mount(NULL, "/", NULL, MS_PRIVATE|MS_REC, NULL) < 0)
+                        goto child_fail;
+
                 if (mount_all(arg_directory) < 0)
                         goto child_fail;
 
@@ -770,7 +827,9 @@ int main(int argc, char *argv[]) {
                         goto child_fail;
                 }
 
-                umask(0002);
+                umask(0022);
+
+                loopback_setup();
 
                 if (drop_capabilities() < 0)
                         goto child_fail;
@@ -797,21 +856,21 @@ int main(int argc, char *argv[]) {
                                 goto child_fail;
                         }
 
-                        if (setregid(gid, gid) < 0) {
+                        if (setresgid(gid, gid, gid) < 0) {
                                 log_error("setregid() failed: %m");
                                 goto child_fail;
                         }
 
-                        if (setreuid(uid, uid) < 0) {
+                        if (setresuid(uid, uid, uid) < 0) {
                                 log_error("setreuid() failed: %m");
                                 goto child_fail;
                         }
                 }
 
-                if ((asprintf((char**)(envp + 2), "HOME=%s", home? home: "/root") < 0) ||
-                    (asprintf((char**)(envp + 3), "USER=%s", arg_user? arg_user : "root") < 0) ||
-                    (asprintf((char**)(envp + 4), "LOGNAME=%s", arg_user? arg_user : "root") < 0)) {
-                    log_error("environment setup failed: %m");
+                if ((asprintf((char**)(envp + 3), "HOME=%s", home? home: "/root") < 0) ||
+                    (asprintf((char**)(envp + 4), "USER=%s", arg_user? arg_user : "root") < 0) ||
+                    (asprintf((char**)(envp + 5), "LOGNAME=%s", arg_user? arg_user : "root") < 0)) {
+                    log_error("Out of memory");
                     goto child_fail;
                 }
 
@@ -821,7 +880,7 @@ int main(int argc, char *argv[]) {
                 if (argc > optind)
                         execvpe(argv[optind], argv + optind, (char**) envp);
                 else {
-                        chdir(home? home : "/root");
+                        chdir(home ? home : "/root");
                         execle("/bin/bash", "-bash", NULL, (char**) envp);
                 }
 
@@ -858,6 +917,7 @@ finish:
                 cg_kill_recursive_and_wait(SYSTEMD_CGROUP_CONTROLLER, newcg, true);
 
         free(arg_directory);
+        strv_free(arg_controllers);
         free(oldcg);
         free(newcg);