diff --git a/libraries/libstratosphere/include/stratosphere/ro/ro_types.hpp b/libraries/libstratosphere/include/stratosphere/ro/ro_types.hpp index db9134963..31abdfe59 100644 --- a/libraries/libstratosphere/include/stratosphere/ro/ro_types.hpp +++ b/libraries/libstratosphere/include/stratosphere/ro/ro_types.hpp @@ -86,8 +86,12 @@ namespace ams::ro { return this->num_hashes; } + size_t GetHashesOffset() const { + return this->hashes_offset; + } + uintptr_t GetHashes() const { - return reinterpret_cast(this) + this->hashes_offset; + return reinterpret_cast(this) + this->GetHashesOffset(); } u32 GetKeyGeneration() const { @@ -114,12 +118,16 @@ namespace ams::ro { return reinterpret_cast(std::addressof(this->program_id)); } - size_t GetSignedAreaSize() const; + size_t GetSignedAreaSize() const { + return this->size - this->GetSignedAreaOffset(); + } + + size_t GetSignedAreaOffset() const; }; static_assert(sizeof(NrrHeader) == 0x350, "NrrHeader definition!"); - inline size_t NrrHeader::GetSignedAreaSize() const { - return this->size - OFFSETOF(NrrHeader, program_id); + inline size_t NrrHeader::GetSignedAreaOffset() const { + return OFFSETOF(NrrHeader, program_id); } class NroHeader { diff --git a/stratosphere/ro/source/impl/ro_nrr_utils.cpp b/stratosphere/ro/source/impl/ro_nrr_utils.cpp index 3293a66dc..db398cf7c 100644 --- a/stratosphere/ro/source/impl/ro_nrr_utils.cpp +++ b/stratosphere/ro/source/impl/ro_nrr_utils.cpp @@ -193,7 +193,7 @@ namespace ams::ro::impl { } /* Utilities for working with NRRs. */ - Result MapAndValidateNrr(NrrHeader **out_header, u64 *out_mapped_code_address, Handle process_handle, ncm::ProgramId program_id, u64 nrr_heap_address, u64 nrr_heap_size, ModuleType expected_type, bool enforce_type) { + Result MapAndValidateNrr(NrrHeader **out_header, u64 *out_mapped_code_address, void *out_hash, size_t out_hash_size, Handle process_handle, ncm::ProgramId program_id, u64 nrr_heap_address, u64 nrr_heap_size, ModuleType expected_type, bool enforce_type) { map::MappedCodeMemory nrr_mcm(ResultInternalError{}); /* First, map the NRR. */ @@ -214,6 +214,9 @@ namespace ams::ro::impl { nrr_map.Invalidate(); nrr_mcm.Invalidate(); + /* Save a copy of the hash that we verified. */ + crypto::GenerateSha256Hash(out_hash, out_hash_size, nrr_header->GetSignedArea(), nrr_header->GetSignedAreaSize()); + *out_header = nrr_header; *out_mapped_code_address = code_address; return ResultSuccess(); @@ -225,4 +228,55 @@ namespace ams::ro::impl { return ResultSuccess(); } + bool ValidateNrrHashTableEntry(const void *nrr_hash, const NrrHeader *header, const u8 *hash_table, const void *desired_hash) { + crypto::Sha256Generator sha256; + sha256.Initialize(); + + const size_t size = header->GetSignedAreaSize(); + const size_t pre_hash_table_size = header->GetHashesOffset() - header->GetSignedAreaOffset(); + const size_t num_hashes = header->GetNumHashes(); + + /* Hash data before the hash table. */ + sha256.Update(header->GetSignedArea(), pre_hash_table_size); + + /* Hash the hash table, checking if the desired hash exists inside it. */ + size_t remaining_size = size - pre_hash_table_size; + bool found_hash = false; + for (size_t i = 0; i < num_hashes; i++) { + /* Get the current hash. */ + u8 cur_hash[crypto::Sha256Generator::HashSize]; + std::memcpy(cur_hash, hash_table, sizeof(cur_hash)); + + /* Hash the current hash. */ + sha256.Update(cur_hash, sizeof(cur_hash)); + + /* Check if the current hash is our target. */ + found_hash |= std::memcmp(cur_hash, desired_hash, sizeof(cur_hash)) == 0; + + /* Advance our pointers. */ + hash_table += sizeof(cur_hash); + remaining_size -= sizeof(cur_hash); + } + + /* Data after the hash table should be all zeroes. */ + u8 work_buf[crypto::Sha256Generator::HashSize]; + { + crypto::ClearMemory(work_buf, sizeof(work_buf)); + while (remaining_size > 0) { + const size_t cur_size = std::min(remaining_size, sizeof(work_buf)); + sha256.Update(work_buf, cur_size); + remaining_size -= cur_size; + } + } + + /* Validate the final hash. */ + sha256.GetHash(work_buf, sizeof(work_buf)); + + /* Use & operator to avoid short circuiting. */ + const bool is_valid = found_hash & (std::memcmp(work_buf, nrr_hash, sizeof(work_buf)) == 0); + + /* Return result. */ + return is_valid; + } + } diff --git a/stratosphere/ro/source/impl/ro_nrr_utils.hpp b/stratosphere/ro/source/impl/ro_nrr_utils.hpp index 7bb0018b2..67e9e582d 100644 --- a/stratosphere/ro/source/impl/ro_nrr_utils.hpp +++ b/stratosphere/ro/source/impl/ro_nrr_utils.hpp @@ -20,7 +20,9 @@ namespace ams::ro::impl { /* Utilities for working with NRRs. */ - Result MapAndValidateNrr(NrrHeader **out_header, u64 *out_mapped_code_address, Handle process_handle, ncm::ProgramId program_id, u64 nrr_heap_address, u64 nrr_heap_size, ModuleType expected_type, bool enforce_type); + Result MapAndValidateNrr(NrrHeader **out_header, u64 *out_mapped_code_address, void *out_hash, size_t out_hash_size, Handle process_handle, ncm::ProgramId program_id, u64 nrr_heap_address, u64 nrr_heap_size, ModuleType expected_type, bool enforce_type); Result UnmapNrr(Handle process_handle, const NrrHeader *header, u64 nrr_heap_address, u64 nrr_heap_size, u64 mapped_code_address); + bool ValidateNrrHashTableEntry(const void *nrr_hash, const NrrHeader *header, const u8 *hash_table, const void *desired_hash); + } \ No newline at end of file diff --git a/stratosphere/ro/source/impl/ro_service_impl.cpp b/stratosphere/ro/source/impl/ro_service_impl.cpp index 95e65d167..934d16252 100644 --- a/stratosphere/ro/source/impl/ro_service_impl.cpp +++ b/stratosphere/ro/source/impl/ro_service_impl.cpp @@ -23,28 +23,28 @@ namespace ams::ro::impl { namespace { /* Convenience definitions. */ - constexpr size_t MaxSessions = 0x4; + constexpr size_t MaxSessions = 0x3; /* 2 official sessions (applet + application, 1 homebrew session). */ constexpr size_t MaxNrrInfos = 0x40; constexpr size_t MaxNroInfos = 0x40; /* Types. */ struct Sha256Hash { - u8 hash[SHA256_HASH_SIZE]; + u8 hash[crypto::Sha256Generator::HashSize]; bool operator==(const Sha256Hash &o) const { - return std::memcmp(this, &o, sizeof(*this)) == 0; + return std::memcmp(this, std::addressof(o), sizeof(*this)) == 0; } bool operator!=(const Sha256Hash &o) const { - return std::memcmp(this, &o, sizeof(*this)) != 0; + return std::memcmp(this, std::addressof(o), sizeof(*this)) != 0; } bool operator<(const Sha256Hash &o) const { - return std::memcmp(this, &o, sizeof(*this)) < 0; + return std::memcmp(this, std::addressof(o), sizeof(*this)) < 0; } bool operator>(const Sha256Hash &o) const { - return std::memcmp(this, &o, sizeof(*this)) > 0; + return std::memcmp(this, std::addressof(o), sizeof(*this)) > 0; } }; - static_assert(sizeof(Sha256Hash) == sizeof(Sha256Hash::hash), "Sha256Hash definition!"); + static_assert(sizeof(Sha256Hash) == sizeof(Sha256Hash::hash)); struct NroInfo { u64 base_address; @@ -58,10 +58,12 @@ namespace ams::ro::impl { }; struct NrrInfo { - const NrrHeader *header; + const NrrHeader *mapped_header; u64 nrr_heap_address; u64 nrr_heap_size; u64 mapped_code_address; + NrrHeader cached_header; + Sha256Hash signed_area_hash; }; struct ProcessContext { @@ -154,14 +156,40 @@ namespace ams::ro::impl { Result ValidateHasNroHash(const NroHeader *nro_header) const { /* Calculate hash. */ Sha256Hash hash; - sha256CalculateHash(&hash, nro_header, nro_header->GetSize()); + crypto::GenerateSha256Hash(std::addressof(hash), sizeof(hash), nro_header, nro_header->GetSize()); for (size_t i = 0; i < MaxNrrInfos; i++) { - if (this->nrr_in_use[i]) { - const NrrHeader *nrr_header = this->nrr_infos[i].header; - const Sha256Hash *nro_hashes = reinterpret_cast(nrr_header->GetHashes()); - R_UNLESS(!std::binary_search(nro_hashes, nro_hashes + nrr_header->GetNumHashes(), hash), ResultSuccess()); + /* Ensure we only check NRRs that are used. */ + if (!this->nrr_in_use[i]) { + continue; } + + /* Get the mapped header, ensure that it has hashes. */ + const NrrHeader *cached_nrr_header = std::addressof(this->nrr_infos[i].cached_header); + const NrrHeader *mapped_nrr_header = this->nrr_infos[i].mapped_header; + const size_t mapped_num_hashes = mapped_nrr_header->GetNumHashes(); + if (mapped_num_hashes == 0) { + continue; + } + + /* Locate the hash within the mapped array. */ + const Sha256Hash *mapped_nro_hashes_start = reinterpret_cast(mapped_nrr_header->GetHashes()); + const Sha256Hash *mapped_nro_hashes_end = mapped_nro_hashes_start + mapped_nrr_header->GetNumHashes(); + + const Sha256Hash *mapped_lower_bound = std::lower_bound(mapped_nro_hashes_start, mapped_nro_hashes_end, hash); + if (mapped_lower_bound == mapped_nro_hashes_end || (*mapped_lower_bound != hash)) { + continue; + } + + /* Check that the hash entry is valid, since our heuristic passed. */ + const void *nrr_hash = std::addressof(this->nrr_infos[i].signed_area_hash); + const u8 *hash_table = reinterpret_cast(mapped_nro_hashes_start); + if (!ValidateNrrHashTableEntry(nrr_hash, cached_nrr_header, hash_table, std::addressof(hash))) { + continue; + } + + /* The hash is valid! */ + return ResultSuccess(); } return ResultNotAuthorized(); @@ -288,7 +316,7 @@ namespace ams::ro::impl { if (context->process_handle != INVALID_HANDLE) { for (size_t i = 0; i < MaxNrrInfos; i++) { if (context->nrr_in_use[i]) { - UnmapNrr(context->process_handle, context->nrr_infos[i].header, context->nrr_infos[i].nrr_heap_address, context->nrr_infos[i].nrr_heap_size, context->nrr_infos[i].mapped_code_address); + UnmapNrr(context->process_handle, context->nrr_infos[i].mapped_header, context->nrr_infos[i].nrr_heap_address, context->nrr_infos[i].nrr_heap_size, context->nrr_infos[i].mapped_code_address); } } svcCloseHandle(context->process_handle); @@ -391,17 +419,23 @@ namespace ams::ro::impl { NrrInfo *nrr_info = nullptr; R_TRY(context->GetFreeNrrInfo(&nrr_info)); + /* Prepare to cache the NRR's signature hash. */ + Sha256Hash signed_area_hash; + ON_SCOPE_EXIT { crypto::ClearMemory(std::addressof(signed_area_hash), sizeof(signed_area_hash)); }; + /* Map. */ NrrHeader *header = nullptr; u64 mapped_code_address = 0; - R_TRY(MapAndValidateNrr(&header, &mapped_code_address, context->process_handle, program_id, nrr_address, nrr_size, expected_type, enforce_type)); + R_TRY(MapAndValidateNrr(&header, &mapped_code_address, std::addressof(signed_area_hash), sizeof(signed_area_hash), context->process_handle, program_id, nrr_address, nrr_size, expected_type, enforce_type)); /* Set NRR info. */ context->SetNrrInfoInUse(nrr_info, true); - nrr_info->header = header; + nrr_info->mapped_header = header; nrr_info->nrr_heap_address = nrr_address; nrr_info->nrr_heap_size = nrr_size; nrr_info->mapped_code_address = mapped_code_address; + nrr_info->cached_header = *header; + std::memcpy(std::addressof(nrr_info->signed_area_hash), std::addressof(signed_area_hash), sizeof(signed_area_hash)); return ResultSuccess(); } @@ -425,7 +459,7 @@ namespace ams::ro::impl { context->SetNrrInfoInUse(nrr_info, false); std::memset(nrr_info, 0, sizeof(*nrr_info)); } - return UnmapNrr(context->process_handle, nrr_backup.header, nrr_backup.nrr_heap_address, nrr_backup.nrr_heap_size, nrr_backup.mapped_code_address); + return UnmapNrr(context->process_handle, nrr_backup.mapped_header, nrr_backup.nrr_heap_address, nrr_backup.nrr_heap_size, nrr_backup.mapped_code_address); } Result LoadNro(u64 *out_address, size_t context_id, u64 nro_address, u64 nro_size, u64 bss_address, u64 bss_size) {