HakiOS — network stack review
A read-level audit of the networking code: the en0/en1 data paths, the links behind them,
and the Apple80211 control plane iOS talks to. The goal was to find real correctness bugs and to
map what is genuinely left to do. No build or device run was part of this pass (done on a Windows
host); every finding is from reading the source, with file and line references so each can be
checked.
Scope
Reviewed in full:
| File | What it is |
|---|---|
xnu/files/bsd/sys/hakios.net.h |
the link ⇄ interface contract |
drivers/net/hakios.net.c |
enN: the Ethernet-looking interface on the kernel KPI |
drivers/net/hakios.virtio_net.c |
the QEMU virtio-net link behind en0 |
drivers/modem/hakios.cell.c |
en1: the modem's IP packets as Ethernet (ARP/DHCP in kernel) |
drivers/modem/hakios.ipa.c |
the IPA/GSI data path (rings, endpoints, tables) |
drivers/wifi/hakios.apple80211.c |
the Apple80211 ioctl/event surface for wifid |
drivers/wifi/hakios.sta.c |
the station boundary (scan/join/leave serialization) |
wifi/lx/src/lx_80211.c |
the ath11k port's boundary: beacon parse, scan results, join |
Not reviewed line-by-line this pass: hakios.wifi.c (DSP/subsystem bring-up — the radio works
today), hakios.mpss.c and hakios.qmi.c (the modem; the in-progress cellular registration lives
here — see the roadmap below), and the non-network virtio devices.
Bottom line
The core data paths are correct and defensively written. No confident, reachable correctness bug
was found in the reviewed code. Bounds are checked at the trust boundaries (user space via
copyin/copyout, and over-the-air beacon data), ring invariants are maintained correctly, and
the cache is synchronised around every DMA buffer. The items below are latent portability concerns
and optional hardening — not live bugs on the POCO X5 Pro.
What was verified correct (not just "looks fine")
virtio-net rings (
hakios.virtio_net.c): the split-queue handling is right. The receive queue is primed with all 64 buffers andavail->idxseeded toQUEUE_SIZE(queue_make, line 155); reclaim/refill use% QUEUE_SIZEmasking with properdmb ishbarriers around theavail->idxpublish (lines 188–192, 213–215). Transmit reclaims completed buffers before grabbing a free one and drops cleanly when full (lines 174–180).IPA GSI receive invariant (
hakios.ipa.c): the "at mostslots-1outstanding" rule is held end to end. Priming postsslots-1buffers (line 859), andreceive_eventconsumes the buffer atdone % slots, then posts the previously free buffer atnext % slots(lines 655–695) — so the just-read buffer becomes the new free slot and is never re-posted while still being read. The off-by-one fixed in commit03a098bis correctly in place.QMAP de-framing (
hakios.ipa.creceive_event, lines 671–689): every field is bounded —at + 4 <= length,at + 4 + packet > lengthbreak, andpad > packetreject — before handingpacket - padbytes up. No over-read.In-kernel ARP/DHCP (
hakios.cell.c): the DHCP reply builder stays inside its buffer. Worst case option set is ~42 bytes frombootp+240; the 300-byte pad floor keeps the frame ≤ the 346-bytereply[](lines 142–221). The request parser boundsihland the options loop against the frame end (lines 126–162).Apple80211 user boundary (
hakios.apple80211.c):give()always clampscopyouttoreq_len(line 239);set_join/set_scanvalidatessid_lengthandkey_lengthagainst the destination beforememcpy(lines 477, 534, 543–549). Join buffers are wiped withmemset_sbefore free (lines 560–565) so passphrases don't linger.Beacon parse (
wifi/lx/src/lx_80211.c):element()(line 525) walks TLVs with bothat + 2 <= lengthandat + 2 + elements[at+1] <= lengthguards. The SSID copy clamps to the 32-byte field (name[1] <= sizeof(network->name), line 608) and the elements copy is gated onlength - 36 <= ELEMENTS_MOST(lines 597, 567). The single caller guardsskb->len >= 36before callingheard()(line 908), so thelength - 36subtraction cannot underflow in practice.
Latent concerns (ranked) — not live bugs on the current phone
1. IPA event-ring write pointer is read as 32-bit only
hakios.ipa.c, channel_events (line 478):
uint32_t wp = gsi_rd(EV_CNTXT(ch->ring_id, 4)), base = (uint32_t)ch->event_ring.phys, ...
if (wp < base || wp >= base + ch->slots * ELEMENT_SIZE) { ... return 0; }
Both the hardware write pointer and the ring base are truncated to 32 bits. This is correct as long as the event ring's physical address — and the whole ring — sit in the low 4 GB, or at least do not straddle a 4 GB boundary. On the POCO X5 Pro the data path comes up, so today this holds. On a future port whose DDR layout places a GSI event ring above 4 GB, the comparison and the index math break silently (the "write pointer outside the ring" path, line 482).
- Risk: medium for porting, none for the current device.
- Recommendation: when porting to a new SoC, also read the high word (
EV_CNTXT(ring, 5)) and compare as 64-bit. Do not change this blind on the working phone — verify that reg 5 carries the write-pointer high word on the target GSI revision first, because a wrong assumption here would break a path that currently works. Flagged as a porting checklist item, not a patch.
2. Defense-in-depth at the bss → user boundary
hakios.apple80211.c give_network (lines 265–266) copies bss->ssid_length into the 32-byte
result->ssid without a local clamp. It is safe only because the producer guarantees
ssid_length ≤ 32 (lx_80211.c:608, and want_length via the :1586 check). That's a real
invariant today, but it lives in a different module (the ath11k port). A one-line clamp
(min(ssid_length, sizeof result->ssid)) at the consumer would make the kernel robust to a future
change in the port without relying on it. Same reasoning for heard() not trusting its caller's
>= 36 guard.
- Risk: none today; cheap insurance against future refactors.
- Recommendation: optional hardening; apply with a build + a scan test.
3. IPv6 asymmetry on en1 is by design, worth a note
hakios.cell.c: inbound IPv6 is wrapped and passed up (cell_receive, version 6 accepted,
line 267), but outbound IPv6 is dropped (cell_transmit, counted as not_ip4, line 253). The
data session is IPv4-only, so this is intentional; just confirming it's not an oversight. If an
IPv6 PDP type is ever enabled, the transmit side needs the matching branch.
The real remaining work: cellular registration & data session
This is the part the README marks "in progress," and it is not in the reviewed files — it's in
hakios.mpss.c / hakios.qmi.c (QMI DPM/WDA/WDS, NAS). The data plumbing below it (en1, the
IPA path, ARP/DHCP) is in place and reviewed-correct; what's left is the modem-side handshake that
brings the session up so hakios_cell_session(up=true, ...) gets called with a real address.
From the git history the current frontier is: DPM Open Port → WDA Set Data Format → WDS Bind Mux →
Set IP Family → Start Network Interface, with registration gating the start (commits 82e0292,
3b146ba, 150d30f). Advancing this needs a live run log from the phone, not source reading —
the failures are QMI error codes and timing, visible only at runtime:
XL_SB_DELAY=8 XL_SECS=600 \
XL_CMDLINE="... modem=1 modem_ipa_data_trace=1 net_trace=20 net_names=1" \
./xl run
Send logs/ from such a run and the cellular path can be debugged against the actual QMI exchange.
How to proceed
- The reviewed data paths are solid — no fixes needed to ship them.
- The two optional hardening items (§2) are safe one-liners; say the word and they go in as a small, clearly-scoped patch for you to build and test.
- The 32-bit event-pointer item (§1) is a porting note, deliberately not a blind patch.
- Cellular registration is the open frontier and is log-driven — a phone run log unblocks it.