github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/cmd/snap-confine/udev-support.c (about) 1 /* 2 * Copyright (C) 2015-2016 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 #include "config.h" 18 19 #include <ctype.h> 20 #include <errno.h> 21 #include <limits.h> 22 #include <sys/sysmacros.h> 23 #include <sched.h> 24 #include <string.h> 25 #include <sys/stat.h> 26 #include <sys/types.h> 27 #include <sys/wait.h> 28 #include <unistd.h> 29 30 #include "../libsnap-confine-private/snap.h" 31 #include "../libsnap-confine-private/string-utils.h" 32 #include "../libsnap-confine-private/utils.h" 33 #include "udev-support.h" 34 35 static void 36 _run_snappy_app_dev_add_majmin(struct snappy_udev *udev_s, 37 const char *path, unsigned major, unsigned minor) 38 { 39 int status = 0; 40 pid_t pid = fork(); 41 if (pid < 0) { 42 die("cannot fork support process for device cgroup assignment"); 43 } 44 if (pid == 0) { 45 uid_t real_uid, effective_uid, saved_uid; 46 if (getresuid(&real_uid, &effective_uid, &saved_uid) != 0) 47 die("cannot get real, effective and saved user IDs"); 48 // can't update the cgroup unless the real_uid is 0, euid as 49 // 0 is not enough 50 if (real_uid != 0 && effective_uid == 0) 51 if (setuid(0) != 0) 52 die("cannot set user ID to zero"); 53 char buf[64] = { 0 }; 54 // pass snappy-add-dev an empty environment so the 55 // user-controlled environment can't be used to subvert 56 // snappy-add-dev 57 char *env[] = { NULL }; 58 if (minor == UINT_MAX) { 59 sc_must_snprintf(buf, sizeof(buf), "%u:*", major); 60 } else { 61 sc_must_snprintf(buf, sizeof(buf), "%u:%u", major, 62 minor); 63 } 64 debug("running snap-device-helper add %s %s %s", 65 udev_s->tagname, path, buf); 66 // This code runs inside the core snap. We have two paths 67 // for the udev helper. 68 // 69 // First try new "snap-device-helper" path first but 70 // when running against an older core snap fallback to 71 // the old name. 72 if (access("/usr/lib/snapd/snap-device-helper", X_OK) == 0) 73 execle("/usr/lib/snapd/snap-device-helper", 74 "/usr/lib/snapd/snap-device-helper", "add", 75 udev_s->tagname, path, buf, NULL, env); 76 else if (access("/usr/libexec/snapd/snap-device-helper", X_OK) == 0) 77 execle("/usr/libexec/snapd/snap-device-helper", 78 "/usr/libexec/snapd/snap-device-helper", "add", 79 udev_s->tagname, path, buf, NULL, env); 80 else 81 execle("/lib/udev/snappy-app-dev", 82 "/lib/udev/snappy-app-dev", "add", 83 udev_s->tagname, path, buf, NULL, env); 84 die("execl failed"); 85 } 86 if (waitpid(pid, &status, 0) < 0) 87 die("waitpid failed"); 88 if (WIFEXITED(status) && WEXITSTATUS(status) != 0) 89 die("child exited with status %i", WEXITSTATUS(status)); 90 else if (WIFSIGNALED(status)) 91 die("child died with signal %i", WTERMSIG(status)); 92 } 93 94 void run_snappy_app_dev_add(struct snappy_udev *udev_s, const char *path) 95 { 96 if (udev_s == NULL) 97 die("snappy_udev is NULL"); 98 if (udev_s->udev == NULL) 99 die("snappy_udev->udev is NULL"); 100 if (udev_s->tagname_len == 0 101 || udev_s->tagname_len >= MAX_BUF 102 || strnlen(udev_s->tagname, MAX_BUF) != udev_s->tagname_len 103 || udev_s->tagname[udev_s->tagname_len] != '\0') 104 die("snappy_udev->tagname has invalid length"); 105 106 debug("%s: %s %s", __func__, path, udev_s->tagname); 107 108 struct udev_device *d = 109 udev_device_new_from_syspath(udev_s->udev, path); 110 // udev may be late with registering the device, e.g. under high load, and 111 // udev lookup will fail. 112 if (d == NULL) { 113 debug("cannot find device from syspath %s", path); 114 return; 115 } 116 dev_t devnum = udev_device_get_devnum(d); 117 udev_device_unref(d); 118 119 unsigned int devmaj = major(devnum); 120 unsigned int devmin = minor(devnum); 121 // per udev_device_get_devnum man page, on failure a device type with minor 122 // and major number set to 0 is returned. 123 if (devmaj == 0 && devmin == 0) { 124 debug("cannot get major/minor numbers for %s", path); 125 } else { 126 _run_snappy_app_dev_add_majmin(udev_s, path, devmaj, devmin); 127 } 128 } 129 130 /* 131 * snappy_udev_init() - setup the snappy_udev structure. Return 0 if devices 132 * are assigned, else return -1. Callers should use snappy_udev_cleanup() to 133 * cleanup. 134 */ 135 int snappy_udev_init(const char *security_tag, struct snappy_udev *udev_s) 136 { 137 debug("%s", __func__); 138 int rc = 0; 139 140 udev_s->tagname[0] = '\0'; 141 udev_s->tagname_len = 0; 142 // TAG+="snap_<security tag>" (udev doesn't like '.' in the tag name) 143 udev_s->tagname_len = sc_must_snprintf(udev_s->tagname, MAX_BUF, 144 "%s", security_tag); 145 for (size_t i = 0; i < udev_s->tagname_len; i++) 146 if (udev_s->tagname[i] == '.') 147 udev_s->tagname[i] = '_'; 148 149 udev_s->udev = udev_new(); 150 if (udev_s->udev == NULL) 151 die("udev_new failed"); 152 153 udev_s->devices = udev_enumerate_new(udev_s->udev); 154 if (udev_s->devices == NULL) 155 die("udev_enumerate_new failed"); 156 157 if (udev_enumerate_add_match_tag(udev_s->devices, udev_s->tagname) < 0) 158 die("udev_enumerate_add_match_tag"); 159 160 if (udev_enumerate_scan_devices(udev_s->devices) < 0) 161 die("udev_enumerate_scan failed"); 162 163 udev_s->assigned = udev_enumerate_get_list_entry(udev_s->devices); 164 if (udev_s->assigned == NULL) 165 rc = -1; 166 167 return rc; 168 } 169 170 void snappy_udev_cleanup(struct snappy_udev *udev_s) 171 { 172 // udev_s->assigned does not need to be unreferenced since it is a 173 // pointer into udev_s->devices 174 if (udev_s->devices != NULL) 175 udev_enumerate_unref(udev_s->devices); 176 if (udev_s->udev != NULL) 177 udev_unref(udev_s->udev); 178 } 179 180 void setup_devices_cgroup(const char *security_tag, struct snappy_udev *udev_s) 181 { 182 debug("%s", __func__); 183 // Hardcode major/minor for common sysfs devices to avoid udev lookup 184 struct { 185 char *name; 186 unsigned int maj; 187 unsigned int min; 188 } static_devices[] = { 189 {.name="/sys/class/mem/null", .maj=1, .min=3}, 190 {.name="/sys/class/mem/full", .maj=1, .min=7}, 191 {.name="/sys/class/mem/zero", .maj=1, .min=5}, 192 {.name="/sys/class/mem/random", .maj=1, .min=8}, 193 {.name="/sys/class/mem/urandom", .maj=1, .min=9}, 194 {.name="/sys/class/tty/tty", .maj=5, .min=0}, 195 {.name="/sys/class/tty/console", .maj=5, .min=1}, 196 {.name="/sys/class/tty/ptmx", .maj=5, .min=2}, 197 {.name=NULL, .maj=0, .min=0} 198 }; 199 200 if (udev_s == NULL) 201 die("snappy_udev is NULL"); 202 if (udev_s->udev == NULL) 203 die("snappy_udev->udev is NULL"); 204 if (udev_s->devices == NULL) 205 die("snappy_udev->devices is NULL"); 206 if (udev_s->assigned == NULL) 207 die("snappy_udev->assigned is NULL"); 208 if (udev_s->tagname_len == 0 209 || udev_s->tagname_len >= MAX_BUF 210 || strnlen(udev_s->tagname, MAX_BUF) != udev_s->tagname_len 211 || udev_s->tagname[udev_s->tagname_len] != '\0') 212 die("snappy_udev->tagname has invalid length"); 213 214 // create devices cgroup controller 215 char cgroup_dir[PATH_MAX] = { 0 }; 216 217 sc_must_snprintf(cgroup_dir, sizeof(cgroup_dir), 218 "/sys/fs/cgroup/devices/%s/", security_tag); 219 sc_identity old = sc_set_effective_identity(sc_root_group_identity()); 220 if (mkdir(cgroup_dir, 0755) < 0 && errno != EEXIST) 221 die("cannot create cgroup hierarchy %s", cgroup_dir); 222 (void)sc_set_effective_identity(old); 223 224 // move ourselves into it 225 char cgroup_file[PATH_MAX] = { 0 }; 226 sc_must_snprintf(cgroup_file, sizeof(cgroup_file), "%s%s", cgroup_dir, 227 "cgroup.procs"); 228 229 char buf[128] = { 0 }; 230 sc_must_snprintf(buf, sizeof(buf), "%i", getpid()); 231 write_string_to_file(cgroup_file, buf); 232 233 // deny by default. Write 'a' to devices.deny to remove all existing 234 // devices that were added in previous launcher invocations, then add 235 // the static and assigned devices. This ensures that at application 236 // launch the cgroup only has what is currently assigned. 237 sc_must_snprintf(cgroup_file, sizeof(cgroup_file), "%s%s", cgroup_dir, 238 "devices.deny"); 239 write_string_to_file(cgroup_file, "a"); 240 241 // add the common devices 242 for (int i = 0; static_devices[i].name != NULL; i++) 243 _run_snappy_app_dev_add_majmin(udev_s, static_devices[i].name, 244 static_devices[i].maj, static_devices[i].min); 245 246 // add glob for current and future PTY slaves. We unconditionally add 247 // them since we use a devpts newinstance. Unix98 PTY slaves major 248 // are 136-143. 249 // https://github.com/torvalds/linux/blob/master/Documentation/admin-guide/devices.txt 250 for (unsigned pty_major = 136; pty_major <= 143; pty_major++) { 251 // '/dev/pts/slaves' is only used for debugging and by 252 // /usr/lib/snapd/snap-device-helper to determine if it is a block 253 // device, so just use something to indicate what the 254 // addition is for 255 _run_snappy_app_dev_add_majmin(udev_s, "/dev/pts/slaves", 256 pty_major, UINT_MAX); 257 } 258 259 // nvidia modules are proprietary and therefore aren't in sysfs and 260 // can't be udev tagged. For now, just add existing nvidia devices to 261 // the cgroup unconditionally (AppArmor will still mediate the access). 262 // We'll want to rethink this if snapd needs to mediate access to other 263 // proprietary devices. 264 // 265 // Device major and minor numbers are described in (though nvidia-uvm 266 // currently isn't listed): 267 // https://github.com/torvalds/linux/blob/master/Documentation/admin-guide/devices.txt 268 char nv_path[15] = { 0 }; // /dev/nvidiaXXX 269 const char *nvctl_path = "/dev/nvidiactl"; 270 const char *nvuvm_path = "/dev/nvidia-uvm"; 271 const char *nvidia_modeset_path = "/dev/nvidia-modeset"; 272 273 struct stat sbuf; 274 275 // /dev/nvidia0 through /dev/nvidia254 276 for (unsigned nv_minor = 0; nv_minor < 255; nv_minor++) { 277 sc_must_snprintf(nv_path, sizeof(nv_path), "/dev/nvidia%u", 278 nv_minor); 279 280 // Stop trying to find devices after one is not found. In this 281 // manner, we'll add /dev/nvidia0 and /dev/nvidia1 but stop 282 // trying to find nvidia3 - nvidia254 if nvidia2 is not found. 283 if (stat(nv_path, &sbuf) != 0) { 284 break; 285 } 286 _run_snappy_app_dev_add_majmin(udev_s, nv_path, 287 major(sbuf.st_rdev), 288 minor(sbuf.st_rdev)); 289 } 290 291 // /dev/nvidiactl 292 if (stat(nvctl_path, &sbuf) == 0) { 293 _run_snappy_app_dev_add_majmin(udev_s, nvctl_path, 294 major(sbuf.st_rdev), 295 minor(sbuf.st_rdev)); 296 } 297 // /dev/nvidia-uvm 298 if (stat(nvuvm_path, &sbuf) == 0) { 299 _run_snappy_app_dev_add_majmin(udev_s, nvuvm_path, 300 major(sbuf.st_rdev), 301 minor(sbuf.st_rdev)); 302 } 303 // /dev/nvidia-modeset 304 if (stat(nvidia_modeset_path, &sbuf) == 0) { 305 _run_snappy_app_dev_add_majmin(udev_s, nvidia_modeset_path, 306 major(sbuf.st_rdev), 307 minor(sbuf.st_rdev)); 308 } 309 // /dev/uhid isn't represented in sysfs, so add it to the device cgroup 310 // if it exists and let AppArmor handle the mediation 311 if (stat("/dev/uhid", &sbuf) == 0) { 312 _run_snappy_app_dev_add_majmin(udev_s, "/dev/uhid", 313 major(sbuf.st_rdev), 314 minor(sbuf.st_rdev)); 315 } 316 // When CONFIG_TUN=m, /dev/net/tun will exist but using it doesn't 317 // autoload the tun module but also /dev/net/tun isn't udev tagged 318 // until it is loaded. To work around this, if /dev/net/tun exists, add 319 // it unconditionally to the cgroup and rely on AppArmor to mediate the 320 // access. LP: #1859084 321 if (stat("/dev/net/tun", &sbuf) == 0) { 322 _run_snappy_app_dev_add_majmin(udev_s, "/dev/net/tun", 323 major(sbuf.st_rdev), 324 minor(sbuf.st_rdev)); 325 } 326 // add the assigned devices 327 while (udev_s->assigned != NULL) { 328 const char *path = udev_list_entry_get_name(udev_s->assigned); 329 if (path == NULL) 330 die("udev_list_entry_get_name failed"); 331 run_snappy_app_dev_add(udev_s, path); 332 udev_s->assigned = udev_list_entry_get_next(udev_s->assigned); 333 } 334 }