• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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