1 //
2 // Copyright (C) 2017 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 "update_engine/payload_generator/squashfs_filesystem.h"
18 
19 #include <unistd.h>
20 
21 #include <algorithm>
22 #include <map>
23 #include <set>
24 #include <string>
25 #include <vector>
26 
27 #include <base/format_macros.h>
28 #include <base/logging.h>
29 #include <base/strings/string_number_conversions.h>
30 #include <base/strings/string_util.h>
31 #include <base/strings/stringprintf.h>
32 #include <gtest/gtest.h>
33 
34 #include "update_engine/common/test_utils.h"
35 #include "update_engine/common/utils.h"
36 #include "update_engine/payload_generator/extent_utils.h"
37 
38 namespace chromeos_update_engine {
39 
40 using std::map;
41 using std::set;
42 using std::string;
43 using std::unique_ptr;
44 using std::vector;
45 
46 using test_utils::GetBuildArtifactsPath;
47 
48 namespace {
49 
50 constexpr uint64_t kTestBlockSize = 4096;
51 constexpr uint64_t kTestSqfsBlockSize = 1 << 15;
52 
53 // Checks that all the blocks in |extents| are in the range [0, total_blocks).
ExpectBlocksInRange(const vector<Extent> & extents,uint64_t total_blocks)54 void ExpectBlocksInRange(const vector<Extent>& extents, uint64_t total_blocks) {
55   for (const Extent& extent : extents) {
56     EXPECT_LE(0U, extent.start_block());
57     EXPECT_LE(extent.start_block() + extent.num_blocks(), total_blocks);
58   }
59 }
60 
GetSimpleHeader()61 SquashfsFilesystem::SquashfsHeader GetSimpleHeader() {
62   // These properties are enough for now. Add more as needed.
63   return {
64       .magic = 0x73717368,
65       .block_size = kTestSqfsBlockSize,
66       .compression_type = 1,  // For gzip.
67       .major_version = 4,
68   };
69 }
70 
71 }  // namespace
72 
73 class SquashfsFilesystemTest : public ::testing::Test {
74  public:
CheckSquashfs(const unique_ptr<SquashfsFilesystem> & fs)75   void CheckSquashfs(const unique_ptr<SquashfsFilesystem>& fs) {
76     ASSERT_TRUE(fs);
77     EXPECT_EQ(kTestBlockSize, fs->GetBlockSize());
78 
79     vector<FilesystemInterface::File> files;
80     ASSERT_TRUE(fs->GetFiles(&files));
81 
82     map<string, FilesystemInterface::File> map_files;
83     for (const auto& file : files) {
84       EXPECT_EQ(map_files.end(), map_files.find(file.name))
85           << "File " << file.name << " repeated in the list.";
86       map_files[file.name] = file;
87       ExpectBlocksInRange(file.extents, fs->GetBlockCount());
88     }
89 
90     // Checking the sortness.
91     EXPECT_TRUE(std::is_sorted(files.begin(),
92                                files.end(),
93                                [](const FilesystemInterface::File& a,
94                                   const FilesystemInterface::File& b) {
95                                  return a.extents[0].start_block() <
96                                         b.extents[0].start_block();
97                                }));
98 
99     auto overlap_check = [](const FilesystemInterface::File& a,
100                             const FilesystemInterface::File& b) {
101       // Return true if overlapping.
102       return a.extents[0].start_block() + a.extents[0].num_blocks() >
103              b.extents[0].start_block();
104     };
105     // Check files are not overlapping.
106     EXPECT_EQ(std::adjacent_find(files.begin(), files.end(), overlap_check),
107               files.end());
108   }
109 };
110 
111 // CreateFromFile() depends on unsquashfs -m, which only exists in Chrome OS.
112 #ifdef __CHROMEOS__
TEST_F(SquashfsFilesystemTest,EmptyFilesystemTest)113 TEST_F(SquashfsFilesystemTest, EmptyFilesystemTest) {
114   unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFile(
115       GetBuildArtifactsPath("gen/disk_sqfs_empty.img"), true, false);
116   CheckSquashfs(fs);
117 
118   // Even an empty squashfs filesystem is rounded up to 4K.
119   EXPECT_EQ(4096 / kTestBlockSize, fs->GetBlockCount());
120 
121   vector<FilesystemInterface::File> files;
122   ASSERT_TRUE(fs->GetFiles(&files));
123   ASSERT_EQ(files.size(), 1u);
124 
125   FilesystemInterface::File file;
126   file.name = "<metadata-0>";
127   file.extents.emplace_back();
128   file.extents[0].set_start_block(0);
129   file.extents[0].set_num_blocks(1);
130   EXPECT_EQ(files[0].name, file.name);
131   EXPECT_EQ(files[0].extents, file.extents);
132 }
133 
TEST_F(SquashfsFilesystemTest,DefaultFilesystemTest)134 TEST_F(SquashfsFilesystemTest, DefaultFilesystemTest) {
135   unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFile(
136       GetBuildArtifactsPath("gen/disk_sqfs_default.img"), true, false);
137   CheckSquashfs(fs);
138 
139   vector<FilesystemInterface::File> files;
140   ASSERT_TRUE(fs->GetFiles(&files));
141   ASSERT_EQ(files.size(), 1u);
142 
143   FilesystemInterface::File file;
144   file.name = "<fragment-0>";
145   file.extents.emplace_back();
146   file.extents[0].set_start_block(0);
147   file.extents[0].set_num_blocks(1);
148   EXPECT_EQ(files[0].name, file.name);
149   EXPECT_EQ(files[0].extents, file.extents);
150 }
151 
TEST_F(SquashfsFilesystemTest,UpdateEngineConfigTest)152 TEST_F(SquashfsFilesystemTest, UpdateEngineConfigTest) {
153   unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFile(
154       GetBuildArtifactsPath("gen/disk_sqfs_unittest.img"), true, true);
155   CheckSquashfs(fs);
156 
157   brillo::KeyValueStore kvs;
158   EXPECT_TRUE(fs->LoadSettings(&kvs));
159   string minor_version;
160   EXPECT_TRUE(kvs.GetString("PAYLOAD_MINOR_VERSION", &minor_version));
161   EXPECT_EQ(minor_version, "1234");
162 }
163 #endif  // __CHROMEOS__
164 
TEST_F(SquashfsFilesystemTest,SimpleFileMapTest)165 TEST_F(SquashfsFilesystemTest, SimpleFileMapTest) {
166   string filemap = R"(dir1/file1 96 4000
167                       dir1/file2 4096 100)";
168   unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
169       filemap, kTestBlockSize * 2, GetSimpleHeader());
170   CheckSquashfs(fs);
171 
172   vector<FilesystemInterface::File> files;
173   ASSERT_TRUE(fs->GetFiles(&files));
174   EXPECT_EQ(files.size(), 2u);
175 }
176 
TEST_F(SquashfsFilesystemTest,FileMapZeroSizeFileTest)177 TEST_F(SquashfsFilesystemTest, FileMapZeroSizeFileTest) {
178   // The second file's size is zero.
179   string filemap = R"(dir1/file1 96 4000
180                       dir1/file2 4096
181                       dir1/file3 4096 100)";
182   unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
183       filemap, kTestBlockSize * 2, GetSimpleHeader());
184   CheckSquashfs(fs);
185 
186   vector<FilesystemInterface::File> files;
187   ASSERT_TRUE(fs->GetFiles(&files));
188   // The second and third files are removed. The file with size zero is removed.
189   EXPECT_EQ(files.size(), 2u);
190 }
191 
192 // Testing the compressed bit.
TEST_F(SquashfsFilesystemTest,CompressedBitTest)193 TEST_F(SquashfsFilesystemTest, CompressedBitTest) {
194   string filemap = "dir1/file1 0 " + std::to_string(4000 | (1 << 24)) + "\n";
195   unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
196       filemap, kTestBlockSize, GetSimpleHeader());
197   CheckSquashfs(fs);
198 
199   vector<FilesystemInterface::File> files;
200   ASSERT_TRUE(fs->GetFiles(&files));
201   ASSERT_EQ(files.size(), 1u);
202   EXPECT_EQ(files[0].extents[0].num_blocks(), 1u);
203 }
204 
205 // Test overlap.
TEST_F(SquashfsFilesystemTest,OverlapingFiles1Test)206 TEST_F(SquashfsFilesystemTest, OverlapingFiles1Test) {
207   string filemap = R"(file1 0 6000
208                       file2 5000 5000)";
209   unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
210       filemap, kTestBlockSize * 3, GetSimpleHeader());
211   CheckSquashfs(fs);
212 
213   vector<FilesystemInterface::File> files;
214   ASSERT_TRUE(fs->GetFiles(&files));
215   ASSERT_EQ(files.size(), 2u);
216   EXPECT_EQ(files[0].extents[0].num_blocks(), 1u);
217   EXPECT_EQ(files[1].extents[0].num_blocks(), 2u);
218 }
219 
220 // Test overlap, first inside second.
TEST_F(SquashfsFilesystemTest,OverlapingFiles2Test)221 TEST_F(SquashfsFilesystemTest, OverlapingFiles2Test) {
222   string filemap = R"(file1 0 4000
223                       file2 0 6000)";
224   unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
225       filemap, kTestBlockSize * 2, GetSimpleHeader());
226   CheckSquashfs(fs);
227 
228   vector<FilesystemInterface::File> files;
229   ASSERT_TRUE(fs->GetFiles(&files));
230   ASSERT_EQ(files.size(), 1u);
231   EXPECT_EQ(files[0].name, "file2");
232   EXPECT_EQ(files[0].extents[0].num_blocks(), 2u);
233 }
234 
235 // Test overlap, second inside first.
TEST_F(SquashfsFilesystemTest,OverlapingFiles3Test)236 TEST_F(SquashfsFilesystemTest, OverlapingFiles3Test) {
237   string filemap = R"(file1 0 8000
238                       file2 100 100)";
239   unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
240       filemap, kTestBlockSize * 2, GetSimpleHeader());
241   CheckSquashfs(fs);
242 
243   vector<FilesystemInterface::File> files;
244   ASSERT_TRUE(fs->GetFiles(&files));
245   ASSERT_EQ(files.size(), 1u);
246   EXPECT_EQ(files[0].name, "file1");
247   EXPECT_EQ(files[0].extents[0].num_blocks(), 2u);
248 }
249 
250 // Fail a line with only one argument.
TEST_F(SquashfsFilesystemTest,FailOnlyFileNameTest)251 TEST_F(SquashfsFilesystemTest, FailOnlyFileNameTest) {
252   string filemap = "dir1/file1\n";
253   unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
254       filemap, kTestBlockSize, GetSimpleHeader());
255   EXPECT_FALSE(fs);
256 }
257 
258 // Fail a line with space separated filen name
TEST_F(SquashfsFilesystemTest,FailSpaceInFileNameTest)259 TEST_F(SquashfsFilesystemTest, FailSpaceInFileNameTest) {
260   string filemap = "dir1 file1 0 10\n";
261   unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
262       filemap, kTestBlockSize, GetSimpleHeader());
263   EXPECT_FALSE(fs);
264 }
265 
266 // Fail empty line
TEST_F(SquashfsFilesystemTest,FailEmptyLineTest)267 TEST_F(SquashfsFilesystemTest, FailEmptyLineTest) {
268   // The second file's size is zero.
269   string filemap = R"(
270   /t
271                       dir1/file3 4096 100)";
272   unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
273       filemap, kTestBlockSize * 2, GetSimpleHeader());
274   EXPECT_FALSE(fs);
275 }
276 
277 // Fail on bad magic or major
TEST_F(SquashfsFilesystemTest,FailBadMagicOrMajorTest)278 TEST_F(SquashfsFilesystemTest, FailBadMagicOrMajorTest) {
279   string filemap = "dir1/file1 0 10\n";
280   auto header = GetSimpleHeader();
281   header.magic = 1;
282   EXPECT_FALSE(
283       SquashfsFilesystem::CreateFromFileMap(filemap, kTestBlockSize, header));
284 
285   header = GetSimpleHeader();
286   header.major_version = 3;
287   EXPECT_FALSE(
288       SquashfsFilesystem::CreateFromFileMap(filemap, kTestBlockSize, header));
289 }
290 
291 // Fail size with larger than block_size
TEST_F(SquashfsFilesystemTest,FailLargerThanBlockSizeTest)292 TEST_F(SquashfsFilesystemTest, FailLargerThanBlockSizeTest) {
293   string filemap = "file1 0 " + std::to_string(kTestSqfsBlockSize + 1) + "\n";
294   unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
295       filemap, kTestBlockSize, GetSimpleHeader());
296   EXPECT_FALSE(fs);
297 }
298 
299 // Test is squashfs image.
TEST_F(SquashfsFilesystemTest,IsSquashfsImageTest)300 TEST_F(SquashfsFilesystemTest, IsSquashfsImageTest) {
301   // Some sample from a recent squashfs file.
302   brillo::Blob super_block = {
303       0x68, 0x73, 0x71, 0x73, 0x59, 0x05, 0x00, 0x00, 0x09, 0x3a, 0x89, 0x58,
304       0x00, 0x00, 0x02, 0x00, 0x9a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x11, 0x00,
305       0xc0, 0x00, 0x06, 0x00, 0x04, 0x00, 0x00, 0x00, 0x89, 0x18, 0xf7, 0x7c,
306       0x00, 0x00, 0x00, 0x00, 0x2e, 0x33, 0xcd, 0x16, 0x00, 0x00, 0x00, 0x00,
307       0x3a, 0x30, 0xcd, 0x16, 0x00, 0x00, 0x00, 0x00, 0x16, 0x33, 0xcd, 0x16,
308       0x00, 0x00, 0x00, 0x00, 0x07, 0x62, 0xcc, 0x16, 0x00, 0x00, 0x00, 0x00,
309       0x77, 0xe6, 0xcc, 0x16, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x25, 0xcd, 0x16,
310       0x00, 0x00, 0x00, 0x00, 0x10, 0x30, 0xcd, 0x16, 0x00, 0x00, 0x00, 0x00};
311 
312   EXPECT_TRUE(SquashfsFilesystem::IsSquashfsImage(super_block));
313 
314   // Bad magic
315   auto bad_super_block = super_block;
316   bad_super_block[1] = 0x02;
317   EXPECT_FALSE(SquashfsFilesystem::IsSquashfsImage(bad_super_block));
318 
319   // Bad major
320   bad_super_block = super_block;
321   bad_super_block[28] = 0x03;
322   EXPECT_FALSE(SquashfsFilesystem::IsSquashfsImage(bad_super_block));
323 
324   // Small size;
325   bad_super_block = super_block;
326   bad_super_block.resize(10);
327   EXPECT_FALSE(SquashfsFilesystem::IsSquashfsImage(bad_super_block));
328 }
329 
330 }  // namespace chromeos_update_engine
331