fix(wifi): resolve kernel page fault CR2:0x658 in ml_thread_policy via distinct IOWorkLoop

### Breakthroughs & Rétro-ingénierie
- Analyse post-mortem du crash dump de panique noyau réel (/Library/Logs/DiagnosticReports/.contents.panic) :
  panic(cpu 2 caller 0xffffff80130d528a): Kernel trap at 0xffffff80130cc673, type = 14=page fault, CR2: 0x0000000000000658.
  Backtrace : _IOLockLock + 0x3 (%rdi == 0x658) <- _ml_thread_policy + 0x27 (_thread_policy_set_internal) <- IONetworkController::start + 0x1d4 <- IO80211Controller::start + 0x41 <- QualcommWifi::start + 0x61.
- Désassemblage de IONetworkController::start (0xffffff80026f3650) :
  Appel de this->getWorkLoop() (slot 209 = 0x688) comparé au workloop du provider IOPCIDevice. En cas de non-concordance, il invoque workloop->getThread() (slot 37 = 0x128) et transmet le thread retourné à ml_thread_policy.
- Désassemblage de IO80211WorkQueue::getThread() (0xffffff80022ee1ea) :
  Contrairement à IOWorkLoop qui maintient un Mach thread_t (offset +0x30), IO80211WorkQueue::getThread() renvoie systématiquement 0 (xorl %eax, %eax). QualcommWifi redirigeait getWorkLoop() vers fWorkQueue, causant un appel à ml_thread_policy(0, ...), qui dereference 0 + 0x658 (thread->mutex), provoquant le Page Fault #PF fatal en tout début de boot.

### Modifications apportées
- QualcommWifi/QualcommWifi.hpp :
  - Déclaration du pointeur dédié IOWorkLoop *fWorkLoop; séparé de IO80211WorkQueue *fWorkQueue;.
- QualcommWifi/QualcommWifi.cpp :
  - init() : Initialisation de fWorkLoop à nullptr.
  - free() : Libération de fWorkLoop via OSSafeReleaseNULL(fWorkLoop).
  - start() : Instanciation séquentielle createWorkLoop() puis createWorkQueue() avant d'appeler super::start(provider).
  - stop() : Libération sûre de fWorkLoop et fWorkQueue après désactivation des interruptions.
  - createWorkLoop() : Allocation d'un véritable IOWorkLoop via IOWorkLoop::workLoop(), garantissant la présence d'un Mach thread valide.
  - getWorkLoop() : Renvoie fWorkLoop (ou provider->getWorkLoop() en repli).
- docs/ISSUES_AND_POSTMORTEMS.md :
  - Ajout de la fiche d'incident complète INC-13 détaillant l'analyse désassemblée, la cause racine et les résolutions.

### Validation
- Compilation réussie via ./build_wifi.sh.
- Vérification binaire : slot 209 (getWorkLoop), slot 306 (createWorkLoop), slot 395 (createWorkQueue), slot 397 (getWorkQueue) parfaitement alignés.
- Validation symbolique et dépendances kext kmutil libraries -p réussie à 100% contre BootKernelExtensions.kc.
This commit is contained in:
Erwan committed 2026-10-18 11:57:51 +02:00
1 parent 3a02c6ff8e
commit a922133da9
3 files changed
+86 -2

No files matched your search

+30 -2
View File
@@ -143,6 +143,7 @@ bool QualcommWifi::init(OSDictionary *dictionary) {
fBar0Virt = nullptr;
fCurrWindow = 0xFFFFFFFF;
fInterruptSource = nullptr;
fWorkLoop = nullptr;
fWorkQueue = nullptr;
fNetif = nullptr;
// Adresse MAC par défaut (OUI Qualcomm 00:03:7F) en attendant la lecture BHI
@@ -174,6 +175,7 @@ void QualcommWifi::free(void) {
logNvramStage("free_enter");
OSSafeReleaseNULL(fFaultReporter);
OSSafeReleaseNULL(fLogger);
OSSafeReleaseNULL(fWorkLoop);
OSSafeReleaseNULL(fWorkQueue);
super::free();
}
@@ -644,6 +646,12 @@ bool QualcommWifi::start(IOService *provider) {
logNvramStage("start_enter");
IOLog("QualcommWifi: Demarrage du pilote Wi-Fi natif (IO80211Controller)...\n");
if (!createWorkLoop()) {
logNvramStage("workLoop_failed");
IOLog("QualcommWifi: Erreur - Impossible de creer IOWorkLoop !\n");
return false;
}
if (!createWorkQueue()) {
logNvramStage("workQueue_failed");
IOLog("QualcommWifi: Erreur - Impossible de creer IO80211WorkQueue !\n");
@@ -779,6 +787,14 @@ void QualcommWifi::stop(IOService *provider) {
OSSafeReleaseNULL(fPciDevice);
}
if (fWorkLoop) {
OSSafeReleaseNULL(fWorkLoop);
}
if (fWorkQueue) {
OSSafeReleaseNULL(fWorkQueue);
}
super::stop(provider);
}
@@ -803,11 +819,23 @@ IO80211WorkQueue* QualcommWifi::getWorkQueue(void) const {
}
bool QualcommWifi::createWorkLoop(void) {
return createWorkQueue();
logNvramStage("createWorkLoop");
if (fWorkLoop) {
return true;
}
fWorkLoop = IOWorkLoop::workLoop();
if (!fWorkLoop) {
logNvramStage("workLoop_alloc_failed");
IOLog("QualcommWifi: Erreur - Impossible d'allouer IOWorkLoop !\n");
return false;
}
logNvramStage("workLoop_ok");
IOLog("QualcommWifi: IOWorkLoop allouee avec succes (0x%p) !\n", fWorkLoop);
return true;
}
IOWorkLoop* QualcommWifi::getWorkLoop(void) const {
return getWorkQueue();
return fWorkLoop ? fWorkLoop : (getProvider() ? getProvider()->getWorkLoop() : nullptr);
}
IOReturn QualcommWifi::getHardwareAddress(IOEthernetAddress *addrP) {
+1
View File
@@ -185,6 +185,7 @@ private:
UInt32 fCurrWindow;
IOInterruptEventSource *fInterruptSource;
IOWorkLoop *fWorkLoop;
IO80211WorkQueue *fWorkQueue;
IONetworkInterface *fNetif;
IOEthernetAddress fMacAddress;
+55
View File
@@ -260,3 +260,58 @@ Ce document répertorie de manière exhaustive l'ensemble des anomalies, blocage
- Taille totale de l'instance fixée à `0x98` octets (`static_assert(sizeof(FakeLogStream) == 0x98)`).
3. **Mise en Conformité de `FakeFaultReporter`** :
- `class FakeFaultReporter : public IOService` avec padding `0x90 - sizeof(IOService)`. Vtable de 269 slots (`0x868` octets) et taille `0x90` octets (`static_assert(sizeof(FakeFaultReporter) == 0x90)`).
---
### INC-13 : Page Fault Kernel Trap `CR2: 0x0000000000000658` dans `ml_thread_policy` / `IONetworkController::start`
* **Symptômes** :
- Bootloop instantané ("coupure de courant" / reboot immédiat en mode verbose, ou blocage sur logo Apple en mode normal).
- Trace matérielle extraite du log de panique (`/Library/Logs/DiagnosticReports/.contents.panic`) :
```
panic(cpu 2 caller 0xffffff80130d528a): Kernel trap at 0xffffff80130cc673, type = 14=page fault, CR2: 0x0000000000000658
Backtrace:
_IOLockLock + 0x3 (avec %rdi == 0x658)
_ml_thread_policy + 0x27 (_thread_policy_set_internal)
IONetworkController::start(IOService*) + 0x1d4 (0xffffff80026f36b0)
IO80211Controller::start(IOService*) + 0x41 (0xffffff8002291f71)
QualcommWifi::start(IOService*) + 0x61
```
* **Causes Racines (Breakthroughs Majeurs & Rétro-ingénierie Noyau)** :
1. **Désassemblage critique de `IONetworkController::start(provider)` (`0xffffff80026f3650`)** :
```assembly
ffffff80026f365b: callq *0x688(%rax) ; Appelle this->getWorkLoop() (slot 209 = 0x688)
ffffff80026f3661: movq %rax, 0x88(%rbx) ; Stocke dans ivars->workLoop
ffffff80026f3676: movq 0x88(%rbx), %r15
ffffff80026f367d: movq (%r14), %rax ; r14 = provider (IOPCIDevice)
ffffff80026f3683: callq *0x688(%rax) ; Appelle provider->getWorkLoop()
ffffff80026f3689: cmpq %rax, %r15 ; Compare le workloop provider avec notre workloop
ffffff80026f368c: je 0xffffff80026f36b0 ; SI IDENTIQUE, SAUTE ml_thread_policy !
ffffff80026f368e: movq 0x88(%rbx), %rdi ; Si différent, utilise notre workloop
ffffff80026f3695: movq (%rdi), %rax ; vtable de notre workloop
ffffff80026f3698: callq *0x128(%rax) ; APPELLE workloop->getThread() (slot 37 = 0x128)
ffffff80026f369e: movq %rax, %rdi ; TRANSMET LE THREAD RETOURNÉ À ml_thread_policy !
ffffff80026f36ab: callq ml_thread_policy ; -> thread_policy_set_internal(thread, ...)
```
2. **Origine du Pointeur Null et Incompatibilité d'ABI `IOWorkLoop` vs `IO80211WorkQueue`** :
- Dans `QualcommWifi.cpp`, l'implémentation initiale de `getWorkLoop()` redirigeait directement vers `getWorkQueue()` (un objet `IO80211WorkQueue`).
- Le désassemblage de `IO80211WorkQueue::getThread()` (`0xffffff80022ee1ea`) dans `BootKernelExtensions.kc` a révélé son implémentation exacte :
```assembly
ffffff80022ee1ea <IO80211WorkQueue::getThread()>:
pushq %rbp
movq %rsp, %rbp
xorl %eax, %eax ; RETOURNE CONSTAMMENT NULL (0) !
popq %rbp
retq
```
- Contrairement à un véritable `IOWorkLoop` qui maintient un Mach thread dédié (`0x30(%rdi)`), `IO80211WorkQueue` retourne toujours `0`.
- `thread_policy_set_internal(thread_t thread, ...)` présume que le thread passé en paramètre est non-nul et exécute `IOLockLock(&thread->mutex)` où le mutex de structure `thread` se situe à l'offset `+0x658`.
- Le déréférencement `IOLockLock(0 + 0x658)` a déclenché le Page Fault `#PF` sur `CR2: 0x0000000000000658`.
* **Résolutions Techniques Appliquées** :
1. **Séparation Stricte des Interfaces `IOWorkLoop` et `IO80211WorkQueue`** :
- Ajout d'une instance dédiée `IOWorkLoop *fWorkLoop;` allouée via `IOWorkLoop::workLoop()` dans `QualcommWifi::createWorkLoop()`.
- `QualcommWifi::getWorkLoop()` retourne `fWorkLoop ? fWorkLoop : (getProvider() ? getProvider()->getWorkLoop() : nullptr);`.
- `QualcommWifi::createWorkQueue()` et `getWorkQueue()` conservent `IO80211WorkQueue *fWorkQueue;` allouée via `IO80211WorkQueue::workQueue()`.
2. **Séquence d'Initialisation et Nettoyage** :
- Dans `QualcommWifi::start(provider)` : instanciation séquentielle de `createWorkLoop()` puis `createWorkQueue()` avant l'invocation de `super::start(provider)`.
- Dans `IONetworkController::start`, `workLoop->getThread()` invoque désormais l'implémentation native de `IOWorkLoop::getThread` (`movq 0x30(%rdi), %rax`), fournissant un `thread_t` valide à `ml_thread_policy`, et permettant l'attachement propre du `IOCommandGate` via `fWorkLoop->addEventSource`.
- Dans `QualcommWifi::stop()` et `QualcommWifi::free()` : libération propre des deux objets via `OSSafeReleaseNULL(fWorkLoop)` et `OSSafeReleaseNULL(fWorkQueue)`.