fix(relinker): accept RELATIVE relocations against a zero-value section symbol

OpenOrbis create-fself adds one to the symbol index of every relocation it
copies from .rela.dyn, so each R_X86_64_RELATIVE entry of a homebrew built
with the OpenOrbis toolchain names symbol 1, a local STT_SECTION symbol with
value 0. FreeBSD rtld computes RELATIVE as base + addend without reading the
symbol. The pipeline, the code analysis and the strict filter now accept
index 0 or a STT_SECTION symbol with value 0, and still throw for any other
symbol. The relocation is written with symbol index 0, where the pipeline
previously dropped RELATIVE entries with a nonzero index.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
saxopa 2026-10-12 03:12:02 +02:00
parent 7a84b22267
commit cded9ba8c3
5 changed files with 76 additions and 12 deletions

View File

@ -181,7 +181,8 @@ std::set<Domain::VirtualAddress> CodeInstructionCollector::Collect(const std::ve
const auto index = info >> 32;
const auto addend = Io::ReadU64(bytes, offset + 16);
if (type == 8) {
if (index != 0 || !pointers.Values.emplace(slot, addend).second) throw Domain::RelinkerException("Code analysis: invalid relative relocation", slot);
const auto zeroSectionSymbol = index != 0 && index < symbolCount && (bytes[symbols + index * 24 + 4] & 15) == 3 && Io::ReadU64(bytes, symbols + index * 24 + 8) == 0;
if ((index != 0 && !zeroSectionSymbol) || !pointers.Values.emplace(slot, addend).second) throw Domain::RelinkerException("Code analysis: invalid relative relocation", slot);
addRoot(addend);
} else if (type == 1 || type == 6 || type == 7) {
if (index == 0 || index >= symbolCount) throw Domain::RelinkerException("Code analysis: invalid relocation symbol", slot);

View File

@ -79,7 +79,12 @@ public:
const auto slot = read<std::uint64_t>(*rela + offset);
const auto info = read<std::uint64_t>(*rela + offset + 8);
if (static_cast<std::uint32_t>(info) != 8) continue;
if (info >> 32) throw RelinkerException("Strict filter: relative relocation has a symbol", slot);
if (const auto symbol = info >> 32; symbol != 0) {
const auto symbols = table(6, 0x61000039);
const auto size = tags.find(0x6100003F);
if (!symbols || size == tags.end() || symbol >= size->second / 24 || (read<std::uint8_t>(*symbols + symbol * 24 + 4) & 15) != 3 || read<std::uint64_t>(*symbols + symbol * 24 + 8) != 0)
throw RelinkerException("Strict filter: relative relocation has a symbol", slot);
}
const auto target = read<std::uint64_t>(*rela + offset + 16);
if (input.ImportSlots.contains(slot) || !input.Pointers.emplace(slot, target).second)
throw RelinkerException("Strict filter: overlapping pointer relocations", slot);

View File

@ -219,6 +219,15 @@ RelinkResult RelinkerPipeline::Relink(const std::vector<std::uint8_t>& sourceElf
return relaOff + off;
};
auto isZeroSectionSymbol = [&](const std::uint32_t symIdx) {
const FileByteOffset symOff = dynSymTabOffset + static_cast<FileByteOffset>(symIdx) * symEntSize;
if (symOff < dynSymTabOffset || symOff > raw.size() || raw.size() - symOff < symEntSize)
throw RelinkerException("Symbol table entry out of bounds", symOff);
std::uint64_t value = 0;
std::memcpy(&value, raw.data() + symOff + 8, sizeof(value));
return (raw[symOff + 4] & 0xf) == 3 && value == 0;
};
auto extractRela = [&](const FileByteOffset relaOff, const ByteCount relaSize) {
for (ByteCount off = 0; relaSize >= relaEntSize && off <= relaSize - relaEntSize; off += relaEntSize) {
const FileByteOffset pos = relaEntryPos(relaOff, off);
@ -236,7 +245,7 @@ RelinkResult RelinkerPipeline::Relink(const std::vector<std::uint8_t>& sourceElf
continue;
if (relType == 8) {
if (symIdx != 0)
if (symIdx != 0 && !isZeroSectionSymbol(symIdx))
throw RelinkerException("RELATIVE relocation has a nonzero symbol index", pos);
continue;
}
@ -405,9 +414,8 @@ RelinkResult RelinkerPipeline::Relink(const std::vector<std::uint8_t>& sourceElf
std::memcpy(&rOffset, raw.data() + pos, 8);
std::memcpy(&rInfo, raw.data() + pos + 8, 8);
std::memcpy(&rAddend, raw.data() + pos + 16, 8);
const std::uint32_t symIdx = static_cast<std::uint32_t>(rInfo >> 32);
const std::uint32_t relType = static_cast<std::uint32_t>(rInfo & 0xffffffff);
if (symIdx == 0 && relType == R_X86_64_RELATIVE)
if (relType == R_X86_64_RELATIVE)
appendRela(dynSection.RelaData, rOffset, static_cast<std::uint64_t>(R_X86_64_RELATIVE), rAddend);
}
};

View File

@ -576,6 +576,32 @@ void sceDynlibDataOffsets() {
"Duplicate PT_SCE_DYNLIBDATA segments were accepted");
requireFailure([&] { (void)Relinker::MakeStrictUnusedNidFilter()->Filter(references, duplicate, input.Text, input.TextVaddr); },
"Strict filter accepted duplicate PT_SCE_DYNLIBDATA segments");
auto sectionRelative = bytes;
write<std::uint64_t>(sectionRelative, 208, 0x70);
write<std::uint64_t>(sectionRelative, 216, 0x70);
write<std::int64_t>(sectionRelative, 0x330, 0x61000039);
write<std::uint64_t>(sectionRelative, 0x338, 0x100);
write<std::int64_t>(sectionRelative, 0x340, 0x6100003B);
write<std::uint64_t>(sectionRelative, 0x348, 24);
write<std::int64_t>(sectionRelative, 0x350, 0x6100003F);
write<std::uint64_t>(sectionRelative, 0x358, 48);
write<std::uint8_t>(sectionRelative, 0x61C, 3);
write<std::uint64_t>(sectionRelative, 0x908, (std::uint64_t{1} << 32) | 8);
require(Relinker::MakeStrictUnusedNidFilter()->Filter(references, sectionRelative, input.Text, input.TextVaddr).size() == 1,
"Strict filter rejected a RELATIVE relocation against a zero-value section symbol");
auto valuedSection = sectionRelative;
write<std::uint64_t>(valuedSection, 0x620, 0x10);
requireFailure([&] { (void)Relinker::MakeStrictUnusedNidFilter()->Filter(references, valuedSection, input.Text, input.TextVaddr); },
"Strict filter accepted a RELATIVE relocation against a section symbol with a value");
auto untyped = sectionRelative;
write<std::uint8_t>(untyped, 0x61C, 0);
requireFailure([&] { (void)Relinker::MakeStrictUnusedNidFilter()->Filter(references, untyped, input.Text, input.TextVaddr); },
"Strict filter accepted a RELATIVE relocation against a non-section symbol");
auto outside = sectionRelative;
write<std::uint64_t>(outside, 0x908, (std::uint64_t{2} << 32) | 8);
requireFailure([&] { (void)Relinker::MakeStrictUnusedNidFilter()->Filter(references, outside, input.Text, input.TextVaddr); },
"Strict filter accepted a RELATIVE relocation symbol outside the symbol table");
}
}

View File

@ -79,13 +79,37 @@ def main():
if target == "windows" and os.name == "nt" and not has_import:
run = subprocess.run([str(output)], capture_output=True, timeout=20)
assert run.returncode == 42, (name, level, run.returncode, run.stderr)
source = work / "invalid-relative.elf"
source.write_bytes(image_with_relocations([noop, (0x300, (1 << 32) | 8, 0x210)], False, False))
output = work / "invalid-relative.exe"
result = subprocess.run([str(relinker), "--skip-sce-module", "--windows", str(source), str(output)],
capture_output=True, text=True, timeout=20)
assert result.returncode == 2 and "RELATIVE relocation has a nonzero symbol index" in result.stderr
assert not output.exists()
for sce in (False, True):
image = image_with_relocations([noop, (0x300, (1 << 32) | 8, 0x210)], sce, False)
struct.pack_into("<IBBHQQ", image, 0x638, 0, 3, 0, 0, 0, 0)
struct.pack_into("<qQ", image, 0x470, 0x6100003f, 48)
struct.pack_into("<QQ", image, 152, 0x90, 0x90)
source = work / f"{sce}-section-relative.elf"
source.write_bytes(image)
for target, options in (("linux", []), ("windows", ["--windows"]), ("macos", ["--macos"])):
for level in (0, 1):
output = work / f"{sce}-section-relative-{target}-{level}.out"
result = subprocess.run([str(relinker), "--skip-sce-module", f"unused-filter={level}", *options,
str(source), str(output)], capture_output=True, text=True, timeout=20)
assert result.returncode == 0, (sce, target, level, result.stdout, result.stderr)
data = output.read_bytes()
if target == "linux":
tags, _ = dynamic_strings(data)
start = file_offset(data, loads(data), tags[7])
assert [struct.unpack_from("<QQq", data, offset) for offset in range(start, start + tags[8], 24)] == [relative]
if target == "windows" and os.name == "nt":
run = subprocess.run([str(output)], capture_output=True, timeout=20)
assert run.returncode == 42, (sce, level, run.returncode, run.stderr)
for name, symbol in (("notype", (0, 0, 0, 0, 0, 0)), ("valued-section", (0, 3, 0, 0, 0x10, 0))):
image = image_with_relocations([noop, (0x300, (1 << 32) | 8, 0x210)], False, False)
struct.pack_into("<IBBHQQ", image, 0x638, *symbol)
source = work / f"invalid-relative-{name}.elf"
source.write_bytes(image)
output = work / f"invalid-relative-{name}.exe"
result = subprocess.run([str(relinker), "--skip-sce-module", "--windows", str(source), str(output)],
capture_output=True, text=True, timeout=20)
assert result.returncode == 2 and "RELATIVE relocation has a nonzero symbol index" in result.stderr, (name, result.stderr)
assert not output.exists()
print("No-op executable relocation tests passed")