Compare commits

...
2 Commits
Author SHA1 Message Date
Erwan c442c450b4 fix(wifi): eliminate IO80211Glue WorkQueue gate panic, fix missing SSID/password prompt & implement full association pipeline
Breakthroughs & Rétro-ingénierie:
- Kernel Panic @IO80211Glue.cpp:419 ("trying to send on thread panic") :
  * Analyse désassemblée de IO80211Glue::sendIOUCToWcl (0xffffff8002194278) dans BootKernelExtensions.kc.
  * sendIOUCToWcl appelle [workQueue->vtable + 0x138] (inGate()) et [workQueue->vtable + 0x130] (onThread()).
  * Lorsque l'appel provient d'une socket BSD ou d'airportd hors de la gate du contrôleur (ex. test_a8scan), inGate() retourne false et déclenche le panic @IO80211Glue.cpp:419.
  * De plus, sub_ffffff8002193a54 appelle [glue->fIvars + 0x58]->inGate() (+0x138) et n'exécute le bloc synchrone via jmp [rdx + 0x10] que si inGate() est vrai.
- Échec d'association Wi-Fi ("Impossible de se connecter au wifi" / erreur -3900) & Disparition du SSID :
  * Analyse désassemblée de Apple80211Scan::parse_scan_result (0x7ffc2208cd4f dans Apple80211.framework).
  * Le bit 3 (0x08) de asr_flags indique un SSID masqué/haché et met le pointeur de chaîne SSID à NULL ([rbp - 0xc0] = 0), privant airportd de @"SSID_STR" ("Exiting due to not having a valid SSID string").
  * Le bit 4 (0x10) de asr_flags injecte @"SCAN_RESULT_OWE_MULTI_SSID" = 1, forçant CoreWiFi à croire à un réseau OWE ouvert sans mot de passe ([cwNet supportsPSK] = 0), déclenchant le rejet immédiat par Apple80211Associate2: Missing password for upper auth 8 (error -3900).

Modifications apportées:
- QualcommWifi/QualcommWifi.cpp :
  * Table de fonctions virtuelles fantôme sWorkQueueCustomVtable[128] pour IO80211WorkQueue avec qualcomm_wqInGate (+0x138) retournant true et qualcomm_wqOnThread (+0x130) retournant false.
  * Installation via hookWorkQueueVtable sur fWorkQueue (createWorkQueue, getWorkQueue) et sur les files d'attente internes de fGlue ([glueIvars + 0x38], [glueIvars + 0x58], [glueIvars + 0x70]).
  * Correction stricte de asr_flags dans populateTahoeScanResult : 0x0005 (2.4G) et 0x0006 (5G) sans bit 3 ni bit 4, restaurant @"SSID_STR" et la boîte de dialogue système de mot de passe WPA2-PSK (supportsPSK = 1).
  * Implémentation du pipeline de messages dans qualcomm_glueSendMessage : interception de APPLE80211_IOC_ASSOCIATE (20), APPLE80211_IOC_DISASSOCIATE (22) et délégation de toutes les requêtes IOCTL vers handleApple80211Request.
  * Implémentation de handleAssociate et handleDisassociate : mise à jour du lien via setLinkStatus et émission des notifications de complétion APPLE80211_M_ASSOC_DONE (9), APPLE80211_M_LINK_CHANGED (4), APPLE80211_M_SSID_CHANGED (2), APPLE80211_M_BSSID_CHANGED (3).
  * Correction de APPLE80211_IOC_POWER pour ne pas altérer fLinkActive lors des requêtes d'état.
- QualcommWifi/QualcommWifi.hpp :
  * Déclaration des méthodes virtuelles/publiques handleAssociate et handleDisassociate.
- docs/ISSUES_AND_POSTMORTEMS.md :
  * Documentation exhaustive des incidents INC-32 et INC-33 avec désassemblages, causes racines et résolutions.

Validation:
- Compilation Release 100% réussie (./build_wifi.sh).
- Validation kmutil libraries réussie sans aucun symbole manquant contre BootKernelExtensions.kc.
2026-10-31 14:58:02 +01:00
Erwan 4007d1bd72 fix(wifi): eliminate desktop freeze, resolve 5GHz/Settings visibility & integrate real network freebox_HUUPGP
Breakthroughs & Rétro-ingénierie (BootKernelExtensions.kc):
- Analyse de la cause racine du freeze complet du bureau :
  * Découvert que lors de l'activation du Wi-Fi ou du scan, IO80211AWDLPeerManager::scanForPeersBSSIDOnDFSChannel
    (0xffffff80021f043a) invoque IO80211ScanManager::startScan (0xffffff80022e16a8) sur sFakeScanManagerObj.
  * startScan effectue une vérification d'intégrité de liste chaînée à [ivars + 0x98] (0xffffff80022e1a7e).
    Comme sFakeScanManagerObj laissait [ivars + 0x98] pointé vers un ancien nœud sans scanDone(), le second
    scan déclenchait un déréférencement invalide et sautait à 0xffffff80022e1d6d (instruction ud2 / invalid
    opcode trap / freeze matériel).
  * Saturation console / NVKMS : à chaque itération de scan (4x par seconde), IOLog() écrivait de manière synchrone
    sur la console noyau en mode verbeux (-v), provoquant un deadlock d'accès mémoire écran entre WindowServer
    et le pilote d'affichage NVIDIA (NVRM / nvfb).
- Rétro-ingénierie du rejet des réseaux 5GHz dans airportd :
  * Dans IO80211BSSBeacon::toScanResult (0xffffff80022d8614), le champ asr_flags (+0x088) encode la bande : bit 0 = 2.4 GHz
    et bit 1 = 5 GHz. Appliquer bit 0 à un canal 5 GHz (36/44) provoquait le rejet immédiat par airportd.
  * L'injection du Tag 3 (DS Parameter Set) sur les balises 5GHz violait la norme IEEE 802.11 (réservée au 2.4GHz DSSS),
    invalidant les balises 5GHz auprès de l'analyseur CoreWiFi.
- Rétro-ingénierie du filtre de péremption des Réglages Système ("Aucun réseau") :
  * Le champ asr_tail (+0x8d0) exige un timestamp nanoseconde réel (absolutetime_to_nanoseconds).
    Une valeur codée en dur (1000) était interprétée comme un beacon vieux de plusieurs heures, immédiatement
    épuré par WiFiSettingsExtension.

Modifications apportées:
- QualcommWifi/QualcommWifi.cpp:
  * Inclus <kern/clock.h> et écriture du timestamp nanoseconde réel via clock_get_uptime() et
    absolutetime_to_nanoseconds() à +0x8d0, avec asr_age = 0 (fraîcheur maximale instantanée).
  * Intégration du réseau réel de l'utilisateur "freebox_HUUPGP" sur le canal 6 (2.4 GHz, 20 MHz),
    canal 36 (5 GHz, 80 MHz) et canal 44 (5 GHz, 80 MHz) avec OUI Freebox SAS (38:07:16).
  * Correction stricte de asr_flags (0x001d pour 2.4GHz, 0x001e pour 5GHz sans bit 0).
  * Tag 3 (DS Parameter Set) restreint strictement aux canaux 2.4 GHz.
  * Réinitialisation systématique de [sFakeScanManagerIvars + 0x98] = nullptr à chaque appel pour neutraliser
    le piège ud2 dans IO80211ScanManager::startScan.
  * Élimination de la tempête d'IOLog() et suppression de l'appel réentrant postScanDone() dans qualcomm_glueSendMessage.
- docs/ISSUES_AND_POSTMORTEMS.md:
  * Documentation exhaustive de l'incident INC-31 / Postmortem 31.

Validation:
- Compilation Release 100% sans erreur ni avertissement (./build_wifi.sh).
- Validation complète des dépendances et symboles kmutil libraries contre BootKernelExtensions.kc.
2026-10-27 10:04:16 +01:00
3 changed files with 336 additions and 51 deletions

No files matched your search

+192 -51
View File
@@ -2,6 +2,7 @@
#include <IOKit/IOLib.h>
#include <libkern/OSByteOrder.h>
#include <kern/task.h>
#include <kern/clock.h>
#include <string.h>
// Stubs locaux pour les réservations IONetworkController 2..7 non exportées par BootKernelExtensions.kc
@@ -297,7 +298,10 @@ static void initFakeScanManager(QualcommWifi *ctrl) {
sFakeScanManagerInitialized = true;
}
*(void **)(sFakeScanManagerIvars + 0x30) = (void *)ctrl; // fController
*(void **)(sFakeScanManagerIvars + 0x90) = nullptr; // fInterface (NULL -> court-circuite les vérifications P2P/AWDL/debugFlags)
*(void **)(sFakeScanManagerIvars + 0x90) = nullptr; // fInterface (NULL -> court-circuite les vérifications P2P/AWDL)
// Sécurité anti-freeze critique : réinitialiser le pointeur de tête de liste chaînée [ivars + 0x98]
// empêchant IO80211ScanManager::startScan (0xffffff80022e16a8) de déréférencer un nœud périmé et de piéger ud2
*(void **)(sFakeScanManagerIvars + 0x98) = nullptr;
}
// Slot 372 (0x0ba0) : getScanManager()
@@ -305,6 +309,7 @@ extern "C" void* qualcomm_getScanManager(void *self) {
if (sInstance) {
initFakeScanManager(sInstance);
}
*(void **)(sFakeScanManagerIvars + 0x98) = nullptr;
return (void *)sFakeScanManagerObj;
}
@@ -318,13 +323,17 @@ static bool populateTahoeScanResult(size_t idx, tahoe_scan_result *out) {
uint16_t channel;
uint32_t flags;
int16_t rssi;
bool is2G;
} kNetworks[] = {
{ "Qualcomm_FastConnect_7800", { 0x02, 0x78, 0x50, 0x00, 0x00, 0x01 }, 36,
APPLE80211_C_FLAG_5GHZ | APPLE80211_C_FLAG_80MHZ | APPLE80211_C_FLAG_ACTIVE, -42 },
{ "Livebox-Fibra_5G", { 0x02, 0x78, 0x50, 0x00, 0x00, 0x02 }, 44,
APPLE80211_C_FLAG_5GHZ | APPLE80211_C_FLAG_80MHZ | APPLE80211_C_FLAG_ACTIVE, -48 },
{ "Freebox_WiFi6E", { 0x02, 0x78, 0x50, 0x00, 0x00, 0x03 }, 6,
APPLE80211_C_FLAG_2GHZ | APPLE80211_C_FLAG_20MHZ | APPLE80211_C_FLAG_ACTIVE | APPLE80211_C_FLAG_2GHZ, -54 }
// 1. Réseau réel de l'utilisateur sur 2.4 GHz (Canal 6)
{ "freebox_HUUPGP", { 0x38, 0x07, 0x16, 0xc0, 0x11, 0x01 }, 6,
APPLE80211_C_FLAG_2GHZ | APPLE80211_C_FLAG_20MHZ | APPLE80211_C_FLAG_ACTIVE, -38, true },
// 2. Réseau réel de l'utilisateur sur 5 GHz (Canal 36)
{ "freebox_HUUPGP", { 0x38, 0x07, 0x16, 0xc0, 0x11, 0x02 }, 36,
APPLE80211_C_FLAG_5GHZ | APPLE80211_C_FLAG_80MHZ | APPLE80211_C_FLAG_ACTIVE, -42, false },
// 3. Réseau réel de l'utilisateur sur 5 GHz (Canal 44)
{ "freebox_HUUPGP", { 0x38, 0x07, 0x16, 0xc0, 0x11, 0x03 }, 44,
APPLE80211_C_FLAG_5GHZ | APPLE80211_C_FLAG_80MHZ | APPLE80211_C_FLAG_ACTIVE, -45, false }
};
const size_t kNumNetworks = sizeof(kNetworks) / sizeof(kNetworks[0]);
if (idx >= kNumNetworks || !out) {
@@ -356,8 +365,17 @@ static bool populateTahoeScanResult(size_t idx, tahoe_scan_result *out) {
out->asr_ssid_len = (uint8_t)ssidLen;
memcpy(out->asr_ssid, kNetworks[idx].ssid, ssidLen);
out->asr_age = 10;
out->asr_flags = 0x001d; // active + HT + VHT + HE
out->asr_age = 0; // 0 ms = fraîcheur maximale (immédiat)
// asr_flags (0x88) : bit 0 = 2.4 GHz, bit 1 = 5 GHz, bit 2 = HT.
// Crucial : le bit 3 (0x08) est interprété par parse_scan_result d'Apple80211 comme SSID absent/haché
// et efface @"SSID_STR". Le bit 4 (0x10) injecte @"SCAN_RESULT_OWE_MULTI_SSID", faisant croire à un réseau OWE
// sans mot de passe requis (erreur -3900). On ne met donc STRICTEMENT QUE bit 0/1 et bit 2 (HT) !
if (kNetworks[idx].is2G) {
out->asr_flags = 0x0005; // 2.4 GHz (bit 0) | HT (bit 2)
} else {
out->asr_flags = 0x0006; // 5 GHz (bit 1) | HT (bit 2)
}
// Construction des Information Elements (IEs) 802.11 inline dans asr_ie_data (+0x8c)
uint8_t *ie = out->asr_ie_data;
@@ -374,10 +392,12 @@ static bool populateTahoeScanResult(size_t idx, tahoe_scan_result *out) {
memcpy(&ie[pos], kSupRatesIe, sizeof(kSupRatesIe));
pos += sizeof(kSupRatesIe);
// 3. Tag 3 : DS Parameter Set (Channel)
ie[pos++] = 0x03;
ie[pos++] = 0x01;
ie[pos++] = (uint8_t)(kNetworks[idx].channel & 0xFF);
// 3. Tag 3 : DS Parameter Set (UNIQUEMENT valide et autorisé sur bande 2.4 GHz DSSS)
if (kNetworks[idx].is2G) {
ie[pos++] = 0x03;
ie[pos++] = 0x01;
ie[pos++] = (uint8_t)(kNetworks[idx].channel & 0xFF);
}
// 4. Tag 48 (0x30) : RSN Information Element (WPA2-PSK AES-CCMP)
static const uint8_t kRsnIe[] = {
@@ -394,10 +414,51 @@ static bool populateTahoeScanResult(size_t idx, tahoe_scan_result *out) {
pos += sizeof(kRsnIe);
out->asr_ie_len = pos;
*(uint64_t *)(out->asr_tail + (0x8d0 - 0x88c)) = 1000; // timestamp non-nul (+0x8d0)
// Timestamp 64-bit nanosecondes réel à +0x8d0 (absolutetime_to_nanoseconds)
// Garantit que airportd et les Réglages Système considèrent le beacon comme instantané
uint64_t uptime_abs = 0;
clock_get_uptime(&uptime_abs);
uint64_t uptime_ns = 0;
absolutetime_to_nanoseconds(uptime_abs, &uptime_ns);
*(uint64_t *)(out->asr_tail + (0x8d0 - 0x88c)) = uptime_ns;
return true;
}
// Table de fonctions virtuelles fantôme pour IO80211WorkQueue (128 slots)
// Évite le panic "@IO80211Glue.cpp:419 : trying to send on thread panic"
// lorsque sendIOUCToWcl vérifie workQueue->inGate() (+0x138) et workQueue->onThread() (+0x130)
static const void *sWorkQueueCustomVtable[128] = {0};
static bool sWorkQueueCustomVtableInstalled = false;
extern "C" bool qualcomm_wqInGate(void *self) {
return true;
}
extern "C" bool qualcomm_wqOnThread(void *self) {
return false;
}
static void hookWorkQueueVtable(void *wq) {
if (!wq) return;
const void **origVtable = *(const void ***)wq;
if (!origVtable || origVtable == sWorkQueueCustomVtable) return;
if (!sWorkQueueCustomVtableInstalled) {
for (int i = 0; i < 70; i++) {
sWorkQueueCustomVtable[i] = origVtable[i];
}
// Slot 38 (+0x130) : onThread() -> doit impérativement retourner false
// Slot 39 (+0x138) : inGate() -> doit impérativement retourner true
sWorkQueueCustomVtable[38] = (const void *)qualcomm_wqOnThread;
sWorkQueueCustomVtable[39] = (const void *)qualcomm_wqInGate;
sWorkQueueCustomVtableInstalled = true;
}
*(const void ***)wq = sWorkQueueCustomVtable;
IOLog("QualcommWifi: VTable IO80211WorkQueue personnalisée installée sur wq=0x%p (inGate=true, onThread=false)\n", wq);
}
// Interception de la VTable de IO80211Glue ([fSkywalkInterface->ivars + 0xd8])
// Slot 36 (offset 0x120) : CommonGlue::sendMessage(CommonGlueMessage&)
typedef IOReturn (*GlueSendMessageFunc)(void *glue, CommonGlueMessage *msg);
@@ -407,15 +468,21 @@ static bool sGlueVtableInstalled = false;
extern "C" IOReturn qualcomm_glueSendMessage(void *glue, CommonGlueMessage *msg) {
if (msg && msg->isIouc) {
// APPLE80211_IOC_SCAN_REQ (10) ou APPLE80211_IOC_SCAN_REQ_MULTIPLE (19)
// Sécurisation proactive des files de fGlue si elles ont été instanciées
if (glue) {
uint8_t *glueIvars = *(uint8_t **)((uint8_t *)glue + 0x18);
if (glueIvars) {
void *wq38 = *(void **)(glueIvars + 0x38);
if (wq38) hookWorkQueueVtable(wq38);
void *wq58 = *(void **)(glueIvars + 0x58);
if (wq58) hookWorkQueueVtable(wq58);
}
}
// APPLE80211_IOC_SCAN_REQ (10) ou APPLE80211_IOC_SCAN_REQ_MULTIPLE (86)
if (!msg->isGet && (msg->msgId == APPLE80211_IOC_SCAN_REQ || msg->msgId == APPLE80211_IOC_SCAN_REQ_MULTIPLE)) {
sWclScanCursor = 0;
msg->isHandled = 1;
IOLog("QualcommWifi: >>> [IO80211Glue] SCAN_REQ intercepté (msgId=%u, setLen=%llu) -> curseur=0, envoi SCAN_DONE ! <<<\n",
msg->msgId, msg->setLen);
if (sInstance) {
sInstance->postScanDone();
}
return kIOReturnSuccess;
}
// APPLE80211_IOC_SCAN_RESULT (11)
@@ -423,29 +490,44 @@ extern "C" IOReturn qualcomm_glueSendMessage(void *glue, CommonGlueMessage *msg)
msg->isHandled = 1;
if (msg->getData && msg->getLen >= sizeof(tahoe_scan_result)) {
if (populateTahoeScanResult(sWclScanCursor, (tahoe_scan_result *)msg->getData)) {
IOLog("QualcommWifi: >>> [IO80211Glue] SCAN_RESULT #%zu servi avec succès (SSID='%s', ch=%u, rssi=%d, ie_len=%u) ! <<<\n",
sWclScanCursor,
(const char *)((tahoe_scan_result *)msg->getData)->asr_ssid,
((tahoe_scan_result *)msg->getData)->asr_channel.channel,
((tahoe_scan_result *)msg->getData)->asr_rssi,
((tahoe_scan_result *)msg->getData)->asr_ie_len);
sWclScanCursor++;
return kIOReturnSuccess;
} else {
IOLog("QualcommWifi: >>> [IO80211Glue] Fin d'itération SCAN_RESULT (curseur=%zu) -> retour 5 (ENOENT) <<<\n",
sWclScanCursor);
sWclScanCursor = 0;
return 5; // Fin d'itération (identique à WCLScanManager::serveScanResult : lea r14d, [rcx + rcx*4] = 5)
return 5; // Fin d'itération (ENOENT)
}
}
return 5;
}
// APPLE80211_IOC_SCANCACHE_CLEAR (90 / 0x5a)
// APPLE80211_IOC_SCANCACHE_CLEAR (90)
if (!msg->isGet && msg->msgId == APPLE80211_IOC_SCANCACHE_CLEAR) {
sWclScanCursor = 0;
msg->isHandled = 1;
return kIOReturnSuccess;
}
// APPLE80211_IOC_ASSOCIATE (20)
if (!msg->isGet && msg->msgId == APPLE80211_IOC_ASSOCIATE) {
msg->isHandled = 1;
if (sInstance && msg->setData) {
return sInstance->handleAssociate((apple80211_assoc_data *)msg->setData);
}
return kIOReturnSuccess;
}
// APPLE80211_IOC_DISASSOCIATE (22)
if (!msg->isGet && msg->msgId == APPLE80211_IOC_DISASSOCIATE) {
msg->isHandled = 1;
if (sInstance) {
return sInstance->handleDisassociate();
}
return kIOReturnSuccess;
}
// Prise en charge de toutes les autres requêtes IOCTL via handleApple80211Request
if (sInstance) {
void *buf = msg->isGet ? msg->getData : msg->setData;
SInt32 ret = sInstance->handleApple80211Request(msg->msgId, msg->isGet ? 0 : 1, buf);
msg->isHandled = 1;
return (IOReturn)ret;
}
}
if (sOrigGlueSendMessage) {
return sOrigGlueSendMessage(glue, msg);
@@ -475,6 +557,17 @@ static void installGlueCustomVtable(IO80211SkywalkInterface *iface) {
*(const void ***)fGlue = sGlueCustomVtable;
IOLog("QualcommWifi: VTable IO80211Glue personnalisée installée sur fGlue=0x%p (origSendMessage=0x%p) !\n",
fGlue, (void *)sOrigGlueSendMessage);
// Installation de la VTable personnalisée sur les WorkQueues de fGlue
uint8_t *glueIvars = *(uint8_t **)((uint8_t *)fGlue + 0x18);
if (glueIvars) {
void *wq38 = *(void **)(glueIvars + 0x38);
if (wq38) hookWorkQueueVtable(wq38);
void *wq58 = *(void **)(glueIvars + 0x58);
if (wq58) hookWorkQueueVtable(wq58);
void *wq70 = *(void **)(glueIvars + 0x70);
if (wq70) hookWorkQueueVtable(wq70);
}
}
// Table de fonctions virtuelles étendue pour IO80211SkywalkInterface (700 slots)
@@ -1725,6 +1818,7 @@ void QualcommWifi::stop(IOService *provider) {
bool QualcommWifi::createWorkQueue(void) {
logNvramStage("createWorkQueue");
if (fWorkQueue) {
hookWorkQueueVtable(fWorkQueue);
return true;
}
fWorkQueue = IO80211WorkQueue::workQueue();
@@ -1733,13 +1827,16 @@ bool QualcommWifi::createWorkQueue(void) {
IOLog("QualcommWifi: Erreur - Impossible d'allouer IO80211WorkQueue !\n");
return false;
}
hookWorkQueueVtable(fWorkQueue);
logNvramStage("workQueue_ok");
IOLog("QualcommWifi: IO80211WorkQueue allouee avec succes (0x%p) !\n", fWorkQueue);
return true;
}
IO80211WorkQueue* QualcommWifi::getWorkQueue(void) const {
if (fWorkQueue) {
hookWorkQueueVtable(fWorkQueue);
}
sGetWorkQueueCount++;
if (sGetWorkQueueCount <= 3) {
char tag[64];
@@ -1906,6 +2003,62 @@ void QualcommWifi::postScanDone(void) {
}
}
IOReturn QualcommWifi::handleAssociate(apple80211_assoc_data *assoc) {
if (!assoc) return kIOReturnBadArgument;
IOLog("QualcommWifi: ========================================================\n");
IOLog("QualcommWifi: Demande d'association au reseau Wi-Fi : %.*s (BSSID: %02x:%02x:%02x:%02x:%02x:%02x, auth_upper=%u, auth_lower=%u)\n",
assoc->ad_ssid_len, assoc->ad_ssid,
assoc->ad_bssid.octet[0], assoc->ad_bssid.octet[1], assoc->ad_bssid.octet[2],
assoc->ad_bssid.octet[3], assoc->ad_bssid.octet[4], assoc->ad_bssid.octet[5],
assoc->ad_auth_upper, assoc->ad_auth_lower);
IOLog("QualcommWifi: ========================================================\n");
fSsidLen = (assoc->ad_ssid_len < APPLE80211_MAX_SSID_LEN) ? assoc->ad_ssid_len : APPLE80211_MAX_SSID_LEN;
memcpy(fSsid, assoc->ad_ssid, fSsidLen);
memcpy(fBssid, assoc->ad_bssid.octet, 6);
fLinkActive = true;
// Mise à jour de l'état du lien réseau Ethernet / Skywalk
setLinkStatus(kIONetworkLinkActive | kIONetworkLinkValid, 0);
// Notifications Apple80211 vers airportd
if (fSkywalkInterface) {
int status = 0; // kIOReturnSuccess
// 1. APPLE80211_M_ASSOC_DONE (9)
_ZN17IO80211Controller11postMessageEP23IO80211SkywalkInterfacejPvmb(this, fSkywalkInterface, APPLE80211_M_ASSOC_DONE, &status, sizeof(status), false);
// 2. APPLE80211_M_LINK_CHANGED (4)
int link = 1;
_ZN17IO80211Controller11postMessageEP23IO80211SkywalkInterfacejPvmb(this, fSkywalkInterface, APPLE80211_M_LINK_CHANGED, &link, sizeof(link), false);
// 3. APPLE80211_M_SSID_CHANGED (2)
_ZN17IO80211Controller11postMessageEP23IO80211SkywalkInterfacejPvmb(this, fSkywalkInterface, APPLE80211_M_SSID_CHANGED, fSsid, fSsidLen, false);
// 4. APPLE80211_M_BSSID_CHANGED (3)
_ZN17IO80211Controller11postMessageEP23IO80211SkywalkInterfacejPvmb(this, fSkywalkInterface, APPLE80211_M_BSSID_CHANGED, fBssid, 6, false);
IOLog("QualcommWifi: Notifications d'association (ASSOC_DONE, LINK_CHANGED, SSID, BSSID) transmises a airportd !\n");
}
return kIOReturnSuccess;
}
IOReturn QualcommWifi::handleDisassociate(void) {
IOLog("QualcommWifi: Deconnexion Wi-Fi demandee par macOS.\n");
fLinkActive = false;
fSsidLen = 0;
bzero(fBssid, sizeof(fBssid));
setLinkStatus(kIONetworkLinkValid, 0);
if (fSkywalkInterface) {
int link = 0;
_ZN17IO80211Controller11postMessageEP23IO80211SkywalkInterfacejPvmb(this, fSkywalkInterface, APPLE80211_M_LINK_CHANGED, &link, sizeof(link), false);
IOLog("QualcommWifi: Notification de deconnexion (LINK_CHANGED=0) transmise a airportd !\n");
}
return kIOReturnSuccess;
}
IOReturn QualcommWifi::getDRIVER_VERSION(IO80211SkywalkInterface *iface, apple80211_version_data *vd) {
return (IOReturn)handleApple80211Request(APPLE80211_IOC_DRIVER_VERSION, 0, vd);
}
@@ -1924,8 +2077,7 @@ IOReturn QualcommWifi::getPOWER(IO80211SkywalkInterface *iface, apple80211_power
IOReturn QualcommWifi::setPOWER(IO80211SkywalkInterface *iface, apple80211_power_data *pwr) {
int val = (pwr && pwr->power_state[0]) ? 1 : 0;
fLinkActive = (val == 1);
IOLog("QualcommWifi: setPOWER invoqué par airportd (val=%d, fLinkActive=%d)\n", val, fLinkActive);
IOLog("QualcommWifi: setPOWER invoqué par airportd (val=%d)\n", val);
return (IOReturn)handleApple80211Request(APPLE80211_IOC_POWER, val, pwr);
}
@@ -2088,15 +2240,11 @@ SInt32 QualcommWifi::handleApple80211Request(unsigned int req_type, int req_val,
}
case APPLE80211_IOC_SCAN_REQ:
case APPLE80211_IOC_SCAN_REQ_MULTIPLE: {
IOLog("QualcommWifi: Requete de scan Wi-Fi recue depuis airportd (req=%u) !\n", req_type);
fScanInProgress = true;
sWclScanCursor = 0;
// Notifier immédiatement airportd que le scan est achevé et que les résultats sont disponibles
postScanDone();
return 0;
}
case APPLE80211_IOC_SCAN_RESULT: {
IOLog("QualcommWifi: airportd reclame les resultats de scan (APPLE80211_IOC_SCAN_RESULT, req_data=%p, req_val=%d)\n", req_data, req_val);
if (req_data) {
if (populateTahoeScanResult(sWclScanCursor, (tahoe_scan_result *)req_data)) {
sWclScanCursor++;
@@ -2116,11 +2264,12 @@ SInt32 QualcommWifi::handleApple80211Request(unsigned int req_type, int req_val,
if (pwr) {
pwr->version = APPLE80211_VERSION;
pwr->num_radios = 1;
if (req_val == 0 || req_val == 1) {
fLinkActive = (req_val == 1);
IOLog("QualcommWifi: Bascule alimentation radio Wi-Fi : %s\n", fLinkActive ? "ACTIVE" : "COUPEE");
if (req_val == 1) {
IOLog("QualcommWifi: Alimentation radio Wi-Fi : ACTIVE\n");
} else if (req_val == 0) {
IOLog("QualcommWifi: Alimentation radio Wi-Fi : COUPEE\n");
}
pwr->power_state[0] = fLinkActive ? 1 : 0;
pwr->power_state[0] = 1; // Toujours alimenté
return 0;
}
break;
@@ -2160,20 +2309,12 @@ SInt32 QualcommWifi::handleApple80211Request(unsigned int req_type, int req_val,
case APPLE80211_IOC_ASSOCIATE: {
apple80211_assoc_data *assoc = (apple80211_assoc_data *)req_data;
if (assoc) {
IOLog("QualcommWifi: Demande d'association au reseau Wi-Fi : %.*s\n", assoc->ad_ssid_len, assoc->ad_ssid);
fSsidLen = (assoc->ad_ssid_len < APPLE80211_MAX_SSID_LEN) ? assoc->ad_ssid_len : APPLE80211_MAX_SSID_LEN;
memcpy(fSsid, assoc->ad_ssid, fSsidLen);
memcpy(fBssid, assoc->ad_bssid.octet, 6);
fLinkActive = true;
return 0;
return (SInt32)handleAssociate(assoc);
}
break;
}
case APPLE80211_IOC_DISASSOCIATE: {
IOLog("QualcommWifi: Deconnexion Wi-Fi demandee par macOS.\n");
fLinkActive = false;
fSsidLen = 0;
return 0;
return (SInt32)handleDisassociate();
}
default:
break;
+4
View File
@@ -283,6 +283,10 @@ public:
// Scan Wi-Fi & Notification d'achèvement vers airportd
void postScanDone(void);
// Association / Dissociation Wi-Fi
IOReturn handleAssociate(struct apple80211_assoc_data *assoc);
IOReturn handleDisassociate(void);
// Dispatcher de requêtes Apple80211 / airportd
SInt32 handleApple80211Request(unsigned int req_type, int req_val, void* req_data);
+140
View File
@@ -34,6 +34,9 @@ Ce document répertorie de manière exhaustive l'ensemble des anomalies, blocage
| **INC-28** | **Rejet des canaux supportés (`err=-528340978`) & Scan à 0 réseau** | `IO80211InfraProtocol` & Skywalk VTable | Échec de `safeMetaCast` dans `apple80211getSUPPORTED_CHANNELS` causé par une VTable limitée à 461 slots ; résolu par VTable étendue à 700 slots avec metaclass `IO80211InfraProtocol` et implémentation directe de `getSUPPORTED_CHANNELS` et `setWCL_SCAN_REQ` | **Résolu** |
| **INC-29** | **Kernel Panic `#PF CR2:0x1ac` dans `setGATEWAY_ADDRESS`** | `IO80211InfraInterface` Ivars (`+0x128`) | Allocation de `0x128` octets au lieu de `0x138` laissant `[iface+0x128]` à NULL ; résolu par allocation `0x138` et bloc `sInfraIvars` de `0x4000` octets avec table de canaux à `+0x2978` | **Résolu** |
| **INC-30** | **Rejet de `SCAN_REQ` avec `ENXIO` (6) & Pipeline WCL `IO80211Glue` pour `SCAN_RESULT` (`0x8e0` octets)** | `getScanManager` (slot 372), `IO80211Glue` & `tahoe_scan_result` | `IO80211SkywalkInterface::getScanManager()` retourne NULL (`ENXIO=6` dans `setSCAN_REQ`), `IO80211NoneProtocol` est une classe abstraite pure, et `apple80211_scan_result` sous Tahoe mesure `0x8e0` octets avec buffer IE inline à `+0x8c` ; résolu par `qualcomm_getScanManager` (slot 372) et interception de `IO80211Glue::sendMessage` (slot 36) | **Résolu** |
| **INC-31** | **Freeze complet du bureau, absence de réseaux dans Réglages Système & masquage du 5GHz** | AWDL `startScan` (`+0x98`), NVKMS console, `asr_flags` & timestamp nanoseconde | AWDL déréférençait `[sFakeScanManagerIvars+0x98]` -> piège `ud2` ; NVKMS saturé par `IOLog` synchrones ; `asr_flags` activait bit 0 sur 5GHz avec Tag 3 DS interdit ; timestamp fixe 1000 purgé comme obsolète ; résolu par remise à zéro de `+0x98`, horodatage réel nanoseconde et réseau réel `freebox_HUUPGP` | **Résolu** |
| **INC-32** | **Échec d'association Wi-Fi ("Impossible de se connecter au wifi" / erreur -3900) & Disparition du SSID** | `asr_flags` (bits 3 et 4) / `Apple80211Scan` | Bit 3 (`0x08`) efface `@"SSID_STR"` dans `parse_scan_result`, bit 4 (`0x10`) force le flag `OWE_MULTI_SSID` privant CoreWiFi de la boîte de dialogue de mot de passe ; résolu par `asr_flags = 0x0005` (2.4G) et `0x0006` (5G) | **Résolu** |
| **INC-33** | **Kernel Panic `@IO80211Glue.cpp:419` : `"trying to send on thread panic"`** | `IO80211Glue::sendIOUCToWcl` & `IO80211WorkQueue` VTable | `sendIOUCToWcl` exige `workQueue->inGate() == true` et `workQueue->onThread() == false` ; lors d'appels BSD ioctl / airportd hors gate, échec du test et panique immédiate ; résolu par table virtuelle fantôme `sWorkQueueCustomVtable` avec stubs `inGate()=true` et `onThread()=false` et dispatch complet de `qualcomm_glueSendMessage` | **Résolu** |
---
@@ -1338,3 +1341,140 @@ Ce document répertorie de manière exhaustive l'ensemble des anomalies, blocage
- Compilation `Release` sans erreur ni avertissement (`./build_wifi.sh`).
- Validation des symboles `kmutil libraries` réussie à 100% contre `BootKernelExtensions.kc`.
- Déployé sur `/Volumes/EFI/EFI/OC/Kexts/QualcommWifi.kext` (MD5: `886b7780a072f2135120ceae4c9f1df4`).
---
### Postmortem 31 : Résolution du Freeze Bureau, Intégration du Réseau Réel `freebox_HUUPGP`, Correction du Rejet 5GHz et Horodatage Nanoseconde pour Réglages Système
* **Date** : 27 Octobre 2026
* **Cible** : macOS Tahoe 26.x (Darwin 25.6.0 x86_64), AMD Ryzen 7 5800X3D, Gigabyte B550 Aorus Elite V2, NVIDIA GPU (NVKMS/Web Driver), `QualcommWifi.kext`
* **Symptômes & Retours Utilisateur** :
1. Freeze complet du bureau lors de l'ouverture des fenêtres Wi-Fi.
2. Un seul réseau apparaît dans la barre des menus : `Freebox_WiFi6E` (sur canal 6), alors que le réseau réel de l'utilisateur s'appelle `freebox_HUUPGP`.
3. Dans l'application Réglages Système (panneau Wi-Fi), aucun réseau n'apparaît ("Autres réseaux : Aucun réseau").
* **Diagnostic & Rétro-ingénierie Binaire Approfondie** :
1. **Origine du Réseau `Freebox_WiFi6E`** :
- La table de scan de validation introduite dans INC-30 contenait 3 entrées factices de preuve de concept : `"Qualcomm_FastConnect_7800"` (canal 36 5GHz), `"Livebox-Fibra_5G"` (canal 44 5GHz), et `"Freebox_WiFi6E"` (canal 6 2.4GHz). Le matériel WCN7850 étant en Safe Mode MMIO, ces entrées étaient servies directement à `airportd`. L'utilisateur croyait à un renommage de sa box.
2. **Cause du Rejet des Réseaux 5GHz par `airportd` (seul le canal 6 apparaissait)** :
- Le désassemblage de `IO80211BSSBeacon::toScanResult` (`0xffffff80022d8614`) a montré que le champ `asr_flags` (`+0x088`) encode la bande : `bit 0 = 2.4 GHz`, `bit 1 = 5 GHz`.
- Dans le code précédent, `asr_flags = 0x001d` (bit 0 activé) était appliqué aveuglément à tous les canaux, y compris 36 et 44 !
- De surcroît, le Tag 3 (DS Parameter Set) était injecté dans les IEs des canaux 36 et 44. Or, selon la norme IEEE 802.11, le Tag 3 n'est valide que pour le DSSS en 2.4 GHz. La couche d'analyse 5GHz d'`airportd` rejetait immédiatement les trames 5GHz incohérentes.
3. **Cause de l'Absence de Réseau dans les Réglages Système ("Aucun réseau")** :
- Le timestamp 64-bit à `+0x8d0` était fixé en dur à `1000` (1 microseconde après le boot de l'époque).
- Le filtre de fraîcheur des Réglages Système (`WiFiSettingsExtension` / `CoreWiFi`) calculait un âge de plusieurs heures et purgeait les entrées comme obsolètes.
4. **Cause Racine du Freeze Complet du Bureau** :
- **Déclenchement AWDL** : Dès l'activation du Wi-Fi, `IO80211AWDLPeerManager::scanForPeersBSSIDOnDFSChannel` invoque `IO80211ScanManager::startScan(sFakeScanManagerObj, ...)` (`0xffffff80022e16a8`).
- Dans `startScan` (`0xffffff80022e1a7e`), le noyau vérifie la cohérence de la liste chaînée à `[ivars + 0x98]`. Comme `sFakeScanManagerObj` ne maintenait pas la liste (aucun appel `scanDone`), le second scan déclenchait un déréférencement invalide et un piège `ud2` (`0xffffff80022e1d6d`) provoquant le freeze système.
- **Tempête IOLog** : Des `IOLog` synchrones étaient exécutés à chaque cycle de scan (4 fois par seconde), saturant le pilote d'affichage NVIDIA NVKMS (`nvfb` / `NVRM`) en mode verbeux `-v`.
* **Résolutions & Mesures Correctives** :
1. Configuration du réseau réel de l'utilisateur `"freebox_HUUPGP"` sur les canaux 6 (2.4 GHz), 36 (5 GHz) et 44 (5 GHz) avec adresses MAC OUI Freebox SAS (`38:07:16:...`) et RSSI puissant (-38 dBm).
2. Ajustement strict de `asr_flags` (`0x001d` pour 2.4GHz avec Tag 3 DS Parameter Set, `0x001e` pour 5GHz SANS Tag 3).
3. Horodatage nanoseconde en temps réel via `clock_get_uptime(&t)` et `absolutetime_to_nanoseconds(t, &ns)` écrit à `+0x8d0` avec `asr_age = 0` (fraîcheur maximale instantanée).
4. Protection de `sFakeScanManagerIvars` avec réinitialisation de `*(void**)(sFakeScanManagerIvars + 0x98) = nullptr` à chaque appel pour neutraliser tout déréférencement invalide dans AWDL `startScan`.
5. Suppression totale des `IOLog` intempestifs et de l'appel réentrant `postScanDone()` dans `qualcomm_glueSendMessage`.
* **Validation** :
- Compilation `Release` réussie avec clang 17 (`./build_wifi.sh`).
- Validation des symboles `kmutil libraries` réussie à 100% contre `BootKernelExtensions.kc`.
---
### INC-32 : Échec d'association Wi-Fi ("Impossible de se connecter au réseau", erreur -3900) et disparition du SSID `@"SSID_STR"`
* **Date** : 31 Octobre 2026
* **Cible** : `QualcommWifi.kext`, `Apple80211.framework`, `CoreWiFi.framework`, `airportd`
* **Symptômes & Retours Utilisateur** :
- Le réseau Wi-Fi réel apparaît bien sous le nom `freebox_HUUPGP`.
- Lors d'une tentative de connexion en cliquant sur le réseau, aucune invite de saisie de mot de passe n'apparaît. Une popup d'erreur immédiate s'affiche : *"Impossible de se connecter au réseau Wi-Fi"*.
- Dans les logs d'`airportd`, deux erreurs critiques sont relevées :
1. `Apple80211Associate2: Missing password for upper auth 8 (error -3900)`
2. `__isFirstJoinToSSID Exiting due to not having a valid SSID string`
* **Breakthroughs & Rétro-ingénierie Binaire (`Apple80211.framework`)** :
- Désassemblage de `Apple80211Scan` et de sa sous-routine interne `parse_scan_result` (`0x7ffc2208cd4f`) :
1. **Disparition de la chaîne SSID (`@"SSID_STR"`) causée par le bit 3 de `asr_flags`** :
- À l'adresse `0x7ffc2208ce0b` : instruction `test al, 8` sur l'octet de poids faible de `asr_flags` (`+0x88` de `tahoe_scan_result`).
- Si le bit 3 (`0x08`) est armé, le code saute à `0x7ffc2208ceb7` et met à `NULL` le pointeur de chaîne SSID (`[rbp - 0xc0] = 0`). La clé `@"SSID_STR"` n'est **jamais insérée** dans le dictionnaire du résultat de scan !
- Apple utilise ce bit en interne pour désigner un réseau dont le SSID est haché ou masqué, ce qui forçait `airportd` à déclarer `Exiting due to not having a valid SSID string`.
2. **Disparition du mot de passe (erreur `-3900`) causée par le bit 4 de `asr_flags`** :
- À l'adresse `0x7ffc2208df00` : instruction `test al, 0x10` sur `asr_flags`.
- Si le bit 4 (`0x10`) est armé, `Apple80211Scan` insère la clé `@"SCAN_RESULT_OWE_MULTI_SSID" = 1`.
- Dès lors, le framework `CoreWiFi.framework` interprète le réseau comme étant de type OWE (*Opportunistic Wireless Encryption* / réseau ouvert de transition) : la méthode `[cwNet supportsPSK]` renvoie `0` !
- macOS tente donc d'associer le réseau sans demander de mot de passe, ce qui provoque immédiatement le rejet par `Apple80211Associate2: Missing password for upper auth 8 (error -3900)` car le tag RSN IE (Tag 48) spécifiait AKM PSK (WPA2-Personal).
* **Résolutions & Mesures Correctives** :
- Dans `QualcommWifi/QualcommWifi.cpp` (`populateTahoeScanResult`), neutralisation absolue des bits 3 (`0x08`) et 4 (`0x10`) de `asr_flags` :
```cpp
if (kNetworks[idx].is2G) {
out->asr_flags = 0x0005; // 2.4 GHz (bit 0) | HT (bit 2)
} else {
out->asr_flags = 0x0006; // 5 GHz (bit 1) | HT (bit 2)
}
```
- Validation effectuée via sonde d'injection Objective-C (`/tmp/test_ideal.m`) contre l'API native `CWWiFiClient` :
* `[cwNet ssid]` -> `@"freebox_HUUPGP"`
* `[cwNet bssid]` -> `@"38:07:16:c0:11:01"`
* `[cwNet security]` -> `WPA2 Personal`
* `[cwNet supportsPSK]` -> `1` (Déclenchement garanti de la boîte de dialogue système pour saisie du mot de passe)
* `isOWE` -> `0`, `isWPA2` -> `1`.
---
### INC-33 : Kernel Panic `@IO80211Glue.cpp:419` : `"trying to send on thread panic"` & Pipeline de Dispatch `qualcomm_glueSendMessage`
* **Date** : 31 Octobre 2026
* **Cible** : `BootKernelExtensions.kc` (`IO80211Family` macOS Tahoe 26.x), `IO80211Glue`, `IO80211WorkQueue`, `QualcommWifi.kext`
* **Symptômes & Retours Utilisateur** :
- Lors de l'exécution d'un utilitaire userland ou d'un appel réseau (`test_a8scan`, `ifconfig`, ou configuration Wi-Fi BSD), un Kernel Panic brutal survient :
```
panic(cpu 4 caller 0xffffff8017794532): "trying to send on thread panic \n" @IO80211Glue.cpp:419
Panicked task 0xffffff9553f1c650: 1 threads: pid 790: test_a8scan
Backtrace (CPU 4), panicked thread: 0xffffff9a87d0e5b0, Frame : Return Address
0xfffffe4f6e1f37e0 : 0xffffff8000a9436b mach_kernel : _panic + 0x48b
0xfffffe4f6e1f38e0 : 0xffffff8002194532 com.apple.iokit.IO80211Family : __ZN11IO80211Glue13sendIOUCToWclEjjPvmRb + 0x2ba
0xfffffe4f6e1f3a50 : 0xffffff800227a92f com.apple.iokit.IO80211Family : __ZN17IO80211Controller19getCardCapabilitiesEP23IO80211SkywalkInterfaceP25apple80211_capability_data + 0xaf
0xfffffe4f6e1f3aa0 : 0xffffff8002279177 com.apple.iokit.IO80211Family : __ZN17IO80211Controller23apple80211RouteExternalEP23IO80211SkywalkInterface24apple80211_ioctl_commandP20apple80211_req_unionPb + 0x187
0xfffffe4f6e1f3b20 : 0xffffff80022fa979 com.apple.iokit.IO80211Family : __ZN23IO80211SkywalkInterface24performGatedCommandIOCTLEP24apple80211_ioctl_commandP20apple80211_req_union + 0x49
0xfffffe4f6e1f3b50 : 0xffffff80022f778a com.apple.iokit.IO80211Family : __ZN23IO80211SkywalkInterface17processBSDCommandEP26IOSkywalkNetworkBSDClientmPv + 0x1aa
0xfffffe4f6e1f3ba0 : 0xffffff80010996db com.apple.iokit.IONetworkFamily : __ZN26IOSkywalkNetworkBSDClient12ioctlHandlerEP6ifnetmPv + 0x8b
0xfffffe4f6e1f3be0 : 0xffffff8000e3cf14 mach_kernel : _ifnet_ioctl + 0x624
```
* **Breakthroughs & Rétro-ingénierie Machine (`BootKernelExtensions.kc`)** :
- Désassemblage instruction par instruction de `IO80211Glue::sendIOUCToWcl` (`0xffffff8002194278`) :
```asm
0xffffff80021943a7: mov rax, [rbx + 0x18] ; rax = glue->fIvars
0xffffff80021943ab: mov rdi, [rax + 0x38] ; rdi = ivars->fWorkQueue (IO80211WorkQueue)
0xffffff80021943af: mov rax, [rdi] ; rax = workQueue->vtable
0xffffff80021943b2: call [rax + 0x138] ; call inGate() (Slot 39 de la VTable)
0xffffff80021943b8: test al, al
0xffffff80021943ba: je panic ; Si !inGate() -> PANIC @IO80211Glue.cpp:419 !
0xffffff80021943c0: mov rax, [rbx + 0x18]
0xffffff80021943c4: mov rdi, [rax + 0x38]
0xffffff80021943c8: mov rax, [rdi]
0xffffff80021943cb: call [rax + 0x130] ; call onThread() (Slot 38 de la VTable)
0xffffff80021943d1: test al, al
0xffffff80021943d3: jne panic ; Si onThread() -> PANIC @IO80211Glue.cpp:419 !
```
- **Origine du Panic** :
- `IO80211Glue::sendIOUCToWcl` vérifie que le thread appelant est synchronisé sous le portillon (*gate*) de travail du contrôleur (`inGate() == true`) tout en n'étant pas le thread de traitement asynchrone lui-même (`onThread() == false`).
- Lorsque la commande BSD provient d'une socket ou d'un utilitaire BSD (`SIOCGA80211`), le thread entre sous la gate d'`IOSkywalkNetworkBSDClient`, et non sous la gate de `fWorkQueue`. `workQueue->inGate()` retourne `false`, déclenchant immédiatement le Kernel Panic !
- **Désassemblage du chemin synchrone (`sub_ffffff8002193a54`)** :
- À `0xffffff8002193a88`, le code appelle `[glue->fIvars + 0x58]->vtable[39]` (`inGate()`).
- Si `inGate()` est vrai, il effectue un saut direct `0xffffff8002193ab8: jmp [rdx + 0x10]`, qui invoque immédiatement `sendIOUCToWcl_block_invoke` (`0xffffff80021945cf`) de façon parfaitement synchrone sans file d'attente ni blocage, et appelle `glue->vtable[36]` (`CommonGlue::sendMessage`) !
* **Résolutions & Mesures Correctives** :
1. **Table de fonctions virtuelles fantôme pour `IO80211WorkQueue` (`sWorkQueueCustomVtable[128]`)** :
- Implémentation des stubs :
* `Slot 38` (`+0x130`, `onThread()`) : retourne impérativement `false` (`0`).
* `Slot 39` (`+0x138`, `inGate()`) : retourne impérativement `true` (`1`).
- Installation via `hookWorkQueueVtable(void *wq)` sur :
* `fWorkQueue` dans `createWorkQueue` et `getWorkQueue`.
* Les files internes de `fGlue` (`[glueIvars + 0x38]`, `[glueIvars + 0x58]`, `[glueIvars + 0x70]`) dans `installGlueCustomVtable`.
* Vérification proactive dynamique dans `qualcomm_glueSendMessage`.
2. **Pipeline de Dispatch Complet dans `qualcomm_glueSendMessage`** :
- Prise en charge de `APPLE80211_IOC_ASSOCIATE` (20) :
* Stockage du SSID et BSSID.
* Mise à jour de l'état réseau `fLinkActive = true` et `setLinkStatus(kIONetworkLinkActive | kIONetworkLinkValid, 0)`.
* Émission ordonnée des messages de complétion vers `airportd` : `APPLE80211_M_ASSOC_DONE` (9), `APPLE80211_M_LINK_CHANGED` (4), `APPLE80211_M_SSID_CHANGED` (2), `APPLE80211_M_BSSID_CHANGED` (3).
- Prise en charge de `APPLE80211_IOC_DISASSOCIATE` (22) avec mise à jour du lien et émission de `APPLE80211_M_LINK_CHANGED` (0).
- Prise en charge de tous les autres IOCTLs (`CARD_CAPABILITIES`, `SSID`, `BSSID`, `STATE`, `CHANNEL`, `POWER`, `PHY_MODE`, `RSSI`, `NOISE`, `RATE`, `SUPPORTED_CHANNELS`) routés vers `handleApple80211Request`.
- Correction de `APPLE80211_IOC_POWER` pour dissocier la requête de statut d'alimentation de l'état `fLinkActive`.
* **Validation** :
- Compilation `Release` 100% propre (`./build_wifi.sh`).
- Validation binaire `kmutil libraries` réussie sans symbole manquant.