code wiki / _hdl_build / nx_scm_rights_gate.nx
nx_scm_rights_gate.nx
buildroot/runtime/_hdl_build/nx_scm_rights_gate.nx
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
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
structs
| none |
consts
| 42 | const SG_NUM_SCRATCH: i64 = 24 |
| 43 | const SG_ASCII_ZERO: i64 = 48 |
| 44 | const SG_B10: i64 = 10 |
| 45 | const SG_ONE_BYTE: i64 = 1 |
| 46 | const SG_BOX_BYTES: i64 = 8 |
| 47 | const SG_SA_IN_BYTES: i64 = 16 |
| 48 | const SG_BYTE_RADIX: i64 = 256 |
| 49 | const SG_BACKLOG: i64 = 4 |
| 52 | const SG_LOOPBACK_A: i64 = 127 |
| 53 | const SG_LOOPBACK_D: i64 = 1 |
| 57 | const SG_PORT_BASE: i64 = 39000 |
| 58 | const SG_PORT_TRIES: i64 = 32 |
| 59 | const SG_PORT_NONE: i64 = 0 - 1 |
| 61 | const SG_PROOF_BYTE: i64 = 88 |
| 62 | const SG_GO_BYTE: i64 = 71 |
| 63 | const SG_PING_BYTE: i64 = 80 |
| 64 | const SG_ECHO_BYTE: i64 = 90 |
| 67 | const SG_CHILD_OK: i64 = 0 |
| 68 | const SG_CHILD_CONN_FAIL: i64 = 2 |
| 69 | const SG_CHILD_NEG_FAIL: i64 = 3 |
| 70 | const SG_CHILD_RECV_FAIL: i64 = 4 |
| 71 | const SG_CHILD_GO_FAIL: i64 = 5 |
| 72 | const SG_CHILD_WRITE_FAIL: i64 = 6 |
| 73 | const SG_CHILD_LRECV_FAIL: i64 = 7 |
| 74 | const SG_CHILD_GO2_FAIL: i64 = 8 |
| 75 | const SG_CHILD_ACCEPT_FAIL: i64 = 9 |
| 76 | const SG_CHILD_ECHO_FAIL: i64 = 10 |
| 80 | const SG_DEADLINE_S: i64 = ACCEPT_TMO_S |
| 83 | const SG_CHILD_ALARM_S: i64 = ACCEPT_TMO_S * 2 |
| 86 | const SG_DIR: *u8 = "/tmp/nx_scm_rights_gate" as *u8 |
| 87 | const SG_SOCK: *u8 = "/tmp/nx_scm_rights_gate/rv.sock" as *u8 |
functions
| 89 | func g_putn(v: i64) -> i64 |
| 100 | func gq(label: *u8, got: i64, want: i64, ctr: *i64) -> i64 |
| 113 | func g_sa_loopback(sa: *u8, port: i64) -> i64 called by 1: main |
| 128 | func g_put1(fd: i64, v: i64) -> i64 |
| 134 | func g_get1(fd: i64) -> i64 |
| 142 | func main() -> i64 |