diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index 44fb13e832..d2a3387683 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -686,6 +686,8 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_syscall_number.py $) add_test(NAME unused_filter_indirect COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_unused_filter_indirect.py $) + add_test(NAME unused_filter_fallthrough_padding COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_unused_filter_fallthrough_padding.py $) else() message(STATUS "Python3 interpreter not found; skipping Python relinker tests") endif() diff --git a/core/relinker/relinker/src/analysis/UnusedNidFilter/ControlFlowGraph.cpp b/core/relinker/relinker/src/analysis/UnusedNidFilter/ControlFlowGraph.cpp index 4ef6bdf112..443ff5dc57 100644 --- a/core/relinker/relinker/src/analysis/UnusedNidFilter/ControlFlowGraph.cpp +++ b/core/relinker/relinker/src/analysis/UnusedNidFilter/ControlFlowGraph.cpp @@ -33,6 +33,8 @@ namespace { void buildGraph(std::span text, VirtualAddress textVaddr, VirtualAddress entryVaddr, const std::vector& extraEntries, const IRelativeRelocationIndex& relativeRelocations, const std::vector* data, bool followCodeAddresses, std::unordered_set& reachable, const std::function& progress, const std::function& isData) { if (text.size() > std::numeric_limits::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 visited; std::queue worklist; @@ -68,8 +70,9 @@ void buildGraph(std::span 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(va - textVaddr); std::size_t available = text.size() - bufOff; if (available == 0) throw RelinkerException("CFG: zero available bytes at target", va); diff --git a/core/relinker/relinker/tests/ControlFlowGraphTests.cpp b/core/relinker/relinker/tests/ControlFlowGraphTests.cpp index 2d0fb562ec..718e5ddb07 100644 --- a/core/relinker/relinker/tests/ControlFlowGraphTests.cpp +++ b/core/relinker/relinker/tests/ControlFlowGraphTests.cpp @@ -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(); diff --git a/core/relinker/relinker/tests/test_unused_filter_fallthrough_padding.py b/core/relinker/relinker/tests/test_unused_filter_fallthrough_padding.py new file mode 100644 index 0000000000..e40e0d5347 --- /dev/null +++ b/core/relinker/relinker/tests/test_unused_filter_fallthrough_padding.py @@ -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(" 1; filtered=1" in result.stdout, (n_nops, result.stdout) + + entry_tail = bytearray(build(0)) + struct.pack_into(" 1; filtered=1" in result.stdout, result.stdout + print("Unused filter fallthrough padding tests passed") + + +if __name__ == "__main__": + main()