/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include "apex_file.h" #include "apex_file_repository.h" #include "apexd_test_utils.h" #include "apexd_verity.h" namespace android { namespace apex { using namespace std::literals; namespace fs = std::filesystem; using android::apex::testing::ApexFileEq; using android::apex::testing::IsOk; using android::base::GetExecutableDirectory; using android::base::StringPrintf; using ::testing::ByRef; using ::testing::UnorderedElementsAre; static std::string GetTestDataDir() { return GetExecutableDirectory(); } static std::string GetTestFile(const std::string& name) { return GetTestDataDir() + "/" + name; } namespace { // Copies the compressed apex to |built_in_dir| and decompresses it to // |decompression_dir void PrepareCompressedApex(const std::string& name, const std::string& built_in_dir, const std::string& decompression_dir) { fs::copy(GetTestFile(name), built_in_dir); auto compressed_apex = ApexFile::Open(StringPrintf("%s/%s", built_in_dir.c_str(), name.c_str())); const auto& pkg_name = compressed_apex->GetManifest().name(); const int version = compressed_apex->GetManifest().version(); auto decompression_path = StringPrintf("%s/%s@%d%s", decompression_dir.c_str(), pkg_name.c_str(), version, kDecompressedApexPackageSuffix); compressed_apex->Decompress(decompression_path); } } // namespace TEST(ApexFileRepositoryTest, InitializeSuccess) { // Prepare test data. TemporaryDir built_in_dir, data_dir, decompression_dir; fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path); fs::copy(GetTestFile("apex.apexd_test_different_app.apex"), built_in_dir.path); fs::copy(GetTestFile("apex.apexd_test.apex"), data_dir.path); fs::copy(GetTestFile("apex.apexd_test_different_app.apex"), data_dir.path); ApexFileRepository instance; ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path}))); ASSERT_TRUE(IsOk(instance.AddDataApex(data_dir.path))); // Now test that apexes were scanned correctly; auto test_fn = [&](const std::string& apex_name) { auto apex = ApexFile::Open(GetTestFile(apex_name)); ASSERT_TRUE(IsOk(apex)); { auto ret = instance.GetPublicKey(apex->GetManifest().name()); ASSERT_TRUE(IsOk(ret)); ASSERT_EQ(apex->GetBundledPublicKey(), *ret); } { auto ret = instance.GetPreinstalledPath(apex->GetManifest().name()); ASSERT_TRUE(IsOk(ret)); ASSERT_EQ(StringPrintf("%s/%s", built_in_dir.path, apex_name.c_str()), *ret); } { auto ret = instance.GetDataPath(apex->GetManifest().name()); ASSERT_TRUE(IsOk(ret)); ASSERT_EQ(StringPrintf("%s/%s", data_dir.path, apex_name.c_str()), *ret); } ASSERT_TRUE(instance.HasPreInstalledVersion(apex->GetManifest().name())); ASSERT_TRUE(instance.HasDataVersion(apex->GetManifest().name())); }; test_fn("apex.apexd_test.apex"); test_fn("apex.apexd_test_different_app.apex"); // Check that second call will succeed as well. ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path}))); ASSERT_TRUE(IsOk(instance.AddDataApex(data_dir.path))); test_fn("apex.apexd_test.apex"); test_fn("apex.apexd_test_different_app.apex"); } TEST(ApexFileRepositoryTest, InitializeFailureCorruptApex) { // Prepare test data. TemporaryDir td; fs::copy(GetTestFile("apex.apexd_test.apex"), td.path); fs::copy(GetTestFile("apex.apexd_test_corrupt_superblock_apex.apex"), td.path); ApexFileRepository instance; ASSERT_FALSE(IsOk(instance.AddPreInstalledApex({td.path}))); } TEST(ApexFileRepositoryTest, InitializeCompressedApexWithoutApex) { // Prepare test data. TemporaryDir td; fs::copy(GetTestFile("com.android.apex.compressed.v1_without_apex.capex"), td.path); ApexFileRepository instance; // Compressed APEX without APEX cannot be opened ASSERT_FALSE(IsOk(instance.AddPreInstalledApex({td.path}))); } TEST(ApexFileRepositoryTest, InitializeSameNameDifferentPathAborts) { // Prepare test data. TemporaryDir td; fs::copy(GetTestFile("apex.apexd_test.apex"), td.path); fs::copy(GetTestFile("apex.apexd_test.apex"), StringPrintf("%s/other.apex", td.path)); ASSERT_DEATH( { ApexFileRepository instance; instance.AddPreInstalledApex({td.path}); }, ""); } TEST(ApexFileRepositoryTest, InitializeSameNameDifferentPathAbortsCompressedApex) { // Prepare test data. TemporaryDir td; fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"), td.path); fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"), StringPrintf("%s/other.capex", td.path)); ASSERT_DEATH( { ApexFileRepository instance; instance.AddPreInstalledApex({td.path}); }, ""); } TEST(ApexFileRepositoryTest, InitializePublicKeyUnexpectdlyChangedAborts) { // Prepare test data. TemporaryDir td; fs::copy(GetTestFile("apex.apexd_test.apex"), td.path); ApexFileRepository instance; ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({td.path}))); // Check that apex was loaded. auto path = instance.GetPreinstalledPath("com.android.apex.test_package"); ASSERT_TRUE(IsOk(path)); ASSERT_EQ(StringPrintf("%s/apex.apexd_test.apex", td.path), *path); auto public_key = instance.GetPublicKey("com.android.apex.test_package"); ASSERT_TRUE(IsOk(public_key)); // Substitute it with another apex with the same name, but different public // key. fs::copy(GetTestFile("apex.apexd_test_different_key.apex"), *path, fs::copy_options::overwrite_existing); { auto apex = ApexFile::Open(*path); ASSERT_TRUE(IsOk(apex)); // Check module name hasn't changed. ASSERT_EQ("com.android.apex.test_package", apex->GetManifest().name()); // Check public key has changed. ASSERT_NE(*public_key, apex->GetBundledPublicKey()); } ASSERT_DEATH({ instance.AddPreInstalledApex({td.path}); }, ""); } TEST(ApexFileRepositoryTest, InitializePublicKeyUnexpectdlyChangedAbortsCompressedApex) { // Prepare test data. TemporaryDir td; fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"), td.path); ApexFileRepository instance; ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({td.path}))); // Check that apex was loaded. auto path = instance.GetPreinstalledPath("com.android.apex.compressed"); ASSERT_TRUE(IsOk(path)); ASSERT_EQ(StringPrintf("%s/com.android.apex.compressed.v1.capex", td.path), *path); auto public_key = instance.GetPublicKey("com.android.apex.compressed"); ASSERT_TRUE(IsOk(public_key)); // Substitute it with another apex with the same name, but different public // key. fs::copy(GetTestFile("com.android.apex.compressed_different_key.capex"), *path, fs::copy_options::overwrite_existing); { auto apex = ApexFile::Open(*path); ASSERT_TRUE(IsOk(apex)); // Check module name hasn't changed. ASSERT_EQ("com.android.apex.compressed", apex->GetManifest().name()); // Check public key has changed. ASSERT_NE(*public_key, apex->GetBundledPublicKey()); } ASSERT_DEATH({ instance.AddPreInstalledApex({td.path}); }, ""); } TEST(ApexFileRepositoryTest, IsPreInstalledApex) { // Prepare test data. TemporaryDir td; fs::copy(GetTestFile("apex.apexd_test.apex"), td.path); fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"), td.path); ApexFileRepository instance; ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({td.path}))); auto compressed_apex = ApexFile::Open( StringPrintf("%s/com.android.apex.compressed.v1.capex", td.path)); ASSERT_TRUE(IsOk(compressed_apex)); ASSERT_TRUE(instance.IsPreInstalledApex(*compressed_apex)); auto apex1 = ApexFile::Open(StringPrintf("%s/apex.apexd_test.apex", td.path)); ASSERT_TRUE(IsOk(apex1)); ASSERT_TRUE(instance.IsPreInstalledApex(*apex1)); // It's same apex, but path is different. Shouldn't be treated as // pre-installed. auto apex2 = ApexFile::Open(GetTestFile("apex.apexd_test.apex")); ASSERT_TRUE(IsOk(apex2)); ASSERT_FALSE(instance.IsPreInstalledApex(*apex2)); auto apex3 = ApexFile::Open(GetTestFile("apex.apexd_test_different_app.apex")); ASSERT_TRUE(IsOk(apex3)); ASSERT_FALSE(instance.IsPreInstalledApex(*apex3)); } TEST(ApexFileRepositoryTest, IsDecompressedApex) { // Prepare instance TemporaryDir decompression_dir; ApexFileRepository instance(decompression_dir.path); // Prepare decompressed apex std::string filename = "com.android.apex.compressed.v1_original.apex"; fs::copy(GetTestFile(filename), decompression_dir.path); auto decompressed_path = StringPrintf("%s/%s", decompression_dir.path, filename.c_str()); auto decompressed_apex = ApexFile::Open(decompressed_path); // Any file which is already located in |decompression_dir| should be // considered decompressed ASSERT_TRUE(instance.IsDecompressedApex(*decompressed_apex)); // Hard links with same file name is not considered decompressed TemporaryDir active_dir; auto active_path = StringPrintf("%s/%s", active_dir.path, filename.c_str()); std::error_code ec; fs::create_hard_link(decompressed_path, active_path, ec); ASSERT_FALSE(ec) << "Failed to create hardlink"; auto active_apex = ApexFile::Open(active_path); ASSERT_FALSE(instance.IsDecompressedApex(*active_apex)); } TEST(ApexFileRepositoryTest, AddAndGetDataApex) { // Prepare test data. TemporaryDir built_in_dir, data_dir, decompression_dir; fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path); fs::copy(GetTestFile("apex.apexd_test_v2.apex"), data_dir.path); PrepareCompressedApex("com.android.apex.compressed.v1.capex", built_in_dir.path, decompression_dir.path); // Add a data apex that has kDecompressedApexPackageSuffix fs::copy(GetTestFile("com.android.apex.compressed.v1_original.apex"), StringPrintf("%s/com.android.apex.compressed@1%s", data_dir.path, kDecompressedApexPackageSuffix)); ApexFileRepository instance(decompression_dir.path); ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path}))); ASSERT_TRUE(IsOk(instance.AddDataApex(data_dir.path))); // ApexFileRepository should only deal with APEX in /data/apex/active. // Decompressed APEX should not be included auto data_apexs = instance.GetDataApexFiles(); auto normal_apex = ApexFile::Open(StringPrintf("%s/apex.apexd_test_v2.apex", data_dir.path)); ASSERT_THAT(data_apexs, UnorderedElementsAre(ApexFileEq(ByRef(*normal_apex)))); } TEST(ApexFileRepositoryTest, AddDataApexIgnoreCompressedApex) { // Prepare test data. TemporaryDir data_dir, decompression_dir; fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"), data_dir.path); ApexFileRepository instance; ASSERT_TRUE(IsOk(instance.AddDataApex(data_dir.path))); auto data_apexs = instance.GetDataApexFiles(); ASSERT_EQ(data_apexs.size(), 0u); } TEST(ApexFileRepositoryTest, AddDataApexIgnoreIfNotPreInstalled) { // Prepare test data. TemporaryDir data_dir, decompression_dir; fs::copy(GetTestFile("apex.apexd_test.apex"), data_dir.path); ApexFileRepository instance; ASSERT_TRUE(IsOk(instance.AddDataApex(data_dir.path))); auto data_apexs = instance.GetDataApexFiles(); ASSERT_EQ(data_apexs.size(), 0u); } TEST(ApexFileRepositoryTest, AddDataApexPrioritizeHigherVersionApex) { // Prepare test data. TemporaryDir built_in_dir, data_dir, decompression_dir; fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path); fs::copy(GetTestFile("apex.apexd_test.apex"), data_dir.path); fs::copy(GetTestFile("apex.apexd_test_v2.apex"), data_dir.path); ApexFileRepository instance; ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path}))); ASSERT_TRUE(IsOk(instance.AddDataApex(data_dir.path))); auto data_apexs = instance.GetDataApexFiles(); auto normal_apex = ApexFile::Open(StringPrintf("%s/apex.apexd_test_v2.apex", data_dir.path)); ASSERT_THAT(data_apexs, UnorderedElementsAre(ApexFileEq(ByRef(*normal_apex)))); } TEST(ApexFileRepositoryTest, AddDataApexDoesNotScanDecompressedApex) { // Prepare test data. TemporaryDir built_in_dir, data_dir, decompression_dir; PrepareCompressedApex("com.android.apex.compressed.v1.capex", built_in_dir.path, decompression_dir.path); ApexFileRepository instance(decompression_dir.path); ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path}))); ASSERT_TRUE(IsOk(instance.AddDataApex(data_dir.path))); auto data_apexs = instance.GetDataApexFiles(); ASSERT_EQ(data_apexs.size(), 0u); } TEST(ApexFileRepositoryTest, AddDataApexIgnoreWrongPublicKey) { // Prepare test data. TemporaryDir built_in_dir, data_dir, decompression_dir; fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path); fs::copy(GetTestFile("apex.apexd_test_different_key.apex"), data_dir.path); ApexFileRepository instance; ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path}))); ASSERT_TRUE(IsOk(instance.AddDataApex(data_dir.path))); auto data_apexs = instance.GetDataApexFiles(); ASSERT_EQ(data_apexs.size(), 0u); } TEST(ApexFileRepositoryTest, GetPreInstalledApexFiles) { // Prepare test data. TemporaryDir built_in_dir; fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path); fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"), built_in_dir.path); ApexFileRepository instance; ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path}))); auto pre_installed_apexs = instance.GetPreInstalledApexFiles(); auto pre_apex_1 = ApexFile::Open( StringPrintf("%s/apex.apexd_test.apex", built_in_dir.path)); auto pre_apex_2 = ApexFile::Open(StringPrintf( "%s/com.android.apex.compressed.v1.capex", built_in_dir.path)); ASSERT_THAT(pre_installed_apexs, UnorderedElementsAre(ApexFileEq(ByRef(*pre_apex_1)), ApexFileEq(ByRef(*pre_apex_2)))); } TEST(ApexFileRepositoryTest, AllApexFilesByName) { TemporaryDir built_in_dir, decompression_dir; fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path); fs::copy(GetTestFile("com.android.apex.cts.shim.apex"), built_in_dir.path); fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"), built_in_dir.path); ApexFileRepository instance; ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path}))); TemporaryDir data_dir; fs::copy(GetTestFile("com.android.apex.cts.shim.v2.apex"), data_dir.path); ASSERT_TRUE(IsOk(instance.AddDataApex(data_dir.path))); auto result = instance.AllApexFilesByName(); // Verify the contents of result auto apexd_test_file = ApexFile::Open( StringPrintf("%s/apex.apexd_test.apex", built_in_dir.path)); auto shim_v1 = ApexFile::Open( StringPrintf("%s/com.android.apex.cts.shim.apex", built_in_dir.path)); auto compressed_apex = ApexFile::Open(StringPrintf( "%s/com.android.apex.compressed.v1.capex", built_in_dir.path)); auto shim_v2 = ApexFile::Open( StringPrintf("%s/com.android.apex.cts.shim.v2.apex", data_dir.path)); ASSERT_EQ(result.size(), 3u); ASSERT_THAT(result[apexd_test_file->GetManifest().name()], UnorderedElementsAre(ApexFileEq(ByRef(*apexd_test_file)))); ASSERT_THAT(result[shim_v1->GetManifest().name()], UnorderedElementsAre(ApexFileEq(ByRef(*shim_v1)), ApexFileEq(ByRef(*shim_v2)))); ASSERT_THAT(result[compressed_apex->GetManifest().name()], UnorderedElementsAre(ApexFileEq(ByRef(*compressed_apex)))); } TEST(ApexFileRepositoryTest, GetDataApex) { // Prepare test data. TemporaryDir built_in_dir, data_dir; fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path); fs::copy(GetTestFile("apex.apexd_test_v2.apex"), data_dir.path); ApexFileRepository instance; ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path}))); ASSERT_TRUE(IsOk(instance.AddDataApex(data_dir.path))); auto apex = ApexFile::Open(StringPrintf("%s/apex.apexd_test_v2.apex", data_dir.path)); ASSERT_RESULT_OK(apex); auto ret = instance.GetDataApex("com.android.apex.test_package"); ASSERT_THAT(ret, ApexFileEq(ByRef(*apex))); } TEST(ApexFileRepositoryTest, GetDataApexNoSuchApexAborts) { ASSERT_DEATH( { ApexFileRepository instance; instance.GetDataApex("whatever"); }, ""); } TEST(ApexFileRepositoryTest, GetPreInstalledApex) { // Prepare test data. TemporaryDir built_in_dir; fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path); ApexFileRepository instance; ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path}))); auto apex = ApexFile::Open( StringPrintf("%s/apex.apexd_test.apex", built_in_dir.path)); ASSERT_RESULT_OK(apex); auto ret = instance.GetPreInstalledApex("com.android.apex.test_package"); ASSERT_THAT(ret, ApexFileEq(ByRef(*apex))); } TEST(ApexFileRepositoryTest, GetPreInstalledApexNoSuchApexAborts) { ASSERT_DEATH( { ApexFileRepository instance; instance.GetPreInstalledApex("whatever"); }, ""); } } // namespace apex } // namespace android