• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
40 using android::base::Basename;
41 using android::base::Dirname;
42 using android::base::Readlink;
43 using android::base::Realpath;
44 using android::base::StartsWith;
45 using android::base::StringPrintf;
46 
47 namespace android {
48 namespace 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 */
FindPciDevicePrefix(const std::string & path,std::string * result)53 static 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 */
FindVbdDevicePrefix(const std::string & path,std::string * result)84 static 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 
Permissions(const std::string & name,mode_t perm,uid_t uid,gid_t gid)104 Permissions::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 
Match(const std::string & path) const128 bool 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 
MatchWithSubsystem(const std::string & path,const std::string & subsystem) const134 bool 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 
SetPermissions(const std::string & path) const144 void 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
FindPlatformDevice(std::string path,std::string * platform_device_path) const163 bool 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 
FixupSysPermissions(const std::string & upath,const std::string & subsystem) const191 void 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 
GetDevicePermissions(const std::string & path,const std::vector<std::string> & links) const209 std::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 
MakeDevice(const std::string & path,bool block,int major,int minor,const std::vector<std::string> & links) const222 void DeviceHandler::MakeDevice(const std::string& path, bool 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 
270 out:
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 
282 // replaces any unacceptable characters with '_', the
283 // length of the resulting string is equal to the input string
SanitizePartitionName(std::string * string)284 void SanitizePartitionName(std::string* string) {
285     const char* accept =
286         "abcdefghijklmnopqrstuvwxyz"
287         "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
288         "0123456789"
289         "_-.";
290 
291     if (!string) return;
292 
293     std::string::size_type pos = 0;
294     while ((pos = string->find_first_not_of(accept, pos)) != std::string::npos) {
295         (*string)[pos] = '_';
296     }
297 }
298 
GetBlockDeviceSymlinks(const Uevent & uevent) const299 std::vector<std::string> DeviceHandler::GetBlockDeviceSymlinks(const Uevent& uevent) const {
300     std::string device;
301     std::string type;
302 
303     if (FindPlatformDevice(uevent.path, &device)) {
304         // Skip /devices/platform or /devices/ if present
305         static const std::string devices_platform_prefix = "/devices/platform/";
306         static const std::string devices_prefix = "/devices/";
307 
308         if (StartsWith(device, devices_platform_prefix.c_str())) {
309             device = device.substr(devices_platform_prefix.length());
310         } else if (StartsWith(device, devices_prefix.c_str())) {
311             device = device.substr(devices_prefix.length());
312         }
313 
314         type = "platform";
315     } else if (FindPciDevicePrefix(uevent.path, &device)) {
316         type = "pci";
317     } else if (FindVbdDevicePrefix(uevent.path, &device)) {
318         type = "vbd";
319     } else {
320         return {};
321     }
322 
323     std::vector<std::string> links;
324 
325     LOG(VERBOSE) << "found " << type << " device " << device;
326 
327     auto link_path = "/dev/block/" + type + "/" + device;
328 
329     if (!uevent.partition_name.empty()) {
330         std::string partition_name_sanitized(uevent.partition_name);
331         SanitizePartitionName(&partition_name_sanitized);
332         if (partition_name_sanitized != uevent.partition_name) {
333             LOG(VERBOSE) << "Linking partition '" << uevent.partition_name << "' as '"
334                          << partition_name_sanitized << "'";
335         }
336         links.emplace_back(link_path + "/by-name/" + partition_name_sanitized);
337     }
338 
339     if (uevent.partition_num >= 0) {
340         links.emplace_back(link_path + "/by-num/p" + std::to_string(uevent.partition_num));
341     }
342 
343     auto last_slash = uevent.path.rfind('/');
344     links.emplace_back(link_path + "/" + uevent.path.substr(last_slash + 1));
345 
346     return links;
347 }
348 
HandleDevice(const std::string & action,const std::string & devpath,bool block,int major,int minor,const std::vector<std::string> & links) const349 void DeviceHandler::HandleDevice(const std::string& action, const std::string& devpath, bool block,
350                                  int major, int minor, const std::vector<std::string>& links) const {
351     if (action == "add") {
352         MakeDevice(devpath, block, major, minor, links);
353         for (const auto& link : links) {
354             if (mkdir_recursive(Dirname(link), 0755, sehandle_)) {
355                 PLOG(ERROR) << "Failed to create directory " << Dirname(link);
356             }
357 
358             if (symlink(devpath.c_str(), link.c_str()) && errno != EEXIST) {
359                 PLOG(ERROR) << "Failed to symlink " << devpath << " to " << link;
360             }
361         }
362     }
363 
364     if (action == "remove") {
365         for (const auto& link : links) {
366             std::string link_path;
367             if (Readlink(link, &link_path) && link_path == devpath) {
368                 unlink(link.c_str());
369             }
370         }
371         unlink(devpath.c_str());
372     }
373 }
374 
HandleDeviceEvent(const Uevent & uevent)375 void DeviceHandler::HandleDeviceEvent(const Uevent& uevent) {
376     if (uevent.action == "add" || uevent.action == "change" || uevent.action == "online") {
377         FixupSysPermissions(uevent.path, uevent.subsystem);
378     }
379 
380     // if it's not a /dev device, nothing to do
381     if (uevent.major < 0 || uevent.minor < 0) return;
382 
383     std::string devpath;
384     std::vector<std::string> links;
385     bool block = false;
386 
387     if (uevent.subsystem == "block") {
388         block = true;
389         devpath = "/dev/block/" + Basename(uevent.path);
390 
391         if (StartsWith(uevent.path, "/devices")) {
392             links = GetBlockDeviceSymlinks(uevent);
393         }
394     } else if (StartsWith(uevent.subsystem, "usb")) {
395         if (uevent.subsystem == "usb") {
396             if (!uevent.device_name.empty()) {
397                 devpath = "/dev/" + uevent.device_name;
398             } else {
399                 // This imitates the file system that would be created
400                 // if we were using devfs instead.
401                 // Minors are broken up into groups of 128, starting at "001"
402                 int bus_id = uevent.minor / 128 + 1;
403                 int device_id = uevent.minor % 128 + 1;
404                 devpath = StringPrintf("/dev/bus/usb/%03d/%03d", bus_id, device_id);
405             }
406         } else {
407             // ignore other USB events
408             return;
409         }
410     } else if (const auto subsystem =
411                    std::find(subsystems_.cbegin(), subsystems_.cend(), uevent.subsystem);
412                subsystem != subsystems_.cend()) {
413         devpath = subsystem->ParseDevPath(uevent);
414     } else {
415         devpath = "/dev/" + Basename(uevent.path);
416     }
417 
418     mkdir_recursive(Dirname(devpath), 0755, sehandle_);
419 
420     HandleDevice(uevent.action, devpath, block, uevent.major, uevent.minor, links);
421 }
422 
DeviceHandler(std::vector<Permissions> dev_permissions,std::vector<SysfsPermissions> sysfs_permissions,std::vector<Subsystem> subsystems,bool skip_restorecon)423 DeviceHandler::DeviceHandler(std::vector<Permissions> dev_permissions,
424                              std::vector<SysfsPermissions> sysfs_permissions,
425                              std::vector<Subsystem> subsystems, bool skip_restorecon)
426     : dev_permissions_(std::move(dev_permissions)),
427       sysfs_permissions_(std::move(sysfs_permissions)),
428       subsystems_(std::move(subsystems)),
429       sehandle_(selinux_android_file_context_handle()),
430       skip_restorecon_(skip_restorecon),
431       sysfs_mount_point_("/sys") {}
432 
DeviceHandler()433 DeviceHandler::DeviceHandler()
434     : DeviceHandler(std::vector<Permissions>{}, std::vector<SysfsPermissions>{},
435                     std::vector<Subsystem>{}, false) {}
436 
437 }  // namespace init
438 }  // namespace android
439