fix(wifi): prevent CPU triple fault and instant reboot via CCLogStream layout conforming FakeLogStream

### Breakthroughs & Rétro-ingénierie
- Analyse forensique du redémarrage instantané ("coupure de courant" sans log) survenu au commit 5357ca6 :
  1. Le désassemblage de `CCLogStream::shouldLog` (0xffffff80032ff9e6) dans `BootKernelExtensions.kc` a révélé un déréférencement en cascade non protégé : `movq 0x90(%rdi), %rax` puis `movq 0x58(%rax), %rax`.
  2. `fLogger` retournant un `OSString*` de seulement 32 octets (0x20), l'accès à +0x90 lisait une adresse arbitraire du tas kernel dont le déréférencement en Ring 0 déclenchait un #PF/#GP transformé en Triple Fault CPU immédiat (reset matériel sans panic handler).
  3. Parallèlement, `IO80211RNGAgent::withOptions` et `IO80211ControllerMonitor::withControllerAndProvider` requièrent impérativement un pointeur non-nul et invoquent `logger->retain()` (*0x20(%rax)).
  4. La résolution consiste en une classe `FakeLogStream : public OSObject` respectant au millimètre l'ABI Apple : un pointeur `p1` placé à l'offset strict +0x90 (validé par `static_assert`) pointant vers une zone mémoire `fP1Mem` dont l'offset +0x58 pointe vers `fP2Mem` initialisé à zéro. `shouldLog` et `log*` retournent ainsi false sans aucun déréférencement illégal et sans risquer d'écrire sur un pipe non configuré.

### Modifications apportées
- `QualcommWifi/QualcommWifi.cpp` :
  - Définition et enregistrement IOKit de `FakeFaultReporter` et `FakeLogStream` avec méthodes usine `create()`.
  - Initialisation de `fFaultReporter = FakeFaultReporter::create()` et `fLogger = FakeLogStream::create()` dans `init()`.
  - Implémentation locale de `QualcommWifi::getActionFramePoolCapacity()` (slot 440) renvoyant 0x100 (256 frames).
  - Suppression de l'écriture directe `kIOPCIConfigCommand` sur le bus PCIe AMD pendant `start()` afin d'éviter tout conflit de bridge ou transaction d'arbitrage invalidée.
  - Nettoyage du buffer statique non-thread-safe dans `logNvramStage`.
- `QualcommWifi/QualcommWifi.hpp` :
  - Déclaration de `virtual UInt32 getActionFramePoolCapacity(void) override`.
- `build_wifi.sh` :
  - Ajout du flag `-Wno-invalid-offsetof` pour compilation propre de l'assertion ABI.
- `docs/ISSUES_AND_POSTMORTEMS.md` :
  - Documentation détaillée du post-mortem INC-10 expliquant le mécanisme de triple fault et la solution apportée.

### Validation
- Compilation réussie avec 0 erreur.
- Vérification de la vtable `__ZTV12QualcommWifi` : taille exacte de 3728 octets (0xe90, 466 slots) conforme à OpenCore et Apple `IO80211Controller`.
- Validation de conformité des symboles et dépendances à 100% via `kmutil libraries`.
- Déploiement du kext compilé dans `/Volumes/EFI/EFI/OC/Kexts/QualcommWifi.kext`.
- Activation confirmée dans `/Volumes/EFI/EFI/OC/config.plist` (`Enabled = True`).
This commit is contained in:
Erwan committed 2026-10-17 23:14:09 +02:00
1 parent d152d81d79
commit 0809b01856
5 files changed
+112 -16

No files matched your search

+3
View File
@@ -23,3 +23,6 @@ xcuserdata/
# Compiled Kexts # Compiled Kexts
*.kext/ *.kext/
# Knowledge graph / Agent artifacts
graphify-out/
+79 -15
View File
@@ -17,17 +17,79 @@ extern "C" {
#include <pexpert/pexpert.h> #include <pexpert/pexpert.h>
#include <IOKit/IODeviceTreeSupport.h> #include <IOKit/IODeviceTreeSupport.h>
static char gLogBuffer[2048] = "QCOM_BOOT:"; static inline void logNvramStage(const char *tag) {
static void logNvramStage(const char *tag) {
size_t curLen = strlen(gLogBuffer);
if (curLen + strlen(tag) + 2 < sizeof(gLogBuffer)) {
strcat(gLogBuffer, ">");
strcat(gLogBuffer, tag);
}
IOLog("QualcommWifi: >>> [STAGE] %s <<<\n", tag); IOLog("QualcommWifi: >>> [STAGE] %s <<<\n", tag);
} }
// Classe simulant un FaultReporter IOKit valide (attendu par IO80211Controller::findAndAttachToFaultReporter)
class FakeFaultReporter : public OSObject {
OSDeclareDefaultStructors(FakeFaultReporter)
public:
virtual bool init() override {
return OSObject::init();
}
static FakeFaultReporter* create() {
FakeFaultReporter *inst = new FakeFaultReporter;
if (inst && !inst->init()) {
inst->release();
return nullptr;
}
return inst;
}
};
OSDefineMetaClassAndStructors(FakeFaultReporter, OSObject)
// Classe de simulation binaire conforme pour CCLogStream (CoreCapture) sous macOS Tahoe
// Évite tout déréférencement invalide dans CCLogStream::shouldLog (offset +0x90 -> +0x58 -> level/flags)
class FakeLogStream : public OSObject {
OSDeclareDefaultStructors(FakeLogStream)
private:
char _pad[0x90 - sizeof(OSObject)];
public:
void *p1;
void *fP1Mem;
void *fP2Mem;
virtual bool init() override {
if (!OSObject::init()) return false;
bzero(_pad, sizeof(_pad));
fP1Mem = IOMallocZero(0x1000);
fP2Mem = IOMallocZero(0x100);
if (fP1Mem && fP2Mem) {
*(void **)((uint8_t *)fP1Mem + 0x58) = fP2Mem;
p1 = fP1Mem;
} else {
p1 = nullptr;
}
return true;
}
virtual void free() override {
if (fP2Mem) {
IOFree(fP2Mem, 0x100);
fP2Mem = nullptr;
}
if (fP1Mem) {
IOFree(fP1Mem, 0x1000);
fP1Mem = nullptr;
}
p1 = nullptr;
OSObject::free();
}
static FakeLogStream* create() {
FakeLogStream *inst = new FakeLogStream;
if (inst && !inst->init()) {
inst->release();
return nullptr;
}
return inst;
}
};
OSDefineMetaClassAndStructors(FakeLogStream, OSObject)
static_assert(__builtin_offsetof(FakeLogStream, p1) == 0x90, "p1 must be at offset 0x90 for CCLogStream ABI");
#define super IO80211Controller #define super IO80211Controller
OSDefineMetaClassAndStructors(QualcommWifi, IO80211Controller) OSDefineMetaClassAndStructors(QualcommWifi, IO80211Controller)
@@ -64,8 +126,8 @@ bool QualcommWifi::init(OSDictionary *dictionary) {
fDmaVirtAddr = nullptr; fDmaVirtAddr = nullptr;
fDmaPrepared = false; fDmaPrepared = false;
fFirmwareLoaded = false; fFirmwareLoaded = false;
fFaultReporter = OSString::withCString("QualcommFaultReporter"); fFaultReporter = FakeFaultReporter::create();
fLogger = OSString::withCString("QualcommLogger"); fLogger = FakeLogStream::create();
return true; return true;
} }
@@ -570,11 +632,9 @@ bool QualcommWifi::start(IOService *provider) {
} }
logNvramStage("pci_open_ok"); logNvramStage("pci_open_ok");
UInt16 pciCommand = fPciDevice->configRead16(kIOPCIConfigCommand); // La gestion de l'espace mémoire PCIe et du Bus Master est gérée automatiquement par IOKit
pciCommand |= kIOPCICommandMemorySpace; // (Éviter les écritures de configuration brutes risquant de déstabiliser le root port AMD)
pciCommand &= ~kIOPCICommandBusMaster; // Protection absolue contre tout accès DMA non configuré sur AMD-Vi logNvramStage("pci_ready");
logNvramStage("pci_cmd_write");
fPciDevice->configWrite16(kIOPCIConfigCommand, pciCommand);
int allow_mmio = 0; int allow_mmio = 0;
PE_parse_boot_argn("qcom_mmio", &allow_mmio, sizeof(allow_mmio)); PE_parse_boot_argn("qcom_mmio", &allow_mmio, sizeof(allow_mmio));
@@ -760,6 +820,10 @@ void* QualcommWifi::getFaultReporterFromDriver(void) {
return (void*)fFaultReporter; return (void*)fFaultReporter;
} }
UInt32 QualcommWifi::getActionFramePoolCapacity(void) {
return 0x100;
}
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);
} }
+1
View File
@@ -257,6 +257,7 @@ public:
virtual IOReturn setGET_DEBUG_INFO(IO80211SkywalkInterface *iface, apple80211_debug_command *dbg) override { return kIOReturnSuccess; } virtual IOReturn setGET_DEBUG_INFO(IO80211SkywalkInterface *iface, apple80211_debug_command *dbg) override { return kIOReturnSuccess; }
virtual void* getLogger(void) const override; virtual void* getLogger(void) const override;
virtual void* getFaultReporterFromDriver(void) override; virtual void* getFaultReporterFromDriver(void) override;
virtual UInt32 getActionFramePoolCapacity(void) override;
// 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);
+1 -1
View File
@@ -24,7 +24,7 @@ clang++ -target x86_64-apple-macos26.5 \
-I"${SCRIPT_DIR}/QualcommWireless/include" \ -I"${SCRIPT_DIR}/QualcommWireless/include" \
-I"${SCRIPT_DIR}/QualcommWifi" \ -I"${SCRIPT_DIR}/QualcommWifi" \
-std=c++17 -fno-builtin -fno-common -mkernel -fno-rtti -fno-exceptions \ -std=c++17 -fno-builtin -fno-common -mkernel -fno-rtti -fno-exceptions \
-fno-stack-protector -fno-strict-aliasing -Os \ -fno-stack-protector -fno-strict-aliasing -Wno-invalid-offsetof -Os \
-DKERNEL -D__IO80211_TARGET=260000 -DAPPLE_KEXT_OVERRIDE=override \ -DKERNEL -D__IO80211_TARGET=260000 -DAPPLE_KEXT_OVERRIDE=override \
-c "${SCRIPT_DIR}/QualcommWifi/QualcommWifi.cpp" \ -c "${SCRIPT_DIR}/QualcommWifi/QualcommWifi.cpp" \
-o "${BUILD_DIR}/QualcommWifi.o" -o "${BUILD_DIR}/QualcommWifi.o"
+28
View File
@@ -166,3 +166,31 @@ Ce document répertorie de manière exhaustive l'ensemble des anomalies, blocage
Par défaut, le noyau XNU sous OpenCore réinitialise le processeur en cas de panique non interceptée. Par défaut, le noyau XNU sous OpenCore réinitialise le processeur en cas de panique non interceptée.
* **Résolution** : * **Résolution** :
Injection de `panic=0` dans les `boot-args` OpenCore du fichier `config.plist`. En cas d'erreur critique, le noyau gèle l'affichage, permettant de lire textuellement le fichier source et la ligne fautive (ex: `IO80211Controller.cpp:2877`). Injection de `panic=0` dans les `boot-args` OpenCore du fichier `config.plist`. En cas d'erreur critique, le noyau gèle l'affichage, permettant de lire textuellement le fichier source et la ligne fautive (ex: `IO80211Controller.cpp:2877`).
---
### INC-10 : Triple Fault Processeur ("Coupure de Courant") lors de l'appel `CCLogStream::shouldLog` sur `fLogger`
* **Symptôme** :
Lors du démarrage d'OpenCore avec `QualcommWifi.kext` actif, le PC s'éteint et redémarre instantanément comme une coupure de courant brutale, sans panic log ni rapport d'erreur NVRAM.
* **Cause Racine (Breakthrough Fondamental)** :
1. Lors du commit `5357ca6`, `getLogger()` avait été implémenté en renvoyant une instance d'`OSString` (`OSString::withCString("QualcommLogger")`).
2. Lors de l'exécution de `IO80211Controller::start()` -> `createIOReporters()` -> `addReporterLegend()`, le noyau macOS invoque `CCLogStream::shouldLog(this->getLogger(), level)` (`0xffffff80032ff9e6`).
3. Le désassemblage de `CCLogStream::shouldLog` a révélé :
```assembly
movq 0x90(%rdi), %rax ; Lit l'offset +0x90 de l'objet passé en rdi (fLogger)
movq 0x58(%rax), %rax ; Déréférence ce pointeur en Ring 0 !
cmpl 0x8(%rax), %esi ; Compare le niveau de log
setle %al
retq
```
4. L'objet `OSString` ne mesurant que 32 octets (`0x20`), l'accès à `+0x90` pointait sur une adresse mémoire arbitraire ou non mappée dans le heap kernel. Le déréférencement immédiat de cette valeur via `movq 0x58(%rax), %rax` déclenchait une faute de page (`#PF`) ou une protection générale (`#GP`) non interceptée en mode superviseur (Ring 0), provoquant un Triple Fault CPU réinitialisant la carte mère instantanément.
5. Parallèlement, `IO80211RNGAgent::withOptions()` (`0xffffff80022dd55c`) et `IO80211ControllerMonitor::initWithControllerAndProvider()` (`0xffffff80023929d0`) requièrent impérativement que `getLogger()` retourne un pointeur non-nul et appellent `logger->retain()` (`*0x20(%rax)`).
* **Résolution Technique** :
1. Conception d'une classe `FakeLogStream : public OSObject` respectant au millimètre l'ABI de `CCLogStream` :
- Taille minimale supérieure à `0x98` octets avec `p1` placé exactement à l'offset `+0x90` (validé par `static_assert(__builtin_offsetof(FakeLogStream, p1) == 0x90)`).
- `p1` pointe vers une zone mémoire `fP1Mem` (`0x1000` octets) dont l'offset `+0x58` pointe vers `fP2Mem` (`0x100` octets initialisés à 0).
- `CCLogStream::shouldLog` et `CCLogStream::log*` lisent ainsi `0x90 -> 0x58 -> 0`, constatent un niveau de log nul, et retournent proprement `false` sans tenter d'émettre sur un pipe inexistant.
- `FakeLogStream` étant un `OSObject`, les appels `retain()` et `release()` effectués par `RNGAgent` s'exécutent avec une fidélité parfaite.
2. Implémentation locale de `QualcommWifi::getActionFramePoolCapacity()` (slot 440) renvoyant `0x100` (256 frames).
3. Suppression de l'écriture directe `kIOPCIConfigCommand` sur le bus PCIe AMD pour éviter toute déstabilisation du root port.