devices.cpp revision 81f5d3ebef2c3789737bf718fc2a2cdd7b9e8b33
1/* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#include "devices.h" 18 19#include <errno.h> 20#include <fnmatch.h> 21#include <sys/sysmacros.h> 22#include <unistd.h> 23 24#include <memory> 25 26#include <android-base/logging.h> 27#include <android-base/stringprintf.h> 28#include <android-base/strings.h> 29#include <private/android_filesystem_config.h> 30#include <selinux/android.h> 31#include <selinux/selinux.h> 32 33#include "ueventd.h" 34#include "util.h" 35 36#ifdef _INIT_INIT_H 37#error "Do not include init.h in files used by ueventd or watchdogd; it will expose init's globals" 38#endif 39 40using android::base::Basename; 41using android::base::Dirname; 42using android::base::Readlink; 43using android::base::Realpath; 44using android::base::StartsWith; 45using android::base::StringPrintf; 46 47namespace android { 48namespace init { 49 50/* Given a path that may start with a PCI device, populate the supplied buffer 51 * with the PCI domain/bus number and the peripheral ID and return 0. 52 * If it doesn't start with a PCI device, or there is some error, return -1 */ 53static bool FindPciDevicePrefix(const std::string& path, std::string* result) { 54 result->clear(); 55 56 if (!StartsWith(path, "/devices/pci")) return false; 57 58 /* Beginning of the prefix is the initial "pci" after "/devices/" */ 59 std::string::size_type start = 9; 60 61 /* End of the prefix is two path '/' later, capturing the domain/bus number 62 * and the peripheral ID. Example: pci0000:00/0000:00:1f.2 */ 63 auto end = path.find('/', start); 64 if (end == std::string::npos) return false; 65 66 end = path.find('/', end + 1); 67 if (end == std::string::npos) return false; 68 69 auto length = end - start; 70 if (length <= 4) { 71 // The minimum string that will get to this check is 'pci/', which is malformed, 72 // so return false 73 return false; 74 } 75 76 *result = path.substr(start, length); 77 return true; 78} 79 80/* Given a path that may start with a virtual block device, populate 81 * the supplied buffer with the virtual block device ID and return 0. 82 * If it doesn't start with a virtual block device, or there is some 83 * error, return -1 */ 84static bool FindVbdDevicePrefix(const std::string& path, std::string* result) { 85 result->clear(); 86 87 if (!StartsWith(path, "/devices/vbd-")) return false; 88 89 /* Beginning of the prefix is the initial "vbd-" after "/devices/" */ 90 std::string::size_type start = 13; 91 92 /* End of the prefix is one path '/' later, capturing the 93 virtual block device ID. Example: 768 */ 94 auto end = path.find('/', start); 95 if (end == std::string::npos) return false; 96 97 auto length = end - start; 98 if (length == 0) return false; 99 100 *result = path.substr(start, length); 101 return true; 102} 103 104Permissions::Permissions(const std::string& name, mode_t perm, uid_t uid, gid_t gid) 105 : name_(name), perm_(perm), uid_(uid), gid_(gid), prefix_(false), wildcard_(false) { 106 // Set 'prefix_' or 'wildcard_' based on the below cases: 107 // 108 // 1) No '*' in 'name' -> Neither are set and Match() checks a given path for strict 109 // equality with 'name' 110 // 111 // 2) '*' only appears as the last character in 'name' -> 'prefix'_ is set to true and 112 // Match() checks if 'name' is a prefix of a given path. 113 // 114 // 3) '*' appears elsewhere -> 'wildcard_' is set to true and Match() uses fnmatch() 115 // with FNM_PATHNAME to compare 'name' to a given path. 116 117 auto wildcard_position = name_.find('*'); 118 if (wildcard_position != std::string::npos) { 119 if (wildcard_position == name_.length() - 1) { 120 prefix_ = true; 121 name_.pop_back(); 122 } else { 123 wildcard_ = true; 124 } 125 } 126} 127 128bool Permissions::Match(const std::string& path) const { 129 if (prefix_) return StartsWith(path, name_.c_str()); 130 if (wildcard_) return fnmatch(name_.c_str(), path.c_str(), FNM_PATHNAME) == 0; 131 return path == name_; 132} 133 134bool SysfsPermissions::MatchWithSubsystem(const std::string& path, 135 const std::string& subsystem) const { 136 std::string path_basename = Basename(path); 137 if (name().find(subsystem) != std::string::npos) { 138 if (Match("/sys/class/" + subsystem + "/" + path_basename)) return true; 139 if (Match("/sys/bus/" + subsystem + "/devices/" + path_basename)) return true; 140 } 141 return Match(path); 142} 143 144void SysfsPermissions::SetPermissions(const std::string& path) const { 145 std::string attribute_file = path + "/" + attribute_; 146 LOG(VERBOSE) << "fixup " << attribute_file << " " << uid() << " " << gid() << " " << std::oct 147 << perm(); 148 149 if (access(attribute_file.c_str(), F_OK) == 0) { 150 if (chown(attribute_file.c_str(), uid(), gid()) != 0) { 151 PLOG(ERROR) << "chown(" << attribute_file << ", " << uid() << ", " << gid() 152 << ") failed"; 153 } 154 if (chmod(attribute_file.c_str(), perm()) != 0) { 155 PLOG(ERROR) << "chmod(" << attribute_file << ", " << perm() << ") failed"; 156 } 157 } 158} 159 160// Given a path that may start with a platform device, find the parent platform device by finding a 161// parent directory with a 'subsystem' symlink that points to the platform bus. 162// If it doesn't start with a platform device, return false 163bool DeviceHandler::FindPlatformDevice(std::string path, std::string* platform_device_path) const { 164 platform_device_path->clear(); 165 166 // Uevents don't contain the mount point, so we need to add it here. 167 path.insert(0, sysfs_mount_point_); 168 169 std::string directory = Dirname(path); 170 171 while (directory != "/" && directory != ".") { 172 std::string subsystem_link_path; 173 if (Realpath(directory + "/subsystem", &subsystem_link_path) && 174 subsystem_link_path == sysfs_mount_point_ + "/bus/platform") { 175 // We need to remove the mount point that we added above before returning. 176 directory.erase(0, sysfs_mount_point_.size()); 177 *platform_device_path = directory; 178 return true; 179 } 180 181 auto last_slash = path.rfind('/'); 182 if (last_slash == std::string::npos) return false; 183 184 path.erase(last_slash); 185 directory = Dirname(path); 186 } 187 188 return false; 189} 190 191void DeviceHandler::FixupSysPermissions(const std::string& upath, 192 const std::string& subsystem) const { 193 // upaths omit the "/sys" that paths in this list 194 // contain, so we prepend it... 195 std::string path = "/sys" + upath; 196 197 for (const auto& s : sysfs_permissions_) { 198 if (s.MatchWithSubsystem(path, subsystem)) s.SetPermissions(path); 199 } 200 201 if (!skip_restorecon_ && access(path.c_str(), F_OK) == 0) { 202 LOG(VERBOSE) << "restorecon_recursive: " << path; 203 if (selinux_android_restorecon(path.c_str(), SELINUX_ANDROID_RESTORECON_RECURSE) != 0) { 204 PLOG(ERROR) << "selinux_android_restorecon(" << path << ") failed"; 205 } 206 } 207} 208 209std::tuple<mode_t, uid_t, gid_t> DeviceHandler::GetDevicePermissions( 210 const std::string& path, const std::vector<std::string>& links) const { 211 // Search the perms list in reverse so that ueventd.$hardware can override ueventd.rc. 212 for (auto it = dev_permissions_.crbegin(); it != dev_permissions_.crend(); ++it) { 213 if (it->Match(path) || std::any_of(links.cbegin(), links.cend(), 214 [it](const auto& link) { return it->Match(link); })) { 215 return {it->perm(), it->uid(), it->gid()}; 216 } 217 } 218 /* Default if nothing found. */ 219 return {0600, 0, 0}; 220} 221 222void DeviceHandler::MakeDevice(const std::string& path, int block, int major, int minor, 223 const std::vector<std::string>& links) const { 224 auto[mode, uid, gid] = GetDevicePermissions(path, links); 225 mode |= (block ? S_IFBLK : S_IFCHR); 226 227 char* secontext = nullptr; 228 if (sehandle_) { 229 std::vector<const char*> c_links; 230 for (const auto& link : links) { 231 c_links.emplace_back(link.c_str()); 232 } 233 c_links.emplace_back(nullptr); 234 if (selabel_lookup_best_match(sehandle_, &secontext, path.c_str(), &c_links[0], mode)) { 235 PLOG(ERROR) << "Device '" << path << "' not created; cannot find SELinux label"; 236 return; 237 } 238 setfscreatecon(secontext); 239 } 240 241 dev_t dev = makedev(major, minor); 242 /* Temporarily change egid to avoid race condition setting the gid of the 243 * device node. Unforunately changing the euid would prevent creation of 244 * some device nodes, so the uid has to be set with chown() and is still 245 * racy. Fixing the gid race at least fixed the issue with system_server 246 * opening dynamic input devices under the AID_INPUT gid. */ 247 if (setegid(gid)) { 248 PLOG(ERROR) << "setegid(" << gid << ") for " << path << " device failed"; 249 goto out; 250 } 251 /* If the node already exists update its SELinux label to handle cases when 252 * it was created with the wrong context during coldboot procedure. */ 253 if (mknod(path.c_str(), mode, dev) && (errno == EEXIST) && secontext) { 254 char* fcon = nullptr; 255 int rc = lgetfilecon(path.c_str(), &fcon); 256 if (rc < 0) { 257 PLOG(ERROR) << "Cannot get SELinux label on '" << path << "' device"; 258 goto out; 259 } 260 261 bool different = strcmp(fcon, secontext) != 0; 262 freecon(fcon); 263 264 if (different && lsetfilecon(path.c_str(), secontext)) { 265 PLOG(ERROR) << "Cannot set '" << secontext << "' SELinux label on '" << path 266 << "' device"; 267 } 268 } 269 270out: 271 chown(path.c_str(), uid, -1); 272 if (setegid(AID_ROOT)) { 273 PLOG(FATAL) << "setegid(AID_ROOT) failed"; 274 } 275 276 if (secontext) { 277 freecon(secontext); 278 setfscreatecon(nullptr); 279 } 280} 281 282std::vector<std::string> DeviceHandler::GetCharacterDeviceSymlinks(const Uevent& uevent) const { 283 std::string parent_device; 284 if (!FindPlatformDevice(uevent.path, &parent_device)) return {}; 285 286 // skip path to the parent driver 287 std::string path = uevent.path.substr(parent_device.length()); 288 289 if (!StartsWith(path, "/usb")) return {}; 290 291 // skip root hub name and device. use device interface 292 // skip 3 slashes, including the first / by starting the search at the 1st character, not 0th. 293 // then extract what comes between the 3rd and 4th slash 294 // e.g. "/usb/usb_device/name/tty2-1:1.0" -> "name" 295 296 std::string::size_type start = 0; 297 start = path.find('/', start + 1); 298 if (start == std::string::npos) return {}; 299 300 start = path.find('/', start + 1); 301 if (start == std::string::npos) return {}; 302 303 auto end = path.find('/', start + 1); 304 if (end == std::string::npos) return {}; 305 306 start++; // Skip the first '/' 307 308 auto length = end - start; 309 if (length == 0) return {}; 310 311 auto name_string = path.substr(start, length); 312 313 std::vector<std::string> links; 314 links.emplace_back("/dev/usb/" + uevent.subsystem + name_string); 315 316 mkdir("/dev/usb", 0755); 317 318 return links; 319} 320 321// replaces any unacceptable characters with '_', the 322// length of the resulting string is equal to the input string 323void SanitizePartitionName(std::string* string) { 324 const char* accept = 325 "abcdefghijklmnopqrstuvwxyz" 326 "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 327 "0123456789" 328 "_-."; 329 330 if (!string) return; 331 332 std::string::size_type pos = 0; 333 while ((pos = string->find_first_not_of(accept, pos)) != std::string::npos) { 334 (*string)[pos] = '_'; 335 } 336} 337 338std::vector<std::string> DeviceHandler::GetBlockDeviceSymlinks(const Uevent& uevent) const { 339 std::string device; 340 std::string type; 341 342 if (FindPlatformDevice(uevent.path, &device)) { 343 // Skip /devices/platform or /devices/ if present 344 static const std::string devices_platform_prefix = "/devices/platform/"; 345 static const std::string devices_prefix = "/devices/"; 346 347 if (StartsWith(device, devices_platform_prefix.c_str())) { 348 device = device.substr(devices_platform_prefix.length()); 349 } else if (StartsWith(device, devices_prefix.c_str())) { 350 device = device.substr(devices_prefix.length()); 351 } 352 353 type = "platform"; 354 } else if (FindPciDevicePrefix(uevent.path, &device)) { 355 type = "pci"; 356 } else if (FindVbdDevicePrefix(uevent.path, &device)) { 357 type = "vbd"; 358 } else { 359 return {}; 360 } 361 362 std::vector<std::string> links; 363 364 LOG(VERBOSE) << "found " << type << " device " << device; 365 366 auto link_path = "/dev/block/" + type + "/" + device; 367 368 if (!uevent.partition_name.empty()) { 369 std::string partition_name_sanitized(uevent.partition_name); 370 SanitizePartitionName(&partition_name_sanitized); 371 if (partition_name_sanitized != uevent.partition_name) { 372 LOG(VERBOSE) << "Linking partition '" << uevent.partition_name << "' as '" 373 << partition_name_sanitized << "'"; 374 } 375 links.emplace_back(link_path + "/by-name/" + partition_name_sanitized); 376 } 377 378 if (uevent.partition_num >= 0) { 379 links.emplace_back(link_path + "/by-num/p" + std::to_string(uevent.partition_num)); 380 } 381 382 auto last_slash = uevent.path.rfind('/'); 383 links.emplace_back(link_path + "/" + uevent.path.substr(last_slash + 1)); 384 385 return links; 386} 387 388void DeviceHandler::HandleDevice(const std::string& action, const std::string& devpath, int block, 389 int major, int minor, const std::vector<std::string>& links) const { 390 if (action == "add") { 391 MakeDevice(devpath, block, major, minor, links); 392 for (const auto& link : links) { 393 if (mkdir_recursive(Dirname(link), 0755, sehandle_)) { 394 PLOG(ERROR) << "Failed to create directory " << Dirname(link); 395 } 396 397 if (symlink(devpath.c_str(), link.c_str()) && errno != EEXIST) { 398 PLOG(ERROR) << "Failed to symlink " << devpath << " to " << link; 399 } 400 } 401 } 402 403 if (action == "remove") { 404 for (const auto& link : links) { 405 std::string link_path; 406 if (Readlink(link, &link_path) && link_path == devpath) { 407 unlink(link.c_str()); 408 } 409 } 410 unlink(devpath.c_str()); 411 } 412} 413 414void DeviceHandler::HandleBlockDeviceEvent(const Uevent& uevent) const { 415 // if it's not a /dev device, nothing to do 416 if (uevent.major < 0 || uevent.minor < 0) return; 417 418 const char* base = "/dev/block/"; 419 make_dir(base, 0755, sehandle_); 420 421 std::string name = Basename(uevent.path); 422 std::string devpath = base + name; 423 424 std::vector<std::string> links; 425 if (StartsWith(uevent.path, "/devices")) { 426 links = GetBlockDeviceSymlinks(uevent); 427 } 428 429 HandleDevice(uevent.action, devpath, 1, uevent.major, uevent.minor, links); 430} 431 432void DeviceHandler::HandleGenericDeviceEvent(const Uevent& uevent) const { 433 // if it's not a /dev device, nothing to do 434 if (uevent.major < 0 || uevent.minor < 0) return; 435 436 std::string devpath; 437 438 if (StartsWith(uevent.subsystem, "usb")) { 439 if (uevent.subsystem == "usb") { 440 if (!uevent.device_name.empty()) { 441 devpath = "/dev/" + uevent.device_name; 442 } else { 443 // This imitates the file system that would be created 444 // if we were using devfs instead. 445 // Minors are broken up into groups of 128, starting at "001" 446 int bus_id = uevent.minor / 128 + 1; 447 int device_id = uevent.minor % 128 + 1; 448 devpath = StringPrintf("/dev/bus/usb/%03d/%03d", bus_id, device_id); 449 } 450 } else { 451 // ignore other USB events 452 return; 453 } 454 } else if (const auto subsystem = 455 std::find(subsystems_.cbegin(), subsystems_.cend(), uevent.subsystem); 456 subsystem != subsystems_.cend()) { 457 devpath = subsystem->ParseDevPath(uevent); 458 } else { 459 devpath = "/dev/" + Basename(uevent.path); 460 } 461 462 mkdir_recursive(Dirname(devpath), 0755, sehandle_); 463 464 auto links = GetCharacterDeviceSymlinks(uevent); 465 466 HandleDevice(uevent.action, devpath, 0, uevent.major, uevent.minor, links); 467} 468 469void DeviceHandler::HandleDeviceEvent(const Uevent& uevent) { 470 if (uevent.action == "add" || uevent.action == "change" || uevent.action == "online") { 471 FixupSysPermissions(uevent.path, uevent.subsystem); 472 } 473 474 if (uevent.subsystem == "block") { 475 HandleBlockDeviceEvent(uevent); 476 } else { 477 HandleGenericDeviceEvent(uevent); 478 } 479} 480 481DeviceHandler::DeviceHandler(std::vector<Permissions> dev_permissions, 482 std::vector<SysfsPermissions> sysfs_permissions, 483 std::vector<Subsystem> subsystems, bool skip_restorecon) 484 : dev_permissions_(std::move(dev_permissions)), 485 sysfs_permissions_(std::move(sysfs_permissions)), 486 subsystems_(std::move(subsystems)), 487 sehandle_(selinux_android_file_context_handle()), 488 skip_restorecon_(skip_restorecon), 489 sysfs_mount_point_("/sys") {} 490 491DeviceHandler::DeviceHandler() 492 : DeviceHandler(std::vector<Permissions>{}, std::vector<SysfsPermissions>{}, 493 std::vector<Subsystem>{}, false) {} 494 495} // namespace init 496} // namespace android 497