Документацію написано англійською.
Wi-Fi bring-up review: hakios.wifi.c
Scope and method
- File:
drivers/wifi/hakios.wifi.c, 4394 lines, read in full, line by line. - A few lines of context were read in other files to size the impact of a finding. They were not audited:
xnu/files/bsd/sys/hakios.qrtr.h,drivers/modem/hakios.qmi.c(the receive path only),userland/build.sh(howfw/networkis installed), and a grep for users ofhakios_wifi_say,hakios_wifi_tellandpassphrase. - Modem code was not examined.
- Method: manual tracing of every value that comes from a remote processor or the firmware (SMEM partition and item headers, SMP2P, the QMP descriptor, GLINK queue words, QRTR headers, MDT/ELF header, QMI-reported register window) to its first use as a size, offset or index. Wait loops, error paths, locks, the shutdown path, the log functions and boot-arg parsing were traced the same way.
- Nothing was built or run. Findings carry CONFIRMED (traced end to end in the source), PLAUSIBLE, or NEEDS-HARDWARE. "Remote" below means the DSP, the AOP or the radio firmware, and the shared memory is writable by them by design.
Findings
| id | severity | confidence | where | one line |
|---|---|---|---|---|
| W1 | HIGH | CONFIRMED | 2470-2488, 2519-2523, 2586-2599 | GLINK message length overflows uint32 to 0; the message is never consumed and glink_pump() loops forever with glink_lock held, which also wedges shutdown |
| W2 | HIGH | CONFIRMED | 571-572, 587-589, 2312-2318, 2358-2363, 2388-2396 | SMEM item offset and size are not checked against the partition or the 2 MB mapping; the GLINK queues index the mapping with remote-chosen extents |
| W3 | MEDIUM | CONFIRMED | 866, 1480, 1562-1566 | the AOP's mailbox box + size <= 0x400 check wraps in uint32, so our_box can lie outside the mapped block |
| W4 | LOW | CONFIRMED | 2915, 2935, 3251 | QRTR at + header.size > length wraps; the receiver is handed a huge length and a pointer that is not inside the packet |
| W5 | MEDIUM | PLAUSIBLE / NEEDS-HARDWARE | 3086-3091, 3974-3977 | a DSP that says "err-fatal" is only logged; the driver is not stopped and hakios_wifi_dsp_is_up() still says yes |
| W6 | LOW | PLAUSIBLE | 604-620, 657-661, 3035, 3217 | state shared by the link thread and the main thread without a lock: static found[256], static at, link_thread_is |
| W7 | LOW | CONFIRMED | 3437-3466 | step 16 maps size bytes at a firmware-given address with no upper bound, and does not check the map result |
| W8 | LOW | CONFIRMED | 1894-1897, 1832-1836, 2440-2442, 399, 502, 1889 | error-path leaks (25 MB mapping, a page per start, intent buffers) and unchecked ml_io_map* results |
| W9 | LOW | NEEDS-HARDWARE | 2146-2156, 1810-1856, 4149-4163 | errors between step 8 and dsp_started = true never ask TrustZone to shut the image down; a panic in a small window is not cleaned up |
| W10 | LOW | PLAUSIBLE | 4104-4113 | the end-of-run stop waits at most 40 s for the start thread, then shuts the DSP down under it |
| W11 | LOW | PLAUSIBLE | 2602-2614 | the list of received packets and the intents' DONE_REUSE have no bound; a flooding DSP can use unbounded memory while the link thread is slow |
| W12 | LOW | NEEDS-HARDWARE | 2469-2475, 2358-2363 | no load barrier between reading the DSP's queue head and reading the payload |
| W13 | LOW | CONFIRMED | 4016, 4048-4052, 4080 | wifi_mac is a boot-arg although the file's own text says boot-args are echoed and printed with a panic; the passphrase length is logged |
Counts: 7 CONFIRMED (W1, W2, W3, W4, W7, W8, W13), 4 PLAUSIBLE (W5, W6, W10, W11; W5 also depends on hardware), 2 NEEDS-HARDWARE (W9, W12). Severity: 2 HIGH (W1, W2), 2 MEDIUM (W3, W5), 9 LOW.
Details
W1 GLINK length overflow: an endless loop under glink_lock (HIGH, CONFIRMED)
glink_pump() (line 2470) works out the message length in a uint32_t from fields of the DSP's own message.
- Line 2470:
uint32_t ... length = sizeof(message). - Line 2477: OPEN,
length += message.param2. - Line 2479: INTENT,
length += 8 * message.param2. - Line 2485: DATA/DATA_MORE,
length = 16 + words[0]. - Line 2487:
length = (length + 7) & ~7u. - Line 2488: the only check is
length > glink.theirs_size - 8.
All three sums wrap. With OPEN param2 = 0xfffffff8, length = 8 + 0xfffffff8 = 0, and (0 + 7) & ~7 = 0, so both of these pass:
0 > theirs_size - 8is false;waiting < 0is false.
The GLINK_OPEN case then runs glink_advance(0) (line 2522), which stores the tail it just read, and breaks. The queue has not moved, waiting is still at least 8, and the while (glink.up && !glink.broken) loop (line 2469) reads the same message again, forever. The same happens with DATA and words[0] = 0xfffffff0. The intent is invalid, so the message is dropped with glink_advance(0) at line 2594, and again nothing is consumed.
A third variant is INTENT with param2 = 0x20000000. Then 8 * param2 is 0 and length is 8, but the loop at line 2546 runs 536 million times with glink_peek inside it, reading past the message. It runs only if param1 == glink.their_channel.
Consequences, all traced:
- The link thread spins at 100% CPU holding
glink_lock. The OPEN path prints one log line per turn (line 2524), which floods the log and the loader log. glink_send()takesglink_lock(line 2671), so every QRTR sender (the driver, step 14 and step 15) blocks for good.- Shutdown cannot finish.
dsp_shutdown()callshakios_lx_stop(), which sends throughglink_sendand blocks. Even without that,link_stop()waits 2 s (line 3125) and then doeslck_mtx_lock(&glink_lock)(line 3132), which never returns. TrustZone is never asked to shut the DSP down, and the next kernel finds it running.
A crashed or hostile DSP can produce this by writing one 8-byte message into its queue.
Fix (sketch):
- uint32_t waiting = glink_waiting(), length = sizeof(message);
+ uint32_t waiting = glink_waiting();
+ uint64_t length = sizeof(message);
...
- length += 8 * message.param2;
+ length += 8ull * message.param2;
...
- length = 16 + words[0];
+ length = 16ull + words[0];
}
- length = (length + 7) & ~7u;
- if (length > glink.theirs_size - 8) {
+ length = (length + 7) & ~7ull;
+ if (length < sizeof(message) || length > glink.theirs_size - 8) {
... glink.broken = true; return;
}
Also bound the INTENT loop by length / 8 - 1 rather than param2, and consider a per-call iteration cap in the pump so that a DSP that never stops writing cannot hold the lock indefinitely.
W2 SMEM item extents unchecked: remote-chosen offsets and sizes index the mapping (HIGH, CONFIRMED)
smem_items() builds found[].offset and .size from the entries in the partition. The partition is in shared memory, and the DSP makes item 480 itself when it starts (see the comment at line 2230).
- Bottom entries (lines 571-572):
offset = partition + at + sizeof(entry) + entry.padding_header,size = entry.size - entry.padding_data.- Both paddings are
uint16_tfields from memory. padding_data > entry.sizemakessizewrap to about 4 GB.- The only bound is
entry.size > header.size(line 566). Nothing checks thatat + 16 + padding_header + sizestays insideheader.size, or insideSMEM_SIZE.
- Both paddings are
- Top entries (lines 587-589) have the same
entry.size - entry.padding_datawrap. Their offset is bounded (entry.size <= at).
Consumers trust the result:
glink_find()(line 2312) accepts anyours_sizeortheirs_sizethat is a multiple of 8 and at least 1024. A wrapped0xfffffff8passes.glink_waiting()(line 2349) only compares head and tail withtheirs_size. Both words are in the DSP-writable descriptor.glink_peek()(line 2361) readssmem[glink.theirs + (at + i) % theirs_size]andglink_put()(line 2396) writesqueue[(head + i) % ours_size]. Neither has any bound against the 2 MB mapping.- A DSP that publishes item 480 with
size = 0x400, padding_data = 0x408getstheirs_size = 0xfffffff8, then a head word of 0x7ffffff8 is "valid". The nextglink_peekreads far outside the mapping, which is a kernel data abort (panic). For item 479 (ours),glink_putwrites the kernel's message bytes at the same kind of unchecked offset. smem_poke()(line 515) has no bound check at all.step_our_item()pokesour_item + 0..340(line 2059) anddsp_listen()anddsp_ask_to_stop()poke intoour_item.our_item_find()(line 1603) only checkssize >= sizeof(struct smp2p), and for a bottom entry that wrapped, this passes with an offset that can lie past the mapping.
smem_peek() itself is safe (it checks offset + size > SMEM_SIZE). The unsafe parts are smem_word(), smem_poke(), glink_peek() and glink_put().
Fix (sketch), in smem_items() before using entry:
+ if ((uint32_t)entry.padding_data + entry.padding_header > entry.size ||
+ at + sizeof(entry) + entry.padding_header + entry.size > header.free_uncached) { break; }
For top entries require entry.padding_data <= entry.size. Then, in smem_item() and glink_find(), reject any item with offset + size > SMEM_SIZE. Make smem_poke() and smem_word() check offset + 4 <= SMEM_SIZE and return false (or ignore) otherwise.
W3 QMP mailbox bounds wrap (MEDIUM, CONFIRMED)
step_mailbox_open_locked()line 1480:if (size < QMP_MESSAGE + 4 || box < 0x3c || box + size > aoss.size || (box & 3)).step_mailbox()line 866 has the same check.
box and size are read from the AOP's message memory (get() at 1478-1479). Both are uint32_t. box = 0x100000 and size = 0xffff0000 sum to 0 and pass. our_box = 0x100000 is accepted (line 1488 or 1523). mailbox_send_locked() then does rd(aoss.base + our_box) and 16 wr(aoss.base + our_box + 4 + 4*i, ...) (lines 1557-1564). The mapping is 0x400 bytes (line 804). This is a read and write outside the mapping, at an offset chosen by the AOP. In step_mailbox it is a read only. The AOP is more trusted than the DSP, but its message memory is remote input all the same.
Fix:
- if (size < QMP_MESSAGE + 4 || box < 0x3c || box + size > aoss.size || (box & 3)) {
+ if (size < QMP_MESSAGE + 4 || box < 0x3c || box > aoss.size || size > aoss.size - box || (box & 3)) {
(the same in step_mailbox).
W4 QRTR size check wraps (LOW, CONFIRMED)
Line 2915: if (header.size == 0 || at + header.size > length). Both at and header.size are uint32_t. With a v1 header (at = 32) a header.size >= 0xffffffe0 makes the sum wrap to a small value and the datagram is accepted. For a v2 header at is 16..271 and the same holds for size >= 0x100000000 - at.
receive(context, ..., data + at, header.size)(line 2948) is called with a length of about 4 GB and a pointer that may run past the packet allocation (the packet issizeof(*packet) + fillbytes).- The QMI path is protected:
qmi_receiveathakios.qmi.c:686rejectslength > largest + QMI_HEADER_SIZE. wlfw_receive()(line 3248) clamps to 64 bytes, so it over-reads at most 64 bytes of adjacent heap intowlfw_answer_bytes. Only step 14 withwifi_ask=1prints it (line 3520).- For control messages, line 2935 copies
min(header.size, 20)bytes fromdata + at(up to 20 bytes beyond the packet).
Impact is a small heap over-read from remote-chosen sizes. Fix: if (header.size == 0 || header.size > length || at > length - header.size).
W5 A crashed DSP is not reported to the driver (MEDIUM, PLAUSIBLE / NEEDS-HARDWARE)
link_thread() (lines 3086-3091) prints once when the DSP's "slave-kernel" word has bit 0 ("err-fatal") and keeps pumping. hakios_wifi_dsp_is_up() (line 3974) returns dsp_started && glink.open, with no fatal state. Nothing stops the driver, or marks the link broken, so glink_send() waits 5 s per call and the driver's own threads keep running.
The file's own comment (lines 3332-3339) records that a phone reset without a word when the driver's thread "was then still writing ring pointers into registers that had stopped answering". After an err-fatal the radio's registers are in the same state. Whether that is the same fault could not be settled from the repository: NEEDS-HARDWARE.
Fix (sketch): add a static volatile bool dsp_dead, set it in link_thread (and in dsp_listen) when DSP_FATAL is seen, make hakios_wifi_dsp_is_up() return false, make glink_send() return ENOTCONN at once, and ask the driver to stop (hakios_lx_stop() from the shutdown path is already in place).
W6 State shared across threads without a lock (LOW, PLAUSIBLE)
smem_item()(line 604) uses onestatic struct smem_item found[256], filled and then searched with no lock. It is called from the link thread (show_crash_reason()at 3089, anddsp_words()whileat == 0) and from the main thread (our_item_find()indsp_ask_to_stop()during shutdown,glink_reset()). If two scans overlap, a match in one can return the offset of an entry that the other has overwritten, andsmem_poke()then writes at the wrong place. Window: a DSP crash message at the same moment as a shutdown, which is narrow. Fix: makefounda stack or per-caller array, or takehakios_smem_alloc_lockaround the scan.dsp_words()cachesatin a function static (line 657) written by both threads (harmless today because the value is idempotent).link_thread_is(lines 3035 and 3217) holds athread_tthat has been deallocated once the thread has ended.qrtr_close()comparescurrent_thread()against it to decide whether to wait onqrtr_calling. If the address is reused by a new thread, that thread skips the wait and a callback that is running can use a closed port. It is set afterkernel_thread_startreturns, so the first moments are also unprotected. Fix: clear it whenlink_runsgoes false.
W7 Step 16 trusts the firmware's register window (LOW, CONFIRMED)
Lines 3437-3454: address (from hakios_wlfw_registers, i.e. what the Wi-Fi firmware told over QMI) is bounded only by <= 0xffffffff, and size only by >= 3 * RADIO_WINDOW. The step maps ml_io_map_unmappable(radio.phys, radio.size, VM_WIMG_IO) (line 3454), checks nothing about the result, and reads seven words at fixed offsets below 3 * RADIO_WINDOW. A firmware that reports another physical address makes the kernel read there (a protected region ends the kernel, as the comment at 3384-3395 says). It only runs with wifi_radio_read=1. Fix: require size == 0x200000 (or <= 0x200000), address + size <= 0x100000000, and a non-zero map result.
W8 Error-path leaks and unchecked map results (LOW, CONFIRMED)
step_image()lines 1894-1897 return false on a misaligned segment withoutml_io_unmap()of the 25 MB mapping made at 1889. A step that fails here and is retried leaks address space. Fix: break out and fall through to the unmap.step_header()allocates one page per start withkmem_alloc(line 1832) that is "never freed" (the comment says so), including on every failure path after it. One page per start attempt.glink_offer()lines 2440-2442: whenour_intents[i].data != NULLandsize != size, a new buffer replaces the pointer and the old one is leaked. The size comes from the DSP's INTENT_REQUEST (line 2576), andour_intent_countis reset by everystep_link. Bounded at 24 buffers of 64 KB per link restart.need()(line 399),smem_peek()(line 502),step_image()(line 1889) andstep_registers()(line 3454) use the result ofml_io_map*without a check. If the call returns 0 on failure (not verified, see Not checked), the next access is a load from a low address.
W9 Half-started TrustZone state (LOW, NEEDS-HARDWARE)
- Step 8 ends with TrustZone holding the image header and the memory window (
INIT_IMAGE,MEM_SETUP). If step 9, 10 or 11 then fails beforeAUTH_AND_RESETsucceeds,steps_over()only shuts down whendsp_started(line 4152-4157), soPAS_SHUTDOWNis never called for that peripheral. The AUTH failure path (line 2150-2154) tells the AOP "off" but does not callPAS_SHUTDOWNeither. The file's own history (lines 69-77) says a wrong TrustZone shutdown state stopped the DSP from starting again. Whether TrustZone needs the shutdown after a failed start, as Linux's remoteproc error path does, cannot be told from the sources here. dsp_startedis set only at line 2156, after the SMC returns, aprintfinsidescm()(which takessay_lockand prints to the console) and the code after it. A panic on another processor inside that window makeshakios_wifi_quiesce()return at line 4135 with the DSP running and not stopped. The window is a few milliseconds at most.
Fix (sketch): set a dsp_starting flag before the SMC and let quiesce and steps_over treat it as started; call PAS_SHUTDOWN on every failure after step 8 succeeded.
W10 The end-of-run stop does not wait for the start thread for long enough (LOW, PLAUSIBLE)
hakios_wifi_stop() (lines 4107-4109) waits whole_runs for 40 s and then goes on regardless. With wifi=1 the thread can still be inside step 15 (the driver start, calibration alone is 7 s, and the file's own comment documents a 12 s join). Shutting the DSP down under a driver that is still starting is the "registers stop answering" case at lines 3332-3339. Fix: either wait without a bound (the thread's own steps all have bounds) or set a "stop wanted" flag that whole_thread checks between steps and link_stop/glink_send honour.
W11 Unbounded queue of received packets (LOW, PLAUSIBLE)
In glink_pump() lines 2600-2616, a complete DATA message is copied into a kalloc_data packet and appended to glink.first/last, and the DSP is told DONE_REUSE at once, so it can send again at once. The consumer is the link thread, which can be held in a callback or in glink_send() for up to 5 s (line 2701) while the main thread's glink_send() also pumps and enqueues. A DSP that streams packets of up to 64 KB (size > 0x10000 is rejected in glink_offer) grows the list without bound during that time. Fix: cap the list (count or bytes) and delay DONE_REUSE until the packet has been consumed.
W12 Missing load barrier on the DSP's queue (LOW, NEEDS-HARDWARE)
glink_waiting() reads the DSP's head with rd() (line 2347), and glink_peek() then reads the payload with plain byte loads from the same mapping (line 2361). The producer side is ordered (settle() between payload and head in glink_put, lines 2399-2400), but the consumer has no dmb ishld or dsb ld between reading the head and the payload. Whether the mapping (VM_WIMG_WCOMB) allows the loads to be reordered is not stated in this repository: NEEDS-HARDWARE. Fix: __asm__ volatile ("dmb ld" ::: "memory") in glink_waiting() after the head read.
W13 Credentials and identifiers in the log and in boot-args (LOW, CONFIRMED)
hakios_wifi_radio_address()(line 4016) reads the radio's MAC from the boot-argwifi_mac. The comment abovehakios_wifi_network()(4048-4052) says boot-args "are echoed by the loader and printed with a panic", which is why the passphrase is not one; the same reasoning applies to the MAC (a device identifier). No code in this file readsfw-wifi/mac; if the reviewer's brief expects it, it is not here (lines 4001-4007 say the value is "nowhere in these sources"). The log prints only the length of the MAC (line 4039), which is fine.hakios_wifi_network()prints the lengths of the name and the passphrase (line 4080) and nothing else;join_given()zeroes its copy after the join is asked for (4196). The passphrase itself never reaches the log from this file.userland/build.sh:384installsfw/networkwith mode 0600.- A failed join flushes the whole kept log (
say_kept(), line 4210) into the real log and into the loader's log. That log holds what the driver and the steps said; this file's own lines contain no secrets, and the driver files were not audited for them (lx_80211.c:24says only lengths are said).
Checked and found fine
smem_peek()bound (line 504):offsetisuint64_t, sooffset + sizecannot wrap; everysmem_peekcaller is safe from out-of-range reads. The unsafe parts are listed in W2.smem_partition()(521-549): the TOCcountis capped at 64 (3104 bytes of table, within the 4 KB), partitionoffset + sizeis checked against the TOC,header.size == entry.sizeis required.smem_items()loop termination: each bottom iteration advancesatby at least 16 and the loop ends atfree_uncached, which is bounded byheader.size; the top loop ends onat <= free_cachedand on theentry.size + step_back > atguard (line 592), so a corrupt partition cannot make it loop. The scan cost is bounded (at most 131072 entries).smp2p_entry()and every use ofwords->valid: capped at 16.show_words()uses%.16sand a boundedsnprintfwith a size guard.dsp_ask_to_stop(),dsp_listen()andstep_our_item()indexentry[]only with the capped value, and the poke offsets (at most 336 of 340 bytes) fit the item.show_crash_reason()(710-726):text[81]is zeroed, the copy ismin(size, 80), printed with%sas an argument (not a format).- Torn reads of the 340-byte SMP2P item:
smem_peekcopies bytewise, so a snapshot can mix old and new bytes, but no state bit can appear that was not set in the old or the new value, andmemcmpchange detection is only an optimisation. - Every
say(),hakios_wifi_say(),hakios_wifi_tell()andprintf(macro forsay) call in this file has a literal format; remote data (DSP names, crash text, SMP2P names, QRTR sizes) is only passed as%s,%uor%.16sarguments. Buffers are 400 bytes withvsnprintf,say_kept()'spiece[201]and its modulo indexing are right, thekept[]ring writes are undersay_lock. The one effect left is that remote text can contain control characters or a fake log line (cosmetic). rd(),wr()andneed(): everyrdorwron a block (ipcc,aoss,rsc,arb_*,tlmm,apps_smmu*,smem_lock) is preceded by aget/put/needin the same step, and the steps run in order:wifi_step=Nalways runs 1..N, andneeded[]forwifi=1includes step 2 (which mapsipccandaoss) before any step that uses them. Offsets checked against block sizes:RSC_TCS(7) + TCS_COMMAND(15)fits 0x10000;ARB_CHANNEL(511)andARB_OWNER(511)fit 0x1100 and 0x1000; the observer window for channel 511 ends at 0x10000;GPIO_*for pins 84-86 fit 0x3000; SMMUSMR/S2CRfor 255 entries fit 0x2000.cmd_db/RPMh readers: slave counts are capped at 512, offsets stay inside the 128 KB map for the sizes involved,data_offset + entry->offset + lengthis checked againstCMD_DB_SIZE - 0x1000,levels[160]is guarded. (The command database is the boot loader's, not remote-writable at run time.)- MDT/ELF loading (1312-1385): the size is capped at one page, segment count is 3..16 and
52 + 32 * count <= size; each loaded segment is checked against the reserved window in 64 bits (paddr >= WPSS_MEMORY,file_size <= memory_size,paddr + memory_size <= window end), so the uint32highestcannot wrap;image.segment[]cannot overflow becausecount <= MAX_SEGMENTS; the segment file sizes are compared to the header;52 + 32 * count + hashes == sizeis required;name[16]fitswpss.bNN.step_image()requires 8-byte alignment of start and length, writes only inside the window, reads everything back, andsegment_word()cannot read beyondfile_size. Only the first few bytes ofheader[0..4]are read before the size is known to be at least 5 (the file is in the RAM disk; at most a few bytes past a tiny file). - SCM calls (1724-1755): all arguments are compile-time constants or validated values (
image.size,WPSS_MEMORY,kvtophysof a page-aligned single page fromkmem_alloc(KMA_KOBJECT)), the metadata page is cache-cleaned before the call, a preempted call is retried at most 100 times, errors are checked by every caller. - Wait and poll loops, with what happens at the timeout:
mailbox_wait(1 s, link and channel taken down again on each failure),mailbox_send_locked(1 s, message taken back),our_item_make_locked(1 s, lock not owned so nothing written; the hardware lock is released on every other path),spmi_read(2 ms),radio_space_onTLB sync (100 ms, returns false before the context is switched on),step_start(wifi_wait, always bounded bylimitor!dsp_started),dsp_ask_to_stop(bound_ms),glink_send(5000 ms),step_link(5 s),link_stop(2 s),doorbells()(8 iterations), the IPCC drain instep_start(8). The only unbounded loop reachable from remote data is W1. - Shutdown path (
dsp_shutdown,hakios_wifi_stop,steps_over,hakios_wifi_quiesce): order is driver, wlfw, link, ask-to-stop, TrustZone shutdown, doorbell disable, AOP "off", SMMU context back (only for a DSP started by this kernel, and only after TrustZone has agreed: if the shutdown fails the SMMU is left on, which is the safe choice). The panic path takes no lock and does not rely on a thread (theleavingflag), and its waits are loops. A failedPAS_SHUTDOWNleavesdsp_startedtrue. - Lock order:
dsp_lock->hakios_aop_mailbox_lock/glink_lock->say_lock;qrtr_calling->qrtr_lock/glink_lock; no path takes them the other way round. No spinlock is held across a sleep (there are none in this file; sleeps aredelay_for_intervalwith mutexes held, which is allowed).qrtr_close()waits for a running callback withqrtr_callingunless called from the link thread. - Use after free:
link_thread()readspacket->lengthwhile building thekfree_dataargument, before the free;link_stop()frees queued packets underglink_lock; QRTR callbacks run underqrtr_calling;our_intents[].databuffers are never freed (so never dangling);qrtr_out_packet()frees afterglink_sendreturns (which copies into the queue). - Uninitialised data:
struct smp2pcopies are fully written bysmem_peekbefore use;qrtr_controlis zeroed before the copy;mailbox_send_lockedzero-fills its 64 bytes;show_words'slineis zeroed;dsp_ask_to_stop()'stheirs.flagsprintout follows a successfuldsp_words. glink_pump()'s other cases: the name copy is bounded (min(param2, 32)) and zero-terminated; DATA copies at most the intent's free space (words[0] <= size - fillis checked before the copy andfill <= sizeis an invariant); the intent index is checked againstour_intent_count;their_intents[]insertion is bounded to 64;glink_offer()caps at 24 intents of at most 64 KB and rejects zero.glink_send()andglink_put()sizes: the message size is bounded against the queue (EMSGSIZE),glink_room()checks the head and tail from shared memory againstours_size(apart from W2), the head is reduced moduloours_size, and a head that is changed between the room check and the use is still reduced.qrtr_out_packet(): length 1..8192, allocationsizeof(header) + padded, andmemcpyoflength. Tables (qrtr_servers[64],qrtr_ports[16], lookups[4]) are bounded; a full table drops silently. A DSP cannot make us answer HELLO more than once.- Boot-arg parsing:
wifi_mac(lines 4008-4046): at most 32 characters, at most 2 digits per byte, at most 6 bytes, a multicast or all-zero address is refused,bytes[]is only copied when valid.wifi_stepis clamped toSTEPS; the other numeric args (wifi_wait,wifi_listen,wifi_watch,wifi_pause,wifi_new) only affect how long a step waits and use 64-bit arithmetic for the time (the one 32-bit product,seconds * 1000instep_qrtr, wraps only for a boot-arg above about 4 million seconds).hakios_wifi_option()returns 0 for anything unknown. hakios_wifi_network()parsing: bounds are byend, an empty line gives 0, CR is trimmed only when present, lengths over the destination size are not copied, a missing second line leaveslengths[1] = 0, which fails the 8..63 check. The result is rejected unless the name is 1..32 and the passphrase 8..63.- SMMU code:
radio_space_find()boundsused[256]and the context againstcontexts;radio_enter()indexes page tables by shifted bits, stops atRADIO_TABLES(32) and returns false;radio_map()bounds the address pool; everyputinto the SMMU is undone byradio_space_off()onceradio_space.onis true. - DMA coherence: the header page and the radio's page tables are cleaned with
dc civacand followed bydsb sy; the image is written through a write-combining map with adsb sybefore TrustZone starts the DSP.
Not checked
- Everything outside this file:
hakios.lx.c,hakios.wlfw.c,hakios.qmi.c(beyond the one bound read),hakios.sta.c,hakios.apple80211.c,hakios.adsp.c(which shares the mailbox lock and the SMEM allocation lock),hakios_image_file(),loader_log_putc()and the Wi-Fi driver underwifi/lx/. Whether any of them logs a key or the passphrase, takeshakios_wifi_sayin interrupt context (it takes a mutex), or callsqrtr_close()from a context that deadlocks withqrtr_calling, is open. - Whether
ml_io_map,ml_io_map_unmappableorml_io_map_wcombreturn 0 on failure or panic (W8); howPE_parse_boot_argntreats a non-numeric value for a 4-byte buffer (a value likewifi_file=offcould be read as non-zero); howthread_terminate(current_thread())behaves on a kernel thread that is finishing itself. - Hardware facts: the mapping attribute behind
VM_WIMG_WCOMB(so the alignment and ordering rules for W12 and unalignedldron item offsets), register layouts of IPCC, QMP, the PMIC arbiter, the RSC, the SMMU and TZ calling conventions, and whether a staleSTATUS_DONEin the arbiter's observer can answer a new read early (spmi_read, line 1104). Timings stated in comments were not verified. - Whether TrustZone expects
PAS_SHUTDOWNafter a failed start (W9) and whether the DSP's SMP2P "restart" handshake indsp_listen()matches Linux in every case. - Modem code (out of scope) and the audio loader beyond the shared-lock declarations.
- No build, run, or runtime test was done.
Fix status
All edits are in drivers/wifi/hakios.wifi.c; nothing was compiled or run. Line numbers are from the file after the edits.
- W1 FIXED:
glink_pump()(about 2505-2535) sums the message length in auint64_t(wide), so OPEN, INTENT and DATA lengths can no longer wrap to 0; the existing> theirs_size - 8check now rejects them (queue marked broken), andlengthis narrowed only after that check. This also bounds the INTENT loop. Not done: a per-call iteration cap in the pump (design change). - W2 FIXED:
smem_items()ends the walk for an entry withpadding_data > sizeor one that reaches pastfree_uncached(bottom), orpadding_data > size(top);glink_find()requires the descriptor and both queues to lie inside the 2 MB mapping;smem_poke()andsmem_word()ignore/return 0 for an offset outside the mapping or while it is not mapped. - W3 FIXED: the mailbox check is now
box > aoss.size || size > aoss.size - boxin bothstep_mailbox()andstep_mailbox_open_locked(). - W4 FIXED:
qrtr_input()checksheader.size > length || at > length - header.sizeinstead of the wrapping sum. - W5 SKIPPED: the fix is a new "dead" state that stops the driver and changes what
glink_send()andhakios_wifi_dsp_is_up()return; it is a design change, and what the radio does after err-fatal needs hardware. - W6 PARTLY FIXED:
link_thread_isis cleared when the link thread ends. SKIPPED: the sharedstatic found[256]insmem_item()(a mutex cannot be used, because the panic path calls it with no lock taken; a 3 KB stack array needs a decision on stack use), and thedsp_words()staticat(idempotent). - W7 FIXED: step 16 requires
size <= 4 * RADIO_WINDOW(2 MB) andaddress + size <= 4 GB, and returns false if the map result is 0. - W8 PARTLY FIXED:
step_image()unmaps the 25 MB window on the misaligned-segment return and returns false if the map is 0;glink_offer()frees a kept intent buffer of another size before replacing it;smem_peek()andstep_registers()check the map result. SKIPPED: the page ofstep_header()that is "never freed" on purpose (TrustZone may still use it; whether it can be freed needs hardware knowledge), andneed()(it returns void and its callers cannot handle a failure; whetherml_io_mapcan return 0 is not known). - W9 SKIPPED: needs knowledge of what TrustZone expects after a failed start, and a new "starting" state (design change).
- W10 SKIPPED: waiting without a bound or a "stop wanted" flag changes the shutdown flow (design change).
- W11 SKIPPED: capping the list and delaying DONE_REUSE changes the protocol flow (design change); a plain cap would drop packets.
- W12 SKIPPED: whether the WCOMB mapping reorders the loads is hardware behaviour, and a barrier there is a memory-ordering decision.
- W13 SKIPPED: reading the MAC from somewhere other than a boot-arg is a new feature, and the logged lengths of the name and passphrase are deliberate and documented in the comments (they help to diagnose a bad
fw/network); no secret reaches the log.