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:
1 parent
d152d81d79
commit
0809b01856
5 files changed
+112
-16
No files matched your search
@@ -23,3 +23,6 @@ xcuserdata/
|
||||
|
||||
# Compiled Kexts
|
||||
*.kext/
|
||||
|
||||
# Knowledge graph / Agent artifacts
|
||||
graphify-out/
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
+1
-1
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in new issue
Block a user