fix(relinker): validate program layout ranges before writing

This commit is contained in:
MrMha 2026-10-11 19:51:17 +04:00
parent a11c8f7f70
commit d85d1c268f
3 changed files with 85 additions and 4 deletions

View File

@ -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

View File

@ -4,6 +4,7 @@
#include <algorithm>
#include <cstdint>
#include <iostream>
#include <limits>
#include <string>
#include <vector>
@ -111,6 +112,8 @@ std::uint64_t ProgramHeaderLayoutBuilder::ComputeExtraBlockVaddr(
if (ph.Type != PT_LOAD)
continue;
foundLoad = true;
if (ph.MemorySize > std::numeric_limits<std::uint64_t>::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<std::uint64_t>(neededPh) * request.PhEntSize;
const auto checkedEnd = [](std::uint64_t start, std::uint64_t size) {
if (size > std::numeric_limits<std::uint64_t>::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<std::uint64_t>(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);

View File

@ -0,0 +1,49 @@
#include <elfpatcher/general/ProgramHeaderLayoutBuilder.hpp>
#include <io/ByteWriter.hpp>
#include <iostream>
#include <limits>
#include <stdexcept>
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<KeepAll>(), std::make_shared<Io::ByteWriter>());
constexpr auto maximum = std::numeric_limits<std::uint64_t>::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<std::uint8_t> 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<std::uint8_t> 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; }
}