1 /*
2  * Copyright (C) 2020 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/logging.h>
21 #include <android-base/properties.h>
22 #include <android-base/result-gmock.h>
23 #include <android-base/stringprintf.h>
24 #include <errno.h>
25 #include <gmock/gmock.h>
26 #include <gtest/gtest.h>
27 #include <microdroid/metadata.h>
28 #include <sys/stat.h>
29 
30 #include <filesystem>
31 #include <string>
32 
33 #include "apex_file.h"
34 #include "apexd_test_utils.h"
35 #include "apexd_verity.h"
36 
37 namespace android {
38 namespace apex {
39 
40 using namespace std::literals;
41 
42 namespace fs = std::filesystem;
43 
44 using android::apex::testing::ApexFileEq;
45 using android::base::GetExecutableDirectory;
46 using android::base::StringPrintf;
47 using android::base::testing::Ok;
48 using ::testing::ByRef;
49 using ::testing::Not;
50 using ::testing::UnorderedElementsAre;
51 
GetTestDataDir()52 static std::string GetTestDataDir() { return GetExecutableDirectory(); }
GetTestFile(const std::string & name)53 static std::string GetTestFile(const std::string& name) {
54   return GetTestDataDir() + "/" + name;
55 }
56 
57 namespace {
58 // Copies the compressed apex to |built_in_dir| and decompresses it to
59 // |decompression_dir
PrepareCompressedApex(const std::string & name,const std::string & built_in_dir,const std::string & decompression_dir)60 void PrepareCompressedApex(const std::string& name,
61                            const std::string& built_in_dir,
62                            const std::string& decompression_dir) {
63   fs::copy(GetTestFile(name), built_in_dir);
64   auto compressed_apex =
65       ApexFile::Open(StringPrintf("%s/%s", built_in_dir.c_str(), name.c_str()));
66 
67   const auto& pkg_name = compressed_apex->GetManifest().name();
68   const int version = compressed_apex->GetManifest().version();
69 
70   auto decompression_path =
71       StringPrintf("%s/%s@%d%s", decompression_dir.c_str(), pkg_name.c_str(),
72                    version, kDecompressedApexPackageSuffix);
73   compressed_apex->Decompress(decompression_path);
74 }
75 }  // namespace
76 
TEST(ApexFileRepositoryTest,InitializeSuccess)77 TEST(ApexFileRepositoryTest, InitializeSuccess) {
78   // Prepare test data.
79   TemporaryDir built_in_dir, data_dir, decompression_dir;
80   fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path);
81   fs::copy(GetTestFile("apex.apexd_test_different_app.apex"),
82            built_in_dir.path);
83 
84   fs::copy(GetTestFile("apex.apexd_test.apex"), data_dir.path);
85   fs::copy(GetTestFile("apex.apexd_test_different_app.apex"), data_dir.path);
86 
87   ApexFileRepository instance;
88   ASSERT_RESULT_OK(instance.AddPreInstalledApex({built_in_dir.path}));
89   ASSERT_RESULT_OK(instance.AddDataApex(data_dir.path));
90 
91   // Now test that apexes were scanned correctly;
92   auto test_fn = [&](const std::string& apex_name) {
93     auto apex = ApexFile::Open(GetTestFile(apex_name));
94     ASSERT_RESULT_OK(apex);
95 
96     {
97       auto ret = instance.GetPublicKey(apex->GetManifest().name());
98       ASSERT_RESULT_OK(ret);
99       ASSERT_EQ(apex->GetBundledPublicKey(), *ret);
100     }
101 
102     {
103       auto ret = instance.GetPreinstalledPath(apex->GetManifest().name());
104       ASSERT_RESULT_OK(ret);
105       ASSERT_EQ(StringPrintf("%s/%s", built_in_dir.path, apex_name.c_str()),
106                 *ret);
107     }
108 
109     {
110       auto ret = instance.GetDataPath(apex->GetManifest().name());
111       ASSERT_RESULT_OK(ret);
112       ASSERT_EQ(StringPrintf("%s/%s", data_dir.path, apex_name.c_str()), *ret);
113     }
114 
115     ASSERT_TRUE(instance.HasPreInstalledVersion(apex->GetManifest().name()));
116     ASSERT_TRUE(instance.HasDataVersion(apex->GetManifest().name()));
117   };
118 
119   test_fn("apex.apexd_test.apex");
120   test_fn("apex.apexd_test_different_app.apex");
121 
122   // Check that second call will succeed as well.
123   ASSERT_RESULT_OK(instance.AddPreInstalledApex({built_in_dir.path}));
124   ASSERT_RESULT_OK(instance.AddDataApex(data_dir.path));
125 
126   test_fn("apex.apexd_test.apex");
127   test_fn("apex.apexd_test_different_app.apex");
128 }
129 
TEST(ApexFileRepositoryTest,InitializeFailureCorruptApex)130 TEST(ApexFileRepositoryTest, InitializeFailureCorruptApex) {
131   // Prepare test data.
132   TemporaryDir td;
133   fs::copy(GetTestFile("apex.apexd_test.apex"), td.path);
134   fs::copy(GetTestFile("apex.apexd_test_corrupt_superblock_apex.apex"),
135            td.path);
136 
137   ApexFileRepository instance;
138   ASSERT_THAT(instance.AddPreInstalledApex({td.path}), Not(Ok()));
139 }
140 
TEST(ApexFileRepositoryTest,InitializeCompressedApexWithoutApex)141 TEST(ApexFileRepositoryTest, InitializeCompressedApexWithoutApex) {
142   // Prepare test data.
143   TemporaryDir td;
144   fs::copy(GetTestFile("com.android.apex.compressed.v1_without_apex.capex"),
145            td.path);
146 
147   ApexFileRepository instance;
148   // Compressed APEX without APEX cannot be opened
149   ASSERT_THAT(instance.AddPreInstalledApex({td.path}), Not(Ok()));
150 }
151 
TEST(ApexFileRepositoryTest,InitializeSameNameDifferentPathAborts)152 TEST(ApexFileRepositoryTest, InitializeSameNameDifferentPathAborts) {
153   // Prepare test data.
154   TemporaryDir td;
155   fs::copy(GetTestFile("apex.apexd_test.apex"), td.path);
156   fs::copy(GetTestFile("apex.apexd_test.apex"),
157            StringPrintf("%s/other.apex", td.path));
158 
159   ASSERT_DEATH(
160       {
161         ApexFileRepository instance;
162         instance.AddPreInstalledApex({td.path});
163       },
164       "");
165 }
166 
TEST(ApexFileRepositoryTest,InitializeMultiInstalledSuccess)167 TEST(ApexFileRepositoryTest, InitializeMultiInstalledSuccess) {
168   // Prepare test data.
169   TemporaryDir td;
170   std::string apex_file = GetTestFile("apex.apexd_test.apex");
171   fs::copy(apex_file, StringPrintf("%s/version_a.apex", td.path));
172   fs::copy(apex_file, StringPrintf("%s/version_b.apex", td.path));
173   std::string apex_name = ApexFile::Open(apex_file)->GetManifest().name();
174 
175   std::string persist_prefix = "debug.apexd.test.persistprefix.";
176   std::string bootconfig_prefix = "debug.apexd.test.bootconfigprefix.";
177   ApexFileRepository instance(/*enforce_multi_install_partition=*/false,
178                               /*multi_install_select_prop_prefixes=*/{
179                                   persist_prefix, bootconfig_prefix});
180 
181   auto test_fn = [&](const std::string& selected_filename) {
182     ASSERT_RESULT_OK(instance.AddPreInstalledApex({td.path}));
183     auto ret = instance.GetPreinstalledPath(apex_name);
184     ASSERT_RESULT_OK(ret);
185     ASSERT_EQ(StringPrintf("%s/%s", td.path, selected_filename.c_str()), *ret);
186     instance.Reset();
187   };
188 
189   // Start with version_a in bootconfig.
190   android::base::SetProperty(bootconfig_prefix + apex_name, "version_a.apex");
191   test_fn("version_a.apex");
192   // Developer chooses version_b with persist prop.
193   android::base::SetProperty(persist_prefix + apex_name, "version_b.apex");
194   test_fn("version_b.apex");
195   // Developer goes back to version_a with persist prop.
196   android::base::SetProperty(persist_prefix + apex_name, "version_a.apex");
197   test_fn("version_a.apex");
198 
199   android::base::SetProperty(persist_prefix + apex_name, "");
200   android::base::SetProperty(bootconfig_prefix + apex_name, "");
201 }
202 
TEST(ApexFileRepositoryTest,InitializeMultiInstalledSkipsForDifferingKeys)203 TEST(ApexFileRepositoryTest, InitializeMultiInstalledSkipsForDifferingKeys) {
204   // Prepare test data.
205   TemporaryDir td;
206   fs::copy(GetTestFile("apex.apexd_test.apex"),
207            StringPrintf("%s/version_a.apex", td.path));
208   fs::copy(GetTestFile("apex.apexd_test_different_key.apex"),
209            StringPrintf("%s/version_b.apex", td.path));
210   std::string apex_name =
211       ApexFile::Open(GetTestFile("apex.apexd_test.apex"))->GetManifest().name();
212   std::string prop_prefix = "debug.apexd.test.bootconfigprefix.";
213   std::string prop = prop_prefix + apex_name;
214   android::base::SetProperty(prop, "version_a.apex");
215 
216   ApexFileRepository instance(
217       /*enforce_multi_install_partition=*/false,
218       /*multi_install_select_prop_prefixes=*/{prop_prefix});
219   ASSERT_RESULT_OK(instance.AddPreInstalledApex({td.path}));
220   // Neither version should be have been installed.
221   ASSERT_THAT(instance.GetPreinstalledPath(apex_name), Not(Ok()));
222 
223   android::base::SetProperty(prop, "");
224 }
225 
TEST(ApexFileRepositoryTest,InitializeMultiInstalledSkipsForInvalidPartition)226 TEST(ApexFileRepositoryTest, InitializeMultiInstalledSkipsForInvalidPartition) {
227   // Prepare test data.
228   TemporaryDir td;
229   // Note: These test files are on /data, which is not a valid partition for
230   // multi-installed APEXes.
231   fs::copy(GetTestFile("apex.apexd_test.apex"),
232            StringPrintf("%s/version_a.apex", td.path));
233   fs::copy(GetTestFile("apex.apexd_test.apex"),
234            StringPrintf("%s/version_b.apex", td.path));
235   std::string apex_name =
236       ApexFile::Open(GetTestFile("apex.apexd_test.apex"))->GetManifest().name();
237   std::string prop_prefix = "debug.apexd.test.bootconfigprefix.";
238   std::string prop = prop_prefix + apex_name;
239   android::base::SetProperty(prop, "version_a.apex");
240 
241   ApexFileRepository instance(
242       /*enforce_multi_install_partition=*/true,
243       /*multi_install_select_prop_prefixes=*/{prop_prefix});
244   ASSERT_RESULT_OK(instance.AddPreInstalledApex({td.path}));
245   // Neither version should be have been installed.
246   ASSERT_THAT(instance.GetPreinstalledPath(apex_name), Not(Ok()));
247 
248   android::base::SetProperty(prop, "");
249 }
250 
TEST(ApexFileRepositoryTest,InitializeSameNameDifferentPathAbortsCompressedApex)251 TEST(ApexFileRepositoryTest,
252      InitializeSameNameDifferentPathAbortsCompressedApex) {
253   // Prepare test data.
254   TemporaryDir td;
255   fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"), td.path);
256   fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"),
257            StringPrintf("%s/other.capex", td.path));
258 
259   ASSERT_DEATH(
260       {
261         ApexFileRepository instance;
262         instance.AddPreInstalledApex({td.path});
263       },
264       "");
265 }
266 
TEST(ApexFileRepositoryTest,InitializePublicKeyUnexpectdlyChangedAborts)267 TEST(ApexFileRepositoryTest, InitializePublicKeyUnexpectdlyChangedAborts) {
268   // Prepare test data.
269   TemporaryDir td;
270   fs::copy(GetTestFile("apex.apexd_test.apex"), td.path);
271 
272   ApexFileRepository instance;
273   ASSERT_RESULT_OK(instance.AddPreInstalledApex({td.path}));
274 
275   // Check that apex was loaded.
276   auto path = instance.GetPreinstalledPath("com.android.apex.test_package");
277   ASSERT_RESULT_OK(path);
278   ASSERT_EQ(StringPrintf("%s/apex.apexd_test.apex", td.path), *path);
279 
280   auto public_key = instance.GetPublicKey("com.android.apex.test_package");
281   ASSERT_RESULT_OK(public_key);
282 
283   // Substitute it with another apex with the same name, but different public
284   // key.
285   fs::copy(GetTestFile("apex.apexd_test_different_key.apex"), *path,
286            fs::copy_options::overwrite_existing);
287 
288   {
289     auto apex = ApexFile::Open(*path);
290     ASSERT_RESULT_OK(apex);
291     // Check module name hasn't changed.
292     ASSERT_EQ("com.android.apex.test_package", apex->GetManifest().name());
293     // Check public key has changed.
294     ASSERT_NE(*public_key, apex->GetBundledPublicKey());
295   }
296 
297   ASSERT_DEATH({ instance.AddPreInstalledApex({td.path}); }, "");
298 }
299 
TEST(ApexFileRepositoryTest,InitializePublicKeyUnexpectdlyChangedAbortsCompressedApex)300 TEST(ApexFileRepositoryTest,
301      InitializePublicKeyUnexpectdlyChangedAbortsCompressedApex) {
302   // Prepare test data.
303   TemporaryDir td;
304   fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"), td.path);
305 
306   ApexFileRepository instance;
307   ASSERT_RESULT_OK(instance.AddPreInstalledApex({td.path}));
308 
309   // Check that apex was loaded.
310   auto path = instance.GetPreinstalledPath("com.android.apex.compressed");
311   ASSERT_RESULT_OK(path);
312   ASSERT_EQ(StringPrintf("%s/com.android.apex.compressed.v1.capex", td.path),
313             *path);
314 
315   auto public_key = instance.GetPublicKey("com.android.apex.compressed");
316   ASSERT_RESULT_OK(public_key);
317 
318   // Substitute it with another apex with the same name, but different public
319   // key.
320   fs::copy(GetTestFile("com.android.apex.compressed_different_key.capex"),
321            *path, fs::copy_options::overwrite_existing);
322 
323   {
324     auto apex = ApexFile::Open(*path);
325     ASSERT_RESULT_OK(apex);
326     // Check module name hasn't changed.
327     ASSERT_EQ("com.android.apex.compressed", apex->GetManifest().name());
328     // Check public key has changed.
329     ASSERT_NE(*public_key, apex->GetBundledPublicKey());
330   }
331 
332   ASSERT_DEATH({ instance.AddPreInstalledApex({td.path}); }, "");
333 }
334 
TEST(ApexFileRepositoryTest,IsPreInstalledApex)335 TEST(ApexFileRepositoryTest, IsPreInstalledApex) {
336   // Prepare test data.
337   TemporaryDir td;
338   fs::copy(GetTestFile("apex.apexd_test.apex"), td.path);
339   fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"), td.path);
340 
341   ApexFileRepository instance;
342   ASSERT_RESULT_OK(instance.AddPreInstalledApex({td.path}));
343 
344   auto compressed_apex = ApexFile::Open(
345       StringPrintf("%s/com.android.apex.compressed.v1.capex", td.path));
346   ASSERT_RESULT_OK(compressed_apex);
347   ASSERT_TRUE(instance.IsPreInstalledApex(*compressed_apex));
348 
349   auto apex1 = ApexFile::Open(StringPrintf("%s/apex.apexd_test.apex", td.path));
350   ASSERT_RESULT_OK(apex1);
351   ASSERT_TRUE(instance.IsPreInstalledApex(*apex1));
352 
353   // It's same apex, but path is different. Shouldn't be treated as
354   // pre-installed.
355   auto apex2 = ApexFile::Open(GetTestFile("apex.apexd_test.apex"));
356   ASSERT_RESULT_OK(apex2);
357   ASSERT_FALSE(instance.IsPreInstalledApex(*apex2));
358 
359   auto apex3 =
360       ApexFile::Open(GetTestFile("apex.apexd_test_different_app.apex"));
361   ASSERT_RESULT_OK(apex3);
362   ASSERT_FALSE(instance.IsPreInstalledApex(*apex3));
363 }
364 
TEST(ApexFileRepositoryTest,IsDecompressedApex)365 TEST(ApexFileRepositoryTest, IsDecompressedApex) {
366   // Prepare instance
367   TemporaryDir decompression_dir;
368   ApexFileRepository instance(decompression_dir.path);
369 
370   // Prepare decompressed apex
371   std::string filename = "com.android.apex.compressed.v1.apex";
372   fs::copy(GetTestFile(filename), decompression_dir.path);
373   auto decompressed_path =
374       StringPrintf("%s/%s", decompression_dir.path, filename.c_str());
375   auto decompressed_apex = ApexFile::Open(decompressed_path);
376 
377   // Any file which is already located in |decompression_dir| should be
378   // considered decompressed
379   ASSERT_TRUE(instance.IsDecompressedApex(*decompressed_apex));
380 
381   // Hard links with same file name is not considered decompressed
382   TemporaryDir active_dir;
383   auto active_path = StringPrintf("%s/%s", active_dir.path, filename.c_str());
384   std::error_code ec;
385   fs::create_hard_link(decompressed_path, active_path, ec);
386   ASSERT_FALSE(ec) << "Failed to create hardlink";
387   auto active_apex = ApexFile::Open(active_path);
388   ASSERT_FALSE(instance.IsDecompressedApex(*active_apex));
389 }
390 
TEST(ApexFileRepositoryTest,AddAndGetDataApex)391 TEST(ApexFileRepositoryTest, AddAndGetDataApex) {
392   // Prepare test data.
393   TemporaryDir built_in_dir, data_dir, decompression_dir;
394   fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path);
395   fs::copy(GetTestFile("apex.apexd_test_v2.apex"), data_dir.path);
396   PrepareCompressedApex("com.android.apex.compressed.v1.capex",
397                         built_in_dir.path, decompression_dir.path);
398   // Add a data apex that has kDecompressedApexPackageSuffix
399   fs::copy(GetTestFile("com.android.apex.compressed.v1.apex"),
400            StringPrintf("%s/com.android.apex.compressed@1%s", data_dir.path,
401                         kDecompressedApexPackageSuffix));
402 
403   ApexFileRepository instance(decompression_dir.path);
404   ASSERT_RESULT_OK(instance.AddPreInstalledApex({built_in_dir.path}));
405   ASSERT_RESULT_OK(instance.AddDataApex(data_dir.path));
406 
407   // ApexFileRepository should only deal with APEX in /data/apex/active.
408   // Decompressed APEX should not be included
409   auto data_apexs = instance.GetDataApexFiles();
410   auto normal_apex =
411       ApexFile::Open(StringPrintf("%s/apex.apexd_test_v2.apex", data_dir.path));
412   ASSERT_THAT(data_apexs,
413               UnorderedElementsAre(ApexFileEq(ByRef(*normal_apex))));
414 }
415 
TEST(ApexFileRepositoryTest,AddDataApexIgnoreCompressedApex)416 TEST(ApexFileRepositoryTest, AddDataApexIgnoreCompressedApex) {
417   // Prepare test data.
418   TemporaryDir data_dir, decompression_dir;
419   fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"), data_dir.path);
420 
421   ApexFileRepository instance;
422   ASSERT_RESULT_OK(instance.AddDataApex(data_dir.path));
423 
424   auto data_apexs = instance.GetDataApexFiles();
425   ASSERT_EQ(data_apexs.size(), 0u);
426 }
427 
TEST(ApexFileRepositoryTest,AddDataApexIgnoreIfNotPreInstalled)428 TEST(ApexFileRepositoryTest, AddDataApexIgnoreIfNotPreInstalled) {
429   // Prepare test data.
430   TemporaryDir data_dir, decompression_dir;
431   fs::copy(GetTestFile("apex.apexd_test.apex"), data_dir.path);
432 
433   ApexFileRepository instance;
434   ASSERT_RESULT_OK(instance.AddDataApex(data_dir.path));
435 
436   auto data_apexs = instance.GetDataApexFiles();
437   ASSERT_EQ(data_apexs.size(), 0u);
438 }
439 
TEST(ApexFileRepositoryTest,AddDataApexPrioritizeHigherVersionApex)440 TEST(ApexFileRepositoryTest, AddDataApexPrioritizeHigherVersionApex) {
441   // Prepare test data.
442   TemporaryDir built_in_dir, data_dir, decompression_dir;
443   fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path);
444   fs::copy(GetTestFile("apex.apexd_test.apex"), data_dir.path);
445   fs::copy(GetTestFile("apex.apexd_test_v2.apex"), data_dir.path);
446 
447   ApexFileRepository instance;
448   ASSERT_RESULT_OK(instance.AddPreInstalledApex({built_in_dir.path}));
449   ASSERT_RESULT_OK(instance.AddDataApex(data_dir.path));
450 
451   auto data_apexs = instance.GetDataApexFiles();
452   auto normal_apex =
453       ApexFile::Open(StringPrintf("%s/apex.apexd_test_v2.apex", data_dir.path));
454   ASSERT_THAT(data_apexs,
455               UnorderedElementsAre(ApexFileEq(ByRef(*normal_apex))));
456 }
457 
TEST(ApexFileRepositoryTest,AddDataApexDoesNotScanDecompressedApex)458 TEST(ApexFileRepositoryTest, AddDataApexDoesNotScanDecompressedApex) {
459   // Prepare test data.
460   TemporaryDir built_in_dir, data_dir, decompression_dir;
461   PrepareCompressedApex("com.android.apex.compressed.v1.capex",
462                         built_in_dir.path, decompression_dir.path);
463 
464   ApexFileRepository instance(decompression_dir.path);
465   ASSERT_RESULT_OK(instance.AddPreInstalledApex({built_in_dir.path}));
466   ASSERT_RESULT_OK(instance.AddDataApex(data_dir.path));
467 
468   auto data_apexs = instance.GetDataApexFiles();
469   ASSERT_EQ(data_apexs.size(), 0u);
470 }
471 
TEST(ApexFileRepositoryTest,AddDataApexIgnoreWrongPublicKey)472 TEST(ApexFileRepositoryTest, AddDataApexIgnoreWrongPublicKey) {
473   // Prepare test data.
474   TemporaryDir built_in_dir, data_dir, decompression_dir;
475   fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path);
476   fs::copy(GetTestFile("apex.apexd_test_different_key.apex"), data_dir.path);
477 
478   ApexFileRepository instance;
479   ASSERT_RESULT_OK(instance.AddPreInstalledApex({built_in_dir.path}));
480   ASSERT_RESULT_OK(instance.AddDataApex(data_dir.path));
481 
482   auto data_apexs = instance.GetDataApexFiles();
483   ASSERT_EQ(data_apexs.size(), 0u);
484 }
485 
TEST(ApexFileRepositoryTest,GetPreInstalledApexFiles)486 TEST(ApexFileRepositoryTest, GetPreInstalledApexFiles) {
487   // Prepare test data.
488   TemporaryDir built_in_dir;
489   fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path);
490   fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"),
491            built_in_dir.path);
492 
493   ApexFileRepository instance;
494   ASSERT_RESULT_OK(instance.AddPreInstalledApex({built_in_dir.path}));
495 
496   auto pre_installed_apexs = instance.GetPreInstalledApexFiles();
497   auto pre_apex_1 = ApexFile::Open(
498       StringPrintf("%s/apex.apexd_test.apex", built_in_dir.path));
499   auto pre_apex_2 = ApexFile::Open(StringPrintf(
500       "%s/com.android.apex.compressed.v1.capex", built_in_dir.path));
501   ASSERT_THAT(pre_installed_apexs,
502               UnorderedElementsAre(ApexFileEq(ByRef(*pre_apex_1)),
503                                    ApexFileEq(ByRef(*pre_apex_2))));
504 }
505 
TEST(ApexFileRepositoryTest,AllApexFilesByName)506 TEST(ApexFileRepositoryTest, AllApexFilesByName) {
507   TemporaryDir built_in_dir, decompression_dir;
508   fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path);
509   fs::copy(GetTestFile("com.android.apex.cts.shim.apex"), built_in_dir.path);
510   fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"),
511            built_in_dir.path);
512   ApexFileRepository instance;
513   ASSERT_RESULT_OK(instance.AddPreInstalledApex({built_in_dir.path}));
514 
515   TemporaryDir data_dir;
516   fs::copy(GetTestFile("com.android.apex.cts.shim.v2.apex"), data_dir.path);
517   ASSERT_RESULT_OK(instance.AddDataApex(data_dir.path));
518 
519   auto result = instance.AllApexFilesByName();
520 
521   // Verify the contents of result
522   auto apexd_test_file = ApexFile::Open(
523       StringPrintf("%s/apex.apexd_test.apex", built_in_dir.path));
524   auto shim_v1 = ApexFile::Open(
525       StringPrintf("%s/com.android.apex.cts.shim.apex", built_in_dir.path));
526   auto compressed_apex = ApexFile::Open(StringPrintf(
527       "%s/com.android.apex.compressed.v1.capex", built_in_dir.path));
528   auto shim_v2 = ApexFile::Open(
529       StringPrintf("%s/com.android.apex.cts.shim.v2.apex", data_dir.path));
530 
531   ASSERT_EQ(result.size(), 3u);
532   ASSERT_THAT(result[apexd_test_file->GetManifest().name()],
533               UnorderedElementsAre(ApexFileEq(ByRef(*apexd_test_file))));
534   ASSERT_THAT(result[shim_v1->GetManifest().name()],
535               UnorderedElementsAre(ApexFileEq(ByRef(*shim_v1)),
536                                    ApexFileEq(ByRef(*shim_v2))));
537   ASSERT_THAT(result[compressed_apex->GetManifest().name()],
538               UnorderedElementsAre(ApexFileEq(ByRef(*compressed_apex))));
539 }
540 
TEST(ApexFileRepositoryTest,GetDataApex)541 TEST(ApexFileRepositoryTest, GetDataApex) {
542   // Prepare test data.
543   TemporaryDir built_in_dir, data_dir;
544   fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path);
545   fs::copy(GetTestFile("apex.apexd_test_v2.apex"), data_dir.path);
546 
547   ApexFileRepository instance;
548   ASSERT_RESULT_OK(instance.AddPreInstalledApex({built_in_dir.path}));
549   ASSERT_RESULT_OK(instance.AddDataApex(data_dir.path));
550 
551   auto apex =
552       ApexFile::Open(StringPrintf("%s/apex.apexd_test_v2.apex", data_dir.path));
553   ASSERT_RESULT_OK(apex);
554 
555   auto ret = instance.GetDataApex("com.android.apex.test_package");
556   ASSERT_THAT(ret, ApexFileEq(ByRef(*apex)));
557 }
558 
TEST(ApexFileRepositoryTest,GetDataApexNoSuchApexAborts)559 TEST(ApexFileRepositoryTest, GetDataApexNoSuchApexAborts) {
560   ASSERT_DEATH(
561       {
562         ApexFileRepository instance;
563         instance.GetDataApex("whatever");
564       },
565       "");
566 }
567 
TEST(ApexFileRepositoryTest,GetPreInstalledApex)568 TEST(ApexFileRepositoryTest, GetPreInstalledApex) {
569   // Prepare test data.
570   TemporaryDir built_in_dir;
571   fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path);
572 
573   ApexFileRepository instance;
574   ASSERT_RESULT_OK(instance.AddPreInstalledApex({built_in_dir.path}));
575 
576   auto apex = ApexFile::Open(
577       StringPrintf("%s/apex.apexd_test.apex", built_in_dir.path));
578   ASSERT_RESULT_OK(apex);
579 
580   auto ret = instance.GetPreInstalledApex("com.android.apex.test_package");
581   ASSERT_THAT(ret, ApexFileEq(ByRef(*apex)));
582 }
583 
TEST(ApexFileRepositoryTest,GetPreInstalledApexNoSuchApexAborts)584 TEST(ApexFileRepositoryTest, GetPreInstalledApexNoSuchApexAborts) {
585   ASSERT_DEATH(
586       {
587         ApexFileRepository instance;
588         instance.GetPreInstalledApex("whatever");
589       },
590       "");
591 }
592 
593 struct ApexFileRepositoryTestAddBlockApex : public ::testing::Test {
594   TemporaryDir test_dir;
595 
596   struct ApexMetadata {
597     std::string public_key;
598     std::string root_digest;
599     int64_t last_update_seconds;
600     bool is_factory = true;
601     int64_t manifest_version;
602     std::string manifest_name;
603   };
604 
605   struct PayloadMetadata {
606     android::microdroid::Metadata metadata;
607     std::string path;
PayloadMetadataandroid::apex::ApexFileRepositoryTestAddBlockApex::PayloadMetadata608     PayloadMetadata(const std::string& path) : path(path) {}
apexandroid::apex::ApexFileRepositoryTestAddBlockApex::PayloadMetadata609     PayloadMetadata& apex(const std::string& name) {
610       return apex(name, ApexMetadata{});
611     }
apexandroid::apex::ApexFileRepositoryTestAddBlockApex::PayloadMetadata612     PayloadMetadata& apex(const std::string& name,
613                           const ApexMetadata& apex_metadata) {
614       auto apex = metadata.add_apexes();
615       apex->set_name(name);
616       apex->set_public_key(apex_metadata.public_key);
617       apex->set_root_digest(apex_metadata.root_digest);
618       apex->set_last_update_seconds(apex_metadata.last_update_seconds);
619       apex->set_is_factory(apex_metadata.is_factory);
620       apex->set_manifest_version(apex_metadata.manifest_version);
621       apex->set_manifest_name(apex_metadata.manifest_name);
622       return *this;
623     }
~PayloadMetadataandroid::apex::ApexFileRepositoryTestAddBlockApex::PayloadMetadata624     ~PayloadMetadata() {
625       metadata.set_version(1);
626       std::ofstream out(path);
627       android::microdroid::WriteMetadata(metadata, out);
628     }
629   };
630 };
631 
TEST_F(ApexFileRepositoryTestAddBlockApex,ScansPayloadDisksAndAddApexFilesToPreInstalled)632 TEST_F(ApexFileRepositoryTestAddBlockApex,
633        ScansPayloadDisksAndAddApexFilesToPreInstalled) {
634   // prepare payload disk
635   //  <test-dir>/vdc1 : metadata
636   //            /vdc2 : apex.apexd_test.apex
637   //            /vdc3 : apex.apexd_test_different_app.apex
638 
639   const auto& test_apex_foo = GetTestFile("apex.apexd_test.apex");
640   const auto& test_apex_bar = GetTestFile("apex.apexd_test_different_app.apex");
641 
642   const std::string metadata_partition_path = test_dir.path + "/vdc1"s;
643   const std::string apex_foo_path = test_dir.path + "/vdc2"s;
644   const std::string apex_bar_path = test_dir.path + "/vdc3"s;
645 
646   PayloadMetadata(metadata_partition_path)
647       .apex(test_apex_foo)
648       .apex(test_apex_bar);
649   auto block_apex1 = WriteBlockApex(test_apex_foo, apex_foo_path);
650   auto block_apex2 = WriteBlockApex(test_apex_bar, apex_bar_path);
651 
652   // call ApexFileRepository::AddBlockApex()
653   ApexFileRepository instance;
654   auto status = instance.AddBlockApex(metadata_partition_path);
655   ASSERT_RESULT_OK(status);
656 
657   auto apex_foo = ApexFile::Open(apex_foo_path);
658   ASSERT_RESULT_OK(apex_foo);
659   // block apexes can be identified with IsBlockApex
660   ASSERT_TRUE(instance.IsBlockApex(*apex_foo));
661 
662   // "block" apexes are treated as "pre-installed"
663   auto ret_foo = instance.GetPreInstalledApex("com.android.apex.test_package");
664   ASSERT_THAT(ret_foo, ApexFileEq(ByRef(*apex_foo)));
665 
666   auto apex_bar = ApexFile::Open(apex_bar_path);
667   ASSERT_RESULT_OK(apex_bar);
668   auto ret_bar =
669       instance.GetPreInstalledApex("com.android.apex.test_package_2");
670   ASSERT_THAT(ret_bar, ApexFileEq(ByRef(*apex_bar)));
671 }
672 
TEST_F(ApexFileRepositoryTestAddBlockApex,ScansOnlySpecifiedInMetadataPartition)673 TEST_F(ApexFileRepositoryTestAddBlockApex,
674        ScansOnlySpecifiedInMetadataPartition) {
675   // prepare payload disk
676   //  <test-dir>/vdc1 : metadata with apex.apexd_test.apex only
677   //            /vdc2 : apex.apexd_test.apex
678   //            /vdc3 : apex.apexd_test_different_app.apex
679 
680   const auto& test_apex_foo = GetTestFile("apex.apexd_test.apex");
681   const auto& test_apex_bar = GetTestFile("apex.apexd_test_different_app.apex");
682 
683   const std::string metadata_partition_path = test_dir.path + "/vdc1"s;
684   const std::string apex_foo_path = test_dir.path + "/vdc2"s;
685   const std::string apex_bar_path = test_dir.path + "/vdc3"s;
686 
687   // metadata lists only "foo"
688   PayloadMetadata(metadata_partition_path).apex(test_apex_foo);
689   auto block_apex1 = WriteBlockApex(test_apex_foo, apex_foo_path);
690   auto block_apex2 = WriteBlockApex(test_apex_bar, apex_bar_path);
691 
692   // call ApexFileRepository::AddBlockApex()
693   ApexFileRepository instance;
694   auto status = instance.AddBlockApex(metadata_partition_path);
695   ASSERT_RESULT_OK(status);
696 
697   // foo is added, but bar is not
698   auto ret_foo = instance.GetPreinstalledPath("com.android.apex.test_package");
699   ASSERT_RESULT_OK(ret_foo);
700   ASSERT_EQ(apex_foo_path, *ret_foo);
701   auto ret_bar =
702       instance.GetPreinstalledPath("com.android.apex.test_package_2");
703   ASSERT_THAT(ret_bar, Not(Ok()));
704 }
705 
TEST_F(ApexFileRepositoryTestAddBlockApex,FailsWhenTheresDuplicateNames)706 TEST_F(ApexFileRepositoryTestAddBlockApex, FailsWhenTheresDuplicateNames) {
707   // prepare payload disk
708   //  <test-dir>/vdc1 : metadata with v1 and v2 of apex.apexd_test
709   //            /vdc2 : apex.apexd_test.apex
710   //            /vdc3 : apex.apexd_test_v2.apex
711 
712   const auto& test_apex_foo = GetTestFile("apex.apexd_test.apex");
713   const auto& test_apex_bar = GetTestFile("apex.apexd_test_v2.apex");
714 
715   const std::string metadata_partition_path = test_dir.path + "/vdc1"s;
716   const std::string apex_foo_path = test_dir.path + "/vdc2"s;
717   const std::string apex_bar_path = test_dir.path + "/vdc3"s;
718 
719   PayloadMetadata(metadata_partition_path)
720       .apex(test_apex_foo)
721       .apex(test_apex_bar);
722   auto block_apex1 = WriteBlockApex(test_apex_foo, apex_foo_path);
723   auto block_apex2 = WriteBlockApex(test_apex_bar, apex_bar_path);
724 
725   ApexFileRepository instance;
726   auto status = instance.AddBlockApex(metadata_partition_path);
727   ASSERT_THAT(status, Not(Ok()));
728 }
729 
TEST_F(ApexFileRepositoryTestAddBlockApex,GetBlockApexRootDigest)730 TEST_F(ApexFileRepositoryTestAddBlockApex, GetBlockApexRootDigest) {
731   // prepare payload disk with root digest
732   //  <test-dir>/vdc1 : metadata with apex.apexd_test.apex only
733   //            /vdc2 : apex.apexd_test.apex
734 
735   const auto& test_apex_foo = GetTestFile("apex.apexd_test.apex");
736 
737   const std::string metadata_partition_path = test_dir.path + "/vdc1"s;
738   const std::string apex_foo_path = test_dir.path + "/vdc2"s;
739 
740   // root digest is stored as bytes in metadata and as hexadecimal in
741   // ApexFileRepository
742   const std::string root_digest = "root_digest";
743   const std::string hex_root_digest = BytesToHex(
744       reinterpret_cast<const uint8_t*>(root_digest.data()), root_digest.size());
745 
746   // metadata lists "foo"
747   ApexMetadata apex_metadata;
748   apex_metadata.root_digest = root_digest;
749   PayloadMetadata(metadata_partition_path).apex(test_apex_foo, apex_metadata);
750   auto block_apex1 = WriteBlockApex(test_apex_foo, apex_foo_path);
751 
752   // call ApexFileRepository::AddBlockApex()
753   ApexFileRepository instance;
754   auto status = instance.AddBlockApex(metadata_partition_path);
755   ASSERT_RESULT_OK(status);
756 
757   ASSERT_EQ(hex_root_digest, instance.GetBlockApexRootDigest(apex_foo_path));
758 }
759 
TEST_F(ApexFileRepositoryTestAddBlockApex,GetBlockApexLastUpdateSeconds)760 TEST_F(ApexFileRepositoryTestAddBlockApex, GetBlockApexLastUpdateSeconds) {
761   // prepare payload disk with last update time
762   //  <test-dir>/vdc1 : metadata with apex.apexd_test.apex only
763   //            /vdc2 : apex.apexd_test.apex
764 
765   const auto& test_apex_foo = GetTestFile("apex.apexd_test.apex");
766 
767   const std::string metadata_partition_path = test_dir.path + "/vdc1"s;
768   const std::string apex_foo_path = test_dir.path + "/vdc2"s;
769 
770   const int64_t last_update_seconds = 123456789;
771 
772   // metadata lists "foo"
773   ApexMetadata apex_metadata;
774   apex_metadata.last_update_seconds = last_update_seconds;
775   PayloadMetadata(metadata_partition_path).apex(test_apex_foo, apex_metadata);
776   auto block_apex1 = WriteBlockApex(test_apex_foo, apex_foo_path);
777 
778   // call ApexFileRepository::AddBlockApex()
779   ApexFileRepository instance;
780   auto status = instance.AddBlockApex(metadata_partition_path);
781   ASSERT_RESULT_OK(status);
782 
783   ASSERT_EQ(last_update_seconds,
784             instance.GetBlockApexLastUpdateSeconds(apex_foo_path));
785 }
786 
TEST_F(ApexFileRepositoryTestAddBlockApex,SucceedsWhenMetadataMatches)787 TEST_F(ApexFileRepositoryTestAddBlockApex, SucceedsWhenMetadataMatches) {
788   // prepare payload disk
789   //  <test-dir>/vdc1 : metadata with apex.apexd_test.apex only
790   //            /vdc2 : apex.apexd_test.apex
791 
792   const auto& test_apex_foo = GetTestFile("apex.apexd_test.apex");
793 
794   const std::string metadata_partition_path = test_dir.path + "/vdc1"s;
795   const std::string apex_foo_path = test_dir.path + "/vdc2"s;
796 
797   std::string public_key;
798   const auto& key_path =
799       GetTestFile("apexd_testdata/com.android.apex.test_package.avbpubkey");
800   ASSERT_TRUE(android::base::ReadFileToString(key_path, &public_key))
801       << "Failed to read " << key_path;
802 
803   // metadata lists "foo"
804   ApexMetadata apex_metadata;
805   apex_metadata.public_key = public_key;
806   apex_metadata.manifest_version = 1;
807   apex_metadata.manifest_name = "com.android.apex.test_package";
808   PayloadMetadata(metadata_partition_path).apex(test_apex_foo, apex_metadata);
809   auto block_apex1 = WriteBlockApex(test_apex_foo, apex_foo_path);
810 
811   // call ApexFileRepository::AddBlockApex()
812   ApexFileRepository instance;
813   auto status = instance.AddBlockApex(metadata_partition_path);
814   ASSERT_RESULT_OK(status);
815 }
816 
TEST_F(ApexFileRepositoryTestAddBlockApex,VerifyPublicKeyWhenAddingBlockApex)817 TEST_F(ApexFileRepositoryTestAddBlockApex, VerifyPublicKeyWhenAddingBlockApex) {
818   // prepare payload disk
819   //  <test-dir>/vdc1 : metadata with apex.apexd_test.apex only
820   //            /vdc2 : apex.apexd_test.apex
821 
822   const auto& test_apex_foo = GetTestFile("apex.apexd_test.apex");
823 
824   const std::string metadata_partition_path = test_dir.path + "/vdc1"s;
825   const std::string apex_foo_path = test_dir.path + "/vdc2"s;
826 
827   // metadata lists "foo"
828   ApexMetadata apex_metadata;
829   apex_metadata.public_key = "wrong public key";
830   PayloadMetadata(metadata_partition_path).apex(test_apex_foo, apex_metadata);
831   auto block_apex1 = WriteBlockApex(test_apex_foo, apex_foo_path);
832 
833   // call ApexFileRepository::AddBlockApex()
834   ApexFileRepository instance;
835   auto status = instance.AddBlockApex(metadata_partition_path);
836   ASSERT_THAT(status, Not(Ok()));
837 }
838 
TEST_F(ApexFileRepositoryTestAddBlockApex,VerifyManifestVersionWhenAddingBlockApex)839 TEST_F(ApexFileRepositoryTestAddBlockApex,
840        VerifyManifestVersionWhenAddingBlockApex) {
841   // prepare payload disk
842   //  <test-dir>/vdc1 : metadata with apex.apexd_test.apex only
843   //            /vdc2 : apex.apexd_test.apex
844 
845   const auto& test_apex_foo = GetTestFile("apex.apexd_test.apex");
846 
847   const std::string metadata_partition_path = test_dir.path + "/vdc1"s;
848   const std::string apex_foo_path = test_dir.path + "/vdc2"s;
849 
850   // metadata lists "foo"
851   ApexMetadata apex_metadata;
852   apex_metadata.manifest_version = 2;
853   PayloadMetadata(metadata_partition_path).apex(test_apex_foo, apex_metadata);
854   auto block_apex1 = WriteBlockApex(test_apex_foo, apex_foo_path);
855 
856   // call ApexFileRepository::AddBlockApex()
857   ApexFileRepository instance;
858   auto status = instance.AddBlockApex(metadata_partition_path);
859   ASSERT_THAT(status, Not(Ok()));
860 }
861 
TEST_F(ApexFileRepositoryTestAddBlockApex,VerifyManifestNameWhenAddingBlockApex)862 TEST_F(ApexFileRepositoryTestAddBlockApex,
863        VerifyManifestNameWhenAddingBlockApex) {
864   // prepare payload disk
865   //  <test-dir>/vdc1 : metadata with apex.apexd_test.apex only
866   //            /vdc2 : apex.apexd_test.apex
867 
868   const auto& test_apex_foo = GetTestFile("apex.apexd_test.apex");
869 
870   const std::string metadata_partition_path = test_dir.path + "/vdc1"s;
871   const std::string apex_foo_path = test_dir.path + "/vdc2"s;
872 
873   // metadata lists "foo"
874   ApexMetadata apex_metadata;
875   apex_metadata.manifest_name = "Wrong name";
876   PayloadMetadata(metadata_partition_path).apex(test_apex_foo, apex_metadata);
877   auto block_apex1 = WriteBlockApex(test_apex_foo, apex_foo_path);
878 
879   // call ApexFileRepository::AddBlockApex()
880   ApexFileRepository instance;
881   auto status = instance.AddBlockApex(metadata_partition_path);
882   ASSERT_THAT(status, Not(Ok()));
883 }
884 
TEST_F(ApexFileRepositoryTestAddBlockApex,RespectIsFactoryBitFromMetadata)885 TEST_F(ApexFileRepositoryTestAddBlockApex, RespectIsFactoryBitFromMetadata) {
886   // prepare payload disk
887   //  <test-dir>/vdc1 : metadata with apex.apexd_test.apex only
888   //            /vdc2 : apex.apexd_test.apex
889 
890   const auto& test_apex_foo = GetTestFile("apex.apexd_test.apex");
891 
892   const std::string metadata_partition_path = test_dir.path + "/vdc1"s;
893   const std::string apex_foo_path = test_dir.path + "/vdc2"s;
894   auto block_apex1 = WriteBlockApex(test_apex_foo, apex_foo_path);
895 
896   for (const bool is_factory : {true, false}) {
897     // metadata lists "foo"
898     ApexMetadata apex_metadata;
899     apex_metadata.is_factory = is_factory;
900     PayloadMetadata(metadata_partition_path).apex(test_apex_foo, apex_metadata);
901 
902     // call ApexFileRepository::AddBlockApex()
903     ApexFileRepository instance;
904     auto status = instance.AddBlockApex(metadata_partition_path);
905     ASSERT_RESULT_OK(status)
906         << "failed to add block apex with is_factory=" << is_factory;
907     ASSERT_EQ(is_factory,
908               instance.HasPreInstalledVersion("com.android.apex.test_package"));
909   }
910 }
911 
912 }  // namespace apex
913 }  // namespace android
914