code wiki / _hdl_build / nx_scm_rights_gate.nx

nx_scm_rights_gate.nx

buildroot/runtime/_hdl_build/nx_scm_rights_gate.nx

13703 B283 linesdepth 3pulls 5 transitivereach 0 importersview sourcekind gate/proof
docsdependenciesstructsconstsfunctions

about

nx_scm_rights_gate.nx -- does SCM_RIGHTS descriptor passing ACTUALLY MOVE A DESCRIPTOR? 2026-08-21, /compare/trafficsafety ITEM 2. sys_sendmsg was ABSENT-PROVEN from the whole tree (corpus_complete=1) until today, so SCM_RIGHTS -- the mechanism nginx, HAProxy and Envoy all use to replace a process WITHOUT releasing its listening socket -- could not be written at all. WHY THE TEETH LOOK LIKE THIS. A wrong msghdr or cmsghdr layout DOES NOT FAIL LOUD: sendmsg still returns a positive byte count and simply transfers nothing, and recvmsg still returns a positive byte count having delivered no ancillary data. A gate that read return codes would be GREEN on a shim that moves no descriptor at all. So every proof here is BEHAVIOURAL and runs between two REAL processes: the parent hands a descriptor over, RELEASES ITS OWN COPY, and only then does the child use it in a way the parent can observe from the other side. THE ANTI-VACUITY DESIGN IS THE SUBJECT'S CREATION ORDER. Every descriptor under test is created AFTER the fork, so the child cannot possibly have inherited it -- a child that "succeeded" by using an inherited descriptor number is impossible by construction, not merely unlikely. AND THE ORDERING IS DETERMINISTIC, NOT HOPEFUL. The child waits for a go-byte that the parent writes only AFTER closing its own copy, so "the descriptor outlives the sender letting go of it" is a proven sequence rather than a race the test usually wins. PHASE 2 IS THE TS1 INVARIANT IN MINIATURE: the descriptor passed is a LISTENING SOCKET, the parent closes its copy, and then the parent CONNECTS TO THE PORT. That connect can only succeed if a listener still exists, and the only process holding one is the child that received it. This is the property SO_REUSEPORT co-binding can never demonstrate: not "two processes can bind", but "the socket never stopped existing while its owner changed". TRANSPORT NOTE, MEASURED NOT ASSUMED: the control channel is a NAMED AF_UNIX rendezvous rather than a socketpair, because socketpair returns EFAULT for every input on this host (pinned by controls in nx_scm_probe; the note lives beside sys_socketpair in the shim). Named rendezvous is also what the field actually uses, so nothing is lost by the substitution. NEG-CONTROL: a receiver must REFUSE to invent a descriptor. An ordinary byte with no ancillary data must come back as the NAMED refusal SCM_ERR_NO_CMSG, never as a plausible small integer that a caller would happily use as an fd. expect_exit: 0 license_tier: ORIGINAL No hw writes (Rule 26).

dependencies 4 imports · 0 importers

nx_syscalls.nx nx_unix_socket.nx nx_gate_emit_lib.nx nx_gate_verdict.nx nx_scm_rights_gate.nx

imports: nx_syscalls.nxnx_unix_socket.nxnx_gate_emit_lib.nxnx_gate_verdict.nx

imported by: nobody (leaf or entry point)

call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown

main gv_ctr sys_mmap nxa_die sys_write sys_exit nxa_lock_take nxa_lock_addr sys_write ↻ nxa_lock_give nxa_lock_addr ↻ nxa_report_overrun sys_write ↻ nxa_dump_printable sys_write ↻ nxa_dump_sizes sys_write ↻ gv_head gv_puts sys_write ↻ sys_mkdir sys_unlinkat sys_unix_listen sys_socket sys_mmap ↻ sys_close sys_bind sys_listen gv_need gv_puts ↻ gv_verdict gv_note_bare_rate gv_bare_rate gv_at gv_obj_has_n gv_at ↻ gv_puts ↻ gv_num sys_mmap ↻ sys_write ↻

structs

none

consts

42const SG_NUM_SCRATCH: i64 = 24
43const SG_ASCII_ZERO: i64 = 48
44const SG_B10: i64 = 10
45const SG_ONE_BYTE: i64 = 1
46const SG_BOX_BYTES: i64 = 8
47const SG_SA_IN_BYTES: i64 = 16
48const SG_BYTE_RADIX: i64 = 256
49const SG_BACKLOG: i64 = 4
52const SG_LOOPBACK_A: i64 = 127
53const SG_LOOPBACK_D: i64 = 1
57const SG_PORT_BASE: i64 = 39000
58const SG_PORT_TRIES: i64 = 32
59const SG_PORT_NONE: i64 = 0 - 1
61const SG_PROOF_BYTE: i64 = 88
62const SG_GO_BYTE: i64 = 71
63const SG_PING_BYTE: i64 = 80
64const SG_ECHO_BYTE: i64 = 90
67const SG_CHILD_OK: i64 = 0
68const SG_CHILD_CONN_FAIL: i64 = 2
69const SG_CHILD_NEG_FAIL: i64 = 3
70const SG_CHILD_RECV_FAIL: i64 = 4
71const SG_CHILD_GO_FAIL: i64 = 5
72const SG_CHILD_WRITE_FAIL: i64 = 6
73const SG_CHILD_LRECV_FAIL: i64 = 7
74const SG_CHILD_GO2_FAIL: i64 = 8
75const SG_CHILD_ACCEPT_FAIL: i64 = 9
76const SG_CHILD_ECHO_FAIL: i64 = 10
80const SG_DEADLINE_S: i64 = ACCEPT_TMO_S
83const SG_CHILD_ALARM_S: i64 = ACCEPT_TMO_S * 2
86const SG_DIR: *u8 = "/tmp/nx_scm_rights_gate" as *u8
87const SG_SOCK: *u8 = "/tmp/nx_scm_rights_gate/rv.sock" as *u8

functions

89func g_putn(v: i64) -> i64
called by 2: gqmain calls 2: sys_writesys_mmap
100func gq(label: *u8, got: i64, want: i64, ctr: *i64) -> i64
called by 1: main calls 3: gv_checkg_putsg_putn
113func g_sa_loopback(sa: *u8, port: i64) -> i64
called by 1: main
128func g_put1(fd: i64, v: i64) -> i64
called by 1: main calls 2: sys_mmapsys_write
134func g_get1(fd: i64) -> i64
called by 1: main calls 2: sys_mmapsys_read
142func main() -> i64