diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index b1d310b80..522e5dd51 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -215,6 +215,22 @@ if(BUILD_TESTING) add_test(NAME linux_desktop_entry COMMAND linux_desktop_entry_tests) endif() + add_test_executable(section_header_ranges_tests + ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/tests/SectionRangeTests.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/general/SectionHeaderTableBuilder.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io/src/ByteWriter.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io/src/BufferUtils.cpp + ) + target_include_directories(section_header_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 section_header_ranges COMMAND section_header_ranges_tests) + set_tests_properties(section_header_ranges PROPERTIES TIMEOUT 20) + add_test_executable(amd64_only_converter_tests ${CMAKE_CURRENT_SOURCE_DIR}/codegen/tests/Amd64OnlyConverterTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/Amd64OnlyConverter.cpp diff --git a/core/relinker/elfpatcher/src/general/SectionHeaderTableBuilder.cpp b/core/relinker/elfpatcher/src/general/SectionHeaderTableBuilder.cpp index e9ee612df..916a1575e 100644 --- a/core/relinker/elfpatcher/src/general/SectionHeaderTableBuilder.cpp +++ b/core/relinker/elfpatcher/src/general/SectionHeaderTableBuilder.cpp @@ -2,6 +2,7 @@ #include #include #include +#include namespace Elfpatcher { @@ -43,18 +44,33 @@ void SectionHeaderTableBuilder::WriteTable( const auto phOffset = Io::ReadU64(buf, kEhdrPhOffOffset); const auto phCount = Io::ReadU16(buf, kEhdrPhNumOffset); const auto phStride = Io::ReadU16(buf, kEhdrPhEntSizeOffset); - const auto mappedAddress = [&](std::uint64_t offset) { + if (phStride < 56 || phOffset > buf.size() || phCount > (buf.size() - phOffset) / phStride) + throw std::runtime_error("Output program header table is out of bounds"); + const auto mappedAddress = [&](std::uint64_t offset, std::uint64_t sectionSize) { + if (offset > buf.size() || sectionSize > buf.size() - offset) + throw std::runtime_error("Allocated output section exceeds file"); for (std::uint16_t index = 0; index < phCount; ++index) { const auto header = phOffset + index * phStride; if (Io::ReadU32(buf, header) != PT_LOAD) continue; const auto start = Io::ReadU64(buf, header + 8); const auto size = Io::ReadU64(buf, header + 32); - if (offset >= start && offset - start < size) - return Io::ReadU64(buf, header + 16) + offset - start; + if (offset >= start && offset - start <= size && sectionSize <= size - (offset - start)) { + const auto relative = offset - start; + const auto address = Io::ReadU64(buf, header + 16); + if (relative > std::numeric_limits::max() - address || + sectionSize > std::numeric_limits::max() - (address + relative)) + throw std::runtime_error("Allocated output section address overflowed"); + return address + relative; + } } throw std::runtime_error("Allocated output section is outside load segments"); }; + const auto dynStrAddress = mappedAddress(request.DynStrOffset, request.DynStrSize); + const auto dynSymAddress = mappedAddress(request.DynSymOffset, request.DynSymSize); + const auto dynamicAddress = mappedAddress(request.DynamicSegmentOffset, request.DynamicSegmentSize); + const auto stubAddress = mappedAddress(request.StubOffset, request.StubSize); + const auto shStrTabOff = static_cast(buf.size()); constexpr std::uint64_t shStrTabSize = sizeof(kShStrTabBlob); for (const char i : kShStrTabBlob) @@ -65,10 +81,10 @@ void SectionHeaderTableBuilder::WriteTable( _writeSectionHeader(buf, shOff + kShdrEntrySize * 0, 0, SHT_NULL, 0, 0, 0, 0, 0, 0, 0, 0); _writeSectionHeader(buf, shOff + kShdrEntrySize * 1, kShStrTabNameOffset, SHT_STRTAB, 0, shStrTabOff, shStrTabOff, shStrTabSize, 0, 0, kNoSpecialAlignment, 0); - _writeSectionHeader(buf, shOff + kShdrEntrySize * 2, kDynStrNameOffset, SHT_STRTAB, SHF_ALLOC, mappedAddress(request.DynStrOffset), request.DynStrOffset, request.DynStrSize, 0, 0, kNoSpecialAlignment, 0); - _writeSectionHeader(buf, shOff + kShdrEntrySize * 3, kDynSymNameOffset, SHT_DYNSYM, SHF_ALLOC, mappedAddress(request.DynSymOffset), request.DynSymOffset, request.DynSymSize, kDynSymLinkToStrTab, kDynSymInfoFirstGlobal, kDynSymAlign, kSymEntrySize); - _writeSectionHeader(buf, shOff + kShdrEntrySize * 4, kDynamicNameOffset, SHT_DYNAMIC, SHF_ALLOC | SHF_WRITE, mappedAddress(request.DynamicSegmentOffset), request.DynamicSegmentOffset, request.DynamicSegmentSize, kDynamicLinkToStrTab, 0, kDynSymAlign, kDynEntrySize); - _writeSectionHeader(buf, shOff + kShdrEntrySize * 5, kTextNameOffset, SHT_PROGBITS, SHF_ALLOC | SHF_EXECINSTR, mappedAddress(request.StubOffset), request.StubOffset, request.StubSize, 0, 0, kNoSpecialAlignment, 0); + _writeSectionHeader(buf, shOff + kShdrEntrySize * 2, kDynStrNameOffset, SHT_STRTAB, SHF_ALLOC, dynStrAddress, request.DynStrOffset, request.DynStrSize, 0, 0, kNoSpecialAlignment, 0); + _writeSectionHeader(buf, shOff + kShdrEntrySize * 3, kDynSymNameOffset, SHT_DYNSYM, SHF_ALLOC, dynSymAddress, request.DynSymOffset, request.DynSymSize, kDynSymLinkToStrTab, kDynSymInfoFirstGlobal, kDynSymAlign, kSymEntrySize); + _writeSectionHeader(buf, shOff + kShdrEntrySize * 4, kDynamicNameOffset, SHT_DYNAMIC, SHF_ALLOC | SHF_WRITE, dynamicAddress, request.DynamicSegmentOffset, request.DynamicSegmentSize, kDynamicLinkToStrTab, 0, kDynSymAlign, kDynEntrySize); + _writeSectionHeader(buf, shOff + kShdrEntrySize * 5, kTextNameOffset, SHT_PROGBITS, SHF_ALLOC | SHF_EXECINSTR, stubAddress, request.StubOffset, request.StubSize, 0, 0, kNoSpecialAlignment, 0); _byteWriter->WriteU64(buf, kEhdrShOffOffset, shOff); _byteWriter->WriteU16(buf, kEhdrShEntSizeOffset, static_cast(kShdrEntrySize)); diff --git a/core/relinker/elfpatcher/tests/SectionRangeTests.cpp b/core/relinker/elfpatcher/tests/SectionRangeTests.cpp new file mode 100644 index 000000000..5332f80e5 --- /dev/null +++ b/core/relinker/elfpatcher/tests/SectionRangeTests.cpp @@ -0,0 +1,47 @@ +#include +#include +#include +#include +#include +#include + +static void Require(bool ok) { if (!ok) throw std::runtime_error("Section range validation failed"); } + +static std::vector Fixture() { + std::vector bytes(512, 0); + Io::WriteU64(bytes, 32, 64); Io::WriteU16(bytes, 54, 56); Io::WriteU16(bytes, 56, 1); + Io::WriteU32(bytes, 64, 1); Io::WriteU64(bytes, 72, 128); + Io::WriteU64(bytes, 80, 0x1000); Io::WriteU64(bytes, 96, 256); + return bytes; +} + +int main() { + try { + Elfpatcher::SectionHeaderTableBuilder builder(std::make_shared()); + const Elfpatcher::SectionHeaderTableRequest valid{128, 8, 136, 24, 160, 16, 176, 16}; + auto bytes = Fixture(); builder.WriteTable(bytes, valid); + const auto table = Io::ReadU64(bytes, 40); + Require(Io::ReadU64(bytes, table + 2 * 64 + 16) == 0x1000); + auto empty = valid; empty.StubOffset = 384; empty.StubSize = 0; + bytes = Fixture(); builder.WriteTable(bytes, empty); + Require(Io::ReadU64(bytes, Io::ReadU64(bytes, 40) + 5 * 64 + 16) == 0x1100); + for (int kind = 0; kind < 8; ++kind) { + bytes = Fixture(); auto request = valid; + switch (kind) { + case 0: request.DynStrSize = 257; break; + case 1: request.DynSymSize = std::numeric_limits::max(); break; + case 2: request.DynamicSegmentOffset = 383; request.DynamicSegmentSize = 2; break; + case 3: request.StubOffset = 384; request.StubSize = 1; break; + case 4: Io::WriteU16(bytes, 54, 1); break; + case 5: Io::WriteU64(bytes, 32, std::numeric_limits::max()); break; + case 6: Io::WriteU64(bytes, 80, std::numeric_limits::max() - 4); break; + case 7: Io::WriteU16(bytes, 56, 20); break; + } + const auto before = bytes; + bool rejected = false; + try { builder.WriteTable(bytes, request); } catch (const std::exception&) { rejected = true; } + Require(rejected && bytes == before); + } + std::cout << "10 section range cases passed\n"; + } catch (const std::exception& error) { std::cerr << error.what() << '\n'; return 1; } +}