mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-23 09:03:22 -05:00
Merge pull request #7453 from Agnibha007/fix/unused-filter-fallthrough-padding-abort
fix(relinker): stop the unused-filter CFG walk before trailing zero padding
This commit is contained in:
4 files changed
+146
-1
No files matched your search
@@ -686,6 +686,8 @@ if(BUILD_TESTING)
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_syscall_number.py $<TARGET_FILE:relinker>)
|
||||
add_test(NAME unused_filter_indirect COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_unused_filter_indirect.py $<TARGET_FILE:relinker>)
|
||||
add_test(NAME unused_filter_fallthrough_padding COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_unused_filter_fallthrough_padding.py $<TARGET_FILE:relinker>)
|
||||
else()
|
||||
message(STATUS "Python3 interpreter not found; skipping Python relinker tests")
|
||||
endif()
|
||||
|
||||
@@ -33,6 +33,8 @@ namespace {
|
||||
void buildGraph(std::span<const std::uint8_t> text, VirtualAddress textVaddr, VirtualAddress entryVaddr, const std::vector<VirtualAddress>& extraEntries, const IRelativeRelocationIndex& relativeRelocations, const std::vector<StrictDataRegion>* data, bool followCodeAddresses, std::unordered_set<VirtualAddress>& reachable, const std::function<void(std::uint64_t)>& progress, const std::function<bool(VirtualAddress)>& isData) {
|
||||
if (text.size() > std::numeric_limits<VirtualAddress>::max() - textVaddr)
|
||||
throw RelinkerException("Text section range exceeds the address space");
|
||||
std::size_t codeSize = text.size();
|
||||
while (codeSize > 0 && text[codeSize - 1] == 0) --codeSize;
|
||||
const Codegen::X64InstructionDecoder decoder;
|
||||
std::unordered_set<VirtualAddress> visited;
|
||||
std::queue<VirtualAddress> worklist;
|
||||
@@ -68,8 +70,9 @@ void buildGraph(std::span<const std::uint8_t> text, VirtualAddress textVaddr, Vi
|
||||
|
||||
VirtualAddress va = worklist.front();
|
||||
worklist.pop();
|
||||
const VirtualAddress blockEntry = va;
|
||||
|
||||
while (va >= textVaddr && va - textVaddr < text.size() && !reachable.contains(va) && !(isData && isData(va))) {
|
||||
while (va >= textVaddr && va - textVaddr < text.size() && (va == blockEntry || va - textVaddr < codeSize) && !reachable.contains(va) && !(isData && isData(va))) {
|
||||
std::size_t bufOff = static_cast<std::size_t>(va - textVaddr);
|
||||
std::size_t available = text.size() - bufOff;
|
||||
if (available == 0) throw RelinkerException("CFG: zero available bytes at target", va);
|
||||
|
||||
@@ -141,6 +141,44 @@ void CheckSpeculativeRoots() {
|
||||
Require(threw, "Malformed mandatory root was treated as speculative");
|
||||
}
|
||||
|
||||
void CheckTrailingZeroRoots() {
|
||||
const Pointers pointers;
|
||||
const Bytes lone{0x00};
|
||||
bool threw = false;
|
||||
try {
|
||||
BuildControlFlowGraph(lone, TextAddress, TextAddress, {}, pointers);
|
||||
} catch (const Codegen::CodegenException&) {
|
||||
threw = true;
|
||||
}
|
||||
Require(threw, "A lone zero instruction at the entry was not decoded");
|
||||
|
||||
const Bytes trailing{0xc3, 0x00};
|
||||
threw = false;
|
||||
try {
|
||||
BuildControlFlowGraph(trailing, TextAddress, TextAddress, {TextAddress + 1}, pointers);
|
||||
} catch (const Codegen::CodegenException&) {
|
||||
threw = true;
|
||||
}
|
||||
Require(threw, "A zero instruction at an extra entry was not decoded");
|
||||
|
||||
const Bytes jump{0xeb, 0x00, 0x00};
|
||||
threw = false;
|
||||
try {
|
||||
BuildControlFlowGraph(jump, TextAddress, TextAddress, {}, pointers);
|
||||
} catch (const Codegen::CodegenException&) {
|
||||
threw = true;
|
||||
}
|
||||
Require(threw, "A branch into a truncated zero instruction was not decoded");
|
||||
|
||||
const Bytes pair{0x00, 0x00};
|
||||
const auto complete = BuildControlFlowGraph(pair, TextAddress, TextAddress, {}, pointers);
|
||||
RequireOffsets(*complete, {0});
|
||||
|
||||
const Bytes immediate{0xb8, 0x00, 0x00, 0x00, 0x00};
|
||||
const auto zeroImmediate = BuildControlFlowGraph(immediate, TextAddress, TextAddress, {}, pointers);
|
||||
RequireOffsets(*zeroImmediate, {0});
|
||||
}
|
||||
|
||||
void CheckBorrowedReachability() {
|
||||
const Pointers pointers;
|
||||
const Bytes text{0x90, 0x90, 0xc3, 0x90, 0xc3};
|
||||
@@ -223,6 +261,7 @@ int main() {
|
||||
CheckRelocatedTransfers();
|
||||
CheckCodeAddresses();
|
||||
CheckSpeculativeRoots();
|
||||
CheckTrailingZeroRoots();
|
||||
CheckBorrowedReachability();
|
||||
CheckAddressOverflow();
|
||||
CheckHighVirtualAddress();
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
CODE = 0x1000
|
||||
GOT_PUTS = 0x2090
|
||||
GOT_STRLEN = 0x2098
|
||||
MESSAGE = 0x2500
|
||||
EXIT_TAIL = b"\xbf\x2a\x00\x00\x00\xb8\xe7\x00\x00\x00\x0f\x05"
|
||||
|
||||
|
||||
def build(n_nops, tail=EXIT_TAIL):
|
||||
image = bytearray(0x3000)
|
||||
image[:16] = b"\x7fELF\x02\x01\x01" + bytes(9)
|
||||
nph = 6
|
||||
struct.pack_into("<HHIQQQIHHHHHH", image, 16, 3, 62, 1, CODE, 64, 0, 0, 64, 56, nph, 0, 0, 0)
|
||||
|
||||
def phdr(index, p_type, flags, offset, vaddr, filesz, memsz, align):
|
||||
struct.pack_into("<IIQQQQQQ", image, 64 + index * 56, p_type, flags, offset, vaddr, vaddr, filesz, memsz, align)
|
||||
|
||||
interpreter = b"/lib64/ld-linux-x86-64.so.2\0"
|
||||
image[0x200:0x200 + len(interpreter)] = interpreter
|
||||
phdr(0, 6, 4, 64, 64, nph * 56, nph * 56, 8)
|
||||
phdr(1, 3, 4, 0x200, 0x200, len(interpreter), len(interpreter), 1)
|
||||
phdr(5, 0x6474e551, 6, 0, 0, 0, 0, 0x10)
|
||||
message = b"hello from AnyPS5\0"
|
||||
image[MESSAGE:MESSAGE + len(message)] = message
|
||||
lea = b"\x48\x8d\x3d" + struct.pack("<i", MESSAGE - (CODE + 7))
|
||||
call = b"\xff\x15" + struct.pack("<i", GOT_PUTS - (CODE + 13))
|
||||
code = lea + call + tail + b"\x90" * n_nops
|
||||
image[CODE:CODE + len(code)] = code
|
||||
phdr(2, 1, 5, 0, 0x0000, 0x2000, 0x2000, 0x1000)
|
||||
phdr(3, 1, 6, 0x2000, 0x2000, 0x1000, 0x1000, 0x1000)
|
||||
struct.pack_into("<Q", image, GOT_PUTS, 0)
|
||||
struct.pack_into("<Q", image, GOT_STRLEN, 0)
|
||||
strtab = b"\0libc.so.6\0puts\0strlen\0"
|
||||
image[0x2100:0x2100 + len(strtab)] = strtab
|
||||
struct.pack_into("<IBBHQQ", image, 0x2120, 0, 0, 0, 0, 0, 0)
|
||||
struct.pack_into("<IBBHQQ", image, 0x2138, 11, 0x12, 0, 0, 0, 0)
|
||||
struct.pack_into("<IBBHQQ", image, 0x2150, 16, 0x12, 0, 0, 0, 0)
|
||||
struct.pack_into("<IIIII", image, 0x2168, 1, 3, 1, 2, 0)
|
||||
struct.pack_into("<QQq", image, 0x2194, GOT_PUTS, (1 << 32) | 6, 0)
|
||||
struct.pack_into("<QQq", image, 0x21ac, GOT_STRLEN, (2 << 32) | 6, 0)
|
||||
tags = [(1, 1), (5, 0x2100), (10, len(strtab)), (6, 0x2120), (11, 24),
|
||||
(4, 0x2168), (7, 0x2194), (8, 48), (9, 24), (0, 0)]
|
||||
for index, tag in enumerate(tags):
|
||||
struct.pack_into("<qQ", image, 0x2200 + index * 16, *tag)
|
||||
phdr(4, 2, 6, 0x2200, 0x2200, len(tags) * 16, len(tags) * 16, 8)
|
||||
return bytes(image)
|
||||
|
||||
|
||||
def run(relinker, source, output):
|
||||
return subprocess.run(
|
||||
[str(relinker), "--skip-sce-module", "--skip-syscall-check", "unused-filter=1",
|
||||
str(source), str(output)],
|
||||
capture_output=True, text=True, timeout=20)
|
||||
|
||||
|
||||
def main():
|
||||
relinker = Path(sys.argv[1]).resolve()
|
||||
with tempfile.TemporaryDirectory(prefix="anyps5-unused-filter-fallthrough-") as directory:
|
||||
work = Path(directory)
|
||||
for n_nops in (0, 1, 2, 3, 4):
|
||||
source = work / f"tail-{n_nops}.elf"
|
||||
output = work / f"tail-{n_nops}.out"
|
||||
source.write_bytes(build(n_nops))
|
||||
result = run(relinker, source, output)
|
||||
assert result.returncode == 0 and output.exists(), (n_nops, result.returncode, result.stdout, result.stderr)
|
||||
assert "CFG/GOT filtering: 2 -> 1; filtered=1" in result.stdout, (n_nops, result.stdout)
|
||||
|
||||
entry_tail = bytearray(build(0))
|
||||
struct.pack_into("<Q", entry_tail, 24, 0x1fff)
|
||||
source = work / "entry-tail.elf"
|
||||
output = work / "entry-tail.out"
|
||||
source.write_bytes(entry_tail)
|
||||
result = run(relinker, source, output)
|
||||
assert result.returncode == 2 and not output.exists(), (result.returncode, result.stdout, result.stderr)
|
||||
assert "Instruction truncated before ModRM byte" in result.stderr, result.stderr
|
||||
|
||||
jump_tail = bytearray(build(0))
|
||||
jump_tail[CODE:CODE + 5] = b"\xe9" + struct.pack("<i", 0x1fff - (CODE + 5))
|
||||
source = work / "jump-tail.elf"
|
||||
output = work / "jump-tail.out"
|
||||
source.write_bytes(jump_tail)
|
||||
result = run(relinker, source, output)
|
||||
assert result.returncode == 2 and not output.exists(), (result.returncode, result.stdout, result.stderr)
|
||||
assert "Instruction truncated before ModRM byte" in result.stderr, result.stderr
|
||||
|
||||
source = work / "zero-immediate.elf"
|
||||
output = work / "zero-immediate.out"
|
||||
source.write_bytes(build(0, tail=b"\xb8\x00\x00\x00\x00"))
|
||||
result = run(relinker, source, output)
|
||||
assert result.returncode == 0 and output.exists(), (result.returncode, result.stdout, result.stderr)
|
||||
assert "CFG/GOT filtering: 2 -> 1; filtered=1" in result.stdout, result.stdout
|
||||
print("Unused filter fallthrough padding tests passed")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user