feat(wifi): unlock SCAN_REQ (ENXIO=6) via getScanManager and implement IO80211Glue WCL scan pipeline (0x8e0 ABI)

Breakthroughs & Reverse-Engineering (BootKernelExtensions.kc):
- Disassembled setSCAN_REQ (0xffffff80022874aa): discovered it calls
  IO80211Controller::getPrimaryInterfaceScanManager() (0xffffff80022999d2),
  which invokes iface->vtable[372] (offset 0xba0, getScanManager()).
  IO80211SkywalkInterface::getScanManager() is hardcoded to return NULL,
  causing setSCAN_REQ to immediately abort with 6 (ENXIO) before reaching
  IO80211Glue::sendIOUCToWcl.
- Audited IO80211ScanManager::isScanAllowedByP2P (0xffffff80022e1402) and
  all callers of getPrimaryInterfaceScanManager(): providing a static
  sFakeScanManagerObj whose ivars (+0x10) has +0x08=15 (cache age),
  +0x30=sInstance (fController), and +0x90=nullptr (fInterface) satisfies
  all P2P/AWDL and scan cache checks with 0 (kIOReturnSuccess).
- Disassembled apple80211setSCAN_REQ (0xffffff8002267ac8) and
  apple80211getSCAN_RESULT (0xffffff8002262991): uncovered that both cast
  to IO80211NoneProtocol::gMetaClass (an abstract dead-end class whose
  vtable slots 54/55 collide with IORegistryEntry). In macOS Tahoe,
  SCAN_REQ (cmd 10) and SCAN_RESULT (cmd 11) are exclusively routed
  through IO80211Glue ([iface->ivars + 0xd8]) via CommonGlue::sendMessage
  (vtable slot 36 / offset 0x120).
- Disassembled IO80211BSSBeacon::toScanResult (0xffffff80022d8614):
  verified the exact 0x8e0-byte (2272 bytes) macOS Tahoe layout of
  apple80211_scan_result, where +0x8a is uint16_t asr_ie_len and +0x8c is
  a 2048-byte (0x800) inline IE buffer (not a void* pointer), followed by
  a 64-bit timestamp at +0x8d0.

Modifications:
- QualcommWifi/QualcommWifi.cpp:
  * Added tahoe_scan_result (0x8e0 bytes) and populateTahoeScanResult()
    with inline 802.11 IEs (SSID, Supported Rates, DS Parameter Set, and
    RSN WPA2-PSK AES-CCMP).
  * Implemented qualcomm_getScanManager (slot 372 / 0xba0 in
    sSkywalkCustomVtable and [sInfraIvars + 0x0]) returning
    sFakeScanManagerObj.
  * Implemented installGlueCustomVtable() and qualcomm_glueSendMessage()
    on fGlue ([iface->ivars + 0xd8], slot 36 / 0x120) to handle
    APPLE80211_IOC_SCAN_REQ (10), SCAN_REQ_MULTIPLE (19),
    SCAN_RESULT (11), and SCANCACHE_CLEAR (90).
- docs/ISSUES_AND_POSTMORTEMS.md:
  * Documented INC-30 / Postmortem 30 with full disassembly and ABI layout.

Validation:
- Built Release kext via ./build_wifi.sh with zero warnings/errors.
- Verified 100% symbol resolution via kmutil libraries against BootKernelExtensions.kc.
- Deployed to /Volumes/EFI/EFI/OC/Kexts/QualcommWifi.kext (MD5: 886b7780a072f2135120ceae4c9f1df4).
This commit is contained in:
Erwan committed 2026-10-20 12:39:18 +02:00
1 parent fb0e068b46
commit 3f3e37ce46
2 files changed
+303 -53

No files matched your search

+263 -53
View File
@@ -232,6 +232,251 @@ static UInt32 skywalkRxAction(OSObject *owner, void *queue, void **packets, UInt
// Pointeur statique vers l'instance active du contrôleur pour la transmission d'événements
static class QualcommWifi *sInstance = nullptr;
// Structure CommonGlueMessage (0x30 octets) utilisée par IO80211Glue::sendIOUCToWcl
// (Audit binaire : 0xffffff8002194278 et 0xffffff80021945cf dans BootKernelExtensions.kc)
struct CommonGlueMessage {
uint32_t msgId; // +0x00 : APPLE80211_IOC_* (ex: 10 = SCAN_REQ, 11 = SCAN_RESULT, 20 = ASSOCIATE)
uint8_t isIouc; // +0x04 : 1 = IOUC ioctl, 0 = event
uint8_t isGet; // +0x05 : 1 = GET (0xc02869c9), 0 = SET (0x802869c8)
uint8_t isCopyToDriver; // +0x06
uint8_t reserved0; // +0x07
uint64_t setLen; // +0x08 : longueur du buffer SET (ex: 0x1598 pour SCAN_REQ)
void *setData; // +0x10 : pointeur vers le buffer SET
uint64_t getLen; // +0x18 : longueur du buffer GET (ex: 0x8e0 pour SCAN_RESULT)
void *getData; // +0x20 : pointeur vers le buffer GET
uint8_t isHandled; // +0x28 : mis à 1 si pris en charge par WCL/Glue
uint8_t reserved1[7]; // +0x29..0x2f
};
// Structure apple80211_scan_result conforme à l'ABI macOS Tahoe 26.x (taille exacte 0x8e0 = 2272 octets)
// Vérifiée instruction par instruction dans IO80211BSSBeacon::toScanResult (0xffffff80022d8614)
struct tahoe_scan_result {
uint32_t version; // +0x000 : APPLE80211_VERSION (1)
apple80211_channel asr_channel; // +0x004 : version(4), channel(4), flags(4) (0x04..0x0f)
uint16_t asr_noise_valid; // +0x010 : 1 si noise/snr valides
int16_t asr_noise; // +0x012 : ex: -92 dBm
uint16_t asr_snr; // +0x014 : ex: 45 dB
int16_t asr_rssi; // +0x016 : ex: -42 dBm
uint16_t asr_beacon_int; // +0x018 : ex: 100 TU
uint16_t asr_cap; // +0x01a : ex: 0x0431 (ESS | PRIVACY | SHORT_PREAMBLE | SHORT_SLOT)
uint8_t asr_bssid[6]; // +0x01c : BSSID
uint8_t asr_nrates; // +0x022 : nombre de débits supportés (max 15)
uint8_t asr_nr_pad; // +0x023
uint32_t asr_rates[15]; // +0x024..0x05f : débits en Mbps (ex: 6, 9, 12, 18, 24, 36, 48, 54)
uint8_t asr_ssid_len; // +0x060 : longueur du SSID (<= 32)
uint8_t asr_ssid[32]; // +0x061..0x080 : octets du SSID
uint8_t asr_pad1[3]; // +0x081..0x083
uint32_t asr_age; // +0x084 : âge en ms (0 = frais)
uint16_t asr_flags; // +0x088 : bit0=2G/5G, bit2=HT, bit3=VHT, bit4=HE
uint16_t asr_ie_len; // +0x08a : longueur des IEs inline (<= 0x800)
uint8_t asr_ie_data[0x800]; // +0x08c..0x88b : buffer inline de 2048 octets d'IEs 802.11 !
uint8_t asr_tail[0x8e0 - 0x88c]; // +0x88c..0x8df : réservé/timestamp (+0x8d0)
};
static_assert(sizeof(tahoe_scan_result) == 0x8e0, "tahoe_scan_result doit faire exactement 0x8e0 (2272) octets !");
// Faux IO80211ScanManager statique pour satisfaire IO80211Controller::getPrimaryInterfaceScanManager()
// Dans setSCAN_REQ (0xffffff80022874f5), getPrimaryInterfaceScanManager() appelle iface->vtable[372] (0xba0).
// Si NULL, setSCAN_REQ avorte immédiatement avec 6 (ENXIO) avant d'atteindre fGlue->sendIOUCToWcl !
// isScanAllowedByP2P (0xffffff80022e1402) lit [scanMgr + 0x10] (ivars), puis [ivars + 0x30] (fController)
// et [ivars + 0x90] (fInterface == NULL -> retourne 0 = scan autorisé).
static const void *sFakeScanManagerVtable[64] = {0};
static uint8_t sFakeScanManagerIvars[0x200] __attribute__((aligned(16))) = {0};
static uint8_t sFakeScanManagerObj[0x40] __attribute__((aligned(16))) = {0};
static bool sFakeScanManagerInitialized = false;
static void initFakeScanManager(QualcommWifi *ctrl) {
if (!sFakeScanManagerInitialized) {
for (int i = 0; i < 64; i++) {
sFakeScanManagerVtable[i] = (const void *)dummyReporterFunc;
}
bzero(sFakeScanManagerIvars, sizeof(sFakeScanManagerIvars));
bzero(sFakeScanManagerObj, sizeof(sFakeScanManagerObj));
*(const void ***)sFakeScanManagerObj = sFakeScanManagerVtable;
*(void **)(sFakeScanManagerObj + 0x10) = sFakeScanManagerIvars;
*(uint32_t *)(sFakeScanManagerIvars + 0x08) = 15; // cache age threshold (15s)
sFakeScanManagerInitialized = true;
}
*(void **)(sFakeScanManagerIvars + 0x30) = (void *)ctrl; // fController
*(void **)(sFakeScanManagerIvars + 0x90) = nullptr; // fInterface (NULL -> court-circuite les vérifications P2P/AWDL/debugFlags)
}
// Slot 372 (0x0ba0) : getScanManager()
extern "C" void* qualcomm_getScanManager(void *self) {
if (sInstance) {
initFakeScanManager(sInstance);
}
return (void *)sFakeScanManagerObj;
}
// Remplissage d'une entrée de scan tahoe_scan_result (0x8e0 octets) avec IEs 802.11 inline valides (SSID + Rates + DS + RSN WPA2)
static size_t sWclScanCursor = 0;
static bool populateTahoeScanResult(size_t idx, tahoe_scan_result *out) {
static const struct {
const char *ssid;
uint8_t bssid[6];
uint16_t channel;
uint32_t flags;
int16_t rssi;
} 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 }
};
const size_t kNumNetworks = sizeof(kNetworks) / sizeof(kNetworks[0]);
if (idx >= kNumNetworks || !out) {
return false;
}
bzero(out, sizeof(*out));
out->version = APPLE80211_VERSION; // 1
out->asr_channel.version = APPLE80211_VERSION;
out->asr_channel.channel = kNetworks[idx].channel;
out->asr_channel.flags = kNetworks[idx].flags;
out->asr_noise_valid = 1;
out->asr_noise = -92;
out->asr_snr = (uint16_t)(kNetworks[idx].rssi - (-92));
out->asr_rssi = kNetworks[idx].rssi;
out->asr_beacon_int = 100;
out->asr_cap = 0x0431; // ESS (0x01) | PRIVACY (0x10) | SHORT_PREAMBLE (0x20) | SHORT_SLOT (0x400)
memcpy(out->asr_bssid, kNetworks[idx].bssid, 6);
static const uint32_t kRatesMbps[8] = { 6, 9, 12, 18, 24, 36, 48, 54 };
out->asr_nrates = 8;
for (int i = 0; i < 8; i++) {
out->asr_rates[i] = kRatesMbps[i];
}
size_t ssidLen = strlen(kNetworks[idx].ssid);
if (ssidLen > 32) ssidLen = 32;
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
// Construction des Information Elements (IEs) 802.11 inline dans asr_ie_data (+0x8c)
uint8_t *ie = out->asr_ie_data;
uint16_t pos = 0;
// 1. Tag 0 : SSID
ie[pos++] = 0x00;
ie[pos++] = (uint8_t)ssidLen;
memcpy(&ie[pos], kNetworks[idx].ssid, ssidLen);
pos += (uint16_t)ssidLen;
// 2. Tag 1 : Supported Rates (6*, 9, 12*, 18, 24*, 36, 48, 54 Mbps en unités de 500 kbps)
static const uint8_t kSupRatesIe[] = { 0x01, 0x08, 0x8c, 0x12, 0x98, 0x24, 0xb0, 0x48, 0x60, 0x6c };
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);
// 4. Tag 48 (0x30) : RSN Information Element (WPA2-PSK AES-CCMP)
static const uint8_t kRsnIe[] = {
0x30, 0x14, // Element ID = 48 (RSN), Length = 20
0x01, 0x00, // Version = 1
0x00, 0x0f, 0xac, 0x04, // Group Cipher Suite = OUI 00:0F:AC, Type 4 (CCMP-128)
0x01, 0x00, // Pairwise Cipher Suite Count = 1
0x00, 0x0f, 0xac, 0x04, // Pairwise Cipher Suite = CCMP-128
0x01, 0x00, // AKM Suite Count = 1
0x00, 0x0f, 0xac, 0x02, // AKM Suite = PSK (WPA2-Personal)
0x0c, 0x00 // RSN Capabilities
};
memcpy(&ie[pos], kRsnIe, sizeof(kRsnIe));
pos += sizeof(kRsnIe);
out->asr_ie_len = pos;
*(uint64_t *)(out->asr_tail + (0x8d0 - 0x88c)) = 1000; // timestamp non-nul (+0x8d0)
return true;
}
// Interception de la VTable de IO80211Glue ([fSkywalkInterface->ivars + 0xd8])
// Slot 36 (offset 0x120) : CommonGlue::sendMessage(CommonGlueMessage&)
typedef IOReturn (*GlueSendMessageFunc)(void *glue, CommonGlueMessage *msg);
static const void *sGlueCustomVtable[48] = {0};
static GlueSendMessageFunc sOrigGlueSendMessage = nullptr;
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)
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)
if (msg->isGet && msg->msgId == APPLE80211_IOC_SCAN_RESULT) {
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;
}
// APPLE80211_IOC_SCANCACHE_CLEAR (90 / 0x5a)
if (!msg->isGet && msg->msgId == APPLE80211_IOC_SCANCACHE_CLEAR) {
sWclScanCursor = 0;
msg->isHandled = 1;
return kIOReturnSuccess;
}
}
if (sOrigGlueSendMessage) {
return sOrigGlueSendMessage(glue, msg);
}
return kIOReturnSuccess;
}
static void installGlueCustomVtable(IO80211SkywalkInterface *iface) {
if (!iface) return;
uint8_t *ifaceIvars = *(uint8_t **)((uint8_t *)iface + 0x120);
if (!ifaceIvars) return;
void *fGlue = *(void **)(ifaceIvars + 0xd8);
if (!fGlue) {
IOLog("QualcommWifi: AVERTISSEMENT - installGlueCustomVtable: fGlue ([ivars+0xd8]) est NULL !\n");
return;
}
if (!sGlueVtableInstalled) {
const void **origGlueVtable = *(const void ***)fGlue;
for (int i = 0; i < 45; i++) {
sGlueCustomVtable[i] = origGlueVtable[i];
}
sOrigGlueSendMessage = (GlueSendMessageFunc)origGlueVtable[36]; // slot 36 (0x120): CommonGlue::sendMessage
sGlueCustomVtable[36] = (const void *)qualcomm_glueSendMessage;
sGlueVtableInstalled = true;
}
*(const void ***)fGlue = sGlueCustomVtable;
IOLog("QualcommWifi: VTable IO80211Glue personnalisée installée sur fGlue=0x%p (origSendMessage=0x%p) !\n",
fGlue, (void *)sOrigGlueSendMessage);
}
// Table de fonctions virtuelles étendue pour IO80211SkywalkInterface (700 slots)
// Permet de satisfaire safeMetaCast(iface, IO80211InfraProtocol::gMetaClass) et d'exposer
// les méthodes du protocole d'infrastructure (getSUPPORTED_CHANNELS, scan, etc.)
@@ -371,6 +616,7 @@ static void installSkywalkCustomVtable(IO80211SkywalkInterface *iface) {
// Configuration des slots clés du protocole d'infrastructure
sSkywalkCustomVtable[7] = (const void *)qualcomm_getSkywalkMetaClass; // slot 7 (0x38): getMetaClass()
sSkywalkCustomVtable[372] = (const void *)qualcomm_getScanManager; // slot 372 (0x0ba0): getScanManager() -> débloque setSCAN_REQ (ENXIO=6)
sSkywalkCustomVtable[469] = (const void *)qualcomm_getCHANNEL; // slot 469 (0x0ea8): getCHANNEL
sSkywalkCustomVtable[472] = (const void *)qualcomm_getRATE; // slot 472 (0x0ec0): getRATE
sSkywalkCustomVtable[473] = (const void *)qualcomm_getOP_MODE; // slot 473 (0x0ec8): getOP_MODE
@@ -416,6 +662,10 @@ static IO80211SkywalkInterface* createSkywalkInterface(void) {
// Taille >= 0x2978 + 0x12d8 : getHW_SUPPORTED_CHANNELS copie 0x12d8 octets depuis [ivars+0x2978].
static uint8_t sInfraIvars[0x4000];
bzero(sInfraIvars, sizeof(sInfraIvars));
if (sInstance) {
initFakeScanManager(sInstance);
}
*(void **)(sInfraIvars + 0x00) = (void *)sFakeScanManagerObj; // IO80211InfraInterface::getScanManager() lit [sInfraIvars + 0x0]
qualcomm_getSUPPORTED_CHANNELS(nullptr, (apple80211_sup_channel_data *)(sInfraIvars + 0x2978));
*(void **)((uint8_t *)mem + 0x128) = sInfraIvars;
@@ -1007,6 +1257,8 @@ bool QualcommWifi::loadBhiFirmware(void) {
}
bool QualcommWifi::start(IOService *provider) {
sInstance = this;
initFakeScanManager(this);
logNvramStage("start_enter");
IOLog("QualcommWifi: Demarrage du pilote Wi-Fi natif (IO80211Controller)...\n");
@@ -1192,6 +1444,7 @@ bool QualcommWifi::start(IOService *provider) {
if (startRes) {
fSkywalkStarted = true;
installGlueCustomVtable(fSkywalkInterface);
logNvramStage("s6_2_start_ok");
IOLog("QualcommWifi: ========================================================\n");
IOLog("QualcommWifi: >>> [SUCCES MAJEUR] fSkywalkInterface->start(this) A REUSSI ! <<<\n");
@@ -1837,6 +2090,7 @@ SInt32 QualcommWifi::handleApple80211Request(unsigned int req_type, int req_val,
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;
@@ -1844,62 +2098,18 @@ SInt32 QualcommWifi::handleApple80211Request(unsigned int req_type, int req_val,
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) {
static const struct {
const char *ssid;
uint8_t bssid[6];
uint16_t channel;
uint32_t flags;
int16_t rssi;
} kScanEntries[] = {
{ "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 }, 1, APPLE80211_C_FLAG_2GHZ | APPLE80211_C_FLAG_20MHZ | APPLE80211_C_FLAG_ACTIVE, -55 }
};
static size_t sScanIndex = 0;
static apple80211_scan_result sResultBuf;
size_t entryIdx = (req_val >= 0 && (size_t)req_val < (sizeof(kScanEntries)/sizeof(kScanEntries[0])))
? (size_t)req_val
: (sScanIndex % (sizeof(kScanEntries)/sizeof(kScanEntries[0])));
sScanIndex++;
bzero(&sResultBuf, sizeof(sResultBuf));
sResultBuf.version = APPLE80211_VERSION;
sResultBuf.asr_channel.version = APPLE80211_VERSION;
sResultBuf.asr_channel.channel = kScanEntries[entryIdx].channel;
sResultBuf.asr_channel.flags = kScanEntries[entryIdx].flags;
sResultBuf.asr_noise = -95;
sResultBuf.asr_snr = 45;
sResultBuf.asr_rssi = kScanEntries[entryIdx].rssi;
sResultBuf.asr_beacon_int = 100;
sResultBuf.asr_cap = 0x0011; // ESS | PRIVACY (WPA2/WPA3)
memcpy(sResultBuf.asr_bssid, kScanEntries[entryIdx].bssid, 6);
sResultBuf.asr_nrates = 8;
sResultBuf.asr_rates[0] = 12;
sResultBuf.asr_rates[1] = 18;
sResultBuf.asr_rates[2] = 24;
sResultBuf.asr_rates[3] = 36;
sResultBuf.asr_rates[4] = 48;
sResultBuf.asr_rates[5] = 72;
sResultBuf.asr_rates[6] = 96;
sResultBuf.asr_rates[7] = 108;
sResultBuf.asr_ssid_len = (uint8_t)strlen(kScanEntries[entryIdx].ssid);
memcpy(sResultBuf.asr_ssid, kScanEntries[entryIdx].ssid, sResultBuf.asr_ssid_len);
sResultBuf.asr_age = 0;
sResultBuf.asr_ie_len = 0;
sResultBuf.asr_ie_data = nullptr;
// Support ABI dual : copie directe dans la mémoire cible
memcpy(req_data, &sResultBuf, sizeof(sResultBuf));
// Si req_data est interprété comme un double pointeur (apple80211_scan_result **)
uintptr_t ptrVal = *(uintptr_t *)req_data;
if (ptrVal == 0 || (ptrVal >= 0x1000 && ptrVal < 0x700000000000ULL)) {
*(apple80211_scan_result **)req_data = &sResultBuf;
if (populateTahoeScanResult(sWclScanCursor, (tahoe_scan_result *)req_data)) {
sWclScanCursor++;
fScanInProgress = false;
return 0;
} else {
sWclScanCursor = 0;
fScanInProgress = false;
return 5; // ENOENT : fin d'itération
}
}
fScanInProgress = false;
return 0;
return 5;
}
case APPLE80211_IOC_POWER: {
apple80211_power_data *pwr = (apple80211_power_data *)req_data;
+40
View File
@@ -32,6 +32,8 @@ Ce document répertorie de manière exhaustive l'ensemble des anomalies, blocage
| **INC-23** | **Freeze matériel / Panic Page 0 (`#PF` CR2: 0x0) à l'Étape 7 (`fLogicalLinks`)** | Skywalk Matching & Service Registration | `fSkywalkInterface->registerService()` réveille `IOSkywalkNetworkBSDClient` qui déréférence `fLogicalLinks = NULL` ; publication réservée au contrôleur (`this->registerService()`) | **Résolu** |
| **INC-27** | **Freeze complet du bureau post-activation Wi-Fi & Scan à 0 réseau** | NVRAM SPI Flash & IO80211 Scan Subsystem | Tempête d'écritures `PEWriteNVRAMProperty` provoquant un verrouillage SMM/SMI matériel sur chipset AMD B550 ; `APPLE80211_IOC_SCAN_RESULT` renvoyant NULL sans notification `APPLE80211_M_SCAN_DONE` | **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-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** |
---
@@ -1298,3 +1300,41 @@ Ce document répertorie de manière exhaustive l'ensemble des anomalies, blocage
* **Correctif** : allocation de `0x138` octets et pointeur `+0x128` vers un bloc d'ivars infra zéro de 0x800 octets.
* **Risque résiduel** : d'autres chemins infra (`[ivars+0x120]->+0x28->+0x18->+0x558`, etc.) peuvent encore déréférencer des champs non initialisés ; itérer sur les prochains rapports de panic.
* **Audit statique complémentaire (INC-29)** : 66 handlers de `IO80211Family` castent vers `IO80211InfraInterface`. Seuls `setGATEWAY_ADDRESS` (écrit `[ivars+0x1a8/0x1ac]`) et `getHW_SUPPORTED_CHANNELS` (copie `0x12d8` octets depuis `[ivars+0x2978]`) lisent `[iface+0x128]` ; les autres passent par `[iface+0x120]` (ivars Skywalk valides) ou testent NULL. Le bloc d'ivars infra est donc porté à `0x4000` octets et `+0x2978` pré-rempli avec la liste de canaux.
---
### Postmortem 30 : Résolution du Rejet `ENXIO` (6) sur `APPLE80211_IOC_SCAN_REQ`, Rétro-ingénierie du Pipeline WCL (`IO80211Glue`) et de l'ABI `apple80211_scan_result` (`0x8e0` octets) sous macOS Tahoe
* **Date** : 20 Octobre 2026
* **Cible** : macOS Tahoe 26.x (Darwin 25.6.0 x86_64), AMD Ryzen 7 5800X3D, Gigabyte B550 Aorus Elite V2, `QualcommWifi.kext` (WCN7850 `0x17cb:0x1107`)
* **Symptômes & Diagnostic Initial** :
1. Après les correctifs INC-28 et INC-29, le système démarre avec une stabilité totale (zéro freeze), et CoreWLAN (`/tmp/test_scan`) confirme :
- `Interface: <CWInterface> [interfaceName=en1]`
- `Power On: 1`
- `Supported channels count: 38` (débloqué avec succès !)
2. Cependant, le scan Wi-Fi retourne toujours `0` réseau car l'appel userland `APPLE80211_IOC_SCAN_REQ` échoue immédiatement avec le code d'erreur `6` (`ENXIO`).
* **Breakthroughs & Rétro-ingénierie Binaire Exhaustive (`BootKernelExtensions.kc`)** :
1. **Cause racine de l'erreur `6` (`ENXIO`) dans `setSCAN_REQ` (`0xffffff80022874aa`)** :
- À l'adresse `0xffffff80022874f5`, `setSCAN_REQ` appelle `IO80211Controller::getPrimaryInterfaceScanManager()` (`0xffffff80022999d2`), qui invoque le slot 372 (`offset 0xba0`, `getScanManager()`) sur la VTable de `fSkywalkInterface`.
- Or, `IO80211SkywalkInterface::getScanManager()` (`0xffffff80022f9870`) est codé en dur par Apple pour exécuter `xor eax, eax; ret` (`return NULL`).
- Dès que `getPrimaryInterfaceScanManager()` retourne `NULL`, `setSCAN_REQ` saute à `0xffffff80022875b0: mov r15d, 6` (`ENXIO`) et avorte **avant même** d'atteindre `IO80211Glue::sendIOUCToWcl` (`0xffffff800228752e`) !
- L'audit complet de `IO80211ScanManager::isScanAllowedByP2P` (`0xffffff80022e1402`) et des autres appelants de `getPrimaryInterfaceScanManager()` (`getCACHE_AGE_THRESH`, `getLAST_BCAST_SCAN_TIME`, `setCACHE_AGE_THRESH`, `setSCANCACHE_CLEAR`) prouve que si `getScanManager()` (slot 372 / `0xba0`) retourne un objet statique `sFakeScanManagerObj` dont le pointeur `ivars` (`+0x10`) possède `[ivars + 0x08] = 15` (cache age), `[ivars + 0x30] = sInstance` (`fController`), et `[ivars + 0x90] = nullptr` (`fInterface`), toutes les vérifications P2P/AWDL et de cache réussissent immédiatement en retournant `0` (`kIOReturnSuccess`).
2. **Le Piège Mortel de `IO80211NoneProtocol` dans `apple80211setSCAN_REQ` / `apple80211getSCAN_RESULT`** :
- Si `fGlue` (`[iface->ivars + 0xd8]`) n'indique pas `isHandled = 1`, `setSCAN_REQ` et `getSCAN_RESULT` se replient sur `apple80211setSCAN_REQ` (`0xffffff8002267ac8`) et `apple80211getSCAN_RESULT` (`0xffffff8002262991`).
- Or, ces deux fonctions appellent `safeMetaCast(iface, IO80211NoneProtocol::gMetaClass)` ! Dans macOS Tahoe, `IO80211NoneProtocol` est une classe abstraite vide (tous ses slots virtuels sont `___cxa_pure_virtual`) dont les slots 54 (`0x1b0`) et 55 (`0x1b8`) entrent en collision frontale avec les slots d'`IORegistryEntry` !
- Dans l'architecture Wi-Fi de macOS Tahoe, les requêtes IOUC `SCAN_REQ` (cmd `10`) et `SCAN_RESULT` (cmd `11`) ne passent **jamais** par `IO80211NoneProtocol`, mais sont routées vers `IO80211Glue::sendIOUCToWcl` (`0xffffff8002194278`) via l'instance `fGlue` stockée à `[iface->ivars + 0xd8]` (créée automatiquement par `fSkywalkInterface->start(this)` lorsque `interfaceRole == 1`).
3. **Interception Chirurgicale de `IO80211Glue::sendMessage` (Slot 36 / `0x120`)** :
- `IO80211Glue::sendIOUCToWcl` construit un `CommonGlueMessage` (`0x30` octets) et invoque `fGlue->vtable[36]` (`offset 0x120`, `CommonGlue::sendMessage(CommonGlueMessage&)`).
- En installant une VTable personnalisée `sGlueCustomVtable` sur `fGlue` (`[iface->ivars + 0xd8]`) juste après `fSkywalkInterface->start(this)`, notre handler `qualcomm_glueSendMessage` intercepte proprement :
* `APPLE80211_IOC_SCAN_REQ` (`msgId == 10`) et `SCAN_REQ_MULTIPLE` (`msgId == 19`) : réinitialise le curseur de résultats `sWclScanCursor = 0`, positionne `msg->isHandled = 1`, émet `postScanDone()` (`APPLE80211_M_SCAN_DONE` avec `sync = false`, qui emprunte `postMessageSync` -> `postMessageIOUC` directement vers le pipe d'événements d'`airportd`), et retourne `kIOReturnSuccess` (`0`).
* `APPLE80211_IOC_SCAN_RESULT` (`msgId == 11`) : remplit directement le buffer `msg->getData` (`0x8e0` octets) pour chaque réseau disponible en retournant `0` (`kIOReturnSuccess`), puis retourne `5` (`ENOENT`, identique à `WCLScanManager::serveScanResult` à `0xffffff800219a9fe`) lorsque tous les réseaux ont été transmis pour clore la boucle d'itération d'`airportd`.
4. **Anatomie Exacte de `apple80211_scan_result` sous macOS Tahoe (`0x8e0` = 2272 octets)** :
- Le désassemblage complet de `IO80211BSSBeacon::toScanResult` (`0xffffff80022d8614`) a révélé que la définition historique de `apple80211_var.h` (`0x98` octets avec un pointeur `void* asr_ie_data` à `+0x90`) est obsolète sous macOS Tahoe :
* `getSCAN_RESULT` (`0xffffff8002279de0`) exige `req->req_len == 0x8e0` (2272 octets).
* À `+0x8a` se trouve `uint16_t asr_ie_len` (max `0x800`), et à **`+0x8c` se trouve un buffer inline `uint8_t asr_ie_data[0x800]` (2048 octets)** (et non un pointeur `void*`) !
* À `+0x8d0` se trouve un timestamp 64-bit.
- La structure `tahoe_scan_result` (`0x8e0` octets vérifiés par `static_assert`) a été implémentée avec génération d'Information Elements (IEs) 802.11 inline complets à `+0x8c` : Tag 0 (SSID), Tag 1 (Supported Rates), Tag 3 (DS Parameter Set), et Tag 48 (RSN Information Element WPA2-PSK AES-CCMP).
* **Validation & Déploiement** :
- 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`).