1 /*
2  * Copyright (C) 2019 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 "apex_database.h"
18 #include "apex_constants.h"
19 #include "apex_file.h"
20 #include "apexd_utils.h"
21 #include "string_log.h"
22 
23 #include <android-base/file.h>
24 #include <android-base/logging.h>
25 #include <android-base/parseint.h>
26 #include <android-base/result.h>
27 #include <android-base/strings.h>
28 
29 #include <filesystem>
30 #include <fstream>
31 #include <string>
32 #include <unordered_map>
33 #include <utility>
34 
35 using android::base::ConsumeSuffix;
36 using android::base::EndsWith;
37 using android::base::ErrnoError;
38 using android::base::Error;
39 using android::base::ParseInt;
40 using android::base::ReadFileToString;
41 using android::base::Result;
42 using android::base::Split;
43 using android::base::StartsWith;
44 using android::base::Trim;
45 
46 namespace fs = std::filesystem;
47 
48 namespace android {
49 namespace apex {
50 
51 namespace {
52 
53 using MountedApexData = MountedApexDatabase::MountedApexData;
54 
55 enum BlockDeviceType {
56   UnknownDevice,
57   LoopDevice,
58   DeviceMapperDevice,
59 };
60 
61 const fs::path kDevBlock = "/dev/block";
62 const fs::path kSysBlock = "/sys/block";
63 
64 class BlockDevice {
65   std::string name;  // loopN, dm-N, ...
66  public:
BlockDevice(const fs::path & path)67   explicit BlockDevice(const fs::path& path) { name = path.filename(); }
68 
GetType() const69   BlockDeviceType GetType() const {
70     if (StartsWith(name, "loop")) return LoopDevice;
71     if (StartsWith(name, "dm-")) return DeviceMapperDevice;
72     return UnknownDevice;
73   }
74 
SysPath() const75   fs::path SysPath() const { return kSysBlock / name; }
76 
DevPath() const77   fs::path DevPath() const { return kDevBlock / name; }
78 
GetProperty(const std::string & property) const79   Result<std::string> GetProperty(const std::string& property) const {
80     auto property_file = SysPath() / property;
81     std::string property_value;
82     if (!ReadFileToString(property_file, &property_value)) {
83       return ErrnoError() << "Fail to read";
84     }
85     return Trim(property_value);
86   }
87 
GetSlaves() const88   std::vector<BlockDevice> GetSlaves() const {
89     std::vector<BlockDevice> slaves;
90     std::error_code ec;
91     auto status = WalkDir(SysPath() / "slaves", [&](const auto& entry) {
92       BlockDevice dev(entry);
93       if (fs::is_block_file(dev.DevPath(), ec)) {
94         slaves.push_back(dev);
95       }
96     });
97     if (!status.ok()) {
98       LOG(WARNING) << status.error();
99     }
100     return slaves;
101   }
102 };
103 
ParseMountInfo(const std::string & mount_info)104 std::pair<fs::path, fs::path> ParseMountInfo(const std::string& mount_info) {
105   const auto& tokens = Split(mount_info, " ");
106   if (tokens.size() < 2) {
107     return std::make_pair("", "");
108   }
109   return std::make_pair(tokens[0], tokens[1]);
110 }
111 
ParseMountPoint(const std::string & mount_point)112 std::pair<std::string, int> ParseMountPoint(const std::string& mount_point) {
113   auto package_id = fs::path(mount_point).filename();
114   auto split = Split(package_id, "@");
115   if (split.size() == 2) {
116     int version;
117     if (!ParseInt(split[1], &version)) {
118       version = -1;
119     }
120     return std::make_pair(split[0], version);
121   }
122   return std::make_pair(package_id, -1);
123 }
124 
IsActiveMountPoint(const std::string & mount_point)125 bool IsActiveMountPoint(const std::string& mount_point) {
126   return (mount_point.find('@') == std::string::npos);
127 }
128 
PopulateLoopInfo(const BlockDevice & top_device,const std::vector<std::string> & data_dirs,const std::string & apex_hash_tree_dir,MountedApexData * apex_data)129 Result<void> PopulateLoopInfo(const BlockDevice& top_device,
130                               const std::vector<std::string>& data_dirs,
131                               const std::string& apex_hash_tree_dir,
132                               MountedApexData* apex_data) {
133   std::vector<BlockDevice> slaves = top_device.GetSlaves();
134   if (slaves.size() != 1 && slaves.size() != 2) {
135     return Error() << "dm device " << top_device.DevPath()
136                    << " has unexpected number of slaves : " << slaves.size();
137   }
138   std::vector<std::string> backing_files;
139   backing_files.reserve(slaves.size());
140   for (const auto& dev : slaves) {
141     if (dev.GetType() != LoopDevice) {
142       return Error() << dev.DevPath() << " is not a loop device";
143     }
144     auto backing_file = dev.GetProperty("loop/backing_file");
145     if (!backing_file.ok()) {
146       return backing_file.error();
147     }
148     backing_files.push_back(std::move(*backing_file));
149   }
150   // Enforce following invariant:
151   //  * slaves[0] always represents a data loop device
152   //  * if size = 2 then slaves[1] represents an external hashtree loop device
153   auto is_data_loop_device = [&](const std::string& backing_file) {
154     return std::any_of(
155         data_dirs.begin(), data_dirs.end(),
156         [&](const std::string& dir) { return StartsWith(backing_file, dir); });
157   };
158   if (slaves.size() == 2) {
159     if (!is_data_loop_device(backing_files[0])) {
160       std::swap(slaves[0], slaves[1]);
161       std::swap(backing_files[0], backing_files[1]);
162     }
163   }
164   if (!is_data_loop_device(backing_files[0])) {
165     return Error() << "Data loop device " << slaves[0].DevPath()
166                    << " has unexpected backing file " << backing_files[0];
167   }
168   if (slaves.size() == 2) {
169     if (!StartsWith(backing_files[1], apex_hash_tree_dir)) {
170       return Error() << "Hashtree loop device " << slaves[1].DevPath()
171                      << " has unexpected backing file " << backing_files[1];
172     }
173     apex_data->hashtree_loop_name = slaves[1].DevPath();
174   }
175   apex_data->loop_name = slaves[0].DevPath();
176   apex_data->full_path = backing_files[0];
177   return {};
178 }
179 
180 // This is not the right place to do this normalization, but proper solution
181 // will require some refactoring first. :(
182 // TODO(b/158469911): introduce MountedApexDataBuilder and delegate all
183 //  building/normalization logic to it.
NormalizeIfDeleted(MountedApexData * apex_data)184 void NormalizeIfDeleted(MountedApexData* apex_data) {
185   std::string_view full_path = apex_data->full_path;
186   if (ConsumeSuffix(&full_path, "(deleted)")) {
187     apex_data->deleted = true;
188     auto it = full_path.rbegin();
189     while (it != full_path.rend() && isspace(*it)) {
190       it++;
191     }
192     full_path.remove_suffix(it - full_path.rbegin());
193   } else {
194     apex_data->deleted = false;
195   }
196   apex_data->full_path = full_path;
197 }
198 
ResolveMountInfo(const BlockDevice & block,const std::string & mount_point,const std::vector<std::string> & data_dirs,const std::string & apex_hash_tree_dir)199 Result<MountedApexData> ResolveMountInfo(
200     const BlockDevice& block, const std::string& mount_point,
201     const std::vector<std::string>& data_dirs,
202     const std::string& apex_hash_tree_dir) {
203   bool temp_mount = EndsWith(mount_point, ".tmp");
204   // Now, see if it is dm-verity or loop mounted
205   switch (block.GetType()) {
206     case LoopDevice: {
207       auto backing_file = block.GetProperty("loop/backing_file");
208       if (!backing_file.ok()) {
209         return backing_file.error();
210       }
211       MountedApexData result;
212       result.loop_name = block.DevPath();
213       result.full_path = *backing_file;
214       result.mount_point = mount_point;
215       result.is_temp_mount = temp_mount;
216       NormalizeIfDeleted(&result);
217       return result;
218     }
219     case DeviceMapperDevice: {
220       auto name = block.GetProperty("dm/name");
221       if (!name.ok()) {
222         return name.error();
223       }
224       MountedApexData result;
225       result.mount_point = mount_point;
226       result.device_name = *name;
227       result.is_temp_mount = temp_mount;
228       auto status =
229           PopulateLoopInfo(block, data_dirs, apex_hash_tree_dir, &result);
230       if (!status.ok()) {
231         return status.error();
232       }
233       NormalizeIfDeleted(&result);
234       return result;
235     }
236     case UnknownDevice: {
237       return Errorf("Can't resolve {}", block.DevPath().string());
238     }
239   }
240 }
241 
242 }  // namespace
243 
244 // On startup, APEX database is populated from /proc/mounts.
245 
246 // /apex/<package-id> can be mounted from
247 // - /dev/block/loopX : loop device
248 // - /dev/block/dm-X : dm-verity
249 
250 // In case of loop device, the original APEX file can be tracked
251 // by /sys/block/loopX/loop/backing_file.
252 
253 // In case of dm-verity, it is mapped to a loop device.
254 // This mapped loop device can be traced by
255 // /sys/block/dm-X/slaves/ directory which contains
256 // a symlink to /sys/block/loopY, which leads to
257 // the original APEX file.
258 // Device name can be retrieved from
259 // /sys/block/dm-Y/dm/name.
260 
261 // Need to read /proc/mounts on startup since apexd can start
262 // at any time (It's a lazy service).
PopulateFromMounts(const std::vector<std::string> & data_dirs,const std::string & apex_hash_tree_dir)263 void MountedApexDatabase::PopulateFromMounts(
264     const std::vector<std::string>& data_dirs,
265     const std::string& apex_hash_tree_dir) REQUIRES(!mounted_apexes_mutex_) {
266   LOG(INFO) << "Populating APEX database from mounts...";
267 
268   std::ifstream mounts("/proc/mounts");
269   std::string line;
270   std::lock_guard lock(mounted_apexes_mutex_);
271   while (std::getline(mounts, line)) {
272     auto [block, mount_point] = ParseMountInfo(line);
273     // TODO(b/158469914): distinguish between temp and non-temp mounts
274     if (fs::path(mount_point).parent_path() != kApexRoot) {
275       continue;
276     }
277     if (IsActiveMountPoint(mount_point)) {
278       continue;
279     }
280 
281     auto mount_data = ResolveMountInfo(BlockDevice(block), mount_point,
282                                        data_dirs, apex_hash_tree_dir);
283     if (!mount_data.ok()) {
284       LOG(WARNING) << "Can't resolve mount info " << mount_data.error();
285       continue;
286     }
287 
288     auto [package, version] = ParseMountPoint(mount_point);
289     mount_data->version = version;
290     AddMountedApexLocked(package, *mount_data);
291 
292     LOG(INFO) << "Found " << mount_point << " backed by"
293               << (mount_data->deleted ? " deleted " : " ") << "file "
294               << mount_data->full_path;
295   }
296 
297   LOG(INFO) << mounted_apexes_.size() << " packages restored.";
298 }
299 
300 }  // namespace apex
301 }  // namespace android
302