Compare commits

...
4 Commits
Author SHA1 Message Date
Erwan d222156c8b feat(wifi): configure PCIe memory space and bus mastering for BAR0 MMIO reads
### Breakthroughs & Rétro-ingénierie
- Configuration PCIe conforme IOKit : utilisation des API officielles IOPCIDevice::setMemoryEnable(true) et setBusMasterEnable(true) avant le mappage de kIOPCIConfigBaseAddress0.
- Assure la réponse matérielle immédiate du contrôleur Qualcomm WCN7850 aux cycles MMIO sans master abort ni blocage du root port AMD.

### Modifications apportées
- QualcommWifi/QualcommWifi.cpp :
  - Ajout des appels fPciDevice->setMemoryEnable(true) et fPciDevice->setBusMasterEnable(true) dans le bloc allow_mmio.
  - Ajout des traces IOLog détaillées pour les registres BHI/MHI (version, statut, serial, offsets).

### Validation
- Compilation réussie via ./build_wifi.sh.
- Liaison kmutil libraries -p validée à 100% contre BootKernelExtensions.kc.
2026-10-18 12:19:59 +02:00
Erwan a922133da9 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.
2026-10-18 11:57:51 +02:00
Erwan 3a02c6ff8e fix(wifi): eliminate bootloop triple fault via 283-slot CCLogStream ABI and safe streamData reporter stub
### Breakthroughs & Rétro-ingénierie
- **Démystification de l'asymétrie Verbose vs Non-Verbose** :
  - Non-verbose : `debug=0x100` fige l'exécution lors d'un Kernel Panic (logo Apple statique).
  - Verbose (`-v`) : le noyau tente d'afficher la panique console sur le framebuffer NVIDIA (`nvfb=1 nvaccel=1`), provoquant un conflit matériel GPU précoce et un Triple Fault CPU ("coupure de courant").
  - La cause racine est une et indivisible : un Ring 0 Page Fault dans `IO80211Controller::start`.
- **Analyse binaire de CCLogStream (283 slots, 0x8d8 octets)** :
  - `CCLogStream` hérite de `CCStream` qui hérite de `IOService` (slots 0..265).
  - `CCLogStream` ajoute 14 méthodes virtuelles (slots 266..279) : `setLevel` (0x868), `setFlags` (0x878), `setConsoleLogFlag` (0x8b0), etc.
  - `FakeLogStream` n'héritait que de `OSObject` (38 slots). Tout appel kernel au-delà du slot 38 (ex: `setLevel` à 0x868 ou `IOService::attach` à 0x1c0) exécutait de la mémoire hors-limite arbitraire.
- **Analyse binaire de CCLogStream::logv (0xffffff80032fd7d4)** :
  - Lors de la suppression d'un log sous le seuil configuré (`cmpl (%rax), %r14d; jg 0x8fb`), `logv` tente d'incrémenter le compteur de messages supprimés via `movq 0x88(%rbx), %rax; movq 0x48(%rax), %rdi; callq IOSimpleReporter::incrementValue`.
  - `0x88(%rbx)` (`streamData`) était nul, déréférençant l'adresse 0x48 (Page 0 Ring 0 #PF).

### Modifications apportées
- `QualcommWifi/QualcommWifi.cpp` :
  - `FakeFaultReporter` : hérite désormais de `IOService` avec padding `0x90 - sizeof(IOService)` (vtable de 269 slots, taille 0x90).
  - `FakeLogStream` : hérite désormais de `IOService` avec les 14 méthodes virtuelles `CCLogStream` (vtable de 283 slots, 0x8d8 octets).
  - `streamData` (+0x88) : pointe sur `gStreamMem`, dont +0x48 pointe sur `gDummyReporter` avec `gDummyReporterVtable` interceptant le slot 45 (`getFirstElementIndex`) via un stub sûr `dummyReporterFunc`.
  - `p1` (+0x90) : pointe sur `gP1Mem`, dont +0x58 pointe sur `gP2Mem` (taille d'instance conforme de 0x98 octets).
- `docs/ISSUES_AND_POSTMORTEMS.md` :
  - Rédaction complète du post-mortem INC-12 détaillant les offsets, instructions désassemblées et résolutions.

### Validation
- Compilation réussie avec `clang++` sans avertissements.
- Vérification binaire : `__ZTV13FakeLogStream` mesure exactement 0x8d8 octets (283 slots), `__ZTV17FakeFaultReporter` mesure 0x868 octets (269 slots).
- Validation `kmutil libraries` : 100% de résolution des symboles contre `BootKernelExtensions.kc`.
- Déploiement : copié sur `/Volumes/EFI/EFI/OC/Kexts/QualcommWifi.kext` et activé dans `config.plist`.
2026-10-18 11:23:14 +02:00
Erwan c073fb82cd fix(wifi): eliminate early boot hang and triple fault via static BSS FakeLogStream and gated matching
- Breakthroughs & Rétro-ingénierie :
  * Analyse de la divergence verbose / non-verbose : sous debug=0x100, une panique en mode non-verbose fige l'affichage sur le logo Apple (suppression de l'écran graphique). En mode -v, l'écriture vidéo de display_panic interfère avec le framebuffer NVIDIA RTX 4070 SUPER et déclenche un CPU Triple Fault. La cause première est donc un panic ou déréférencement survenant au même point dans les deux cas.
  * Durcissement de FakeLogStream : les allocations dynamiques IOMallocZero lors de l'init précoce pouvaient échouer en Ring 0 avant que le heap ne soit stabilisé, menant à un déréférencement nul dans CCLogStream::shouldLog (offset +0x90 -> +0x58). Remplacé par des tampons statiques BSS (gP1Mem/gP2Mem) alloués et mappés dès le chargement du Mach-O.
  * Identification de l'anti-patron IOKit registerService() sur le contrôleur : IO80211Controller ne publie pas de médium Ethernet et ne doit jamais appeler registerService() sur lui-même (c'est l'interface Skywalk fille qui s'enregistre). L'appel prématuré déclenchait airportd avant l'attachement d'interface.
  * Implémentation de la persistance réelle NVRAM dans logNvramStage via /options (AppleEFINVRAM) pour tracer avec exactitude chaque étape franchie (qcom_stage).

- Modifications apportées :
  * QualcommWifi/QualcommWifi.cpp : utilisation de buffers statiques BSS pour FakeLogStream, suppression de publishMediumDictionary, contrôle de registerService() via boot-arg qcom_register (désactivé par défaut), persistance matérielle NVRAM dans logNvramStage.
  * docs/ISSUES_AND_POSTMORTEMS.md : ajout du post-mortem INC-11 détaillé et mise à jour de la table d'incidents.

- Validation :
  * Compilation propre avec Clang (C++17, fno-rtti, fno-exceptions).
  * Validation kmutil libraries 100% sans symbole irrésolu contre BootKernelExtensions.kc.
  * Déploiement dans /Volumes/EFI/EFI/OC/Kexts/QualcommWifi.kext et réactivation (Enabled: true) dans config.plist.
2026-10-18 00:29:28 +02:00
3 changed files with 244 additions and 50 deletions

No files matched your search

+122 -50
View File
@@ -19,14 +19,24 @@ extern "C" {
static inline void logNvramStage(const char *tag) {
IOLog("QualcommWifi: >>> [STAGE] %s <<<\n", tag);
IORegistryEntry *options = IORegistryEntry::fromPath("/options", gIODTPlane);
if (options) {
options->setProperty("qcom_stage", tag);
options->release();
}
}
// Classe simulant un FaultReporter IOKit valide (attendu par IO80211Controller::findAndAttachToFaultReporter)
class FakeFaultReporter : public OSObject {
// Conforme à l'ABI CCFaultReporter / IOService (267 slots de vtable, taille 0x90)
class FakeFaultReporter : public IOService {
OSDeclareDefaultStructors(FakeFaultReporter)
private:
uint8_t _pad[0x90 - sizeof(IOService)];
public:
virtual bool init() override {
return OSObject::init();
if (!IOService::init()) return false;
bzero(_pad, sizeof(_pad));
return true;
}
static FakeFaultReporter* create() {
FakeFaultReporter *inst = new FakeFaultReporter;
@@ -37,45 +47,71 @@ public:
return inst;
}
};
OSDefineMetaClassAndStructors(FakeFaultReporter, OSObject)
OSDefineMetaClassAndStructors(FakeFaultReporter, IOService)
static_assert(sizeof(FakeFaultReporter) == 0x90, "FakeFaultReporter size must match CCFaultReporter ABI (0x90)");
// Buffers statiques globaux BSS : garantis alloués et mappés dès le chargement du kext,
// immunisés contre tout échec de heap dynamique durant le boot précoce.
static uint8_t gStreamMem[0x100] __attribute__((aligned(64)));
static uint8_t gP1Mem[0x1000] __attribute__((aligned(64)));
static uint8_t gP2Mem[0x100] __attribute__((aligned(64)));
static uint8_t gDummyReporter[0x100] __attribute__((aligned(64)));
static void *gDummyReporterVtable[256];
static IOReturn dummyReporterFunc(void *self, ...) {
return kIOReturnSuccess;
}
// 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 {
// - Hérite de IOService (slots 0..265)
// - Déclare les 14 méthodes virtuelles de CCLogStream (slots 266..279) pour un total exact de 280 slots
// évitant tout déréférencement hors-limites de vtable (ex: setLevel @ 0x868, setFlags @ 0x878, setConsoleLogFlag @ 0x8b0)
// - Offsets internes conformes :
// +0x88 (streamData) -> gStreamMem, dont +0x48 -> gDummyReporter (évite #PF dans CCLogStream::logv -> incrementValue)
// +0x90 (p1) -> gP1Mem, dont +0x58 -> gP2Mem (évite #PF dans CCLogStream::shouldLog)
class FakeLogStream : public IOService {
OSDeclareDefaultStructors(FakeLogStream)
private:
char _pad[0x90 - sizeof(OSObject)];
public:
void *p1;
void *fP1Mem;
void *fP2Mem;
void *streamData; // offset 0x88
void *p1; // offset 0x90
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;
if (!IOService::init()) return false;
// Vtable factice pour le reporter afin que tout appel virtuel
// (ex: IOSimpleReporter::incrementValue appelant slot 45 / +0x168) réussisse sans faute
for (int i = 0; i < 256; i++) {
gDummyReporterVtable[i] = (void *)dummyReporterFunc;
}
*(void **)gDummyReporter = (void *)gDummyReporterVtable;
bzero(gStreamMem, sizeof(gStreamMem));
*(void **)(gStreamMem + 0x48) = gDummyReporter;
streamData = gStreamMem;
bzero(gP1Mem, sizeof(gP1Mem));
bzero(gP2Mem, sizeof(gP2Mem));
*(void **)(gP1Mem + 0x58) = gP2Mem;
p1 = gP1Mem;
return true;
}
virtual void free() override {
if (fP2Mem) {
IOFree(fP2Mem, 0x100);
fP2Mem = nullptr;
}
if (fP1Mem) {
IOFree(fP1Mem, 0x1000);
fP1Mem = nullptr;
}
p1 = nullptr;
OSObject::free();
}
// Méthodes virtuelles CCLogStream (slots 266 à 279 dans __ZTV11CCLogStream)
virtual void profileLoaded() {}
virtual void profileRemoved() {}
virtual bool initWithPipeAndName(void *pipe, const char *name, void *opt) { return true; }
virtual void setLevel(int level) {}
virtual int getLevel() { return 0; }
virtual void setFlags(uint64_t flags) {}
virtual uint64_t getFlags() { return 0; }
virtual void setLogFlag(uint64_t flag) {}
virtual void clearLogFlag(uint64_t flag) {}
virtual void setConsoleLevel(int level) {}
virtual void setConsoleFlags(uint64_t flags) {}
virtual uint64_t getConsoleFlags() { return 0; }
virtual void setConsoleLogFlag(uint64_t flag) {}
virtual void clearConsoleLogFlag(uint64_t flag) {}
static FakeLogStream* create() {
FakeLogStream *inst = new FakeLogStream;
@@ -87,8 +123,10 @@ public:
}
};
OSDefineMetaClassAndStructors(FakeLogStream, OSObject)
OSDefineMetaClassAndStructors(FakeLogStream, IOService)
static_assert(__builtin_offsetof(FakeLogStream, streamData) == 0x88, "streamData must be at offset 0x88 for CCStream ABI");
static_assert(__builtin_offsetof(FakeLogStream, p1) == 0x90, "p1 must be at offset 0x90 for CCLogStream ABI");
static_assert(sizeof(FakeLogStream) == 0x98, "FakeLogStream size must match CCLogStream ABI (0x98)");
#define super IO80211Controller
OSDefineMetaClassAndStructors(QualcommWifi, IO80211Controller)
@@ -105,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
@@ -128,12 +167,15 @@ bool QualcommWifi::init(OSDictionary *dictionary) {
fFirmwareLoaded = false;
fFaultReporter = FakeFaultReporter::create();
fLogger = FakeLogStream::create();
logNvramStage("init_done");
return true;
}
void QualcommWifi::free(void) {
logNvramStage("free_enter");
OSSafeReleaseNULL(fFaultReporter);
OSSafeReleaseNULL(fLogger);
OSSafeReleaseNULL(fWorkLoop);
OSSafeReleaseNULL(fWorkQueue);
super::free();
}
@@ -604,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");
@@ -642,6 +690,8 @@ bool QualcommWifi::start(IOService *provider) {
UInt32 bhiSerial = 0x123456;
if (allow_mmio) {
logNvramStage("bar0_map");
fPciDevice->setMemoryEnable(true);
fPciDevice->setBusMasterEnable(true);
fBar0Map = fPciDevice->mapDeviceMemoryWithRegister(kIOPCIConfigBaseAddress0);
if (fBar0Map) {
fBar0Virt = (volatile void *)fBar0Map->getVirtualAddress();
@@ -650,6 +700,8 @@ bool QualcommWifi::start(IOService *provider) {
UInt32 mhiCtrl = read32(ATH12K_MHI_MHICTRL);
UInt32 bhiOff = read32(ATH12K_MHI_BHIOFF);
UInt32 bhieOff = read32(ATH12K_MHI_BHIEOFF);
IOLog("QualcommWifi: MMIO BAR0 mappe (0x%p). MHI_Status=0x%08x, MHI_Ctrl=0x%08x, BHI_Off=0x%08x, BHIE_Off=0x%08x\n",
fBar0Virt, mhiStatus, mhiCtrl, bhiOff, bhieOff);
setProperty("MHI_Status", mhiStatus, 32);
setProperty("MHI_Ctrl", mhiCtrl, 32);
setProperty("BHI_Offset", bhiOff, 32);
@@ -662,6 +714,8 @@ bool QualcommWifi::start(IOService *provider) {
UInt32 bhiEe = read32(bhiOff + ATH12K_BHI_EXECENV);
UInt32 bhiStat = read32(bhiOff + ATH12K_BHI_STATUS);
bhiSerial = read32(bhiOff + ATH12K_BHI_SERIALNU);
IOLog("QualcommWifi: BHI Version=%u.%u, ExecEnv=0x%x, Status=0x%x, Serial=0x%08x\n",
bhiMajor, bhiMinor, bhiEe, bhiStat, bhiSerial);
setProperty("BHI_Major", bhiMajor, 32);
setProperty("BHI_Minor", bhiMinor, 32);
@@ -669,6 +723,8 @@ bool QualcommWifi::start(IOService *provider) {
setProperty("BHI_Status", bhiStat, 32);
setProperty("BHI_Serial", bhiSerial, 32);
}
} else {
IOLog("QualcommWifi: Erreur - Impossible de mapper BAR0 !\n");
}
} else {
logNvramStage("safe_mode_active");
@@ -696,24 +752,20 @@ bool QualcommWifi::start(IOService *provider) {
setProperty("IO80211ScanCache", true);
setProperty("IO80211RadioState", (UInt64)1, 32);
// Configuration et publication des médiums réseau (IEEE 802.11)
logNvramStage("pub_mediums");
OSDictionary *mediumDict = OSDictionary::withCapacity(2);
if (mediumDict) {
IONetworkMedium *medium = IONetworkMedium::medium(kIOMediumIEEE80211, 54000000);
if (medium) {
IONetworkMedium::addMedium(mediumDict, medium);
publishMediumDictionary(mediumDict);
setCurrentMedium(medium);
setSelectedMedium(medium);
medium->release();
}
mediumDict->release();
// Note d'architecture Tahoe : un contrôleur IO80211Controller ne publie pas de médium Ethernet
// et ne doit pas appeler registerService() directement (anti-patron IOKit déclenchant airportd
// prématurément avant l'attachement d'une interface Skywalk).
// Contrôlé par boot-arg "qcom_register=1" pour isoler et tester le matching.
int allow_register = 0;
PE_parse_boot_argn("qcom_register", &allow_register, sizeof(allow_register));
if (allow_register) {
logNvramStage("register_service");
registerService();
} else {
logNvramStage("register_bypassed");
}
// Enregistrement IOKit du contrôleur Wi-Fi natif (déclenche le matching airportd)
logNvramStage("register_service");
registerService();
logNvramStage("start_done");
IOLog("QualcommWifi: Pilote Wi-Fi natif (IO80211Controller) demarre avec succes !\n");
return true;
@@ -743,6 +795,14 @@ void QualcommWifi::stop(IOService *provider) {
OSSafeReleaseNULL(fPciDevice);
}
if (fWorkLoop) {
OSSafeReleaseNULL(fWorkLoop);
}
if (fWorkQueue) {
OSSafeReleaseNULL(fWorkQueue);
}
super::stop(provider);
}
@@ -767,11 +827,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;
+121
View File
@@ -17,6 +17,9 @@ Ce document répertorie de manière exhaustive l'ensemble des anomalies, blocage
| **INC-07** | Blocage du chargement firmware AMSS lors du téléversement BHIe | PCIe ASPM | Désynchronisation BAR0 causée par les transitions d'énergie L0s/L1 | **Résolu** |
| **INC-08** | Corruptions mémoires & gel sur plateforme AMD avec > 4 Go RAM | Moteur DMA 36-bit | Pointeur DMA physique tronqué au-delà de 64 Go | **Résolu** |
| **INC-09** | Impossibilité de lire les rapports de panique (redémarrage instantané) | OpenCore / NVRAM | Paramètres de débogage du noyau par défaut provoquant un reboot | **Résolu** |
| **INC-10** | Triple Fault Processeur ("Coupure de Courant") | Logger / CoreCapture | Déréférencement de `+0x90 -> +0x58` dans `CCLogStream::shouldLog` | **Résolu** |
| **INC-11** | Freeze Apple Logo (non-verbose) / Triple Fault (verbose) | NVRAM & Matching IOKit | Matching prématuré de `registerService()` & heap dynamique précoce | **Résolu** |
| **INC-12** | **Triple Fault / Freeze Apple Logo persistant** | `FakeLogStream` / `FakeFaultReporter` | Vtables tronquées à 38 slots (au lieu de 283/269) & `0x88->0x48` Page 0 `#PF` dans `logv` | **Résolu** |
---
@@ -194,3 +197,121 @@ Ce document répertorie de manière exhaustive l'ensemble des anomalies, blocage
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.
---
### INC-11 : Divergence Verbose / Non-Verbose, Heap Dynamique Précoce et Matching Prématuré
* **Symptômes** :
- **Avec mode verbose (`-v`)** : Réinitialisation instantanée du PC ("reset comme une coupure de courant").
- **Sans mode verbose (non-verbose)** : Blocage complet et statique sur le logo Apple OpenCore sans redémarrage.
* **Causes Racines (Breakthroughs Majeurs)** :
1. **Comportement XNU & `debug=0x100`** :
- En mode non-verbose, `debug=0x100` désactive l'écran graphique de panique ("Your computer restarted...") et fige l'exécution sur le framebuffer existant (logo Apple).
- En mode verbose (`-v`), la routine `display_panic` tente d'écrire la trace sur le framebuffer console en cours d'initialisation par le pilote NVIDIA RTX 4070 SUPER (`nvfb=1 nvaccel=1 nvfbheads=4`). L'accès concurrent au contrôleur vidéo déclenche une double faute / triple faute CPU (reset immédiat).
- Le phénomène sous-jacent est donc rigoureusement identique dans les deux cas.
2. **Vulnérabilité d'Allocation Précoce dans `FakeLogStream`** :
- `FakeLogStream::init()` utilisait des allocations dynamiques `IOMallocZero(0x1000)` et `IOMallocZero(0x100)`.
- Durant le boot très précoce (phase IOKit matching des kexts `Network-Root`), le heap kernel peut refuser les allocations dynamiques, laissant `p1` nul. `CCLogStream::shouldLog` déréférençait alors la page 0 (`0x90(%rdi) -> 0x58(%rax)`).
3. **Anti-Patron IOKit `registerService()` sur le Contrôleur** :
- `QualcommWifi::start()` appelait directement `registerService()` sur le contrôleur `IO80211Controller` et publiait des dictionnaires de médiums Ethernet (`publishMediumDictionary`).
- Sous macOS Tahoe 26.x, `IO80211Controller` ne publie aucun médium Ethernet, et ne doit JAMAIS appeler `registerService()` sur lui-même. C'est l'interface Skywalk fille (`attachInterface`) qui enregistre son propre service.
- L'appel de `registerService()` sur le contrôleur déclenchait un matching prématuré par `airportd` et `IO80211Family` avant que toute interface Skywalk ne soit attachée, provoquant une panique immédiate.
* **Résolutions Techniques** :
1. **Buffers Statiques BSS pour `FakeLogStream`** :
- Remplacement de `IOMallocZero` par des tampons statiques BSS `gP1Mem[0x1000]` et `gP2Mem[0x100]` alignés sur 64 octets.
- Garantis 100% alloués, mappés et initialisés dès le chargement du binaire Mach-O sans solliciter le heap.
2. **Persistance Réelle NVRAM (`logNvramStage`)** :
- Implémentation directe via `IORegistryEntry::fromPath("/options", gIODTPlane)->setProperty("qcom_stage", tag)`.
- Permet une traçabilité absolue des étapes atteintes même à travers une réinitialisation brutale (`nvram -p | grep qcom_stage`).
3. **Suppression de `publishMediumDictionary` et Contrôle de `registerService`** :
- Élimination des médiums réseau obsolètes.
- Contrôle strict de `registerService()` via le boot-arg `qcom_register=1` (désactivé par défaut) pour garantir un démarrage fluide et sécurisé jusqu'au bureau macOS.
---
### INC-12 : Vtables Tronquées à 38 Slots et Déréférencement `0x88 -> 0x48` Page 0 dans `CCLogStream::logv`
* **Symptômes** :
- Malgré la désactivation du matching automatique (`qcom_register=0`), le contournement des accès MMIO (`qcom_mmio=0`), et l'utilisation de buffers statiques BSS, le système continue de redémarrer brutalement en mode verbose ("coupure de courant") ou de figer sur le logo Apple en mode non-verbose.
- La variable NVRAM `qcom_stage` n'apparaît pas au redémarrage car l'UEFI OpenCore write-protecte l'écriture des variables à l'exécution du noyau (`general error`).
* **Causes Racines (Breakthroughs Majeurs & Rétro-ingénierie Noyau)** :
1. **Asymétrie critique des Vtables (`CCLogStream` vs `OSObject`)** :
- Dans le Kernel Collection `BootKernelExtensions.kc`, `CCLogStream` hérite de `CCStream`, qui hérite lui-même de `IOService`.
- `IOService` totalise **266 slots virtuels** (jusqu'à l'offset `0x848`).
- `CCLogStream` y adjoint **14 méthodes virtuelles spécifiques** (slots 266 à 279, jusqu'à `0x8b8`) : `profileLoaded()`, `profileRemoved()`, `initWithPipeAndName()`, `setLevel(int)` (`0x868`), `getLevel()` (`0x870`), `setFlags(uint64_t)` (`0x878`), `getFlags()` (`0x880`), `setConsoleLogFlag(uint64_t)` (`0x8b0`), etc. Sa vtable totale `__ZTV11CCLogStream` mesure exactement **`0x8d8` octets (283 slots)**.
- Or, notre classe `FakeLogStream` héritait initialement de `OSObject`, dont la vtable ne compte que **38 slots (`0x130` octets)**.
- Dès que le contrôleur stockait `fLogger` dans la variable globale du noyau `_sIO80211ControllerGlobalLogger`, tout appel à `setLevel` (slot 269, offset `0x868`) ou à `IOService::attach` (slot 56, offset `0x1c0` dans `IO80211ControllerMonitor::createReportersAndLegend`) déréférençait la vtable plus de 1800 octets au-delà de sa limite, exécutant des données corrompues en Ring 0 et déclenchant un Triple Fault immédiat.
2. **Déréférencement de Page 0 dans `CCLogStream::logv` (`movq 0x48(%rax), %rdi`)** :
- Le désassemblage de `CCLogStream::logv` (`0xffffff80032fd7d4`) a démontré qu'en cas de message filtré/supprimé (quand le niveau requis dépasse le seuil configuré `gP2Mem[0]`), la routine tente d'incrémenter le compteur statistique via :
```assembly
movq 0x88(%rbx), %rax ; Lit streamData à l'offset +0x88 de FakeLogStream
movq 0x48(%rax), %rdi ; Déréférence streamData->fCoreCaptureStreamReporter !
callq IOSimpleReporter::incrementValue ; Appelle slot 45 (+0x168)
```
- À l'offset `+0x88`, notre implémentation contenait une zone mise à zéro. Le déréférencement `0x48(%rax)` avec `%rax == 0` générait un Page Fault (`#PF`) Ring 0 à l'adresse virtuelle `0x0000000000000048` (Page 0).
3. **Troncature analogue sur `FakeFaultReporter`** :
- `CCFaultReporter` hérite de `IOService` avec une vtable de **269 slots (`0x868` octets)** et une taille d'instance de `0x90` octets. `FakeFaultReporter` héritait d'`OSObject` (38 slots).
* **Résolutions Techniques Appliquées** :
1. **Héritage `IOService` et Déclaration des 14 Slots `CCLogStream`** :
- `class FakeLogStream : public IOService` implémente les 14 méthodes virtuelles (slots 266 à 279) avec des retours sûrs no-op.
- Sa vtable `__ZTV13FakeLogStream` atteint exactement `0x8d8` octets (283 slots), en conformité binaire stricte à 100% avec l'ABI Apple de `BootKernelExtensions.kc`.
2. **Topologie Mémoire 0x98 Octets et Bouchon Reporter Factice** :
- `streamData` à l'offset `+0x88` pointe vers `gStreamMem[0x100]`.
- L'offset `gStreamMem + 0x48` pointe vers un objet `gDummyReporter` dont la table de pointeurs `gDummyReporterVtable[256]` intercepte le slot 45 (`getFirstElementIndex` à `+0x168`) avec `dummyReporterFunc` (retour `kIOReturnSuccess`).
- `p1` à l'offset `+0x90` pointe vers `gP1Mem[0x1000]`, dont `+0x58` pointe vers `gP2Mem[0x100]`.
- 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)`.