Merge pull request #3780 from MrMha/fix/unwind-record-bounds
fix(relinker): validate file-backed unwind record ranges
This commit is contained in:
commit
66ab8753bf
|
|
@ -468,6 +468,21 @@ if(BUILD_TESTING)
|
|||
else()
|
||||
message(STATUS "Python3 interpreter not found; skipping Python relinker tests")
|
||||
endif()
|
||||
add_test_executable(unwind_record_bounds_tests
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/UnwindRecordBoundsTests.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter/EhFrameReader.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/io/src/BufferUtils.cpp
|
||||
)
|
||||
target_include_directories(unwind_record_bounds_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 unwind_record_bounds COMMAND unwind_record_bounds_tests)
|
||||
set_tests_properties(unwind_record_bounds PROPERTIES TIMEOUT 20)
|
||||
|
||||
add_test_executable(windows_icon_resource_tests
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/tests/WindowsIconResourceTests.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsIconResourceBuilder.cpp
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include <relinker/analysis/UnusedNidFilter/EhFrameReader.hpp>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <algorithm>
|
||||
|
||||
namespace Relinker::UnusedNidFilter {
|
||||
|
||||
|
|
@ -37,7 +38,7 @@ public:
|
|||
for (std::uint64_t index = 0; index < count; ++index) {
|
||||
const auto begin = encoded(position, tableEncoding, segment.MappedAddress);
|
||||
const auto fde = encoded(position, tableEncoding, segment.MappedAddress);
|
||||
if (position > segment.MappedAddress + segment.FileSize) throw RelinkerException("Strict filter: EH frame table exceeds segment", position);
|
||||
if (position < segment.MappedAddress || position - segment.MappedAddress > segment.FileSize) throw RelinkerException("Strict filter: EH frame table exceeds segment", position);
|
||||
result.push_back(readFde(begin, fde));
|
||||
}
|
||||
}
|
||||
|
|
@ -51,11 +52,38 @@ private:
|
|||
const std::set<VirtualAddress>& importSlots;
|
||||
std::map<VirtualAddress, Cie> cies;
|
||||
|
||||
VirtualAddress checkedRangeEnd(VirtualAddress address, std::uint64_t size) const {
|
||||
if (size > std::numeric_limits<VirtualAddress>::max() - address)
|
||||
throw RelinkerException("Strict filter: unwind range overflowed", address);
|
||||
const auto end = address + size;
|
||||
while (address < end) {
|
||||
bool found = false;
|
||||
for (const auto& segment : headers) {
|
||||
if (segment.Type != 1 || address < segment.MappedAddress) continue;
|
||||
const auto relative = address - segment.MappedAddress;
|
||||
if (relative >= segment.FileSize) continue;
|
||||
const auto chunk = std::min(end - address, segment.FileSize - relative);
|
||||
if (segment.Offset > bytes.size() || relative > bytes.size() - segment.Offset ||
|
||||
chunk > bytes.size() - segment.Offset - relative)
|
||||
throw RelinkerException("Strict filter: unwind data exceeds file", address);
|
||||
address += chunk;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
if (!found) throw RelinkerException("Strict filter: unwind range is not file-backed", address);
|
||||
}
|
||||
return end;
|
||||
}
|
||||
|
||||
template<typename TValue>
|
||||
TValue read(VirtualAddress& address) const {
|
||||
for (const auto& segment : headers) {
|
||||
if (segment.Type != 1 || address < segment.MappedAddress || address - segment.MappedAddress > segment.FileSize || sizeof(TValue) > segment.FileSize - (address - segment.MappedAddress)) continue;
|
||||
const auto offset = segment.Offset + address - segment.MappedAddress;
|
||||
const auto relative = address - segment.MappedAddress;
|
||||
if (segment.Offset > bytes.size() || relative > bytes.size() - segment.Offset ||
|
||||
sizeof(TValue) > std::numeric_limits<VirtualAddress>::max() - address)
|
||||
throw RelinkerException("Strict filter: unwind data range overflowed", address);
|
||||
const auto offset = segment.Offset + relative;
|
||||
if (offset > bytes.size() || sizeof(TValue) > bytes.size() - offset) throw RelinkerException("Strict filter: unwind data exceeds file", address);
|
||||
TValue value;
|
||||
std::memcpy(&value, bytes.data() + offset, sizeof(value));
|
||||
|
|
@ -118,7 +146,7 @@ private:
|
|||
auto position = address;
|
||||
const auto length = read<std::uint32_t>(position);
|
||||
if (length == 0xFFFFFFFF || length < 8) throw RelinkerException("Strict filter: unsupported CIE record size", address);
|
||||
const auto end = position + length;
|
||||
const auto end = checkedRangeEnd(position, length);
|
||||
if (read<std::uint32_t>(position) != 0) throw RelinkerException("Strict filter: invalid CIE identifier", address);
|
||||
const auto version = read<std::uint8_t>(position);
|
||||
if (version != 1 && version != 3 && version != 4) throw RelinkerException("Strict filter: unsupported CIE version", address);
|
||||
|
|
@ -140,8 +168,9 @@ private:
|
|||
if (augmentation.front() != 'z') throw RelinkerException("Strict filter: unsupported CIE augmentation", address);
|
||||
cie.Augmented = true;
|
||||
const auto size = leb(position);
|
||||
if (position > end || size > end - position)
|
||||
throw RelinkerException("Strict filter: CIE augmentation exceeds record", address);
|
||||
const auto augmentationEnd = position + size;
|
||||
if (augmentationEnd > end) throw RelinkerException("Strict filter: CIE augmentation exceeds record", address);
|
||||
for (std::size_t index = 1; index < augmentation.size(); ++index) {
|
||||
switch (augmentation[index]) {
|
||||
case 'L': cie.LsdaEncoding = read<std::uint8_t>(position); break;
|
||||
|
|
@ -166,7 +195,7 @@ private:
|
|||
auto position = address;
|
||||
const auto length = read<std::uint32_t>(position);
|
||||
if (length == 0xFFFFFFFF || length < 8) throw RelinkerException("Strict filter: unsupported FDE record size", address);
|
||||
const auto end = position + length;
|
||||
const auto end = checkedRangeEnd(position, length);
|
||||
const auto cieField = position;
|
||||
const auto cieOffset = read<std::uint32_t>(position);
|
||||
if (cieOffset > cieField) throw RelinkerException("Strict filter: invalid FDE CIE offset", address);
|
||||
|
|
@ -179,6 +208,8 @@ private:
|
|||
if (cie.Personality != 0) region.ExtraTargets.push_back(cie.Personality);
|
||||
if (cie.Augmented) {
|
||||
const auto augmentationSize = leb(position);
|
||||
if (position > end || augmentationSize > end - position)
|
||||
throw RelinkerException("Strict filter: FDE augmentation exceeds record", address);
|
||||
const auto augmentationEnd = position + augmentationSize;
|
||||
if (augmentationEnd > end) throw RelinkerException("Strict filter: FDE augmentation exceeds record", address);
|
||||
if (cie.LsdaEncoding != 0xFF) {
|
||||
|
|
@ -201,7 +232,7 @@ private:
|
|||
if ((callEncoding & 0xF0) != 0) throw RelinkerException("Strict filter: unsupported LSDA call-site encoding", address);
|
||||
const auto tableSize = leb(position);
|
||||
if (tableSize > bytes.size()) throw RelinkerException("Strict filter: invalid LSDA table size", address);
|
||||
const auto tableEnd = position + tableSize;
|
||||
const auto tableEnd = checkedRangeEnd(position, tableSize);
|
||||
while (position < tableEnd) {
|
||||
encoded(position, callEncoding);
|
||||
encoded(position, callEncoding);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
#include <relinker/analysis/UnusedNidFilter/EhFrameReader.hpp>
|
||||
#include <io/BufferUtils.hpp>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
static void Require(bool ok) { if (!ok) throw std::runtime_error("Unwind record validation failed"); }
|
||||
|
||||
static std::vector<std::uint8_t> Fixture() {
|
||||
std::vector<std::uint8_t> bytes(128, 0);
|
||||
bytes[0] = 1; bytes[1] = 0; bytes[2] = 3; bytes[3] = 0;
|
||||
Io::WriteU64(bytes, 4, 0x1040); Io::WriteU32(bytes, 12, 1);
|
||||
Io::WriteU64(bytes, 16, 0x2000); Io::WriteU64(bytes, 24, 0x1060);
|
||||
Io::WriteU32(bytes, 64, 9); bytes[72] = 1; bytes[73] = 0; bytes[74] = 1; bytes[75] = 0x78; bytes[76] = 16;
|
||||
Io::WriteU32(bytes, 96, 20); Io::WriteU32(bytes, 100, 36);
|
||||
Io::WriteU64(bytes, 104, 0x2000); Io::WriteU64(bytes, 112, 0x10);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
Domain::ProgramHeader load{}; load.Type = 1; load.MappedAddress = 0x1000; load.FileSize = 128;
|
||||
auto eh = load; eh.Type = 0x6474E550; eh.FileSize = 32;
|
||||
const auto regions = Relinker::UnusedNidFilter::ReadExceptionFunctions(Fixture(), {load, eh}, {}, {});
|
||||
Require(regions.size() == 1 && regions[0].Begin == 0x2000 && regions[0].End == 0x2010);
|
||||
auto second = load; second.MappedAddress = 0x1048; second.Offset = 72; second.FileSize = 56;
|
||||
auto first = load; first.FileSize = 72;
|
||||
Require(Relinker::UnusedNidFilter::ReadExceptionFunctions(Fixture(), {first, second, eh}, {}, {}).size() == 1);
|
||||
for (int kind = 0; kind < 7; ++kind) {
|
||||
auto bytes = Fixture(); auto segment = load;
|
||||
switch (kind) {
|
||||
case 0: Io::WriteU32(bytes, 64, 0x1000); break;
|
||||
case 1: Io::WriteU32(bytes, 96, 0x1000); break;
|
||||
case 2: Io::WriteU32(bytes, 64, 100); break;
|
||||
case 3: Io::WriteU32(bytes, 96, 32); bytes.resize(120); break;
|
||||
case 4: segment.Offset = std::numeric_limits<std::uint64_t>::max() - 4095; segment.MappedAddress = 0; segment.FileSize = 0x1100; break;
|
||||
case 5: segment.FileSize = 119; break;
|
||||
case 6: Io::WriteU32(bytes, 64, 8); break;
|
||||
}
|
||||
bool rejected = false;
|
||||
try { Relinker::UnusedNidFilter::ReadExceptionFunctions(bytes, {segment, eh}, {}, {}); }
|
||||
catch (const std::exception&) { rejected = true; }
|
||||
Require(rejected);
|
||||
}
|
||||
std::cout << "9 unwind record cases passed\n";
|
||||
} catch (const std::exception& error) { std::cerr << error.what() << '\n'; return 1; }
|
||||
}
|
||||
Loading…
Reference in New Issue