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