1 /* 2 * Copyright (C) 2018 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 <string> 18 19 #include <android-base/file.h> 20 #include <android-base/logging.h> 21 #include <android-base/scopeguard.h> 22 #include <android-base/strings.h> 23 #include <gmock/gmock.h> 24 #include <gtest/gtest.h> 25 #include <libavb/libavb.h> 26 #include <ziparchive/zip_archive.h> 27 28 #include "apex_file.h" 29 #include "apexd_test_utils.h" 30 #include "apexd_utils.h" 31 32 using android::base::GetExecutableDirectory; 33 using android::base::Result; 34 35 static const std::string kTestDataDir = GetExecutableDirectory() + "/"; 36 37 namespace android { 38 namespace apex { 39 namespace { 40 41 struct ApexFileTestParam { 42 const char* type; 43 const char* prefix; 44 }; 45 46 constexpr const ApexFileTestParam kParameters[] = { 47 {"ext4", "apex.apexd_test"}, {"f2fs", "apex.apexd_test_f2fs"}}; 48 49 class ApexFileTest : public ::testing::TestWithParam<ApexFileTestParam> {}; 50 51 INSTANTIATE_TEST_SUITE_P(Apex, ApexFileTest, ::testing::ValuesIn(kParameters)); 52 53 TEST_P(ApexFileTest, GetOffsetOfSimplePackage) { 54 const std::string file_path = kTestDataDir + GetParam().prefix + ".apex"; 55 Result<ApexFile> apex_file = ApexFile::Open(file_path); 56 ASSERT_TRUE(apex_file.ok()); 57 58 int32_t zip_image_offset; 59 size_t zip_image_size; 60 { 61 ZipArchiveHandle handle; 62 int32_t rc = OpenArchive(file_path.c_str(), &handle); 63 ASSERT_EQ(0, rc); 64 auto close_guard = 65 android::base::make_scope_guard([&handle]() { CloseArchive(handle); }); 66 67 ZipEntry entry; 68 rc = FindEntry(handle, "apex_payload.img", &entry); 69 ASSERT_EQ(0, rc); 70 71 zip_image_offset = entry.offset; 72 EXPECT_EQ(zip_image_offset % 4096, 0); 73 zip_image_size = entry.uncompressed_length; 74 EXPECT_EQ(zip_image_size, entry.compressed_length); 75 } 76 77 EXPECT_EQ(zip_image_offset, apex_file->GetImageOffset().value()); 78 EXPECT_EQ(zip_image_size, apex_file->GetImageSize().value()); 79 } 80 81 TEST(ApexFileTest, GetOffsetMissingFile) { 82 const std::string file_path = kTestDataDir + "missing.apex"; 83 Result<ApexFile> apex_file = ApexFile::Open(file_path); 84 ASSERT_FALSE(apex_file.ok()); 85 ASSERT_THAT(apex_file.error().message(), 86 ::testing::HasSubstr("Failed to open package")); 87 } 88 89 TEST_P(ApexFileTest, GetApexManifest) { 90 const std::string file_path = kTestDataDir + GetParam().prefix + ".apex"; 91 Result<ApexFile> apex_file = ApexFile::Open(file_path); 92 ASSERT_RESULT_OK(apex_file); 93 EXPECT_EQ("com.android.apex.test_package", apex_file->GetManifest().name()); 94 EXPECT_EQ(1u, apex_file->GetManifest().version()); 95 } 96 97 TEST_P(ApexFileTest, VerifyApexVerity) { 98 const std::string file_path = kTestDataDir + GetParam().prefix + ".apex"; 99 Result<ApexFile> apex_file = ApexFile::Open(file_path); 100 ASSERT_RESULT_OK(apex_file); 101 102 auto verity_or = 103 apex_file->VerifyApexVerity(apex_file->GetBundledPublicKey()); 104 ASSERT_RESULT_OK(verity_or); 105 106 const ApexVerityData& data = *verity_or; 107 EXPECT_NE(nullptr, data.desc.get()); 108 EXPECT_EQ(std::string("368a22e64858647bc45498e92f749f85482ac468" 109 "50ca7ec8071f49dfa47a243c"), 110 data.salt); 111 112 const std::string digest_path = 113 kTestDataDir + GetParam().prefix + "_digest.txt"; 114 std::string root_digest; 115 ASSERT_TRUE(android::base::ReadFileToString(digest_path, &root_digest)) 116 << "Failed to read " << digest_path; 117 root_digest = android::base::Trim(root_digest); 118 119 EXPECT_EQ(std::string(root_digest), data.root_digest); 120 } 121 122 TEST_P(ApexFileTest, VerifyApexVerityWrongKey) { 123 const std::string file_path = kTestDataDir + GetParam().prefix + ".apex"; 124 Result<ApexFile> apex_file = ApexFile::Open(file_path); 125 ASSERT_RESULT_OK(apex_file); 126 127 auto verity_or = apex_file->VerifyApexVerity("wrong-key"); 128 ASSERT_FALSE(verity_or.ok()); 129 } 130 131 TEST_P(ApexFileTest, GetBundledPublicKey) { 132 const std::string file_path = kTestDataDir + GetParam().prefix + ".apex"; 133 Result<ApexFile> apex_file = ApexFile::Open(file_path); 134 ASSERT_RESULT_OK(apex_file); 135 136 const std::string key_path = 137 kTestDataDir + "apexd_testdata/com.android.apex.test_package.avbpubkey"; 138 std::string key_content; 139 ASSERT_TRUE(android::base::ReadFileToString(key_path, &key_content)) 140 << "Failed to read " << key_path; 141 142 EXPECT_EQ(key_content, apex_file->GetBundledPublicKey()); 143 } 144 145 TEST(ApexFileTest, CorrutedApexB146895998) { 146 const std::string apex_path = kTestDataDir + "corrupted_b146895998.apex"; 147 Result<ApexFile> apex = ApexFile::Open(apex_path); 148 ASSERT_RESULT_OK(apex); 149 ASSERT_FALSE(apex->VerifyApexVerity("ignored").ok()); 150 } 151 152 TEST_P(ApexFileTest, RetrieveFsType) { 153 const std::string file_path = kTestDataDir + GetParam().prefix + ".apex"; 154 Result<ApexFile> apex_file = ApexFile::Open(file_path); 155 ASSERT_TRUE(apex_file.ok()); 156 157 EXPECT_EQ(std::string(GetParam().type), apex_file->GetFsType().value()); 158 } 159 160 TEST(ApexFileTest, OpenInvalidFilesystem) { 161 const std::string file_path = 162 kTestDataDir + "apex.apexd_test_corrupt_superblock_apex.apex"; 163 Result<ApexFile> apex_file = ApexFile::Open(file_path); 164 ASSERT_FALSE(apex_file.ok()); 165 ASSERT_THAT(apex_file.error().message(), 166 ::testing::HasSubstr("Failed to retrieve filesystem type")); 167 } 168 169 TEST(ApexFileTest, OpenCompressedApexFile) { 170 const std::string file_path = 171 kTestDataDir + "com.android.apex.compressed.v1.capex"; 172 Result<ApexFile> apex_file = ApexFile::Open(file_path); 173 ASSERT_TRUE(apex_file.ok()); 174 175 ASSERT_TRUE(apex_file->IsCompressed()); 176 ASSERT_FALSE(apex_file->GetImageOffset().has_value()); 177 ASSERT_FALSE(apex_file->GetImageSize().has_value()); 178 ASSERT_FALSE(apex_file->GetFsType().has_value()); 179 } 180 181 TEST(ApexFileTest, OpenFailureForCompressedApexWithoutApex) { 182 const std::string file_path = 183 kTestDataDir + "com.android.apex.compressed.v1_without_apex.capex"; 184 Result<ApexFile> apex_file = ApexFile::Open(file_path); 185 ASSERT_FALSE(apex_file.ok()); 186 ASSERT_THAT(apex_file.error().message(), 187 ::testing::HasSubstr("Could not find entry")); 188 } 189 190 TEST(ApexFileTest, GetCompressedApexManifest) { 191 const std::string file_path = 192 kTestDataDir + "com.android.apex.compressed.v1.capex"; 193 Result<ApexFile> apex_file = ApexFile::Open(file_path); 194 ASSERT_RESULT_OK(apex_file); 195 EXPECT_EQ("com.android.apex.compressed", apex_file->GetManifest().name()); 196 EXPECT_EQ(1u, apex_file->GetManifest().version()); 197 } 198 199 TEST(ApexFileTest, GetBundledPublicKeyOfCompressedApex) { 200 const std::string file_path = 201 kTestDataDir + "com.android.apex.compressed.v1.capex"; 202 Result<ApexFile> apex_file = ApexFile::Open(file_path); 203 ASSERT_RESULT_OK(apex_file); 204 205 const std::string key_path = 206 kTestDataDir + "apexd_testdata/com.android.apex.compressed.avbpubkey"; 207 std::string key_content; 208 ASSERT_TRUE(android::base::ReadFileToString(key_path, &key_content)) 209 << "Failed to read " << key_path; 210 211 EXPECT_EQ(key_content, apex_file->GetBundledPublicKey()); 212 } 213 214 TEST(ApexFileTest, CannotVerifyApexVerityForCompressedApex) { 215 const std::string file_path = 216 kTestDataDir + "com.android.apex.compressed.v1.capex"; 217 auto apex = ApexFile::Open(file_path); 218 ASSERT_RESULT_OK(apex); 219 auto result = apex->VerifyApexVerity(apex->GetBundledPublicKey()); 220 ASSERT_FALSE(result.ok()); 221 ASSERT_THAT( 222 result.error().message(), 223 ::testing::HasSubstr("Cannot verify ApexVerity of compressed APEX")); 224 } 225 226 TEST(ApexFileTest, DecompressCompressedApex) { 227 const std::string file_path = 228 kTestDataDir + "com.android.apex.compressed.v1.capex"; 229 Result<ApexFile> apex_file = ApexFile::Open(file_path); 230 ASSERT_RESULT_OK(apex_file); 231 232 // Create a temp dir for decompression 233 TemporaryDir tmp_dir; 234 235 const std::string package_name = apex_file->GetManifest().name(); 236 const std::string decompression_file_path = 237 tmp_dir.path + package_name + ".capex"; 238 239 auto result = apex_file->Decompress(decompression_file_path); 240 ASSERT_RESULT_OK(result); 241 242 // Assert output path is not empty 243 auto exists = PathExists(decompression_file_path); 244 ASSERT_RESULT_OK(exists); 245 ASSERT_TRUE(*exists) << decompression_file_path << " does not exist"; 246 247 // Assert that decompressed apex is same as original apex 248 const std::string original_apex_file_path = 249 kTestDataDir + "com.android.apex.compressed.v1_original.apex"; 250 auto comparison_result = 251 CompareFiles(original_apex_file_path, decompression_file_path); 252 ASSERT_RESULT_OK(comparison_result); 253 ASSERT_TRUE(*comparison_result); 254 } 255 256 TEST(ApexFileTest, DecompressFailForNormalApex) { 257 const std::string file_path = 258 kTestDataDir + "com.android.apex.compressed.v1_original.apex"; 259 Result<ApexFile> apex_file = ApexFile::Open(file_path); 260 ASSERT_RESULT_OK(apex_file); 261 262 TemporaryFile decompression_file; 263 264 auto result = apex_file->Decompress(decompression_file.path); 265 ASSERT_FALSE(result.ok()); 266 ASSERT_THAT(result.error().message(), 267 ::testing::HasSubstr("Cannot decompress an uncompressed APEX")); 268 } 269 270 TEST(ApexFileTest, DecompressFailIfDecompressionPathExists) { 271 const std::string file_path = 272 kTestDataDir + "com.android.apex.compressed.v1.capex"; 273 Result<ApexFile> apex_file = ApexFile::Open(file_path); 274 275 // Attempt to decompress in a path that already exists 276 TemporaryFile decompression_file; 277 auto exists = PathExists(decompression_file.path); 278 ASSERT_RESULT_OK(exists); 279 ASSERT_TRUE(*exists) << decompression_file.path << " does not exist"; 280 281 auto result = apex_file->Decompress(decompression_file.path); 282 ASSERT_FALSE(result.ok()); 283 ASSERT_THAT(result.error().message(), 284 ::testing::HasSubstr("Failed to open decompression destination")); 285 } 286 287 TEST(ApexFileTest, GetPathReturnsRealpath) { 288 const std::string real_path = kTestDataDir + "apex.apexd_test.apex"; 289 const std::string symlink_path = 290 kTestDataDir + "apex.apexd_test.symlink.apex"; 291 292 // In case the link already exists 293 int ret = unlink(symlink_path.c_str()); 294 ASSERT_TRUE(ret == 0 || errno == ENOENT) 295 << "failed to unlink " << symlink_path; 296 297 ret = symlink(real_path.c_str(), symlink_path.c_str()); 298 ASSERT_EQ(0, ret) << "failed to create symlink at " << symlink_path; 299 300 // Open with the symlink. Realpath is expected. 301 Result<ApexFile> apex_file = ApexFile::Open(symlink_path); 302 ASSERT_RESULT_OK(apex_file); 303 ASSERT_EQ(real_path, apex_file->GetPath()); 304 } 305 306 TEST(ApexFileTest, CompressedSharedLibsApexIsRejected) { 307 const std::string file_path = 308 kTestDataDir + "com.android.apex.compressed_sharedlibs.capex"; 309 Result<ApexFile> apex_file = ApexFile::Open(file_path); 310 311 ASSERT_FALSE(apex_file.ok()); 312 ASSERT_THAT(apex_file.error().message(), 313 ::testing::HasSubstr("Apex providing sharedlibs shouldn't " 314 "be compressed")); 315 } 316 317 // Check if CAPEX contains originalApexDigest in its manifest 318 TEST(ApexFileTest, OriginalApexDigest) { 319 const std::string capex_path = 320 kTestDataDir + "com.android.apex.compressed.v1.capex"; 321 auto capex = ApexFile::Open(capex_path); 322 ASSERT_TRUE(capex.ok()); 323 const std::string decompressed_apex_path = 324 kTestDataDir + "com.android.apex.compressed.v1_original.apex"; 325 auto decompressed_apex = ApexFile::Open(decompressed_apex_path); 326 ASSERT_TRUE(decompressed_apex.ok()); 327 // Validate root digest 328 auto digest = decompressed_apex->VerifyApexVerity( 329 decompressed_apex->GetBundledPublicKey()); 330 ASSERT_TRUE(digest.ok()); 331 ASSERT_EQ(digest->root_digest, 332 capex->GetManifest().capexmetadata().originalapexdigest()); 333 } 334 } // namespace 335 } // namespace apex 336 } // namespace android 337