Merge pull request #12817 from slopgobblercobbler/fix/relinker-syscall-scanner-bounds

fix(relinker): validate code section bounds for instruction matches and SDK stubs
This commit is contained in:
Dean authored and GitHub committed 2026-10-30 08:19:09 +13:00
commit 5dc9aa339b
2 files changed
+33 -6

No files matched your search

@@ -31,16 +31,18 @@ static constexpr std::array<std::uint8_t, 23> SDK_GENERIC_SYSCALL_STUB{
0x4D, 0x89, 0xC2, 0x4D, 0x89, 0xC8, 0x4C, 0x8B, 0x4C, 0x24, 0x08,
};
static bool IsSdkSyscallStub(const std::vector<std::uint8_t>& code, const std::size_t syscallOffset) {
static bool IsSdkSyscallStub(const std::vector<std::uint8_t>& code, const std::size_t syscallOffset, const std::size_t limit) {
constexpr std::array<std::uint8_t, 3> numberLoad{0x48, 0xC7, 0xC0};
constexpr std::array<std::uint8_t, 3> argumentShuffle{0x49, 0x89, 0xCA};
if (syscallOffset <= code.size() && syscallOffset >= SDK_GENERIC_SYSCALL_STUB.size() && code.size() - syscallOffset >= 3u) {
if (limit > code.size() || syscallOffset > limit || limit - syscallOffset < 3u)
return false;
if (syscallOffset >= SDK_GENERIC_SYSCALL_STUB.size()) {
const auto begin = code.begin() + static_cast<std::ptrdiff_t>(syscallOffset - SDK_GENERIC_SYSCALL_STUB.size());
if (std::equal(SDK_GENERIC_SYSCALL_STUB.begin(), SDK_GENERIC_SYSCALL_STUB.end(), begin)
&& code[syscallOffset] == 0x0F && code[syscallOffset + 1] == 0x05 && code[syscallOffset + 2] == 0xC3)
return true;
}
if (syscallOffset > code.size() || syscallOffset < 10u || code.size() - syscallOffset < 3u)
if (syscallOffset < 10u)
return false;
const auto begin = code.begin() + static_cast<std::ptrdiff_t>(syscallOffset - 10u);
return std::equal(numberLoad.begin(), numberLoad.end(), begin)
@@ -108,7 +110,7 @@ static bool IsSdkMmanSyscallStub(const std::vector<std::uint8_t>& code, std::siz
}
static ForbiddenInstruction ClassifyForbiddenInstruction(const std::vector<std::uint8_t>& codeSection, const std::size_t opcodeOffset, const std::size_t limit) {
if (opcodeOffset + 1 >= limit)
if (opcodeOffset >= limit || limit - opcodeOffset < 2u)
return ForbiddenInstruction::None;
const std::uint8_t byte0 = codeSection[opcodeOffset];
@@ -208,6 +210,8 @@ struct ActivePrefixes {
static ActivePrefixes ActivePrefixesBefore(const std::vector<std::uint8_t>& code, const std::size_t opcode) {
ActivePrefixes prefixes{false, 0, 0};
if (opcode > code.size())
return prefixes;
std::size_t start = opcode;
if (start > 0 && (code[start - 1] & 0xF0) == 0x40) {
prefixes.Rex = code[start - 1];
@@ -227,6 +231,8 @@ static ActivePrefixes ActivePrefixesBefore(const std::vector<std::uint8_t>& code
}
static std::optional<std::uint64_t> ConstantRaxSetEndingAt(const std::vector<std::uint8_t>& code, const std::size_t end) {
if (end > code.size())
return std::nullopt;
if (end >= 10 && code[end - 10] == 0x48 && code[end - 9] == 0xB8) {
std::uint64_t value = 0;
std::memcpy(&value, code.data() + end - 8, sizeof(value));
@@ -261,6 +267,8 @@ struct LowRaxWrite {
};
static std::optional<LowRaxWrite> ImmediateLowRaxWriteEndingAt(const std::vector<std::uint8_t>& code, const std::size_t end) {
if (end > code.size())
return std::nullopt;
if (end >= 2 && code[end - 2] == 0xB0) {
const ActivePrefixes prefixes = ActivePrefixesBefore(code, end - 2);
if ((prefixes.Rex & 0x01) == 0)
@@ -275,6 +283,8 @@ static std::optional<LowRaxWrite> ImmediateLowRaxWriteEndingAt(const std::vector
}
static std::optional<std::uint64_t> ConstantSyscallNumberInBytes(const std::vector<std::uint8_t>& code, const std::size_t syscall) {
if (syscall > code.size())
return std::nullopt;
using Step = std::tuple<std::size_t, std::uint64_t, std::uint64_t>;
std::vector<Step> pending{{syscall, 0, 0}};
std::set<Step> seen(pending.begin(), pending.end());
@@ -327,7 +337,7 @@ std::vector<std::size_t> SyscallScanner::ScanCodeSectionForSyscalls(const std::v
recent.Push(match);
if (match.Offset < codeSectionOffset) throw RelinkerException("Instruction match precedes code section offset", match.Offset);
const std::size_t start = static_cast<std::size_t>(match.Offset - codeSectionOffset);
if (start > codeSection.size() || match.Length > codeSection.size() - start) throw RelinkerException("Instruction match exceeds code section", match.Offset);
if (start > limit || match.Length > limit - start) throw RelinkerException("Instruction match exceeds code section", match.Offset);
const auto opcodeOffset = Codegen::DecodedInstruction{codeSection.data() + start, match.Length}.OpcodeOffset();
const std::size_t opcode = start + opcodeOffset;
const std::size_t spanLimit = std::min(limit, start + static_cast<std::size_t>(match.Length));
@@ -338,7 +348,7 @@ std::vector<std::size_t> SyscallScanner::ScanCodeSectionForSyscalls(const std::v
return;
if (trapSdkStubs && kind == ForbiddenInstruction::Invocation &&
(IsSdkSyscallStub(codeSection, opcode) || IsSdkMmanSyscallStub(codeSection, opcode, limit, recent, codeSectionOffset))) {
(IsSdkSyscallStub(codeSection, opcode, limit) || IsSdkMmanSyscallStub(codeSection, opcode, limit, recent, codeSectionOffset))) {
trapped.push_back(opcode);
std::ostringstream message;
message << "Trapping SDK syscall instruction at code offset 0x" << std::hex << match.Offset << '\n';
@@ -88,6 +88,23 @@ void CheckFixtures() {
sizeExceededRejected = std::string(error.what()) == "Code section exceeds the input buffer";
}
Require(sizeExceededRejected, "Scanner accepted code section size exceeding input");
const Bytes sdkGenericStub = {
0x48, 0x89, 0xF8, 0x48, 0x89, 0xF7, 0x48, 0x89, 0xD6, 0x48, 0x89, 0xCA,
0x4D, 0x89, 0xC2, 0x4D, 0x89, 0xC8, 0x4C, 0x8B, 0x4C, 0x24, 0x08,
0x0F, 0x05, 0xC3,
};
const Bytes sdkNumberedStub = {
0x48, 0xC7, 0xC0, 0x09, 0x00, 0x00, 0x00,
0x49, 0x89, 0xCA,
0x0F, 0x05, 0xC3,
};
for (const auto& stubFixture : std::vector<Fixture>{{sdkGenericStub, 23}, {sdkNumberedStub, 10}}) {
Bytes padded = stubFixture.code;
padded.resize(stubFixture.code.size() + 16, 0x90);
const auto trapped = Relinker::MakeSyscallScanner(testProgress, true)->ScanCodeSectionForSyscalls(padded, 0, stubFixture.code.size());
Require(trapped == std::vector<std::size_t>{stubFixture.syscall}, "SDK stub returned the wrong patch offset");
Require(Rejected(padded, true, stubFixture.syscall + 2), "SDK stub matching read past the code section boundary");
}
std::cout << "SDK inline syscall checks passed, " << rejectedMutations << " near-match rejections\n";
}