diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_partition_file_system.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_partition_file_system.hpp index a1c5274cd..1230665c3 100644 --- a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_partition_file_system.hpp +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_partition_file_system.hpp @@ -67,5 +67,6 @@ namespace ams::fssystem { }; using PartitionFileSystem = PartitionFileSystemCore; + using Sha256PartitionFileSystem = PartitionFileSystemCore; } diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_partition_file_system_meta.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_partition_file_system_meta.hpp index 619dee581..5e34906d6 100644 --- a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_partition_file_system_meta.hpp +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_partition_file_system_meta.hpp @@ -40,6 +40,29 @@ namespace ams::fssystem { using ResultSignatureVerificationFailed = fs::ResultPartitionSignatureVerificationFailed; }; + struct Sha256PartitionFileSystemFormat { + static constexpr size_t HashSize = ::ams::crypto::Sha256Generator::HashSize; + + #pragma pack(push, 1) + struct PartitionEntry { + u64 offset; + u64 size; + u32 name_offset; + u32 hash_target_size; + u64 hash_target_offset; + char hash[HashSize]; + }; + static_assert(std::is_pod::value); + #pragma pack(pop) + + static constexpr const char VersionSignature[] = { 'H', 'F', 'S', '0' }; + + static constexpr size_t EntryNameLengthMax = ::ams::fs::EntryNameLengthMax; + static constexpr size_t FileDataAlignmentSize = 0x200; + + using ResultSignatureVerificationFailed = fs::ResultSha256PartitionSignatureVerificationFailed; + }; + } template @@ -81,4 +104,10 @@ namespace ams::fssystem { using PartitionFileSystemMeta = PartitionFileSystemMetaCore; + class Sha256PartitionFileSystemMeta : public PartitionFileSystemMetaCore { + public: + using PartitionFileSystemMetaCore::Initialize; + Result Initialize(fs::IStorage *base_storage, MemoryResource *allocator, const void *hash, size_t hash_size, std::optional suffix = std::nullopt); + }; + } diff --git a/libraries/libstratosphere/source/fssystem/fssystem_partition_file_system.cpp b/libraries/libstratosphere/source/fssystem/fssystem_partition_file_system.cpp index 7ffe7cc64..85632d939 100644 --- a/libraries/libstratosphere/source/fssystem/fssystem_partition_file_system.cpp +++ b/libraries/libstratosphere/source/fssystem/fssystem_partition_file_system.cpp @@ -126,6 +126,94 @@ namespace ams::fssystem { return ResultSuccess(); } + template<> + Result PartitionFileSystemCore::PartitionFile::ReadImpl(size_t *out, s64 offset, void *dst, size_t dst_size, const fs::ReadOption &option) { + /* Perform a dry read. */ + size_t read_size = 0; + R_TRY(this->DryRead(std::addressof(read_size), offset, dst_size, option, this->mode)); + + const s64 entry_start = this->parent->meta_data_size + this->partition_entry->offset; + const s64 read_end = static_cast(offset + read_size); + const s64 hash_start = static_cast(this->partition_entry->hash_target_offset); + const s64 hash_end = hash_start + this->partition_entry->hash_target_size; + + if (read_end <= hash_start || hash_end <= offset) { + /* We aren't reading hashed data, so we can just read from the base storage. */ + R_TRY(this->parent->base_storage->Read(entry_start + offset, dst, read_size)); + } else { + /* Only hash target offset == 0 is supported. */ + R_UNLESS(hash_start == 0, fs::ResultInvalidSha256PartitionHashTarget()); + + /* Ensure that the hash region is valid. */ + R_UNLESS(this->partition_entry->hash_target_offset + this->partition_entry->hash_target_size <= this->partition_entry->size, fs::ResultInvalidSha256PartitionHashTarget()); + + /* Validate our read offset. */ + const s64 read_offset = entry_start + offset; + R_UNLESS(read_offset >= offset, fs::ResultOutOfRange()); + + /* Prepare a buffer for our calculated hash. */ + char hash[crypto::Sha256Generator::HashSize]; + crypto::Sha256Generator generator; + + /* Ensure we can perform our read. */ + const bool hash_in_read = offset <= hash_start && hash_end <= read_end; + const bool read_in_hash = hash_start <= offset && read_end <= hash_end; + R_UNLESS(hash_in_read || read_in_hash, fs::ResultInvalidSha256PartitionHashTarget()); + + /* Initialize the generator. */ + generator.Initialize(); + + if (hash_in_read) { + /* Easy case: hash region is contained within the bounds. */ + R_TRY(this->parent->base_storage->Read(entry_start + offset, dst, read_size)); + generator.Update(static_cast(dst) + hash_start - offset, this->partition_entry->hash_target_size); + } else /* if (read_in_hash) */ { + /* We're reading a portion of what's hashed. */ + s64 remaining_hash_size = this->partition_entry->hash_target_size; + s64 hash_offset = entry_start + hash_start; + s64 remaining_size = read_size; + s64 copy_offset = 0; + while (remaining_hash_size > 0) { + /* Read some portion of data into the buffer. */ + constexpr size_t HashBufferSize = 0x200; + char hash_buffer[HashBufferSize]; + size_t cur_size = static_cast(std::min(static_cast(HashBufferSize), remaining_hash_size)); + R_TRY(this->parent->base_storage->Read(hash_offset, hash_buffer, cur_size)); + + /* Update the hash. */ + generator.Update(hash_buffer, cur_size); + + /* If we need to copy, do so. */ + if (read_offset <= (hash_offset + static_cast(cur_size)) && remaining_size > 0) { + const s64 hash_buffer_offset = std::max(read_offset - hash_offset, 0); + const size_t copy_size = static_cast(std::min(cur_size - hash_buffer_offset, remaining_size)); + std::memcpy(static_cast(dst) + copy_offset, hash_buffer + hash_buffer_offset, copy_size); + remaining_size -= copy_size; + copy_offset += copy_size; + } + + /* Update offsets. */ + remaining_hash_size -= cur_size; + hash_offset += cur_size; + } + } + + /* Get the hash. */ + generator.GetHash(hash, sizeof(hash)); + + /* Validate the hash. */ + auto hash_guard = SCOPE_GUARD { std::memset(dst, 0, read_size); }; + R_UNLESS(crypto::IsSameBytes(this->partition_entry->hash, hash, sizeof(hash)), fs::ResultSha256PartitionHashVerificationFailed()); + + /* We successfully completed our read. */ + hash_guard.Cancel(); + } + + /* Set output size. */ + *out = read_size; + return ResultSuccess(); + } + template class PartitionFileSystemCore::PartitionDirectory : public fs::fsa::IDirectory, public fs::impl::Newable { private: @@ -357,5 +445,6 @@ namespace ams::fssystem { } template class PartitionFileSystemCore; + template class PartitionFileSystemCore; } diff --git a/libraries/libstratosphere/source/fssystem/fssystem_partition_file_system_meta.cpp b/libraries/libstratosphere/source/fssystem/fssystem_partition_file_system_meta.cpp index 0daa64e75..dc598e492 100644 --- a/libraries/libstratosphere/source/fssystem/fssystem_partition_file_system_meta.cpp +++ b/libraries/libstratosphere/source/fssystem/fssystem_partition_file_system_meta.cpp @@ -160,5 +160,60 @@ namespace ams::fssystem { } template class PartitionFileSystemMetaCore; + template class PartitionFileSystemMetaCore; + + Result Sha256PartitionFileSystemMeta::Initialize(fs::IStorage *base_storage, MemoryResource *allocator, const void *hash, size_t hash_size, std::optional suffix) { + /* Ensure preconditions. */ + R_UNLESS(hash_size == crypto::Sha256Generator::HashSize, fs::ResultPreconditionViolation()); + + /* Get metadata size. */ + R_TRY(QueryMetaDataSize(std::addressof(this->meta_data_size), base_storage)); + + /* Ensure we have no buffer. */ + this->DeallocateBuffer(); + + /* Set allocator and allocate buffer. */ + this->allocator = allocator; + this->buffer = static_cast(this->allocator->Allocate(this->meta_data_size)); + R_UNLESS(this->buffer != nullptr, fs::ResultAllocationFailureInPartitionFileSystemMetaB()); + + /* Read metadata. */ + R_TRY(base_storage->Read(0, this->buffer, this->meta_data_size)); + + /* Calculate hash. */ + char calc_hash[crypto::Sha256Generator::HashSize]; + { + crypto::Sha256Generator generator; + generator.Initialize(); + generator.Update(this->buffer, this->meta_data_size); + if (suffix) { + u8 suffix_val = *suffix; + generator.Update(std::addressof(suffix_val), 1); + } + generator.GetHash(calc_hash, sizeof(calc_hash)); + } + + /* Ensure hash is valid. */ + R_UNLESS(crypto::IsSameBytes(hash, calc_hash, sizeof(calc_hash)), fs::ResultSha256PartitionHashVerificationFailed()); + + /* Give access to Format */ + using Format = impl::Sha256PartitionFileSystemFormat; + + /* Set header. */ + this->header = reinterpret_cast(this->buffer); + R_UNLESS(crypto::IsSameBytes(this->header->signature, Format::VersionSignature, sizeof(Format::VersionSignature)), typename Format::ResultSignatureVerificationFailed()); + + /* Validate size for entries and name table. */ + const size_t entries_size = this->header->entry_count * sizeof(typename Format::PartitionEntry); + R_UNLESS(this->meta_data_size >= sizeof(PartitionFileSystemHeader) + entries_size + this->header->name_table_size, fs::ResultInvalidSha256PartitionMetaDataSize()); + + /* Set entries and name table. */ + this->entries = reinterpret_cast(this->buffer + sizeof(PartitionFileSystemHeader)); + this->name_table = this->buffer + sizeof(PartitionFileSystemHeader) + entries_size; + + /* We initialized. */ + this->initialized = true; + return ResultSuccess(); + } }