fssystem: implement Sha256PartitionFileSystem

This commit is contained in:
Michael Scire 2020-03-27 02:46:22 -07:00
parent 15229b61a4
commit faf57bffbf
4 changed files with 174 additions and 0 deletions

View file

@ -67,5 +67,6 @@ namespace ams::fssystem {
};
using PartitionFileSystem = PartitionFileSystemCore<PartitionFileSystemMeta>;
using Sha256PartitionFileSystem = PartitionFileSystemCore<Sha256PartitionFileSystemMeta>;
}

View file

@ -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<PartitionEntry>::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<typename Format>
@ -81,4 +104,10 @@ namespace ams::fssystem {
using PartitionFileSystemMeta = PartitionFileSystemMetaCore<impl::PartitionFileSystemFormat>;
class Sha256PartitionFileSystemMeta : public PartitionFileSystemMetaCore<impl::Sha256PartitionFileSystemFormat> {
public:
using PartitionFileSystemMetaCore<impl::Sha256PartitionFileSystemFormat>::Initialize;
Result Initialize(fs::IStorage *base_storage, MemoryResource *allocator, const void *hash, size_t hash_size, std::optional<u8> suffix = std::nullopt);
};
}

View file

@ -126,6 +126,94 @@ namespace ams::fssystem {
return ResultSuccess();
}
template<>
Result PartitionFileSystemCore<Sha256PartitionFileSystemMeta>::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<s64>(offset + read_size);
const s64 hash_start = static_cast<s64>(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<u8 *>(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<size_t>(std::min(static_cast<s64>(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<s64>(cur_size)) && remaining_size > 0) {
const s64 hash_buffer_offset = std::max<s64>(read_offset - hash_offset, 0);
const size_t copy_size = static_cast<size_t>(std::min<s64>(cur_size - hash_buffer_offset, remaining_size));
std::memcpy(static_cast<u8 *>(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 <typename MetaType>
class PartitionFileSystemCore<MetaType>::PartitionDirectory : public fs::fsa::IDirectory, public fs::impl::Newable {
private:
@ -357,5 +445,6 @@ namespace ams::fssystem {
}
template class PartitionFileSystemCore<PartitionFileSystemMeta>;
template class PartitionFileSystemCore<Sha256PartitionFileSystemMeta>;
}

View file

@ -160,5 +160,60 @@ namespace ams::fssystem {
}
template class PartitionFileSystemMetaCore<impl::PartitionFileSystemFormat>;
template class PartitionFileSystemMetaCore<impl::Sha256PartitionFileSystemFormat>;
Result Sha256PartitionFileSystemMeta::Initialize(fs::IStorage *base_storage, MemoryResource *allocator, const void *hash, size_t hash_size, std::optional<u8> 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<char *>(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<PartitionFileSystemHeader *>(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<PartitionEntry *>(this->buffer + sizeof(PartitionFileSystemHeader));
this->name_table = this->buffer + sizeof(PartitionFileSystemHeader) + entries_size;
/* We initialized. */
this->initialized = true;
return ResultSuccess();
}
}