Added use_switchroot option
authorEli Zrihen <ezrihen@gmail.com>
Thu, 17 Jun 2021 11:57:01 +0000 (14:57 +0300)
committerEli Zrihen <ezrihen@gmail.com>
Thu, 17 Jun 2021 11:57:01 +0000 (14:57 +0300)
cmdline.cc
config.cc
config.proto
mnt.cc
nsjail.h

index d706b766a04d5f76631067e33d5f88ae58918585..793f6115a3691c28ca6005e99b4e40f2f46913e6 100644 (file)
@@ -76,6 +76,7 @@ struct custom_option custom_opts[] = {
     { { "exec_file", required_argument, NULL, 'x' }, "File to exec (default: argv[0])" },
     { { "execute_fd", no_argument, NULL, 0x0607 }, "Use execveat() to execute a file-descriptor instead of executing the binary path. In such case argv[0]/exec_file denotes a file path before mount namespacing" },
     { { "chroot", required_argument, NULL, 'c' }, "Directory containing / of the jail (default: none)" },
+    { { "use_switchroot", no_argument, NULL, 0x600 }, "When creating a mount namespace, use switch_root rather then the default pivot_root (usefull when using rootfs, on which the kernel disallows pivot_root)" },
     { { "rw", no_argument, NULL, 0x601 }, "Mount chroot dir (/) R/W (default: R/O)" },
     { { "user", required_argument, NULL, 'u' }, "Username/uid of processes inside the jail (default: your current uid). You can also use inside_ns_uid:outside_ns_uid:count convention here. Can be specified multiple times" },
     { { "group", required_argument, NULL, 'g' }, "Groupname/gid of processes inside the jail (default: your current gid). You can also use inside_ns_gid:global_ns_gid:count convention here. Can be specified multiple times" },
@@ -431,6 +432,7 @@ std::unique_ptr<nsjconf_t> parseArgs(int argc, char* argv[]) {
        nsjconf->clone_newnet = true;
        nsjconf->clone_newuser = true;
        nsjconf->clone_newns = true;
+       nsjconf->use_switchroot = false;
        nsjconf->clone_newpid = true;
        nsjconf->clone_newipc = true;
        nsjconf->clone_newuts = true;
@@ -648,6 +650,9 @@ std::unique_ptr<nsjconf_t> parseArgs(int argc, char* argv[]) {
                        }
                        nsjconf->caps.push_back(cap);
                } break;
+               case 0x0600:
+                       nsjconf->use_switchroot = true;
+                       break;
                case 0x0601:
                        nsjconf->is_root_rw = true;
                        break;
index 413f6fc33e794efa1aebe5b6b49a5a0debb99daa..9c4cf73dd83d3de297ef58d5ed197ee16c268d91 100644 (file)
--- a/config.cc
+++ b/config.cc
@@ -184,6 +184,8 @@ static bool configParseInternal(nsjconf_t* nsjconf, const nsjail::NsJailConfig&
        nsjconf->clone_newuts = njc.clone_newuts();
        nsjconf->clone_newcgroup = njc.clone_newcgroup();
        nsjconf->clone_newtime = njc.clone_newtime();
+       
+       nsjconf->use_switchroot = njc.use_switchroot();
 
        for (ssize_t i = 0; i < njc.uidmap_size(); i++) {
                if (!user::parseId(nsjconf, njc.uidmap(i).inside_id(), njc.uidmap(i).outside_id(),
index 8c55991054c36eada29833f197f6b01cde3064d0..31f8342ec1d778b1c6ee1afa632888652ebb7e4d 100644 (file)
@@ -86,6 +86,9 @@ message NsJailConfig {
     /* Initial current working directory for the binary */
     optional string cwd = 9 [default = "/"];
 
+    /* Defines whether to use switch_root or pivot_root */
+    optional bool use_switchroot = 88  [default = false];
+    
     /* TCP port to listen to. Valid with mode=LISTEN only */
     optional uint32 port = 10 [default = 0];
     /* Host to bind to for mode=LISTEN. Must be in IPv6 format */
diff --git a/mnt.cc b/mnt.cc
index ef2dbd73f250f7bc9832765f7df0f4fbc5ab2576..e99f4d83d9a26e21d8b0c6a2007ea51afdf2009e 100644 (file)
--- a/mnt.cc
+++ b/mnt.cc
@@ -398,30 +398,61 @@ static bool initCloneNs(nsjconf_t* nsjconf) {
                PLOG_E("umount2('%s', MNT_DETACH)", tmpdir->c_str());
                return false;
        }
-       /*
-        * This requires some explanation: It's actually possible to pivot_root('/', '/').
-        * After this operation has been completed, the old root is mounted over the new
-        * root, and it's OK to simply umount('/') now, and to have new_root as '/'. This
-        * allows us not care about providing any special directory for old_root, which is
-        * sometimes not easy, given that e.g. /tmp might not always be present inside
-        * new_root
-        */
-       if (util::syscall(
-               __NR_pivot_root, (uintptr_t)destdir->c_str(), (uintptr_t)destdir->c_str()) == -1) {
-               PLOG_E("pivot_root('%s', '%s')", destdir->c_str(), destdir->c_str());
-               return false;
-       }
-
-       if (umount2("/", MNT_DETACH) == -1) {
-               PLOG_E("umount2('/', MNT_DETACH)");
-               return false;
-       }
-
-       for (const auto& p : nsjconf->mountpts) {
-               if (!remountPt(p) && p.is_mandatory) {
-                       return false;
-               }
-       }
+       
+    if (false == nsjconf->use_switchroot) {
+        /*
+        * This requires some explanation: It's actually possible to pivot_root('/', '/').
+        * After this operation has been completed, the old root is mounted over the new
+        * root, and it's OK to simply umount('/') now, and to have new_root as '/'. This
+        * allows us not care about providing any special directory for old_root, which is
+        * sometimes not easy, given that e.g. /tmp might not always be present inside
+        * new_root
+        */
+        if (util::syscall(
+            __NR_pivot_root, (uintptr_t)destdir->c_str(), (uintptr_t)destdir->c_str()) == -1) {
+            PLOG_E("pivot_root('%s', '%s')", destdir->c_str(), destdir->c_str());
+            return false;
+        }
+
+        if (umount2("/", MNT_DETACH) == -1) {
+            PLOG_E("umount2('/', MNT_DETACH)");
+            return false;
+        }
+    } else {
+        /*
+        * pivot_root would normally un-mount the old root, however in certain cases this
+        * operation is forbidden. There are systems (mainly embedded) that keep their root
+        * file system in RAM, when initially loaded by the kernel (e.g. initramfs),
+        * and there is no other file system that is mounted on top of it.In such systems,
+        * there is option to pivot_root!
+        * For more information, see kernel.org/doc/Documentation/filesystems/ramfs-rootfs-initramfs.txt.
+        * switch_root alternative:
+        * Innstead of un-mounting the old rootfs, it is over mounted by moving the new root to it.
+        * This way, we prevent the process from hacking its way back into the old root. 
+        */
+        if (chdir(destdir->c_str()) == -1) {
+            PLOG_E("chdir('%s')", destdir->c_str());
+            return false;
+        }
+
+        /* mount moving the new root on top of '/'. This operation is atomic and doesn't involve
+        un-mounting '/' at any stage */
+        if (mount(".", "/", NULL, MS_MOVE, NULL) == -1) {
+            PLOG_E("mount('/', %s, NULL, MS_MOVE, NULL)", destdir->c_str());
+            return false;
+        }
+
+        if (chroot(".") == -1) {
+            PLOG_E("chroot('%s')", destdir->c_str());
+            return false;
+        }
+    }
+
+    for (const auto& p : nsjconf->mountpts) {
+        if (!remountPt(p) && p.is_mandatory) {
+            return false;
+        }
+    }
 
        return true;
 }
index 7dd588e55ed31d9deee981645a78f733d8cc2e70..75acc1fdc052e3c8855923fae9bcaf730a4d5d4d 100644 (file)
--- a/nsjail.h
+++ b/nsjail.h
@@ -120,6 +120,7 @@ struct nsjconf_t {
        bool clone_newnet;
        bool clone_newuser;
        bool clone_newns;
+       bool use_switchroot;
        bool clone_newpid;
        bool clone_newipc;
        bool clone_newuts;