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