github.com/tompreston/snapd@v0.0.0-20210817193607-954edfcb9611/cmd/snap-device-helper/snap-device-helper.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 <errno.h> 18 #include <libgen.h> 19 #include <limits.h> 20 #include <stdbool.h> 21 #include <stddef.h> 22 #include <stdio.h> 23 #include <stdlib.h> 24 #include <string.h> 25 #include <sys/stat.h> 26 #include <unistd.h> 27 28 #include "../libsnap-confine-private/cleanup-funcs.h" 29 #include "../libsnap-confine-private/device-cgroup-support.h" 30 #include "../libsnap-confine-private/snap.h" 31 #include "../libsnap-confine-private/string-utils.h" 32 #include "../libsnap-confine-private/utils.h" 33 34 #include "snap-device-helper.h" 35 36 static unsigned long must_strtoul(char *str) { 37 char *end = str; 38 unsigned long val = strtoul(str, &end, 10); 39 if (*end != '\0') { 40 die("malformed number \"%s\"", str); 41 } 42 return val; 43 } 44 45 /* udev_to_security_tag converts a udev tag (snap_foo_bar) to security tag 46 * (snap.foo.bar) */ 47 static char *udev_to_security_tag(const char *udev_tag) { 48 if (!sc_startswith(udev_tag, "snap_")) { 49 die("malformed tag \"%s\"", udev_tag); 50 } 51 char *tag = sc_strdup(udev_tag); 52 /* possible udev tags are: 53 * snap_foo_bar 54 * snap_foo_instance_bar 55 * snap_foo_hook_hookname 56 * snap_foo_instance_hook_hookname 57 * convert those to: 58 * snap.foo.bar 59 * snap.foo_instance.bar 60 * snap.foo.hook.hookname 61 * snap.foo_instance.hook.hookname 62 */ 63 size_t tag_len = strlen(tag); 64 if (tag_len < strlen("snap_a_b") || tag_len > SNAP_SECURITY_TAG_MAX_LEN) { 65 die("tag \"%s\" length %zu is incorrect", udev_tag, tag_len); 66 } 67 const size_t snap_prefix_len = strlen("snap_"); 68 /* we know that the tag at least has a snap_ prefix because it was checked 69 * before */ 70 tag[snap_prefix_len - 1] = '.'; 71 char *snap_name_start = tag + snap_prefix_len; 72 char *snap_name_end = NULL; 73 74 /* find the last separator */ 75 char *last_sep = strrchr(tag, '_'); 76 if (last_sep == NULL) { 77 die("missing app name in tag \"%s\"", udev_tag); 78 } 79 *last_sep = '.'; 80 /* we are left with the following possibilities: 81 * snap.foo.bar 82 * snap.foo_instance.bar 83 * snap.foo_instance_hook.hookname 84 * snap.foo_hook.hookname 85 */ 86 char *more_sep = strchr(tag, '_'); 87 if (more_sep == NULL) { 88 /* no more separators, we have snap.foo.bar */ 89 snap_name_end = last_sep; 90 } else { 91 /* we are left with the following possibilities: 92 * snap.foo_instance.bar 93 * snap.foo_instance_hook.hookname 94 * snap.foo_hook.hookname 95 */ 96 97 /* do we have another separator? */ 98 char *another_sep = strchr(more_sep + 1, '_'); 99 if (another_sep == NULL) { 100 /* no, so we are left with the following possibilities: 101 * snap.foo_instance.bar 102 * snap.foo_hook.hookname 103 * 104 * there is ambiguity and we cannot correctly handle an instance named 105 * 'hook' as snap.foo_hook.bar could be snap.foo.hook.bar or 106 * snap.foo_hook.bar, for simplicity assume snap.foo.hook.bar more likely. 107 */ 108 if (sc_startswith(more_sep, "_hook.")) { 109 /* snap.foo_hook.bar -> snap.foo.hook.bar */ 110 *more_sep = '.'; 111 snap_name_end = more_sep; 112 } else { 113 snap_name_end = last_sep; 114 } 115 } else { 116 /* we have found 2 separators, so this is the only possibility: 117 * snap.foo_instance_hook.hookname 118 * which should be converted to: 119 * snap.foo_instance.hook.hookname 120 */ 121 *another_sep = '.'; 122 snap_name_end = another_sep; 123 } 124 } 125 if (snap_name_end <= snap_name_start) { 126 die("missing snap name in tag \"%s\"", udev_tag); 127 } 128 129 /* let's validate the tag, but we need to extract the snap name first */ 130 char snap_instance[SNAP_INSTANCE_LEN + 1] = {0}; 131 size_t snap_instance_len = (size_t)(snap_name_end - snap_name_start); 132 if (snap_instance_len >= sizeof(snap_instance)) { 133 die("snap instance of tag \"%s\" is too long", udev_tag); 134 } 135 memcpy(snap_instance, snap_name_start, snap_instance_len); 136 debug("snap instance \"%s\"", snap_instance); 137 138 if (!sc_security_tag_validate(tag, snap_instance)) { 139 die("security tag \"%s\" for snap \"%s\" is not valid", tag, snap_instance); 140 } 141 142 return tag; 143 } 144 145 /* sysroot can be mocked in tests */ 146 const char *sysroot = "/"; 147 148 int snap_device_helper_run(const struct sdh_invocation *inv) { 149 const char *action = inv->action; 150 const char *udev_tagname = inv->tagname; 151 const char *devpath = inv->devpath; 152 const char *majmin = inv->majmin; 153 154 bool allow = false; 155 156 if (strlen(majmin) < 3) { 157 die("no or malformed major/minor \"%s\"", majmin); 158 } 159 if (strlen(devpath) <= strlen("/devices/")) { 160 die("no or malformed devpath \"%s\"", devpath); 161 } 162 if (sc_streq(action, "add") || sc_streq(action, "change")) { 163 allow = true; 164 } else if (sc_streq(action, "remove")) { 165 allow = false; 166 } else { 167 die("ERROR: unknown action \"%s\"", action); 168 } 169 170 char *security_tag SC_CLEANUP(sc_cleanup_string) = udev_to_security_tag(udev_tagname); 171 172 int devtype = S_IFCHR; 173 /* find out the actual subsystem */ 174 char sysdevsubsystem[PATH_MAX] = {0}; 175 char fullsubsystem[PATH_MAX] = {0}; 176 sc_must_snprintf(sysdevsubsystem, sizeof(sysdevsubsystem), "%s/sys/%s/subsystem", sysroot, devpath); 177 if (readlink(sysdevsubsystem, fullsubsystem, sizeof(fullsubsystem)) < 0) { 178 die("cannot read symlink %s", sysdevsubsystem); 179 } 180 char *subsystem = basename(fullsubsystem); 181 if (sc_streq(subsystem, "block")) { 182 devtype = S_IFBLK; 183 } 184 sc_device_cgroup *cgroup = sc_device_cgroup_new(security_tag, SC_DEVICE_CGROUP_FROM_EXISTING); 185 if (!cgroup) { 186 if (errno == ENOENT) { 187 debug("device cgroup does not exist"); 188 return 0; 189 } 190 die("cannot create device cgroup wrapper"); 191 } 192 193 /* the format is <major>:<minor> */ 194 char *major SC_CLEANUP(sc_cleanup_string) = sc_strdup(majmin); 195 char *sep = strchr(major, ':'); 196 // sep is always \0 terminated so this checks if the part after ":" is empty 197 if (sep == NULL || sep[1] == '\0') { 198 /* not found, or a last character */ 199 die("malformed major:minor string: %s", major); 200 } 201 /* set an end for the major number string */ 202 *sep = '\0'; 203 sep++; 204 char *minor = sep; 205 206 int devmajor = must_strtoul(major); 207 int devminor = must_strtoul(minor); 208 debug("%s device type is %s, %d:%d", inv->action, (devtype == S_IFCHR) ? "char" : "block", devmajor, devminor); 209 if (allow) { 210 sc_device_cgroup_allow(cgroup, devtype, devmajor, devminor); 211 } else { 212 sc_device_cgroup_deny(cgroup, devtype, devmajor, devminor); 213 } 214 215 return 0; 216 }