1 /*
2 * Copyright (C) 2019 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 <stdint.h>
18 #include <stdio.h>
19
20 #include <map>
21 #include <string>
22 #include <vector>
23
24 #include <android-base/file.h>
25 #include <android-base/stringprintf.h>
26 #include <android-base/strings.h>
27 #include <bsdiff/bsdiff.h>
28 #include <gtest/gtest.h>
29 #include <ziparchive/zip_writer.h>
30
31 #include "otautil/paths.h"
32 #include "otautil/print_sha1.h"
33 #include "updater/blockimg.h"
34 #include "updater/build_info.h"
35 #include "updater/install.h"
36 #include "updater/simulator_runtime.h"
37 #include "updater/target_files.h"
38 #include "updater/updater.h"
39
40 using std::string;
41
42 // echo -n "system.img" > system.img && img2simg system.img sparse_system_string_.img 4096 &&
43 // hexdump -v -e '" " 12/1 "0x%02x, " "\n"' sparse_system_string_.img
44 // The total size of the result sparse image is 4136 bytes; and we can append 0s in the end to get
45 // the full image.
46 constexpr uint8_t SPARSE_SYSTEM_HEADER[] = {
47 0x3a, 0xff, 0x26, 0xed, 0x01, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x0c, 0x00, 0x00, 0x10, 0x00,
48 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc1, 0xca,
49 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x10, 0x00, 0x00, 0x73, 0x79, 0x73, 0x74, 0x65,
50 0x6d, 0x2e, 0x69, 0x6d, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
51 };
52
AddZipEntries(int fd,const std::map<string,string> & entries)53 static void AddZipEntries(int fd, const std::map<string, string>& entries) {
54 FILE* zip_file = fdopen(fd, "w");
55 ZipWriter writer(zip_file);
56 for (const auto& pair : entries) {
57 ASSERT_EQ(0, writer.StartEntry(pair.first.c_str(), 0));
58 ASSERT_EQ(0, writer.WriteBytes(pair.second.data(), pair.second.size()));
59 ASSERT_EQ(0, writer.FinishEntry());
60 }
61 ASSERT_EQ(0, writer.Finish());
62 ASSERT_EQ(0, fclose(zip_file));
63 }
64
CalculateSha1(const string & data)65 static string CalculateSha1(const string& data) {
66 uint8_t digest[SHA_DIGEST_LENGTH];
67 SHA1(reinterpret_cast<const uint8_t*>(data.c_str()), data.size(), digest);
68 return print_sha1(digest);
69 }
70
CreateBsdiffPatch(const string & src,const string & tgt,string * patch)71 static void CreateBsdiffPatch(const string& src, const string& tgt, string* patch) {
72 TemporaryFile patch_file;
73 ASSERT_EQ(0, bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(src.data()), src.size(),
74 reinterpret_cast<const uint8_t*>(tgt.data()), tgt.size(),
75 patch_file.path, nullptr));
76 ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, patch));
77 }
78
RunSimulation(std::string_view src_tf,std::string_view ota_package,bool expected)79 static void RunSimulation(std::string_view src_tf, std::string_view ota_package, bool expected) {
80 TemporaryFile cmd_pipe;
81 TemporaryFile temp_saved_source;
82 TemporaryFile temp_last_command;
83 TemporaryDir temp_stash_base;
84
85 Paths::Get().set_cache_temp_source(temp_saved_source.path);
86 Paths::Get().set_last_command_file(temp_last_command.path);
87 Paths::Get().set_stash_directory_base(temp_stash_base.path);
88
89 // Configure edify's functions.
90 RegisterBuiltins();
91 RegisterInstallFunctions();
92 RegisterBlockImageFunctions();
93
94 // Run the update simulation and check the result.
95 TemporaryDir work_dir;
96 BuildInfo build_info(work_dir.path, false);
97 ASSERT_TRUE(build_info.ParseTargetFile(src_tf, false));
98 Updater updater(std::make_unique<SimulatorRuntime>(&build_info));
99 ASSERT_TRUE(updater.Init(cmd_pipe.release(), ota_package, false));
100 ASSERT_EQ(expected, updater.RunUpdate());
101 // TODO(xunchang) check the recovery&system has the expected contents.
102 }
103
104 class UpdateSimulatorTest : public ::testing::Test {
105 protected:
SetUp()106 void SetUp() override {
107 std::vector<string> props = {
108 "import /oem/oem.prop oem*",
109 "# begin build properties",
110 "# autogenerated by buildinfo.sh",
111 "ro.build.id=OPR1.170510.001",
112 "ro.build.display.id=OPR1.170510.001 dev-keys",
113 "ro.build.version.incremental=3993052",
114 "ro.build.version.release=O",
115 "ro.build.date=Wed May 10 11:10:29 UTC 2017",
116 "ro.build.date.utc=1494414629",
117 "ro.build.type=user",
118 "ro.build.tags=dev-keys",
119 "ro.build.flavor=angler-user",
120 "ro.product.system.brand=google",
121 "ro.product.system.name=angler",
122 "ro.product.system.device=angler",
123 };
124 build_prop_string_ = android::base::Join(props, "\n");
125
126 fstab_content_ = R"(
127 #<src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
128 # More comments.....
129
130 /dev/block/by-name/system /system ext4 ro,barrier=1 wait
131 /dev/block/by-name/vendor /vendor ext4 ro wait,verify=/dev/metadata
132 /dev/block/by-name/cache /cache ext4 noatime,errors=panic wait,check
133 /dev/block/by-name/modem /firmware vfat ro,uid=1000,gid=1000, wait
134 /dev/block/by-name/boot /boot emmc defaults defaults
135 /dev/block/by-name/recovery /recovery emmc defaults defaults
136 /dev/block/by-name/misc /misc emmc defaults
137 /dev/block/by-name/modem /modem emmc defaults defaults)";
138
139 raw_system_string_ = "system.img" + string(4086, '\0'); // raw image is 4096 bytes in total
140 sparse_system_string_ = string(SPARSE_SYSTEM_HEADER, std::end(SPARSE_SYSTEM_HEADER)) +
141 string(4136 - sizeof(SPARSE_SYSTEM_HEADER), '\0');
142 }
143
144 string build_prop_string_;
145 string fstab_content_;
146 string raw_system_string_;
147 string sparse_system_string_;
148 };
149
TEST_F(UpdateSimulatorTest,TargetFile_ExtractImage)150 TEST_F(UpdateSimulatorTest, TargetFile_ExtractImage) {
151 TemporaryFile zip_file;
152 AddZipEntries(zip_file.release(), { { "META/misc_info.txt", "extfs_sparse_flag=-s" },
153 { "IMAGES/system.img", sparse_system_string_ } });
154 TargetFile target_file(zip_file.path, false);
155 ASSERT_TRUE(target_file.Open());
156
157 TemporaryDir temp_dir;
158 TemporaryFile raw_image;
159 ASSERT_TRUE(target_file.ExtractImage(
160 "IMAGES/system.img", FstabInfo("/dev/system", "system", "ext4"), temp_dir.path, &raw_image));
161
162 // Check the raw image has expected contents.
163 string content;
164 ASSERT_TRUE(android::base::ReadFileToString(raw_image.path, &content));
165 string expected_content = "system.img" + string(4086, '\0');
166 ASSERT_EQ(expected_content, content);
167 }
168
TEST_F(UpdateSimulatorTest,TargetFile_ParseFstabInfo)169 TEST_F(UpdateSimulatorTest, TargetFile_ParseFstabInfo) {
170 TemporaryFile zip_file;
171 AddZipEntries(zip_file.release(),
172 { { "META/misc_info.txt", "" },
173 { "RECOVERY/RAMDISK/system/etc/recovery.fstab", fstab_content_ } });
174 TargetFile target_file(zip_file.path, false);
175 ASSERT_TRUE(target_file.Open());
176
177 std::vector<FstabInfo> fstab_info;
178 EXPECT_TRUE(target_file.ParseFstabInfo(&fstab_info));
179
180 std::vector<std::vector<string>> transformed;
181 std::transform(fstab_info.begin(), fstab_info.end(), std::back_inserter(transformed),
182 [](const FstabInfo& info) {
183 return std::vector<string>{ info.blockdev_name, info.mount_point, info.fs_type };
184 });
185
186 std::vector<std::vector<string>> expected = {
187 { "/dev/block/by-name/system", "/system", "ext4" },
188 { "/dev/block/by-name/vendor", "/vendor", "ext4" },
189 { "/dev/block/by-name/cache", "/cache", "ext4" },
190 { "/dev/block/by-name/boot", "/boot", "emmc" },
191 { "/dev/block/by-name/recovery", "/recovery", "emmc" },
192 { "/dev/block/by-name/misc", "/misc", "emmc" },
193 { "/dev/block/by-name/modem", "/modem", "emmc" },
194 };
195 EXPECT_EQ(expected, transformed);
196 }
197
TEST_F(UpdateSimulatorTest,BuildInfo_ParseTargetFile)198 TEST_F(UpdateSimulatorTest, BuildInfo_ParseTargetFile) {
199 std::map<string, string> entries = {
200 { "META/misc_info.txt", "" },
201 { "SYSTEM/build.prop", build_prop_string_ },
202 { "RECOVERY/RAMDISK/system/etc/recovery.fstab", fstab_content_ },
203 { "IMAGES/recovery.img", "" },
204 { "IMAGES/boot.img", "" },
205 { "IMAGES/misc.img", "" },
206 { "IMAGES/system.map", "" },
207 { "IMAGES/system.img", sparse_system_string_ },
208 };
209
210 TemporaryFile zip_file;
211 AddZipEntries(zip_file.release(), entries);
212
213 TemporaryDir temp_dir;
214 BuildInfo build_info(temp_dir.path, false);
215 ASSERT_TRUE(build_info.ParseTargetFile(zip_file.path, false));
216
217 std::map<string, string> expected_result = {
218 { "ro.build.id", "OPR1.170510.001" },
219 { "ro.build.display.id", "OPR1.170510.001 dev-keys" },
220 { "ro.build.version.incremental", "3993052" },
221 { "ro.build.version.release", "O" },
222 { "ro.build.date", "Wed May 10 11:10:29 UTC 2017" },
223 { "ro.build.date.utc", "1494414629" },
224 { "ro.build.type", "user" },
225 { "ro.build.tags", "dev-keys" },
226 { "ro.build.flavor", "angler-user" },
227 { "ro.product.brand", "google" },
228 { "ro.product.name", "angler" },
229 { "ro.product.device", "angler" },
230 };
231
232 for (const auto& [key, value] : expected_result) {
233 ASSERT_EQ(value, build_info.GetProperty(key, ""));
234 }
235
236 // Check that the temp files for each block device are created successfully.
237 for (auto name : { "/dev/block/by-name/system", "/dev/block/by-name/recovery",
238 "/dev/block/by-name/boot", "/dev/block/by-name/misc" }) {
239 ASSERT_EQ(0, access(build_info.FindBlockDeviceName(name).c_str(), R_OK));
240 }
241 }
242
TEST_F(UpdateSimulatorTest,RunUpdateSmoke)243 TEST_F(UpdateSimulatorTest, RunUpdateSmoke) {
244 string recovery_img_string = "recovery.img";
245 string boot_img_string = "boot.img";
246
247 std::map<string, string> src_entries{
248 { "META/misc_info.txt", "extfs_sparse_flag=-s" },
249 { "RECOVERY/RAMDISK/etc/recovery.fstab", fstab_content_ },
250 { "SYSTEM/build.prop", build_prop_string_ },
251 { "IMAGES/recovery.img", "" },
252 { "IMAGES/boot.img", boot_img_string },
253 { "IMAGES/system.img", sparse_system_string_ },
254 };
255
256 // Construct the source target-files.
257 TemporaryFile src_tf;
258 AddZipEntries(src_tf.release(), src_entries);
259
260 string recovery_from_boot;
261 CreateBsdiffPatch(boot_img_string, recovery_img_string, &recovery_from_boot);
262
263 // Set up the apply patch commands to patch the recovery image.
264 string recovery_sha1 = CalculateSha1(recovery_img_string);
265 string boot_sha1 = CalculateSha1(boot_img_string);
266 string apply_patch_source_string = android::base::StringPrintf(
267 "EMMC:/dev/block/by-name/boot:%zu:%s", boot_img_string.size(), boot_sha1.c_str());
268 string apply_patch_target_string = android::base::StringPrintf(
269 "EMMC:/dev/block/by-name/recovery:%zu:%s", recovery_img_string.size(), recovery_sha1.c_str());
270 string check_command = android::base::StringPrintf(
271 R"(patch_partition_check("%s", "%s") || abort("check failed");)",
272 apply_patch_target_string.c_str(), apply_patch_source_string.c_str());
273 string patch_command = android::base::StringPrintf(
274 R"(patch_partition("%s", "%s", package_extract_file("patch.p")) || abort("patch failed");)",
275 apply_patch_target_string.c_str(), apply_patch_source_string.c_str());
276
277 // Add the commands to update the system image. Test common commands:
278 // * getprop
279 // * ui_print
280 // * patch_partition
281 // * package_extract_file (single argument)
282 // * block_image_verify, block_image_update
283 string tgt_system_string = string(4096, 'a');
284 string system_patch;
285 CreateBsdiffPatch(raw_system_string_, tgt_system_string, &system_patch);
286
287 string tgt_system_hash = CalculateSha1(tgt_system_string);
288 string src_system_hash = CalculateSha1(raw_system_string_);
289
290 std::vector<std::string> transfer_list = {
291 "4",
292 "1",
293 "0",
294 "0",
295 android::base::StringPrintf("bsdiff 0 %zu %s %s 2,0,1 1 2,0,1", system_patch.size(),
296 src_system_hash.c_str(), tgt_system_hash.c_str()),
297 };
298
299 // Construct the updater_script.
300 std::vector<string> updater_commands = {
301 R"(getprop("ro.product.device") == "angler" || abort("This package is for \"angler\"");)",
302 R"(ui_print("Source: angler/OPR1.170510.001");)",
303 check_command,
304 patch_command,
305 R"(block_image_verify("/dev/block/by-name/system", )"
306 R"(package_extract_file("system.transfer.list"), "system.new.dat", "system.patch.dat") || )"
307 R"(abort("Failed to verify system.");)",
308 R"(block_image_update("/dev/block/by-name/system", )"
309 R"(package_extract_file("system.transfer.list"), "system.new.dat", "system.patch.dat") || )"
310 R"(abort("Failed to verify system.");)",
311 };
312 string updater_script = android::base::Join(updater_commands, '\n');
313
314 // Construct the ota update package.
315 std::map<string, string> ota_entries{
316 { "system.new.dat", "" },
317 { "system.patch.dat", system_patch },
318 { "system.transfer.list", android::base::Join(transfer_list, '\n') },
319 { "META-INF/com/google/android/updater-script", updater_script },
320 { "patch.p", recovery_from_boot },
321 };
322
323 TemporaryFile ota_package;
324 AddZipEntries(ota_package.release(), ota_entries);
325
326 RunSimulation(src_tf.path, ota_package.path, true);
327 }
328
329 TEST_F(UpdateSimulatorTest, RunUpdateUnrecognizedFunction) {
330 std::map<string, string> src_entries{
331 { "META/misc_info.txt", "extfs_sparse_flag=-s" },
332 { "IMAGES/system.img", sparse_system_string_ },
333 { "RECOVERY/RAMDISK/etc/recovery.fstab", fstab_content_ },
334 { "SYSTEM/build.prop", build_prop_string_ },
335 };
336
337 TemporaryFile src_tf;
338 AddZipEntries(src_tf.release(), src_entries);
339
340 std::map<string, string> ota_entries{
341 { "system.new.dat", "" },
342 { "system.patch.dat", "" },
343 { "system.transfer.list", "" },
344 { "META-INF/com/google/android/updater-script", R"(bad_function("");)" },
345 };
346
347 TemporaryFile ota_package;
348 AddZipEntries(ota_package.release(), ota_entries);
349
350 RunSimulation(src_tf.path, ota_package.path, false);
351 }
352
353 TEST_F(UpdateSimulatorTest, RunUpdateApplyPatchFailed) {
354 string recovery_img_string = "recovery.img";
355 string boot_img_string = "boot.img";
356
357 std::map<string, string> src_entries{
358 { "META/misc_info.txt", "extfs_sparse_flag=-s" },
359 { "IMAGES/recovery.img", "" },
360 { "IMAGES/boot.img", boot_img_string },
361 { "IMAGES/system.img", sparse_system_string_ },
362 { "RECOVERY/RAMDISK/etc/recovery.fstab", fstab_content_ },
363 { "SYSTEM/build.prop", build_prop_string_ },
364 };
365
366 TemporaryFile src_tf;
367 AddZipEntries(src_tf.release(), src_entries);
368
369 string recovery_sha1 = CalculateSha1(recovery_img_string);
370 string boot_sha1 = CalculateSha1(boot_img_string);
371 string apply_patch_source_string = android::base::StringPrintf(
372 "EMMC:/dev/block/by-name/boot:%zu:%s", boot_img_string.size(), boot_sha1.c_str());
373 string apply_patch_target_string = android::base::StringPrintf(
374 "EMMC:/dev/block/by-name/recovery:%zu:%s", recovery_img_string.size(), recovery_sha1.c_str());
375 string check_command = android::base::StringPrintf(
376 R"(patch_partition_check("%s", "%s") || abort("check failed");)",
377 apply_patch_target_string.c_str(), apply_patch_source_string.c_str());
378 string patch_command = android::base::StringPrintf(
379 R"(patch_partition("%s", "%s", package_extract_file("patch.p")) || abort("failed");)",
380 apply_patch_target_string.c_str(), apply_patch_source_string.c_str());
381
382 // Give an invalid recovery patch and expect the apply patch to fail.
383 // TODO(xunchang) check the cause code.
384 std::vector<string> updater_commands = {
385 R"(ui_print("Source: angler/OPR1.170510.001");)",
386 check_command,
387 patch_command,
388 };
389
390 string updater_script = android::base::Join(updater_commands, '\n');
391 std::map<string, string> ota_entries{
392 { "system.new.dat", "" },
393 { "system.patch.dat", "" },
394 { "system.transfer.list", "" },
395 { "META-INF/com/google/android/updater-script", updater_script },
396 { "patch.p", "random string" },
397 };
398
399 TemporaryFile ota_package;
400 AddZipEntries(ota_package.release(), ota_entries);
401
402 RunSimulation(src_tf.path, ota_package.path, false);
403 }
404