• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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