diff options
Diffstat (limited to 'tests/component/updater_test.cpp')
-rw-r--r-- | tests/component/updater_test.cpp | 1225 |
1 files changed, 0 insertions, 1225 deletions
diff --git a/tests/component/updater_test.cpp b/tests/component/updater_test.cpp deleted file mode 100644 index a0a7b66ab..000000000 --- a/tests/component/updater_test.cpp +++ /dev/null @@ -1,1225 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <stdio.h> -#include <stdlib.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> - -#include <algorithm> -#include <memory> -#include <string> -#include <string_view> -#include <unordered_map> -#include <vector> - -#include <android-base/file.h> -#include <android-base/logging.h> -#include <android-base/parseint.h> -#include <android-base/properties.h> -#include <android-base/stringprintf.h> -#include <android-base/strings.h> -#include <bootloader_message/bootloader_message.h> -#include <brotli/encode.h> -#include <bsdiff/bsdiff.h> -#include <gtest/gtest.h> -#include <verity/hash_tree_builder.h> -#include <ziparchive/zip_archive.h> -#include <ziparchive/zip_writer.h> - -#include "applypatch/applypatch.h" -#include "common/test_constants.h" -#include "edify/expr.h" -#include "otautil/error_code.h" -#include "otautil/paths.h" -#include "otautil/print_sha1.h" -#include "otautil/sysutil.h" -#include "private/commands.h" -#include "updater/blockimg.h" -#include "updater/install.h" -#include "updater/updater.h" - -using namespace std::string_literals; - -using PackageEntries = std::unordered_map<std::string, std::string>; - -struct selabel_handle* sehandle = nullptr; - -static void expect(const char* expected, const std::string& expr_str, CauseCode cause_code, - UpdaterInfo* info = nullptr) { - std::unique_ptr<Expr> e; - int error_count = 0; - ASSERT_EQ(0, ParseString(expr_str, &e, &error_count)); - ASSERT_EQ(0, error_count); - - State state(expr_str, info); - - std::string result; - bool status = Evaluate(&state, e, &result); - - if (expected == nullptr) { - ASSERT_FALSE(status); - } else { - ASSERT_TRUE(status) << "Evaluate() finished with error message: " << state.errmsg; - ASSERT_STREQ(expected, result.c_str()); - } - - // Error code is set in updater/updater.cpp only, by parsing State.errmsg. - ASSERT_EQ(kNoError, state.error_code); - - // Cause code should always be available. - ASSERT_EQ(cause_code, state.cause_code); -} - -static void BuildUpdatePackage(const PackageEntries& entries, int fd) { - FILE* zip_file_ptr = fdopen(fd, "wb"); - ZipWriter zip_writer(zip_file_ptr); - - for (const auto& entry : entries) { - // All the entries are written as STORED. - ASSERT_EQ(0, zip_writer.StartEntry(entry.first.c_str(), 0)); - if (!entry.second.empty()) { - ASSERT_EQ(0, zip_writer.WriteBytes(entry.second.data(), entry.second.size())); - } - ASSERT_EQ(0, zip_writer.FinishEntry()); - } - - ASSERT_EQ(0, zip_writer.Finish()); - ASSERT_EQ(0, fclose(zip_file_ptr)); -} - -static void RunBlockImageUpdate(bool is_verify, const PackageEntries& entries, - const std::string& image_file, const std::string& result, - CauseCode cause_code = kNoCause) { - CHECK(entries.find("transfer_list") != entries.end()); - - // Build the update package. - TemporaryFile zip_file; - BuildUpdatePackage(entries, zip_file.release()); - - MemMapping map; - ASSERT_TRUE(map.MapFile(zip_file.path)); - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle)); - - // Set up the handler, command_pipe, patch offset & length. - UpdaterInfo updater_info; - updater_info.package_zip = handle; - TemporaryFile temp_pipe; - updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe"); - updater_info.package_zip_addr = map.addr; - updater_info.package_zip_len = map.length; - - std::string new_data = entries.find("new_data.br") != entries.end() ? "new_data.br" : "new_data"; - std::string script = is_verify ? "block_image_verify" : "block_image_update"; - script += R"((")" + image_file + R"(", package_extract_file("transfer_list"), ")" + new_data + - R"(", "patch_data"))"; - expect(result.c_str(), script, cause_code, &updater_info); - - ASSERT_EQ(0, fclose(updater_info.cmd_pipe)); - CloseArchive(handle); -} - -static std::string GetSha1(std::string_view content) { - uint8_t digest[SHA_DIGEST_LENGTH]; - SHA1(reinterpret_cast<const uint8_t*>(content.data()), content.size(), digest); - return print_sha1(digest); -} - -static Value* BlobToString(const char* name, State* state, - const std::vector<std::unique_ptr<Expr>>& argv) { - if (argv.size() != 1) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size()); - } - - std::vector<std::unique_ptr<Value>> args; - if (!ReadValueArgs(state, argv, &args)) { - return nullptr; - } - - if (args[0]->type != Value::Type::BLOB) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects a BLOB argument", name); - } - - args[0]->type = Value::Type::STRING; - return args[0].release(); -} - -class UpdaterTest : public ::testing::Test { - protected: - void SetUp() override { - RegisterBuiltins(); - RegisterInstallFunctions(); - RegisterBlockImageFunctions(); - - RegisterFunction("blob_to_string", BlobToString); - - // Each test is run in a separate process (isolated mode). Shared temporary files won't cause - // conflicts. - Paths::Get().set_cache_temp_source(temp_saved_source_.path); - Paths::Get().set_last_command_file(temp_last_command_.path); - Paths::Get().set_stash_directory_base(temp_stash_base_.path); - - // Enable a special command "abort" to simulate interruption. - Command::abort_allowed_ = true; - - last_command_file_ = temp_last_command_.path; - image_file_ = image_temp_file_.path; - } - - void TearDown() override { - // Clean up the last_command_file if any. - ASSERT_TRUE(android::base::RemoveFileIfExists(last_command_file_)); - - // Clear partition updated marker if any. - std::string updated_marker{ temp_stash_base_.path }; - updated_marker += "/" + GetSha1(image_temp_file_.path) + ".UPDATED"; - ASSERT_TRUE(android::base::RemoveFileIfExists(updated_marker)); - } - - TemporaryFile temp_saved_source_; - TemporaryDir temp_stash_base_; - std::string last_command_file_; - std::string image_file_; - - private: - TemporaryFile temp_last_command_; - TemporaryFile image_temp_file_; -}; - -TEST_F(UpdaterTest, getprop) { - expect(android::base::GetProperty("ro.product.device", "").c_str(), - "getprop(\"ro.product.device\")", - kNoCause); - - expect(android::base::GetProperty("ro.build.fingerprint", "").c_str(), - "getprop(\"ro.build.fingerprint\")", - kNoCause); - - // getprop() accepts only one parameter. - expect(nullptr, "getprop()", kArgsParsingFailure); - expect(nullptr, "getprop(\"arg1\", \"arg2\")", kArgsParsingFailure); -} - -TEST_F(UpdaterTest, patch_partition_check) { - // Zero argument is not valid. - expect(nullptr, "patch_partition_check()", kArgsParsingFailure); - - std::string source_file = from_testdata_base("boot.img"); - std::string source_content; - ASSERT_TRUE(android::base::ReadFileToString(source_file, &source_content)); - size_t source_size = source_content.size(); - std::string source_hash = GetSha1(source_content); - Partition source(source_file, source_size, source_hash); - - std::string target_file = from_testdata_base("recovery.img"); - std::string target_content; - ASSERT_TRUE(android::base::ReadFileToString(target_file, &target_content)); - size_t target_size = target_content.size(); - std::string target_hash = GetSha1(target_content); - Partition target(target_file, target_size, target_hash); - - // One argument is not valid. - expect(nullptr, "patch_partition_check(\"" + source.ToString() + "\")", kArgsParsingFailure); - expect(nullptr, "patch_partition_check(\"" + target.ToString() + "\")", kArgsParsingFailure); - - // Both of the source and target have the desired checksum. - std::string cmd = - "patch_partition_check(\"" + source.ToString() + "\", \"" + target.ToString() + "\")"; - expect("t", cmd, kNoCause); - - // Only source partition has the desired checksum. - Partition bad_target(target_file, target_size - 1, target_hash); - cmd = "patch_partition_check(\"" + source.ToString() + "\", \"" + bad_target.ToString() + "\")"; - expect("t", cmd, kNoCause); - - // Only target partition has the desired checksum. - Partition bad_source(source_file, source_size + 1, source_hash); - cmd = "patch_partition_check(\"" + bad_source.ToString() + "\", \"" + target.ToString() + "\")"; - expect("t", cmd, kNoCause); - - // Neither of the source or target has the desired checksum. - cmd = - "patch_partition_check(\"" + bad_source.ToString() + "\", \"" + bad_target.ToString() + "\")"; - expect("", cmd, kNoCause); -} - -TEST_F(UpdaterTest, file_getprop) { - // file_getprop() expects two arguments. - expect(nullptr, "file_getprop()", kArgsParsingFailure); - expect(nullptr, "file_getprop(\"arg1\")", kArgsParsingFailure); - expect(nullptr, "file_getprop(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); - - // File doesn't exist. - expect(nullptr, "file_getprop(\"/doesntexist\", \"key1\")", kFreadFailure); - - // Reject too large files (current limit = 65536). - TemporaryFile temp_file1; - std::string buffer(65540, '\0'); - ASSERT_TRUE(android::base::WriteStringToFile(buffer, temp_file1.path)); - - // Read some keys. - TemporaryFile temp_file2; - std::string content("ro.product.name=tardis\n" - "# comment\n\n\n" - "ro.product.model\n" - "ro.product.board = magic \n"); - ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file2.path)); - - std::string script1("file_getprop(\"" + std::string(temp_file2.path) + - "\", \"ro.product.name\")"); - expect("tardis", script1, kNoCause); - - std::string script2("file_getprop(\"" + std::string(temp_file2.path) + - "\", \"ro.product.board\")"); - expect("magic", script2, kNoCause); - - // No match. - std::string script3("file_getprop(\"" + std::string(temp_file2.path) + - "\", \"ro.product.wrong\")"); - expect("", script3, kNoCause); - - std::string script4("file_getprop(\"" + std::string(temp_file2.path) + - "\", \"ro.product.name=\")"); - expect("", script4, kNoCause); - - std::string script5("file_getprop(\"" + std::string(temp_file2.path) + - "\", \"ro.product.nam\")"); - expect("", script5, kNoCause); - - std::string script6("file_getprop(\"" + std::string(temp_file2.path) + - "\", \"ro.product.model\")"); - expect("", script6, kNoCause); -} - -// TODO: Test extracting to block device. -TEST_F(UpdaterTest, package_extract_file) { - // package_extract_file expects 1 or 2 arguments. - expect(nullptr, "package_extract_file()", kArgsParsingFailure); - expect(nullptr, "package_extract_file(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); - - std::string zip_path = from_testdata_base("ziptest_valid.zip"); - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle)); - - // Need to set up the ziphandle. - UpdaterInfo updater_info; - updater_info.package_zip = handle; - - // Two-argument version. - TemporaryFile temp_file1; - std::string script("package_extract_file(\"a.txt\", \"" + std::string(temp_file1.path) + "\")"); - expect("t", script, kNoCause, &updater_info); - - // Verify the extracted entry. - std::string data; - ASSERT_TRUE(android::base::ReadFileToString(temp_file1.path, &data)); - ASSERT_EQ(kATxtContents, data); - - // Now extract another entry to the same location, which should overwrite. - script = "package_extract_file(\"b.txt\", \"" + std::string(temp_file1.path) + "\")"; - expect("t", script, kNoCause, &updater_info); - - ASSERT_TRUE(android::base::ReadFileToString(temp_file1.path, &data)); - ASSERT_EQ(kBTxtContents, data); - - // Missing zip entry. The two-argument version doesn't abort. - script = "package_extract_file(\"doesntexist\", \"" + std::string(temp_file1.path) + "\")"; - expect("", script, kNoCause, &updater_info); - - // Extract to /dev/full should fail. - script = "package_extract_file(\"a.txt\", \"/dev/full\")"; - expect("", script, kNoCause, &updater_info); - - // One-argument version. package_extract_file() gives a VAL_BLOB, which needs to be converted to - // VAL_STRING for equality test. - script = "blob_to_string(package_extract_file(\"a.txt\")) == \"" + kATxtContents + "\""; - expect("t", script, kNoCause, &updater_info); - - script = "blob_to_string(package_extract_file(\"b.txt\")) == \"" + kBTxtContents + "\""; - expect("t", script, kNoCause, &updater_info); - - // Missing entry. The one-argument version aborts the evaluation. - script = "package_extract_file(\"doesntexist\")"; - expect(nullptr, script, kPackageExtractFileFailure, &updater_info); - - CloseArchive(handle); -} - -TEST_F(UpdaterTest, read_file) { - // read_file() expects one argument. - expect(nullptr, "read_file()", kArgsParsingFailure); - expect(nullptr, "read_file(\"arg1\", \"arg2\")", kArgsParsingFailure); - - // Write some value to file and read back. - TemporaryFile temp_file; - std::string script("write_value(\"foo\", \""s + temp_file.path + "\");"); - expect("t", script, kNoCause); - - script = "read_file(\""s + temp_file.path + "\") == \"foo\""; - expect("t", script, kNoCause); - - script = "read_file(\""s + temp_file.path + "\") == \"bar\""; - expect("", script, kNoCause); - - // It should fail gracefully when read fails. - script = "read_file(\"/doesntexist\")"; - expect("", script, kNoCause); -} - -TEST_F(UpdaterTest, compute_hash_tree_smoke) { - std::string data; - for (unsigned char i = 0; i < 128; i++) { - data += std::string(4096, i); - } - // Appends an additional block for verity data. - data += std::string(4096, 0); - ASSERT_EQ(129 * 4096, data.size()); - ASSERT_TRUE(android::base::WriteStringToFile(data, image_file_)); - - std::string salt = "aee087a5be3b982978c923f566a94613496b417f2af592639bc80d141e34dfe7"; - std::string expected_root_hash = - "7e0a8d8747f54384014ab996f5b2dc4eb7ff00c630eede7134c9e3f05c0dd8ca"; - // hash_tree_ranges, source_ranges, hash_algorithm, salt_hex, root_hash - std::vector<std::string> tokens{ "compute_hash_tree", "2,128,129", "2,0,128", "sha256", salt, - expected_root_hash }; - std::string hash_tree_command = android::base::Join(tokens, " "); - - std::vector<std::string> transfer_list{ - "4", "2", "0", "2", hash_tree_command, - }; - - PackageEntries entries{ - { "new_data", "" }, - { "patch_data", "" }, - { "transfer_list", android::base::Join(transfer_list, "\n") }, - }; - - RunBlockImageUpdate(false, entries, image_file_, "t"); - - std::string updated; - ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated)); - ASSERT_EQ(129 * 4096, updated.size()); - ASSERT_EQ(data.substr(0, 128 * 4096), updated.substr(0, 128 * 4096)); - - // Computes the SHA256 of the salt + hash_tree_data and expects the result to match with the - // root_hash. - std::vector<unsigned char> salt_bytes; - ASSERT_TRUE(HashTreeBuilder::ParseBytesArrayFromString(salt, &salt_bytes)); - std::vector<unsigned char> hash_tree = std::move(salt_bytes); - hash_tree.insert(hash_tree.end(), updated.begin() + 128 * 4096, updated.end()); - - std::vector<unsigned char> digest(SHA256_DIGEST_LENGTH); - SHA256(hash_tree.data(), hash_tree.size(), digest.data()); - ASSERT_EQ(expected_root_hash, HashTreeBuilder::BytesArrayToString(digest)); -} - -TEST_F(UpdaterTest, compute_hash_tree_root_mismatch) { - std::string data; - for (size_t i = 0; i < 128; i++) { - data += std::string(4096, i); - } - // Appends an additional block for verity data. - data += std::string(4096, 0); - ASSERT_EQ(129 * 4096, data.size()); - // Corrupts one bit - data[4096] = 'A'; - ASSERT_TRUE(android::base::WriteStringToFile(data, image_file_)); - - std::string salt = "aee087a5be3b982978c923f566a94613496b417f2af592639bc80d141e34dfe7"; - std::string expected_root_hash = - "7e0a8d8747f54384014ab996f5b2dc4eb7ff00c630eede7134c9e3f05c0dd8ca"; - // hash_tree_ranges, source_ranges, hash_algorithm, salt_hex, root_hash - std::vector<std::string> tokens{ "compute_hash_tree", "2,128,129", "2,0,128", "sha256", salt, - expected_root_hash }; - std::string hash_tree_command = android::base::Join(tokens, " "); - - std::vector<std::string> transfer_list{ - "4", "2", "0", "2", hash_tree_command, - }; - - PackageEntries entries{ - { "new_data", "" }, - { "patch_data", "" }, - { "transfer_list", android::base::Join(transfer_list, "\n") }, - }; - - RunBlockImageUpdate(false, entries, image_file_, "", kHashTreeComputationFailure); -} - -TEST_F(UpdaterTest, write_value) { - // write_value() expects two arguments. - expect(nullptr, "write_value()", kArgsParsingFailure); - expect(nullptr, "write_value(\"arg1\")", kArgsParsingFailure); - expect(nullptr, "write_value(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); - - // filename cannot be empty. - expect(nullptr, "write_value(\"value\", \"\")", kArgsParsingFailure); - - // Write some value to file. - TemporaryFile temp_file; - std::string value = "magicvalue"; - std::string script("write_value(\"" + value + "\", \"" + std::string(temp_file.path) + "\")"); - expect("t", script, kNoCause); - - // Verify the content. - std::string content; - ASSERT_TRUE(android::base::ReadFileToString(temp_file.path, &content)); - ASSERT_EQ(value, content); - - // Allow writing empty string. - script = "write_value(\"\", \"" + std::string(temp_file.path) + "\")"; - expect("t", script, kNoCause); - - // Verify the content. - ASSERT_TRUE(android::base::ReadFileToString(temp_file.path, &content)); - ASSERT_EQ("", content); - - // It should fail gracefully when write fails. - script = "write_value(\"value\", \"/proc/0/file1\")"; - expect("", script, kNoCause); -} - -TEST_F(UpdaterTest, get_stage) { - // get_stage() expects one argument. - expect(nullptr, "get_stage()", kArgsParsingFailure); - expect(nullptr, "get_stage(\"arg1\", \"arg2\")", kArgsParsingFailure); - expect(nullptr, "get_stage(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); - - // Set up a local file as BCB. - TemporaryFile tf; - std::string temp_file(tf.path); - bootloader_message boot; - strlcpy(boot.stage, "2/3", sizeof(boot.stage)); - std::string err; - ASSERT_TRUE(write_bootloader_message_to(boot, temp_file, &err)); - - // Can read the stage value. - std::string script("get_stage(\"" + temp_file + "\")"); - expect("2/3", script, kNoCause); - - // Bad BCB path. - script = "get_stage(\"doesntexist\")"; - expect("", script, kNoCause); -} - -TEST_F(UpdaterTest, set_stage) { - // set_stage() expects two arguments. - expect(nullptr, "set_stage()", kArgsParsingFailure); - expect(nullptr, "set_stage(\"arg1\")", kArgsParsingFailure); - expect(nullptr, "set_stage(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); - - // Set up a local file as BCB. - TemporaryFile tf; - std::string temp_file(tf.path); - bootloader_message boot; - strlcpy(boot.command, "command", sizeof(boot.command)); - strlcpy(boot.stage, "2/3", sizeof(boot.stage)); - std::string err; - ASSERT_TRUE(write_bootloader_message_to(boot, temp_file, &err)); - - // Write with set_stage(). - std::string script("set_stage(\"" + temp_file + "\", \"1/3\")"); - expect(tf.path, script, kNoCause); - - // Verify. - bootloader_message boot_verify; - ASSERT_TRUE(read_bootloader_message_from(&boot_verify, temp_file, &err)); - - // Stage should be updated, with command part untouched. - ASSERT_STREQ("1/3", boot_verify.stage); - ASSERT_STREQ(boot.command, boot_verify.command); - - // Bad BCB path. - script = "set_stage(\"doesntexist\", \"1/3\")"; - expect("", script, kNoCause); - - script = "set_stage(\"/dev/full\", \"1/3\")"; - expect("", script, kNoCause); -} - -TEST_F(UpdaterTest, set_progress) { - // set_progress() expects one argument. - expect(nullptr, "set_progress()", kArgsParsingFailure); - expect(nullptr, "set_progress(\"arg1\", \"arg2\")", kArgsParsingFailure); - - // Invalid progress argument. - expect(nullptr, "set_progress(\"arg1\")", kArgsParsingFailure); - expect(nullptr, "set_progress(\"3x+5\")", kArgsParsingFailure); - expect(nullptr, "set_progress(\".3.5\")", kArgsParsingFailure); - - TemporaryFile tf; - UpdaterInfo updater_info; - updater_info.cmd_pipe = fdopen(tf.release(), "w"); - expect(".52", "set_progress(\".52\")", kNoCause, &updater_info); - fflush(updater_info.cmd_pipe); - - std::string cmd; - ASSERT_TRUE(android::base::ReadFileToString(tf.path, &cmd)); - ASSERT_EQ(android::base::StringPrintf("set_progress %f\n", .52), cmd); - // recovery-updater protocol expects 2 tokens ("set_progress <frac>"). - ASSERT_EQ(2U, android::base::Split(cmd, " ").size()); - ASSERT_EQ(0, fclose(updater_info.cmd_pipe)); -} - -TEST_F(UpdaterTest, show_progress) { - // show_progress() expects two arguments. - expect(nullptr, "show_progress()", kArgsParsingFailure); - expect(nullptr, "show_progress(\"arg1\")", kArgsParsingFailure); - expect(nullptr, "show_progress(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); - - // Invalid progress arguments. - expect(nullptr, "show_progress(\"arg1\", \"arg2\")", kArgsParsingFailure); - expect(nullptr, "show_progress(\"3x+5\", \"10\")", kArgsParsingFailure); - expect(nullptr, "show_progress(\".3\", \"5a\")", kArgsParsingFailure); - - TemporaryFile tf; - UpdaterInfo updater_info; - updater_info.cmd_pipe = fdopen(tf.release(), "w"); - expect(".52", "show_progress(\".52\", \"10\")", kNoCause, &updater_info); - fflush(updater_info.cmd_pipe); - - std::string cmd; - ASSERT_TRUE(android::base::ReadFileToString(tf.path, &cmd)); - ASSERT_EQ(android::base::StringPrintf("progress %f %d\n", .52, 10), cmd); - // recovery-updater protocol expects 3 tokens ("progress <frac> <secs>"). - ASSERT_EQ(3U, android::base::Split(cmd, " ").size()); - ASSERT_EQ(0, fclose(updater_info.cmd_pipe)); -} - -TEST_F(UpdaterTest, block_image_update_parsing_error) { - std::vector<std::string> transfer_list{ - // clang-format off - "4", - "2", - "0", - // clang-format on - }; - - PackageEntries entries{ - { "new_data", "" }, - { "patch_data", "" }, - { "transfer_list", android::base::Join(transfer_list, '\n') }, - }; - - RunBlockImageUpdate(false, entries, image_file_, "", kArgsParsingFailure); -} - -// Generates the bsdiff of the given source and target images, and writes the result entries. -// target_blocks specifies the block count to be written into the `bsdiff` command, which may be -// different from the given target size in order to trigger overrun / underrun paths. -static void GetEntriesForBsdiff(std::string_view source, std::string_view target, - size_t target_blocks, PackageEntries* entries) { - // Generate the patch data. - TemporaryFile patch_file; - ASSERT_EQ(0, bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(source.data()), source.size(), - reinterpret_cast<const uint8_t*>(target.data()), target.size(), - patch_file.path, nullptr)); - std::string patch_content; - ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch_content)); - - // Create the transfer list that contains a bsdiff. - std::string src_hash = GetSha1(source); - std::string tgt_hash = GetSha1(target); - size_t source_blocks = source.size() / 4096; - std::vector<std::string> transfer_list{ - // clang-format off - "4", - std::to_string(target_blocks), - "0", - "0", - // bsdiff patch_offset patch_length source_hash target_hash target_range source_block_count - // source_range - android::base::StringPrintf("bsdiff 0 %zu %s %s 2,0,%zu %zu 2,0,%zu", patch_content.size(), - src_hash.c_str(), tgt_hash.c_str(), target_blocks, source_blocks, - source_blocks), - // clang-format on - }; - - *entries = { - { "new_data", "" }, - { "patch_data", patch_content }, - { "transfer_list", android::base::Join(transfer_list, '\n') }, - }; -} - -TEST_F(UpdaterTest, block_image_update_patch_data) { - // Both source and target images have 10 blocks. - std::string source = - std::string(4096, 'a') + std::string(4096, 'c') + std::string(4096 * 3, '\0'); - std::string target = - std::string(4096, 'b') + std::string(4096, 'd') + std::string(4096 * 3, '\0'); - ASSERT_TRUE(android::base::WriteStringToFile(source, image_file_)); - - PackageEntries entries; - GetEntriesForBsdiff(std::string_view(source).substr(0, 4096 * 2), - std::string_view(target).substr(0, 4096 * 2), 2, &entries); - RunBlockImageUpdate(false, entries, image_file_, "t"); - - // The update_file should be patched correctly. - std::string updated; - ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated)); - ASSERT_EQ(target, updated); -} - -TEST_F(UpdaterTest, block_image_update_patch_overrun) { - // Both source and target images have 10 blocks. - std::string source = - std::string(4096, 'a') + std::string(4096, 'c') + std::string(4096 * 3, '\0'); - std::string target = - std::string(4096, 'b') + std::string(4096, 'd') + std::string(4096 * 3, '\0'); - ASSERT_TRUE(android::base::WriteStringToFile(source, image_file_)); - - // Provide one less block to trigger the overrun path. - PackageEntries entries; - GetEntriesForBsdiff(std::string_view(source).substr(0, 4096 * 2), - std::string_view(target).substr(0, 4096 * 2), 1, &entries); - - // The update should fail due to overrun. - RunBlockImageUpdate(false, entries, image_file_, "", kPatchApplicationFailure); -} - -TEST_F(UpdaterTest, block_image_update_patch_underrun) { - // Both source and target images have 10 blocks. - std::string source = - std::string(4096, 'a') + std::string(4096, 'c') + std::string(4096 * 3, '\0'); - std::string target = - std::string(4096, 'b') + std::string(4096, 'd') + std::string(4096 * 3, '\0'); - ASSERT_TRUE(android::base::WriteStringToFile(source, image_file_)); - - // Provide one more block to trigger the overrun path. - PackageEntries entries; - GetEntriesForBsdiff(std::string_view(source).substr(0, 4096 * 2), - std::string_view(target).substr(0, 4096 * 2), 3, &entries); - - // The update should fail due to underrun. - RunBlockImageUpdate(false, entries, image_file_, "", kPatchApplicationFailure); -} - -TEST_F(UpdaterTest, block_image_update_fail) { - std::string src_content(4096 * 2, 'e'); - std::string src_hash = GetSha1(src_content); - // Stash and free some blocks, then fail the update intentionally. - std::vector<std::string> transfer_list{ - // clang-format off - "4", - "2", - "0", - "2", - "stash " + src_hash + " 2,0,2", - "free " + src_hash, - "abort", - // clang-format on - }; - - // Add a new data of 10 bytes to test the deadlock. - PackageEntries entries{ - { "new_data", std::string(10, 0) }, - { "patch_data", "" }, - { "transfer_list", android::base::Join(transfer_list, '\n') }, - }; - - ASSERT_TRUE(android::base::WriteStringToFile(src_content, image_file_)); - - RunBlockImageUpdate(false, entries, image_file_, ""); - - // Updater generates the stash name based on the input file name. - std::string name_digest = GetSha1(image_file_); - std::string stash_base = std::string(temp_stash_base_.path) + "/" + name_digest; - ASSERT_EQ(0, access(stash_base.c_str(), F_OK)); - // Expect the stashed blocks to be freed. - ASSERT_EQ(-1, access((stash_base + src_hash).c_str(), F_OK)); - ASSERT_EQ(0, rmdir(stash_base.c_str())); -} - -TEST_F(UpdaterTest, new_data_over_write) { - std::vector<std::string> transfer_list{ - // clang-format off - "4", - "1", - "0", - "0", - "new 2,0,1", - // clang-format on - }; - - // Write 4096 + 100 bytes of new data. - PackageEntries entries{ - { "new_data", std::string(4196, 0) }, - { "patch_data", "" }, - { "transfer_list", android::base::Join(transfer_list, '\n') }, - }; - - RunBlockImageUpdate(false, entries, image_file_, "t"); -} - -TEST_F(UpdaterTest, new_data_short_write) { - std::vector<std::string> transfer_list{ - // clang-format off - "4", - "1", - "0", - "0", - "new 2,0,1", - // clang-format on - }; - - PackageEntries entries{ - { "patch_data", "" }, - { "transfer_list", android::base::Join(transfer_list, '\n') }, - }; - - // Updater should report the failure gracefully rather than stuck in deadlock. - entries["new_data"] = ""; - RunBlockImageUpdate(false, entries, image_file_, ""); - - entries["new_data"] = std::string(10, 'a'); - RunBlockImageUpdate(false, entries, image_file_, ""); - - // Expect to write 1 block of new data successfully. - entries["new_data"] = std::string(4096, 'a'); - RunBlockImageUpdate(false, entries, image_file_, "t"); -} - -TEST_F(UpdaterTest, brotli_new_data) { - auto generator = []() { return rand() % 128; }; - // Generate 100 blocks of random data. - std::string brotli_new_data; - brotli_new_data.reserve(4096 * 100); - generate_n(back_inserter(brotli_new_data), 4096 * 100, generator); - - size_t encoded_size = BrotliEncoderMaxCompressedSize(brotli_new_data.size()); - std::string encoded_data(encoded_size, 0); - ASSERT_TRUE(BrotliEncoderCompress( - BROTLI_DEFAULT_QUALITY, BROTLI_DEFAULT_WINDOW, BROTLI_DEFAULT_MODE, brotli_new_data.size(), - reinterpret_cast<const uint8_t*>(brotli_new_data.data()), &encoded_size, - reinterpret_cast<uint8_t*>(const_cast<char*>(encoded_data.data())))); - encoded_data.resize(encoded_size); - - // Write a few small chunks of new data, then a large chunk, and finally a few small chunks. - // This helps us to catch potential short writes. - std::vector<std::string> transfer_list = { - "4", - "100", - "0", - "0", - "new 2,0,1", - "new 2,1,2", - "new 4,2,50,50,97", - "new 2,97,98", - "new 2,98,99", - "new 2,99,100", - }; - - PackageEntries entries{ - { "new_data.br", std::move(encoded_data) }, - { "patch_data", "" }, - { "transfer_list", android::base::Join(transfer_list, '\n') }, - }; - - RunBlockImageUpdate(false, entries, image_file_, "t"); - - std::string updated_content; - ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated_content)); - ASSERT_EQ(brotli_new_data, updated_content); -} - -TEST_F(UpdaterTest, last_command_update) { - std::string block1(4096, '1'); - std::string block2(4096, '2'); - std::string block3(4096, '3'); - std::string block1_hash = GetSha1(block1); - std::string block2_hash = GetSha1(block2); - std::string block3_hash = GetSha1(block3); - - // Compose the transfer list to fail the first update. - std::vector<std::string> transfer_list_fail{ - // clang-format off - "4", - "2", - "0", - "2", - "stash " + block1_hash + " 2,0,1", - "move " + block1_hash + " 2,1,2 1 2,0,1", - "stash " + block3_hash + " 2,2,3", - "abort", - // clang-format on - }; - - // Mimic a resumed update with the same transfer commands. - std::vector<std::string> transfer_list_continue{ - // clang-format off - "4", - "2", - "0", - "2", - "stash " + block1_hash + " 2,0,1", - "move " + block1_hash + " 2,1,2 1 2,0,1", - "stash " + block3_hash + " 2,2,3", - "move " + block1_hash + " 2,2,3 1 2,0,1", - // clang-format on - }; - - ASSERT_TRUE(android::base::WriteStringToFile(block1 + block2 + block3, image_file_)); - - PackageEntries entries{ - { "new_data", "" }, - { "patch_data", "" }, - { "transfer_list", android::base::Join(transfer_list_fail, '\n') }, - }; - - // "2\nstash " + block3_hash + " 2,2,3" - std::string last_command_content = - "2\n" + transfer_list_fail[TransferList::kTransferListHeaderLines + 2]; - - RunBlockImageUpdate(false, entries, image_file_, ""); - - // Expect last_command to contain the last stash command. - std::string last_command_actual; - ASSERT_TRUE(android::base::ReadFileToString(last_command_file_, &last_command_actual)); - EXPECT_EQ(last_command_content, last_command_actual); - - std::string updated_contents; - ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated_contents)); - ASSERT_EQ(block1 + block1 + block3, updated_contents); - - // "Resume" the update. Expect the first 'move' to be skipped but the second 'move' to be - // executed. Note that we intentionally reset the image file. - entries["transfer_list"] = android::base::Join(transfer_list_continue, '\n'); - ASSERT_TRUE(android::base::WriteStringToFile(block1 + block2 + block3, image_file_)); - RunBlockImageUpdate(false, entries, image_file_, "t"); - - ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated_contents)); - ASSERT_EQ(block1 + block2 + block1, updated_contents); -} - -TEST_F(UpdaterTest, last_command_update_unresumable) { - std::string block1(4096, '1'); - std::string block2(4096, '2'); - std::string block1_hash = GetSha1(block1); - std::string block2_hash = GetSha1(block2); - - // Construct an unresumable update with source blocks mismatch. - std::vector<std::string> transfer_list_unresumable{ - // clang-format off - "4", - "2", - "0", - "2", - "stash " + block1_hash + " 2,0,1", - "move " + block2_hash + " 2,1,2 1 2,0,1", - // clang-format on - }; - - PackageEntries entries{ - { "new_data", "" }, - { "patch_data", "" }, - { "transfer_list", android::base::Join(transfer_list_unresumable, '\n') }, - }; - - ASSERT_TRUE(android::base::WriteStringToFile(block1 + block1, image_file_)); - - std::string last_command_content = - "0\n" + transfer_list_unresumable[TransferList::kTransferListHeaderLines]; - ASSERT_TRUE(android::base::WriteStringToFile(last_command_content, last_command_file_)); - - RunBlockImageUpdate(false, entries, image_file_, ""); - - // The last_command_file will be deleted if the update encounters an unresumable failure later. - ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK)); -} - -TEST_F(UpdaterTest, last_command_verify) { - std::string block1(4096, '1'); - std::string block2(4096, '2'); - std::string block3(4096, '3'); - std::string block1_hash = GetSha1(block1); - std::string block2_hash = GetSha1(block2); - std::string block3_hash = GetSha1(block3); - - std::vector<std::string> transfer_list_verify{ - // clang-format off - "4", - "2", - "0", - "2", - "stash " + block1_hash + " 2,0,1", - "move " + block1_hash + " 2,0,1 1 2,0,1", - "move " + block1_hash + " 2,1,2 1 2,0,1", - "stash " + block3_hash + " 2,2,3", - // clang-format on - }; - - PackageEntries entries{ - { "new_data", "" }, - { "patch_data", "" }, - { "transfer_list", android::base::Join(transfer_list_verify, '\n') }, - }; - - ASSERT_TRUE(android::base::WriteStringToFile(block1 + block1 + block3, image_file_)); - - // Last command: "move " + block1_hash + " 2,1,2 1 2,0,1" - std::string last_command_content = - "2\n" + transfer_list_verify[TransferList::kTransferListHeaderLines + 2]; - - // First run: expect the verification to succeed and the last_command_file is intact. - ASSERT_TRUE(android::base::WriteStringToFile(last_command_content, last_command_file_)); - - RunBlockImageUpdate(true, entries, image_file_, "t"); - - std::string last_command_actual; - ASSERT_TRUE(android::base::ReadFileToString(last_command_file_, &last_command_actual)); - EXPECT_EQ(last_command_content, last_command_actual); - - // Second run with a mismatching block image: expect the verification to succeed but - // last_command_file to be deleted; because the target blocks in the last command don't have the - // expected contents for the second move command. - ASSERT_TRUE(android::base::WriteStringToFile(block1 + block2 + block3, image_file_)); - RunBlockImageUpdate(true, entries, image_file_, "t"); - ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK)); -} - -class ResumableUpdaterTest : public testing::TestWithParam<size_t> { - protected: - void SetUp() override { - RegisterBuiltins(); - RegisterInstallFunctions(); - RegisterBlockImageFunctions(); - - Paths::Get().set_cache_temp_source(temp_saved_source_.path); - Paths::Get().set_last_command_file(temp_last_command_.path); - Paths::Get().set_stash_directory_base(temp_stash_base_.path); - - // Enable a special command "abort" to simulate interruption. - Command::abort_allowed_ = true; - - index_ = GetParam(); - image_file_ = image_temp_file_.path; - last_command_file_ = temp_last_command_.path; - } - - void TearDown() override { - // Clean up the last_command_file if any. - ASSERT_TRUE(android::base::RemoveFileIfExists(last_command_file_)); - - // Clear partition updated marker if any. - std::string updated_marker{ temp_stash_base_.path }; - updated_marker += "/" + GetSha1(image_temp_file_.path) + ".UPDATED"; - ASSERT_TRUE(android::base::RemoveFileIfExists(updated_marker)); - } - - TemporaryFile temp_saved_source_; - TemporaryDir temp_stash_base_; - std::string last_command_file_; - std::string image_file_; - size_t index_; - - private: - TemporaryFile temp_last_command_; - TemporaryFile image_temp_file_; -}; - -static std::string g_source_image; -static std::string g_target_image; -static PackageEntries g_entries; - -static std::vector<std::string> GenerateTransferList() { - std::string a(4096, 'a'); - std::string b(4096, 'b'); - std::string c(4096, 'c'); - std::string d(4096, 'd'); - std::string e(4096, 'e'); - std::string f(4096, 'f'); - std::string g(4096, 'g'); - std::string h(4096, 'h'); - std::string i(4096, 'i'); - std::string zero(4096, '\0'); - - std::string a_hash = GetSha1(a); - std::string b_hash = GetSha1(b); - std::string c_hash = GetSha1(c); - std::string e_hash = GetSha1(e); - - auto loc = [](const std::string& range_text) { - std::vector<std::string> pieces = android::base::Split(range_text, "-"); - size_t left; - size_t right; - if (pieces.size() == 1) { - CHECK(android::base::ParseUint(pieces[0], &left)); - right = left + 1; - } else { - CHECK_EQ(2u, pieces.size()); - CHECK(android::base::ParseUint(pieces[0], &left)); - CHECK(android::base::ParseUint(pieces[1], &right)); - right++; - } - return android::base::StringPrintf("2,%zu,%zu", left, right); - }; - - // patch 1: "b d c" -> "g" - TemporaryFile patch_file_bdc_g; - std::string bdc = b + d + c; - std::string bdc_hash = GetSha1(bdc); - std::string g_hash = GetSha1(g); - CHECK_EQ(0, bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(bdc.data()), bdc.size(), - reinterpret_cast<const uint8_t*>(g.data()), g.size(), - patch_file_bdc_g.path, nullptr)); - std::string patch_bdc_g; - CHECK(android::base::ReadFileToString(patch_file_bdc_g.path, &patch_bdc_g)); - - // patch 2: "a b c d" -> "d c b" - TemporaryFile patch_file_abcd_dcb; - std::string abcd = a + b + c + d; - std::string abcd_hash = GetSha1(abcd); - std::string dcb = d + c + b; - std::string dcb_hash = GetSha1(dcb); - CHECK_EQ(0, bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(abcd.data()), abcd.size(), - reinterpret_cast<const uint8_t*>(dcb.data()), dcb.size(), - patch_file_abcd_dcb.path, nullptr)); - std::string patch_abcd_dcb; - CHECK(android::base::ReadFileToString(patch_file_abcd_dcb.path, &patch_abcd_dcb)); - - std::vector<std::string> transfer_list{ - "4", - "10", // total blocks written - "2", // maximum stash entries - "2", // maximum number of stashed blocks - - // a b c d e a b c d e - "stash " + b_hash + " " + loc("1"), - // a b c d e a b c d e [b(1)] - "stash " + c_hash + " " + loc("2"), - // a b c d e a b c d e [b(1)][c(2)] - "new " + loc("1-2"), - // a i h d e a b c d e [b(1)][c(2)] - "zero " + loc("0"), - // 0 i h d e a b c d e [b(1)][c(2)] - - // bsdiff "b d c" (from stash, 3, stash) to get g(3) - android::base::StringPrintf( - "bsdiff 0 %zu %s %s %s 3 %s %s %s:%s %s:%s", - patch_bdc_g.size(), // patch start (0), patch length - bdc_hash.c_str(), // source hash - g_hash.c_str(), // target hash - loc("3").c_str(), // target range - loc("3").c_str(), loc("1").c_str(), // load "d" from block 3, into buffer at offset 1 - b_hash.c_str(), loc("0").c_str(), // load "b" from stash, into buffer at offset 0 - c_hash.c_str(), loc("2").c_str()), // load "c" from stash, into buffer at offset 2 - - // 0 i h g e a b c d e [b(1)][c(2)] - "free " + b_hash, - // 0 i h g e a b c d e [c(2)] - "free " + a_hash, - // 0 i h g e a b c d e - "stash " + a_hash + " " + loc("5"), - // 0 i h g e a b c d e [a(5)] - "move " + e_hash + " " + loc("5") + " 1 " + loc("4"), - // 0 i h g e e b c d e [a(5)] - - // bsdiff "a b c d" (from stash, 6-8) to "d c b" (6-8) - android::base::StringPrintf( // - "bsdiff %zu %zu %s %s %s 4 %s %s %s:%s", - patch_bdc_g.size(), // patch start - patch_bdc_g.size() + patch_abcd_dcb.size(), // patch length - abcd_hash.c_str(), // source hash - dcb_hash.c_str(), // target hash - loc("6-8").c_str(), // target range - loc("6-8").c_str(), // load "b c d" from blocks 6-8 - loc("1-3").c_str(), // into buffer at offset 1-3 - a_hash.c_str(), // load "a" from stash - loc("0").c_str()), // into buffer at offset 0 - - // 0 i h g e e d c b e [a(5)] - "new " + loc("4"), - // 0 i h g f e d c b e [a(5)] - "move " + a_hash + " " + loc("9") + " 1 - " + a_hash + ":" + loc("0"), - // 0 i h g f e d c b a [a(5)] - "free " + a_hash, - // 0 i h g f e d c b a - }; - - std::string new_data = i + h + f; - std::string patch_data = patch_bdc_g + patch_abcd_dcb; - - g_entries = { - { "new_data", new_data }, - { "patch_data", patch_data }, - }; - g_source_image = a + b + c + d + e + a + b + c + d + e; - g_target_image = zero + i + h + g + f + e + d + c + b + a; - - return transfer_list; -} - -static const std::vector<std::string> g_transfer_list = GenerateTransferList(); - -INSTANTIATE_TEST_CASE_P(InterruptAfterEachCommand, ResumableUpdaterTest, - ::testing::Range(static_cast<size_t>(0), - g_transfer_list.size() - - TransferList::kTransferListHeaderLines)); - -TEST_P(ResumableUpdaterTest, InterruptVerifyResume) { - ASSERT_TRUE(android::base::WriteStringToFile(g_source_image, image_file_)); - - LOG(INFO) << "Interrupting at line " << index_ << " (" - << g_transfer_list[TransferList::kTransferListHeaderLines + index_] << ")"; - - std::vector<std::string> transfer_list_copy{ g_transfer_list }; - transfer_list_copy[TransferList::kTransferListHeaderLines + index_] = "abort"; - - g_entries["transfer_list"] = android::base::Join(transfer_list_copy, '\n'); - - // Run update that's expected to fail. - RunBlockImageUpdate(false, g_entries, image_file_, ""); - - std::string last_command_expected; - - // Assert the last_command_file. - if (index_ == 0) { - ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK)); - } else { - last_command_expected = std::to_string(index_ - 1) + "\n" + - g_transfer_list[TransferList::kTransferListHeaderLines + index_ - 1]; - std::string last_command_actual; - ASSERT_TRUE(android::base::ReadFileToString(last_command_file_, &last_command_actual)); - ASSERT_EQ(last_command_expected, last_command_actual); - } - - g_entries["transfer_list"] = android::base::Join(g_transfer_list, '\n'); - - // Resume the interrupted update, by doing verification first. - RunBlockImageUpdate(true, g_entries, image_file_, "t"); - - // last_command_file should remain intact. - if (index_ == 0) { - ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK)); - } else { - std::string last_command_actual; - ASSERT_TRUE(android::base::ReadFileToString(last_command_file_, &last_command_actual)); - ASSERT_EQ(last_command_expected, last_command_actual); - } - - // Resume the update. - RunBlockImageUpdate(false, g_entries, image_file_, "t"); - - // last_command_file should be gone after successful update. - ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK)); - - std::string updated_image_actual; - ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated_image_actual)); - ASSERT_EQ(g_target_image, updated_image_actual); -} |