/* * Copyright (c) Atmosphère-NX * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include "fssystem_hierarchical_sha256_storage.hpp" namespace ams::fssystem { namespace { s32 Log2(s32 value) { AMS_ASSERT(value > 0); AMS_ASSERT(util::IsPowerOfTwo(value)); s32 log = 0; while ((value >>= 1) > 0) { ++log; } return log; } } template Result HierarchicalSha256Storage::Initialize(BaseStorageType *base_storages, s32 layer_count, size_t htbs, void *hash_buf, size_t hash_buf_size, fssystem::IHash256GeneratorFactory *hgf) { /* Validate preconditions. */ AMS_ASSERT(layer_count == LayerCount); AMS_ASSERT(util::IsPowerOfTwo(htbs)); AMS_ASSERT(hash_buf != nullptr); AMS_ASSERT(hgf != nullptr); AMS_UNUSED(layer_count); /* Set size tracking members. */ m_hash_target_block_size = htbs; m_log_size_ratio = Log2(m_hash_target_block_size / HashSize); m_hash_generator_factory = hgf; /* Get the base storage size. */ R_TRY(base_storages[2]->GetSize(std::addressof(m_base_storage_size))); { auto size_guard = SCOPE_GUARD { m_base_storage_size = 0; }; R_UNLESS(m_base_storage_size <= static_cast(HashSize) << m_log_size_ratio << m_log_size_ratio, fs::ResultHierarchicalSha256BaseStorageTooLarge()); size_guard.Cancel(); } /* Set hash buffer tracking members. */ m_base_storage = base_storages[2]; m_hash_buffer = static_cast(hash_buf); m_hash_buffer_size = hash_buf_size; /* Read the master hash. */ u8 master_hash[HashSize]; R_TRY(base_storages[0]->Read(0, master_hash, HashSize)); /* Read and validate the data being hashed. */ s64 hash_storage_size; R_TRY(base_storages[1]->GetSize(std::addressof(hash_storage_size))); AMS_ASSERT(util::IsAligned(hash_storage_size, HashSize)); AMS_ASSERT(hash_storage_size <= m_hash_target_block_size); AMS_ASSERT(hash_storage_size <= static_cast(m_hash_buffer_size)); R_TRY(base_storages[1]->Read(0, m_hash_buffer, static_cast(hash_storage_size))); /* Calculate and verify the master hash. */ u8 calc_hash[HashSize]; m_hash_generator_factory->GenerateHash(calc_hash, sizeof(calc_hash), m_hash_buffer, static_cast(hash_storage_size)); R_UNLESS(crypto::IsSameBytes(master_hash, calc_hash, HashSize), fs::ResultHierarchicalSha256HashVerificationFailed()); return ResultSuccess(); } template Result HierarchicalSha256Storage::Read(s64 offset, void *buffer, size_t size) { /* Succeed if zero-size. */ R_SUCCEED_IF(size == 0); /* Validate that we have a buffer to read into. */ R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); /* Validate preconditions. */ R_UNLESS(util::IsAligned(offset, m_hash_target_block_size), fs::ResultInvalidArgument()); R_UNLESS(util::IsAligned(size, m_hash_target_block_size), fs::ResultInvalidArgument()); /* Read the data. */ const size_t reduced_size = static_cast(std::min(m_base_storage_size, util::AlignUp(offset + size, m_hash_target_block_size)) - offset); R_TRY(m_base_storage->Read(offset, buffer, reduced_size)); /* Temporarily increase our thread priority. */ ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative); /* Setup tracking variables. */ auto cur_offset = offset; auto remaining_size = reduced_size; while (remaining_size > 0) { /* Generate the hash of the region we're validating. */ u8 hash[HashSize]; const auto cur_size = static_cast(std::min(m_hash_target_block_size, remaining_size)); m_hash_generator_factory->GenerateHash(hash, sizeof(hash), static_cast(buffer) + (cur_offset - offset), cur_size); AMS_ASSERT(static_cast(cur_offset >> m_log_size_ratio) < m_hash_buffer_size); /* Check the hash. */ { std::scoped_lock lk(m_mutex); auto clear_guard = SCOPE_GUARD { std::memset(buffer, 0, size); }; R_UNLESS(crypto::IsSameBytes(hash, std::addressof(m_hash_buffer[cur_offset >> m_log_size_ratio]), HashSize), fs::ResultHierarchicalSha256HashVerificationFailed()); clear_guard.Cancel(); } /* Advance. */ cur_offset += cur_size; remaining_size -= cur_size; } return ResultSuccess(); } template Result HierarchicalSha256Storage::Write(s64 offset, const void *buffer, size_t size) { /* Succeed if zero-size. */ R_SUCCEED_IF(size == 0); /* Validate that we have a buffer to read into. */ R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); /* Validate preconditions. */ R_UNLESS(util::IsAligned(offset, m_hash_target_block_size), fs::ResultInvalidArgument()); R_UNLESS(util::IsAligned(size, m_hash_target_block_size), fs::ResultInvalidArgument()); /* Setup tracking variables. */ const size_t reduced_size = static_cast(std::min(m_base_storage_size, util::AlignUp(offset + size, m_hash_target_block_size)) - offset); auto cur_offset = offset; auto remaining_size = reduced_size; while (remaining_size > 0) { /* Generate the hash of the region we're validating. */ u8 hash[HashSize]; const auto cur_size = static_cast(std::min(m_hash_target_block_size, remaining_size)); { /* Temporarily increase our thread priority. */ ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative); m_hash_generator_factory->GenerateHash(hash, sizeof(hash), static_cast(buffer) + (cur_offset - offset), cur_size); } /* Write the data. */ R_TRY(m_base_storage->Write(cur_offset, static_cast(buffer) + (cur_offset - offset), cur_size)); /* Write the hash. */ { std::scoped_lock lk(m_mutex); std::memcpy(std::addressof(m_hash_buffer[cur_offset >> m_log_size_ratio]), hash, HashSize); } /* Advance. */ cur_offset += cur_size; remaining_size -= cur_size; } return ResultSuccess(); } template Result HierarchicalSha256Storage::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) { if (op_id == fs::OperationId::Invalidate) { return m_base_storage->OperateRange(fs::OperationId::Invalidate, offset, size); } else { /* Succeed if zero-size. */ R_SUCCEED_IF(size == 0); /* Validate preconditions. */ R_UNLESS(util::IsAligned(offset, m_hash_target_block_size), fs::ResultInvalidArgument()); R_UNLESS(util::IsAligned(size, m_hash_target_block_size), fs::ResultInvalidArgument()); /* Determine size to use. */ const auto reduced_size = std::min(m_base_storage_size, util::AlignUp(offset + size, m_hash_target_block_size)) - offset; /* Operate on the base storage. */ return m_base_storage->OperateRange(dst, dst_size, op_id, offset, reduced_size, src, src_size); } } template class HierarchicalSha256Storage; }