ro: add missing fixes

This commit is contained in:
hexkyz
2026-09-25 01:33:40 +01:00
parent 788546f476
commit 2ae182bd5d
2 changed files with 56 additions and 46 deletions

View File

@@ -40,29 +40,31 @@ namespace ams::util {
/* It's similar to the official lz4frame code, but has additional constraints and uses static work buffers. */ /* It's similar to the official lz4frame code, but has additional constraints and uses static work buffers. */
/* Evaluate if we should use lz4frame instead and implement matching frame compression. */ /* Evaluate if we should use lz4frame instead and implement matching frame compression. */
int DecompressLZ4Frame(size_t* out_size, void *dst, size_t dst_size, const void *src, size_t src_size, void *src_work, size_t src_work_size, void *dst_work, size_t dst_work_size) { int DecompressLZ4Frame(size_t* out_size, void *dst, size_t dst_size, const void *src, size_t src_size, void *src_work, size_t src_work_size, void *dst_work, size_t dst_work_size) {
const u8 *src_buffer = reinterpret_cast<const u8 *>(src);
u8 *dst_buffer = reinterpret_cast<u8 *>(dst);
/* Destination buffer must be behind source buffer. */ /* Destination buffer must be behind source buffer. */
if (dst > src) { if (dst_buffer > src_buffer) {
std::memset(dst, 0, dst_size); std::memset(dst, 0, dst_size);
return -1; return -1;
} }
const char *src_end = static_cast<const char *>(src) + src_size; /* Size checks. */
const char *dst_end = static_cast<const char *>(dst) + dst_size; if (((src_buffer + src_size) != (dst_buffer + dst_size)) || (src_work_size != 0x10000) || (dst_work_size != 0x10000)) {
if ((src_end != dst_end) || (src_work_size != 0x10000) || (dst_work_size != 0x10000)) {
std::memset(dst, 0, dst_size); std::memset(dst, 0, dst_size);
return -1; return -1;
} }
/* Source size must at least include one frame header. */
if (src_size < 7) { if (src_size < 7) {
std::memset(dst, 0, dst_size); std::memset(dst, 0, dst_size);
return -2; return -2;
} }
const uint32_t magic = *reinterpret_cast<const uint32_t *>(src); const u32 magic = *reinterpret_cast<const u32 *>(src_buffer);
const uint8_t flg = *reinterpret_cast<const uint8_t *>(src) + 4; const u8 flg = src_buffer[4];
const uint8_t bd = *reinterpret_cast<const uint8_t *>(src) + 5; const u8 bd = src_buffer[5];
const uint8_t header_checksum = *reinterpret_cast<const uint8_t *>(src) + 6; const u8 header_checksum = src_buffer[6];
/* Check for LZ4F_MAGICNUMBER, validate flags and block size. */ /* Check for LZ4F_MAGICNUMBER, validate flags and block size. */
if ((magic != 0x184D2204) || ((flg & 1) != 0) || (flg != 0x60) || (bd != 0x40)) { if ((magic != 0x184D2204) || ((flg & 1) != 0) || (flg != 0x60) || (bd != 0x40)) {
@@ -73,8 +75,8 @@ namespace ams::util {
/* TODO: Implement header checksum validation. This requires xxhash, do we want to have xxhash in util? */ /* TODO: Implement header checksum validation. This requires xxhash, do we want to have xxhash in util? */
AMS_UNUSED(header_checksum); AMS_UNUSED(header_checksum);
uint32_t src_pos = 7; /* Skip the header. */ u32 src_pos = 7; /* Skip the header. */
uint32_t dst_pos = 0; u32 dst_pos = 0;
/* Destination size is too small. */ /* Destination size is too small. */
if (dst_size < src_pos) { if (dst_size < src_pos) {
@@ -89,17 +91,17 @@ namespace ams::util {
return -4; return -4;
} }
const char* src_data_block = reinterpret_cast<const char *>(src) + src_pos; src_buffer += src_pos;
char* dst_data_block = reinterpret_cast<char *>(dst) + dst_pos; dst_buffer += dst_pos;
const uint32_t block_header = *reinterpret_cast<const uint32_t *>(src_data_block); const u32 block_header = *reinterpret_cast<const u32 *>(src_buffer);
/* We've reached the end. */ /* We've reached the end. */
if (!block_header) { if (!block_header) {
break; break;
} }
const uint32_t block_size = block_header & 0x7FFFFFFF; const u32 block_size = block_header & 0x7FFFFFFF;
/* Block size is invalid. */ /* Block size is invalid. */
if (block_size > 0x10000) { if (block_size > 0x10000) {
@@ -108,20 +110,20 @@ namespace ams::util {
} }
/* Advance the data block. */ /* Advance the data block. */
src_data_block += sizeof(block_header); src_buffer += sizeof(block_header);
/* Block size is invalid. */ /* Block size is invalid. */
if ((dst_end - src_data_block) < block_size) { if (((dst_buffer + dst_size) - src_buffer) < block_size) {
std::memset(dst, 0, dst_size); std::memset(dst, 0, dst_size);
return -4; return -4;
} }
/* Copy the data block to the source work buffer. */ /* Copy the data block to the source work buffer. */
std::memcpy(static_cast<char *>(src_work), src_data_block, block_size); std::memcpy(static_cast<u8 *>(src_work), src_buffer, block_size);
if ((block_header & 0x80000000) != 0) { if ((block_header & 0x80000000) != 0) {
/* Block size is invalid. */ /* Block size is invalid. */
if ((dst_end - dst_data_block) < block_size) { if (dst_size < block_size) {
std::memset(dst, 0, dst_size); std::memset(dst, 0, dst_size);
return -4; return -4;
} }
@@ -136,10 +138,10 @@ namespace ams::util {
} }
/* This is a raw block, copy it. */ /* This is a raw block, copy it. */
std::memmove(dst_data_block, src_data_block, block_size); std::memmove(dst_buffer, src_buffer, block_size);
} else { } else {
/* This is a compressed block, decompress it. */ /* This is a compressed block, decompress it. */
int decompressed_size = LZ4_decompress_safe(reinterpret_cast<const char *>(src_work), reinterpret_cast<char *>(dst_work), static_cast<int>(src_work_size), static_cast<int>(dst_work_size)); int decompressed_size = LZ4_decompress_safe(reinterpret_cast<const char *>(src_work), reinterpret_cast<char *>(dst_work), static_cast<int>(block_size), 0x10000);
/* Decompressed size is invalid. */ /* Decompressed size is invalid. */
if (decompressed_size < 1) { if (decompressed_size < 1) {
@@ -148,7 +150,7 @@ namespace ams::util {
} }
/* Decompressed size is invalid. */ /* Decompressed size is invalid. */
if ((dst_end - dst_data_block) < decompressed_size) { if (dst_size < static_cast<size_t>(decompressed_size)) {
std::memset(dst, 0, dst_size); std::memset(dst, 0, dst_size);
return -4; return -4;
} }
@@ -163,7 +165,7 @@ namespace ams::util {
} }
/* Copy the decompressed data back. */ /* Copy the decompressed data back. */
std::memcpy(dst_data_block, static_cast<char *>(dst_work), decompressed_size); std::memcpy(dst_buffer, static_cast<u8 *>(dst_work), decompressed_size);
} }
/* We've exceeded the destination's size. */ /* We've exceeded the destination's size. */

View File

@@ -136,6 +136,9 @@ namespace ams::ro::impl {
m_process_id = os::InvalidProcessId; m_process_id = os::InvalidProcessId;
m_in_use = false; m_in_use = false;
std::memset(m_decompression_src_work_buffer, 0, sizeof(m_decompression_src_work_buffer));
std::memset(m_decompression_dst_work_buffer, 0, sizeof(m_decompression_dst_work_buffer));
} }
os::NativeHandle GetProcessHandle() const { os::NativeHandle GetProcessHandle() const {
@@ -365,7 +368,7 @@ namespace ams::ro::impl {
R_SUCCEED(); R_SUCCEED();
} }
Result DecompressNro(u8 *mapped_nro, u64 expected_nro_size, const NroHeader *nro_header, u32 compressed_size) { Result DecompressNro(u8 *mapped_nro, u64 total_size, const NroHeader *nro_header, u32 compressed_size) {
/* Read sizes from header. */ /* Read sizes from header. */
const u64 rw_ofs = nro_header->GetRwOffset(); const u64 rw_ofs = nro_header->GetRwOffset();
const u64 rw_size = nro_header->GetRwSize(); const u64 rw_size = nro_header->GetRwSize();
@@ -373,10 +376,10 @@ namespace ams::ro::impl {
/* Divide the mapped NRO into regions devised for the decompression. */ /* Divide the mapped NRO into regions devised for the decompression. */
u8 *nro_aligned_start = static_cast<u8 *>(mapped_nro + 0x1000); u8 *nro_aligned_start = static_cast<u8 *>(mapped_nro + 0x1000);
const u8 *nro_expected_end = static_cast<const u8 *>(mapped_nro + expected_nro_size); const u8 *nro_expected_end = static_cast<const u8 *>(mapped_nro + total_size);
const u64 nro_src_buffer_size = compressed_size - 0x1000; const u64 nro_src_buffer_size = compressed_size - 0x1000;
u8 *nro_src_buffer = static_cast<u8 *>(mapped_nro + expected_nro_size - nro_src_buffer_size); u8 *nro_src_buffer = static_cast<u8 *>(mapped_nro + total_size - nro_src_buffer_size);
const u64 nro_dst_buffer_size = expected_nro_size - 0xC00; const u64 nro_dst_buffer_size = total_size - 0xC00;
u8 *nro_dst_buffer = static_cast<u8 *>(mapped_nro + 0xC00); u8 *nro_dst_buffer = static_cast<u8 *>(mapped_nro + 0xC00);
/* Copy to source buffer. */ /* Copy to source buffer. */
@@ -384,7 +387,7 @@ namespace ams::ro::impl {
/* Decompress. */ /* Decompress. */
u64 decompressed_size = 0; u64 decompressed_size = 0;
R_UNLESS(util::DecompressLZ4Frame(std::addressof(decompressed_size), nro_dst_buffer, nro_dst_buffer_size, nro_src_buffer, nro_src_buffer_size, m_decompression_src_work_buffer, sizeof(m_decompression_src_work_buffer), m_decompression_dst_work_buffer, sizeof(m_decompression_dst_work_buffer)), ro::ResultInvalidNro()); R_UNLESS(util::DecompressLZ4Frame(std::addressof(decompressed_size), nro_dst_buffer, nro_dst_buffer_size, nro_src_buffer, nro_src_buffer_size, m_decompression_src_work_buffer, sizeof(m_decompression_src_work_buffer), m_decompression_dst_work_buffer, sizeof(m_decompression_dst_work_buffer)) == 0, ro::ResultInvalidNro());
/* Find the BSS. */ /* Find the BSS. */
u8 *nro_expected_bss_start = static_cast<u8 *>(mapped_nro + rw_ofs + rw_size); u8 *nro_expected_bss_start = static_cast<u8 *>(mapped_nro + rw_ofs + rw_size);
@@ -423,15 +426,15 @@ namespace ams::ro::impl {
R_SUCCEED(); R_SUCCEED();
} }
Result ParseNro(ModuleId *out_module_id, u64 *out_rx_size, u64 *out_ro_size, u64 *out_rw_size, bool *out_aligned_header, bool *out_is_compress, u64 base_address, u64 expected_nro_size, u64 expected_bss_size) { Result ParseNro(ModuleId *out_module_id, u64 *out_rx_size, u64 *out_ro_size, u64 *out_rw_size, u64 *out_bss_size, bool *out_aligned_header, bool *out_is_compress, u64 base_address, u64 total_size, u64 expected_nro_size, u64 expected_bss_size) {
/* Map the NRO. */ /* Map the NRO. */
void *mapped_memory = nullptr; void *mapped_memory = nullptr;
R_TRY_CATCH(os::MapProcessMemory(std::addressof(mapped_memory), m_process_handle, base_address, expected_nro_size, ro::impl::GenerateSecureRandom)) { R_TRY_CATCH(os::MapProcessMemory(std::addressof(mapped_memory), m_process_handle, base_address, total_size, ro::impl::GenerateSecureRandom)) {
R_CONVERT(os::ResultOutOfAddressSpace, ro::ResultOutOfAddressSpace()) R_CONVERT(os::ResultOutOfAddressSpace, ro::ResultOutOfAddressSpace())
} R_END_TRY_CATCH; } R_END_TRY_CATCH;
/* When we're done, unmap the memory. */ /* When we're done, unmap the memory. */
ON_SCOPE_EXIT { os::UnmapProcessMemory(mapped_memory, m_process_handle, base_address, expected_nro_size); }; ON_SCOPE_EXIT { os::UnmapProcessMemory(mapped_memory, m_process_handle, base_address, total_size); };
/* ValidateNro */ /* ValidateNro */
NroHeader *nro_header = nullptr; NroHeader *nro_header = nullptr;
@@ -441,7 +444,7 @@ namespace ams::ro::impl {
/* DecompressNro */ /* DecompressNro */
if (nro_header->IsCompress()) { if (nro_header->IsCompress()) {
R_TRY(this->DecompressNro(static_cast<u8 *>(mapped_memory), expected_nro_size, nro_header, additional_nro_header_info.compressed_size)); R_TRY(this->DecompressNro(static_cast<u8 *>(mapped_memory), total_size, nro_header, additional_nro_header_info.compressed_size));
/* CheckAdditionalHeaderHash */ /* CheckAdditionalHeaderHash */
if (nro_header->GetAdditionalHeaderOffset() != 0) { if (nro_header->GetAdditionalHeaderOffset() != 0) {
@@ -458,6 +461,7 @@ namespace ams::ro::impl {
*out_rx_size = nro_header->GetTextSize(); *out_rx_size = nro_header->GetTextSize();
*out_ro_size = nro_header->GetRoSize(); *out_ro_size = nro_header->GetRoSize();
*out_rw_size = nro_header->GetRwSize(); *out_rw_size = nro_header->GetRwSize();
*out_bss_size = nro_header->GetBssSize();
*out_aligned_header = nro_header->IsAlignedHeader(); *out_aligned_header = nro_header->IsAlignedHeader();
*out_is_compress = nro_header->IsCompress(); *out_is_compress = nro_header->IsCompress();
R_SUCCEED(); R_SUCCEED();
@@ -706,13 +710,13 @@ namespace ams::ro::impl {
ON_RESULT_FAILURE { R_DISCARD(UnmapNro(context->GetProcessHandle(), nro_info->base_address, nro_address, nro_size, bss_address, bss_size)); }; ON_RESULT_FAILURE { R_DISCARD(UnmapNro(context->GetProcessHandle(), nro_info->base_address, nro_address, nro_size, bss_address, bss_size)); };
/* Parse the NRO. */ /* Parse the NRO. */
u64 rx_size = 0, ro_size = 0, rw_size = 0; u64 nro_rx_size = 0, nro_ro_size = 0, nro_rw_size = 0, nro_bss_size = 0;
bool aligned_header = false; bool nro_aligned_header = false;
bool is_compress = false; bool nro_is_compress = false;
R_TRY(context->ParseNro(std::addressof(nro_info->module_id), std::addressof(rx_size), std::addressof(ro_size), std::addressof(rw_size), std::addressof(aligned_header), std::addressof(is_compress), nro_info->base_address, nro_size, bss_size)); R_TRY(context->ParseNro(std::addressof(nro_info->module_id), std::addressof(nro_rx_size), std::addressof(nro_ro_size), std::addressof(nro_rw_size), std::addressof(nro_bss_size), std::addressof(nro_aligned_header), std::addressof(nro_is_compress), nro_info->base_address, total_size, nro_size, bss_size));
/* Set NRO perms. */ /* Set NRO perms. */
R_TRY(SetNroPerms(context->GetProcessHandle(), nro_info->base_address, rx_size, ro_size, rw_size + bss_size, aligned_header)); R_TRY(SetNroPerms(context->GetProcessHandle(), nro_info->base_address, nro_rx_size, nro_ro_size, nro_rw_size + nro_bss_size, nro_aligned_header));
context->SetNroInfoInUse(nro_info, true); context->SetNroInfoInUse(nro_info, true);
nro_info->nro_heap_address = nro_address; nro_info->nro_heap_address = nro_address;
@@ -720,14 +724,14 @@ namespace ams::ro::impl {
nro_info->bss_heap_address = bss_address; nro_info->bss_heap_address = bss_address;
nro_info->bss_heap_size = bss_size; nro_info->bss_heap_size = bss_size;
if (hos::GetVersion() >= hos::Version_23_0_0) { if (hos::GetVersion() >= hos::Version_23_0_0) {
nro_info->code_size = rx_size; nro_info->code_size = nro_rx_size;
nro_info->ro_size = ro_size; nro_info->ro_size = nro_ro_size;
} else { } else {
nro_info->code_size = rx_size + ro_size; nro_info->code_size = nro_rx_size + nro_ro_size;
nro_info->ro_size = 0; nro_info->ro_size = 0;
} }
nro_info->rw_size = rw_size; nro_info->rw_size = nro_rw_size;
nro_info->is_compress = is_compress; nro_info->is_compress = nro_is_compress;
*out_address = nro_info->base_address; *out_address = nro_info->base_address;
R_SUCCEED(); R_SUCCEED();
} }
@@ -770,8 +774,12 @@ namespace ams::ro::impl {
context->SetNroInfoInUse(nro_info, false); context->SetNroInfoInUse(nro_info, false);
std::memset(nro_info, 0, sizeof(*nro_info)); std::memset(nro_info, 0, sizeof(*nro_info));
} }
if (hos::GetVersion() >= hos::Version_23_0_0) {
R_RETURN(UnmapNro(context->GetProcessHandle(), nro_backup.base_address, nro_backup.nro_heap_address, nro_backup.nro_heap_size, nro_backup.bss_heap_address, nro_backup.bss_heap_size));
} else {
R_RETURN(UnmapNro(context->GetProcessHandle(), nro_backup.base_address, nro_backup.nro_heap_address, nro_backup.code_size + nro_backup.rw_size, nro_backup.bss_heap_address, nro_backup.bss_heap_size)); R_RETURN(UnmapNro(context->GetProcessHandle(), nro_backup.base_address, nro_backup.nro_heap_address, nro_backup.code_size + nro_backup.rw_size, nro_backup.bss_heap_address, nro_backup.bss_heap_size));
} }
}
/* Debug service implementations. */ /* Debug service implementations. */
Result GetProcessModuleInfo(u32 *out_count, ldr::ModuleInfo *out_infos, size_t max_out_count, os::ProcessId process_id) { Result GetProcessModuleInfo(u32 *out_count, ldr::ModuleInfo *out_infos, size_t max_out_count, os::ProcessId process_id) {