[X86] A debug instruction changes LVI hardening fence placement
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
## Summary
One `DBG_INSTR_REF` at the start of a join block changes the gadget graph built
by X86 LVI load hardening. The greedy graph cut consequently moves an
`LFENCE` between two ordinary loads. Removing debug information restores the
other placement.
This reproduces with an assertions-enabled LLVM 24.0.0git build at revision
`bc79eb547e6ef6a9bceef4968df0e0a150b96a8d` on x86-64 Linux. Both variants
pass `-verify-machineinstrs`.
## Reproducer
Attach `debug.ll.txt` and `issue-reproducer.sh`, then run:
[debug.ll.txt](https://github.com/user-attachments/files/32361467/debug.ll.txt)
[issue-reproducer.sh](https://github.com/user-attachments/files/32361466/issue-reproducer.sh)
```console
$ bash issue-reproducer.sh
```
The input uses the `+lvi-load-hardening` function attribute. The script runs
the input both as written and after `opt --strip-debug`, prints the locations
of both fences, and compares the final `.text` sections.
## Actual behavior
The input with the debug record places the second fence immediately before the
pointer-consuming load at the outer-loop join. The strip-debug control places
it immediately after the pointer-producing load in the inner loop.
The unmodified compiler produces these `.text` hashes:
```text
with debug: bf285285242f1568f1601ffa49d7e6eb1c380cafc82b564a57f24c2d500b7a45
strip-debug: bb85cdb5db461aa59b0994cefc3e5da5fb848c839ad45c38588383e6bbeac0cc
```
The total number of fences remains two. This report concerns debug-dependent
behavior in a security-hardening pass; it does not claim an exploitable change
or a runtime failure.
## Cause
`X86LoadValueInjectionLoadHardeningImpl::getGadgetGraph()` uses
`MBB->begin()` as each block's graph representative. A leading
`DBG_INSTR_REF` therefore becomes an ordinary graph vertex. The two predecessor
edges enter that debug vertex, while one edge connects it to the first
ordinary instruction, changing the ingress-versus-egress cost used by the
greedy cut.
I have a candidate patch that chooses
`MBB->getFirstNonDebugInstr(/*SkipPseudoOp=*/false)` instead. The explicit
`false` preserves pseudo probes as graph vertices while excluding debug-only
instructions.
## Expected behavior
Debug-only instructions should not be structural vertices in the LVI gadget
graph. Adding or removing debug information should not relocate an `LFENCE`
between ordinary instructions.
Contributor guide
Research direction
Start in X86LoadValueInjectionLoadHardeningImpl::getGadgetGraph() and inspect how each block's graph representative is selected. Run issue-reproducer.sh with the attached debug.ll.txt, compare the fence locations and .text hashes for the debug and strip-debug variants, and confirm that debug-only instructions no longer alter the LFENCE placement.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers, security
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100