• 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 #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     BlockApexOverride overrides;
265 
266     // A block device doesn't have an inherent timestamp, so it is carried in
267     // the metadata.
268     if (int64_t last_update_seconds = apex_config.last_update_seconds();
269         last_update_seconds != 0) {
270       overrides.last_update_seconds = last_update_seconds;
271     }
272 
273     // When metadata specifies the root digest of the apex, it should be used
274     // when activating the apex. So we need to keep it.
275     if (auto root_digest = apex_config.root_digest(); root_digest != "") {
276       overrides.block_apex_root_digest =
277           BytesToHex(reinterpret_cast<const uint8_t*>(root_digest.data()),
278                      root_digest.size());
279     }
280 
281     if (overrides.last_update_seconds.has_value() ||
282         overrides.block_apex_root_digest.has_value()) {
283       block_apex_overrides_.emplace(apex_path, std::move(overrides));
284     }
285 
286     // Depending on whether the APEX was a factory version in the host or not,
287     // put it to different stores.
288     auto& store = apex_config.is_factory() ? pre_installed_store_ : data_store_;
289     // We want "uniqueness" in each store.
290     if (auto it = store.find(name); it != store.end()) {
291       return Error() << "duplicate of " << name << " found in "
292                      << it->second.GetPath();
293     }
294     store.emplace(name, std::move(*apex_file));
295 
296     ret++;
297   }
298   return {ret};
299 }
300 
301 // TODO(b/179497746): AddDataApex should not concern with filtering out invalid
302 //   apex.
AddDataApex(const std::string & data_dir)303 Result<void> ApexFileRepository::AddDataApex(const std::string& data_dir) {
304   LOG(INFO) << "Scanning " << data_dir << " for data ApexFiles";
305   if (access(data_dir.c_str(), F_OK) != 0 && errno == ENOENT) {
306     LOG(WARNING) << data_dir << " does not exist. Skipping";
307     return {};
308   }
309 
310   Result<std::vector<std::string>> active_apex =
311       FindFilesBySuffix(data_dir, {kApexPackageSuffix});
312   if (!active_apex.ok()) {
313     return active_apex.error();
314   }
315 
316   // TODO(b/179248390): scan parallelly if possible
317   for (const auto& file : *active_apex) {
318     LOG(INFO) << "Found updated apex " << file;
319     Result<ApexFile> apex_file = ApexFile::Open(file);
320     if (!apex_file.ok()) {
321       LOG(ERROR) << "Failed to open " << file << " : " << apex_file.error();
322       continue;
323     }
324 
325     const std::string& name = apex_file->GetManifest().name();
326     if (!HasPreInstalledVersion(name)) {
327       LOG(ERROR) << "Skipping " << file << " : no preinstalled apex";
328       // Ignore data apex without corresponding pre-installed apex
329       continue;
330     }
331 
332     std::string select_filename = GetApexSelectFilenameFromProp(
333         multi_install_select_prop_prefixes_, name);
334     if (!select_filename.empty()) {
335       LOG(WARNING) << "APEX " << name << " is a multi-installed APEX."
336                    << " Any updated version in /data will always overwrite"
337                    << " the multi-installed preinstalled version, if possible.";
338     }
339 
340     auto pre_installed_public_key = GetPublicKey(name);
341     if (!pre_installed_public_key.ok() ||
342         apex_file->GetBundledPublicKey() != *pre_installed_public_key) {
343       // Ignore data apex if public key doesn't match with pre-installed apex
344       LOG(ERROR) << "Skipping " << file
345                  << " : public key doesn't match pre-installed one";
346       continue;
347     }
348 
349     if (EndsWith(apex_file->GetPath(), kDecompressedApexPackageSuffix)) {
350       LOG(WARNING) << "Skipping " << file
351                    << " : Non-decompressed APEX should not have "
352                    << kDecompressedApexPackageSuffix << " suffix";
353       continue;
354     }
355 
356     auto it = data_store_.find(name);
357     if (it == data_store_.end()) {
358       data_store_.emplace(name, std::move(*apex_file));
359       continue;
360     }
361 
362     const auto& existing_version = it->second.GetManifest().version();
363     const auto new_version = apex_file->GetManifest().version();
364     // If multiple data apexs are preset, select the one with highest version
365     bool prioritize_higher_version = new_version > existing_version;
366     // For same version, non-decompressed apex gets priority
367     if (prioritize_higher_version) {
368       it->second = std::move(*apex_file);
369     }
370   }
371   return {};
372 }
373 
374 // TODO(b/179497746): remove this method when we add api for fetching ApexFile
375 //  by name
GetPublicKey(const std::string & name) const376 Result<const std::string> ApexFileRepository::GetPublicKey(
377     const std::string& name) const {
378   auto it = pre_installed_store_.find(name);
379   if (it == pre_installed_store_.end()) {
380     // Special casing for APEXes backed by block devices, i.e. APEXes in VM.
381     // Inside a VM, we fall back to find the key from data_store_. This is
382     // because an APEX is put to either pre_installed_store_ or data_store,
383     // depending on whether it was a factory APEX or not in the host.
384     it = data_store_.find(name);
385     if (it != data_store_.end() && IsBlockApex(it->second)) {
386       return it->second.GetBundledPublicKey();
387     }
388     return Error() << "No preinstalled apex found for package " << name;
389   }
390   return it->second.GetBundledPublicKey();
391 }
392 
393 // TODO(b/179497746): remove this method when we add api for fetching ApexFile
394 //  by name
GetPreinstalledPath(const std::string & name) const395 Result<const std::string> ApexFileRepository::GetPreinstalledPath(
396     const std::string& name) const {
397   auto it = pre_installed_store_.find(name);
398   if (it == pre_installed_store_.end()) {
399     return Error() << "No preinstalled data found for package " << name;
400   }
401   return it->second.GetPath();
402 }
403 
404 // TODO(b/179497746): remove this method when we add api for fetching ApexFile
405 //  by name
GetDataPath(const std::string & name) const406 Result<const std::string> ApexFileRepository::GetDataPath(
407     const std::string& name) const {
408   auto it = data_store_.find(name);
409   if (it == data_store_.end()) {
410     return Error() << "No data apex found for package " << name;
411   }
412   return it->second.GetPath();
413 }
414 
GetBlockApexRootDigest(const std::string & path) const415 std::optional<std::string> ApexFileRepository::GetBlockApexRootDigest(
416     const std::string& path) const {
417   auto it = block_apex_overrides_.find(path);
418   if (it == block_apex_overrides_.end()) {
419     return std::nullopt;
420   }
421   return it->second.block_apex_root_digest;
422 }
423 
GetBlockApexLastUpdateSeconds(const std::string & path) const424 std::optional<int64_t> ApexFileRepository::GetBlockApexLastUpdateSeconds(
425     const std::string& path) const {
426   auto it = block_apex_overrides_.find(path);
427   if (it == block_apex_overrides_.end()) {
428     return std::nullopt;
429   }
430   return it->second.last_update_seconds;
431 }
432 
HasPreInstalledVersion(const std::string & name) const433 bool ApexFileRepository::HasPreInstalledVersion(const std::string& name) const {
434   return pre_installed_store_.find(name) != pre_installed_store_.end();
435 }
436 
HasDataVersion(const std::string & name) const437 bool ApexFileRepository::HasDataVersion(const std::string& name) const {
438   return data_store_.find(name) != data_store_.end();
439 }
440 
441 // ApexFile is considered a decompressed APEX if it is located in decompression
442 // dir
IsDecompressedApex(const ApexFile & apex) const443 bool ApexFileRepository::IsDecompressedApex(const ApexFile& apex) const {
444   return apex.GetPath().starts_with(decompression_dir_);
445 }
446 
IsPreInstalledApex(const ApexFile & apex) const447 bool ApexFileRepository::IsPreInstalledApex(const ApexFile& apex) const {
448   auto it = pre_installed_store_.find(apex.GetManifest().name());
449   if (it == pre_installed_store_.end()) {
450     return false;
451   }
452   return it->second.GetPath() == apex.GetPath() || IsDecompressedApex(apex);
453 }
454 
IsBlockApex(const ApexFile & apex) const455 bool ApexFileRepository::IsBlockApex(const ApexFile& apex) const {
456   return block_disk_path_.has_value() &&
457          apex.GetPath().starts_with(*block_disk_path_);
458 }
459 
GetPreInstalledApexFiles() const460 std::vector<ApexFileRef> ApexFileRepository::GetPreInstalledApexFiles() const {
461   std::vector<ApexFileRef> result;
462   result.reserve(pre_installed_store_.size());
463   for (const auto& it : pre_installed_store_) {
464     result.emplace_back(std::cref(it.second));
465   }
466   return std::move(result);
467 }
468 
GetDataApexFiles() const469 std::vector<ApexFileRef> ApexFileRepository::GetDataApexFiles() const {
470   std::vector<ApexFileRef> result;
471   result.reserve(data_store_.size());
472   for (const auto& it : data_store_) {
473     result.emplace_back(std::cref(it.second));
474   }
475   return std::move(result);
476 }
477 
478 // Group pre-installed APEX and data APEX by name
479 std::unordered_map<std::string, std::vector<ApexFileRef>>
AllApexFilesByName() const480 ApexFileRepository::AllApexFilesByName() const {
481   // Collect all apex files
482   std::vector<ApexFileRef> all_apex_files;
483   auto pre_installed_apexs = GetPreInstalledApexFiles();
484   auto data_apexs = GetDataApexFiles();
485   std::move(pre_installed_apexs.begin(), pre_installed_apexs.end(),
486             std::back_inserter(all_apex_files));
487   std::move(data_apexs.begin(), data_apexs.end(),
488             std::back_inserter(all_apex_files));
489 
490   // Group them by name
491   std::unordered_map<std::string, std::vector<ApexFileRef>> result;
492   for (const auto& apex_file_ref : all_apex_files) {
493     const ApexFile& apex_file = apex_file_ref.get();
494     const std::string& package_name = apex_file.GetManifest().name();
495     if (result.find(package_name) == result.end()) {
496       result[package_name] = std::vector<ApexFileRef>{};
497     }
498     result[package_name].emplace_back(apex_file_ref);
499   }
500 
501   return std::move(result);
502 }
503 
GetDataApex(const std::string & name) const504 ApexFileRef ApexFileRepository::GetDataApex(const std::string& name) const {
505   auto it = data_store_.find(name);
506   CHECK(it != data_store_.end());
507   return std::cref(it->second);
508 }
509 
GetPreInstalledApex(const std::string & name) const510 ApexFileRef ApexFileRepository::GetPreInstalledApex(
511     const std::string& name) const {
512   auto it = pre_installed_store_.find(name);
513   CHECK(it != pre_installed_store_.end());
514   return std::cref(it->second);
515 }
516 
517 }  // namespace apex
518 }  // namespace android
519