github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/cmd/libsnap-confine-private/device-cgroup-support.c (about) 1 /* 2 * Copyright (C) 2021 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 <errno.h> 20 #include <fcntl.h> 21 #include <stdarg.h> 22 #include <string.h> 23 #include <sys/stat.h> 24 25 #include "cgroup-support.h" 26 #include "cleanup-funcs.h" 27 #include "snap.h" 28 #include "string-utils.h" 29 #include "utils.h" 30 31 #include "device-cgroup-support.h" 32 33 typedef struct sc_cgroup_fds { 34 int devices_allow_fd; 35 int devices_deny_fd; 36 int cgroup_procs_fd; 37 } sc_cgroup_fds; 38 39 static sc_cgroup_fds sc_cgroup_fds_new(void) { 40 /* Note that -1 is the neutral value for a file descriptor. 41 * This is relevant as a cleanup handler for sc_cgroup_fds, 42 * closes all file descriptors that are not -1. */ 43 sc_cgroup_fds empty = {-1, -1, -1}; 44 return empty; 45 } 46 47 struct sc_device_cgroup { 48 bool is_v2; 49 char *security_tag; 50 union { 51 struct { 52 sc_cgroup_fds fds; 53 } v1; 54 struct { 55 int cgroup_fd; 56 int devmap_fd; 57 char *tag; 58 } v2; 59 }; 60 }; 61 62 __attribute__((format(printf, 2, 3))) static void sc_dprintf(int fd, const char *format, ...); 63 64 static int sc_udev_open_cgroup_v1(const char *security_tag, int flags, sc_cgroup_fds *fds); 65 static void sc_cleanup_cgroup_fds(sc_cgroup_fds *fds); 66 67 static int _sc_cgroup_v1_init(sc_device_cgroup *self, int flags) { 68 self->v1.fds = sc_cgroup_fds_new(); 69 70 /* initialize to something sane */ 71 if (sc_udev_open_cgroup_v1(self->security_tag, flags, &self->v1.fds) < 0) { 72 if (flags == SC_DEVICE_CGROUP_FROM_EXISTING) { 73 return -1; 74 } 75 die("cannot prepare cgroup v1 device hierarchy"); 76 } 77 /* Deny device access by default. 78 * 79 * Write 'a' to devices.deny to remove all existing devices that were added 80 * in previous launcher invocations, then add the static and assigned 81 * devices. This ensures that at application launch the cgroup only has 82 * what is currently assigned. */ 83 sc_dprintf(self->v1.fds.devices_deny_fd, "a"); 84 return 0; 85 } 86 87 static void _sc_cgroup_v1_close(sc_device_cgroup *self) { sc_cleanup_cgroup_fds(&self->v1.fds); } 88 89 static void _sc_cgroup_v1_action(int fd, int kind, int major, int minor) { 90 if ((uint32_t)minor != SC_DEVICE_MINOR_ANY) { 91 sc_dprintf(fd, "%c %u:%u rwm\n", (kind == S_IFCHR) ? 'c' : 'b', major, minor); 92 } else { 93 /* use a mask to allow/deny all minor devices for that major */ 94 sc_dprintf(fd, "%c %u:* rwm\n", (kind == S_IFCHR) ? 'c' : 'b', major); 95 } 96 } 97 98 static void _sc_cgroup_v1_allow(sc_device_cgroup *self, int kind, int major, int minor) { 99 _sc_cgroup_v1_action(self->v1.fds.devices_allow_fd, kind, major, minor); 100 } 101 102 static void _sc_cgroup_v1_deny(sc_device_cgroup *self, int kind, int major, int minor) { 103 _sc_cgroup_v1_action(self->v1.fds.devices_deny_fd, kind, major, minor); 104 } 105 106 static void _sc_cgroup_v1_attach_pid(sc_device_cgroup *self, pid_t pid) { 107 sc_dprintf(self->v1.fds.cgroup_procs_fd, "%i\n", getpid()); 108 } 109 110 static void sc_device_cgroup_close(sc_device_cgroup *self); 111 112 sc_device_cgroup *sc_device_cgroup_new(const char *security_tag, int flags) { 113 sc_device_cgroup *self = calloc(1, sizeof(sc_device_cgroup)); 114 if (self == NULL) { 115 die("cannot allocate device cgroup wrapper"); 116 } 117 self->is_v2 = sc_cgroup_is_v2(); 118 self->security_tag = sc_strdup(security_tag); 119 120 int ret = 0; 121 if (!self->is_v2) { 122 ret = _sc_cgroup_v1_init(self, flags); 123 } 124 125 if (ret < 0) { 126 sc_device_cgroup_close(self); 127 return NULL; 128 } 129 130 return self; 131 } 132 133 static void sc_device_cgroup_close(sc_device_cgroup *self) { 134 if (!self->is_v2) { 135 _sc_cgroup_v1_close(self); 136 } 137 sc_cleanup_string(&self->security_tag); 138 free(self); 139 } 140 141 void sc_device_cgroup_cleanup(sc_device_cgroup **self) { 142 if (*self == NULL) { 143 return; 144 } 145 sc_device_cgroup_close(*self); 146 *self = NULL; 147 } 148 149 int sc_device_cgroup_allow(sc_device_cgroup *self, int kind, int major, int minor) { 150 if (kind != S_IFCHR && kind != S_IFBLK) { 151 die("unsupported device kind 0x%04x", kind); 152 } 153 if (!self->is_v2) { 154 _sc_cgroup_v1_allow(self, kind, major, minor); 155 } 156 return 0; 157 } 158 159 int sc_device_cgroup_deny(sc_device_cgroup *self, int kind, int major, int minor) { 160 if (kind != S_IFCHR && kind != S_IFBLK) { 161 die("unsupported device kind 0x%04x", kind); 162 } 163 if (!self->is_v2) { 164 _sc_cgroup_v1_deny(self, kind, major, minor); 165 } 166 return 0; 167 } 168 169 int sc_device_cgroup_attach_pid(sc_device_cgroup *self, pid_t pid) { 170 if (!self->is_v2) { 171 _sc_cgroup_v1_attach_pid(self, pid); 172 } 173 return 0; 174 } 175 176 static void sc_dprintf(int fd, const char *format, ...) { 177 va_list ap1; 178 va_list ap2; 179 int n_expected, n_actual; 180 181 va_start(ap1, format); 182 va_copy(ap2, ap1); 183 n_expected = vsnprintf(NULL, 0, format, ap2); 184 n_actual = vdprintf(fd, format, ap1); 185 if (n_actual == -1 || n_expected != n_actual) { 186 die("cannot write to fd %d", fd); 187 } 188 va_end(ap2); 189 va_end(ap1); 190 } 191 192 static int sc_udev_open_cgroup_v1(const char *security_tag, int flags, sc_cgroup_fds *fds) { 193 /* Open /sys/fs/cgroup */ 194 const char *cgroup_path = "/sys/fs/cgroup"; 195 int SC_CLEANUP(sc_cleanup_close) cgroup_fd = -1; 196 cgroup_fd = open(cgroup_path, O_PATH | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW); 197 if (cgroup_fd < 0) { 198 die("cannot open %s", cgroup_path); 199 } 200 201 /* Open devices relative to /sys/fs/cgroup */ 202 const char *devices_relpath = "devices"; 203 int SC_CLEANUP(sc_cleanup_close) devices_fd = -1; 204 devices_fd = openat(cgroup_fd, devices_relpath, O_PATH | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW); 205 if (devices_fd < 0) { 206 die("cannot open %s/%s", cgroup_path, devices_relpath); 207 } 208 209 /* Open snap.$SNAP_NAME.$APP_NAME relative to /sys/fs/cgroup/devices, 210 * creating the directory if necessary. Note that we always chown the 211 * resulting directory to root:root. */ 212 const char *security_tag_relpath = security_tag; 213 sc_identity old = sc_set_effective_identity(sc_root_group_identity()); 214 if (mkdirat(devices_fd, security_tag_relpath, 0755) < 0) { 215 if (errno != EEXIST) { 216 die("cannot create directory %s/%s/%s", cgroup_path, devices_relpath, security_tag_relpath); 217 } 218 } 219 (void)sc_set_effective_identity(old); 220 221 int SC_CLEANUP(sc_cleanup_close) security_tag_fd = -1; 222 security_tag_fd = openat(devices_fd, security_tag_relpath, O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW); 223 if (security_tag_fd < 0) { 224 die("cannot open %s/%s/%s", cgroup_path, devices_relpath, security_tag_relpath); 225 } 226 227 /* Open devices.allow relative to /sys/fs/cgroup/devices/snap.$SNAP_NAME.$APP_NAME */ 228 const char *devices_allow_relpath = "devices.allow"; 229 int SC_CLEANUP(sc_cleanup_close) devices_allow_fd = -1; 230 devices_allow_fd = openat(security_tag_fd, devices_allow_relpath, O_WRONLY | O_CLOEXEC | O_NOFOLLOW); 231 if (devices_allow_fd < 0) { 232 die("cannot open %s/%s/%s/%s", cgroup_path, devices_relpath, security_tag_relpath, devices_allow_relpath); 233 } 234 235 /* Open devices.deny relative to /sys/fs/cgroup/devices/snap.$SNAP_NAME.$APP_NAME */ 236 const char *devices_deny_relpath = "devices.deny"; 237 int SC_CLEANUP(sc_cleanup_close) devices_deny_fd = -1; 238 devices_deny_fd = openat(security_tag_fd, devices_deny_relpath, O_WRONLY | O_CLOEXEC | O_NOFOLLOW); 239 if (devices_deny_fd < 0) { 240 if (flags == SC_DEVICE_CGROUP_FROM_EXISTING && errno == ENOENT) { 241 return -1; 242 } 243 die("cannot open %s/%s/%s/%s", cgroup_path, devices_relpath, security_tag_relpath, devices_deny_relpath); 244 } 245 246 /* Open cgroup.procs relative to /sys/fs/cgroup/devices/snap.$SNAP_NAME.$APP_NAME */ 247 const char *cgroup_procs_relpath = "cgroup.procs"; 248 int SC_CLEANUP(sc_cleanup_close) cgroup_procs_fd = -1; 249 cgroup_procs_fd = openat(security_tag_fd, cgroup_procs_relpath, O_WRONLY | O_CLOEXEC | O_NOFOLLOW); 250 if (cgroup_procs_fd < 0) { 251 if (flags == SC_DEVICE_CGROUP_FROM_EXISTING && errno == ENOENT) { 252 return -1; 253 } 254 die("cannot open %s/%s/%s/%s", cgroup_path, devices_relpath, security_tag_relpath, cgroup_procs_relpath); 255 } 256 257 /* Everything worked so pack the result and "move" the descriptors over so 258 * that they are not closed by the cleanup functions associated with the 259 * individual variables. */ 260 fds->devices_allow_fd = devices_allow_fd; 261 fds->devices_deny_fd = devices_deny_fd; 262 fds->cgroup_procs_fd = cgroup_procs_fd; 263 /* Reset the locals so that they are not closed by the cleanup handlers. */ 264 devices_allow_fd = -1; 265 devices_deny_fd = -1; 266 cgroup_procs_fd = -1; 267 return 0; 268 } 269 270 static void sc_cleanup_cgroup_fds(sc_cgroup_fds *fds) { 271 if (fds != NULL) { 272 sc_cleanup_close(&fds->devices_allow_fd); 273 sc_cleanup_close(&fds->devices_deny_fd); 274 sc_cleanup_close(&fds->cgroup_procs_fd); 275 } 276 }