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