Add an updater function to compute hash tree
The new command is part of the transfer.list and allows us to compute the hash
tree on non-ab devices.
The required arguments for the hash_tree computation are:
hash_tree_ranges
source_ranges
hash_algorithm
salt_hex
root_hash
Bug: 25170618
Test: unit tests pass; run simulator with compute_hash_tree
Change-Id: I8ff0d582cc8adabb8a060db7845f38b35b28e62c
diff --git a/updater/Android.mk b/updater/Android.mk
index ac9aecb..78d0bd4 100644
--- a/updater/Android.mk
+++ b/updater/Android.mk
@@ -34,6 +34,7 @@
libext4_utils \
libfec \
libfec_rs \
+ libverity_tree \
libfs_mgr \
libgtest_prod \
liblog \
diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp
index 2a2ab19..96b2d9f 100644
--- a/updater/blockimg.cpp
+++ b/updater/blockimg.cpp
@@ -49,6 +49,7 @@
#include <fec/io.h>
#include <openssl/sha.h>
#include <private/android_filesystem_config.h>
+#include <verity/hash_tree_builder.h>
#include <ziparchive/zip_archive.h>
#include "edify/expr.h"
@@ -1495,6 +1496,105 @@
return -1;
}
+// Computes the hash_tree bytes based on the parameters, checks if the root hash of the tree
+// matches the expected hash and writes the result to the specified range on the block_device.
+// Hash_tree computation arguments:
+// hash_tree_ranges
+// source_ranges
+// hash_algorithm
+// salt_hex
+// root_hash
+static int PerformCommandComputeHashTree(CommandParameters& params) {
+ if (params.cpos + 5 != params.tokens.size()) {
+ LOG(ERROR) << "Invaild arguments count in hash computation " << params.cmdline;
+ return -1;
+ }
+
+ // Expects the hash_tree data to be contiguous.
+ RangeSet hash_tree_ranges = RangeSet::Parse(params.tokens[params.cpos++]);
+ if (!hash_tree_ranges || hash_tree_ranges.size() != 1) {
+ LOG(ERROR) << "Invalid hash tree ranges in " << params.cmdline;
+ return -1;
+ }
+
+ RangeSet source_ranges = RangeSet::Parse(params.tokens[params.cpos++]);
+ if (!source_ranges) {
+ LOG(ERROR) << "Invalid source ranges in " << params.cmdline;
+ return -1;
+ }
+
+ auto hash_function = HashTreeBuilder::HashFunction(params.tokens[params.cpos++]);
+ if (hash_function == nullptr) {
+ LOG(ERROR) << "Invalid hash algorithm in " << params.cmdline;
+ return -1;
+ }
+
+ std::vector<unsigned char> salt;
+ std::string salt_hex = params.tokens[params.cpos++];
+ if (salt_hex.empty() || !HashTreeBuilder::ParseBytesArrayFromString(salt_hex, &salt)) {
+ LOG(ERROR) << "Failed to parse salt in " << params.cmdline;
+ return -1;
+ }
+
+ std::string expected_root_hash = params.tokens[params.cpos++];
+ if (expected_root_hash.empty()) {
+ LOG(ERROR) << "Invalid root hash in " << params.cmdline;
+ return -1;
+ }
+
+ // Starts the hash_tree computation.
+ HashTreeBuilder builder(BLOCKSIZE, hash_function);
+ if (!builder.Initialize(source_ranges.blocks() * BLOCKSIZE, salt)) {
+ LOG(ERROR) << "Failed to initialize hash tree computation, source " << source_ranges.ToString()
+ << ", salt " << salt_hex;
+ return -1;
+ }
+
+ // Iterates through every block in the source_ranges and updates the hash tree structure
+ // accordingly.
+ for (const auto& range : source_ranges) {
+ uint8_t buffer[BLOCKSIZE];
+ if (!check_lseek(params.fd, static_cast<off64_t>(range.first) * BLOCKSIZE, SEEK_SET)) {
+ PLOG(ERROR) << "Failed to seek to block: " << range.first;
+ return -1;
+ }
+
+ for (size_t i = range.first; i < range.second; i++) {
+ if (read_all(params.fd, buffer, BLOCKSIZE) == -1) {
+ LOG(ERROR) << "Failed to read data in " << range.first << ":" << range.second;
+ return -1;
+ }
+
+ if (!builder.Update(reinterpret_cast<unsigned char*>(buffer), BLOCKSIZE)) {
+ LOG(ERROR) << "Failed to update hash tree builder";
+ return -1;
+ }
+ }
+ }
+
+ if (!builder.BuildHashTree()) {
+ LOG(ERROR) << "Failed to build hash tree";
+ return -1;
+ }
+
+ std::string root_hash_hex = HashTreeBuilder::BytesArrayToString(builder.root_hash());
+ if (root_hash_hex != expected_root_hash) {
+ LOG(ERROR) << "Root hash of the verity hash tree doesn't match the expected value. Expected: "
+ << expected_root_hash << ", actual: " << root_hash_hex;
+ return -1;
+ }
+
+ uint64_t write_offset = static_cast<uint64_t>(hash_tree_ranges.GetBlockNumber(0)) * BLOCKSIZE;
+ if (params.canwrite && !builder.WriteHashTreeToFd(params.fd, write_offset)) {
+ LOG(ERROR) << "Failed to write hash tree to output";
+ return -1;
+ }
+
+ // TODO(xunchang) validates the written bytes
+
+ return 0;
+}
+
using CommandFunction = std::function<int(CommandParameters&)>;
using CommandMap = std::unordered_map<Command::Type, CommandFunction>;
@@ -1737,6 +1837,9 @@
if (performer(params) == -1) {
LOG(ERROR) << "failed to execute command [" << line << "]";
+ if (cmd_type == Command::Type::COMPUTE_HASH_TREE && failure_type == kNoCause) {
+ failure_type = kHashTreeComputationFailure;
+ }
goto pbiudone;
}
@@ -1894,15 +1997,16 @@
// Commands which are not allowed are set to nullptr to skip them completely.
const CommandMap command_map{
// clang-format off
- { Command::Type::ABORT, PerformCommandAbort },
- { Command::Type::BSDIFF, PerformCommandDiff },
- { Command::Type::ERASE, nullptr },
- { Command::Type::FREE, PerformCommandFree },
- { Command::Type::IMGDIFF, PerformCommandDiff },
- { Command::Type::MOVE, PerformCommandMove },
- { Command::Type::NEW, nullptr },
- { Command::Type::STASH, PerformCommandStash },
- { Command::Type::ZERO, nullptr },
+ { Command::Type::ABORT, PerformCommandAbort },
+ { Command::Type::BSDIFF, PerformCommandDiff },
+ { Command::Type::COMPUTE_HASH_TREE, PerformCommandComputeHashTree },
+ { Command::Type::ERASE, nullptr },
+ { Command::Type::FREE, PerformCommandFree },
+ { Command::Type::IMGDIFF, PerformCommandDiff },
+ { Command::Type::MOVE, PerformCommandMove },
+ { Command::Type::NEW, nullptr },
+ { Command::Type::STASH, PerformCommandStash },
+ { Command::Type::ZERO, nullptr },
// clang-format on
};
CHECK_EQ(static_cast<size_t>(Command::Type::LAST), command_map.size());
@@ -1915,15 +2019,16 @@
const std::vector<std::unique_ptr<Expr>>& argv) {
const CommandMap command_map{
// clang-format off
- { Command::Type::ABORT, PerformCommandAbort },
- { Command::Type::BSDIFF, PerformCommandDiff },
- { Command::Type::ERASE, PerformCommandErase },
- { Command::Type::FREE, PerformCommandFree },
- { Command::Type::IMGDIFF, PerformCommandDiff },
- { Command::Type::MOVE, PerformCommandMove },
- { Command::Type::NEW, PerformCommandNew },
- { Command::Type::STASH, PerformCommandStash },
- { Command::Type::ZERO, PerformCommandZero },
+ { Command::Type::ABORT, PerformCommandAbort },
+ { Command::Type::BSDIFF, PerformCommandDiff },
+ { Command::Type::COMPUTE_HASH_TREE, PerformCommandComputeHashTree },
+ { Command::Type::ERASE, PerformCommandErase },
+ { Command::Type::FREE, PerformCommandFree },
+ { Command::Type::IMGDIFF, PerformCommandDiff },
+ { Command::Type::MOVE, PerformCommandMove },
+ { Command::Type::NEW, PerformCommandNew },
+ { Command::Type::STASH, PerformCommandStash },
+ { Command::Type::ZERO, PerformCommandZero },
// clang-format on
};
CHECK_EQ(static_cast<size_t>(Command::Type::LAST), command_map.size());
diff --git a/updater/commands.cpp b/updater/commands.cpp
index e881496..15a787c 100644
--- a/updater/commands.cpp
+++ b/updater/commands.cpp
@@ -40,6 +40,8 @@
return Type::ABORT;
} else if (type_str == "bsdiff") {
return Type::BSDIFF;
+ } else if (type_str == "compute_hash_tree") {
+ return Type::COMPUTE_HASH_TREE;
} else if (type_str == "erase") {
return Type::ERASE;
} else if (type_str == "free") {
@@ -175,6 +177,7 @@
SourceInfo source_info;
StashInfo stash_info;
+ // TODO(xunchang) add the parse code of compute_hash_tree
if (op == Type::ZERO || op == Type::NEW || op == Type::ERASE) {
// zero/new/erase <rangeset>
if (pos + 1 != tokens.size()) {
diff --git a/updater/include/private/commands.h b/updater/include/private/commands.h
index 087d7cf..7f9dc79 100644
--- a/updater/include/private/commands.h
+++ b/updater/include/private/commands.h
@@ -213,6 +213,10 @@
// - Free the given stash data.
// - Meaningful args: StashInfo
//
+// compute_hash_tree <hash_tree_ranges> <source_ranges> <hash_algorithm> <salt_hex> <root_hash>
+// - Computes the hash_tree bytes and writes the result to the specified range on the
+// block_device.
+//
// abort
// - Abort the current update. Allowed for testing code only.
//
@@ -221,6 +225,7 @@
enum class Type {
ABORT,
BSDIFF,
+ COMPUTE_HASH_TREE,
ERASE,
FREE,
IMGDIFF,