From 0f45390a92e87781d16d724b5e779047f4e9a30e Mon Sep 17 00:00:00 2001 From: Karan Shrivastava Date: Fri, 23 Oct 2026 20:08:20 -0400 Subject: [PATCH] fix(relinker): agree on the syscall number between the two constant-load paths --- .../relinker/src/analysis/SyscallScanner.cpp | 59 ++++++---- .../relinker/tests/SyscallScannerTests.cpp | 111 ++++++++++++++++++ .../relinker/tests/test_sce_code_gaps.py | 5 + .../relinker/tests/test_syscall_number.py | 44 ++++++- 4 files changed, 191 insertions(+), 28 deletions(-) diff --git a/core/relinker/relinker/src/analysis/SyscallScanner.cpp b/core/relinker/relinker/src/analysis/SyscallScanner.cpp index 6003cda8e4d5..786e065c5ebd 100644 --- a/core/relinker/relinker/src/analysis/SyscallScanner.cpp +++ b/core/relinker/relinker/src/analysis/SyscallScanner.cpp @@ -128,27 +128,53 @@ static ForbiddenInstruction ClassifyForbiddenInstruction(const std::vector ConstantRaxLoad(const std::uint8_t* bytes, const std::size_t length) { +static std::optional ConstantRaxLoad(const std::uint8_t* bytes, const std::size_t length) { const std::size_t opcode = Codegen::DecodedInstruction{bytes, length}.OpcodeOffset(); const bool hasRex = opcode > 0 && (bytes[opcode - 1] & 0xF0) == 0x40; const std::uint8_t rex = hasRex ? bytes[opcode - 1] : 0; if (hasRex && rex == 0x48 && length - opcode == 6 && bytes[opcode] == 0xC7 && bytes[opcode + 1] == 0xC0) { - std::uint32_t value = 0; - std::memcpy(&value, bytes + opcode + 2, sizeof(value)); + std::int32_t immediate = 0; + std::memcpy(&immediate, bytes + opcode + 2, sizeof(immediate)); + return static_cast(static_cast(immediate)); + } + + if (hasRex && rex == 0x48 && length - opcode == 9 && bytes[opcode] == 0xB8) { + std::uint64_t value = 0; + std::memcpy(&value, bytes + opcode + 1, sizeof(value)); return value; } - if (!hasRex && length - opcode == 5 && bytes[opcode] == 0xB8) { + if ((rex & 0x09) == 0 && length - opcode == 5 && bytes[opcode] == 0xB8) { std::uint32_t value = 0; std::memcpy(&value, bytes + opcode + 1, sizeof(value)); return value; } + if ((rex & 0x05) == 0 && length - opcode == 2 && (bytes[opcode] == 0x31 || bytes[opcode] == 0x33) && bytes[opcode + 1] == 0xC0 + && std::find(bytes, bytes + opcode, 0x66) == bytes + opcode) + return 0; + return std::nullopt; } -static std::optional ConstantSyscallNumber(const std::vector& codeSection, const FileByteOffset codeSectionOffset, const RecentInstructions& recent) { +static bool MovesOtherRegisterThanRax(const std::uint8_t* bytes, const std::size_t length) { + const std::uint8_t rex = length == 3 ? bytes[0] : 0; + if (length == 3 && (rex & 0xF0) != 0x40) + return false; + const std::uint8_t opcode = bytes[length - 2]; + const std::uint8_t modrm = bytes[length - 1]; + if ((modrm & 0xC0) != 0xC0) + return false; + const bool writesRm = opcode == 0x89 || opcode == 0x31; + const bool writesReg = opcode == 0x8B || opcode == 0x33; + if (!writesRm && !writesReg) + return false; + const unsigned written = writesRm ? (modrm & 7u) | ((rex & 1u) << 3) : ((modrm >> 3) & 7u) | ((rex & 4u) << 1); + return written != 0; +} + +static std::optional ConstantSyscallNumber(const std::vector& codeSection, const FileByteOffset codeSectionOffset, const RecentInstructions& recent) { FileByteOffset next = recent.Before(0).Offset; for (std::size_t step = 1; step <= SYSCALL_NUMBER_LOOKBACK && step < recent.count; ++step) { const auto& match = recent.Before(step); @@ -166,11 +192,8 @@ static std::optional ConstantSyscallNumber(const std::vector 0 && (bytes[opcode - 1] & 0xF0) == 0x40; - const bool movesFirstSyscallArgument = hasRex && bytes[opcode - 1] == 0x49 - && match.Length - opcode == 2 && bytes[opcode] == 0x89 && bytes[opcode + 1] == 0xCA; - - if (!movesFirstSyscallArgument) + const std::size_t begin = opcode > 0 && (bytes[opcode - 1] & 0xF0) == 0x40 ? opcode - 1 : opcode; + if (match.Length - opcode != 2 || !MovesOtherRegisterThanRax(bytes + begin, match.Length - begin)) return std::nullopt; } @@ -211,22 +234,6 @@ static std::optional ImmediateLowRaxWriteEndingAt(const std::vector return std::nullopt; } -static bool MovesOtherRegisterThanRax(const std::uint8_t* bytes, const std::size_t length) { - const std::uint8_t rex = length == 3 ? bytes[0] : 0; - if (length == 3 && (rex & 0xF0) != 0x40) - return false; - const std::uint8_t opcode = bytes[length - 2]; - const std::uint8_t modrm = bytes[length - 1]; - if ((modrm & 0xC0) != 0xC0) - return false; - const bool writesRm = opcode == 0x89 || opcode == 0x31; - const bool writesReg = opcode == 0x8B || opcode == 0x33; - if (!writesRm && !writesReg) - return false; - const unsigned written = writesRm ? (modrm & 7u) | ((rex & 1u) << 3) : ((modrm >> 3) & 7u) | ((rex & 4u) << 1); - return written != 0; -} - static std::optional ConstantSyscallNumberInBytes(const std::vector& code, const std::size_t syscall) { using Step = std::tuple; std::vector pending{{syscall, 0, 0}}; diff --git a/core/relinker/relinker/tests/SyscallScannerTests.cpp b/core/relinker/relinker/tests/SyscallScannerTests.cpp index 65901a6f332e..742627e104d1 100644 --- a/core/relinker/relinker/tests/SyscallScannerTests.cpp +++ b/core/relinker/relinker/tests/SyscallScannerTests.cpp @@ -76,6 +76,102 @@ const Bytes kSdkGenericStub = { 0x0F, 0x05, 0xC3, }; +const Bytes kSignExtendedStub = { + 0x48, 0xC7, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, + 0x49, 0x89, 0xCA, + 0x0F, 0x05, + 0xC3, +}; + +const Bytes kImm64Stub = { + 0x48, 0xB8, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x49, 0x89, 0xCA, + 0x0F, 0x05, + 0xC3, +}; + +const Bytes kImm64NegativeStub = { + 0x48, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x49, 0x89, 0xCA, + 0x0F, 0x05, + 0xC3, +}; + +const Bytes kZeroedStub = { + 0x31, 0xC0, + 0x0F, 0x05, + 0xC3, +}; + +const Bytes kWideZeroedStub = { + 0x48, 0x31, 0xC0, + 0x0F, 0x05, + 0xC3, +}; + +const Bytes kWordZeroedStub = { + 0x66, 0x31, 0xC0, + 0x0F, 0x05, + 0xC3, +}; + +const Bytes kExtendedRegisterZeroedStub = { + 0x45, 0x31, 0xC0, + 0x0F, 0x05, + 0xC3, +}; + +const Bytes kExtendedSourceXorStub = { + 0x44, 0x31, 0xC0, + 0x0F, 0x05, + 0xC3, +}; + +const Bytes kExtendedIndexXorStub = { + 0x42, 0x31, 0xC0, + 0x0F, 0x05, + 0xC3, +}; + +const Bytes kLoadThenExtendedZeroedStub = { + 0xB8, 0x3B, 0x00, 0x00, 0x00, + 0x47, 0x31, 0xC0, + 0x0F, 0x05, + 0xC3, +}; + +const Bytes kPrefixAwareLoadStub = { + 0x44, 0xB8, 0x3B, 0x00, 0x00, 0x00, + 0x0F, 0x05, + 0xC3, +}; + +const Bytes kWordWideZeroedStub = { + 0x66, 0x48, 0x31, 0xC0, + 0x0F, 0x05, + 0xC3, +}; + +const Bytes kExtendedLoadStub = { + 0x41, 0xB8, 0x3B, 0x00, 0x00, 0x00, + 0x0F, 0x05, + 0xC3, +}; + +const Bytes kLoadThenMemoryLoadStub = { + 0xB8, 0x3B, 0x00, 0x00, 0x00, + 0x8B, 0x44, 0x8B, 0xF8, + 0x0F, 0x05, + 0xC3, +}; + +const Bytes kLoadThenImmediateAddStub = { + 0xB8, 0x3B, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x31, 0xC9, + 0x0F, 0x05, + 0xC3, +}; + std::string RunScanner(const std::string& binary, const Bytes& code) { const TempDirectory directory; const auto input = directory.Path() / "input.elf"; @@ -141,11 +237,26 @@ int main(const int argc, char** argv) { RequireNumber(binary, kConstantStub, "syscall number 9", "64-bit constant stub"); RequireNumber(binary, kUnsignedStub, "syscall number 57", "32-bit constant stub"); RequireNumber(binary, kInt80Stub, "syscall number 11", "int 0x80 stub"); + RequireNumber(binary, kSignExtendedStub, "syscall number 18446744073709551615", "negative 32-bit constant stub"); + RequireNumber(binary, kImm64Stub, "syscall number 9", "64-bit immediate stub"); + RequireNumber(binary, kImm64NegativeStub, "syscall number 18446744073709551615", "negative 64-bit immediate stub"); + RequireNumber(binary, kZeroedStub, "syscall number 0", "zeroed stub"); + RequireNumber(binary, kWideZeroedStub, "syscall number 0", "wide zeroed stub"); + RequireNumber(binary, kExtendedIndexXorStub, "syscall number 0", "stub that zeroes eax with an index-only REX"); + RequireNumber(binary, kLoadThenExtendedZeroedStub, "syscall number 59", "stub whose intervening xor zeroes r8d"); + RequireNumber(binary, kPrefixAwareLoadStub, "syscall number 59", "32-bit constant stub with an index-only REX"); RequireNoNumber(binary, kRegisterStub, "stub that loads the number from a register"); RequireNoNumber(binary, kSysenterStub, "sysenter stub"); RequireNoNumber(binary, kSysretStub, "sysret stub with a constant load in front of it"); RequireNoNumber(binary, kClobberedStub, "stub whose intervening instruction writes rax"); + RequireNoNumber(binary, kWordZeroedStub, "stub that zeroes only ax"); + RequireNoNumber(binary, kWordWideZeroedStub, "stub whose wide zeroing xor carries an operand-size prefix"); + RequireNoNumber(binary, kExtendedRegisterZeroedStub, "stub that zeroes r8d"); + RequireNoNumber(binary, kExtendedSourceXorStub, "stub that xors eax with r8d"); + RequireNoNumber(binary, kExtendedLoadStub, "stub that loads r8d"); + RequireNoNumber(binary, kLoadThenMemoryLoadStub, "stub whose intervening instruction loads eax from memory"); + RequireNoNumber(binary, kLoadThenImmediateAddStub, "stub whose intervening instruction adds to eax"); RequireNumber(binary, kPrefixedConstantStub, "syscall number 9", "prefixed constant stub"); RequireNoNumber(binary, kPrefixedClobberedStub, "prefixed stub whose intervening instruction writes rax"); diff --git a/core/relinker/relinker/tests/test_sce_code_gaps.py b/core/relinker/relinker/tests/test_sce_code_gaps.py index aee8daf982b8..d9382dcecd3a 100644 --- a/core/relinker/relinker/tests/test_sce_code_gaps.py +++ b/core/relinker/relinker/tests/test_sce_code_gaps.py @@ -25,9 +25,14 @@ ("padding-after-undecodable", UNDECODABLE + b"\xcc\xcc" + MOV_EAX_SYSCALL, 0x21D, 59), ("register-move", bytes.fromhex("b8 3b 00 00 00 48 89 df 0f 05 c3"), 0x218, 59), ("zeroed", bytes.fromhex("31 c0 0f 05 c3"), 0x212, 0), + ("rex-x-zeroed", bytes.fromhex("42 31 c0 0f 05 c3"), 0x213, 0), + ("prefixed-zeroed", bytes.fromhex("31 c0 f3 0f 05 c3"), 0x212, 0), + ("rex-r-load", bytes.fromhex("44 b8 3b 00 00 00 0f 05 c3"), 0x216, 59), ("mov-rax-imm64", bytes.fromhex("48 b8 3b 00 00 00 00 00 00 00 0f 05 c3"), 0x21A, 59), + ("prefixed-mov-rax-imm64", bytes.fromhex("48 b8 3b 00 00 00 00 00 00 00 f2 49 89 ca 0f 05 c3"), 0x21E, 59), ("low-byte-after-zeroing", bytes.fromhex("31 c0 b0 3b 0f 05 c3"), 0x214, 59), ("low-word-after-zeroing", bytes.fromhex("31 c0 66 b8 3b 01 0f 05 c3"), 0x216, 0x13B), + ("low-word-ending-in-move", bytes.fromhex("b8 3b 00 00 00 66 b8 89 c1 0f 05 c3"), 0x219, 0xC189), ("eight-register-moves", bytes.fromhex("b8 3b 00 00 00") + bytes.fromhex("89 db") * 8 + SYSCALL, 0x225, 59), ) JUMP_TABLE_DISPATCH = bytes.fromhex("48 8d 0d 09 00 00 00 48 63 04 b9 48 01 c8 ff e0") diff --git a/core/relinker/relinker/tests/test_syscall_number.py b/core/relinker/relinker/tests/test_syscall_number.py index d9f47031d3f0..688335bf0b79 100644 --- a/core/relinker/relinker/tests/test_syscall_number.py +++ b/core/relinker/relinker/tests/test_syscall_number.py @@ -15,6 +15,26 @@ CLOBBERED = bytes.fromhex("48c7c009000000") + bytes.fromhex("4883c001") + b"\x0f\x05\xc3" PREFIXED_CONSTANT = bytes.fromhex("6648c7c009000000") + bytes.fromhex("f24989ca") + bytes.fromhex("f30f05") + b"\xc3" PREFIXED_CLOBBERED = bytes.fromhex("6648c7c009000000") + bytes.fromhex("664883c001") + bytes.fromhex("f30f05") + b"\xc3" +SIGN_EXTENDED = bytes.fromhex("48c7c0ffffffff") + bytes.fromhex("4989ca") + b"\x0f\x05\xc3" +IMM64 = bytes.fromhex("48b80900000000000000") + bytes.fromhex("4989ca") + b"\x0f\x05\xc3" +IMM64_NEGATIVE = bytes.fromhex("48b8ffffffffffffffff") + bytes.fromhex("4989ca") + b"\x0f\x05\xc3" +ZEROED = bytes.fromhex("31c0") + b"\x0f\x05\xc3" +WIDE_ZEROED = bytes.fromhex("4831c0") + b"\x0f\x05\xc3" +WORD_ZEROED = bytes.fromhex("6631c0") + b"\x0f\x05\xc3" +EXTENDED_REGISTER_ZEROED = bytes.fromhex("4531c0") + b"\x0f\x05\xc3" +EXTENDED_SOURCE_XOR = bytes.fromhex("4431c0") + b"\x0f\x05\xc3" +EXTENDED_INDEX_XOR = bytes.fromhex("4231c0") + b"\x0f\x05\xc3" +LOAD_THEN_EXTENDED_ZEROED = bytes.fromhex("b83b0000004731c0") + b"\x0f\x05\xc3" +LOAD_THEN_EXTENDED_XOR = bytes.fromhex("b83b0000004531c0") + b"\x0f\x05\xc3" +LOAD_THEN_EXTENDED_MOVE = bytes.fromhex("b83b0000004589c0") + b"\x0f\x05\xc3" +LOAD_THEN_EXTENDED_TO_R9 = bytes.fromhex("b83b0000004d89c1") + b"\x0f\x05\xc3" +PREFIX_AWARE_LOAD = bytes.fromhex("44b83b000000") + b"\x0f\x05\xc3" +MPROTECT_TAIL = bytes.fromhex("b84a0000004889f74c89c64531d24531c04531c9") + b"\x0f\x05\xc3" +WORD_WIDE_ZEROED = bytes.fromhex("664831c0") + b"\x0f\x05\xc3" +EXTENDED_LOAD = bytes.fromhex("41b83b000000") + b"\x0f\x05\xc3" +LOAD_THEN_EXTENDED_LOW_BYTE = bytes.fromhex("b83b00000041b005") + b"\x0f\x05\xc3" +LOAD_THEN_MEMORY_LOAD = bytes.fromhex("b83b0000008b448bf8") + b"\x0f\x05\xc3" +LOAD_THEN_IMMEDIATE_ADD = bytes.fromhex("b83b00000005000031c9") + b"\x0f\x05\xc3" def fixture(code): @@ -53,13 +73,33 @@ def reject(name, code): for name, code, expected in (("constant", CONSTANT, "syscall number 9"), ("unsigned", UNSIGNED, "syscall number 57"), ("int80", INT80, "syscall number 11"), - ("prefixed-constant", PREFIXED_CONSTANT, "syscall number 9")): + ("prefixed-constant", PREFIXED_CONSTANT, "syscall number 9"), + ("sign-extended", SIGN_EXTENDED, "syscall number 18446744073709551615"), + ("imm64", IMM64, "syscall number 9"), + ("imm64-negative", IMM64_NEGATIVE, "syscall number 18446744073709551615"), + ("zeroed", ZEROED, "syscall number 0"), + ("wide-zeroed", WIDE_ZEROED, "syscall number 0"), + ("extended-index-xor", EXTENDED_INDEX_XOR, "syscall number 0"), + ("load-then-extended-zeroed", LOAD_THEN_EXTENDED_ZEROED, "syscall number 59"), + ("load-then-extended-xor", LOAD_THEN_EXTENDED_XOR, "syscall number 59"), + ("load-then-extended-move", LOAD_THEN_EXTENDED_MOVE, "syscall number 59"), + ("load-then-extended-to-r9", LOAD_THEN_EXTENDED_TO_R9, "syscall number 59"), + ("prefix-aware-load", PREFIX_AWARE_LOAD, "syscall number 59"), + ("mprotect-tail", MPROTECT_TAIL, "syscall number 74")): if expected not in reject(name, code): raise AssertionError((name, "did not report " + expected)) for name, code in (("register", REGISTER), ("sysenter", SYSENTER), ("sysret", SYSRET), ("clobbered", CLOBBERED), - ("prefixed-clobbered", PREFIXED_CLOBBERED)): + ("prefixed-clobbered", PREFIXED_CLOBBERED), + ("word-zeroed", WORD_ZEROED), + ("word-wide-zeroed", WORD_WIDE_ZEROED), + ("extended-register-zeroed", EXTENDED_REGISTER_ZEROED), + ("extended-source-xor", EXTENDED_SOURCE_XOR), + ("extended-load", EXTENDED_LOAD), + ("load-then-extended-low-byte", LOAD_THEN_EXTENDED_LOW_BYTE), + ("load-then-memory-load", LOAD_THEN_MEMORY_LOAD), + ("load-then-immediate-add", LOAD_THEN_IMMEDIATE_ADD)): stderr = reject(name, code) if "syscall number" in stderr: raise AssertionError((name, "reported a number it cannot establish", stderr))