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 #define LOG_TAG "apexd"
18
19 #include "apex_file_repository.h"
20
21 #include <android-base/file.h>
22 #include <android-base/properties.h>
23 #include <android-base/result.h>
24 #include <android-base/stringprintf.h>
25 #include <android-base/strings.h>
26 #include <microdroid/metadata.h>
27
28 #include <unordered_map>
29
30 #include "apex_constants.h"
31 #include "apex_file.h"
32 #include "apexd_utils.h"
33 #include "apexd_verity.h"
34
35 using android::base::EndsWith;
36 using android::base::Error;
37 using android::base::GetProperty;
38 using android::base::Result;
39
40 namespace android {
41 namespace apex {
42
ConsumeApexPackageSuffix(const std::string & path)43 std::string ConsumeApexPackageSuffix(const std::string& path) {
44 std::string_view path_view(path);
45 android::base::ConsumeSuffix(&path_view, kApexPackageSuffix);
46 android::base::ConsumeSuffix(&path_view, kCompressedApexPackageSuffix);
47 return std::string(path_view);
48 }
49
GetApexSelectFilenameFromProp(const std::vector<std::string> & prefixes,const std::string & apex_name)50 std::string GetApexSelectFilenameFromProp(
51 const std::vector<std::string>& prefixes, const std::string& apex_name) {
52 for (const std::string& prefix : prefixes) {
53 const std::string& filename = GetProperty(prefix + apex_name, "");
54 if (filename != "") {
55 return ConsumeApexPackageSuffix(filename);
56 }
57 }
58 return "";
59 }
60
ScanBuiltInDir(const std::string & dir)61 Result<void> ApexFileRepository::ScanBuiltInDir(const std::string& dir) {
62 LOG(INFO) << "Scanning " << dir << " for pre-installed ApexFiles";
63 if (access(dir.c_str(), F_OK) != 0 && errno == ENOENT) {
64 LOG(WARNING) << dir << " does not exist. Skipping";
65 return {};
66 }
67
68 Result<std::vector<std::string>> all_apex_files = FindFilesBySuffix(
69 dir, {kApexPackageSuffix, kCompressedApexPackageSuffix});
70 if (!all_apex_files.ok()) {
71 return all_apex_files.error();
72 }
73
74 // TODO(b/179248390): scan parallelly if possible
75 for (const auto& file : *all_apex_files) {
76 LOG(INFO) << "Found pre-installed APEX " << file;
77 Result<ApexFile> apex_file = ApexFile::Open(file);
78 if (!apex_file.ok()) {
79 return Error() << "Failed to open " << file << " : " << apex_file.error();
80 }
81
82 const std::string& name = apex_file->GetManifest().name();
83
84 // Check if this APEX name is treated as a multi-install APEX.
85 //
86 // Note: apexd is a oneshot service which runs at boot, but can be restarted
87 // when needed (such as staging an APEX update). If a multi-install select
88 // property changes between boot and when apexd restarts, the LOG messages
89 // below will report the version that will be activated on next reboot,
90 // which may differ from the currently-active version.
91 std::string select_filename = GetApexSelectFilenameFromProp(
92 multi_install_select_prop_prefixes_, name);
93 if (!select_filename.empty()) {
94 std::string path;
95 if (!android::base::Realpath(apex_file->GetPath(), &path)) {
96 LOG(ERROR) << "Unable to resolve realpath of APEX with path "
97 << apex_file->GetPath();
98 continue;
99 }
100 if (enforce_multi_install_partition_ &&
101 !android::base::StartsWith(path, "/vendor/apex/")) {
102 LOG(ERROR) << "Multi-install APEX " << path
103 << " can only be preinstalled on /vendor/apex/.";
104 continue;
105 }
106
107 auto& keys = multi_install_public_keys_[name];
108 keys.insert(apex_file->GetBundledPublicKey());
109 if (keys.size() > 1) {
110 LOG(ERROR) << "Multi-install APEXes for " << name
111 << " have different public keys.";
112 // If any versions of a multi-installed APEX differ in public key,
113 // then no version should be installed.
114 if (auto it = pre_installed_store_.find(name);
115 it != pre_installed_store_.end()) {
116 pre_installed_store_.erase(it);
117 }
118 continue;
119 }
120
121 if (ConsumeApexPackageSuffix(android::base::Basename(path)) ==
122 select_filename) {
123 LOG(INFO) << "Found APEX at path " << path << " for multi-install APEX "
124 << name;
125 // Add the APEX file to the store if its filename matches the property.
126 pre_installed_store_.emplace(name, std::move(*apex_file));
127 } else {
128 LOG(INFO) << "Skipping APEX at path " << path
129 << " because it does not match expected multi-install"
130 << " APEX property for " << name;
131 }
132
133 continue;
134 }
135
136 auto it = pre_installed_store_.find(name);
137 if (it == pre_installed_store_.end()) {
138 pre_installed_store_.emplace(name, std::move(*apex_file));
139 } else if (it->second.GetPath() != apex_file->GetPath()) {
140 auto level = base::FATAL;
141 // On some development (non-REL) builds the VNDK apex could be in /vendor.
142 // When testing CTS-on-GSI on these builds, there would be two VNDK apexes
143 // in the system, one in /system and one in /vendor.
144 static constexpr char kVndkApexModuleNamePrefix[] = "com.android.vndk.";
145 static constexpr char kPlatformVersionCodenameProperty[] =
146 "ro.build.version.codename";
147 if (android::base::StartsWith(name, kVndkApexModuleNamePrefix) &&
148 GetProperty(kPlatformVersionCodenameProperty, "REL") != "REL") {
149 level = android::base::INFO;
150 }
151 LOG(level) << "Found two apex packages " << it->second.GetPath()
152 << " and " << apex_file->GetPath()
153 << " with the same module name " << name;
154 } else if (it->second.GetBundledPublicKey() !=
155 apex_file->GetBundledPublicKey()) {
156 LOG(FATAL) << "Public key of apex package " << it->second.GetPath()
157 << " (" << name << ") has unexpectedly changed";
158 }
159 }
160 multi_install_public_keys_.clear();
161 return {};
162 }
163
GetInstance()164 ApexFileRepository& ApexFileRepository::GetInstance() {
165 static ApexFileRepository instance;
166 return instance;
167 }
168
AddPreInstalledApex(const std::vector<std::string> & prebuilt_dirs)169 android::base::Result<void> ApexFileRepository::AddPreInstalledApex(
170 const std::vector<std::string>& prebuilt_dirs) {
171 for (const auto& dir : prebuilt_dirs) {
172 if (auto result = ScanBuiltInDir(dir); !result.ok()) {
173 return result.error();
174 }
175 }
176 return {};
177 }
178
AddBlockApex(const std::string & metadata_partition)179 Result<int> ApexFileRepository::AddBlockApex(
180 const std::string& metadata_partition) {
181 CHECK(!block_disk_path_.has_value())
182 << "AddBlockApex() can't be called twice.";
183
184 auto metadata_ready = WaitForFile(metadata_partition, kBlockApexWaitTime);
185 if (!metadata_ready.ok()) {
186 LOG(ERROR) << "Error waiting for metadata_partition : "
187 << metadata_ready.error();
188 return {};
189 }
190
191 // TODO(b/185069443) consider moving the logic to find disk_path from
192 // metadata_partition to its own library
193 LOG(INFO) << "Scanning " << metadata_partition << " for host apexes";
194 if (access(metadata_partition.c_str(), F_OK) != 0 && errno == ENOENT) {
195 LOG(WARNING) << metadata_partition << " does not exist. Skipping";
196 return {};
197 }
198
199 std::string metadata_realpath;
200 if (!android::base::Realpath(metadata_partition, &metadata_realpath)) {
201 LOG(WARNING) << "Can't get realpath of " << metadata_partition
202 << ". Skipping";
203 return {};
204 }
205
206 std::string_view metadata_path_view(metadata_realpath);
207 if (!android::base::ConsumeSuffix(&metadata_path_view, "1")) {
208 LOG(WARNING) << metadata_realpath << " is not a first partition. Skipping";
209 return {};
210 }
211
212 block_disk_path_ = std::string(metadata_path_view);
213
214 // Read the payload metadata.
215 // "metadata" can be overridden by microdroid_manager. To ensure that
216 // "microdroid" is started with the same/unmodified set of host APEXes,
217 // microdroid stores APEXes' pubkeys in its encrypted instance disk. Next
218 // time, microdroid checks if there's pubkeys in the instance disk and use
219 // them to activate APEXes. Microdroid_manager passes pubkeys in instance.img
220 // via the following file.
221 if (auto exists = PathExists("/apex/vm-payload-metadata");
222 exists.ok() && *exists) {
223 metadata_realpath = "/apex/vm-payload-metadata";
224 LOG(INFO) << "Overriding metadata to " << metadata_realpath;
225 }
226 auto metadata = android::microdroid::ReadMetadata(metadata_realpath);
227 if (!metadata.ok()) {
228 LOG(WARNING) << "Failed to load metadata from " << metadata_realpath
229 << ". Skipping: " << metadata.error();
230 return {};
231 }
232
233 int ret = 0;
234
235 // subsequent partitions are APEX archives.
236 static constexpr const int kFirstApexPartition = 2;
237 for (int i = 0; i < metadata->apexes_size(); i++) {
238 const auto& apex_config = metadata->apexes(i);
239
240 const std::string apex_path =
241 *block_disk_path_ + std::to_string(i + kFirstApexPartition);
242
243 auto apex_ready = WaitForFile(apex_path, kBlockApexWaitTime);
244 if (!apex_ready.ok()) {
245 return Error() << "Error waiting for apex file : " << apex_ready.error();
246 }
247
248 auto apex_file = ApexFile::Open(apex_path);
249 if (!apex_file.ok()) {
250 return Error() << "Failed to open " << apex_path << " : "
251 << apex_file.error();
252 }
253
254 // When metadata specifies the public key of the apex, it should match the
255 // bundled key. Otherwise we accept it.
256 if (apex_config.public_key() != "" &&
257 apex_config.public_key() != apex_file->GetBundledPublicKey()) {
258 return Error() << "public key doesn't match: " << apex_path;
259 }
260
261 const std::string& name = apex_file->GetManifest().name();
262
263 BlockApexOverride overrides;
264
265 // A block device doesn't have an inherent timestamp, so it is carried in
266 // the metadata.
267 if (int64_t last_update_seconds = apex_config.last_update_seconds();
268 last_update_seconds != 0) {
269 overrides.last_update_seconds = last_update_seconds;
270 }
271
272 // When metadata specifies the root digest of the apex, it should be used
273 // when activating the apex. So we need to keep it.
274 if (auto root_digest = apex_config.root_digest(); root_digest != "") {
275 overrides.block_apex_root_digest =
276 BytesToHex(reinterpret_cast<const uint8_t*>(root_digest.data()),
277 root_digest.size());
278 }
279
280 if (overrides.last_update_seconds.has_value() ||
281 overrides.block_apex_root_digest.has_value()) {
282 block_apex_overrides_.emplace(apex_path, std::move(overrides));
283 }
284
285 // Depending on whether the APEX was a factory version in the host or not,
286 // put it to different stores.
287 auto& store = apex_config.is_factory() ? pre_installed_store_ : data_store_;
288 // We want "uniqueness" in each store.
289 if (auto it = store.find(name); it != store.end()) {
290 return Error() << "duplicate of " << name << " found in "
291 << it->second.GetPath();
292 }
293 store.emplace(name, std::move(*apex_file));
294
295 ret++;
296 }
297 return {ret};
298 }
299
300 // TODO(b/179497746): AddDataApex should not concern with filtering out invalid
301 // apex.
AddDataApex(const std::string & data_dir)302 Result<void> ApexFileRepository::AddDataApex(const std::string& data_dir) {
303 LOG(INFO) << "Scanning " << data_dir << " for data ApexFiles";
304 if (access(data_dir.c_str(), F_OK) != 0 && errno == ENOENT) {
305 LOG(WARNING) << data_dir << " does not exist. Skipping";
306 return {};
307 }
308
309 Result<std::vector<std::string>> active_apex =
310 FindFilesBySuffix(data_dir, {kApexPackageSuffix});
311 if (!active_apex.ok()) {
312 return active_apex.error();
313 }
314
315 // TODO(b/179248390): scan parallelly if possible
316 for (const auto& file : *active_apex) {
317 LOG(INFO) << "Found updated apex " << file;
318 Result<ApexFile> apex_file = ApexFile::Open(file);
319 if (!apex_file.ok()) {
320 LOG(ERROR) << "Failed to open " << file << " : " << apex_file.error();
321 continue;
322 }
323
324 const std::string& name = apex_file->GetManifest().name();
325 if (!HasPreInstalledVersion(name)) {
326 LOG(ERROR) << "Skipping " << file << " : no preinstalled apex";
327 // Ignore data apex without corresponding pre-installed apex
328 continue;
329 }
330
331 std::string select_filename = GetApexSelectFilenameFromProp(
332 multi_install_select_prop_prefixes_, name);
333 if (!select_filename.empty()) {
334 LOG(WARNING) << "APEX " << name << " is a multi-installed APEX."
335 << " Any updated version in /data will always overwrite"
336 << " the multi-installed preinstalled version, if possible.";
337 }
338
339 auto pre_installed_public_key = GetPublicKey(name);
340 if (!pre_installed_public_key.ok() ||
341 apex_file->GetBundledPublicKey() != *pre_installed_public_key) {
342 // Ignore data apex if public key doesn't match with pre-installed apex
343 LOG(ERROR) << "Skipping " << file
344 << " : public key doesn't match pre-installed one";
345 continue;
346 }
347
348 if (EndsWith(apex_file->GetPath(), kDecompressedApexPackageSuffix)) {
349 LOG(WARNING) << "Skipping " << file
350 << " : Non-decompressed APEX should not have "
351 << kDecompressedApexPackageSuffix << " suffix";
352 continue;
353 }
354
355 auto it = data_store_.find(name);
356 if (it == data_store_.end()) {
357 data_store_.emplace(name, std::move(*apex_file));
358 continue;
359 }
360
361 const auto& existing_version = it->second.GetManifest().version();
362 const auto new_version = apex_file->GetManifest().version();
363 // If multiple data apexs are preset, select the one with highest version
364 bool prioritize_higher_version = new_version > existing_version;
365 // For same version, non-decompressed apex gets priority
366 if (prioritize_higher_version) {
367 it->second = std::move(*apex_file);
368 }
369 }
370 return {};
371 }
372
373 // TODO(b/179497746): remove this method when we add api for fetching ApexFile
374 // by name
GetPublicKey(const std::string & name) const375 Result<const std::string> ApexFileRepository::GetPublicKey(
376 const std::string& name) const {
377 auto it = pre_installed_store_.find(name);
378 if (it == pre_installed_store_.end()) {
379 // Special casing for APEXes backed by block devices, i.e. APEXes in VM.
380 // Inside a VM, we fall back to find the key from data_store_. This is
381 // because an APEX is put to either pre_installed_store_ or data_store,
382 // depending on whether it was a factory APEX or not in the host.
383 it = data_store_.find(name);
384 if (it != data_store_.end() && IsBlockApex(it->second)) {
385 return it->second.GetBundledPublicKey();
386 }
387 return Error() << "No preinstalled apex found for package " << name;
388 }
389 return it->second.GetBundledPublicKey();
390 }
391
392 // TODO(b/179497746): remove this method when we add api for fetching ApexFile
393 // by name
GetPreinstalledPath(const std::string & name) const394 Result<const std::string> ApexFileRepository::GetPreinstalledPath(
395 const std::string& name) const {
396 auto it = pre_installed_store_.find(name);
397 if (it == pre_installed_store_.end()) {
398 return Error() << "No preinstalled data found for package " << name;
399 }
400 return it->second.GetPath();
401 }
402
403 // TODO(b/179497746): remove this method when we add api for fetching ApexFile
404 // by name
GetDataPath(const std::string & name) const405 Result<const std::string> ApexFileRepository::GetDataPath(
406 const std::string& name) const {
407 auto it = data_store_.find(name);
408 if (it == data_store_.end()) {
409 return Error() << "No data apex found for package " << name;
410 }
411 return it->second.GetPath();
412 }
413
GetBlockApexRootDigest(const std::string & path) const414 std::optional<std::string> ApexFileRepository::GetBlockApexRootDigest(
415 const std::string& path) const {
416 auto it = block_apex_overrides_.find(path);
417 if (it == block_apex_overrides_.end()) {
418 return std::nullopt;
419 }
420 return it->second.block_apex_root_digest;
421 }
422
GetBlockApexLastUpdateSeconds(const std::string & path) const423 std::optional<int64_t> ApexFileRepository::GetBlockApexLastUpdateSeconds(
424 const std::string& path) const {
425 auto it = block_apex_overrides_.find(path);
426 if (it == block_apex_overrides_.end()) {
427 return std::nullopt;
428 }
429 return it->second.last_update_seconds;
430 }
431
HasPreInstalledVersion(const std::string & name) const432 bool ApexFileRepository::HasPreInstalledVersion(const std::string& name) const {
433 return pre_installed_store_.find(name) != pre_installed_store_.end();
434 }
435
HasDataVersion(const std::string & name) const436 bool ApexFileRepository::HasDataVersion(const std::string& name) const {
437 return data_store_.find(name) != data_store_.end();
438 }
439
440 // ApexFile is considered a decompressed APEX if it is located in decompression
441 // dir
IsDecompressedApex(const ApexFile & apex) const442 bool ApexFileRepository::IsDecompressedApex(const ApexFile& apex) const {
443 return apex.GetPath().starts_with(decompression_dir_);
444 }
445
IsPreInstalledApex(const ApexFile & apex) const446 bool ApexFileRepository::IsPreInstalledApex(const ApexFile& apex) const {
447 auto it = pre_installed_store_.find(apex.GetManifest().name());
448 if (it == pre_installed_store_.end()) {
449 return false;
450 }
451 return it->second.GetPath() == apex.GetPath() || IsDecompressedApex(apex);
452 }
453
IsBlockApex(const ApexFile & apex) const454 bool ApexFileRepository::IsBlockApex(const ApexFile& apex) const {
455 return block_disk_path_.has_value() &&
456 apex.GetPath().starts_with(*block_disk_path_);
457 }
458
GetPreInstalledApexFiles() const459 std::vector<ApexFileRef> ApexFileRepository::GetPreInstalledApexFiles() const {
460 std::vector<ApexFileRef> result;
461 for (const auto& it : pre_installed_store_) {
462 result.emplace_back(std::cref(it.second));
463 }
464 return std::move(result);
465 }
466
GetDataApexFiles() const467 std::vector<ApexFileRef> ApexFileRepository::GetDataApexFiles() const {
468 std::vector<ApexFileRef> result;
469 for (const auto& it : data_store_) {
470 result.emplace_back(std::cref(it.second));
471 }
472 return std::move(result);
473 }
474
475 // Group pre-installed APEX and data APEX by name
476 std::unordered_map<std::string, std::vector<ApexFileRef>>
AllApexFilesByName() const477 ApexFileRepository::AllApexFilesByName() const {
478 // Collect all apex files
479 std::vector<ApexFileRef> all_apex_files;
480 auto pre_installed_apexs = GetPreInstalledApexFiles();
481 auto data_apexs = GetDataApexFiles();
482 std::move(pre_installed_apexs.begin(), pre_installed_apexs.end(),
483 std::back_inserter(all_apex_files));
484 std::move(data_apexs.begin(), data_apexs.end(),
485 std::back_inserter(all_apex_files));
486
487 // Group them by name
488 std::unordered_map<std::string, std::vector<ApexFileRef>> result;
489 for (const auto& apex_file_ref : all_apex_files) {
490 const ApexFile& apex_file = apex_file_ref.get();
491 const std::string& package_name = apex_file.GetManifest().name();
492 if (result.find(package_name) == result.end()) {
493 result[package_name] = std::vector<ApexFileRef>{};
494 }
495 result[package_name].emplace_back(apex_file_ref);
496 }
497
498 return std::move(result);
499 }
500
GetDataApex(const std::string & name) const501 ApexFileRef ApexFileRepository::GetDataApex(const std::string& name) const {
502 auto it = data_store_.find(name);
503 CHECK(it != data_store_.end());
504 return std::cref(it->second);
505 }
506
GetPreInstalledApex(const std::string & name) const507 ApexFileRef ApexFileRepository::GetPreInstalledApex(
508 const std::string& name) const {
509 auto it = pre_installed_store_.find(name);
510 CHECK(it != pre_installed_store_.end());
511 return std::cref(it->second);
512 }
513
514 } // namespace apex
515 } // namespace android
516