// Pin down the LAST forked-child blocker: an indirect call (function pointer). // clang routes indirect calls through __jsl_indir, whose target slot is selected // by a single global self-modifying operand __jsl_indir_op that crt0 patches to // the MAIN thread's DP+$B8. A forked child stores its target to its OWN (fresh) // DP+$B8, but __jsl_indir still reads through the parent's operand -> the child's // indirect call dispatches through the WRONG direct page. gFp is volatile so the // call is a genuine indirect dispatch, not a devirtualized direct call. // // 0x025060 0001 child entered // 0x025062 BEEF child is ABOUT to make the indirect call (crash localizer) // 0x025064 0001 gFp(41)==42 (indirect call returned correctly) // 0x025066 E0DD child reached end (no crash/wild-jump) // 0x025068 5050 parent reaped the child #include extern int fork(void *subr); extern int wait(int *status); extern __attribute__((noreturn)) void _exit(int status); #define M(a) (*(volatile uint16_t *)(a)) #define BAD 0xDEAD static uint16_t addOne(uint16_t x) { return x + 1; } static uint16_t (*volatile gFp)(uint16_t) = addOne; static void child(void) { uint16_t r; M(0x025060UL) = 0x0001; M(0x025062UL) = 0xBEEF; // reached the indirect-call site r = gFp(41); // indirect dispatch via __jsl_indir if (r == 42) { M(0x025064UL) = 0x0001; } M(0x025066UL) = 0xE0DD; // survived the indirect call _exit(0); } int main(void) { int pid; int st; M(0x025060UL) = BAD; M(0x025062UL) = BAD; M(0x025064UL) = BAD; M(0x025066UL) = BAD; M(0x025068UL) = BAD; pid = fork((void *)child); if (pid > 0) { wait(&st); M(0x025068UL) = 0x5050; } for (volatile unsigned long j = 0; j < 300000UL; j++) { } return 0; }