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