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