= Mount namespace setup in snap-confine =

This document provides a terse explanation of the mount setup using syscall
traces to show precisely what is happening and show the difference between
all snaps images and classic.

Obtain traces with (ignoring select helps keep strace from hanging):
$ sudo snap install hello-world
$ sudo /usr/lib/snapd/snap-discard-ns hello-world
$ sudo strace -f -vv -s8192 -o /tmp/trace.unshare -e trace='!select' /snap/bin/hello-world
$ sudo strace -f -vv -s8192 -o /tmp/trace.setns -e trace='!select' /snap/bin/hello-world

Examine /tmp/trace.unshare for initial mount namespace setup and
/tmp/trace.setns for seeing how the mount namespace is reused on subsequent
runs. Note that running /usr/lib/snapd/snap-discard-ns prior to running the
command is required for creating the new mount namespace (otherwise the
previous mount namespace will be reused).


= Mount namespace setup in detail =
Here are the steps snap-confine takes when setting up the mount namespace for a
given snap:

# Create the /run/snapd/ns directory to save off the mount namespace to be
# shared on other app-invocations
open("/", O_RDONLY|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC) = 3
mkdirat(3, "run", 0755)           = -1 EEXIST (File exists)
openat(3, "run", O_RDONLY|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC) = 4
mkdirat(4, "snapd", 0755)         = -1 EEXIST (File exists)
openat(4, "snapd", O_RDONLY|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC) = 3
mkdirat(3, "ns", 0755)            = -1 EEXIST (File exists)
openat(3, "ns", O_RDONLY|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC) = 4

# If /run/snapd/ns/<snap name>.mnt exists, enter that namespace:
openat(3, "hello-world.mnt", O_RDONLY|O_CREAT|O_NOFOLLOW|O_CLOEXEC, 0600) = 5
fstatfs(5, {f_type=0x6e736673, ...) = 0
setns(5, CLONE_NEWNS)             = 0
... mount namespace setup finished, go on to setup the rest of the sandbox ...


# Otherwise, create a new mount namespace
unshare(CLONE_NEWNS)
mount("none", "/", NULL, MS_REC|MS_SLAVE, NULL) = 0

# Classic-only - mount rootfs in the namespace
mkdir("/tmp/snap.rootfs_HkQghZ", 0700) = 0
mount("/snap/ubuntu-core/current", "/tmp/snap.rootfs_HkQghZ", NULL, MS_BIND, NULL) = 0

# Classic only - mount directories from host over rootfs
mount("/dev", "/tmp/snap.rootfs_HkQghZ/dev", NULL, MS_BIND|MS_REC|MS_SLAVE, NULL) = 0
mount("/etc", "/tmp/snap.rootfs_HkQghZ/etc", NULL, MS_BIND|MS_REC|MS_SLAVE, NULL) = 0
mount("/home", "/tmp/snap.rootfs_HkQghZ/home", NULL, MS_BIND|MS_REC|MS_SLAVE, NULL) = 0
mount("/root", "/tmp/snap.rootfs_HkQghZ/root", NULL, MS_BIND|MS_REC|MS_SLAVE, NULL) = 0
mount("/proc", "/tmp/snap.rootfs_HkQghZ/proc", NULL, MS_BIND|MS_REC|MS_SLAVE, NULL) = 0
mount("/sys", "/tmp/snap.rootfs_HkQghZ/sys", NULL, MS_BIND|MS_REC|MS_SLAVE, NULL) = 0
mount("/tmp", "/tmp/snap.rootfs_HkQghZ/tmp", NULL, MS_BIND|MS_REC|MS_SLAVE, NULL) = 0
mount("/var/snap", "/tmp/snap.rootfs_HkQghZ/var/snap", NULL, MS_BIND|MS_REC|MS_SLAVE, NULL) = 0
mount("/var/lib/snapd", "/tmp/snap.rootfs_HkQghZ/var/lib/snapd", NULL, MS_BIND|MS_REC|MS_SLAVE, NULL) = 0
mount("/var/tmp", "/tmp/snap.rootfs_HkQghZ/var/tmp", NULL, MS_BIND|MS_REC|MS_SLAVE, NULL) = 0
mount("/run", "/tmp/snap.rootfs_HkQghZ/run", NULL, MS_BIND|MS_REC|MS_SLAVE, NULL) = 0
mount("/media", "/tmp/snap.rootfs_HkQghZ/media", NULL, MS_BIND|MS_REC|MS_SLAVE, NULL) = 0
mount("/lib/modules", "/tmp/snap.rootfs_HkQghZ/lib/modules", NULL, MS_BIND|MS_REC|MS_SLAVE, NULL) = 0
mount("/usr/src", "/tmp/snap.rootfs_HkQghZ/usr/src", NULL, MS_BIND|MS_REC|MS_SLAVE, NULL) = 0
mount("/var/log", "/tmp/snap.rootfs_HkQghZ/var/log", NULL, MS_BIND|MS_REC|MS_SLAVE, NULL) = 0
mount("/snap", "/tmp/snap.rootfs_HkQghZ/snap", NULL, MS_BIND|MS_REC|MS_SLAVE, NULL) = 0
mount("/snap/ubuntu-core/current/etc/alternatives", "/tmp/snap.rootfs_HkQghZ/etc/alternatives", NULL, MS_BIND|MS_SLAVE, NULL) = 0
mount("/", "/tmp/snap.rootfs_HkQghZ/var/lib/snapd/hostfs", NULL, MS_RDONLY|MS_BIND, NULL) = 0

# Classic only - pivot_root into the rootfs
pivot_root(".", ".")              = 0
umount2(".", MNT_DETACH)          = 0

# Create a bind-mounted private /tmp
mkdir("/tmp/snap.0_snap.hello-world.hello-world_QXGSt1", 0700) = 0
mkdir("/tmp/snap.0_snap.hello-world.hello-world_QXGSt1/tmp", 01777) = 0
mount("/tmp/snap.0_snap.hello-world.hello-world_QXGSt1/tmp", "/tmp", NULL, MS_BIND, NULL) = 0
mount("none", "/tmp", NULL, MS_PRIVATE, NULL) = 0

# Create a per-snap /dev/pts
mount("devpts", "/dev/pts", "devpts", MS_MGC_VAL, "newinstance,ptmxmode=0666,mode=0"...)
mount("/dev/pts/ptmx", "/dev/ptmx", 0x5574dfe9a5c3, MS_BIND, NULL)

# Process snap-defined mounts (eg, for content interface, mount the source to
# the target as defined in /var/lib/snapd/mount/snap.<name>.<command>.fstab)
# Eg:
mount("/snap/some-content-snap/current/src", "/snap/hello-world/current/dst", NULL, MS_RDONLY|MS_NOSUID|MS_NODEV|MS_BIND, NULL)

# Bind mount this namespace to the application-specific NSFS magic file to
# preserve it across snap invocations (an fchdir() happened just after the
# unshare(), above).
mount("/proc/12887/ns/mnt", "hello-world.mnt", NULL, MS_BIND, NULL) = 0
... mount namespace setup finished, go on to setup the rest of the sandbox ...
