nx_ownership.nx
buildroot/runtime/nx_ownership.nx
about
nx_ownership.nx -- LN4 + LN5 OWNERSHIP fixture organ (lang.plan rungs LN4 own_check_move and LN5
own_check_uaf; /compare/lang rows "Memory safety (bounds, ownership, use-after-free)" and
"Use-after-free prevented (CWE-416 class)").
THE HAZARD, twice, in the two functions that ARE the watch symbols. Under the default build both
compile clean AND RUN EXIT 0 -- and the second one is the whole argument for this rung, because it
is right for a reason nobody wrote down: sys_munmap's body is `if len <= NXA_SMALL_MAX { return 0 }`
(nx_syscalls.nx:294, NXA_SMALL_MAX = 256), so releasing 64 bytes is a NO-OP in the bump arena and
the read afterwards happens to find its own data still there. Change the constant, or the size, or
the allocator, and the same source is a SIGSEGV. THAT is what "the arena no-free doctrine
mitigates in practice" actually means, and it is why a doctrine is not a guarantee.
Under `--ownership` the compiler REFUSES both, naming the local, the line that ended its life, and
which of the two rules fired (capability=own-check-move / capability=own-check-uaf).
Siblings: nx_ownership_ok.nx is the POSITIVE CONTROL (every accept idiom, must still compile AND
run under the mode -- a deny-everything checker fails there, not here); nx_ownership_alias.nx is
the alias-sweep witness; nx_ownership_dfree.nx is the double-free witness.
license_tier: ORIGINAL No hw writes (Rule 26).
dependencies 1 imports · 0 importers
imports: nx_syscalls.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 25 | const OWN_FIX_BYTES: i64 = 64 |
| 26 | const OWN_FIX_MARK: i64 = 41 |
functions
| 33 | func own_check_move(seed: i64) -> i64 |
| 43 | func own_check_uaf(seed: i64) -> i64 |
| 50 | func main(argc: i64, argv: *i64) -> i64 |