1
0
mirror of https://github.com/RPCS3/rpcs3.git synced 2024-11-22 02:32:36 +01:00

Patches: Fix potential RPCS3 crashes due to invalid patches

This commit is contained in:
Eladash 2023-07-12 20:43:33 +03:00 committed by Elad Ashkenazi
parent c0280b43f2
commit 083b4f0d3b
5 changed files with 114 additions and 29 deletions

View File

@ -77,6 +77,7 @@ void fmt_class_string<patch_type>::format(std::string& out, u64 arg)
case patch_type::lef32: return "lef32"; case patch_type::lef32: return "lef32";
case patch_type::lef64: return "lef64"; case patch_type::lef64: return "lef64";
case patch_type::utf8: return "utf8"; case patch_type::utf8: return "utf8";
case patch_type::c_utf8: return "cutf8";
case patch_type::move_file: return "move_file"; case patch_type::move_file: return "move_file";
case patch_type::hide_file: return "hide_file"; case patch_type::hide_file: return "hide_file";
} }
@ -805,7 +806,7 @@ void unmap_vm_area(std::shared_ptr<vm::block_t>& ptr)
} }
// Returns old 'applied' size // Returns old 'applied' size
static usz apply_modification(std::basic_string<u32>& applied, patch_engine::patch_info& patch, std::function<u8*(u32)> mem_translate, u32 filesz, u32 min_addr) static usz apply_modification(std::basic_string<u32>& applied, patch_engine::patch_info& patch, std::function<u8*(u32, u32)> mem_translate, u32 filesz, u32 min_addr)
{ {
const usz old_applied_size = applied.size(); const usz old_applied_size = applied.size();
@ -847,7 +848,7 @@ static usz apply_modification(std::basic_string<u32>& applied, patch_engine::pat
if (p.type != patch_type::alloc) continue; if (p.type != patch_type::alloc) continue;
// Do not allow null address or if resultant ptr is not a VM ptr // 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<u32>(p.value.long_value) + alloc_at % 4096, 4096); const u32 alloc_size = utils::align(static_cast<u32>(p.value.long_value) + alloc_at % 4096, 4096);
@ -926,7 +927,74 @@ static usz apply_modification(std::basic_string<u32>& applied, patch_engine::pat
relocate_instructions_at = 0; 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<u32>(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 // This patch is out of range for this segment
continue; continue;
@ -934,9 +1002,9 @@ static usz apply_modification(std::basic_string<u32>& applied, patch_engine::pat
offset -= min_addr; 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 // Memory translation failed
continue; continue;
@ -966,7 +1034,7 @@ static usz apply_modification(std::basic_string<u32>& applied, patch_engine::pat
} }
case patch_type::code_alloc: 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 // 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)) 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<u32>& applied, patch_engine::pat
case patch_type::jump: case patch_type::jump:
case patch_type::jump_link: 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<u32>(p.value.long_value); const u32 dest = static_cast<u32>(p.value.long_value);
// Allow only if points to a PPU executable instruction // Allow only if points to a PPU executable instruction
@ -1066,7 +1134,7 @@ static usz apply_modification(std::basic_string<u32>& applied, patch_engine::pat
{ {
const std::string& str = p.original_value; 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(':'); const usz sep_pos = str.find_first_of(':');
// Must contain only a single ':' or none // Must contain only a single ':' or none
@ -1082,7 +1150,7 @@ static usz apply_modification(std::basic_string<u32>& applied, patch_engine::pat
if (func_name.starts_with("0x"sv)) 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); 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) if (result.ec != std::errc() || str.data() + sep_pos != result.ptr)
@ -1203,6 +1271,11 @@ static usz apply_modification(std::basic_string<u32>& applied, patch_engine::pat
std::memcpy(ptr, p.original_value.data(), p.original_value.size()); std::memcpy(ptr, p.original_value.data(), p.original_value.size());
break; 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::move_file:
case patch_type::hide_file: case patch_type::hide_file:
{ {
@ -1257,7 +1330,7 @@ static usz apply_modification(std::basic_string<u32>& applied, patch_engine::pat
return old_applied_size; return old_applied_size;
} }
std::basic_string<u32> patch_engine::apply(const std::string& name, std::function<u8*(u32)> mem_translate, u32 filesz, u32 min_addr) std::basic_string<u32> patch_engine::apply(const std::string& name, std::function<u8*(u32, u32)> mem_translate, u32 filesz, u32 min_addr)
{ {
if (!m_map.contains(name)) if (!m_map.contains(name))
{ {
@ -1269,7 +1342,7 @@ std::basic_string<u32> patch_engine::apply(const std::string& name, std::functio
const auto& serial = Emu.GetTitleID(); const auto& serial = Emu.GetTitleID();
const auto& app_version = Emu.GetAppVersion(); const auto& app_version = Emu.GetAppVersion();
// Different containers in order to seperate the patches // Different containers in order to separate the patches
std::vector<std::shared_ptr<patch_info>> patches_for_this_serial_and_this_version; std::vector<std::shared_ptr<patch_info>> patches_for_this_serial_and_this_version;
std::vector<std::shared_ptr<patch_info>> patches_for_this_serial_and_all_versions; std::vector<std::shared_ptr<patch_info>> patches_for_this_serial_and_all_versions;
std::vector<std::shared_ptr<patch_info>> patches_for_all_serials_and_this_version; std::vector<std::shared_ptr<patch_info>> patches_for_all_serials_and_this_version;

View File

@ -53,13 +53,14 @@ enum class patch_type
bef32, bef32,
bef64, bef64,
utf8, // Text of string (not null-terminated automatically) utf8, // Text of string (not null-terminated automatically)
c_utf8, // Text of string (null-terminated automatically)
move_file, // Move file move_file, // Move file
hide_file, // Hide file hide_file, // Hide file
}; };
static constexpr bool patch_type_uses_hex_offset(patch_type type) 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 enum class patch_configurable_type
@ -213,7 +214,7 @@ public:
void append_title_patches(const std::string& title_id); void append_title_patches(const std::string& title_id);
// Apply patch (returns the number of entries applied) // Apply patch (returns the number of entries applied)
std::basic_string<u32> apply(const std::string& name, std::function<u8*(u32)> mem_translate, u32 filesz = -1, u32 min_addr = 0); std::basic_string<u32> apply(const std::string& name, std::function<u8*(u32, u32)> mem_translate, u32 filesz = -1, u32 min_addr = 0);
// Deallocate memory used by patches // Deallocate memory used by patches
void unload(const std::string& name); void unload(const std::string& name);

View File

@ -112,7 +112,7 @@ struct ppu_module
void validate(u32 reloc); void validate(u32 reloc);
template <typename T> template <typename T>
to_be_t<T>* get_ptr(u32 addr) const to_be_t<T>* get_ptr(u32 addr, u32 size_bytes) const
{ {
auto it = addr_to_seg_index.upper_bound(addr); 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_size = seg.size;
const u32 seg_addr = seg.addr; const u32 seg_addr = seg.addr;
constexpr usz size_element = std::is_void_v<T> ? 0 : sizeof(std::conditional_t<std::is_void_v<T>, char, T>); if (seg_size >= std::max<usz>(size_bytes, 1) && addr <= seg_addr + seg_size - size_bytes)
if (seg_size >= std::max<usz>(size_element, 1) && addr <= seg_addr + seg_size - size_element)
{ {
return reinterpret_cast<to_be_t<T>*>(static_cast<u8*>(seg.ptr) + (addr - seg_addr)); return reinterpret_cast<to_be_t<T>*>(static_cast<u8*>(seg.ptr) + (addr - seg_addr));
} }
@ -137,10 +135,18 @@ struct ppu_module
return nullptr; return nullptr;
} }
template <typename T, typename U> requires requires (const U& obj) { +obj.addr() * 0; } template <typename T>
to_be_t<T>* get_ptr(u32 addr) const
{
constexpr usz size_element = std::is_void_v<T> ? 0 : sizeof(std::conditional_t<std::is_void_v<T>, char, T>);
return get_ptr<T>(addr, u32{size_element});
}
template <typename T, typename U> requires requires (const U& obj) { +obj.size() * 0; }
to_be_t<T>* get_ptr(U&& addr) const to_be_t<T>* get_ptr(U&& addr) const
{ {
return get_ptr<T>(addr.addr()); constexpr usz size_element = std::is_void_v<T> ? 0 : sizeof(std::conditional_t<std::is_void_v<T>, char, T>);
return get_ptr<T>(addr.addr(), u32{size_element});
} }
}; };

View File

@ -1142,12 +1142,12 @@ static void ppu_check_patch_spu_images(const ppu_module& mod, const ppu_segment&
for (const auto& prog : obj.progs) for (const auto& prog : obj.progs)
{ {
// Apply the patch // Apply the patch
applied += g_fxo->get<patch_engine>().apply(hash, [&](u32 addr) { return addr + elf_header + prog.p_offset; }, prog.p_filesz, prog.p_vaddr); applied += g_fxo->get<patch_engine>().apply(hash, [&](u32 addr, u32 /*size*/) { return addr + elf_header + prog.p_offset; }, prog.p_filesz, prog.p_vaddr);
if (!Emu.GetTitleID().empty()) if (!Emu.GetTitleID().empty())
{ {
// Alternative patch // Alternative patch
applied += g_fxo->get<patch_engine>().apply(Emu.GetTitleID() + '-' + hash, [&](u32 addr) { return addr + elf_header + prog.p_offset; }, prog.p_filesz, prog.p_vaddr); applied += g_fxo->get<patch_engine>().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<lv2_prx> ppu_load_prx(const ppu_prx_object& elf, bool virtual_lo
const std::string hash_seg = fmt::format("%s-%u", hash, i); const std::string hash_seg = fmt::format("%s-%u", hash, i);
// Apply the patch // Apply the patch
auto _applied = g_fxo->get<patch_engine>().apply(hash_seg, [&](u32 addr) { return prx->get_ptr<u8>(addr + seg.addr); }, seg.size); auto _applied = g_fxo->get<patch_engine>().apply(hash_seg, [&](u32 addr, u32 size) { return prx->get_ptr<u8>(addr + seg.addr, size); }, seg.size);
if (!Emu.GetTitleID().empty()) if (!Emu.GetTitleID().empty())
{ {
// Alternative patch // Alternative patch
_applied += g_fxo->get<patch_engine>().apply(Emu.GetTitleID() + '-' + hash_seg, [&](u32 addr) { return prx->get_ptr<u8>(addr + seg.addr); }, seg.size); _applied += g_fxo->get<patch_engine>().apply(Emu.GetTitleID() + '-' + hash_seg, [&](u32 addr, u32 size) { return prx->get_ptr<u8>(addr + seg.addr, size); }, seg.size);
} }
// Rebase patch offsets // 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); Emu.SetExecutableHash(hash);
// Apply the patch // Apply the patch
auto applied = g_fxo->get<patch_engine>().apply(!ar ? hash : std::string{}, [&](u32 addr) { return _main.get_ptr<u8>(addr); }); auto applied = g_fxo->get<patch_engine>().apply(!ar ? hash : std::string{}, [&](u32 addr, u32 size) { return _main.get_ptr<u8>(addr, size); });
if (!ar && !Emu.GetTitleID().empty()) if (!ar && !Emu.GetTitleID().empty())
{ {
// Alternative patch // Alternative patch
applied += g_fxo->get<patch_engine>().apply(Emu.GetTitleID() + '-' + hash, [&](u32 addr) { return _main.get_ptr<u8>(addr); }); applied += g_fxo->get<patch_engine>().apply(Emu.GetTitleID() + '-' + hash, [&](u32 addr, u32 size) { return _main.get_ptr<u8>(addr, size); });
} }
if (applied.empty()) if (applied.empty())
@ -2571,12 +2571,12 @@ std::pair<std::shared_ptr<lv2_overlay>, CellError> ppu_load_overlay(const ppu_ex
} }
// Apply the patch // Apply the patch
auto applied = g_fxo->get<patch_engine>().apply(hash, [ovlm](u32 addr) { return ovlm->get_ptr<u8>(addr); }); auto applied = g_fxo->get<patch_engine>().apply(hash, [ovlm](u32 addr, u32 size) { return ovlm->get_ptr<u8>(addr, size); });
if (!Emu.GetTitleID().empty()) if (!Emu.GetTitleID().empty())
{ {
// Alternative patch // Alternative patch
applied += g_fxo->get<patch_engine>().apply(Emu.GetTitleID() + '-' + hash, [ovlm](u32 addr) { return ovlm->get_ptr<u8>(addr); }); applied += g_fxo->get<patch_engine>().apply(Emu.GetTitleID() + '-' + hash, [ovlm](u32 addr, u32 size) { return ovlm->get_ptr<u8>(addr, size); });
} }
// Embedded SPU elf patching // Embedded SPU elf patching

View File

@ -184,13 +184,18 @@ void sys_spu_image::deploy(u8* loc, std::span<const sys_spu_segment> segs, bool
hash[5 + i * 2] = pal[sha1_hash[i] & 15]; hash[5 + i * 2] = pal[sha1_hash[i] & 15];
} }
auto mem_translate = [loc](u32 addr, u32 size)
{
return utils::add_saturate<u32>(addr, size) <= SPU_LS_SIZE ? loc + addr : nullptr;
};
// Apply the patch // Apply the patch
auto applied = g_fxo->get<patch_engine>().apply(hash, [loc](u32 addr) { return loc + addr; }); auto applied = g_fxo->get<patch_engine>().apply(hash, mem_translate);
if (!Emu.GetTitleID().empty()) if (!Emu.GetTitleID().empty())
{ {
// Alternative patch // Alternative patch
applied += g_fxo->get<patch_engine>().apply(Emu.GetTitleID() + '-' + hash, [loc](u32 addr) { return loc + addr; }); applied += g_fxo->get<patch_engine>().apply(Emu.GetTitleID() + '-' + hash, mem_translate);
} }
(is_verbose ? spu_log.notice : sys_spu.trace)("Loaded SPU image: %s (<- %u)%s", hash, applied.size(), dump); (is_verbose ? spu_log.notice : sys_spu.trace)("Loaded SPU image: %s (<- %u)%s", hash, applied.size(), dump);