1/* 2 * Copyright (C) 2003-2013 Kay Sievers <kay@vrfy.org> 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 as published by 6 * the Free Software Foundation, either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18#include <stdlib.h> 19#include <string.h> 20#include <stdio.h> 21#include <stddef.h> 22#include <stdbool.h> 23#include <fcntl.h> 24#include <unistd.h> 25#include <errno.h> 26#include <grp.h> 27#include <dirent.h> 28#include <sys/time.h> 29#include <sys/stat.h> 30#include <sys/types.h> 31#include <sys/sysmacros.h> 32 33#include "udev.h" 34#include "smack-util.h" 35 36static int node_symlink(struct udev_device *dev, const char *node, const char *slink) { 37 struct stat stats; 38 char target[UTIL_PATH_SIZE]; 39 char *s; 40 size_t l; 41 char slink_tmp[UTIL_PATH_SIZE + 32]; 42 int i = 0; 43 int tail = 0; 44 int err = 0; 45 46 /* use relative link */ 47 target[0] = '\0'; 48 while (node[i] && (node[i] == slink[i])) { 49 if (node[i] == '/') 50 tail = i+1; 51 i++; 52 } 53 s = target; 54 l = sizeof(target); 55 while (slink[i] != '\0') { 56 if (slink[i] == '/') 57 l = strpcpy(&s, l, "../"); 58 i++; 59 } 60 l = strscpy(s, l, &node[tail]); 61 if (l == 0) { 62 err = -EINVAL; 63 goto exit; 64 } 65 66 /* preserve link with correct target, do not replace node of other device */ 67 if (lstat(slink, &stats) == 0) { 68 if (S_ISBLK(stats.st_mode) || S_ISCHR(stats.st_mode)) { 69 log_error("conflicting device node '%s' found, link to '%s' will not be created", slink, node); 70 goto exit; 71 } else if (S_ISLNK(stats.st_mode)) { 72 char buf[UTIL_PATH_SIZE]; 73 int len; 74 75 len = readlink(slink, buf, sizeof(buf)); 76 if (len > 0 && len < (int)sizeof(buf)) { 77 buf[len] = '\0'; 78 if (streq(target, buf)) { 79 log_debug("preserve already existing symlink '%s' to '%s'", slink, target); 80 label_fix(slink, true, false); 81 utimensat(AT_FDCWD, slink, NULL, AT_SYMLINK_NOFOLLOW); 82 goto exit; 83 } 84 } 85 } 86 } else { 87 log_debug("creating symlink '%s' to '%s'", slink, target); 88 do { 89 err = mkdir_parents_label(slink, 0755); 90 if (err != 0 && err != -ENOENT) 91 break; 92 mac_selinux_create_file_prepare(slink, S_IFLNK); 93 err = symlink(target, slink); 94 if (err != 0) 95 err = -errno; 96 mac_selinux_create_file_clear(); 97 } while (err == -ENOENT); 98 if (err == 0) 99 goto exit; 100 } 101 102 log_debug("atomically replace '%s'", slink); 103 strscpyl(slink_tmp, sizeof(slink_tmp), slink, ".tmp-", udev_device_get_id_filename(dev), NULL); 104 unlink(slink_tmp); 105 do { 106 err = mkdir_parents_label(slink_tmp, 0755); 107 if (err != 0 && err != -ENOENT) 108 break; 109 mac_selinux_create_file_prepare(slink_tmp, S_IFLNK); 110 err = symlink(target, slink_tmp); 111 if (err != 0) 112 err = -errno; 113 mac_selinux_create_file_clear(); 114 } while (err == -ENOENT); 115 if (err != 0) { 116 log_error_errno(errno, "symlink '%s' '%s' failed: %m", target, slink_tmp); 117 goto exit; 118 } 119 err = rename(slink_tmp, slink); 120 if (err != 0) { 121 log_error_errno(errno, "rename '%s' '%s' failed: %m", slink_tmp, slink); 122 unlink(slink_tmp); 123 } 124exit: 125 return err; 126} 127 128/* find device node of device with highest priority */ 129static const char *link_find_prioritized(struct udev_device *dev, bool add, const char *stackdir, char *buf, size_t bufsize) { 130 struct udev *udev = udev_device_get_udev(dev); 131 DIR *dir; 132 int priority = 0; 133 const char *target = NULL; 134 135 if (add) { 136 priority = udev_device_get_devlink_priority(dev); 137 strscpy(buf, bufsize, udev_device_get_devnode(dev)); 138 target = buf; 139 } 140 141 dir = opendir(stackdir); 142 if (dir == NULL) 143 return target; 144 for (;;) { 145 struct udev_device *dev_db; 146 struct dirent *dent; 147 148 dent = readdir(dir); 149 if (dent == NULL || dent->d_name[0] == '\0') 150 break; 151 if (dent->d_name[0] == '.') 152 continue; 153 154 log_debug("found '%s' claiming '%s'", dent->d_name, stackdir); 155 156 /* did we find ourself? */ 157 if (streq(dent->d_name, udev_device_get_id_filename(dev))) 158 continue; 159 160 dev_db = udev_device_new_from_device_id(udev, dent->d_name); 161 if (dev_db != NULL) { 162 const char *devnode; 163 164 devnode = udev_device_get_devnode(dev_db); 165 if (devnode != NULL) { 166 if (target == NULL || udev_device_get_devlink_priority(dev_db) > priority) { 167 log_debug("'%s' claims priority %i for '%s'", 168 udev_device_get_syspath(dev_db), udev_device_get_devlink_priority(dev_db), stackdir); 169 priority = udev_device_get_devlink_priority(dev_db); 170 strscpy(buf, bufsize, devnode); 171 target = buf; 172 } 173 } 174 udev_device_unref(dev_db); 175 } 176 } 177 closedir(dir); 178 return target; 179} 180 181/* manage "stack of names" with possibly specified device priorities */ 182static void link_update(struct udev_device *dev, const char *slink, bool add) { 183 char name_enc[UTIL_PATH_SIZE]; 184 char filename[UTIL_PATH_SIZE * 2]; 185 char dirname[UTIL_PATH_SIZE]; 186 const char *target; 187 char buf[UTIL_PATH_SIZE]; 188 189 util_path_encode(slink + strlen("/dev"), name_enc, sizeof(name_enc)); 190 strscpyl(dirname, sizeof(dirname), UDEV_ROOT_RUN "/udev/links/", name_enc, NULL); 191 strscpyl(filename, sizeof(filename), dirname, "/", udev_device_get_id_filename(dev), NULL); 192 193 if (!add && unlink(filename) == 0) 194 rmdir(dirname); 195 196 target = link_find_prioritized(dev, add, dirname, buf, sizeof(buf)); 197 if (target == NULL) { 198 log_debug("no reference left, remove '%s'", slink); 199 if (unlink(slink) == 0) 200 rmdir_parents(slink, "/"); 201 } else { 202 log_debug("creating link '%s' to '%s'", slink, target); 203 node_symlink(dev, target, slink); 204 } 205 206 if (add) { 207 int err; 208 209 do { 210 int fd; 211 212 err = mkdir_parents(filename, 0755); 213 if (err != 0 && err != -ENOENT) 214 break; 215 fd = open(filename, O_WRONLY|O_CREAT|O_CLOEXEC|O_TRUNC|O_NOFOLLOW, 0444); 216 if (fd >= 0) 217 close(fd); 218 else 219 err = -errno; 220 } while (err == -ENOENT); 221 } 222} 223 224void udev_node_update_old_links(struct udev_device *dev, struct udev_device *dev_old) { 225 struct udev_list_entry *list_entry; 226 227 /* update possible left-over symlinks */ 228 udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(dev_old)) { 229 const char *name = udev_list_entry_get_name(list_entry); 230 struct udev_list_entry *list_entry_current; 231 int found; 232 233 /* check if old link name still belongs to this device */ 234 found = 0; 235 udev_list_entry_foreach(list_entry_current, udev_device_get_devlinks_list_entry(dev)) { 236 const char *name_current = udev_list_entry_get_name(list_entry_current); 237 238 if (streq(name, name_current)) { 239 found = 1; 240 break; 241 } 242 } 243 if (found) 244 continue; 245 246 log_debug("update old name, '%s' no longer belonging to '%s'", 247 name, udev_device_get_devpath(dev)); 248 link_update(dev, name, false); 249 } 250} 251 252static int node_permissions_apply(struct udev_device *dev, bool apply, 253 mode_t mode, uid_t uid, gid_t gid, 254 struct udev_list *seclabel_list) { 255 const char *devnode = udev_device_get_devnode(dev); 256 dev_t devnum = udev_device_get_devnum(dev); 257 struct stat stats; 258 struct udev_list_entry *entry; 259 int err = 0; 260 261 if (streq(udev_device_get_subsystem(dev), "block")) 262 mode |= S_IFBLK; 263 else 264 mode |= S_IFCHR; 265 266 if (lstat(devnode, &stats) != 0) { 267 err = -errno; 268 log_debug_errno(errno, "can not stat() node '%s' (%m)", devnode); 269 goto out; 270 } 271 272 if (((stats.st_mode & S_IFMT) != (mode & S_IFMT)) || (stats.st_rdev != devnum)) { 273 err = -EEXIST; 274 log_debug("found node '%s' with non-matching devnum %s, skip handling", 275 udev_device_get_devnode(dev), udev_device_get_id_filename(dev)); 276 goto out; 277 } 278 279 if (apply) { 280 bool selinux = false; 281 bool smack = false; 282 283 if ((stats.st_mode & 0777) != (mode & 0777) || stats.st_uid != uid || stats.st_gid != gid) { 284 log_debug("set permissions %s, %#o, uid=%u, gid=%u", devnode, mode, uid, gid); 285 err = chmod(devnode, mode); 286 if (err < 0) 287 log_warning_errno(errno, "setting mode of %s to %#o failed: %m", devnode, mode); 288 err = chown(devnode, uid, gid); 289 if (err < 0) 290 log_warning_errno(errno, "setting owner of %s to uid=%u, gid=%u failed: %m", devnode, uid, gid); 291 } else { 292 log_debug("preserve permissions %s, %#o, uid=%u, gid=%u", devnode, mode, uid, gid); 293 } 294 295 /* apply SECLABEL{$module}=$label */ 296 udev_list_entry_foreach(entry, udev_list_get_entry(seclabel_list)) { 297 const char *name, *label; 298 int r; 299 300 name = udev_list_entry_get_name(entry); 301 label = udev_list_entry_get_value(entry); 302 303 if (streq(name, "selinux")) { 304 selinux = true; 305 306 r = mac_selinux_apply(devnode, label); 307 if (r < 0) 308 log_error_errno(r, "SECLABEL: failed to set SELinux label '%s': %m", label); 309 else 310 log_debug("SECLABEL: set SELinux label '%s'", label); 311 312 } else if (streq(name, "smack")) { 313 smack = true; 314 315 r = mac_smack_apply(devnode, label); 316 if (r < 0) 317 log_error_errno(r, "SECLABEL: failed to set SMACK label '%s': %m", label); 318 else 319 log_debug("SECLABEL: set SMACK label '%s'", label); 320 321 } else 322 log_error("SECLABEL: unknown subsystem, ignoring '%s'='%s'", name, label); 323 } 324 325 /* set the defaults */ 326 if (!selinux) 327 mac_selinux_fix(devnode, true, false); 328 if (!smack) 329 mac_smack_apply(devnode, NULL); 330 } 331 332 /* always update timestamp when we re-use the node, like on media change events */ 333 utimensat(AT_FDCWD, devnode, NULL, 0); 334out: 335 return err; 336} 337 338void udev_node_add(struct udev_device *dev, bool apply, 339 mode_t mode, uid_t uid, gid_t gid, 340 struct udev_list *seclabel_list) { 341 char filename[UTIL_PATH_SIZE]; 342 struct udev_list_entry *list_entry; 343 344 log_debug("handling device node '%s', devnum=%s, mode=%#o, uid="UID_FMT", gid="GID_FMT, 345 udev_device_get_devnode(dev), udev_device_get_id_filename(dev), mode, uid, gid); 346 347 if (node_permissions_apply(dev, apply, mode, uid, gid, seclabel_list) < 0) 348 return; 349 350 /* always add /dev/{block,char}/$major:$minor */ 351 snprintf(filename, sizeof(filename), "/dev/%s/%u:%u", 352 streq(udev_device_get_subsystem(dev), "block") ? "block" : "char", 353 major(udev_device_get_devnum(dev)), minor(udev_device_get_devnum(dev))); 354 node_symlink(dev, udev_device_get_devnode(dev), filename); 355 356 /* create/update symlinks, add symlinks to name index */ 357 udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(dev)) 358 link_update(dev, udev_list_entry_get_name(list_entry), true); 359} 360 361void udev_node_remove(struct udev_device *dev) { 362 struct udev_list_entry *list_entry; 363 char filename[UTIL_PATH_SIZE]; 364 365 /* remove/update symlinks, remove symlinks from name index */ 366 udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(dev)) 367 link_update(dev, udev_list_entry_get_name(list_entry), false); 368 369 /* remove /dev/{block,char}/$major:$minor */ 370 snprintf(filename, sizeof(filename), "/dev/%s/%u:%u", 371 streq(udev_device_get_subsystem(dev), "block") ? "block" : "char", 372 major(udev_device_get_devnum(dev)), minor(udev_device_get_devnum(dev))); 373 unlink(filename); 374} 375