fix(relinker): validate program layout ranges before writing
This commit is contained in:
parent
a11c8f7f70
commit
d85d1c268f
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
Loading…
Reference in New Issue