From 0809b01856a1aa426f6f8f0605c9ffe06aead54d Mon Sep 17 00:00:00 2001 From: Erwan Date: Sat, 17 Oct 2026 23:14:09 +0200 Subject: [PATCH] fix(wifi): prevent CPU triple fault and instant reboot via CCLogStream layout conforming FakeLogStream MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### 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`). --- .gitignore | 3 ++ QualcommWifi/QualcommWifi.cpp | 94 ++++++++++++++++++++++++++++------ QualcommWifi/QualcommWifi.hpp | 1 + build_wifi.sh | 2 +- docs/ISSUES_AND_POSTMORTEMS.md | 28 ++++++++++ 5 files changed, 112 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index 6bc2b1e..3503187 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,6 @@ xcuserdata/ # Compiled Kexts *.kext/ + +# Knowledge graph / Agent artifacts +graphify-out/ diff --git a/QualcommWifi/QualcommWifi.cpp b/QualcommWifi/QualcommWifi.cpp index 0c92aa8..393053f 100644 --- a/QualcommWifi/QualcommWifi.cpp +++ b/QualcommWifi/QualcommWifi.cpp @@ -17,17 +17,79 @@ extern "C" { #include #include -static char gLogBuffer[2048] = "QCOM_BOOT:"; - -static void logNvramStage(const char *tag) { - size_t curLen = strlen(gLogBuffer); - if (curLen + strlen(tag) + 2 < sizeof(gLogBuffer)) { - strcat(gLogBuffer, ">"); - strcat(gLogBuffer, tag); - } +static inline void logNvramStage(const char *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 OSDefineMetaClassAndStructors(QualcommWifi, IO80211Controller) @@ -64,8 +126,8 @@ bool QualcommWifi::init(OSDictionary *dictionary) { fDmaVirtAddr = nullptr; fDmaPrepared = false; fFirmwareLoaded = false; - fFaultReporter = OSString::withCString("QualcommFaultReporter"); - fLogger = OSString::withCString("QualcommLogger"); + fFaultReporter = FakeFaultReporter::create(); + fLogger = FakeLogStream::create(); return true; } @@ -570,11 +632,9 @@ bool QualcommWifi::start(IOService *provider) { } logNvramStage("pci_open_ok"); - UInt16 pciCommand = fPciDevice->configRead16(kIOPCIConfigCommand); - pciCommand |= kIOPCICommandMemorySpace; - pciCommand &= ~kIOPCICommandBusMaster; // Protection absolue contre tout accès DMA non configuré sur AMD-Vi - logNvramStage("pci_cmd_write"); - fPciDevice->configWrite16(kIOPCIConfigCommand, pciCommand); + // La gestion de l'espace mémoire PCIe et du Bus Master est gérée automatiquement par IOKit + // (Éviter les écritures de configuration brutes risquant de déstabiliser le root port AMD) + logNvramStage("pci_ready"); int allow_mmio = 0; PE_parse_boot_argn("qcom_mmio", &allow_mmio, sizeof(allow_mmio)); @@ -760,6 +820,10 @@ void* QualcommWifi::getFaultReporterFromDriver(void) { return (void*)fFaultReporter; } +UInt32 QualcommWifi::getActionFramePoolCapacity(void) { + return 0x100; +} + IOReturn QualcommWifi::getDRIVER_VERSION(IO80211SkywalkInterface *iface, apple80211_version_data *vd) { return (IOReturn)handleApple80211Request(APPLE80211_IOC_DRIVER_VERSION, 0, vd); } diff --git a/QualcommWifi/QualcommWifi.hpp b/QualcommWifi/QualcommWifi.hpp index a5b8194..a6c88d7 100644 --- a/QualcommWifi/QualcommWifi.hpp +++ b/QualcommWifi/QualcommWifi.hpp @@ -257,6 +257,7 @@ public: virtual IOReturn setGET_DEBUG_INFO(IO80211SkywalkInterface *iface, apple80211_debug_command *dbg) override { return kIOReturnSuccess; } virtual void* getLogger(void) const override; virtual void* getFaultReporterFromDriver(void) override; + virtual UInt32 getActionFramePoolCapacity(void) override; // Dispatcher de requêtes Apple80211 / airportd SInt32 handleApple80211Request(unsigned int req_type, int req_val, void* req_data); diff --git a/build_wifi.sh b/build_wifi.sh index bc70787..d96d262 100755 --- a/build_wifi.sh +++ b/build_wifi.sh @@ -24,7 +24,7 @@ clang++ -target x86_64-apple-macos26.5 \ -I"${SCRIPT_DIR}/QualcommWireless/include" \ -I"${SCRIPT_DIR}/QualcommWifi" \ -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 \ -c "${SCRIPT_DIR}/QualcommWifi/QualcommWifi.cpp" \ -o "${BUILD_DIR}/QualcommWifi.o" diff --git a/docs/ISSUES_AND_POSTMORTEMS.md b/docs/ISSUES_AND_POSTMORTEMS.md index a8db845..a7f2c4d 100644 --- a/docs/ISSUES_AND_POSTMORTEMS.md +++ b/docs/ISSUES_AND_POSTMORTEMS.md @@ -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. * **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`). + +--- + +### 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. +