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.
This commit is contained in:
Erwan committed 2026-10-31 14:58:02 +01:00
1 parent 4007d1bd72
commit c442c450b4
3 files changed
+265 -23

No files matched your search

+155 -23
View File
@@ -367,12 +367,14 @@ static bool populateTahoeScanResult(size_t idx, tahoe_scan_result *out) {
out->asr_age = 0; // 0 ms = fraîcheur maximale (immédiat) 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, bit 3 = VHT, bit 4 = HE // asr_flags (0x88) : bit 0 = 2.4 GHz, bit 1 = 5 GHz, bit 2 = HT.
// Crucial : le bit 0 ne doit JAMAIS être armé sur un canal 5 GHz sous peine de rejet par airportd // 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) { if (kNetworks[idx].is2G) {
out->asr_flags = 0x001d; // 2.4G (bit0) + HT + VHT + HE out->asr_flags = 0x0005; // 2.4 GHz (bit 0) | HT (bit 2)
} else { } else {
out->asr_flags = 0x001e; // 5G (bit1) + HT + VHT + HE out->asr_flags = 0x0006; // 5 GHz (bit 1) | HT (bit 2)
} }
// Construction des Information Elements (IEs) 802.11 inline dans asr_ie_data (+0x8c) // Construction des Information Elements (IEs) 802.11 inline dans asr_ie_data (+0x8c)
@@ -424,6 +426,39 @@ static bool populateTahoeScanResult(size_t idx, tahoe_scan_result *out) {
return true; 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]) // Interception de la VTable de IO80211Glue ([fSkywalkInterface->ivars + 0xd8])
// Slot 36 (offset 0x120) : CommonGlue::sendMessage(CommonGlueMessage&) // Slot 36 (offset 0x120) : CommonGlue::sendMessage(CommonGlueMessage&)
typedef IOReturn (*GlueSendMessageFunc)(void *glue, CommonGlueMessage *msg); typedef IOReturn (*GlueSendMessageFunc)(void *glue, CommonGlueMessage *msg);
@@ -433,7 +468,18 @@ static bool sGlueVtableInstalled = false;
extern "C" IOReturn qualcomm_glueSendMessage(void *glue, CommonGlueMessage *msg) { extern "C" IOReturn qualcomm_glueSendMessage(void *glue, CommonGlueMessage *msg) {
if (msg && msg->isIouc) { 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)) { if (!msg->isGet && (msg->msgId == APPLE80211_IOC_SCAN_REQ || msg->msgId == APPLE80211_IOC_SCAN_REQ_MULTIPLE)) {
sWclScanCursor = 0; sWclScanCursor = 0;
msg->isHandled = 1; msg->isHandled = 1;
@@ -453,12 +499,35 @@ extern "C" IOReturn qualcomm_glueSendMessage(void *glue, CommonGlueMessage *msg)
} }
return 5; return 5;
} }
// APPLE80211_IOC_SCANCACHE_CLEAR (90 / 0x5a) // APPLE80211_IOC_SCANCACHE_CLEAR (90)
if (!msg->isGet && msg->msgId == APPLE80211_IOC_SCANCACHE_CLEAR) { if (!msg->isGet && msg->msgId == APPLE80211_IOC_SCANCACHE_CLEAR) {
sWclScanCursor = 0; sWclScanCursor = 0;
msg->isHandled = 1; msg->isHandled = 1;
return kIOReturnSuccess; 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) { if (sOrigGlueSendMessage) {
return sOrigGlueSendMessage(glue, msg); return sOrigGlueSendMessage(glue, msg);
@@ -488,6 +557,17 @@ static void installGlueCustomVtable(IO80211SkywalkInterface *iface) {
*(const void ***)fGlue = sGlueCustomVtable; *(const void ***)fGlue = sGlueCustomVtable;
IOLog("QualcommWifi: VTable IO80211Glue personnalisée installée sur fGlue=0x%p (origSendMessage=0x%p) !\n", IOLog("QualcommWifi: VTable IO80211Glue personnalisée installée sur fGlue=0x%p (origSendMessage=0x%p) !\n",
fGlue, (void *)sOrigGlueSendMessage); 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) // Table de fonctions virtuelles étendue pour IO80211SkywalkInterface (700 slots)
@@ -1738,6 +1818,7 @@ void QualcommWifi::stop(IOService *provider) {
bool QualcommWifi::createWorkQueue(void) { bool QualcommWifi::createWorkQueue(void) {
logNvramStage("createWorkQueue"); logNvramStage("createWorkQueue");
if (fWorkQueue) { if (fWorkQueue) {
hookWorkQueueVtable(fWorkQueue);
return true; return true;
} }
fWorkQueue = IO80211WorkQueue::workQueue(); fWorkQueue = IO80211WorkQueue::workQueue();
@@ -1746,13 +1827,16 @@ bool QualcommWifi::createWorkQueue(void) {
IOLog("QualcommWifi: Erreur - Impossible d'allouer IO80211WorkQueue !\n"); IOLog("QualcommWifi: Erreur - Impossible d'allouer IO80211WorkQueue !\n");
return false; return false;
} }
hookWorkQueueVtable(fWorkQueue);
logNvramStage("workQueue_ok"); logNvramStage("workQueue_ok");
IOLog("QualcommWifi: IO80211WorkQueue allouee avec succes (0x%p) !\n", fWorkQueue); IOLog("QualcommWifi: IO80211WorkQueue allouee avec succes (0x%p) !\n", fWorkQueue);
return true; return true;
} }
IO80211WorkQueue* QualcommWifi::getWorkQueue(void) const { IO80211WorkQueue* QualcommWifi::getWorkQueue(void) const {
if (fWorkQueue) {
hookWorkQueueVtable(fWorkQueue);
}
sGetWorkQueueCount++; sGetWorkQueueCount++;
if (sGetWorkQueueCount <= 3) { if (sGetWorkQueueCount <= 3) {
char tag[64]; char tag[64];
@@ -1919,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) { IOReturn QualcommWifi::getDRIVER_VERSION(IO80211SkywalkInterface *iface, apple80211_version_data *vd) {
return (IOReturn)handleApple80211Request(APPLE80211_IOC_DRIVER_VERSION, 0, vd); return (IOReturn)handleApple80211Request(APPLE80211_IOC_DRIVER_VERSION, 0, vd);
} }
@@ -1937,8 +2077,7 @@ IOReturn QualcommWifi::getPOWER(IO80211SkywalkInterface *iface, apple80211_power
IOReturn QualcommWifi::setPOWER(IO80211SkywalkInterface *iface, apple80211_power_data *pwr) { IOReturn QualcommWifi::setPOWER(IO80211SkywalkInterface *iface, apple80211_power_data *pwr) {
int val = (pwr && pwr->power_state[0]) ? 1 : 0; int val = (pwr && pwr->power_state[0]) ? 1 : 0;
fLinkActive = (val == 1); IOLog("QualcommWifi: setPOWER invoqué par airportd (val=%d)\n", val);
IOLog("QualcommWifi: setPOWER invoqué par airportd (val=%d, fLinkActive=%d)\n", val, fLinkActive);
return (IOReturn)handleApple80211Request(APPLE80211_IOC_POWER, val, pwr); return (IOReturn)handleApple80211Request(APPLE80211_IOC_POWER, val, pwr);
} }
@@ -2125,11 +2264,12 @@ SInt32 QualcommWifi::handleApple80211Request(unsigned int req_type, int req_val,
if (pwr) { if (pwr) {
pwr->version = APPLE80211_VERSION; pwr->version = APPLE80211_VERSION;
pwr->num_radios = 1; pwr->num_radios = 1;
if (req_val == 0 || req_val == 1) { if (req_val == 1) {
fLinkActive = (req_val == 1); IOLog("QualcommWifi: Alimentation radio Wi-Fi : ACTIVE\n");
IOLog("QualcommWifi: Bascule alimentation radio Wi-Fi : %s\n", fLinkActive ? "ACTIVE" : "COUPEE"); } 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; return 0;
} }
break; break;
@@ -2169,20 +2309,12 @@ SInt32 QualcommWifi::handleApple80211Request(unsigned int req_type, int req_val,
case APPLE80211_IOC_ASSOCIATE: { case APPLE80211_IOC_ASSOCIATE: {
apple80211_assoc_data *assoc = (apple80211_assoc_data *)req_data; apple80211_assoc_data *assoc = (apple80211_assoc_data *)req_data;
if (assoc) { if (assoc) {
IOLog("QualcommWifi: Demande d'association au reseau Wi-Fi : %.*s\n", assoc->ad_ssid_len, assoc->ad_ssid); return (SInt32)handleAssociate(assoc);
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;
} }
break; break;
} }
case APPLE80211_IOC_DISASSOCIATE: { case APPLE80211_IOC_DISASSOCIATE: {
IOLog("QualcommWifi: Deconnexion Wi-Fi demandee par macOS.\n"); return (SInt32)handleDisassociate();
fLinkActive = false;
fSsidLen = 0;
return 0;
} }
default: default:
break; break;
+4
View File
@@ -283,6 +283,10 @@ public:
// Scan Wi-Fi & Notification d'achèvement vers airportd // Scan Wi-Fi & Notification d'achèvement vers airportd
void postScanDone(void); void postScanDone(void);
// Association / Dissociation Wi-Fi
IOReturn handleAssociate(struct apple80211_assoc_data *assoc);
IOReturn handleDisassociate(void);
// Dispatcher de requêtes Apple80211 / airportd // Dispatcher de requêtes Apple80211 / airportd
SInt32 handleApple80211Request(unsigned int req_type, int req_val, void* req_data); SInt32 handleApple80211Request(unsigned int req_type, int req_val, void* req_data);
+106
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-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-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-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** |
--- ---
@@ -1372,3 +1375,106 @@ Ce document répertorie de manière exhaustive l'ensemble des anomalies, blocage
* **Validation** : * **Validation** :
- Compilation `Release` réussie avec clang 17 (`./build_wifi.sh`). - Compilation `Release` réussie avec clang 17 (`./build_wifi.sh`).
- Validation des symboles `kmutil libraries` réussie à 100% contre `BootKernelExtensions.kc`. - 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.