github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/cmd/snap-discard-ns/snap-discard-ns.c (about)

     1  /*
     2   * Copyright (C) 2015-2019 Canonical Ltd
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU General Public License version 3 as
     6   * published by the Free Software Foundation.
     7   *
     8   * This program is distributed in the hope that it will be useful,
     9   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    10   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11   * GNU General Public License for more details.
    12   *
    13   * You should have received a copy of the GNU General Public License
    14   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15   *
    16   */
    17  
    18  #define _GNU_SOURCE
    19  
    20  #include <dirent.h>
    21  #include <errno.h>
    22  #include <fcntl.h>
    23  #include <fnmatch.h>
    24  #include <limits.h>
    25  #include <linux/magic.h>
    26  #include <stdio.h>
    27  #include <sys/mount.h>
    28  #include <sys/stat.h>
    29  #include <sys/types.h>
    30  #include <sys/vfs.h>
    31  #include <unistd.h>
    32  
    33  #include "../libsnap-confine-private/error.h"
    34  #include "../libsnap-confine-private/locking.h"
    35  #include "../libsnap-confine-private/snap.h"
    36  #include "../libsnap-confine-private/string-utils.h"
    37  #include "../libsnap-confine-private/utils.h"
    38  
    39  #ifndef NSFS_MAGIC
    40  #define NSFS_MAGIC 0x6e736673
    41  #endif
    42  
    43  int main(int argc, char** argv) {
    44      if (argc != 2 && argc != 3) {
    45          printf("Usage: snap-discard-ns [--from-snap-confine] <SNAP-INSTANCE-NAME>\n");
    46          return 0;
    47      }
    48      const char* snap_instance_name;
    49      bool from_snap_confine;
    50  
    51      if (argc == 3) {
    52          if (!sc_streq(argv[1], "--from-snap-confine")) {
    53              die("unexpected argument %s", argv[1]);
    54          }
    55          from_snap_confine = true;
    56          snap_instance_name = argv[2];
    57      } else {
    58          from_snap_confine = false;
    59          snap_instance_name = argv[1];
    60      }
    61  
    62      sc_error* err = NULL;
    63      sc_instance_name_validate(snap_instance_name, &err);
    64      sc_die_on_error(err);
    65  
    66      int snap_lock_fd = -1;
    67      if (from_snap_confine) {
    68          sc_verify_snap_lock(snap_instance_name);
    69      } else {
    70          /* Grab the lock holding the snap instance. This prevents races from
    71           * concurrently executing snap-confine. The lock is explicitly released
    72           * during normal operation but it is not preserved across the life-cycle of
    73           * the process anyway so no attempt is made to unlock it ahead of any call
    74           * to die() */
    75          snap_lock_fd = sc_lock_snap(snap_instance_name);
    76      }
    77      debug("discarding mount namespaces of snap %s", snap_instance_name);
    78  
    79      const char* ns_dir_path = "/run/snapd/ns";
    80      int ns_dir_fd = open(ns_dir_path, O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
    81      if (ns_dir_fd < 0) {
    82          /* The directory may legitimately not exist if no snap has started to
    83           * prepare it. This is not an error condition. */
    84          if (errno == ENOENT) {
    85              return 0;
    86          }
    87          die("cannot open path %s", ns_dir_path);
    88      }
    89  
    90      /* Move to the namespace directory. This is used so that we don't need to
    91       * traverse the path over and over in our upcoming umount2(2) calls. */
    92      if (fchdir(ns_dir_fd) < 0) {
    93          die("cannot move to directory %s", ns_dir_path);
    94      }
    95  
    96      /* Create shell patterns that describe the things we are interested in:
    97       *
    98       * Preserved mount namespaces to unmount and unlink:
    99       * - "$SNAP_INSTANCE_NAME.mnt"
   100       * - "$SNAP_INSTANCE_NAME.[0-9]+.mnt"
   101       *
   102       * Applied mount profiles to unlink:
   103       * - "snap.$SNAP_INSTANCE_NAME.fstab"
   104       * - "snap.$SNAP_INSTANCE_NAME.[0-9]+.user-fstab"
   105       *
   106       * Mount namespace information files:
   107       * - "snap.$SNAP_INSTANCE_NAME.info"
   108       *
   109       * Use PATH_MAX as the size of each buffer since those can store any file
   110       * name. */
   111      char sys_fstab_pattern[PATH_MAX];
   112      char usr_fstab_pattern[PATH_MAX];
   113      char sys_mnt_pattern[PATH_MAX];
   114      char usr_mnt_pattern[PATH_MAX];
   115      char sys_info_pattern[PATH_MAX];
   116      sc_must_snprintf(sys_fstab_pattern, sizeof sys_fstab_pattern, "snap\\.%s\\.fstab", snap_instance_name);
   117      sc_must_snprintf(usr_fstab_pattern, sizeof usr_fstab_pattern, "snap\\.%s\\.*\\.user-fstab", snap_instance_name);
   118      sc_must_snprintf(sys_mnt_pattern, sizeof sys_mnt_pattern, "%s\\.mnt", snap_instance_name);
   119      sc_must_snprintf(usr_mnt_pattern, sizeof usr_mnt_pattern, "%s\\.*\\.mnt", snap_instance_name);
   120      sc_must_snprintf(sys_info_pattern, sizeof sys_info_pattern, "snap\\.%s\\.info", snap_instance_name);
   121  
   122      DIR* ns_dir = fdopendir(ns_dir_fd);
   123      if (ns_dir == NULL) {
   124          die("cannot fdopendir");
   125      }
   126      /* ns_dir_fd is now owned by ns_dir and will not be closed. */
   127  
   128      while (true) {
   129          /* Reset errno ahead of any call to readdir to differentiate errors
   130           * from legitimate end of directory. */
   131          errno = 0;
   132          struct dirent* dent = readdir(ns_dir);
   133          if (dent == NULL) {
   134              if (errno != 0) {
   135                  die("cannot read next directory entry");
   136              }
   137              /* We've seen the whole directory. */
   138              break;
   139          }
   140  
   141          /* We use dnet->d_name a lot so let's shorten it. */
   142          const char* dname = dent->d_name;
   143  
   144          /* Check the four patterns that we have against the name and set the
   145           * two should flags to decide further actions. Note that we always
   146           * unlink matching files so that is not reflected in the structure. */
   147          bool should_unmount = false;
   148          bool should_unlink = false;
   149          struct variant {
   150              const char* pattern;
   151              bool unmount;
   152          };
   153          struct variant variants[] = {
   154              {.pattern = sys_mnt_pattern, .unmount = true},
   155              {.pattern = usr_mnt_pattern, .unmount = true},
   156              {.pattern = sys_fstab_pattern},
   157              {.pattern = usr_fstab_pattern},
   158              {.pattern = sys_info_pattern},
   159          };
   160          for (size_t i = 0; i < sizeof variants / sizeof *variants; ++i) {
   161              struct variant* v = &variants[i];
   162              debug("checking if %s matches %s", dname, v->pattern);
   163              int match_result = fnmatch(v->pattern, dname, 0);
   164              if (match_result == FNM_NOMATCH) {
   165                  continue;
   166              } else if (match_result == 0) {
   167                  should_unmount |= v->unmount;
   168                  should_unlink = true;
   169                  debug("file %s matches pattern %s", dname, v->pattern);
   170                  /* One match is enough. */
   171                  break;
   172              } else if (match_result < 0) {
   173                  die("cannot execute match against pattern %s", v->pattern);
   174              }
   175          }
   176  
   177          /* Stat the candidate directory entry to know what we are dealing with.
   178           */
   179          struct stat file_info;
   180          if (fstatat(ns_dir_fd, dname, &file_info, AT_SYMLINK_NOFOLLOW) < 0) {
   181              die("cannot inspect file %s", dname);
   182          }
   183  
   184          /* We are only interested in regular files. The .mnt files, even if
   185           * bind-mounted, appear as regular files and not as symbolic links due
   186           * to the peculiarities of the Linux kernel. */
   187          if (!S_ISREG(file_info.st_mode)) {
   188              continue;
   189          }
   190  
   191          if (should_unmount) {
   192              /* If we are asked to unmount the file double check that it is
   193               * really a preserved mount namespace since the error code from
   194               * umount2(2) is inconclusive. */
   195              int path_fd = openat(ns_dir_fd, dname, O_PATH | O_CLOEXEC | O_NOFOLLOW);
   196              if (path_fd < 0) {
   197                  die("cannot open path %s", dname);
   198              }
   199              struct statfs fs_info;
   200              if (fstatfs(path_fd, &fs_info) < 0) {
   201                  die("cannot inspect file-system at %s", dname);
   202              }
   203              close(path_fd);
   204              if (fs_info.f_type == NSFS_MAGIC || fs_info.f_type == PROC_SUPER_MAGIC) {
   205                  debug("unmounting %s", dname);
   206                  if (umount2(dname, MNT_DETACH | UMOUNT_NOFOLLOW) < 0) {
   207                      die("cannot unmount %s", dname);
   208                  }
   209              }
   210          }
   211  
   212          if (should_unlink) {
   213              debug("unlinking %s", dname);
   214              if (unlinkat(ns_dir_fd, dname, 0) < 0) {
   215                  die("cannot unlink %s", dname);
   216              }
   217          }
   218      }
   219  
   220      /* Close the directory and release the lock, we're done. */
   221      if (closedir(ns_dir) < 0) {
   222          die("cannot close directory");
   223      }
   224      if (snap_lock_fd != -1) {
   225          sc_unlock(snap_lock_fd);
   226      }
   227      return 0;
   228  }