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

+79 -15
View File
@@ -17,17 +17,79 @@ extern "C" {
#include <pexpert/pexpert.h>
#include <IOKit/IODeviceTreeSupport.h>
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);
}
+1
View File
@@ -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);