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 }