Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 33 additions & 26 deletions core/relinker/relinker/src/analysis/SyscallScanner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,27 +128,53 @@ static ForbiddenInstruction ClassifyForbiddenInstruction(const std::vector<std::
return ForbiddenInstruction::None;
}

static std::optional<std::uint32_t> ConstantRaxLoad(const std::uint8_t* bytes, const std::size_t length) {
static std::optional<std::uint64_t> 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<std::uint64_t>(static_cast<std::int64_t>(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<std::uint32_t> ConstantSyscallNumber(const std::vector<std::uint8_t>& 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<std::uint64_t> ConstantSyscallNumber(const std::vector<std::uint8_t>& 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);
Expand All @@ -166,11 +192,8 @@ static std::optional<std::uint32_t> ConstantSyscallNumber(const std::vector<std:
return value;

const std::size_t opcode = Codegen::DecodedInstruction{bytes, match.Length}.OpcodeOffset();
const bool hasRex = opcode > 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;
}

Expand Down Expand Up @@ -211,22 +234,6 @@ static std::optional<LowRaxWrite> 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<std::uint64_t> ConstantSyscallNumberInBytes(const std::vector<std::uint8_t>& code, const std::size_t syscall) {
using Step = std::tuple<std::size_t, std::uint64_t, std::uint64_t>;
std::vector<Step> pending{{syscall, 0, 0}};
Expand Down
111 changes: 111 additions & 0 deletions core/relinker/relinker/tests/SyscallScannerTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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");
Expand Down
5 changes: 5 additions & 0 deletions core/relinker/relinker/tests/test_sce_code_gaps.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
44 changes: 42 additions & 2 deletions core/relinker/relinker/tests/test_syscall_number.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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))
Expand Down
Loading