[CFGuard] setjmp call site missing from longjmp table when IAT load is hoisted out of a loop
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
When a `setjmp` call sits inside a loop and the callee is `dllimport` (mingw ucrt), MachineLICM hoists the IAT load out of the loop and the call becomes register-indirect. The CFGuardLongjmp pass then fails to record the call site, so the binary's Guard longjmp table is missing the entry and `longjmp` back to it is killed by the OS with `FAST_FAIL_INVALID_LONGJUMP_TARGET` (0xC0000409).
## Reproducer
```c
// test.c
#include
#include
static jmp_buf buf;
__attribute__((noinline)) static void work(int i) {
if (i == 1)
longjmp(buf, 1);
}
__attribute__((noinline)) static int loop_setjmp(int n) {
for (int i = 0; i < n; i++) {
if (setjmp(buf) == 0)
work(i);
}
return 0;
}
__attribute__((noinline)) static int single_setjmp(void) {
if (setjmp(buf) == 0)
longjmp(buf, 1);
return 1;
}
int main(int argc, char **argv) {
printf("single_setjmp: %d\n", single_setjmp());
fflush(stdout);
printf("loop_setjmp: entering\n");
fflush(stdout);
loop_setjmp(argc + 1);
printf("loop_setjmp: ok\n");
return 0;
}
```
```console
clang -O2 -mguard=cf test.c -o cfguard-fail.exe
./cfguard-fail.exe
```
clang 22.1.7, x86_64-w64-windows-gnu (msys2 clang64, ucrt), Windows 11.
## Expected
Both functions survive the longjmp.
## Actual
```text
single_setjmp: 1
loop_setjmp: entering
(exit 0xC0000409, FAST_FAIL_INVALID_LONGJUMP_TARGET)
```
`clang -O2 -mguard=cf -S`:
```asm
single_setjmp:
callq *__imp__setjmp(%rip)
$cfgsj_single_setjmp0:
loop_setjmp:
movq __imp__setjmp(%rip), %r15 # hoisted out of the loop
...
callq *%r15 # no $cfgsj_ symbol after this call
```
```asm
.section .gljmp$y,"dr"
.symidx $cfgsj_single_setjmp0 # only entry
```
---
I searched for similar issues, there is #66063, but it's different. There the setjmp TU is compiled *without* guard, while here the TU is compiled with `-mguard=cf` and the entry is lost by optimization passes (notably `-O0` works, both `.gljmp` entries are emitted).
---
For reference, this is seen in freetype2 on Windows, see https://github.com/mpv-player/mpv/issues/18332 for real life issue.
---
Thanks,
Kacper
Contributor guide
Research direction
Start with the test.c reproducer and the clang -O2 -mguard=cf command, then trace how MachineLICM changes the setjmp call before CFGuardLongjmp processes it. Inspect the CFGuardLongjmp handling of register-indirect calls and verify that the generated .gljmp$y section contains entries for both setjmp call sites and that the program survives longjmp.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100