about summary refs log tree commit diff
path: root/pkgs/profpatsch/sandbox.nix
diff options
context:
space:
mode:
authorProfpatsch <mail@profpatsch.de>2019-12-08 02:39:44 +0100
committerProfpatsch <mail@profpatsch.de>2019-12-08 02:39:44 +0100
commit3cd2df8f8eb3a63a5e8823ca094785589d4039df (patch)
treea78895d856609a1840abd3b53e0fbcdfdd0d4e45 /pkgs/profpatsch/sandbox.nix
parent9d88b75f6261b9b4f5d280ec081cd0e53b47f6be (diff)
pkgs/profpatsch: add sandbox primitive
Small sandboxing utility, which unshares the filesystem via
user-namespaces and can optionally bind-mount existing paths into the
sandbox.
Diffstat (limited to 'pkgs/profpatsch/sandbox.nix')
-rw-r--r--pkgs/profpatsch/sandbox.nix63
1 files changed, 63 insertions, 0 deletions
diff --git a/pkgs/profpatsch/sandbox.nix b/pkgs/profpatsch/sandbox.nix
new file mode 100644
index 00000000..b3107f5a
--- /dev/null
+++ b/pkgs/profpatsch/sandbox.nix
@@ -0,0 +1,63 @@
+{ pkgs, writeExecline }:
+
+let
+
+  # remove everything but a few selected environment variables
+  runInEmptyEnv = keepVars:
+    let
+        importas = pkgs.lib.concatMap (var: [ "importas" "-i" var var ]) keepVars;
+        # we have to explicitely call export here, because PATH is probably empty
+        export = pkgs.lib.concatMap (var: [ "${pkgs.execline}/bin/export" var ''''${${var}}'' ]) keepVars;
+    in writeExecline "empty-env" {}
+         (importas ++ [ "emptyenv" ] ++ export ++ [ "${pkgs.execline}/bin/exec" "$@" ]);
+
+
+  # lightweight sandbox; execute any command in an unshared
+  # namespace that only has access to /nix and the specified
+  # directories from `extraMounts`.
+  sandbox = { extraMounts ? [] }:
+    let
+      pathsToMount = [
+        "/nix"
+        "/dev" "/proc" "/sys"
+      ] ++ extraMounts;
+      # chain execlines and exit immediately if one fails
+      all = builtins.concatMap (c: [ "if" c ]);
+      mount = "${pkgs.utillinux}/bin/mount";
+      unshare = "${pkgs.utillinux}/bin/unshare";
+      # this is the directory the sandbox runs under (in a separate mount namespace)
+      newroot = pkgs.runCommand "sandbox-root" {} ''mkdir "$out"'';
+      # this runs in a separate namespace, sets up a chroot root
+      # and then chroots into the new root.
+      sandbox = writeExecline "sandbox" {} (builtins.concatLists [
+        # first, unshare the mount namespace and make us root
+        # -> requires user namespaces!
+        [ unshare "--mount" "--map-root-user" ]
+        (all
+          # mount a temporary file system which we can chroot to;
+          # we can use the fixed path newroot here, because the resulting
+          # tmpfs cannot be seen from the outside world (we are in an unshared
+          # mount )
+          ([ [ mount "-t" "tmpfs" "container_root" newroot ] ]
+          # now mount the file system parts we need into the chroot
+          ++ builtins.concatMap
+               (rootPath: [
+                 [ "${pkgs.coreutils}/bin/mkdir" "-p" "${newroot}${rootPath}" ]
+                 [ mount "--rbind" rootPath "${newroot}${rootPath}" ]
+               ])
+               pathsToMount))
+        [ # finally, chroot into our new root directory
+          "${pkgs.coreutils}/bin/chroot" newroot
+          # drop root permissions, become user nobody;
+          # This is because many programs don’t like to be root
+          # TODO: this unshare does not work, because we don’t have
+          # the right permissions to do that here, unfortunately :(
+          # unshare "--user" "--"
+          "$@"
+        ]
+      ]);
+    in sandbox;
+
+in {
+  inherit sandbox runInEmptyEnv;
+}