Files
sx/examples/1809-concurrency-fiber-guard-stack.sx
agra dd532ab7b2 fibers B1.3b: mmap guard-page fiber stacks (x86_64 switch sibling deferred)
Fiber stacks are now mmap'd with a PROT_NONE guard page at the low end: mmap a
[guard | usable] region and mprotect the low 16KB page PROT_NONE, so a stack
overflow faults at the guard boundary instead of silently corrupting a neighbor
(design 8.1.1 — fixed stacks without a guard corrupt silently on overflow).

Locked by examples/1809-concurrency-fiber-guard-stack.sx (aarch64-macos-pinned):
guard armed: 1 (mprotect -> 0) + sum: 20100 (a fiber runs real recursion on the
guarded stack and yields). The guard FIRING is validated manually (a fiber
recursing past its 128KB stack faults with Bus error at region+GUARD, exit 134
via the sx crash handler) — not corpus-pinned, since a deliberate-overflow crash
is host-fragile and a 'child faulted' fork test would not prove the boundary
catch specifically.

The x86_64 swap_context sibling is DEFERRED: sx build --target x86_64-macos
mislinks on this arm64 host (object x86_64, link step arm64) and x86_64-linux
can't run here, so it could only ship IR-only / unrun. For the highest-
corruption-risk asm, shipping un-run / un-negative-controlled code violates the
design 10.7 'correctness not existence' rule. SysV target notes (rbx/rbp/r12-r15
/rsp, no callee-saved XMM, rsp-carried return address) recorded for a future
x86_64 host. Suite green 735/0.
2026-06-21 06:51:29 +03:00

105 lines
3.8 KiB
Plaintext

// Stream B1 (fibers) B1.3b — fiber stacks are `mmap`'d with a PROT_NONE GUARD
// PAGE at their low end (§8.1.1: a fixed stack without a guard silently
// corrupts neighbors on overflow — the guard turns overflow into an immediate,
// loud fault instead). The stack grows DOWN, so the guard sits at the lowest
// address; when a fiber's SP descends past the usable region it hits the
// unwritable guard and faults at the boundary.
//
// This is the positive integration: a fiber bootstrapped on a guarded `mmap`
// stack runs real recursion + yields correctly, and the guard syscall is
// asserted to have succeeded (`mprotect` → 0). The guard FIRING on overflow is
// validated separately (a fiber recursing past its 128KB stack faults with
// "Bus error" at the guard page, address = region+GUARD) — a deterministic
// corpus assertion for it is omitted because a deliberate stack-overflow crash
// is host/runtime-fragile (the sx crash handler turns the fault into SIGABRT,
// and the fault address varies run-to-run).
//
// aarch64-macos-pinned: the `mmap` flag constants (MAP_ANON = 0x1000) and the
// 16 KB page size are Apple-specific; the asm switch is per-arch. Runs
// end-to-end here, ir-only on a mismatch.
#import "modules/std.sx";
mmap :: (addr: *void, len: i64, prot: i32, flags: i32, fd: i32, off: i64) -> *void extern libc "mmap";
mprotect :: (addr: *void, len: i64, prot: i32) -> i32 extern libc "mprotect";
PROT_NONE :: 0;
PROT_RW :: 3; // PROT_READ | PROT_WRITE
MAP_AP :: 0x1002; // macOS MAP_PRIVATE (0x2) | MAP_ANON (0x1000)
GUARD :: 16384; // one 16 KB page (aarch64-macOS)
STACK :: 131072; // 128 KB usable
FiberCtx :: struct { regs: [13]u64; }
Fiber :: struct { ctx: FiberCtx; finish: *FiberCtx; out: i64; guard_ok: i64; }
swap_context :: (from: *FiberCtx, to: *FiberCtx) abi(.naked) {
asm volatile {
#string ASM
stp x19, x20, [x0, #0]
stp x21, x22, [x0, #16]
stp x23, x24, [x0, #32]
stp x25, x26, [x0, #48]
stp x27, x28, [x0, #64]
stp x29, x30, [x0, #80]
mov x9, sp
str x9, [x0, #96]
ldp x19, x20, [x1, #0]
ldp x21, x22, [x1, #16]
ldp x23, x24, [x1, #32]
ldp x25, x26, [x1, #48]
ldp x27, x28, [x1, #64]
ldp x29, x30, [x1, #80]
ldr x9, [x1, #96]
mov sp, x9
ret
ASM
};
}
asm {
#string T
.global _fib_tramp
_fib_tramp:
mov x0, x19
bl _fib_body
brk #0
T,
};
fib_tramp :: () extern;
// Real recursion → genuine stack usage on the guarded stack (well within 128KB).
sum_to :: (n: i64) -> i64 {
if n == 0 { return 0; }
return n + sum_to(n - 1);
}
fib_body :: (self: *Fiber) export "fib_body" {
self.out = sum_to(200); // 200*201/2 = 20100
swap_context(@self.ctx, self.finish);
}
// mmap a [guard | usable-stack] region and mprotect the low guard page
// PROT_NONE. Returns the 16-aligned stack top; reports guard-syscall success.
guarded_stack :: (f: *Fiber, size: i64) -> u64 {
region : *void = mmap(null, GUARD + size, PROT_RW, MAP_AP, -1, 0);
if (xx region) == (xx (0 - 1)) { f.guard_ok = 0; return 0; }
f.guard_ok = 0;
if mprotect(region, GUARD, PROT_NONE) == 0 { f.guard_ok = 1; }
usable : u64 = (xx region) + GUARD;
top : u64 = usable + size;
return top - (top % 16);
}
main :: () -> i64 {
main_ctx : FiberCtx = ---;
f : Fiber = ---; f.finish = @main_ctx; f.out = -1; f.guard_ok = 0;
top := guarded_stack(@f, STACK);
f.ctx.regs[0] = xx @f;
f.ctx.regs[10] = 0;
f.ctx.regs[11] = xx fib_tramp;
f.ctx.regs[12] = top;
swap_context(@main_ctx, @f.ctx);
print("guard armed: {}\n", f.guard_ok); // 1 — mprotect(PROT_NONE) succeeded
print("sum: {}\n", f.out); // 20100 — fiber ran on the guarded stack
return 0;
}