DF-3388 / verdict.json
{ "finding_id": "DF-3388", "status": "inconclusive", "reproduced": 0, "impact": "none", "confidence": "likely", "verdict": "Race window code-proven: wg_peer_destroy() kfree()s the wg_peer and noise_remote_free() clears the handshake index while in-flight noise_consume_initiation() consumers (already past noise_remote_lookup(), holding a remote ref) can still re-insert &r->r_index into l_index_hash and dereference the freed peer via noise_remote_arg(); FreeBSD defers these frees via NET_EPOCH_CALL + r_cleanup, the DragonFly port dropped both. The userspace forger (built from the kernels own crypto) provably reaches the racing consumer path - 199 accepted forged initiations with 92-byte kernel responses observed - and ~5.3M peer-destroy events were raced against ~340k consumer critical sections over ~28 minutes with no panic; both UAF manifestation paths are silent on the stock kernel because the churns M_ZERO same-type reallocation heals the peer deref and explicit_bzero() turns the dangling index node into a null-terminated (chain-truncating) list element. Bug: certain; runtime manifestation on stock kernel: not observed.", "exploit_chain": "", "evidence": [ "logs/tcpdump_sanity.log: 199 x 92-byte handshake responses = forged initiations accepted into noise_consume_initiation critical section", "logs/churn_race.log + env.txt: 165,815 destroy-all cycles (5.3M wg_peer_destroy) vs ~895k forged initiations, guest stable, NOISE object count 33 (no orphans)", "VERDICT.md: interleaving analysis with wg_noise.c:1121-1158, wg_noise.c:674-681, wg_noise.c:596-611, if_wg.c:640-696 citations" ], "kernel_refs": [ "sys/net/wg/wg_noise.c:613-620", "sys/net/wg/wg_noise.c:1088-1169", "sys/net/wg/wg_noise.c:1140-1158", "sys/net/wg/wg_noise.c:674-681", "sys/net/wg/wg_noise.c:596-611", "sys/net/wg/if_wg.c:640-696", "sys/net/wg/if_wg.c:1648-1660" ], "poc_changes": "Wrote the PoC from scratch (no seed): userspace Noise_IKpsk2 initiation forger using the kernels own portable blake2s/curve25519/chacha20/poly1305 compiled with a sys/ shim; SIOCSWG churn driver with WG_INTERFACE_REPLACE_PEERS; keygen. Fixed during bring-up: i_port is host byte order (kernel htons()s it itself), peer public keys must be derived from the peer private keys (churner originally registered the private keys as publics), pace below the wg underload threshold (handshake queue >= 512 switches to cookie mode which bounces initiations before the racing path).", "attempts": 4, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "runtime_sec": 4200, "guest_dirty": 0, "build_cmd": "cd /tmp/df3388 && sh build.sh", "run_cmd": "./wgchurn /tmp/wgkeys.bin & ./wgforge 127.0.0.1 51820 /tmp/wgkeys.bin & (x3 forgers)", "code_hash": "7547b7b433f8e73f238e6c2012396fc515837499b79b9250070236059fee951f 48f6d09c8ce7ecb7170a7d561b4954a4486ac9a7eedf2dd47f3af639337eee80 cf701704de1d7e97464d4f9a657fbd95e253d8761bf08ca44c723e388954c2a7", "notes": "Fix validation skipped: memory-corruption finding did not reproduce visibly on the stock kernel (status inconclusive), so the mandatory fix-build loop was not triggered; fix.diff is authored against the read-only sys/ tree but was not built. Underlying bug classification stays High/certain from code analysis. Guest reset (with-src) performed after evidence collection.", "recommended_fix": "Port FreeBSDs deferred teardown: add a last-reference cleanup callback to struct noise_remote that frees the wg_peer (instead of kfree in wg_peer_destroy), and re-check a r_dying flag under r_handshake_lock in noise_consume_initiation/consume_response/create_response before re-inserting the index." } |