Name local plt stubs in x86 retpoline layouts ##analysis - #26469
Conversation
|
The bug: Fix — make slot completeness explicit instead of defaulting the displacement to 0: case R_ANAL_OP_TYPE_JMP:
case R_ANAL_OP_TYPE_CALL:
// retpoline stubs branch to an in-section thunk
if (op.jump >= sec_vaddr && op.jump < sec_end) {
if (lea_ptr != UT64_MAX && load_disp != UT64_MAX) {
slot = lea_ptr + load_disp; // split lea+load shape
} else if (lea_is_load) {
slot = lea_ptr; // x86: mov reg, [rip+disp] is the full slot
}
}
break;with a Other problems, in descending order of relevance:
Caveat: the diff hunks cut off the tracking cases (LEA/MOV recording and the reset points), so I can't see exactly which op type sets |
813104f to
c694154
Compare
c694154 to
996228b
Compare
|
All four addressed; head 996228b, gate clean. Fixed slot bug. Instead of a hand-set flag I took op.direction, which the decoder already fills: mov r11, [rip+disp] is read (the ptr is the slot), lea/adrp are ref (a base). So lea_ptr + load_disp when a load was seen, bare lea_ptr only when it came from a read, otherwise decline. Not hypothetical — on a copy of aarch64-bti-lld.so, moving add2's .rela.plt r_offset to the page base 0x3000 and turning mul2's ldr into b .plt (r2 -n -w -c 'wx 0030000000000000 @ 0x380; wx f1ffff17 @ 0x50c') flags the mul2 stub as sym.plt.add2 before the fix, and nothing after. You were right that it can't be asserted in-tree: reloc parsing doesn't read through the io cache (wx + oob, bin.cache=true and reopen all re-parse the original .rela.plt), so it needs a new binary in testbins — happy to add one if you want it.
Also folded the oplen < 1 early-continue into the same if/else chain, so per-entry state resets in exactly one place instead of two — that was the drift behind (1). One more found while in there: R_ANAL_OP_TYPE_CJMP is JMP | COND and type is masked with ~COND, so a conditional branch was reaching the new case — replacing the lazy stub's call with a jne to the same thunk still produced sym.plt.add2. Now guarded with !(op.type & R_ANAL_OP_TYPE_COND) in the same condition, so a conditional branch still terminates an entry but no longer resolves a slot. Test added, 1 XX with the guard reverted. The indirect path masks COND the same way — that's #26457's code and I have no fixture for it, so I left it; happy to follow up. |
|
Nice! |
Description
Step 5 of the plan, done the way you recommended — a small extension to #26457's scanner rather than the elf.c byte matcher.
lld's retpoline PLT loads the GOT slot into r11 and then reaches the shared thunk with a direct branch instead of jumping through the slot, so the scan never saw an indirect terminator. An unconditional call/jmp whose target stays inside the section being scanned now yields a slot candidate too — but only when one is actually complete: a dereferencing load gives the slot outright, a bare lea/adrp gives a base and is only usable once a displacement has been seen.
One difference from your sketch: it named only the call-terminated form, but -z now ends in a direct jmp, so accepting only calls leaves that half at zero. Both are handled.
No testbins dependency after all — elf/pltrel/x86_64-retpoline.so and -now.so are already merged, so the five tests land here. caller in both goes from call fcn.000014b0 to call sym.plt.add2.
Section-less retpoline stays at zero, since sections are picked by name — same as every other arch here.