= 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)

# Classic only - process quirks mounts by:
#   creating temporary quirks directory for moving /var/lib/snapd aside
mkdir("/tmp/snapd.quirks_xKIzG3", 0700) = 0
#   moving /var/lib/snapd aside
mount("/var/lib/snapd", "/tmp/snapd.quirks_xKIzG3", NULL, MS_MOVE, NULL) = 0
#   creating a tmpfs on /var/lib for our mount points
mount("none", "/var/lib", "tmpfs", MS_NOSUID|MS_NODEV, NULL) = 0
#   mimicking the vanilla /var/lib/* from the core snap in /var/lib in tmpfs
#   (the directories to mimic are dynamically determined and will vary as the
#   core snap changes. Syscalls for finding what to mount and creating the
#   mount points are omitted)
mount("/snap/ubuntu-core/current/var/lib/apparmor", "/var/lib/apparmor", NULL, MS_RDONLY|MS_NOSUID|MS_NODEV|MS_BIND|MS_REC|MS_SLAVE, NULL) = 0
mount("/snap/ubuntu-core/current/var/lib/classic", "/var/lib/classic", ...) = 0
mount("/snap/ubuntu-core/current/var/lib/console-conf", "/var/lib/console-conf", ...) = 0
mount("/snap/ubuntu-core/current/var/lib/dbus", "/var/lib/dbus", ...) = 0
mount("/snap/ubuntu-core/current/var/lib/dhcp", "/var/lib/dhcp", ...) = 0
mount("/snap/ubuntu-core/current/var/lib/extrausers", "/var/lib/extrausers", ...) = 0
mount("/snap/ubuntu-core/current/var/lib/initramfs-tools", "/var/lib/initramfs-tools", ...) = 0
mount("/snap/ubuntu-core/current/var/lib/initscripts", "/var/lib/initscripts", ...) = 0
mount("/snap/ubuntu-core/current/var/lib/insserv", "/var/lib/insserv", ...) = 0
mount("/snap/ubuntu-core/current/var/lib/logrotate", "/var/lib/logrotate", ...) = 0
mount("/snap/ubuntu-core/current/var/lib/machines", "/var/lib/machines", ...) = 0
mount("/snap/ubuntu-core/current/var/lib/misc", "/var/lib/misc", ...) = 0
mount("/snap/ubuntu-core/current/var/lib/pam", "/var/lib/pam", ...) = 0
mount("/snap/ubuntu-core/current/var/lib/python", "/var/lib/python", ...) = 0
mount("/snap/ubuntu-core/current/var/lib/resolvconf", "/var/lib/resolvconf", ...) = 0
mount("/snap/ubuntu-core/current/var/lib/snapd", "/var/lib/snapd", ...) = 0
mount("/snap/ubuntu-core/current/var/lib/sudo", "/var/lib/sudo", ...) = 0
mount("/snap/ubuntu-core/current/var/lib/systemd", "/var/lib/systemd", ...) = 0
mount("/snap/ubuntu-core/current/var/lib/ubuntu-fan", "/var/lib/ubuntu-fan", ...) = 0
mount("/snap/ubuntu-core/current/var/lib/ucf", "/var/lib/ucf", ...) = 0
mount("/snap/ubuntu-core/current/var/lib/update-rc.d", "/var/lib/update-rc.d", ...) = 0
mount("/snap/ubuntu-core/current/var/lib/urandom", "/var/lib/urandom", ...) = 0
mount("/snap/ubuntu-core/current/var/lib/vim", "/var/lib/vim", ...) = 0
mount("/snap/ubuntu-core/current/var/lib/waagent", "/var/lib/waagent", ...) = 0
#   unmounting the /var/lib/snapd that was just mimicked
umount2("/var/lib/snapd", 0)
#   moving back the /var/lib/snapd that was set aside
mount("/tmp/snapd.quirks_xKIzG3", "/var/lib/snapd", NULL, MS_MOVE, NULL) = 0
#   cleaning up the temporary directory
rmdir("/tmp/snapd.quirks_xKIzG3") = 0
#   applying the actual quirk mounts as needed (for now, lxd, but more may
#   come). Eg:
mount("/var/lib/snapd/hostfs/var/lib/lxd", "/var/lib/lxd", NULL, MS_REC|MS_SLAVE|MS_NODEV|MS_NOSUID|MS_NOEXEC) = 0
# End quirk mounts on classic

# 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 ...
