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