uint64_t n = offset / FEC_BLOCKSIZE;
- return !check_block_hash(&f->hashtree().hash[n * SHA256_DIGEST_LENGTH],
- data, f->hashtree().salt);
+ return !f->hashtree().check_block_hash_with_index(n, data);
}
/* check if `offset' is within a block expected to contain zeros */
{
auto hashtree = f->hashtree();
- if (hashtree.hash.empty() || unlikely(offset >= f->data_size)) {
+ if (hashtree.hash_data.empty() || unlikely(offset >= f->data_size)) {
return false;
}
uint64_t hash_offset = (offset / FEC_BLOCKSIZE) * SHA256_DIGEST_LENGTH;
- if (unlikely(hash_offset > hashtree.hash_data_blocks * FEC_BLOCKSIZE -
- SHA256_DIGEST_LENGTH)) {
+ if (unlikely(hash_offset >
+ hashtree.hash_data.size() - SHA256_DIGEST_LENGTH)) {
return false;
}
- return !memcmp(hashtree.zero_hash.data(), &hashtree.hash[hash_offset],
+ return !memcmp(hashtree.zero_hash.data(), &hashtree.hash_data[hash_offset],
SHA256_DIGEST_LENGTH);
}
int neras = 0;
/* verity is required to check for erasures */
- check(!use_erasures || !f->hashtree().hash.empty());
+ check(!use_erasures || !f->hashtree().hash_data.empty());
for (int i = 0; i < e->rsn; ++i) {
uint64_t interleaved = fec_ecc_interleave(rsb * e->rsn + i, e->rsn,
error("RS block %" PRIu64 ": decoding failed (%d erasures)",
rsb, neras);
dump("raw RS block", rsb, copy, FEC_RSM);
- } else if (f->hashtree().hash.empty()) {
+ } else if (f->hashtree().hash_data.empty()) {
warn("RS block %" PRIu64 ": decoding failed", rsb);
} else {
debug("RS block %" PRIu64 ": decoding failed", rsb);
check(dest);
check(offset < f->data_size);
check(offset + count <= f->data_size);
- check(!f->hashtree().hash.empty());
+ check(!f->hashtree().hash_data.empty());
check(errors);
debug("[%" PRIu64 ", %" PRIu64 ")", offset, offset + count);
size_t left = count;
uint8_t data[FEC_BLOCKSIZE];
- uint64_t max_hash_block = (f->hashtree().hash_data_blocks * FEC_BLOCKSIZE -
- SHA256_DIGEST_LENGTH) /
- SHA256_DIGEST_LENGTH;
+ uint64_t max_hash_block =
+ (f->hashtree().hash_data.size() - SHA256_DIGEST_LENGTH) /
+ SHA256_DIGEST_LENGTH;
while (left > 0) {
check(curr <= max_hash_block);
-
- uint8_t *hash = &f->hashtree().hash[curr * SHA256_DIGEST_LENGTH];
uint64_t curr_offset = curr * FEC_BLOCKSIZE;
bool expect_zeros = is_zero(f, curr_offset);
return -1;
}
- if (likely(check_block_hash(hash, data, f->hashtree().salt))) {
+ if (likely(f->hashtree().check_block_hash_with_index(curr, data))) {
goto valid;
}
erasure locations is slower */
if (__ecc_read(f, rs.get(), data, curr_offset, false, ecc_data.get(),
errors) == FEC_BLOCKSIZE &&
- check_block_hash(hash, data, f->hashtree().salt)) {
+ f->hashtree().check_block_hash_with_index(curr, data)) {
goto corrected;
}
/* try to correct with erasures */
if (__ecc_read(f, rs.get(), data, curr_offset, true, ecc_data.get(),
errors) == FEC_BLOCKSIZE &&
- check_block_hash(hash, data, f->hashtree().salt)) {
+ f->hashtree().check_block_hash_with_index(curr, data)) {
goto corrected;
}
return -1;
}
- if (!f->hashtree().hash.empty()) {
+ if (!f->hashtree().hash_data.empty()) {
return process(f, (uint8_t *)buf,
get_max_count(offset, count, f->data_size), offset,
verity_read);
#include <vector>
#include <android-base/strings.h>
+#include <openssl/evp.h>
#include "fec_private.h"
number of hash tree levels in `verity_levels,' and the number of hashes per
level in `level_hashes', if the parameters are non-NULL */
uint64_t verity_get_size(uint64_t file_size, uint32_t *verity_levels,
- uint32_t *level_hashes)
-{
- /* we assume a known metadata size, 4 KiB block size, and SHA-256 to avoid
- relying on disk content */
+ uint32_t *level_hashes, uint32_t padded_digest_size) {
+ // we assume a known metadata size, 4 KiB block size, and SHA-256 or SHA1 to
+ // avoid relying on disk content.
uint32_t level = 0;
uint64_t total = 0;
level_hashes[level] = hashes;
}
- hashes = fec_div_round_up(hashes * SHA256_DIGEST_LENGTH, FEC_BLOCKSIZE);
+ hashes = fec_div_round_up(hashes * padded_digest_size, FEC_BLOCKSIZE);
total += hashes;
++level;
return total * FEC_BLOCKSIZE;
}
-// Computes a SHA-256 salted with 'salt' from a FEC_BLOCKSIZE byte buffer
-// 'block', and copies the hash to 'hash'.
-static inline int get_hash(const uint8_t *block, uint8_t *hash,
- const std::vector<uint8_t> &salt) {
- SHA256_CTX ctx;
- SHA256_Init(&ctx);
+int hashtree_info::get_hash(const uint8_t *block, uint8_t *hash) {
+ auto md = EVP_get_digestbynid(nid_);
+ check(md)
+ auto mdctx = EVP_MD_CTX_new();
+ check(mdctx)
- check(!salt.empty());
- SHA256_Update(&ctx, salt.data(), salt.size());
+ EVP_DigestInit_ex(mdctx, md, nullptr);
+ EVP_DigestUpdate(mdctx, salt.data(), salt.size());
+ EVP_DigestUpdate(mdctx, block, FEC_BLOCKSIZE);
+ unsigned int hash_size;
+ EVP_DigestFinal_ex(mdctx, hash, &hash_size);
+ EVP_MD_CTX_free(mdctx);
- check(block);
- SHA256_Update(&ctx, block, FEC_BLOCKSIZE);
+ check(hash_size == digest_length_)
- check(hash);
- SHA256_Final(hash, &ctx);
+ std::fill(hash + hash_size, hash + padded_digest_length_, 0);
return 0;
}
-/* computes a verity hash for FEC_BLOCKSIZE bytes from buffer `block' and
- compares it to the expected value in `expected' */
-bool check_block_hash(const uint8_t *expected, const uint8_t *block,
- const std::vector<uint8_t> &salt) {
- check(block);
+int hashtree_info::initialize(uint64_t hash_start, uint64_t data_blocks,
+ const std::vector<uint8_t> &salt, int nid) {
+ check(nid == NID_sha256 || nid == NID_sha1);
+
+ this->hash_start = hash_start;
+ this->data_blocks = data_blocks;
+ this->salt = salt;
+ this->nid_ = nid;
- uint8_t hash[SHA256_DIGEST_LENGTH];
+ digest_length_ = nid == NID_sha1 ? SHA_DIGEST_LENGTH : SHA256_DIGEST_LENGTH;
+ // The padded digest size for both sha256 and sha1 are 256 bytes.
+ padded_digest_length_ = SHA256_DIGEST_LENGTH;
- if (unlikely(get_hash(block, hash, salt) == -1)) {
+ return 0;
+}
+
+bool hashtree_info::check_block_hash(const uint8_t *expected,
+ const uint8_t *block) {
+ check(block);
+ std::vector<uint8_t> hash(digest_length_, 0);
+
+ if (unlikely(get_hash(block, hash.data()) == -1)) {
error("failed to hash");
return false;
}
check(expected);
- return !memcmp(expected, hash, SHA256_DIGEST_LENGTH);
+ return !memcmp(expected, hash.data(), digest_length_);
}
-/* reads a verity hash and the corresponding data block using error correction,
- if available */
-static bool ecc_read_hashes(fec_handle *f, uint64_t hash_offset,
- uint8_t *hash, uint64_t data_offset, uint8_t *data)
-{
+bool hashtree_info::check_block_hash_with_index(uint64_t index,
+ const uint8_t *block) {
+ check(index < data_blocks)
+
+ const uint8_t *expected = &hash_data[index * padded_digest_length_];
+ return check_block_hash(expected, block);
+}
+
+// Reads the hash and the corresponding data block using error correction, if
+// available.
+bool hashtree_info::ecc_read_hashes(fec_handle *f, uint64_t hash_offset,
+ uint8_t *hash, uint64_t data_offset,
+ uint8_t *data) {
check(f);
- if (hash && fec_pread(f, hash, SHA256_DIGEST_LENGTH, hash_offset) !=
- SHA256_DIGEST_LENGTH) {
+ if (hash &&
+ fec_pread(f, hash, digest_length_, hash_offset) != digest_length_) {
error("failed to read hash tree: offset %" PRIu64 ": %s", hash_offset,
- strerror(errno));
+ strerror(errno));
return false;
}
if (fec_pread(f, data, FEC_BLOCKSIZE, data_offset) != FEC_BLOCKSIZE) {
error("failed to read hash tree: data_offset %" PRIu64 ": %s",
- data_offset, strerror(errno));
+ data_offset, strerror(errno));
return false;
}
return true;
}
-/* reads the verity hash tree, validates it against the root hash in `root',
- corrects errors if necessary, and copies valid data blocks for later use
- to `f->verity.hash' */
-static int verify_tree(hashtree_info *hashtree, const fec_handle *f,
- const uint8_t *root) {
- uint8_t data[FEC_BLOCKSIZE];
- uint8_t hash[SHA256_DIGEST_LENGTH];
-
- check(hashtree);
+int hashtree_info::verify_tree(const fec_handle *f, const uint8_t *root) {
check(f);
check(root);
+ uint8_t data[FEC_BLOCKSIZE];
+
uint32_t levels = 0;
/* calculate the size and the number of levels in the hash tree */
- hashtree->hash_size =
- verity_get_size(hashtree->data_blocks * FEC_BLOCKSIZE, &levels, NULL);
+ uint64_t hash_size = verity_get_size(data_blocks * FEC_BLOCKSIZE, &levels,
+ NULL, padded_digest_length_);
- check(hashtree->hash_start < UINT64_MAX - hashtree->hash_size);
- check(hashtree->hash_start + hashtree->hash_size <= f->data_size);
+ check(hash_start < UINT64_MAX - hash_size);
+ check(hash_start + hash_size <= f->data_size);
- uint64_t hash_offset = hashtree->hash_start;
+ uint64_t hash_offset = hash_start;
uint64_t data_offset = hash_offset + FEC_BLOCKSIZE;
- hashtree->hash_data_offset = data_offset;
-
/* validate the root hash */
if (!raw_pread(f->fd, data, FEC_BLOCKSIZE, hash_offset) ||
- !check_block_hash(root, data, hashtree->salt)) {
+ !check_block_hash(root, data)) {
/* try to correct */
- if (!ecc_read_hashes(const_cast<fec_handle *>(f), 0, NULL, hash_offset,
- data) ||
- !check_block_hash(root, data, hashtree->salt)) {
+ if (!ecc_read_hashes(const_cast<fec_handle *>(f), 0, nullptr,
+ hash_offset, data) ||
+ !check_block_hash(root, data)) {
error("root hash invalid");
return -1;
} else if (f->mode & O_RDWR &&
/* calculate the number of hashes on each level */
uint32_t hashes[levels];
- verity_get_size(hashtree->data_blocks * FEC_BLOCKSIZE, NULL, hashes);
+ verity_get_size(data_blocks * FEC_BLOCKSIZE, NULL, hashes,
+ padded_digest_length_);
+ uint64_t hash_data_offset = data_offset;
+ uint32_t hash_data_blocks = 0;
/* calculate the size and offset for the data hashes */
for (uint32_t i = 1; i < levels; ++i) {
uint32_t blocks = hashes[levels - i];
debug("%u hash blocks on level %u", blocks, levels - i);
- hashtree->hash_data_offset = data_offset;
- hashtree->hash_data_blocks = blocks;
+ hash_data_offset = data_offset;
+ hash_data_blocks = blocks;
data_offset += blocks * FEC_BLOCKSIZE;
}
- check(hashtree->hash_data_blocks);
- check(hashtree->hash_data_blocks <= hashtree->hash_size / FEC_BLOCKSIZE);
+ check(hash_data_blocks);
+ check(hash_data_blocks <= hash_size / FEC_BLOCKSIZE);
- check(hashtree->hash_data_offset);
- check(hashtree->hash_data_offset <=
- UINT64_MAX - (hashtree->hash_data_blocks * FEC_BLOCKSIZE));
- check(hashtree->hash_data_offset < f->data_size);
- check(hashtree->hash_data_offset +
- hashtree->hash_data_blocks * FEC_BLOCKSIZE <=
- f->data_size);
+ check(hash_data_offset);
+ check(hash_data_offset <= UINT64_MAX - (hash_data_blocks * FEC_BLOCKSIZE));
+ check(hash_data_offset < f->data_size);
+ check(hash_data_offset + hash_data_blocks * FEC_BLOCKSIZE <= f->data_size);
/* copy data hashes to memory in case they are corrupted, so we don't
have to correct them every time they are needed */
- std::vector<uint8_t> data_hashes(hashtree->hash_data_blocks * FEC_BLOCKSIZE,
- 0);
+ std::vector<uint8_t> data_hashes(hash_data_blocks * FEC_BLOCKSIZE, 0);
/* validate the rest of the hash tree */
data_offset = hash_offset + FEC_BLOCKSIZE;
+ std::vector<uint8_t> buffer(padded_digest_length_, 0);
for (uint32_t i = 1; i < levels; ++i) {
uint32_t blocks = hashes[levels - i];
for (uint32_t j = 0; j < blocks; ++j) {
/* ecc reads are very I/O intensive, so read raw hash tree and do
error correcting only if it doesn't validate */
- if (!raw_pread(f->fd, hash, SHA256_DIGEST_LENGTH,
- hash_offset + j * SHA256_DIGEST_LENGTH) ||
+ if (!raw_pread(f->fd, buffer.data(), padded_digest_length_,
+ hash_offset + j * padded_digest_length_) ||
!raw_pread(f->fd, data, FEC_BLOCKSIZE,
data_offset + j * FEC_BLOCKSIZE)) {
error("failed to read hashes: %s", strerror(errno));
return -1;
}
- if (!check_block_hash(hash, data, hashtree->salt)) {
+ if (!check_block_hash(buffer.data(), data)) {
/* try to correct */
if (!ecc_read_hashes(const_cast<fec_handle *>(f),
- hash_offset + j * SHA256_DIGEST_LENGTH,
- hash, data_offset + j * FEC_BLOCKSIZE,
- data) ||
- !check_block_hash(hash, data, hashtree->salt)) {
+ hash_offset + j * padded_digest_length_,
+ buffer.data(),
+ data_offset + j * FEC_BLOCKSIZE, data) ||
+ !check_block_hash(buffer.data(), data)) {
error("invalid hash tree: hash_offset %" PRIu64
", "
"data_offset %" PRIu64 ", block %u",
/* update the corrected blocks to the file if we are in r/w
mode */
if (f->mode & O_RDWR) {
- if (!raw_pwrite(f->fd, hash, SHA256_DIGEST_LENGTH,
- hash_offset + j * SHA256_DIGEST_LENGTH) ||
+ if (!raw_pwrite(f->fd, buffer.data(), padded_digest_length_,
+ hash_offset + j * padded_digest_length_) ||
!raw_pwrite(f->fd, data, FEC_BLOCKSIZE,
data_offset + j * FEC_BLOCKSIZE)) {
error("failed to write hashes: %s", strerror(errno));
}
}
- if (blocks == hashtree->hash_data_blocks) {
+ if (blocks == hash_data_blocks) {
std::copy(data, data + FEC_BLOCKSIZE,
data_hashes.begin() + j * FEC_BLOCKSIZE);
}
debug("valid");
- hashtree->hash = std::move(data_hashes);
+ this->hash_data = std::move(data_hashes);
+
+ std::vector<uint8_t> zero_block(FEC_BLOCKSIZE, 0);
+ zero_hash.resize(padded_digest_length_, 0);
+ if (get_hash(zero_block.data(), zero_hash.data()) == -1) {
+ error("failed to hash");
+ return -1;
+ }
return 0;
}
debug("offset = %" PRIu64 ", size = %u", offset, size);
- verity_info *v = &f->verity;
std::string table(size, 0);
if (!useecc) {
int i = 0;
std::vector<uint8_t> salt;
uint8_t root[SHA256_DIGEST_LENGTH];
+ uint64_t hash_start = 0;
+ uint64_t data_blocks = 0;
auto tokens = android::base::Split(table, " ");
break;
case 5: /* num_data_blocks */
if (parse_uint64(token.c_str(), f->data_size / FEC_BLOCKSIZE,
- &v->hashtree.data_blocks) == -1) {
+ &data_blocks) == -1) {
error("invalid number of verity data blocks: %s",
token.c_str());
return -1;
break;
case 6: /* hash_start_block */
if (parse_uint64(token.c_str(), f->data_size / FEC_BLOCKSIZE,
- &v->hashtree.hash_start) == -1) {
+ &hash_start) == -1) {
error("invalid verity hash start block: %s", token.c_str());
return -1;
}
- v->hashtree.hash_start *= FEC_BLOCKSIZE;
+ hash_start *= FEC_BLOCKSIZE;
break;
case 7: /* algorithm */
if (token != "sha256") {
return -1;
}
- check(v->hashtree.hash_start < f->data_size);
+ check(hash_start < f->data_size);
- if (v->metadata_start < v->hashtree.hash_start) {
- check(v->hashtree.data_blocks == v->metadata_start / FEC_BLOCKSIZE);
+ verity_info *v = &f->verity;
+ if (v->metadata_start < hash_start) {
+ check(data_blocks == v->metadata_start / FEC_BLOCKSIZE);
} else {
- check(v->hashtree.data_blocks ==
- v->hashtree.hash_start / FEC_BLOCKSIZE);
+ check(data_blocks == hash_start / FEC_BLOCKSIZE);
}
- v->hashtree.salt = std::move(salt);
v->table = std::move(table);
+ v->hashtree.initialize(hash_start, data_blocks, salt, NID_sha256);
if (!(f->flags & FEC_VERITY_DISABLE)) {
- if (verify_tree(&v->hashtree, f, root) == -1) {
+ if (v->hashtree.verify_tree(f, root) == -1) {
return -1;
}
- check(!v->hashtree.hash.empty());
-
- std::vector<uint8_t> zero_block(FEC_BLOCKSIZE, 0);
- v->hashtree.zero_hash.assign(SHA256_DIGEST_LENGTH, 0);
- if (get_hash(zero_block.data(), v->hashtree.zero_hash.data(),
- v->hashtree.salt) == -1) {
- error("failed to hash");
- return -1;
- }
+ check(!v->hashtree.hash_data.empty());
+ check(!v->hashtree.zero_hash.empty());
}
return 0;
std::vector<uint8_t> tmp_vec(4096, i);
image_.insert(image_.end(), tmp_vec.begin(), tmp_vec.end());
}
-
+ }
+ void BuildHashtree(const std::string &hash_name) {
// Build the hashtree.
- HashTreeBuilder builder(4096, HashTreeBuilder::HashFunction("sha256"));
+ HashTreeBuilder builder(4096, HashTreeBuilder::HashFunction(hash_name));
// Use a random salt.
salt_ = std::vector<uint8_t>(64, 10);
ASSERT_TRUE(builder.Initialize(image_.size(), salt_));
ASSERT_TRUE(builder.BuildHashTree());
root_hash_ = builder.root_hash();
- // Append the hashtree to the end of image.
TemporaryFile temp_file;
ASSERT_TRUE(builder.WriteHashTreeToFd(temp_file.fd, 0));
android::base::ReadFileToString(temp_file.path, &hashtree_content_);
- image_.insert(image_.end(), hashtree_content_.begin(),
- hashtree_content_.end());
}
// Builds the verity metadata and appends the bytes to the image.
void BuildAndAppendsVerityMetadata() {
+ BuildHashtree("sha256");
+ // Append the hashtree to the end of image.
+ image_.insert(image_.end(), hashtree_content_.begin(),
+ hashtree_content_.end());
+
// The metadata table has the format: "1 block_device, block_device,
// BLOCK_SIZE, BLOCK_SIZE, data_blocks, data_blocks, 'sha256',
// root_hash, salt".
// the fec hashtree only stores the hash of the lowest level.
ASSERT_EQ(std::vector<uint8_t>(hashtree_content_.begin() + 4096,
hashtree_content_.end()),
- handle->hashtree().hash);
- ASSERT_EQ(hashtree_content_.size(), handle->hashtree().hash_size);
+ handle->hashtree().hash_data);
+
+ uint64_t hash_size =
+ verity_get_size(handle->hashtree().data_blocks * FEC_BLOCKSIZE, nullptr,
+ nullptr, SHA256_DIGEST_LENGTH);
+ ASSERT_EQ(hashtree_content_.size(), hash_size);
}
TEST_F(FecUnitTest, LoadVerityImage_ParseEcc) {
// 256 (data) + 3 (hashtree) + 8 (verity meta)
ASSERT_EQ(267, ecc_metadata.blocks);
}
+
+TEST_F(FecUnitTest, VerityImage_FecRead) {
+ TemporaryFile verity_image;
+ BuildAndAppendsVerityMetadata();
+ ASSERT_TRUE(android::base::WriteFully(verity_image.fd, image_.data(),
+ image_.size()));
+ TemporaryFile ecc_image;
+ BuildAndAppendsEccImage(verity_image.path, ecc_image.path);
+ std::string ecc_content;
+ ASSERT_TRUE(android::base::ReadFileToString(ecc_image.path, &ecc_content));
+ ASSERT_TRUE(android::base::WriteStringToFd(ecc_content, verity_image.fd));
+
+ // Corrupt the last block
+ uint64_t corrupt_offset = 4096 * 255;
+ ASSERT_EQ(corrupt_offset, lseek64(verity_image.fd, corrupt_offset, 0));
+ std::vector<uint8_t> corruption(100, 10);
+ ASSERT_TRUE(android::base::WriteFully(verity_image.fd, corruption.data(),
+ corruption.size()));
+
+ std::vector<uint8_t> read_data(1024, 0);
+ struct fec_handle *handle = nullptr;
+ ASSERT_EQ(0,
+ fec_open(&handle, verity_image.path, O_RDONLY, FEC_FS_EXT4, 2));
+ std::unique_ptr<fec_handle> guard(handle);
+
+ ASSERT_EQ(1024, fec_pread(handle, read_data.data(), 1024, corrupt_offset));
+ ASSERT_EQ(std::vector<uint8_t>(1024, 255), read_data);
+}