mirror of
https://github.com/Atmosphere-NX/Atmosphere.git
synced 2024-11-10 06:01:52 +00:00
ro: implement 9.1.0 security measures
This commit is contained in:
parent
f427c3d7a5
commit
ba8c77fec6
4 changed files with 121 additions and 23 deletions
|
@ -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<uintptr_t>(this) + this->hashes_offset;
|
||||
return reinterpret_cast<uintptr_t>(this) + this->GetHashesOffset();
|
||||
}
|
||||
|
||||
u32 GetKeyGeneration() const {
|
||||
|
@ -114,12 +118,16 @@ namespace ams::ro {
|
|||
return reinterpret_cast<const u8 *>(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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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<const Sha256Hash *>(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<const Sha256Hash *>(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<const u8 *>(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) {
|
||||
|
|
Loading…
Reference in a new issue