Tao Bao | b8cb2e6 | 2018-07-06 12:14:36 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2016 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 agree 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 <stdio.h> |
| 18 | #include <stdlib.h> |
| 19 | |
| 20 | #include <string> |
| 21 | #include <vector> |
| 22 | |
| 23 | #include <android-base/file.h> |
| 24 | #include <android-base/logging.h> |
| 25 | #include <android-base/stringprintf.h> |
| 26 | #include <android-base/test_utils.h> |
| 27 | #include <bsdiff/bsdiff.h> |
| 28 | #include <gtest/gtest.h> |
| 29 | #include <openssl/sha.h> |
| 30 | #include <zlib.h> |
| 31 | |
| 32 | #include "applypatch/applypatch_modes.h" |
| 33 | #include "common/test_constants.h" |
| 34 | #include "otautil/paths.h" |
| 35 | #include "otautil/print_sha1.h" |
| 36 | |
| 37 | using namespace std::string_literals; |
| 38 | |
| 39 | // TODO(b/67849209) Remove after debug the flakiness. |
| 40 | static void DecompressAndDumpRecoveryImage(const std::string& image_path) { |
| 41 | // Expected recovery_image structure |
| 42 | // chunk normal: 45066 bytes |
| 43 | // chunk deflate: 479442 bytes |
| 44 | // chunk normal: 5199 bytes |
| 45 | std::string recovery_content; |
| 46 | ASSERT_TRUE(android::base::ReadFileToString(image_path, &recovery_content)); |
| 47 | ASSERT_GT(recovery_content.size(), 45066 + 5199); |
| 48 | |
| 49 | z_stream strm = {}; |
| 50 | strm.avail_in = recovery_content.size() - 45066 - 5199; |
| 51 | strm.next_in = |
| 52 | const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(recovery_content.data())) + 45066; |
| 53 | |
| 54 | ASSERT_EQ(Z_OK, inflateInit2(&strm, -15)); |
| 55 | |
| 56 | constexpr unsigned int BUFFER_SIZE = 32768; |
| 57 | std::vector<uint8_t> uncompressed_data(BUFFER_SIZE); |
| 58 | size_t uncompressed_length = 0; |
| 59 | SHA_CTX ctx; |
| 60 | SHA1_Init(&ctx); |
| 61 | int ret; |
| 62 | do { |
| 63 | strm.avail_out = BUFFER_SIZE; |
| 64 | strm.next_out = uncompressed_data.data(); |
| 65 | |
| 66 | ret = inflate(&strm, Z_NO_FLUSH); |
| 67 | ASSERT_GE(ret, 0); |
| 68 | |
| 69 | SHA1_Update(&ctx, uncompressed_data.data(), BUFFER_SIZE - strm.avail_out); |
| 70 | uncompressed_length += BUFFER_SIZE - strm.avail_out; |
| 71 | } while (ret != Z_STREAM_END); |
| 72 | inflateEnd(&strm); |
| 73 | |
| 74 | uint8_t digest[SHA_DIGEST_LENGTH]; |
| 75 | SHA1_Final(digest, &ctx); |
| 76 | GTEST_LOG_(INFO) << "uncompressed length " << uncompressed_length |
| 77 | << " sha1: " << short_sha1(digest); |
| 78 | } |
| 79 | |
| 80 | static void sha1sum(const std::string& fname, std::string* sha1, size_t* fsize = nullptr) { |
| 81 | ASSERT_TRUE(sha1 != nullptr); |
| 82 | |
| 83 | std::string data; |
| 84 | ASSERT_TRUE(android::base::ReadFileToString(fname, &data)); |
| 85 | |
| 86 | if (fsize != nullptr) { |
| 87 | *fsize = data.size(); |
| 88 | } |
| 89 | |
| 90 | uint8_t digest[SHA_DIGEST_LENGTH]; |
| 91 | SHA1(reinterpret_cast<const uint8_t*>(data.c_str()), data.size(), digest); |
| 92 | *sha1 = print_sha1(digest); |
| 93 | } |
| 94 | |
| 95 | static void test_logger(android::base::LogId /* id */, android::base::LogSeverity severity, |
| 96 | const char* /* tag */, const char* /* file */, unsigned int /* line */, |
| 97 | const char* message) { |
| 98 | if (severity >= android::base::GetMinimumLogSeverity()) { |
| 99 | fprintf(stdout, "%s\n", message); |
| 100 | } |
| 101 | } |
| 102 | |
| 103 | class ApplyPatchModesTest : public ::testing::Test { |
| 104 | protected: |
| 105 | void SetUp() override { |
| 106 | Paths::Get().set_cache_temp_source(cache_source.path); |
| 107 | android::base::InitLogging(nullptr, &test_logger); |
| 108 | android::base::SetMinimumLogSeverity(android::base::LogSeverity::DEBUG); |
| 109 | } |
| 110 | |
| 111 | TemporaryFile cache_source; |
| 112 | }; |
| 113 | |
| 114 | TEST_F(ApplyPatchModesTest, InvalidArgs) { |
| 115 | // At least two args (including the filename). |
| 116 | ASSERT_EQ(2, applypatch_modes(1, (const char* []){ "applypatch" })); |
| 117 | |
| 118 | // Unrecognized args. |
| 119 | ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-x" })); |
| 120 | } |
| 121 | |
| 122 | TEST_F(ApplyPatchModesTest, PatchModeEmmcTarget) { |
| 123 | std::string boot_img = from_testdata_base("boot.img"); |
| 124 | size_t boot_img_size; |
| 125 | std::string boot_img_sha1; |
| 126 | sha1sum(boot_img, &boot_img_sha1, &boot_img_size); |
| 127 | |
| 128 | std::string recovery_img = from_testdata_base("recovery.img"); |
| 129 | size_t recovery_img_size; |
| 130 | std::string recovery_img_sha1; |
| 131 | sha1sum(recovery_img, &recovery_img_sha1, &recovery_img_size); |
| 132 | std::string recovery_img_size_arg = std::to_string(recovery_img_size); |
| 133 | |
| 134 | std::string bonus_file = from_testdata_base("bonus.file"); |
| 135 | |
| 136 | // applypatch -b <bonus-file> <src-file> <tgt-file> <tgt-sha1> <tgt-size> <src-sha1>:<patch> |
| 137 | std::string src_file_arg = |
| 138 | "EMMC:" + boot_img + ":" + std::to_string(boot_img_size) + ":" + boot_img_sha1; |
| 139 | TemporaryFile tgt_file; |
| 140 | std::string tgt_file_arg = "EMMC:"s + tgt_file.path; |
| 141 | std::string patch_arg = boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot.p"); |
| 142 | std::vector<const char*> args = { "applypatch", |
| 143 | "-b", |
| 144 | bonus_file.c_str(), |
| 145 | src_file_arg.c_str(), |
| 146 | tgt_file_arg.c_str(), |
| 147 | recovery_img_sha1.c_str(), |
| 148 | recovery_img_size_arg.c_str(), |
| 149 | patch_arg.c_str() }; |
| 150 | ASSERT_EQ(0, applypatch_modes(args.size(), args.data())); |
| 151 | } |
| 152 | |
| 153 | // Tests patching the EMMC target without a separate bonus file (i.e. recovery-from-boot patch has |
| 154 | // everything). |
| 155 | TEST_F(ApplyPatchModesTest, PatchModeEmmcTargetWithoutBonusFile) { |
| 156 | std::string boot_img = from_testdata_base("boot.img"); |
| 157 | size_t boot_img_size; |
| 158 | std::string boot_img_sha1; |
| 159 | sha1sum(boot_img, &boot_img_sha1, &boot_img_size); |
| 160 | |
| 161 | std::string recovery_img = from_testdata_base("recovery.img"); |
| 162 | size_t recovery_img_size; |
| 163 | std::string recovery_img_sha1; |
| 164 | sha1sum(recovery_img, &recovery_img_sha1, &recovery_img_size); |
| 165 | std::string recovery_img_size_arg = std::to_string(recovery_img_size); |
| 166 | |
| 167 | // applypatch <src-file> <tgt-file> <tgt-sha1> <tgt-size> <src-sha1>:<patch> |
| 168 | std::string src_file_arg = |
| 169 | "EMMC:" + boot_img + ":" + std::to_string(boot_img_size) + ":" + boot_img_sha1; |
| 170 | TemporaryFile tgt_file; |
| 171 | std::string tgt_file_arg = "EMMC:"s + tgt_file.path; |
| 172 | std::string patch_arg = |
| 173 | boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot-with-bonus.p"); |
| 174 | std::vector<const char*> args = { "applypatch", |
| 175 | src_file_arg.c_str(), |
| 176 | tgt_file_arg.c_str(), |
| 177 | recovery_img_sha1.c_str(), |
| 178 | recovery_img_size_arg.c_str(), |
| 179 | patch_arg.c_str() }; |
| 180 | |
| 181 | if (applypatch_modes(args.size(), args.data()) != 0) { |
| 182 | DecompressAndDumpRecoveryImage(tgt_file.path); |
| 183 | FAIL(); |
| 184 | } |
| 185 | } |
| 186 | |
| 187 | TEST_F(ApplyPatchModesTest, PatchModeEmmcTargetWithMultiplePatches) { |
| 188 | std::string boot_img = from_testdata_base("boot.img"); |
| 189 | size_t boot_img_size; |
| 190 | std::string boot_img_sha1; |
| 191 | sha1sum(boot_img, &boot_img_sha1, &boot_img_size); |
| 192 | |
| 193 | std::string recovery_img = from_testdata_base("recovery.img"); |
| 194 | size_t recovery_img_size; |
| 195 | std::string recovery_img_sha1; |
| 196 | sha1sum(recovery_img, &recovery_img_sha1, &recovery_img_size); |
| 197 | std::string recovery_img_size_arg = std::to_string(recovery_img_size); |
| 198 | |
| 199 | std::string bonus_file = from_testdata_base("bonus.file"); |
| 200 | |
| 201 | // applypatch -b <bonus-file> <src-file> <tgt-file> <tgt-sha1> <tgt-size> \ |
| 202 | // <src-sha1-fake1>:<patch1> <src-sha1>:<patch2> <src-sha1-fake2>:<patch3> |
| 203 | std::string src_file_arg = |
| 204 | "EMMC:" + boot_img + ":" + std::to_string(boot_img_size) + ":" + boot_img_sha1; |
| 205 | TemporaryFile tgt_file; |
| 206 | std::string tgt_file_arg = "EMMC:"s + tgt_file.path; |
| 207 | std::string bad_sha1_a = android::base::StringPrintf("%040x", rand()); |
| 208 | std::string bad_sha1_b = android::base::StringPrintf("%040x", rand()); |
| 209 | std::string patch1 = bad_sha1_a + ":" + from_testdata_base("recovery-from-boot.p"); |
| 210 | std::string patch2 = boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot.p"); |
| 211 | std::string patch3 = bad_sha1_b + ":" + from_testdata_base("recovery-from-boot.p"); |
| 212 | std::vector<const char*> args = { "applypatch", |
| 213 | "-b", |
| 214 | bonus_file.c_str(), |
| 215 | src_file_arg.c_str(), |
| 216 | tgt_file_arg.c_str(), |
| 217 | recovery_img_sha1.c_str(), |
| 218 | recovery_img_size_arg.c_str(), |
| 219 | patch1.c_str(), |
| 220 | patch2.c_str(), |
| 221 | patch3.c_str() }; |
| 222 | // TODO(b/67849209): Remove after addressing the flakiness. |
| 223 | printf("Calling applypatch_modes with the following args:\n"); |
| 224 | for (const auto& arg : args) { |
| 225 | printf(" %s\n", arg); |
| 226 | } |
| 227 | |
| 228 | if (applypatch_modes(args.size(), args.data()) != 0) { |
| 229 | DecompressAndDumpRecoveryImage(tgt_file.path); |
| 230 | FAIL(); |
| 231 | } |
| 232 | } |
| 233 | |
| 234 | // Ensures that applypatch works with a bsdiff based recovery-from-boot.p. |
| 235 | TEST_F(ApplyPatchModesTest, PatchModeEmmcTargetWithBsdiffPatch) { |
| 236 | std::string boot_img_file = from_testdata_base("boot.img"); |
| 237 | std::string boot_img_sha1; |
| 238 | size_t boot_img_size; |
| 239 | sha1sum(boot_img_file, &boot_img_sha1, &boot_img_size); |
| 240 | |
| 241 | std::string recovery_img_file = from_testdata_base("recovery.img"); |
| 242 | std::string recovery_img_sha1; |
| 243 | size_t recovery_img_size; |
| 244 | sha1sum(recovery_img_file, &recovery_img_sha1, &recovery_img_size); |
| 245 | |
| 246 | // Generate the bsdiff patch of recovery-from-boot.p. |
| 247 | std::string src_content; |
| 248 | ASSERT_TRUE(android::base::ReadFileToString(boot_img_file, &src_content)); |
| 249 | |
| 250 | std::string tgt_content; |
| 251 | ASSERT_TRUE(android::base::ReadFileToString(recovery_img_file, &tgt_content)); |
| 252 | |
| 253 | TemporaryFile patch_file; |
| 254 | ASSERT_EQ(0, |
| 255 | bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(src_content.data()), src_content.size(), |
| 256 | reinterpret_cast<const uint8_t*>(tgt_content.data()), tgt_content.size(), |
| 257 | patch_file.path, nullptr)); |
| 258 | |
| 259 | // applypatch <src-file> <tgt-file> <tgt-sha1> <tgt-size> <src-sha1>:<patch> |
| 260 | std::string src_file_arg = |
| 261 | "EMMC:" + boot_img_file + ":" + std::to_string(boot_img_size) + ":" + boot_img_sha1; |
| 262 | TemporaryFile tgt_file; |
| 263 | std::string tgt_file_arg = "EMMC:"s + tgt_file.path; |
| 264 | std::string recovery_img_size_arg = std::to_string(recovery_img_size); |
| 265 | std::string patch_arg = boot_img_sha1 + ":" + patch_file.path; |
| 266 | std::vector<const char*> args = { "applypatch", |
| 267 | src_file_arg.c_str(), |
| 268 | tgt_file_arg.c_str(), |
| 269 | recovery_img_sha1.c_str(), |
| 270 | recovery_img_size_arg.c_str(), |
| 271 | patch_arg.c_str() }; |
| 272 | ASSERT_EQ(0, applypatch_modes(args.size(), args.data())); |
| 273 | |
| 274 | // Double check the patched recovery image. |
| 275 | std::string tgt_file_sha1; |
| 276 | size_t tgt_file_size; |
| 277 | sha1sum(tgt_file.path, &tgt_file_sha1, &tgt_file_size); |
| 278 | ASSERT_EQ(recovery_img_size, tgt_file_size); |
| 279 | ASSERT_EQ(recovery_img_sha1, tgt_file_sha1); |
| 280 | } |
| 281 | |
| 282 | TEST_F(ApplyPatchModesTest, PatchModeInvalidArgs) { |
| 283 | // Invalid bonus file. |
| 284 | ASSERT_NE(0, applypatch_modes(3, (const char* []){ "applypatch", "-b", "/doesntexist" })); |
| 285 | |
| 286 | std::string bonus_file = from_testdata_base("bonus.file"); |
| 287 | // With bonus file, but missing args. |
| 288 | ASSERT_EQ(2, applypatch_modes(3, (const char* []){ "applypatch", "-b", bonus_file.c_str() })); |
| 289 | |
| 290 | std::string boot_img = from_testdata_base("boot.img"); |
| 291 | size_t boot_img_size; |
| 292 | std::string boot_img_sha1; |
| 293 | sha1sum(boot_img, &boot_img_sha1, &boot_img_size); |
| 294 | |
| 295 | std::string recovery_img = from_testdata_base("recovery.img"); |
| 296 | size_t size; |
| 297 | std::string recovery_img_sha1; |
| 298 | sha1sum(recovery_img, &recovery_img_sha1, &size); |
| 299 | std::string recovery_img_size = std::to_string(size); |
| 300 | |
| 301 | // Bonus file is not supported in flash mode. |
| 302 | // applypatch -b <bonus-file> <src-file> <tgt-file> <tgt-sha1> <tgt-size> |
| 303 | TemporaryFile tmp4; |
| 304 | std::vector<const char*> args4 = { |
| 305 | "applypatch", |
| 306 | "-b", |
| 307 | bonus_file.c_str(), |
| 308 | boot_img.c_str(), |
| 309 | tmp4.path, |
| 310 | recovery_img_sha1.c_str(), |
| 311 | recovery_img_size.c_str(), |
| 312 | }; |
| 313 | ASSERT_NE(0, applypatch_modes(args4.size(), args4.data())); |
| 314 | |
| 315 | // Failed to parse patch args. |
| 316 | TemporaryFile tmp5; |
| 317 | std::string bad_arg1 = |
| 318 | "invalid-sha1:filename" + from_testdata_base("recovery-from-boot-with-bonus.p"); |
| 319 | std::vector<const char*> args5 = { |
| 320 | "applypatch", |
| 321 | boot_img.c_str(), |
| 322 | tmp5.path, |
| 323 | recovery_img_sha1.c_str(), |
| 324 | recovery_img_size.c_str(), |
| 325 | bad_arg1.c_str(), |
| 326 | }; |
| 327 | ASSERT_NE(0, applypatch_modes(args5.size(), args5.data())); |
| 328 | |
| 329 | // Target size cannot be zero. |
| 330 | TemporaryFile tmp6; |
| 331 | std::string patch = boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot-with-bonus.p"); |
| 332 | std::vector<const char*> args6 = { |
| 333 | "applypatch", boot_img.c_str(), tmp6.path, recovery_img_sha1.c_str(), |
| 334 | "0", // target size |
| 335 | patch.c_str(), |
| 336 | }; |
| 337 | ASSERT_NE(0, applypatch_modes(args6.size(), args6.data())); |
| 338 | } |
| 339 | |
| 340 | TEST_F(ApplyPatchModesTest, CheckModeInvalidArgs) { |
| 341 | // Insufficient args. |
| 342 | ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-c" })); |
| 343 | } |
| 344 | |
| 345 | TEST_F(ApplyPatchModesTest, ShowLicenses) { |
| 346 | ASSERT_EQ(0, applypatch_modes(2, (const char* []){ "applypatch", "-l" })); |
| 347 | } |