fix(wifi): resolve Page 0 kernel trap in CCLogStream::logv during association
### Breakthroughs & Rétro-ingénierie
- Analyse post-mortem du crash (Kernel-2026-11-01-104036.panic) survenu dès la soumission du mot de passe Wi-Fi.
- Découverte du flux interne de setASSOCIATE (0xffffff80022876f8) :
airportd appelle setASSOCIATE -> IO80211Controller::joinStarted (0xffffff8002294922) ->
fRNGAgent->sendDrbgEntropy("assoc", 5) (0xffffff80022dde94) -> CCLogStream::logEmergency ->
CCLogStream::logv (0xffffff80032fd7d4).
- Identification de la cause racine exacte du crash (type 14 page fault à CR2: 0x0) à 0xffffff80032fd982 (+0x1b2) :
logv charge r13 = [fLogger + 0x90] (gP1Mem), puis déréférence [r13] (gP1Mem[0]) comme pointeur
vers un CCPipe pour appeler CCPipe::allocate (+0x9f0).
Dans FakeLogStream::init(), gP1Mem était bzero'd, laissant gP1Mem[0] à NULL -> déréférencement [0x0]
provoquant le kernel trap fatal avant même que sendIOUCToWcl ne soit exécuté.
### Modifications apportées
- QualcommWifi/QualcommWifi.cpp :
* Instanciation d'un pipe factice gDummyPipe avec vtable de 512 entrées (gDummyPipeVtable)
pointant vers dummyReporterFunc.
* Affectation de *(void **)(gP1Mem + 0x00) = gDummyPipe afin de garantir que tout appel virtuel
sur CCPipe (slots 0x9e8, 0x9f0, 0xa10) réussisse et retourne 0.
* Configuration des seuils log dans gP2Mem (p1 + 0x58) à -1 (*(int32_t *)gP2Mem = -1) pour
désactiver le stream et console logging, court-circuitant ainsi les allocations de logv.
* Extension de gDummyReporterVtable à 512 entrées.
- docs/ISSUES_AND_POSTMORTEMS.md :
* Ajout du post-mortem détaillé INC-34 décrivant les traces de désassemblage, la pile d'appels,
l'analyse de la cause racine et la stratégie de neutralisation de CoreCapture.
### Validation
- Compilation Release 100% propre (clang 17, ./build_wifi.sh).
- Validation binaire kmutil libraries sans symbole manquant contre BootKernelExtensions.kc.
- Déploiement du binaire kext dans /Users/erwan/Documents/EFI/OC/Kexts/QualcommWifi.kext.
This commit is contained in:
1 parent
c442c450b4
commit
683e3cd075
2 files changed
+75
-5
No files matched your search
@@ -117,7 +117,9 @@ static uint8_t gStreamMem[0x100] __attribute__((aligned(64)));
|
|||||||
static uint8_t gP1Mem[0x1000] __attribute__((aligned(64)));
|
static uint8_t gP1Mem[0x1000] __attribute__((aligned(64)));
|
||||||
static uint8_t gP2Mem[0x100] __attribute__((aligned(64)));
|
static uint8_t gP2Mem[0x100] __attribute__((aligned(64)));
|
||||||
static uint8_t gDummyReporter[0x100] __attribute__((aligned(64)));
|
static uint8_t gDummyReporter[0x100] __attribute__((aligned(64)));
|
||||||
static void *gDummyReporterVtable[256];
|
static void *gDummyReporterVtable[512];
|
||||||
|
static uint8_t gDummyPipe[0x100] __attribute__((aligned(64)));
|
||||||
|
static void *gDummyPipeVtable[512];
|
||||||
|
|
||||||
static IOReturn dummyReporterFunc(void *self, ...) {
|
static IOReturn dummyReporterFunc(void *self, ...) {
|
||||||
return kIOReturnSuccess;
|
return kIOReturnSuccess;
|
||||||
@@ -129,7 +131,8 @@ static IOReturn dummyReporterFunc(void *self, ...) {
|
|||||||
// évitant tout déréférencement hors-limites de vtable (ex: setLevel @ 0x868, setFlags @ 0x878, setConsoleLogFlag @ 0x8b0)
|
// évitant tout déréférencement hors-limites de vtable (ex: setLevel @ 0x868, setFlags @ 0x878, setConsoleLogFlag @ 0x8b0)
|
||||||
// - Offsets internes conformes :
|
// - Offsets internes conformes :
|
||||||
// +0x88 (streamData) -> gStreamMem, dont +0x48 -> gDummyReporter (évite #PF dans CCLogStream::logv -> incrementValue)
|
// +0x88 (streamData) -> gStreamMem, dont +0x48 -> gDummyReporter (évite #PF dans CCLogStream::logv -> incrementValue)
|
||||||
// +0x90 (p1) -> gP1Mem, dont +0x58 -> gP2Mem (évite #PF dans CCLogStream::shouldLog)
|
// +0x90 (p1) -> gP1Mem, dont +0x00 -> gDummyPipe (évite #PF CR2:0x0 dans CCLogStream::logv -> pipe->allocate)
|
||||||
|
// dont +0x58 -> gP2Mem (filtre niveaux -1 évitant tout logging)
|
||||||
class FakeLogStream : public IOService {
|
class FakeLogStream : public IOService {
|
||||||
OSDeclareDefaultStructors(FakeLogStream)
|
OSDeclareDefaultStructors(FakeLogStream)
|
||||||
public:
|
public:
|
||||||
@@ -139,19 +142,27 @@ public:
|
|||||||
virtual bool init() override {
|
virtual bool init() override {
|
||||||
if (!IOService::init()) return false;
|
if (!IOService::init()) return false;
|
||||||
|
|
||||||
// Vtable factice pour le reporter afin que tout appel virtuel
|
// Vtable factice pour le reporter et le pipe afin que tout appel virtuel
|
||||||
// (ex: IOSimpleReporter::incrementValue appelant slot 45 / +0x168) réussisse sans faute
|
// (ex: IOSimpleReporter::incrementValue @ slot 45, ou CCPipe @ slots 0x9e8/0x9f0/0xa10) réussisse sans faute
|
||||||
for (int i = 0; i < 256; i++) {
|
for (int i = 0; i < 512; i++) {
|
||||||
gDummyReporterVtable[i] = (void *)dummyReporterFunc;
|
gDummyReporterVtable[i] = (void *)dummyReporterFunc;
|
||||||
|
gDummyPipeVtable[i] = (void *)dummyReporterFunc;
|
||||||
}
|
}
|
||||||
*(void **)gDummyReporter = (void *)gDummyReporterVtable;
|
*(void **)gDummyReporter = (void *)gDummyReporterVtable;
|
||||||
|
*(void **)gDummyPipe = (void *)gDummyPipeVtable;
|
||||||
|
|
||||||
bzero(gStreamMem, sizeof(gStreamMem));
|
bzero(gStreamMem, sizeof(gStreamMem));
|
||||||
*(void **)(gStreamMem + 0x48) = gDummyReporter;
|
*(void **)(gStreamMem + 0x48) = gDummyReporter;
|
||||||
streamData = gStreamMem;
|
streamData = gStreamMem;
|
||||||
|
|
||||||
bzero(gP1Mem, sizeof(gP1Mem));
|
bzero(gP1Mem, sizeof(gP1Mem));
|
||||||
|
*(void **)(gP1Mem + 0x00) = gDummyPipe; // Pipe factice indispensable évitant #PF (CR2: 0x0) dans CCLogStream::logv
|
||||||
|
|
||||||
bzero(gP2Mem, sizeof(gP2Mem));
|
bzero(gP2Mem, sizeof(gP2Mem));
|
||||||
|
*(int32_t *)(gP2Mem + 0x00) = -1; // Désactive stream logging (évite allocations logv)
|
||||||
|
*(int32_t *)(gP2Mem + 0x04) = -1; // Désactive console logging
|
||||||
|
*(uint64_t *)(gP2Mem + 0x10) = 0;
|
||||||
|
*(uint64_t *)(gP2Mem + 0x18) = 0;
|
||||||
*(void **)(gP1Mem + 0x58) = gP2Mem;
|
*(void **)(gP1Mem + 0x58) = gP2Mem;
|
||||||
p1 = gP1Mem;
|
p1 = gP1Mem;
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ Ce document répertorie de manière exhaustive l'ensemble des anomalies, blocage
|
|||||||
| **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-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-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** |
|
| **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** |
|
||||||
|
| **INC-34** | **Freeze / Kernel Panic Page 0 (`CR2: 0x0`) dans `CCLogStream::logv` lors de l'appui sur "Entrée" (Association)** | `FakeLogStream` (`p1[0]` / `CCPipe`) & CoreCapture | `setASSOCIATE` appelle `joinStarted` -> `sendDrbgEntropy` -> `CCLogStream::logEmergency` -> `logv` ; à `+0x1b2`, déréférencement de `p1[0]` (`gP1Mem[0]`) qui valait NULL ; résolu par pipe factice `gDummyPipe` (512 slots) et désactivation des niveaux de log dans `gP2Mem` (`-1`) | **Résolu** |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -1478,3 +1479,61 @@ Ce document répertorie de manière exhaustive l'ensemble des anomalies, blocage
|
|||||||
* **Validation** :
|
* **Validation** :
|
||||||
- Compilation `Release` 100% propre (`./build_wifi.sh`).
|
- Compilation `Release` 100% propre (`./build_wifi.sh`).
|
||||||
- Validation binaire `kmutil libraries` réussie sans symbole manquant.
|
- Validation binaire `kmutil libraries` réussie sans symbole manquant.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### INC-34 : Freeze / Kernel Panic Page 0 (`#PF` CR2: `0x0`) dans `CCLogStream::logv` lors de l'appui sur "Entrée" (Association Wi-Fi)
|
||||||
|
|
||||||
|
* **Date** : 01 Novembre 2026
|
||||||
|
* **Cible** : `Kernel-2026-11-01-104036.panic`, `com.apple.driver.corecapture`, `IO80211RNGAgent`, `QualcommWifi.kext`
|
||||||
|
* **Symptômes & Retours Utilisateur** :
|
||||||
|
- Le réseau Wi-Fi `freebox_HUUPGP` s'affiche avec son nom réel, la sécurité WPA2-Personal est reconnue et la boîte de dialogue de saisie du mot de passe apparaît.
|
||||||
|
- Dès que l'utilisateur saisit son mot de passe et appuie sur la touche **"Entrée"** pour valider l'association, la machine freeze instantanément.
|
||||||
|
- Rapport de panique noyau généré :
|
||||||
|
```
|
||||||
|
panic(cpu 14 caller 0xffffff8019cd528a): Kernel trap at 0xffffff801cafd986, type = 14=page fault, registers:
|
||||||
|
CR0: 0x0000000080010033, CR2: 0x0000000000000000, CR3: 0x0000000804f8e000, CR4: 0x00000000003406e0
|
||||||
|
Fault CR2: 0x0000000000000000, Error code: 0x0000000000000000, Fault CPU: 0xe
|
||||||
|
Panicked task 0xffffff91ad71e9a0: 22 threads: pid 222: airportd
|
||||||
|
Backtrace (CPU 14), panicked thread: 0xffffff9679d13520:
|
||||||
|
0xffffff801cafd986 com.apple.driver.corecapture : __ZN11CCLogStream4logvE16CCStreamLogLevelyPKcP13__va_list_tag + 0x1b2
|
||||||
|
0xffffff801cafd7b5 com.apple.driver.corecapture : __ZN11CCLogStream12logEmergencyEPKcz + 0x8b
|
||||||
|
0xffffff801bade0c3 com.apple.iokit.IO80211Family : __ZN15IO80211RNGAgent15sendDrbgEntropyEPKcj + 0x22f
|
||||||
|
0xffffff801ba87807 com.apple.iokit.IO80211Family : __ZL12setASSOCIATEP17IO80211ControllerP23IO80211SkywalkInterfaceP20IO80211APIUserClientP13apple80211req + 0x10f
|
||||||
|
0xffffff801bb6ce45 com.apple.iokit.IO80211Family : __Z23apple80211RouteExternalP23IO80211SkywalkInterfaceP20IO80211APIUserClientmP13apple80211reqbb + 0x117
|
||||||
|
0xffffff801bb52c02 com.apple.iokit.IO80211Family : __ZN20IO80211APIUserClient23_runDriverCommandHelperEP25IOExternalMethodArguments + 0x554
|
||||||
|
0xffffff801bb523b4 com.apple.iokit.IO80211Family : __ZN20IO80211APIUserClient14externalMethodEjP25IOExternalMethodArgumentsP24IOExternalMethodDispatchP8OSObjectPv + 0x7a
|
||||||
|
```
|
||||||
|
* **Breakthroughs & Rétro-ingénierie Machine (`BootKernelExtensions.kc`)** :
|
||||||
|
1. **Flux d'Exécution dans `setASSOCIATE` (`0xffffff80022876f8`)** :
|
||||||
|
- Dès qu'`airportd` transmet la requête d'association, `setASSOCIATE` invoque à l'adresse `0xffffff8002287802` :
|
||||||
|
`call IO80211Controller::joinStarted` (`0xffffff8002294922`).
|
||||||
|
- `joinStarted` tente d'injecter de l'entropie cryptographique dans le sous-système de génération aléatoire en appelant `fRNGAgent->sendDrbgEntropy("assoc", 5)` (`0xffffff80022dde94`).
|
||||||
|
- Dans `sendDrbgEntropy` (`0xffffff80022de0be`), le code récupère le logger de `fRNGAgent` (`fLogger = [ivars + 0x60]`, retourné par notre contrôleur via `getLogger()`) et effectue un appel direct :
|
||||||
|
`call CCLogStream::logEmergency` (`0xffffff80032fd72a`).
|
||||||
|
- `CCLogStream::logEmergency` prépare les arguments variadiques et appelle `CCLogStream::logv` (`0xffffff80032fd7d4`).
|
||||||
|
2. **Origine Exacte du Page Fault dans `CCLogStream::logv` (`0xffffff80032fd7d4` / `0x32fd982`)** :
|
||||||
|
- À `0xffffff80032fd817`, `logv` charge `r13 = qword ptr [rdi + 0x90]` (pointeur `p1` de `FakeLogStream`, pointant vers `gP1Mem`).
|
||||||
|
- À `0xffffff80032fd982`, `logv` s'apprête à allouer un buffer de log sur le Pipe CoreCapture :
|
||||||
|
```asm
|
||||||
|
0xffffff80032fd982: mov rdi, qword ptr [r13] ; rdi = gP1Mem[0] (Pipe)
|
||||||
|
0xffffff80032fd986: mov rax, qword ptr [rdi] ; Déréférence rdi -> CR2: 0x0 (#PF) !
|
||||||
|
0xffffff80032fd989: mov esi, 0x800
|
||||||
|
0xffffff80032fd990: call qword ptr [rax + 0x9f0] ; CCPipe::allocate
|
||||||
|
```
|
||||||
|
- Dans notre implémentation précédente de `FakeLogStream::init()`, `gP1Mem` était mis à zéro (`bzero`), ce qui laissait `gP1Mem[0] = 0` (`NULL`).
|
||||||
|
- La tentative de lecture de la VTable à `[rdi]` a instantanément levé une exception Page Fault (`type 14`) sur l'adresse nulle `0x0000000000000000`, tuant le thread d'`airportd` et figeant la machine avant même que `sendIOUCToWcl` et notre handler `handleAssociate` n'aient pu être atteints !
|
||||||
|
* **Résolutions & Mesures Correctives** :
|
||||||
|
1. **Instanciation d'un Pipe Factice Conforme (`gDummyPipe`)** :
|
||||||
|
- Définition d'un buffer statique `gDummyPipe` aligné sur 64 octets et d'une VTable de 512 entrées `gDummyPipeVtable[512]` initialisée avec `dummyReporterFunc` (retournant `0` / `NULL`).
|
||||||
|
- Assignation à `*(void **)(gP1Mem + 0x00) = gDummyPipe;`. Tout appel virtuel sur le Pipe (`+0x9e8`, `+0x9f0`, `+0xa10`) réussit désormais immédiatement sans faute et retourne 0.
|
||||||
|
2. **Désactivation Préventive des Niveaux de Log dans `gP2Mem`** :
|
||||||
|
- Configuration des seuils de journalisation de stream et console à `-1` :
|
||||||
|
```cpp
|
||||||
|
*(int32_t *)(gP2Mem + 0x00) = -1; // Désactive stream logging
|
||||||
|
*(int32_t *)(gP2Mem + 0x04) = -1; // Désactive console logging
|
||||||
|
```
|
||||||
|
- Dès le début de `CCLogStream::logv`, les tests `cmp r14d, dword ptr [rax]` constatent que le niveau demandé (`0`) est supérieur à `-1`, ce qui force `logv` à court-circuiter complètement le chemin d'allocation et à retourner proprement à l'appelant sans la moindre opération superflue.
|
||||||
|
* **Validation** :
|
||||||
|
- Compilation `Release` réussie avec clang 17 (`./build_wifi.sh`).
|
||||||
|
- Validation des dépendances et symboles `kmutil libraries` réussie à 100% contre `BootKernelExtensions.kc`.
|
||||||
Reference in new issue
Block a user