cmd/snap-confine: re-associate with pid-1 mount namespace if required #2624

Merged
merged 37 commits into from Mar 16, 2017
Commits
Jump to file or symbol
Failed to load files and symbols.
+110 −0
Split
@@ -78,6 +78,8 @@ void sc_init_apparmor_support(struct sc_apparmor *apparmor)
if (aa_getcon(&label, &mode) < 0) {
die("cannot query current apparmor profile");
}
+ debug("apparmor label on snap-confine is: %s", label);
+ debug("apparmor mode is: %s", mode);
// The label has a special value "unconfined" that is applied to all
// processes without a dedicated profile. If that label is used then the
// current process is not confined. All other labels imply confinement.
@@ -169,6 +169,54 @@ static bool sc_is_ns_group_dir_private()
return false;
}
+void sc_reassociate_with_pid1_mount_ns()
+{
+ int init_mnt_fd __attribute__ ((cleanup(sc_cleanup_close))) = -1;
+ int self_mnt_fd __attribute__ ((cleanup(sc_cleanup_close))) = -1;
+
+ debug("checking if the current process shares mount namespace"
+ " with the init process");
+
+ init_mnt_fd = open("/proc/1/ns/mnt",
+ O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_PATH);
+ if (init_mnt_fd < 0) {
+ die("cannot open mount namespace of the init process (O_PATH)");
+ }
+ self_mnt_fd = open("/proc/self/ns/mnt",
+ O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_PATH);
+ if (self_mnt_fd < 0) {
+ die("cannot open mount namespace of the current process (O_PATH)");
+ }
+ char init_buf[128], self_buf[128];
+ memset(init_buf, 0, sizeof init_buf);
+ if (readlinkat(init_mnt_fd, "", init_buf, sizeof init_buf) < 0) {
+ die("cannot perform readlinkat() on the mount namespace file "
+ "descriptor of the init process");
+ }
+ memset(self_buf, 0, sizeof self_buf);
+ if (readlinkat(self_mnt_fd, "", self_buf, sizeof self_buf) < 0) {
+ die("cannot perform readlinkat() on the mount namespace file "
+ "descriptor of the current process");
+ }
+ if (memcmp(init_buf, self_buf, sizeof init_buf) != 0) {
+ debug("the current process does not share mount namespace with "
+ "the init process, re-association required");
+ // NOTE: we cannot use O_NOFOLLOW here because that file will always be a
+ // symbolic link. We actually want to open it this way.
+ int init_mnt_fd_real
+ __attribute__ ((cleanup(sc_cleanup_close))) = -1;
+ init_mnt_fd_real = open("/proc/1/ns/mnt", O_RDONLY | O_CLOEXEC);
+ if (init_mnt_fd_real < 0) {
+ die("cannot open mount namespace of the init process");
+ }
+ if (setns(init_mnt_fd_real, CLONE_NEWNS) < 0) {
+ die("cannot re-associate the mount namespace with the init process");
+ }
+ } else {
+ debug("re-associating is not required");
+ }
+}
+
void sc_initialize_ns_groups()
{
debug("creating namespace group directory %s", sc_ns_dir);
@@ -22,6 +22,17 @@
#include "apparmor-support.h"
+/**
+ * Re-associate the current process with the mount namespace of pid 1.
+ *
+ * This function inspects the mount namespace of the current process and that
+ * of pid 1. In case they differ the current process is re-associated with the
+ * mount namespace of pid 1.
+ *
+ * This function should be called before sc_initialize_ns_groups().
+ **/
+void sc_reassociate_with_pid1_mount_ns();
+
/**
* Initialize namespace sharing.
*
@@ -275,6 +275,10 @@
/var/lib/snapd/hostfs/var/lib/lxd r,
# support for the mount namespace sharing
+ capability sys_ptrace,
+ # allow snap-confine to read /proc/1/ns/mnt
+ ptrace trace peer=unconfined,
+
mount options=(rw rbind) /run/snapd/ns/ -> /run/snapd/ns/,
mount options=(private) -> /run/snapd/ns/,
/ rw,
@@ -123,6 +123,21 @@ int main(int argc, char **argv)
debug
("skipping sandbox setup, classic confinement in use");
} else {
+ /* snap-confine uses privately-shared /run/snapd/ns to store
+ * bind-mounted mount namespaces of each snap. In the case that
+ * snap-confine is invoked from the mount namespace it typically
+ * constructs, the said directory does not contain mount entries
+ * for preserved namespaces as those are only visible in the main,
+ * outer namespace.
+ *
+ * In order to operate in such an environment snap-confine must
+ * first re-associate its own process with another namespace in
+ * which the /run/snapd/ns directory is visible. The most obvious
+ * candidate is pid one, which definitely doesn't run in a
+ * snap-specific namespace, has a predictable PID and is long
+ * lived.
+ */
+ sc_reassociate_with_pid1_mount_ns();
const char *group_name = snap_name;
if (group_name == NULL) {
die("SNAP_NAME is not set");
@@ -0,0 +1,30 @@
+summary: Regression test for https://bugs.launchpad.net/snap-confine/+bug/1644439
+# NOTE: This test is excluded on core systems as the kernel release schedule
+# there is separate from classic Ubuntu. Once the fixed kernel is available
+# this line should be removed.
+systems: [-ubuntu-core-16-*]
+details: |
+ snap-confine uses privately-shared /run/snapd/ns to store bind-mounted
+ mount namespaces of each snap. In the case that snap-confine is invoked
+ from the mount namespace it typically constructs, the said directory does
+ not contain mount entries for preserved namespaces as those are only
+ visible in the main, outer namespace. In order to operate in such an
+ environment snap-confine must first re-associate its own process with
+ another namespace in which the /run/snapd/ns directory is visible.
+ The most obvious candidate is pid one, which definitely doesn't run in a
+ snap-specific namespace, has a predictable PID and is long lived.
+prepare: |
+ echo "Having installed the test snap in devmode"
+ . $TESTSLIB/snaps.sh
+ install_local_devmode test-snapd-tools
+execute: |
+ echo "We can now run a snap command from the namespace of a snap command and see it work"
+ test-snapd-tools.cmd /bin/true
+ test-snapd-tools.cmd /bin/sh -c "SNAP_CONFINE_DEBUG=yes /snap/bin/test-snapd-tools.cmd /bin/true"
+ echo "We can now discard the namespace and repeat the test as a non-root user"
+ /usr/lib/snapd/snap-discard-ns test-snapd-tools
+ su -l -c 'test-snapd-tools.cmd /bin/true' test
+ su -l -c 'test-snapd-tools.cmd /bin/sh -c "SNAP_CONFINE_DEBUG=yes /snap/bin/test-snapd-tools.cmd /bin/true"' test
+debug: |
+ # Kernel version is an important input in understing failures of this test
+ uname -a