From 302bed03cad67b53348ea3ffd0447f16ca206db1 Mon Sep 17 00:00:00 2001 From: Zygmunt Krynicki Date: Wed, 17 Oct 2018 15:09:43 +0200 Subject: [PATCH] cmd/snap-discard-ns: add support for per-user mount namespaces This patch rewrites snap-discard-ns to support user mounts. This involves scanning for the new per-user mount namespaces and mount profiles. The program was simplified to not use the "abstraction" of provided by "ns-support.h". The abstraction was poor and with the upcoming changes to snap-confine to accommodate per-user mount namespaces it is easier to audit and understand the code directly. This change makes "ns-support.h" private to snap-confine so it can evolve separately. The manual page was updated to reflect the new functionality and refresh some cruft like project bug report URL. Signed-off-by: Zygmunt Krynicki --- cmd/snap-discard-ns/snap-discard-ns.c | 190 +++++++++++++++++++++--- cmd/snap-discard-ns/snap-discard-ns.rst | 18 ++- 2 files changed, 181 insertions(+), 27 deletions(-) diff --git a/cmd/snap-discard-ns/snap-discard-ns.c b/cmd/snap-discard-ns/snap-discard-ns.c index e511ef957115..43ea9eaea96e 100644 --- a/cmd/snap-discard-ns/snap-discard-ns.c +++ b/cmd/snap-discard-ns/snap-discard-ns.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Canonical Ltd + * Copyright (C) 2015-2018 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -15,41 +15,187 @@ * */ +#define _GNU_SOURCE + +#include #include +#include +#include #include +#include +#include +#include +#include +#include +#include #include #include "../libsnap-confine-private/locking.h" #include "../libsnap-confine-private/string-utils.h" #include "../libsnap-confine-private/utils.h" -#include "../snap-confine/ns-support.h" + +#ifndef NSFS_MAGIC +#define NSFS_MAGIC 0x6e736673 +#endif int main(int argc, char **argv) { - if (argc != 2) - die("Usage: %s snap-name", argv[0]); - const char *snap_name = argv[1]; - - int snap_lock_fd = sc_lock_snap(snap_name); - debug("initializing mount namespace: %s", snap_name); - struct sc_mount_ns *group = - sc_open_mount_ns(snap_name, SC_NS_FAIL_GRACEFULLY); - if (group != NULL) { - sc_discard_preserved_mount_ns(group); - sc_close_mount_ns(group); + if (argc != 2) { + printf("Usage: snap-discard-ns \n"); + return 0; + } + + const char *snap_instance_name = argv[1]; + + /* Grab the lock holding the snap instance. This prevents races from + * concurrently executing snap-confine. The lock is explicitly released + * during normal operation but it is not preserved across the life-cycle of + * the process anyway so no attempt is made to unlock it ahead of any call + * to die() */ + int snap_lock_fd = sc_lock_snap(snap_instance_name); + debug("discarding mount namespaces of snap %s", snap_instance_name); + + const char *ns_dir_path = "/run/snapd/ns"; + int ns_dir_fd = open(ns_dir_path, O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW); + if (ns_dir_fd < 0) { + /* The directory may legitimately not exist if no snap has started to + * prepare it. This is not an error condition. */ + if (errno == ENOENT) { + return 0; + } + die("cannot open path %s", ns_dir_path); + } + + /* Move to the namespace directory. This is used so that we don't need to + * traverse the path over and over in our upcoming unmount2(2) calls. */ + if (fchdir(ns_dir_fd) < 0) { + die("cannot move to directory %s", ns_dir_path); } - // Unlink the current mount profile, if any. - char profile_path[PATH_MAX] = { 0 }; - sc_must_snprintf(profile_path, sizeof(profile_path), - "/run/snapd/ns/snap.%s.fstab", snap_name); - if (unlink(profile_path) < 0) { - // Silently ignore ENOENT as the profile doens't have to be there. - if (errno != ENOENT) { - die("cannot remove current mount profile: %s", - profile_path); + + /* Create shell patterns that describe the things we are interested in: + * + * Preserved mount namespaces to unmount and unlink: + * - "$SNAP_INSTANCE_NAME.mnt" + * - "$SNAP_INSTANCE_NAME.[0-9]+.mnt" + * + * Applied mount profiles to unlink: + * - "snap.$SNAP_INSTANCE_NAME.fstab" + * - "snap.$SNAP_INSTANCE_NAME.[0-9]+.fstab" */ + char sys_fstab_pattern[PATH_MAX]; + char usr_fstab_pattern[PATH_MAX]; + char sys_mnt_pattern[PATH_MAX]; + char usr_mnt_pattern[PATH_MAX]; + sc_must_snprintf(sys_fstab_pattern, sizeof sys_fstab_pattern, + "snap\\.%s\\.fstab", snap_instance_name); + sc_must_snprintf(usr_fstab_pattern, sizeof usr_fstab_pattern, + "snap\\.%s\\.*\\.fstab", snap_instance_name); + sc_must_snprintf(sys_mnt_pattern, sizeof sys_mnt_pattern, + "%s\\.mnt", snap_instance_name); + sc_must_snprintf(usr_mnt_pattern, sizeof usr_mnt_pattern, + "%s\\.*\\.mnt", snap_instance_name); + + DIR *ns_dir = fdopendir(ns_dir_fd); + /* ns_dir_fd is now owned by ns_dir and will not be closed. */ + + while (true) { + /* Reset errno ahead of any call to readdir to differentiate errors + * from legitimate end of directory. */ + errno = 0; + struct dirent *dent = readdir(ns_dir); + if (dent == NULL) { + if (errno != 0) { + die("cannot read next directory entry"); + } + /* We've seen the whole directory. */ + break; + } + + /* We use dnet->d_name a lot so let's shorten it. */ + const char *dname = dent->d_name; + + /* Check the four patterns that we have against the name and set the + * two should flags to decide further actions. Note that we always + * unlink matching files so that is not reflected in the structure. */ + bool should_unmount = false; + bool should_unlink = false; + struct variant { + const char *p; + bool unmount; + }; + struct variant variants[4] = { + {.p = sys_mnt_pattern,.unmount = true}, + {.p = usr_mnt_pattern,.unmount = true}, + {.p = sys_fstab_pattern}, + {.p = usr_fstab_pattern}, + }; + for (size_t i = 0; i < sizeof variants / sizeof *variants; ++i) { + struct variant *v = &variants[i]; + debug("checking if %s matches %s", dname, v->p); + int match_result = fnmatch(v->p, dname, 0); + if (match_result < 0) { + die("cannot execute match against pattern %s", + v->p); + } + if (match_result == 0) { + should_unmount |= v->unmount; + should_unlink = true; + debug("file %s matches pattern %s", dname, + v->p); + /* One match is enough. */ + break; + } + } + + /* Stat the candidate directory entry to know what we are dealing with. */ + struct stat file_info; + if (fstatat(ns_dir_fd, dname, &file_info, + AT_SYMLINK_NOFOLLOW) < 0) { + die("cannot inspect file %s", dname); + } + + /* We are only interested in regular files. The .mnt files, even if + * bind-mounted, appear as regular files and not as symbolic links due + * to the peculiarities of the Linux kernel. */ + if (!S_ISREG(file_info.st_mode)) { + continue; + } + + if (should_unmount) { + /* If we are asked to unmount the file double check that it is + * really a preserved mount namespace since the error code from + * umount2(2) is inconclusive. */ + int path_fd = openat(ns_dir_fd, dname, + O_PATH | O_CLOEXEC | O_NOFOLLOW); + if (path_fd < 0) { + die("cannot open path %s", dname); + } + struct statfs fs_info; + if (fstatfs(path_fd, &fs_info) < 0) { + die("cannot inspect file-system at %s", dname); + } + close(path_fd); + if (fs_info.f_type == NSFS_MAGIC + || fs_info.f_type == PROC_SUPER_MAGIC) { + debug("unmounting %s", dname); + if (umount2(dname, MNT_DETACH | UMOUNT_NOFOLLOW) + < 0) { + die("cannot unmount %s", dname); + } + } + } + + if (should_unlink) { + debug("unlinking %s", dname); + if (unlinkat(ns_dir_fd, dname, 0) < 0) { + die("cannot unlink %s", dname); + } } } + /* Close the directory and release the lock, we're done. */ + if (closedir(ns_dir) < 0) { + die("cannot close directory"); + } sc_unlock(snap_lock_fd); return 0; } diff --git a/cmd/snap-discard-ns/snap-discard-ns.rst b/cmd/snap-discard-ns/snap-discard-ns.rst index 6e470bc8c734..c7bc6ada855e 100644 --- a/cmd/snap-discard-ns/snap-discard-ns.rst +++ b/cmd/snap-discard-ns/snap-discard-ns.rst @@ -7,16 +7,16 @@ internal tool for discarding preserved namespaces of snappy applications ------------------------------------------------------------------------ :Author: zygmunt.krynicki@canonical.com -:Date: 2016-10-05 +:Date: 2018-10-17 :Copyright: Canonical Ltd. -:Version: 1.0.43 +:Version: 2.36 :Manual section: 5 :Manual group: snappy SYNOPSIS ======== - snap-discard-ns SNAP_NAME + snap-discard-ns SNAP_INSTANCE_NAME DESCRIPTION =========== @@ -44,10 +44,18 @@ FILES `snap-discard-ns` uses the following files: `/run/snapd/ns/$SNAP_NAME.mnt`: +`/run/snapd/ns/$SNAP_NAME.[0-9].mnt`: - The preserved mount namespace that is unmounted by `snap-discard-ns`. + The preserved mount namespace that is unmounted and removed by + `snap-discard-ns`. + +`/run/snapd/ns/snap.$SNAP_NAME.fstab`: +`/run/snapd/ns/snap.$SNAP_NAME.[0-9]+.fstab`: + + The current mount profile of a preserved mount namespace that is removed by + `snap-discard-ns`. BUGS ==== -Please report all bugs with https://bugs.launchpad.net/snap-confine/+filebug +Please report all bugs with https://bugs.launchpad.net/snapd/+filebug