diff --git a/EncryptInplace.cpp b/EncryptInplace.cpp index bdb2da7..8baed78 100644 --- a/EncryptInplace.cpp +++ b/EncryptInplace.cpp @@ -20,614 +20,356 @@ #include #include #include -#include -#include -#include -#include -#include #include #include +#include #include #include +#include -// HORRIBLE HACK, FIXME -#include "cryptfs.h" - -// FIXME horrible cut-and-paste code -static inline int unix_read(int fd, void* buff, int len) { - return TEMP_FAILURE_RETRY(read(fd, buff, len)); -} - -static inline int unix_write(int fd, const void* buff, int len) { - return TEMP_FAILURE_RETRY(write(fd, buff, len)); -} - -#define CRYPT_SECTORS_PER_BUFSIZE (CRYPT_INPLACE_BUFSIZE / CRYPT_SECTOR_SIZE) - -/* aligned 32K writes tends to make flash happy. - * SD card association recommends it. - */ -#define BLOCKS_AT_A_TIME 8 - -struct encryptGroupsData { - int realfd; - int cryptofd; - off64_t numblocks; - off64_t one_pct, cur_pct, new_pct; - off64_t blocks_already_done, tot_numblocks; - off64_t used_blocks_already_done, tot_used_blocks; - const char* real_blkdev; - const char* crypto_blkdev; - int count; - off64_t offset; - char* buffer; - off64_t last_written_sector; - int completed; - time_t time_started; - int remaining_time; - bool set_progress_properties; +enum EncryptInPlaceError { + kSuccess, + kFailed, + kFilesystemNotFound, }; -static void update_progress(struct encryptGroupsData* data, int is_used) { - data->blocks_already_done++; +static uint64_t round_up(uint64_t val, size_t amount) { + if (val % amount) val += amount - (val % amount); + return val; +} - if (is_used) { - data->used_blocks_already_done++; - } - if (data->tot_used_blocks) { - data->new_pct = data->used_blocks_already_done / data->one_pct; +class InPlaceEncrypter { + public: + bool EncryptInPlace(const std::string& crypto_blkdev, const std::string& real_blkdev, + uint64_t nr_sec, bool set_progress_properties); + bool ProcessUsedBlock(uint64_t block_num); + + private: + // aligned 32K writes tends to make flash happy. + // SD card association recommends it. + static const size_t kIOBufferSize = 32768; + + // Avoid spamming the logs. Print the "Encrypting blocks" log message once + // every 10000 blocks (which is usually every 40 MB or so), and once at the end. + static const int kLogInterval = 10000; + + std::string DescribeFilesystem(); + void InitFs(const std::string& fs_type, uint64_t blocks_to_encrypt, uint64_t total_blocks, + unsigned int block_size); + void UpdateProgress(size_t blocks, bool done); + bool EncryptPendingData(); + bool DoEncryptInPlace(); + + // ext4 methods + bool ReadExt4BlockBitmap(uint32_t group, uint8_t* buf); + uint64_t FirstBlockInGroup(uint32_t group); + uint32_t NumBlocksInGroup(uint32_t group); + uint32_t NumBaseMetaBlocksInGroup(uint64_t group); + EncryptInPlaceError EncryptInPlaceExt4(); + + // f2fs methods + EncryptInPlaceError EncryptInPlaceF2fs(); + + std::string real_blkdev_; + std::string crypto_blkdev_; + uint64_t nr_sec_; + bool set_progress_properties_; + + android::base::unique_fd realfd_; + android::base::unique_fd cryptofd_; + + time_t time_started_; + int remaining_time_; + + std::string fs_type_; + uint64_t blocks_done_; + uint64_t blocks_to_encrypt_; + unsigned int block_size_; + unsigned int cur_pct_; + + std::vector io_buffer_; + uint64_t first_pending_block_; + size_t blocks_pending_; +}; + +std::string InPlaceEncrypter::DescribeFilesystem() { + if (fs_type_.empty()) + return "full block device " + real_blkdev_; + else + return fs_type_ + " filesystem on " + real_blkdev_; +} + +// Finishes initializing the encrypter, now that the filesystem details are known. +void InPlaceEncrypter::InitFs(const std::string& fs_type, uint64_t blocks_to_encrypt, + uint64_t total_blocks, unsigned int block_size) { + fs_type_ = fs_type; + blocks_done_ = 0; + blocks_to_encrypt_ = blocks_to_encrypt; + block_size_ = block_size; + cur_pct_ = 0; + + // Allocate the I/O buffer. kIOBufferSize should always be a multiple of + // the filesystem block size, but round it up just in case. + io_buffer_.resize(round_up(kIOBufferSize, block_size)); + first_pending_block_ = 0; + blocks_pending_ = 0; + + LOG(INFO) << "Encrypting " << DescribeFilesystem() << " in-place via " << crypto_blkdev_; + LOG(INFO) << blocks_to_encrypt << " blocks (" << (blocks_to_encrypt * block_size) / 1000000 + << " MB) of " << total_blocks << " blocks are in-use"; +} + +void InPlaceEncrypter::UpdateProgress(size_t blocks, bool done) { + // A log message already got printed for blocks_done_ if one was due, so the + // next message will be due at the *next* block rounded up to kLogInterval. + uint64_t blocks_next_msg = round_up(blocks_done_ + 1, kLogInterval); + + blocks_done_ += blocks; + + // Ensure that a log message gets printed at the end, but not if one was + // already printed due to the block count being a multiple of kLogInterval. + // E.g. we want to show "50000 of 50327" and then "50327 of "50327", but not + // "50000 of 50000" and then redundantly "50000 of 50000" again. + if (done && blocks_done_ % kLogInterval != 0) blocks_next_msg = blocks_done_; + + if (blocks_done_ >= blocks_next_msg) + LOG(DEBUG) << "Encrypted " << blocks_next_msg << " of " << blocks_to_encrypt_ << " blocks"; + + if (!set_progress_properties_) return; + + uint64_t new_pct; + if (done) { + new_pct = 100; } else { - data->new_pct = data->blocks_already_done / data->one_pct; + new_pct = (blocks_done_ * 100) / std::max(blocks_to_encrypt_, 1); + new_pct = std::min(new_pct, 99); + } + if (new_pct > cur_pct_) { + cur_pct_ = new_pct; + android::base::SetProperty("vold.encrypt_progress", std::to_string(new_pct)); } - if (!data->set_progress_properties) return; - - if (data->new_pct > data->cur_pct) { - char buf[8]; - data->cur_pct = data->new_pct; - snprintf(buf, sizeof(buf), "%" PRId64, data->cur_pct); - android::base::SetProperty("vold.encrypt_progress", buf); - } - - if (data->cur_pct >= 5) { + if (cur_pct_ >= 5) { struct timespec time_now; if (clock_gettime(CLOCK_MONOTONIC, &time_now)) { - LOG(WARNING) << "Error getting time"; + PLOG(WARNING) << "Error getting time while updating encryption progress"; } else { - double elapsed_time = difftime(time_now.tv_sec, data->time_started); - off64_t remaining_blocks = data->tot_used_blocks - data->used_blocks_already_done; - int remaining_time = - (int)(elapsed_time * remaining_blocks / data->used_blocks_already_done); + double elapsed_time = difftime(time_now.tv_sec, time_started_); + + uint64_t remaining_blocks = 0; + if (blocks_done_ < blocks_to_encrypt_) + remaining_blocks = blocks_to_encrypt_ - blocks_done_; + + int remaining_time = 0; + if (blocks_done_ != 0) + remaining_time = (int)(elapsed_time * remaining_blocks / blocks_done_); // Change time only if not yet set, lower, or a lot higher for // best user experience - if (data->remaining_time == -1 || remaining_time < data->remaining_time || - remaining_time > data->remaining_time + 60) { - char buf[8]; - snprintf(buf, sizeof(buf), "%d", remaining_time); - android::base::SetProperty("vold.encrypt_time_remaining", buf); - data->remaining_time = remaining_time; + if (remaining_time_ == -1 || remaining_time < remaining_time_ || + remaining_time > remaining_time_ + 60) { + remaining_time_ = remaining_time; + android::base::SetProperty("vold.encrypt_time_remaining", + std::to_string(remaining_time)); } } } } -static void log_progress(struct encryptGroupsData const* data, bool completed) { - // Precondition - if completed data = 0 else data != 0 +bool InPlaceEncrypter::EncryptPendingData() { + if (blocks_pending_ == 0) return true; - // Track progress so we can skip logging blocks - static off64_t offset = -1; + ssize_t bytes = blocks_pending_ * block_size_; + uint64_t offset = first_pending_block_ * block_size_; - // Need to close existing 'Encrypting from' log? - if (completed || (offset != -1 && data->offset != offset)) { - LOG(INFO) << "Encrypted to sector " << offset / info.block_size * CRYPT_SECTOR_SIZE; - offset = -1; + if (pread64(realfd_, &io_buffer_[0], bytes, offset) != bytes) { + PLOG(ERROR) << "Error reading real_blkdev " << real_blkdev_ << " for inplace encrypt"; + return false; } - // Need to start new 'Encrypting from' log? - if (!completed && offset != data->offset) { - LOG(INFO) << "Encrypting from sector " << data->offset / info.block_size * CRYPT_SECTOR_SIZE; + if (pwrite64(cryptofd_, &io_buffer_[0], bytes, offset) != bytes) { + PLOG(ERROR) << "Error writing crypto_blkdev " << crypto_blkdev_ << " for inplace encrypt"; + return false; } - // Update offset - if (!completed) { - offset = data->offset + (off64_t)data->count * info.block_size; - } + UpdateProgress(blocks_pending_, false); + + blocks_pending_ = 0; + return true; } -static int flush_outstanding_data(struct encryptGroupsData* data) { - if (data->count == 0) { - return 0; +bool InPlaceEncrypter::ProcessUsedBlock(uint64_t block_num) { + // Flush if the amount of pending data has reached the I/O buffer size, if + // there's a gap between the pending blocks and the next block (due to + // block(s) not being used by the filesystem and thus not needing + // encryption), or if the next block will be aligned to the I/O buffer size. + if (blocks_pending_ * block_size_ == io_buffer_.size() || + block_num != first_pending_block_ + blocks_pending_ || + (block_num * block_size_) % io_buffer_.size() == 0) { + if (!EncryptPendingData()) return false; + first_pending_block_ = block_num; + } + blocks_pending_++; + return true; +} + +// Reads the block bitmap for block group |group| into |buf|. +bool InPlaceEncrypter::ReadExt4BlockBitmap(uint32_t group, uint8_t* buf) { + uint64_t offset = (uint64_t)aux_info.bg_desc[group].bg_block_bitmap * info.block_size; + if (pread64(realfd_, buf, info.block_size, offset) != (ssize_t)info.block_size) { + PLOG(ERROR) << "Failed to read block bitmap for block group " << group; + return false; + } + return true; +} + +uint64_t InPlaceEncrypter::FirstBlockInGroup(uint32_t group) { + return aux_info.first_data_block + (group * (uint64_t)info.blocks_per_group); +} + +uint32_t InPlaceEncrypter::NumBlocksInGroup(uint32_t group) { + uint64_t remaining = aux_info.len_blocks - FirstBlockInGroup(group); + return std::min(info.blocks_per_group, remaining); +} + +// In block groups with an uninitialized block bitmap, we only need to encrypt +// the backup superblock and the block group descriptors (if they are present). +uint32_t InPlaceEncrypter::NumBaseMetaBlocksInGroup(uint64_t group) { + if (!ext4_bg_has_super_block(group)) return 0; + return 1 + aux_info.bg_desc_blocks; +} + +EncryptInPlaceError InPlaceEncrypter::EncryptInPlaceExt4() { + if (setjmp(setjmp_env)) // NOLINT + return kFilesystemNotFound; + + if (read_ext(realfd_, 0) != 0) return kFilesystemNotFound; + + LOG(DEBUG) << "ext4 filesystem has " << aux_info.groups << " block groups"; + + uint64_t blocks_to_encrypt = 0; + for (uint32_t group = 0; group < aux_info.groups; group++) { + if (aux_info.bg_desc[group].bg_flags & EXT4_BG_BLOCK_UNINIT) + blocks_to_encrypt += NumBaseMetaBlocksInGroup(group); + else + blocks_to_encrypt += + (NumBlocksInGroup(group) - aux_info.bg_desc[group].bg_free_blocks_count); } - LOG(DEBUG) << "Copying " << data->count << " blocks at offset " << data->offset; + InitFs("ext4", blocks_to_encrypt, aux_info.len_blocks, info.block_size); - if (pread64(data->realfd, data->buffer, info.block_size * data->count, data->offset) <= 0) { - LOG(ERROR) << "Error reading real_blkdev " << data->real_blkdev << " for inplace encrypt"; - return -1; + // Encrypt each block group. + std::vector block_bitmap(info.block_size); + for (uint32_t group = 0; group < aux_info.groups; group++) { + if (!ReadExt4BlockBitmap(group, &block_bitmap[0])) return kFailed; + + uint64_t first_block_num = FirstBlockInGroup(group); + bool uninit = (aux_info.bg_desc[group].bg_flags & EXT4_BG_BLOCK_UNINIT); + uint32_t block_count = uninit ? NumBaseMetaBlocksInGroup(group) : NumBlocksInGroup(group); + + // Encrypt each used block in the block group. + for (uint32_t i = 0; i < block_count; i++) { + if (uninit || bitmap_get_bit(&block_bitmap[0], i)) + ProcessUsedBlock(first_block_num + i); + } } + return kSuccess; +} - if (pwrite64(data->cryptofd, data->buffer, info.block_size * data->count, data->offset) <= 0) { - LOG(ERROR) << "Error writing crypto_blkdev " << data->crypto_blkdev - << " for inplace encrypt"; - return -1; - } else { - log_progress(data, false); - } - - data->count = 0; - data->last_written_sector = - (data->offset + data->count) / info.block_size * CRYPT_SECTOR_SIZE - 1; +static int encrypt_f2fs_block(uint64_t block_num, void* _encrypter) { + InPlaceEncrypter* encrypter = reinterpret_cast(_encrypter); + if (!encrypter->ProcessUsedBlock(block_num)) return -1; return 0; } -static int encrypt_groups(struct encryptGroupsData* data) { - unsigned int i; - u8* block_bitmap = 0; - unsigned int block; - off64_t ret; - int rc = -1; +EncryptInPlaceError InPlaceEncrypter::EncryptInPlaceF2fs() { + std::unique_ptr fs_info( + generate_f2fs_info(realfd_), free_f2fs_info); + if (!fs_info) return kFilesystemNotFound; - data->buffer = (char*)malloc(info.block_size * BLOCKS_AT_A_TIME); - if (!data->buffer) { - LOG(ERROR) << "Failed to allocate crypto buffer"; - goto errout; - } - - block_bitmap = (u8*)malloc(info.block_size); - if (!block_bitmap) { - LOG(ERROR) << "failed to allocate block bitmap"; - goto errout; - } - - for (i = 0; i < aux_info.groups; ++i) { - LOG(INFO) << "Encrypting group " << i; - - u32 first_block = aux_info.first_data_block + i * info.blocks_per_group; - u32 block_count = std::min(info.blocks_per_group, (u32)(aux_info.len_blocks - first_block)); - - off64_t offset = (u64)info.block_size * aux_info.bg_desc[i].bg_block_bitmap; - - ret = pread64(data->realfd, block_bitmap, info.block_size, offset); - if (ret != (int)info.block_size) { - LOG(ERROR) << "failed to read all of block group bitmap " << i; - goto errout; - } - - offset = (u64)info.block_size * first_block; - - data->count = 0; - - for (block = 0; block < block_count; block++) { - int used; - - if (aux_info.bg_desc[i].bg_flags & EXT4_BG_BLOCK_UNINIT) { - // In block groups with an uninitialized block bitmap, we only - // need to encrypt the backup superblock (if one is present). - used = (ext4_bg_has_super_block(i) && block < 1 + aux_info.bg_desc_blocks); - } else { - used = bitmap_get_bit(block_bitmap, block); - } - - update_progress(data, used); - if (used) { - if (data->count == 0) { - data->offset = offset; - } - data->count++; - } else { - if (flush_outstanding_data(data)) { - goto errout; - } - } - - offset += info.block_size; - - /* Write data if we are aligned or buffer size reached */ - if (offset % (info.block_size * BLOCKS_AT_A_TIME) == 0 || - data->count == BLOCKS_AT_A_TIME) { - if (flush_outstanding_data(data)) { - goto errout; - } - } - } - if (flush_outstanding_data(data)) { - goto errout; - } - } - - data->completed = 1; - rc = 0; - -errout: - log_progress(0, true); - free(data->buffer); - free(block_bitmap); - return rc; + InitFs("f2fs", get_num_blocks_used(fs_info.get()), fs_info->total_blocks, fs_info->block_size); + if (run_on_used_blocks(0, fs_info.get(), encrypt_f2fs_block, this) != 0) return kFailed; + return kSuccess; } -static int cryptfs_enable_inplace_ext4(const char* crypto_blkdev, const char* real_blkdev, - off64_t size, off64_t* size_already_done, off64_t tot_size, - off64_t previously_encrypted_upto, - bool set_progress_properties) { - u32 i; - struct encryptGroupsData data; - int rc; // Can't initialize without causing warning -Wclobbered - int retries = RETRY_MOUNT_ATTEMPTS; +bool InPlaceEncrypter::DoEncryptInPlace() { + EncryptInPlaceError rc; + + rc = EncryptInPlaceExt4(); + if (rc != kFilesystemNotFound) return rc == kSuccess; + + rc = EncryptInPlaceF2fs(); + if (rc != kFilesystemNotFound) return rc == kSuccess; + + LOG(WARNING) << "No recognized filesystem found on " << real_blkdev_ + << ". Falling back to encrypting the full block device."; + InitFs("", nr_sec_, nr_sec_, 512); + for (uint64_t i = 0; i < nr_sec_; i++) { + if (!ProcessUsedBlock(i)) return false; + } + return true; +} + +bool InPlaceEncrypter::EncryptInPlace(const std::string& crypto_blkdev, + const std::string& real_blkdev, uint64_t nr_sec, + bool set_progress_properties) { struct timespec time_started = {0}; - if (previously_encrypted_upto > *size_already_done) { - LOG(DEBUG) << "Not fast encrypting since resuming part way through"; - return -1; - } + real_blkdev_ = real_blkdev; + crypto_blkdev_ = crypto_blkdev; + nr_sec_ = nr_sec; + set_progress_properties_ = set_progress_properties; - memset(&data, 0, sizeof(data)); - data.real_blkdev = real_blkdev; - data.crypto_blkdev = crypto_blkdev; - data.set_progress_properties = set_progress_properties; - - LOG(DEBUG) << "Opening" << real_blkdev; - if ((data.realfd = open(real_blkdev, O_RDWR | O_CLOEXEC)) < 0) { + realfd_.reset(open64(real_blkdev.c_str(), O_RDONLY | O_CLOEXEC)); + if (realfd_ < 0) { PLOG(ERROR) << "Error opening real_blkdev " << real_blkdev << " for inplace encrypt"; - rc = -1; - goto errout; + return false; } - LOG(DEBUG) << "Opening" << crypto_blkdev; - // Wait until the block device appears. Re-use the mount retry values since it is reasonable. - while ((data.cryptofd = open(crypto_blkdev, O_WRONLY | O_CLOEXEC)) < 0) { - if (--retries) { - PLOG(ERROR) << "Error opening crypto_blkdev " << crypto_blkdev - << " for ext4 inplace encrypt, retrying"; - sleep(RETRY_MOUNT_DELAY_SECONDS); - } else { - PLOG(ERROR) << "Error opening crypto_blkdev " << crypto_blkdev - << " for ext4 inplace encrypt"; - rc = ENABLE_INPLACE_ERR_DEV; - goto errout; - } - } - - if (setjmp(setjmp_env)) { // NOLINT - LOG(ERROR) << "Reading ext4 extent caused an exception"; - rc = -1; - goto errout; - } - - if (read_ext(data.realfd, 0) != 0) { - LOG(ERROR) << "Failed to read ext4 extent"; - rc = -1; - goto errout; - } - - data.numblocks = size / CRYPT_SECTORS_PER_BUFSIZE; - data.tot_numblocks = tot_size / CRYPT_SECTORS_PER_BUFSIZE; - data.blocks_already_done = *size_already_done / CRYPT_SECTORS_PER_BUFSIZE; - - LOG(INFO) << "Encrypting ext4 filesystem in place..."; - - data.tot_used_blocks = data.numblocks; - for (i = 0; i < aux_info.groups; ++i) { - data.tot_used_blocks -= aux_info.bg_desc[i].bg_free_blocks_count; - } - - data.one_pct = data.tot_used_blocks / 100; - data.cur_pct = 0; - - if (clock_gettime(CLOCK_MONOTONIC, &time_started)) { - LOG(WARNING) << "Error getting time at start"; - // Note - continue anyway - we'll run with 0 - } - data.time_started = time_started.tv_sec; - data.remaining_time = -1; - - rc = encrypt_groups(&data); - if (rc) { - LOG(ERROR) << "Error encrypting groups"; - goto errout; - } - - *size_already_done += data.completed ? size : data.last_written_sector; - rc = 0; - -errout: - close(data.realfd); - close(data.cryptofd); - - return rc; -} - -static void log_progress_f2fs(u64 block, bool completed) { - // Precondition - if completed data = 0 else data != 0 - - // Track progress so we can skip logging blocks - static u64 last_block = (u64)-1; - - // Need to close existing 'Encrypting from' log? - if (completed || (last_block != (u64)-1 && block != last_block + 1)) { - LOG(INFO) << "Encrypted to block " << last_block; - last_block = -1; - } - - // Need to start new 'Encrypting from' log? - if (!completed && (last_block == (u64)-1 || block != last_block + 1)) { - LOG(INFO) << "Encrypting from block " << block; - } - - // Update offset - if (!completed) { - last_block = block; - } -} - -static int encrypt_one_block_f2fs(u64 pos, void* data) { - struct encryptGroupsData* priv_dat = (struct encryptGroupsData*)data; - - priv_dat->blocks_already_done = pos - 1; - update_progress(priv_dat, 1); - - off64_t offset = pos * CRYPT_INPLACE_BUFSIZE; - - if (pread64(priv_dat->realfd, priv_dat->buffer, CRYPT_INPLACE_BUFSIZE, offset) <= 0) { - LOG(ERROR) << "Error reading real_blkdev " << priv_dat->crypto_blkdev - << " for f2fs inplace encrypt"; - return -1; - } - - if (pwrite64(priv_dat->cryptofd, priv_dat->buffer, CRYPT_INPLACE_BUFSIZE, offset) <= 0) { - LOG(ERROR) << "Error writing crypto_blkdev " << priv_dat->crypto_blkdev - << " for f2fs inplace encrypt"; - return -1; - } else { - log_progress_f2fs(pos, false); - } - - return 0; -} - -static int cryptfs_enable_inplace_f2fs(const char* crypto_blkdev, const char* real_blkdev, - off64_t size, off64_t* size_already_done, off64_t tot_size, - off64_t previously_encrypted_upto, - bool set_progress_properties) { - struct encryptGroupsData data; - struct f2fs_info* f2fs_info = NULL; - int rc = ENABLE_INPLACE_ERR_OTHER; - struct timespec time_started = {0}; - - if (previously_encrypted_upto > *size_already_done) { - LOG(DEBUG) << "Not fast encrypting since resuming part way through"; - return ENABLE_INPLACE_ERR_OTHER; - } - memset(&data, 0, sizeof(data)); - data.real_blkdev = real_blkdev; - data.crypto_blkdev = crypto_blkdev; - data.set_progress_properties = set_progress_properties; - data.realfd = -1; - data.cryptofd = -1; - if ((data.realfd = open64(real_blkdev, O_RDWR | O_CLOEXEC)) < 0) { - PLOG(ERROR) << "Error opening real_blkdev " << real_blkdev << " for f2fs inplace encrypt"; - goto errout; - } - if ((data.cryptofd = open64(crypto_blkdev, O_WRONLY | O_CLOEXEC)) < 0) { - PLOG(ERROR) << "Error opening crypto_blkdev " << crypto_blkdev - << " for f2fs inplace encrypt"; - rc = ENABLE_INPLACE_ERR_DEV; - goto errout; - } - - f2fs_info = generate_f2fs_info(data.realfd); - if (!f2fs_info) goto errout; - - data.numblocks = size / CRYPT_SECTORS_PER_BUFSIZE; - data.tot_numblocks = tot_size / CRYPT_SECTORS_PER_BUFSIZE; - data.blocks_already_done = *size_already_done / CRYPT_SECTORS_PER_BUFSIZE; - - data.tot_used_blocks = get_num_blocks_used(f2fs_info); - - data.one_pct = data.tot_used_blocks / 100; - data.cur_pct = 0; - if (clock_gettime(CLOCK_MONOTONIC, &time_started)) { - LOG(WARNING) << "Error getting time at start"; - // Note - continue anyway - we'll run with 0 - } - data.time_started = time_started.tv_sec; - data.remaining_time = -1; - - - data.buffer = (char*)malloc(f2fs_info->block_size); - if (!data.buffer) { - LOG(ERROR) << "Failed to allocate crypto buffer"; - goto errout; - } - - data.count = 0; - - /* Currently, this either runs to completion, or hits a nonrecoverable error */ - rc = run_on_used_blocks(data.blocks_already_done, f2fs_info, &encrypt_one_block_f2fs, &data); - - if (rc) { - LOG(ERROR) << "Error in running over f2fs blocks"; - rc = ENABLE_INPLACE_ERR_OTHER; - goto errout; - } - - *size_already_done += size; - rc = 0; - -errout: - if (rc) LOG(ERROR) << "Failed to encrypt f2fs filesystem on " << real_blkdev; - - log_progress_f2fs(0, true); - free(f2fs_info); - free(data.buffer); - close(data.realfd); - close(data.cryptofd); - - return rc; -} - -static int cryptfs_enable_inplace_full(const char* crypto_blkdev, const char* real_blkdev, - off64_t size, off64_t* size_already_done, off64_t tot_size, - off64_t previously_encrypted_upto, - bool set_progress_properties) { - int realfd, cryptofd; - char* buf[CRYPT_INPLACE_BUFSIZE]; - int rc = ENABLE_INPLACE_ERR_OTHER; - off64_t numblocks, i, remainder; - off64_t one_pct, cur_pct, new_pct; - off64_t blocks_already_done, tot_numblocks; - - if ((realfd = open(real_blkdev, O_RDONLY | O_CLOEXEC)) < 0) { - PLOG(ERROR) << "Error opening real_blkdev " << real_blkdev << " for inplace encrypt"; - return ENABLE_INPLACE_ERR_OTHER; - } - - if ((cryptofd = open(crypto_blkdev, O_WRONLY | O_CLOEXEC)) < 0) { + cryptofd_.reset(open64(crypto_blkdev.c_str(), O_WRONLY | O_CLOEXEC)); + if (cryptofd_ < 0) { PLOG(ERROR) << "Error opening crypto_blkdev " << crypto_blkdev << " for inplace encrypt"; - close(realfd); - return ENABLE_INPLACE_ERR_DEV; + return false; } - /* This is pretty much a simple loop of reading 4K, and writing 4K. - * The size passed in is the number of 512 byte sectors in the filesystem. - * So compute the number of whole 4K blocks we should read/write, - * and the remainder. - */ - numblocks = size / CRYPT_SECTORS_PER_BUFSIZE; - remainder = size % CRYPT_SECTORS_PER_BUFSIZE; - tot_numblocks = tot_size / CRYPT_SECTORS_PER_BUFSIZE; - blocks_already_done = *size_already_done / CRYPT_SECTORS_PER_BUFSIZE; - - LOG(ERROR) << "Encrypting filesystem in place..."; - - i = previously_encrypted_upto + 1 - *size_already_done; - - if (lseek64(realfd, i * CRYPT_SECTOR_SIZE, SEEK_SET) < 0) { - PLOG(ERROR) << "Cannot seek to previously encrypted point on " << real_blkdev; - goto errout; + if (clock_gettime(CLOCK_MONOTONIC, &time_started)) { + PLOG(WARNING) << "Error getting time at start of in-place encryption"; + // Note - continue anyway - we'll run with 0 } + time_started_ = time_started.tv_sec; + remaining_time_ = -1; - if (lseek64(cryptofd, i * CRYPT_SECTOR_SIZE, SEEK_SET) < 0) { - PLOG(ERROR) << "Cannot seek to previously encrypted point on " << crypto_blkdev; - goto errout; + bool success = DoEncryptInPlace(); + + if (success) success &= EncryptPendingData(); + + if (!success) { + LOG(ERROR) << "In-place encryption of " << DescribeFilesystem() << " failed"; + return false; } - - for (; i < size && i % CRYPT_SECTORS_PER_BUFSIZE != 0; ++i) { - if (unix_read(realfd, buf, CRYPT_SECTOR_SIZE) <= 0) { - PLOG(ERROR) << "Error reading initial sectors from real_blkdev " << real_blkdev - << " for inplace encrypt"; - goto errout; - } - if (unix_write(cryptofd, buf, CRYPT_SECTOR_SIZE) <= 0) { - PLOG(ERROR) << "Error writing initial sectors to crypto_blkdev " << crypto_blkdev - << " for inplace encrypt"; - goto errout; - } else { - LOG(INFO) << "Encrypted 1 block at " << i; - } + if (blocks_done_ != blocks_to_encrypt_) { + LOG(WARNING) << "blocks_to_encrypt (" << blocks_to_encrypt_ + << ") was incorrect; we actually encrypted " << blocks_done_ + << " blocks. Encryption progress was inaccurate"; } - - one_pct = tot_numblocks / 100; - cur_pct = 0; - /* process the majority of the filesystem in blocks */ - for (i /= CRYPT_SECTORS_PER_BUFSIZE; i < numblocks; i++) { - new_pct = (i + blocks_already_done) / one_pct; - if (set_progress_properties && new_pct > cur_pct) { - char property_buf[8]; - - cur_pct = new_pct; - snprintf(property_buf, sizeof(property_buf), "%" PRId64, cur_pct); - android::base::SetProperty("vold.encrypt_progress", property_buf); - } - if (unix_read(realfd, buf, CRYPT_INPLACE_BUFSIZE) <= 0) { - PLOG(ERROR) << "Error reading real_blkdev " << real_blkdev << " for inplace encrypt"; - goto errout; - } - if (unix_write(cryptofd, buf, CRYPT_INPLACE_BUFSIZE) <= 0) { - PLOG(ERROR) << "Error writing crypto_blkdev " << crypto_blkdev << " for inplace encrypt"; - goto errout; - } else { - LOG(DEBUG) << "Encrypted " << CRYPT_SECTORS_PER_BUFSIZE << " block at " - << i * CRYPT_SECTORS_PER_BUFSIZE; - } - } - - /* Do any remaining sectors */ - for (i = 0; i < remainder; i++) { - if (unix_read(realfd, buf, CRYPT_SECTOR_SIZE) <= 0) { - LOG(ERROR) << "Error reading final sectors from real_blkdev " << real_blkdev - << " for inplace encrypt"; - goto errout; - } - if (unix_write(cryptofd, buf, CRYPT_SECTOR_SIZE) <= 0) { - LOG(ERROR) << "Error writing final sectors to crypto_blkdev " << crypto_blkdev - << " for inplace encrypt"; - goto errout; - } else { - LOG(INFO) << "Encrypted 1 block at next location"; - } - } - - *size_already_done += size; - rc = 0; - -errout: - close(realfd); - close(cryptofd); - - return rc; + // Make sure vold.encrypt_progress gets set to 100. + UpdateProgress(0, true); + LOG(INFO) << "Successfully encrypted " << DescribeFilesystem(); + return true; } -/* returns on of the ENABLE_INPLACE_* return codes */ -int cryptfs_enable_inplace(const char* crypto_blkdev, const char* real_blkdev, off64_t size, - off64_t* size_already_done, off64_t tot_size, - off64_t previously_encrypted_upto, bool set_progress_properties) { - int rc_ext4, rc_f2fs, rc_full; - LOG(DEBUG) << "cryptfs_enable_inplace(" << crypto_blkdev << ", " << real_blkdev << ", " << size - << ", " << size_already_done << ", " << tot_size << ", " << previously_encrypted_upto - << ", " << set_progress_properties << ")"; - if (previously_encrypted_upto) { - LOG(DEBUG) << "Continuing encryption from " << previously_encrypted_upto; - } +// Encrypts |real_blkdev| in-place by reading the data from |real_blkdev| and +// writing it to |crypto_blkdev|, which should be a dm-crypt or dm-default-key +// device backed by |real_blkdev|. The size to encrypt is |nr_sec| 512-byte +// sectors; however, if a filesystem is detected, then its size will be used +// instead, and only the in-use blocks of the filesystem will be encrypted. +bool encrypt_inplace(const std::string& crypto_blkdev, const std::string& real_blkdev, + uint64_t nr_sec, bool set_progress_properties) { + LOG(DEBUG) << "encrypt_inplace(" << crypto_blkdev << ", " << real_blkdev << ", " << nr_sec + << ", " << (set_progress_properties ? "true" : "false") << ")"; - if (*size_already_done + size < previously_encrypted_upto) { - LOG(DEBUG) << "cryptfs_enable_inplace already done"; - *size_already_done += size; - return 0; - } - - /* TODO: identify filesystem type. - * As is, cryptfs_enable_inplace_ext4 will fail on an f2fs partition, and - * then we will drop down to cryptfs_enable_inplace_f2fs. - * */ - if ((rc_ext4 = cryptfs_enable_inplace_ext4(crypto_blkdev, real_blkdev, size, size_already_done, - tot_size, previously_encrypted_upto, - set_progress_properties)) == 0) { - LOG(DEBUG) << "cryptfs_enable_inplace_ext4 success"; - return 0; - } - LOG(DEBUG) << "cryptfs_enable_inplace_ext4()=" << rc_ext4; - - if ((rc_f2fs = cryptfs_enable_inplace_f2fs(crypto_blkdev, real_blkdev, size, size_already_done, - tot_size, previously_encrypted_upto, - set_progress_properties)) == 0) { - LOG(DEBUG) << "cryptfs_enable_inplace_f2fs success"; - return 0; - } - LOG(DEBUG) << "cryptfs_enable_inplace_f2fs()=" << rc_f2fs; - - rc_full = - cryptfs_enable_inplace_full(crypto_blkdev, real_blkdev, size, size_already_done, tot_size, - previously_encrypted_upto, set_progress_properties); - LOG(DEBUG) << "cryptfs_enable_inplace_full()=" << rc_full; - - /* Hack for b/17898962, the following is the symptom... */ - if (rc_ext4 == ENABLE_INPLACE_ERR_DEV && rc_f2fs == ENABLE_INPLACE_ERR_DEV && - rc_full == ENABLE_INPLACE_ERR_DEV) { - LOG(DEBUG) << "ENABLE_INPLACE_ERR_DEV"; - return ENABLE_INPLACE_ERR_DEV; - } - return rc_full; + InPlaceEncrypter encrypter; + return encrypter.EncryptInPlace(crypto_blkdev, real_blkdev, nr_sec, set_progress_properties); } diff --git a/EncryptInplace.h b/EncryptInplace.h index a2b46cf..480a47c 100644 --- a/EncryptInplace.h +++ b/EncryptInplace.h @@ -17,20 +17,10 @@ #ifndef _ENCRYPT_INPLACE_H #define _ENCRYPT_INPLACE_H -#include +#include +#include -#define CRYPT_INPLACE_BUFSIZE 4096 -#define CRYPT_SECTOR_SIZE 512 -#define RETRY_MOUNT_ATTEMPTS 10 -#define RETRY_MOUNT_DELAY_SECONDS 1 - -/* Return values for cryptfs_enable_inplace() */ -#define ENABLE_INPLACE_OK 0 -#define ENABLE_INPLACE_ERR_OTHER (-1) -#define ENABLE_INPLACE_ERR_DEV (-2) /* crypto_blkdev issue */ - -int cryptfs_enable_inplace(const char* crypto_blkdev, const char* real_blkdev, off64_t size, - off64_t* size_already_done, off64_t tot_size, - off64_t previously_encrypted_upto, bool set_progress_properties); +bool encrypt_inplace(const std::string& crypto_blkdev, const std::string& real_blkdev, + uint64_t nr_sec, bool set_progress_properties); #endif diff --git a/MetadataCrypt.cpp b/MetadataCrypt.cpp index 5950425..f794ee3 100644 --- a/MetadataCrypt.cpp +++ b/MetadataCrypt.cpp @@ -305,21 +305,7 @@ bool fscrypt_mount_metadata_encrypted(const std::string& blk_device, const std:: return false; // FIXME handle the corrupt case - if (needs_encrypt) { - LOG(INFO) << "Beginning inplace encryption, nr_sec: " << nr_sec; - off64_t size_already_done = 0; - auto rc = cryptfs_enable_inplace(crypto_blkdev.data(), blk_device.data(), nr_sec, - &size_already_done, nr_sec, 0, false); - if (rc != 0) { - LOG(ERROR) << "Inplace crypto failed with code: " << rc; - return false; - } - if (static_cast(size_already_done) != nr_sec) { - LOG(ERROR) << "Inplace crypto only got up to sector: " << size_already_done; - return false; - } - LOG(INFO) << "Inplace encryption complete"; - } + if (needs_encrypt && !encrypt_inplace(crypto_blkdev, blk_device, nr_sec, false)) return false; LOG(DEBUG) << "Mounting metadata-encrypted filesystem:" << mount_point; mount_via_fs_mgr(mount_point.c_str(), crypto_blkdev.c_str()); diff --git a/cryptfs.cpp b/cryptfs.cpp index 8b7ac0a..faed65b 100644 --- a/cryptfs.cpp +++ b/cryptfs.cpp @@ -90,6 +90,8 @@ using namespace std::chrono_literals; #define CRYPT_FOOTER_TO_PERSIST_OFFSET 0x1000 #define CRYPT_PERSIST_DATA_SIZE 0x1000 +#define CRYPT_SECTOR_SIZE 512 + #define MAX_CRYPTO_TYPE_NAME_LEN 64 #define MAX_KEY_LEN 48 @@ -98,9 +100,7 @@ using namespace std::chrono_literals; /* definitions of flags in the structure below */ #define CRYPT_MNT_KEY_UNENCRYPTED 0x1 /* The key for the partition is not encrypted. */ -#define CRYPT_ENCRYPTION_IN_PROGRESS \ - 0x2 /* Encryption partially completed, \ - encrypted_upto valid*/ +#define CRYPT_ENCRYPTION_IN_PROGRESS 0x2 /* no longer used */ #define CRYPT_INCONSISTENT_STATE \ 0x4 /* Set when starting encryption, clear when \ exit cleanly, either through success or \ @@ -195,12 +195,8 @@ struct crypt_mnt_ftr { __le8 N_factor; /* (1 << N) */ __le8 r_factor; /* (1 << r) */ __le8 p_factor; /* (1 << p) */ - __le64 encrypted_upto; /* If we are in state CRYPT_ENCRYPTION_IN_PROGRESS and - we have to stop (e.g. power low) this is the last - encrypted 512 byte sector.*/ - __le8 hash_first_block[SHA256_DIGEST_LENGTH]; /* When CRYPT_ENCRYPTION_IN_PROGRESS - set, hash of first block, used - to validate before continuing*/ + __le64 encrypted_upto; /* no longer used */ + __le8 hash_first_block[SHA256_DIGEST_LENGTH]; /* no longer used */ /* key_master key, used to sign the derived key which is then used to generate * the intermediate key @@ -2069,61 +2065,6 @@ static int cryptfs_init_crypt_mnt_ftr(struct crypt_mnt_ftr* ftr) { #define FRAMEWORK_BOOT_WAIT 60 -static int cryptfs_SHA256_fileblock(const char* filename, __le8* buf) { - int fd = open(filename, O_RDONLY | O_CLOEXEC); - if (fd == -1) { - SLOGE("Error opening file %s", filename); - return -1; - } - - char block[CRYPT_INPLACE_BUFSIZE]; - memset(block, 0, sizeof(block)); - if (unix_read(fd, block, sizeof(block)) < 0) { - SLOGE("Error reading file %s", filename); - close(fd); - return -1; - } - - close(fd); - - SHA256_CTX c; - SHA256_Init(&c); - SHA256_Update(&c, block, sizeof(block)); - SHA256_Final(buf, &c); - - return 0; -} - -static int cryptfs_enable_all_volumes(struct crypt_mnt_ftr* crypt_ftr, const char* crypto_blkdev, - const char* real_blkdev, int previously_encrypted_upto) { - off64_t cur_encryption_done = 0, tot_encryption_size = 0; - int rc = -1; - - /* The size of the userdata partition, and add in the vold volumes below */ - tot_encryption_size = crypt_ftr->fs_size; - - rc = cryptfs_enable_inplace(crypto_blkdev, real_blkdev, crypt_ftr->fs_size, &cur_encryption_done, - tot_encryption_size, previously_encrypted_upto, true); - - if (rc == ENABLE_INPLACE_ERR_DEV) { - /* Hack for b/17898962 */ - SLOGE("cryptfs_enable: crypto block dev failure. Must reboot...\n"); - cryptfs_reboot(RebootType::reboot); - } - - if (!rc) { - crypt_ftr->encrypted_upto = cur_encryption_done; - } - - if (!rc && crypt_ftr->encrypted_upto == crypt_ftr->fs_size) { - /* The inplace routine never actually sets the progress to 100% due - * to the round down nature of integer division, so set it here */ - property_set("vold.encrypt_progress", "100"); - } - - return rc; -} - static int vold_unmountAll(void) { VolumeManager* vm = VolumeManager::Instance(); return vm->unmountAll(); @@ -2140,26 +2081,12 @@ int cryptfs_enable_internal(int crypt_type, const char* passwd, int no_ui) { char lockid[32] = {0}; std::string key_loc; int num_vols; - off64_t previously_encrypted_upto = 0; bool rebootEncryption = false; bool onlyCreateHeader = false; std::unique_ptr wakeLock = nullptr; if (get_crypt_ftr_and_key(&crypt_ftr) == 0) { - if (crypt_ftr.flags & CRYPT_ENCRYPTION_IN_PROGRESS) { - /* An encryption was underway and was interrupted */ - previously_encrypted_upto = crypt_ftr.encrypted_upto; - crypt_ftr.encrypted_upto = 0; - crypt_ftr.flags &= ~CRYPT_ENCRYPTION_IN_PROGRESS; - - /* At this point, we are in an inconsistent state. Until we successfully - complete encryption, a reboot will leave us broken. So mark the - encryption failed in case that happens. - On successfully completing encryption, remove this flag */ - crypt_ftr.flags |= CRYPT_INCONSISTENT_STATE; - - put_crypt_ftr_and_key(&crypt_ftr); - } else if (crypt_ftr.flags & CRYPT_FORCE_ENCRYPTION) { + if (crypt_ftr.flags & CRYPT_FORCE_ENCRYPTION) { if (!check_ftr_sha(&crypt_ftr)) { memset(&crypt_ftr, 0, sizeof(crypt_ftr)); put_crypt_ftr_and_key(&crypt_ftr); @@ -2177,7 +2104,7 @@ int cryptfs_enable_internal(int crypt_type, const char* passwd, int no_ui) { } property_get("ro.crypto.state", encrypted_state, ""); - if (!strcmp(encrypted_state, "encrypted") && !previously_encrypted_upto) { + if (!strcmp(encrypted_state, "encrypted")) { SLOGE("Device is already running encrypted, aborting"); goto error_unencrypted; } @@ -2264,7 +2191,7 @@ int cryptfs_enable_internal(int crypt_type, const char* passwd, int no_ui) { /* Start the actual work of making an encrypted filesystem */ /* Initialize a crypt_mnt_ftr for the partition */ - if (previously_encrypted_upto == 0 && !rebootEncryption) { + if (!rebootEncryption) { if (cryptfs_init_crypt_mnt_ftr(&crypt_ftr)) { goto error_shutting_down; } @@ -2339,77 +2266,46 @@ int cryptfs_enable_internal(int crypt_type, const char* passwd, int no_ui) { } decrypt_master_key(passwd, decrypted_master_key, &crypt_ftr, 0, 0); - create_crypto_blk_dev(&crypt_ftr, decrypted_master_key, real_blkdev.c_str(), &crypto_blkdev, - CRYPTO_BLOCK_DEVICE, 0); - - /* If we are continuing, check checksums match */ - rc = 0; - if (previously_encrypted_upto) { - __le8 hash_first_block[SHA256_DIGEST_LENGTH]; - rc = cryptfs_SHA256_fileblock(crypto_blkdev.c_str(), hash_first_block); - - if (!rc && - memcmp(hash_first_block, crypt_ftr.hash_first_block, sizeof(hash_first_block)) != 0) { - SLOGE("Checksums do not match - trigger wipe"); - rc = -1; - } - } - + rc = create_crypto_blk_dev(&crypt_ftr, decrypted_master_key, real_blkdev.c_str(), + &crypto_blkdev, CRYPTO_BLOCK_DEVICE, 0); if (!rc) { - rc = cryptfs_enable_all_volumes(&crypt_ftr, crypto_blkdev.c_str(), real_blkdev.data(), - previously_encrypted_upto); - } - - /* Calculate checksum if we are not finished */ - if (!rc && crypt_ftr.encrypted_upto != crypt_ftr.fs_size) { - rc = cryptfs_SHA256_fileblock(crypto_blkdev.c_str(), crypt_ftr.hash_first_block); - if (rc) { - SLOGE("Error calculating checksum for continuing encryption"); + if (encrypt_inplace(crypto_blkdev, real_blkdev, crypt_ftr.fs_size, true)) { + crypt_ftr.encrypted_upto = crypt_ftr.fs_size; + rc = 0; + } else { rc = -1; } + /* Undo the dm-crypt mapping whether we succeed or not */ + delete_crypto_blk_dev(CRYPTO_BLOCK_DEVICE); } - /* Undo the dm-crypt mapping whether we succeed or not */ - delete_crypto_blk_dev(CRYPTO_BLOCK_DEVICE); - if (!rc) { /* Success */ crypt_ftr.flags &= ~CRYPT_INCONSISTENT_STATE; - if (crypt_ftr.encrypted_upto != crypt_ftr.fs_size) { - SLOGD("Encrypted up to sector %lld - will continue after reboot", - crypt_ftr.encrypted_upto); - crypt_ftr.flags |= CRYPT_ENCRYPTION_IN_PROGRESS; - } - put_crypt_ftr_and_key(&crypt_ftr); - if (crypt_ftr.encrypted_upto == crypt_ftr.fs_size) { - char value[PROPERTY_VALUE_MAX]; - property_get("ro.crypto.state", value, ""); - if (!strcmp(value, "")) { - /* default encryption - continue first boot sequence */ - property_set("ro.crypto.state", "encrypted"); - property_set("ro.crypto.type", "block"); - wakeLock.reset(nullptr); - if (rebootEncryption && crypt_ftr.crypt_type != CRYPT_TYPE_DEFAULT) { - // Bring up cryptkeeper that will check the password and set it - property_set("vold.decrypt", "trigger_shutdown_framework"); - sleep(2); - property_set("vold.encrypt_progress", ""); - cryptfs_trigger_restart_min_framework(); - } else { - cryptfs_check_passwd(DEFAULT_PASSWORD); - cryptfs_restart_internal(1); - } - return 0; + char value[PROPERTY_VALUE_MAX]; + property_get("ro.crypto.state", value, ""); + if (!strcmp(value, "")) { + /* default encryption - continue first boot sequence */ + property_set("ro.crypto.state", "encrypted"); + property_set("ro.crypto.type", "block"); + wakeLock.reset(nullptr); + if (rebootEncryption && crypt_ftr.crypt_type != CRYPT_TYPE_DEFAULT) { + // Bring up cryptkeeper that will check the password and set it + property_set("vold.decrypt", "trigger_shutdown_framework"); + sleep(2); + property_set("vold.encrypt_progress", ""); + cryptfs_trigger_restart_min_framework(); } else { - sleep(2); /* Give the UI a chance to show 100% progress */ - cryptfs_reboot(RebootType::reboot); + cryptfs_check_passwd(DEFAULT_PASSWORD); + cryptfs_restart_internal(1); } + return 0; } else { - sleep(2); /* Partially encrypted, ensure writes flushed to ssd */ - cryptfs_reboot(RebootType::shutdown); + sleep(2); /* Give the UI a chance to show 100% progress */ + cryptfs_reboot(RebootType::reboot); } } else { char value[PROPERTY_VALUE_MAX];