| // Copyright 2010 The ChromiumOS Authors |
| // Use of this source code is governed by the GPL v2 license that can |
| // be found in the LICENSE file. |
| // |
| // Implementation of FileHasher |
| |
| #define __STDC_LIMIT_MACROS 1 |
| #define __STDC_FORMAT_MACROS 1 |
| |
| #include <linux/fs.h> |
| #include <stdint.h> |
| #include <sys/ioctl.h> |
| #include <sys/stat.h> |
| |
| #include <string> |
| |
| #include <base/bits.h> |
| #include <base/check.h> |
| #include <base/files/file.h> |
| #include <base/logging.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_util.h> |
| |
| #include "verity/dm-bht.h" |
| #include "verity/dm_verity_table.h" |
| #include "verity/file_hasher.h" |
| |
| namespace verity { |
| |
| using std::string; |
| |
| namespace { |
| // |base::File| doesn't have a good way of getting block device's size. So we |
| // have to do linux trickery here. |
| int64_t GetFileSize(base::File* file) { |
| struct stat statbuf; |
| int rc = fstat(file->GetPlatformFile(), &statbuf); |
| PLOG_IF(FATAL, rc != 0) << "Failed to get file status"; |
| if (S_ISBLK(statbuf.st_mode)) { |
| int64_t size = 0; |
| rc = ioctl(file->GetPlatformFile(), BLKGETSIZE64, &size); |
| PLOG_IF(FATAL, rc != 0) << "Failed to get block size of input device"; |
| return size; |
| } |
| return file->GetLength(); |
| } |
| } // namespace |
| |
| FileHasher::~FileHasher() { |
| if (initialized_) |
| dm_bht_destroy(&tree_); |
| } |
| |
| bool FileHasher::Initialize() { |
| CHECK(!initialized_); |
| |
| if (!alg_ || !source_ || !destination_) { |
| LOG(ERROR) << "Invalid arguments supplied to ctor."; |
| LOG(INFO) << "s: " << source_ << " d: " << destination_; |
| return false; |
| } |
| int64_t source_size = GetFileSize(source_.get()); |
| if (source_size < 0) { |
| PLOG(ERROR) << "Failed to get the file size"; |
| return false; |
| } |
| if (block_limit_ > source_size / PAGE_SIZE) { |
| LOG(ERROR) << block_limit_ << " blocks exceeds image size of " |
| << source_size; |
| return false; |
| } else if (block_limit_ == 0) { |
| block_limit_ = source_size / PAGE_SIZE; |
| if (source_size % PAGE_SIZE) { |
| LOG(ERROR) << "The source file size must be divisible by the block size, " |
| << "Size: " << source_size; |
| LOG(INFO) << "Suggested size: " |
| << base::bits::AlignUp(static_cast<uint64_t>(source_size), |
| uint64_t{PAGE_SIZE}); |
| return false; |
| } |
| } |
| |
| // Now we initialize the tree |
| if (dm_bht_create(&tree_, block_limit_, alg_)) { |
| LOG(ERROR) << "Could not create the BH tree"; |
| return false; |
| } |
| |
| sectors_ = dm_bht_sectors(&tree_); |
| hash_data_.resize(verity_to_bytes(sectors_)); |
| |
| // No reading is needed. |
| dm_bht_set_read_cb(&tree_, dm_bht_zeroread_callback); |
| dm_bht_set_buffer(&tree_, hash_data_.data()); |
| initialized_ = true; |
| return true; |
| } |
| |
| bool FileHasher::Store() { |
| return destination_->WriteAtCurrentPos(hash_data_.data(), |
| hash_data_.size()) >= 0; |
| } |
| |
| bool FileHasher::Hash() { |
| // TODO(wad) abstract size when dm-bht needs to do break from PAGE_SIZE |
| uint8_t block_data[PAGE_SIZE]; |
| uint32_t block = 0; |
| |
| while (block < block_limit_) { |
| if (source_->ReadAtCurrentPos(reinterpret_cast<char*>(block_data), |
| PAGE_SIZE) < 0) { |
| PLOG(ERROR) << "Failed to read for block: " << block; |
| return false; |
| } |
| if (dm_bht_store_block(&tree_, block, block_data)) { |
| LOG(ERROR) << "Failed to store block " << block; |
| return false; |
| } |
| ++block; |
| } |
| return !dm_bht_compute(&tree_); |
| } |
| |
| void FileHasher::set_salt(const char* salt) { |
| if (!strcmp(salt, "random")) |
| salt = RandomSalt(); |
| dm_bht_set_salt(&tree_, salt); |
| salt_ = salt; |
| } |
| |
| const char* FileHasher::RandomSalt() { |
| char buf[DM_BHT_SALT_SIZE]; |
| base::FilePath urandom_path("/dev/urandom"); |
| base::File source(urandom_path, |
| base::File::FLAG_OPEN | base::File::FLAG_READ); |
| |
| LOG_IF(FATAL, !source.IsValid()) |
| << "Failed to open the random source: " << urandom_path; |
| PLOG_IF(FATAL, source.ReadAtCurrentPos(buf, sizeof(buf)) < 0) |
| << "Failed to read the random source"; |
| |
| for (size_t i = 0; i < sizeof(buf); ++i) { |
| // NOLINTNEXTLINE(runtime/printf) |
| sprintf(&random_salt_[i * 2], "%02x", buf[i]); |
| } |
| random_salt_[sizeof(random_salt_) - 1] = '\0'; |
| |
| return random_salt_; |
| } |
| |
| std::string FileHasher::GetTable(const PrintArgs& args) { |
| return GetRawTable(args.colocated ? DmVerityTable::HashPlacement::COLOCATED |
| : DmVerityTable::HashPlacement::SEPARATE) |
| .Print(args.vanilla ? DmVerityTable::Format::VANILLA |
| : DmVerityTable::Format::CROS) |
| .value_or(""); |
| } |
| |
| DmVerityTable FileHasher::GetRawTable( |
| const DmVerityTable::HashPlacement& hash_placement) { |
| // Grab the digest (up to 1kbit supported) |
| uint8_t digest[128] = {0}; |
| char hexsalt[DM_BHT_SALT_SIZE * 2 + 1] = {'\0'}; |
| bool have_salt; |
| |
| dm_bht_root_hexdigest(&tree_, digest, sizeof(digest)); |
| have_salt = dm_bht_salt(&tree_, hexsalt) == 0; |
| |
| return DmVerityTable( |
| alg_, std::to_array(digest), |
| have_salt ? std::make_optional(std::to_array(hexsalt)) : std::nullopt, |
| /*data_dev=*/ |
| DmVerityTable::DevInfo{ |
| .dev = "ROOT_DEV", |
| .block_size = PAGE_SIZE, |
| .block_count = block_limit_, |
| }, |
| /*hash_dev=*/ |
| DmVerityTable::DevInfo{ |
| .dev = "HASH_DEV", |
| .block_size = PAGE_SIZE, |
| .block_count = |
| 0, // This value doesn't really matter for hash device. |
| }, |
| hash_placement); |
| } |
| |
| void FileHasher::PrintTable(const PrintArgs& args) { |
| printf("%s\n", GetTable(args).c_str()); |
| } |
| |
| } // namespace verity |