Relocate R_*_RELATIVE against the load bias, not the mapped base - #773
Relocate R_*_RELATIVE against the load bias, not the mapped base#773zardus wants to merge 1 commit into
Conversation
|
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS Validation record for head The diff- return self.owner.mapped_base + self.addend
+ # B + A, where B is the load bias (mapped_base - linked_base) and A is a
+ # link-time virtual address.
+ return AT.from_lva(self.addend, self.owner).to_mva()Regression
The CFGFast and census measurements below were computed on cle The object that surfaced itNot redistributable, so it is named by its header rather than attached. Read CLE maps it at its preferred base, so 19,394 of the 21,474 relocated pointers land outside the image The nine jump tables, all in Public reproducer__attribute__((noinline)) static int alpha(void) { return 1; }
__attribute__((noinline)) static int beta(void) { return 2; }
__attribute__((noinline)) static int delta(void) { return 3; }
void *const table[] = {(void *)alpha, (void *)beta, (void *)delta};Three CFGFast A/B on that object
Every difference accounted for:
Blast radius over 234,056 ELF objectsThe change is provably a no-op when All 180 were loaded under both arms and their relocated-value sets compared. 174 Over 8,586,108 relative relocations:
The two ppc64 Negative control40 ELF objects that carry relative relocations but are linked at zero, spread Full CFGFast A/B on 18 affected objectsSame protocol, covering every architecture and both
11 objects have a different block digest. 10 of them also have a different No object gained an error and none gained a timeout. One mips object Merge-base lint and typecheckComplete workspace gateTwo notes from checking prior art
Relation to angr/angr#6889 and angr/angr#6856The A/B above is this cle change alone, with nothing applied on the angr side, This is also not the Mach-O mechanism in angr/angr#6856 / angr/angr#6880, where Rebased 2026-08-27. This record was measured at Re-keyed 2026-08-28. The figures above were measured at Re-keyed 2026-09-04, after a rebase onto cle master Hosted CI at Correction, 2026-09-04. An earlier version of the paragraph above read this same head at 16:24Z as 15 |
|
Corpus decompilation diffs can be found at angr/dec-snapshots@master...angr/cle_773 |
9e0e718 to
81ea676
Compare
|
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS The pointer table an ELF relative relocation fills, at the object's preferred base and rebased. The fixture comes from the angr/binaries pull request linked from the description. the reproducerimport logging, os
logging.getLogger("cle").setLevel(logging.CRITICAL)
import cle
BIN = os.environ["BINARIES"] # a checkout of angr/binaries
T = lambda *p: os.path.join(BIN, "tests", *p)
from cle.backends.elf.relocation.amd64 import R_X86_64_RELATIVE
path = T("x86_64", "relative_reloc_nonzero_base.so")
for base in (None, 0x1000000):
opts = {"backend": "elf"}
if base is not None:
opts["base_addr"] = base
ld = cle.Loader(path, auto_load_libs=False, main_opts=opts)
obj = ld.main_object
print(f"linked_base={obj.linked_base:#x} mapped_base={obj.mapped_base:#x} "
f"image {obj.min_addr:#x}-{obj.max_addr:#x}")
table = obj.get_symbol("table")
for i, name in enumerate(("alpha", "beta", "delta")):
word = ld.memory.unpack_word(table.rebased_addr + i * 8)
want = obj.get_symbol(name).rebased_addr
inside = obj.min_addr <= word <= obj.max_addr
print(f" table[{i}] = {word:#x} {name} = {want:#x} "
f"{'ok' if word == want else 'WRONG'}, {'inside' if inside else 'OUTSIDE'} the image")Before — mapped_base + addend puts every entry linked_base bytes too high, outside the image: cle master at
|
39d8728 to
f4d48fb
Compare
An ELF relative relocation writes B + A, where B is the load bias and A is a link-time virtual address. The bias is mapped_base - linked_base. CLE wrote mapped_base + addend, which is the same number only when the object was linked at zero. Every shared library and almost every PIE is, which is why this has held: on those objects linked_base is zero and the two expressions coincide. They do not coincide on a PIE the linker gave a non-zero text segment address, which is what Go's linker emits -- ET_DYN with its lowest PT_LOAD at 0x400000. Loading one puts every relative relocation 0x400000 too high. On a 5.9 MB Go executable 19,394 of 21,474 relocated pointers then point outside the image altogether, 2,071 of them into the extern object CLE placed above it. The switch tables in .data.rel.ro are relocated the same way, so CFGFast resolves an indirect jump to the extern object and decodes 8,267 blocks of its zero fill, while 883 real code addresses in .text -- 790 of them left without any predecessor -- are recovered as standalone functions instead of as blocks of the functions they belong to. GenericIRelativeReloc, two classes above, already computes the same quantity the correct way. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
f4d48fb to
4c29d96
Compare
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS
Problem
Every
R_*_RELATIVErelocation in an object the linker gave a non-zero textsegment address is written too high, by the linked base. On
binaries/tests/x86_64/relative_reloc_nonzero_base.so, linked with-Wl,-Ttext-segment=0x400000, the three-entry pointer table in.data.rel.ropoints outside the image:
That is the shape Go's linker emits. A pointer table that lands outside the image
is not a silent inaccuracy: CFGFast follows it, and the addresses resolve into
cle's extern object, so blocks get decoded out of zero fill while the real switch
targets lose their only in-edge.
Root cause
The ELF ABI defines this relocation as
B + A, whereBis the load bias --mapped_base - linked_base-- andAis a link-time virtual address.mapped_base + addendis the same number only whenlinked_baseis zero, whichis the case for every ordinary shared library and almost every PIE. That is why
the confusion survives: it is correct on nearly everything.
Fix
Use the load-bias translation that
GenericIRelativeRelocin the same filealready uses,
AT.from_lva(self.addend, self.owner).to_mva(). The branch for arelocation that resolved to a symbol is untouched.
The angr-side issue angr/angr#6889 addresses only the
first half of the damage, the blocks decoded out of the extern object.
Testing
tests/test_relative_relocations.pyloads the fixture at its preferred base andrebased -- so that the two terms the old expression conflated are both non-zero
and different -- and checks each table entry against the symbol it should name.
test_relative_relocation_at_a_zero_linked_baseis the negative case onbinaries/tests/x86_64/libc.so.6, which must not move. The first two fail on themerge base. Reported as angr/angr#6834.
Validation: #773 (comment)
sync: angr/binaries#188
session: sharpen