test(tests): pin the bti j split that no bundled binary can show - #331
Conversation
danielplohmann#310 refuses a `bti j` word as a function start and keeps `bti c`, on the grounds that a jump-only hint marks an indirect branch target inside a routine where a call hint marks a callable entry. danielplohmann#322 records the figure behind it as unverifiable here, because no bundled fixture contained a `bti j` word and every AArch64 fixture was bit-identical across the change. The first half of that has since stopped being true: danielplohmann#300 bundled elf_cxx_landing_pads_arm64_xored, which carries five `bti j` words, and they do reach the rule. The second half still holds, and now for a reason worth writing down. All five sit inside exception landing pads, where the shape test refuses them on its own account and the LSDA rule refuses them earlier still, so the flag decides nothing on that fixture and toggling it moves no address. What the flag needs in order to decide by itself is a `bti j` the shape test would otherwise accept: after alignment padding, opening a block that looks like an entry, in an image declaring no landing pads at all. A raw buffer is exactly that image, so this builds one rather than asking for a binary that cannot be bundled. With the flag off the hint word is booked as a function and the block behind it is not; with it on the hint is refused and the block is recovered in its place. `bti c`, `bti jc` and a bare `bti` are asserted unaffected in both settings, which is what makes the case measure the jump-only split rather than bti handling in general.
|
Reviewed here and good to go. The raw buffer with no The three controls are what make the test measure the jump-only split rather than BTI handling in general, and With this and #333 in, both rows of #322 item 2 have their mechanism pinned in-repo. The magnitudes stay measured-and-posted rather than reproducible, and I agree that is what the fixtures owe. |
Item 2 of #322, the
bti jrow.What changed since the issue was written
The first clause is no longer true. #300 bundled
elf_cxx_landing_pads_arm64_xored, which carries fivebti jwords, and instrumenting a run shows all five reach_isLikelyInteriorBtiCandidate.The second clause still holds, and the reason is worth recording rather than just working around. All five sit inside exception landing pads:
isDeclaredLandingPadis true for every one, and the shape test independently returns "interior" for every one. SoUSE_AARCH64_BTI_TARGET_TYPEdecides nothing there — the address is refused with the flag on or off, by two other rules, one of which (#300's LSDA pass) is documented as having to precede the shape test precisely because under-mbranch-protectionevery pad opens with abti.That is why bundling a BTI-built binary would not have closed this row either. A compiler emits
bti jat exception landing pads and jump-table cases, and on anything with unwind data the landing pads are already refused by the time this rule is consulted.What the flag needs to decide by itself
A
bti jthe shape test would otherwise accept: after alignment padding, opening a block that looks like an entry, in an image that declares no landing pads at all. A raw buffer is exactly that image, so this builds one instead of asking for a binary that cannot be bundled.bti j+0x20+0x24bookedbti cbti jcbtiThe three that do not move are the point of the test as much as the one that does: they are what makes it measure the jump-only split rather than
btihandling in general.bti jcadmits a call and a barebtiadmits both, so neither says jump-only, and neither should be refused.Tests
tests/testAArch64BtiTargetType.py, six cases: the default, the refusal, the flag-off control,bti c, the combined and bare hints, and a case asserting the first function is recovered in all eight arrangements so no other assertion can pass by reading an empty set.Verified to fail when the rule is disabled: forcing the
word == BTI_Jbranch false failstestAJumpOnlyHintIsNotAnEntryand nothing else.What this does not close
The corpus magnitude in #310 — 803 false / 0 true for
bti jagainstbti c's 505/0 — is still not reproducible from anything in this repo, and this PR does not claim to change that. It makes the mechanism regression-testable, which is the durable half of the two options the issue lists; the number remains a figure measured elsewhere.Gates
python -m pytest tests/→ 2,066 passed, 1 skipped, 2,593 subtestsruff check .andruff format --check .→ cleanmake typecheck→ exit 0, 261 diagnostics, identical to mastersrc/change, no fixture added, no baseline moves.You marked these as yours in the issue, so this is offered rather than assumed.