ldr: avoid out of cave memory with a warcrime (overwriting dead destructor)

This commit is contained in:
Lightos1 committed 2026-10-10 15:45:07 +02:00
1 parent 69bd7e088f
commit dbc37c6982
6 files changed
+208

No files matched your search

@@ -29,6 +29,7 @@
#include "pcv_mariko_mtc.hpp"
#include "pcv_mariko_soc.hpp"
#include "pcv_mariko_log.hpp"
#include "pcv_mariko_cave.hpp"
#include "calculate_timings_mariko.hpp"
namespace ams::ldr::hoc::pcv::mariko {
@@ -39,6 +40,8 @@ namespace ams::ldr::hoc::pcv::mariko {
DEFINE_HOOK_PAYLOAD_PTR(HookPayloadData, m_HookPayloadData);
Result InstallHooks() {
R_TRY(CaveInstall());
R_TRY(Hooks().CheckEnabled());
R_TRY(Hooks().CopyPayload());
@@ -96,6 +99,7 @@ namespace ams::ldr::hoc::pcv::mariko {
{ "CPU Lut Max Asm2", &CpuLutMaxAsm2, 1, &CpuLutAsmPatternFn2 },
{ "CPU Lut Writer", &LutWriterFind, 1, &LutWriterPatternFn },
{ "CPU Dvco Rate Cfg", &CpuLutDvcoRateCfg, 1, &CpuLutDvcoRateCfgPatternFn },
{ "Hook Cave", &DeadDestructorCaveFind, 0, &CaveFlagRunPatternFn, 0, 0, true },
/* Debugging patches */
#if HOC_UART_LOG
@@ -0,0 +1,80 @@
/*
* Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "pcv_mariko_cave.hpp"
namespace ams::ldr::hoc::pcv::mariko {
namespace {
struct {
u32 *site = nullptr;
u32 *end = nullptr;
} caveCache;
bool HasDirectCaller(const u32 *target, const u32 *begin, const u32 *end) {
for (const u32 *ptr = begin; ptr < end; ++ptr) {
const u32 top6 = *ptr & 0xFC000000u;
if (top6 != _asm::op::B && top6 != _asm::op::Bl) {
continue;
}
if (_asm::Target26(*ptr, reinterpret_cast<uintptr_t>(ptr)) == reinterpret_cast<uintptr_t>(target)) {
return true;
}
}
return false;
}
}
Result DeadDestructorCaveFind(u32 *ptr) {
u32 *textEnd = reinterpret_cast<u32 *>(g_pcv_cave);
u32 *site = _asm::FindFnPrologue(ptr, CavePrologueMargin, nsoStart);
R_UNLESS(site != nullptr, ldr::ResultInvalidHookCave());
u32 *end = _asm::FindFnEnd(site, textEnd);
R_UNLESS(end != nullptr, ldr::ResultInvalidHookCave());
/* Ensure this function is never called. */
R_UNLESS(!HasDirectCaller(site, nsoStart, textEnd), ldr::ResultInvalidHookCave());
caveCache.site = site;
caveCache.end = end;
R_SUCCEED();
}
Result CaveInstall() {
if (Hooks().CaveFree() >= HookContext::RequiredCaveSize()) {
R_SUCCEED();
}
R_UNLESS(caveCache.site != nullptr, ldr::ResultInvalidHookCave());
const uintptr_t start = util::AlignUp(reinterpret_cast<uintptr_t>(caveCache.site + 1), sizeof(u64));
const uintptr_t end = reinterpret_cast<uintptr_t>(caveCache.end);
R_UNLESS(start < end && end - start >= HookContext::RequiredCaveSize(), ldr::ResultHookPayloadTooLarge());
/* Insert a break in case this function gets called for whatever reason. */
*caveCache.site = _asm::BrkIns;
Hooks().SetCave(start, end - start);
R_SUCCEED();
}
}
@@ -0,0 +1,77 @@
/*
* Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "../pcv.hpp"
#include "../pcv_asm.hpp"
#include "../pcv_hook.hpp"
namespace ams::ldr::hoc::pcv::mariko {
extern u32 *nsoStart;
constexpr u32 CaveFlagChunkCount = 4;
constexpr u32 CaveFlagChunkInsCount = 4;
constexpr u32 CaveFlagLdrbDelta = 0x30;
constexpr u32 CavePrologueMargin = 8;
/* This function consists of multiple chunks. */
/* Each chunk does a load, a comparison and a conditional branch. The next chunk's address setup (adrp) follows right after it. */
inline bool IsFlagCheckChunk(const u32 *ptr) {
return _asm::IsOp(ptr[0], _asm::op::LdrbImm, _asm::field::Rt, _asm::field::Rn, _asm::field::Off1)
&& _asm::Ignoring(ptr[1], _asm::CmpImm32(0, 1), _asm::field::Rn)
&& _asm::Ignoring(ptr[2], _asm::Encode(_asm::op::BCond, {_asm::field::BCond, _asm::cond::Eq}), _asm::field::Imm19);
}
inline bool IsChunkDeltaValid(const u32 *prev, const u32 *next) {
return IsFlagCheckChunk(next) && _asm::Get(*next, _asm::field::Off1) == _asm::Get(*prev, _asm::field::Off1) + CaveFlagLdrbDelta;
}
inline bool ValidateChunkConsistency(const u32 *first) {
for (u32 i = 1; i < CaveFlagChunkCount; ++i) {
if (!IsChunkDeltaValid(first + (i - 1) * CaveFlagChunkInsCount, first + i * CaveFlagChunkInsCount)) {
return false;
}
}
return true;
}
inline bool CaveFlagRunPatternFn(u32 *ptr) {
if (Hooks().CaveFree() >= HookContext::RequiredCaveSize()) {
return false;
}
/* In case we don't have enough cave memory, we need to commit this warcrime. */
/* Ensure we don't dereference invalid memory. */
u32 *textEnd = reinterpret_cast<u32 *>(g_pcv_cave);
if (ptr - CaveFlagChunkInsCount < nsoStart || ptr + CaveFlagChunkCount * CaveFlagChunkInsCount > textEnd) {
return false;
}
/* Validate the current chunk and the previous chunk and ensure the delta of the ldrb instructions is consistent. */
if (!IsFlagCheckChunk(ptr) || IsChunkDeltaValid(ptr - CaveFlagChunkInsCount, ptr)) {
return false;
}
return ValidateChunkConsistency(ptr);
}
Result DeadDestructorCaveFind(u32 *ptr);
Result CaveInstall();
}