From d85d1c268fdc6b681f61850d18f3a4348d2ae9ef Mon Sep 17 00:00:00 2001 From: MrMha <114929652+MrMha@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:51:17 +0400 Subject: [PATCH] fix(relinker): validate program layout ranges before writing --- core/relinker/CMakeLists.txt | 15 ++++++ .../general/ProgramHeaderLayoutBuilder.cpp | 25 ++++++++-- .../tests/ProgramLayoutRangeTests.cpp | 49 +++++++++++++++++++ 3 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 core/relinker/elfpatcher/tests/ProgramLayoutRangeTests.cpp diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index 4561113eb..8ecae1f1f 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -154,6 +154,21 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/domain/include ) add_test(NAME cli_args COMMAND cli_args_tests) + add_test_executable(program_layout_ranges_tests + ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/tests/ProgramLayoutRangeTests.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/general/ProgramHeaderLayoutBuilder.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io/src/ByteWriter.cpp + ) + target_include_directories(program_layout_ranges_tests PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/domain/include + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/include + ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/include + ${CMAKE_CURRENT_SOURCE_DIR}/codegen/include + ${CMAKE_CURRENT_SOURCE_DIR}/io/include + ) + add_test(NAME program_layout_ranges COMMAND program_layout_ranges_tests) + set_tests_properties(program_layout_ranges PROPERTIES TIMEOUT 20) + add_test_executable(buffer_bounds_tests ${CMAKE_CURRENT_SOURCE_DIR}/io/tests/BufferBoundsTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io/src/ByteReader.cpp diff --git a/core/relinker/elfpatcher/src/general/ProgramHeaderLayoutBuilder.cpp b/core/relinker/elfpatcher/src/general/ProgramHeaderLayoutBuilder.cpp index d70c3385d..cd7ca36df 100644 --- a/core/relinker/elfpatcher/src/general/ProgramHeaderLayoutBuilder.cpp +++ b/core/relinker/elfpatcher/src/general/ProgramHeaderLayoutBuilder.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -111,6 +112,8 @@ std::uint64_t ProgramHeaderLayoutBuilder::ComputeExtraBlockVaddr( if (ph.Type != PT_LOAD) continue; foundLoad = true; + if (ph.MemorySize > std::numeric_limits::max() - ph.MappedAddress) + throw Domain::RelinkerException("Load segment virtual range overflowed"); const std::uint64_t vaddrEnd = ph.MappedAddress + ph.MemorySize; if (vaddrEnd > highestVaddrEnd) highestVaddrEnd = vaddrEnd; @@ -156,22 +159,36 @@ std::uint16_t ProgramHeaderLayoutBuilder::WriteLayout( "Not enough program header slots: need " + std::to_string(neededPh) + ", available " + std::to_string(request.PhNum)); - const std::uint64_t headerBlockSize = request.PhOff + static_cast(neededPh) * request.PhEntSize; + const auto checkedEnd = [](std::uint64_t start, std::uint64_t size) { + if (size > std::numeric_limits::max() - start) + throw Domain::RelinkerException("Program header layout range overflowed"); + return start + size; + }; + if (request.PhEntSize < 56) + throw Domain::RelinkerException("Program header entry is too small"); + const std::uint64_t headerBlockSize = checkedEnd(request.PhOff, static_cast(neededPh) * request.PhEntSize); + if (headerBlockSize > buf.size()) + throw Domain::RelinkerException("Program header table exceeds the output file"); + if (request.ExtraBlockOffset > buf.size() || request.ExtraBlockSize > buf.size() - request.ExtraBlockOffset) + throw Domain::RelinkerException("Extra block exceeds the output file"); std::uint64_t headerBlockAlign = kDefaultLoadAlignment; for (const auto& ph : request.OriginalHeaders) { if (ph.Type == PT_LOAD && !_segmentFilter->ShouldSkip(ph) && ph.Alignment > headerBlockAlign && (ph.Alignment & (ph.Alignment - 1)) == 0) headerBlockAlign = ph.Alignment; } const std::uint64_t headerBlockVaddr = - (request.ExtraBlockVaddr + request.ExtraBlockSize + headerBlockAlign - 1) & ~(headerBlockAlign - 1); + checkedEnd(checkedEnd(request.ExtraBlockVaddr, request.ExtraBlockSize), headerBlockAlign - 1) & ~(headerBlockAlign - 1); + checkedEnd(headerBlockVaddr, headerBlockSize); if (request.DynamicSegmentOffset < request.ExtraBlockOffset) throw Domain::RelinkerException("Dynamic segment offset lies before the extra block"); - if (request.DynamicSegmentOffset + request.DynamicSegmentSize > request.ExtraBlockOffset + request.ExtraBlockSize) + if (request.DynamicSegmentOffset - request.ExtraBlockOffset > request.ExtraBlockSize || + request.DynamicSegmentSize > request.ExtraBlockSize - (request.DynamicSegmentOffset - request.ExtraBlockOffset)) throw Domain::RelinkerException("Dynamic segment does not fit within the extra block"); if (request.InterpOffset < request.ExtraBlockOffset) throw Domain::RelinkerException("Interp offset lies before the extra block"); - if (request.InterpOffset + request.InterpSize > request.ExtraBlockOffset + request.ExtraBlockSize) + if (request.InterpOffset - request.ExtraBlockOffset > request.ExtraBlockSize || + request.InterpSize > request.ExtraBlockSize - (request.InterpOffset - request.ExtraBlockOffset)) throw Domain::RelinkerException("Interp data does not fit within the extra block"); const std::uint64_t dynamicSegmentVaddr = request.ExtraBlockVaddr + (request.DynamicSegmentOffset - request.ExtraBlockOffset); diff --git a/core/relinker/elfpatcher/tests/ProgramLayoutRangeTests.cpp b/core/relinker/elfpatcher/tests/ProgramLayoutRangeTests.cpp new file mode 100644 index 000000000..c2059ece9 --- /dev/null +++ b/core/relinker/elfpatcher/tests/ProgramLayoutRangeTests.cpp @@ -0,0 +1,49 @@ +#include +#include +#include +#include +#include + +class KeepAll : public Elfpatcher::ISegmentFilter { +public: + bool ShouldSkip(const Domain::ProgramHeader&) const override { return false; } +}; + +static void Require(bool ok) { if (!ok) throw std::runtime_error("Program layout range validation failed"); } + +int main() { + try { + Elfpatcher::ProgramHeaderLayoutBuilder builder(std::make_shared(), std::make_shared()); + constexpr auto maximum = std::numeric_limits::max(); + Domain::ProgramHeader load{}; + load.Type = 1; load.MappedAddress = maximum - 3; load.MemorySize = 8; + bool rejected = false; + try { builder.ComputeExtraBlockVaddr({load}, 0x1000); } catch (const std::exception&) { rejected = true; } + Require(rejected); + load.MappedAddress = 0x1000; load.MemorySize = 0x1000; + Require(builder.ComputeExtraBlockVaddr({load}, 0x1003) == 0x2003); + Elfpatcher::ProgramHeaderLayoutRequest valid{64, 56, 6, {load}, 0x1000, 0x2000, 0x100, 0x1000, 16, 0x1010, 8}; + std::vector output(0x1100, 0xA5); + Require(builder.WriteLayout(output, valid) == 6); + for (int kind = 0; kind < 9; ++kind) { + auto request = valid; + switch (kind) { + case 0: request.PhEntSize = 55; break; + case 1: request.PhOff = maximum - 10; break; + case 2: request.PhOff = 0x10F0; break; + case 3: request.ExtraBlockVaddr = maximum - 10; break; + case 4: request.DynamicSegmentSize = maximum; break; + case 5: request.InterpSize = maximum; break; + case 6: request.DynamicSegmentOffset = maximum; break; + case 7: request.InterpOffset = maximum; break; + case 8: request.ExtraBlockOffset = maximum; break; + } + std::vector bytes(0x1100, 0xA5); + const auto before = bytes; + rejected = false; + try { builder.WriteLayout(bytes, request); } catch (const std::exception&) { rejected = true; } + Require(rejected && bytes == before); + } + std::cout << "12 program layout cases passed\n"; + } catch (const std::exception& error) { std::cerr << error.what() << '\n'; return 1; } +}