From 083b4f0d3b2321d80cb524559b741b435876b6e9 Mon Sep 17 00:00:00 2001 From: Eladash Date: Wed, 12 Jul 2023 20:43:33 +0300 Subject: [PATCH] Patches: Fix potential RPCS3 crashes due to invalid patches --- Utilities/bin_patch.cpp | 95 ++++++++++++++++++++++++++++++---- Utilities/bin_patch.h | 5 +- rpcs3/Emu/Cell/PPUAnalyser.h | 18 ++++--- rpcs3/Emu/Cell/PPUModule.cpp | 16 +++--- rpcs3/Emu/Cell/lv2/sys_spu.cpp | 9 +++- 5 files changed, 114 insertions(+), 29 deletions(-) diff --git a/Utilities/bin_patch.cpp b/Utilities/bin_patch.cpp index 2ac98d81e5..037a085451 100644 --- a/Utilities/bin_patch.cpp +++ b/Utilities/bin_patch.cpp @@ -77,6 +77,7 @@ void fmt_class_string::format(std::string& out, u64 arg) case patch_type::lef32: return "lef32"; case patch_type::lef64: return "lef64"; case patch_type::utf8: return "utf8"; + case patch_type::c_utf8: return "cutf8"; case patch_type::move_file: return "move_file"; case patch_type::hide_file: return "hide_file"; } @@ -805,7 +806,7 @@ void unmap_vm_area(std::shared_ptr& ptr) } // Returns old 'applied' size -static usz apply_modification(std::basic_string& applied, patch_engine::patch_info& patch, std::function mem_translate, u32 filesz, u32 min_addr) +static usz apply_modification(std::basic_string& applied, patch_engine::patch_info& patch, std::function mem_translate, u32 filesz, u32 min_addr) { const usz old_applied_size = applied.size(); @@ -847,7 +848,7 @@ static usz apply_modification(std::basic_string& applied, patch_engine::pat if (p.type != patch_type::alloc) continue; // Do not allow null address or if resultant ptr is not a VM ptr - if (const u32 alloc_at = vm::try_get_addr(mem_translate(p.offset & -4096)).first; alloc_at >> 16) + if (const u32 alloc_at = (p.offset & -4096); alloc_at >> 16) { const u32 alloc_size = utils::align(static_cast(p.value.long_value) + alloc_at % 4096, 4096); @@ -926,7 +927,74 @@ static usz apply_modification(std::basic_string& applied, patch_engine::pat relocate_instructions_at = 0; } - if (!relocate_instructions_at && (offset < min_addr || offset - min_addr >= filesz)) + u32 memory_size = 0; + + switch (p.type) + { + case patch_type::invalid: + case patch_type::load: + { + // Invalid in this context + break; + } + case patch_type::alloc: + { + // Applied before + memory_size = 0; + continue; + } + case patch_type::byte: + { + memory_size = sizeof(u8); + break; + } + case patch_type::le16: + case patch_type::be16: + { + memory_size = sizeof(u16); + break; + } + case patch_type::code_alloc: + case patch_type::jump: + case patch_type::jump_link: + case patch_type::jump_func: + case patch_type::le32: + case patch_type::lef32: + case patch_type::bd32: + case patch_type::be32: + case patch_type::bef32: + { + memory_size = sizeof(u32); + break; + } + case patch_type::lef64: + case patch_type::le64: + case patch_type::bd64: + case patch_type::be64: + case patch_type::bef64: + { + memory_size = sizeof(u64); + break; + } + case patch_type::utf8: + { + memory_size = p.original_value.size(); + break; + } + case patch_type::c_utf8: + { + memory_size = utils::add_saturate(p.original_value.size(), 1); + break; + } + case patch_type::move_file: + case patch_type::hide_file: + { + memory_size = 0; + break; + } + } + + if (memory_size != 0 && !relocate_instructions_at && (filesz < memory_size || offset < min_addr || offset - min_addr > filesz - memory_size)) { // This patch is out of range for this segment continue; @@ -934,9 +1002,9 @@ static usz apply_modification(std::basic_string& applied, patch_engine::pat offset -= min_addr; - auto ptr = mem_translate(offset); + auto ptr = mem_translate(offset, memory_size); - if (!ptr) + if (!ptr && memory_size != 0) { // Memory translation failed continue; @@ -966,7 +1034,7 @@ static usz apply_modification(std::basic_string& applied, patch_engine::pat } case patch_type::code_alloc: { - const u32 out_branch = vm::try_get_addr(mem_translate(offset & -4)).first; + const u32 out_branch = vm::try_get_addr(mem_translate(offset & -4, 4)).first; // Allow only if points to a PPU executable instruction if (out_branch < 0x10000 || out_branch >= 0x4000'0000 || !vm::check_addr<4>(out_branch, vm::page_executable)) @@ -1050,7 +1118,7 @@ static usz apply_modification(std::basic_string& applied, patch_engine::pat case patch_type::jump: case patch_type::jump_link: { - const u32 out_branch = vm::try_get_addr(mem_translate(offset & -4)).first; + const u32 out_branch = vm::try_get_addr(mem_translate(offset & -4, 4)).first; const u32 dest = static_cast(p.value.long_value); // Allow only if points to a PPU executable instruction @@ -1066,7 +1134,7 @@ static usz apply_modification(std::basic_string& applied, patch_engine::pat { const std::string& str = p.original_value; - const u32 out_branch = vm::try_get_addr(mem_translate(offset & -4)).first; + const u32 out_branch = vm::try_get_addr(mem_translate(offset & -4, 4)).first; const usz sep_pos = str.find_first_of(':'); // Must contain only a single ':' or none @@ -1082,7 +1150,7 @@ static usz apply_modification(std::basic_string& applied, patch_engine::pat if (func_name.starts_with("0x"sv)) { - // Raw hexadeciaml-formatted FNID (real function name cannot contain a digit at the start, derived from C/CPP which were used in PS3 development) + // Raw hexadecimal-formatted FNID (real function name cannot contain a digit at the start, derived from C/CPP which were used in PS3 development) const auto result = std::from_chars(func_name.data() + 2, func_name.data() + func_name.size() - 2, id, 16); if (result.ec != std::errc() || str.data() + sep_pos != result.ptr) @@ -1203,6 +1271,11 @@ static usz apply_modification(std::basic_string& applied, patch_engine::pat std::memcpy(ptr, p.original_value.data(), p.original_value.size()); break; } + case patch_type::c_utf8: + { + std::memcpy(ptr, p.original_value.data(), p.original_value.size() + 1); + break; + } case patch_type::move_file: case patch_type::hide_file: { @@ -1257,7 +1330,7 @@ static usz apply_modification(std::basic_string& applied, patch_engine::pat return old_applied_size; } -std::basic_string patch_engine::apply(const std::string& name, std::function mem_translate, u32 filesz, u32 min_addr) +std::basic_string patch_engine::apply(const std::string& name, std::function mem_translate, u32 filesz, u32 min_addr) { if (!m_map.contains(name)) { @@ -1269,7 +1342,7 @@ std::basic_string patch_engine::apply(const std::string& name, std::functio const auto& serial = Emu.GetTitleID(); const auto& app_version = Emu.GetAppVersion(); - // Different containers in order to seperate the patches + // Different containers in order to separate the patches std::vector> patches_for_this_serial_and_this_version; std::vector> patches_for_this_serial_and_all_versions; std::vector> patches_for_all_serials_and_this_version; diff --git a/Utilities/bin_patch.h b/Utilities/bin_patch.h index ff46f59e72..41f675c3f7 100644 --- a/Utilities/bin_patch.h +++ b/Utilities/bin_patch.h @@ -53,13 +53,14 @@ enum class patch_type bef32, bef64, utf8, // Text of string (not null-terminated automatically) + c_utf8, // Text of string (null-terminated automatically) move_file, // Move file hide_file, // Hide file }; static constexpr bool patch_type_uses_hex_offset(patch_type type) { - return type >= patch_type::alloc && type <= patch_type::utf8; + return type >= patch_type::alloc && type <= patch_type::c_utf8; } enum class patch_configurable_type @@ -213,7 +214,7 @@ public: void append_title_patches(const std::string& title_id); // Apply patch (returns the number of entries applied) - std::basic_string apply(const std::string& name, std::function mem_translate, u32 filesz = -1, u32 min_addr = 0); + std::basic_string apply(const std::string& name, std::function mem_translate, u32 filesz = -1, u32 min_addr = 0); // Deallocate memory used by patches void unload(const std::string& name); diff --git a/rpcs3/Emu/Cell/PPUAnalyser.h b/rpcs3/Emu/Cell/PPUAnalyser.h index 81dc157a78..8c5330c2b3 100644 --- a/rpcs3/Emu/Cell/PPUAnalyser.h +++ b/rpcs3/Emu/Cell/PPUAnalyser.h @@ -112,7 +112,7 @@ struct ppu_module void validate(u32 reloc); template - to_be_t* get_ptr(u32 addr) const + to_be_t* get_ptr(u32 addr, u32 size_bytes) const { auto it = addr_to_seg_index.upper_bound(addr); @@ -127,9 +127,7 @@ struct ppu_module const u32 seg_size = seg.size; const u32 seg_addr = seg.addr; - constexpr usz size_element = std::is_void_v ? 0 : sizeof(std::conditional_t, char, T>); - - if (seg_size >= std::max(size_element, 1) && addr <= seg_addr + seg_size - size_element) + if (seg_size >= std::max(size_bytes, 1) && addr <= seg_addr + seg_size - size_bytes) { return reinterpret_cast*>(static_cast(seg.ptr) + (addr - seg_addr)); } @@ -137,10 +135,18 @@ struct ppu_module return nullptr; } - template requires requires (const U& obj) { +obj.addr() * 0; } + template + to_be_t* get_ptr(u32 addr) const + { + constexpr usz size_element = std::is_void_v ? 0 : sizeof(std::conditional_t, char, T>); + return get_ptr(addr, u32{size_element}); + } + + template requires requires (const U& obj) { +obj.size() * 0; } to_be_t* get_ptr(U&& addr) const { - return get_ptr(addr.addr()); + constexpr usz size_element = std::is_void_v ? 0 : sizeof(std::conditional_t, char, T>); + return get_ptr(addr.addr(), u32{size_element}); } }; diff --git a/rpcs3/Emu/Cell/PPUModule.cpp b/rpcs3/Emu/Cell/PPUModule.cpp index ace6c03e30..6dbbf6c195 100644 --- a/rpcs3/Emu/Cell/PPUModule.cpp +++ b/rpcs3/Emu/Cell/PPUModule.cpp @@ -1142,12 +1142,12 @@ static void ppu_check_patch_spu_images(const ppu_module& mod, const ppu_segment& for (const auto& prog : obj.progs) { // Apply the patch - applied += g_fxo->get().apply(hash, [&](u32 addr) { return addr + elf_header + prog.p_offset; }, prog.p_filesz, prog.p_vaddr); + applied += g_fxo->get().apply(hash, [&](u32 addr, u32 /*size*/) { return addr + elf_header + prog.p_offset; }, prog.p_filesz, prog.p_vaddr); if (!Emu.GetTitleID().empty()) { // Alternative patch - applied += g_fxo->get().apply(Emu.GetTitleID() + '-' + hash, [&](u32 addr) { return addr + elf_header + prog.p_offset; }, prog.p_filesz, prog.p_vaddr); + applied += g_fxo->get().apply(Emu.GetTitleID() + '-' + hash, [&](u32 addr, u32 /*size*/) { return addr + elf_header + prog.p_offset; }, prog.p_filesz, prog.p_vaddr); } } @@ -1617,12 +1617,12 @@ std::shared_ptr ppu_load_prx(const ppu_prx_object& elf, bool virtual_lo const std::string hash_seg = fmt::format("%s-%u", hash, i); // Apply the patch - auto _applied = g_fxo->get().apply(hash_seg, [&](u32 addr) { return prx->get_ptr(addr + seg.addr); }, seg.size); + auto _applied = g_fxo->get().apply(hash_seg, [&](u32 addr, u32 size) { return prx->get_ptr(addr + seg.addr, size); }, seg.size); if (!Emu.GetTitleID().empty()) { // Alternative patch - _applied += g_fxo->get().apply(Emu.GetTitleID() + '-' + hash_seg, [&](u32 addr) { return prx->get_ptr(addr + seg.addr); }, seg.size); + _applied += g_fxo->get().apply(Emu.GetTitleID() + '-' + hash_seg, [&](u32 addr, u32 size) { return prx->get_ptr(addr + seg.addr, size); }, seg.size); } // Rebase patch offsets @@ -1933,12 +1933,12 @@ bool ppu_load_exec(const ppu_exec_object& elf, bool virtual_load, const std::str Emu.SetExecutableHash(hash); // Apply the patch - auto applied = g_fxo->get().apply(!ar ? hash : std::string{}, [&](u32 addr) { return _main.get_ptr(addr); }); + auto applied = g_fxo->get().apply(!ar ? hash : std::string{}, [&](u32 addr, u32 size) { return _main.get_ptr(addr, size); }); if (!ar && !Emu.GetTitleID().empty()) { // Alternative patch - applied += g_fxo->get().apply(Emu.GetTitleID() + '-' + hash, [&](u32 addr) { return _main.get_ptr(addr); }); + applied += g_fxo->get().apply(Emu.GetTitleID() + '-' + hash, [&](u32 addr, u32 size) { return _main.get_ptr(addr, size); }); } if (applied.empty()) @@ -2571,12 +2571,12 @@ std::pair, CellError> ppu_load_overlay(const ppu_ex } // Apply the patch - auto applied = g_fxo->get().apply(hash, [ovlm](u32 addr) { return ovlm->get_ptr(addr); }); + auto applied = g_fxo->get().apply(hash, [ovlm](u32 addr, u32 size) { return ovlm->get_ptr(addr, size); }); if (!Emu.GetTitleID().empty()) { // Alternative patch - applied += g_fxo->get().apply(Emu.GetTitleID() + '-' + hash, [ovlm](u32 addr) { return ovlm->get_ptr(addr); }); + applied += g_fxo->get().apply(Emu.GetTitleID() + '-' + hash, [ovlm](u32 addr, u32 size) { return ovlm->get_ptr(addr, size); }); } // Embedded SPU elf patching diff --git a/rpcs3/Emu/Cell/lv2/sys_spu.cpp b/rpcs3/Emu/Cell/lv2/sys_spu.cpp index 5e1ced65a2..d3cb822239 100644 --- a/rpcs3/Emu/Cell/lv2/sys_spu.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_spu.cpp @@ -184,13 +184,18 @@ void sys_spu_image::deploy(u8* loc, std::span segs, bool hash[5 + i * 2] = pal[sha1_hash[i] & 15]; } + auto mem_translate = [loc](u32 addr, u32 size) + { + return utils::add_saturate(addr, size) <= SPU_LS_SIZE ? loc + addr : nullptr; + }; + // Apply the patch - auto applied = g_fxo->get().apply(hash, [loc](u32 addr) { return loc + addr; }); + auto applied = g_fxo->get().apply(hash, mem_translate); if (!Emu.GetTitleID().empty()) { // Alternative patch - applied += g_fxo->get().apply(Emu.GetTitleID() + '-' + hash, [loc](u32 addr) { return loc + addr; }); + applied += g_fxo->get().apply(Emu.GetTitleID() + '-' + hash, mem_translate); } (is_verbose ? spu_log.notice : sys_spu.trace)("Loaded SPU image: %s (<- %u)%s", hash, applied.size(), dump);