Merge pull request #11067 from slopgobblercobbler/fix/relinker-unwind-narrow-unsigned-overflow

fix(relinker): reject unsigned address overflow for narrow unwind encodings
This commit is contained in:
Dean authored and GitHub committed 2026-10-27 10:36:42 +13:00
commit 357b586723
3 files changed
+33 -2

No files matched your search

@@ -140,6 +140,9 @@ private:
const bool negative = (value >> 63) != 0;
if (negative ? base < std::uint64_t{0} - value : value > std::numeric_limits<VirtualAddress>::max() - base)
throw RelinkerException("Strict filter: unwind pointer address overflowed", field);
} else if ((encoding & 0x0F) == 2 || (encoding & 0x0F) == 3) {
if (value > std::numeric_limits<VirtualAddress>::max() - base)
throw RelinkerException("Strict filter: unwind pointer address overflowed", field);
}
value = base + value;
}
@@ -34,10 +34,22 @@ int main() {
Domain::ProgramHeader funcSeg{}; funcSeg.Type = 1; funcSeg.MappedAddress = 0x800; funcSeg.FileSize = 0x20;
const auto below = Relinker::UnusedNidFilter::ReadExceptionFunctions(belowBytes, {funcSeg, load, eh}, {}, {});
Require(below.size() == 1 && below[0].Begin == 0x800 && below[0].End == 0x810);
auto belowDatarelUdata8 = Fixture();
belowDatarelUdata8[3] = 0x34;
Io::WriteU64(belowDatarelUdata8, 16, 0x800 - 0x1000);
Io::WriteU64(belowDatarelUdata8, 24, 0x1060 - 0x1000);
Io::WriteU64(belowDatarelUdata8, 104, 0x800);
Require(Relinker::UnusedNidFilter::ReadExceptionFunctions(belowDatarelUdata8, {funcSeg, load, eh}, {}, {}).size() == 1);
auto belowPcrelUdata8 = Fixture();
belowPcrelUdata8[3] = 0x14;
Io::WriteU64(belowPcrelUdata8, 16, 0x800 - 0x1010);
Io::WriteU64(belowPcrelUdata8, 24, 0x1060 - 0x1018);
Io::WriteU64(belowPcrelUdata8, 104, 0x800);
Require(Relinker::UnusedNidFilter::ReadExceptionFunctions(belowPcrelUdata8, {funcSeg, load, eh}, {}, {}).size() == 1);
auto empty = Fixture();
Io::WriteU64(empty, 112, 0);
Require(Relinker::UnusedNidFilter::ReadExceptionFunctions(empty, {load, eh}, {}, {}).empty());
for (int kind = 0; kind < 10; ++kind) {
for (int kind = 0; kind < 11; ++kind) {
auto bytes = Fixture(); auto segment = load;
switch (kind) {
case 0: Io::WriteU32(bytes, 64, 0x1000); break;
@@ -56,6 +68,16 @@ int main() {
break;
}
case 9: bytes[3] = 0x1C; Io::WriteU64(bytes, 16, 0x8000000000000000ULL); break;
case 10: {
segment.MappedAddress = 0xFFFFFFFFFFFF0000ULL;
eh.MappedAddress = 0xFFFFFFFFFFFF0000ULL;
Io::WriteU64(bytes, 4, 0xFFFFFFFFFFFF0040ULL);
bytes[3] = 0x13;
Io::WriteU32(bytes, 16, 0x107F0);
Io::WriteU32(bytes, 20, 0x4C);
Io::WriteU64(bytes, 104, 0x800);
break;
}
}
bool rejected = false;
std::vector<Domain::ProgramHeader> headers = {segment, eh};
@@ -63,10 +85,14 @@ int main() {
Domain::ProgramHeader high{}; high.Type = 1; high.MappedAddress = 0xFFFFFFFFFFFFF000ULL; high.FileSize = 0x100;
headers.push_back(high);
}
if (kind == 10) {
Domain::ProgramHeader funcSeg{}; funcSeg.Type = 1; funcSeg.MappedAddress = 0x800; funcSeg.FileSize = 0x20;
headers = {segment, eh, funcSeg};
}
try { Relinker::UnusedNidFilter::ReadExceptionFunctions(bytes, headers, {}, {}); }
catch (const std::exception&) { rejected = true; }
Require(rejected);
}
std::cout << "14 unwind record cases passed\n";
std::cout << "17 unwind record cases passed\n";
} catch (const std::exception& error) { std::cerr << error.what() << '\n'; return 1; }
}
+2
View File
@@ -44,3 +44,5 @@
- `--trace-imports` ([Windows entry stub](../../../core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp), [Linux entry stub](../../../core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp), [macOS entry](../../../core/relinker/elfpatcher/src/macos/MacOsImage.cpp)) leaves every import that is not a PLT slot or a function GOT slot untraced, and the same for a function reached through any other relocation, `sceKernelDlsym`, a weak import, or a module loaded at run time. On Linux the entry stub makes the page of every traced slot readable, writable and executable with `mprotect` before it stores the thunk, so a slot inside `PT_GNU_RELRO` can be replaced; those pages stay writable, and data pages become executable, while tracing is on. On macOS the stub writes the thunk into the bound slot and does not change its protection, because that slot stays in a writable segment
- [ANDN lowering](../../../core/relinker/codegen/src/x86/AndnLowering.cpp) uses scalar NOT and AND for 32- and 64-bit register and memory operands under `--to-rosetta`, following the ANDN entry in [Intel SDM volume 2A](https://cdrdv2-public.intel.com/812383/253666-sdm-vol-2a.pdf). SF and ZF follow the result, CF and OF clear, and AF and PF are undefined. The scratch register is saved below the 128-byte red zone. A 64-bit stack-relative displacement that exceeds signed disp32 after reserving the spill frame throws; other BMI instructions are not lowered. The M1 test covers register overlap, RSP operands, memory addressing, RIP relocation, flags and red-zone preservation; precise fault delivery inside a spill sequence has not been verified.
- Relative pointer-width unwind pointer decoding in the [exception frame reader](../../../core/relinker/relinker/src/analysis/UnusedNidFilter/EhFrameReader.cpp) (`DW_EH_PE_absptr` and `DW_EH_PE_udata8`) wraps modulo 2^64 to accommodate backward offsets, as standard DWARF unwinders add them. Console unwind table validation for out-of-range displacements is unmeasured. Unsigned offsets narrower than pointer width (`DW_EH_PE_udata2` and `DW_EH_PE_udata4`) cannot express negative displacements and throw when address addition overflows.