github.com/rigado/snapd@v2.42.5-go-mod+incompatible/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 if (d == NULL) 111 die("cannot find device from syspath %s", path); 112 dev_t devnum = udev_device_get_devnum(d); 113 udev_device_unref(d); 114 115 _run_snappy_app_dev_add_majmin(udev_s, path, major(devnum), 116 minor(devnum)); 117 } 118 119 /* 120 * snappy_udev_init() - setup the snappy_udev structure. Return 0 if devices 121 * are assigned, else return -1. Callers should use snappy_udev_cleanup() to 122 * cleanup. 123 */ 124 int snappy_udev_init(const char *security_tag, struct snappy_udev *udev_s) 125 { 126 debug("%s", __func__); 127 int rc = 0; 128 129 udev_s->tagname[0] = '\0'; 130 udev_s->tagname_len = 0; 131 // TAG+="snap_<security tag>" (udev doesn't like '.' in the tag name) 132 udev_s->tagname_len = sc_must_snprintf(udev_s->tagname, MAX_BUF, 133 "%s", security_tag); 134 for (size_t i = 0; i < udev_s->tagname_len; i++) 135 if (udev_s->tagname[i] == '.') 136 udev_s->tagname[i] = '_'; 137 138 udev_s->udev = udev_new(); 139 if (udev_s->udev == NULL) 140 die("udev_new failed"); 141 142 udev_s->devices = udev_enumerate_new(udev_s->udev); 143 if (udev_s->devices == NULL) 144 die("udev_enumerate_new failed"); 145 146 if (udev_enumerate_add_match_tag(udev_s->devices, udev_s->tagname) < 0) 147 die("udev_enumerate_add_match_tag"); 148 149 if (udev_enumerate_scan_devices(udev_s->devices) < 0) 150 die("udev_enumerate_scan failed"); 151 152 udev_s->assigned = udev_enumerate_get_list_entry(udev_s->devices); 153 if (udev_s->assigned == NULL) 154 rc = -1; 155 156 return rc; 157 } 158 159 void snappy_udev_cleanup(struct snappy_udev *udev_s) 160 { 161 // udev_s->assigned does not need to be unreferenced since it is a 162 // pointer into udev_s->devices 163 if (udev_s->devices != NULL) 164 udev_enumerate_unref(udev_s->devices); 165 if (udev_s->udev != NULL) 166 udev_unref(udev_s->udev); 167 } 168 169 void setup_devices_cgroup(const char *security_tag, struct snappy_udev *udev_s) 170 { 171 debug("%s", __func__); 172 // Devices that must always be present 173 const char *static_devices[] = { 174 "/sys/class/mem/null", 175 "/sys/class/mem/full", 176 "/sys/class/mem/zero", 177 "/sys/class/mem/random", 178 "/sys/class/mem/urandom", 179 "/sys/class/tty/tty", 180 "/sys/class/tty/console", 181 "/sys/class/tty/ptmx", 182 NULL, 183 }; 184 185 if (udev_s == NULL) 186 die("snappy_udev is NULL"); 187 if (udev_s->udev == NULL) 188 die("snappy_udev->udev is NULL"); 189 if (udev_s->devices == NULL) 190 die("snappy_udev->devices is NULL"); 191 if (udev_s->assigned == NULL) 192 die("snappy_udev->assigned is NULL"); 193 if (udev_s->tagname_len == 0 194 || udev_s->tagname_len >= MAX_BUF 195 || strnlen(udev_s->tagname, MAX_BUF) != udev_s->tagname_len 196 || udev_s->tagname[udev_s->tagname_len] != '\0') 197 die("snappy_udev->tagname has invalid length"); 198 199 // create devices cgroup controller 200 char cgroup_dir[PATH_MAX] = { 0 }; 201 202 sc_must_snprintf(cgroup_dir, sizeof(cgroup_dir), 203 "/sys/fs/cgroup/devices/%s/", security_tag); 204 205 if (mkdir(cgroup_dir, 0755) < 0 && errno != EEXIST) 206 die("cannot create cgroup hierarchy %s", cgroup_dir); 207 208 // move ourselves into it 209 char cgroup_file[PATH_MAX] = { 0 }; 210 sc_must_snprintf(cgroup_file, sizeof(cgroup_file), "%s%s", cgroup_dir, 211 "tasks"); 212 213 char buf[128] = { 0 }; 214 sc_must_snprintf(buf, sizeof(buf), "%i", getpid()); 215 write_string_to_file(cgroup_file, buf); 216 217 // deny by default. Write 'a' to devices.deny to remove all existing 218 // devices that were added in previous launcher invocations, then add 219 // the static and assigned devices. This ensures that at application 220 // launch the cgroup only has what is currently assigned. 221 sc_must_snprintf(cgroup_file, sizeof(cgroup_file), "%s%s", cgroup_dir, 222 "devices.deny"); 223 write_string_to_file(cgroup_file, "a"); 224 225 // add the common devices 226 for (int i = 0; static_devices[i] != NULL; i++) 227 run_snappy_app_dev_add(udev_s, static_devices[i]); 228 229 // add glob for current and future PTY slaves. We unconditionally add 230 // them since we use a devpts newinstance. Unix98 PTY slaves major 231 // are 136-143. 232 // https://github.com/torvalds/linux/blob/master/Documentation/admin-guide/devices.txt 233 for (unsigned pty_major = 136; pty_major <= 143; pty_major++) { 234 // '/dev/pts/slaves' is only used for debugging and by 235 // /usr/lib/snapd/snap-device-helper to determine if it is a block 236 // device, so just use something to indicate what the 237 // addition is for 238 _run_snappy_app_dev_add_majmin(udev_s, "/dev/pts/slaves", 239 pty_major, UINT_MAX); 240 } 241 242 // nvidia modules are proprietary and therefore aren't in sysfs and 243 // can't be udev tagged. For now, just add existing nvidia devices to 244 // the cgroup unconditionally (AppArmor will still mediate the access). 245 // We'll want to rethink this if snapd needs to mediate access to other 246 // proprietary devices. 247 // 248 // Device major and minor numbers are described in (though nvidia-uvm 249 // currently isn't listed): 250 // https://github.com/torvalds/linux/blob/master/Documentation/admin-guide/devices.txt 251 char nv_path[15] = { 0 }; // /dev/nvidiaXXX 252 const char *nvctl_path = "/dev/nvidiactl"; 253 const char *nvuvm_path = "/dev/nvidia-uvm"; 254 const char *nvidia_modeset_path = "/dev/nvidia-modeset"; 255 256 struct stat sbuf; 257 258 // /dev/nvidia0 through /dev/nvidia254 259 for (unsigned nv_minor = 0; nv_minor < 255; nv_minor++) { 260 sc_must_snprintf(nv_path, sizeof(nv_path), "/dev/nvidia%u", 261 nv_minor); 262 263 // Stop trying to find devices after one is not found. In this 264 // manner, we'll add /dev/nvidia0 and /dev/nvidia1 but stop 265 // trying to find nvidia3 - nvidia254 if nvidia2 is not found. 266 if (stat(nv_path, &sbuf) != 0) { 267 break; 268 } 269 _run_snappy_app_dev_add_majmin(udev_s, nv_path, 270 major(sbuf.st_rdev), 271 minor(sbuf.st_rdev)); 272 } 273 274 // /dev/nvidiactl 275 if (stat(nvctl_path, &sbuf) == 0) { 276 _run_snappy_app_dev_add_majmin(udev_s, nvctl_path, 277 major(sbuf.st_rdev), 278 minor(sbuf.st_rdev)); 279 } 280 // /dev/nvidia-uvm 281 if (stat(nvuvm_path, &sbuf) == 0) { 282 _run_snappy_app_dev_add_majmin(udev_s, nvuvm_path, 283 major(sbuf.st_rdev), 284 minor(sbuf.st_rdev)); 285 } 286 // /dev/nvidia-modeset 287 if (stat(nvidia_modeset_path, &sbuf) == 0) { 288 _run_snappy_app_dev_add_majmin(udev_s, nvidia_modeset_path, 289 major(sbuf.st_rdev), 290 minor(sbuf.st_rdev)); 291 } 292 // /dev/uhid isn't represented in sysfs, so add it to the device cgroup 293 // if it exists and let AppArmor handle the mediation 294 if (stat("/dev/uhid", &sbuf) == 0) { 295 _run_snappy_app_dev_add_majmin(udev_s, "/dev/uhid", 296 major(sbuf.st_rdev), 297 minor(sbuf.st_rdev)); 298 } 299 // add the assigned devices 300 while (udev_s->assigned != NULL) { 301 const char *path = udev_list_entry_get_name(udev_s->assigned); 302 if (path == NULL) 303 die("udev_list_entry_get_name failed"); 304 run_snappy_app_dev_add(udev_s, path); 305 udev_s->assigned = udev_list_entry_get_next(udev_s->assigned); 306 } 307 }