Compare commits
4
Commits
0809b01856
...
d222156c8b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d222156c8b | ||
|
|
a922133da9 | ||
|
|
3a02c6ff8e | ||
|
|
c073fb82cd |
No files matched your search
+122
-50
@@ -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) {
|
||||
|
||||
@@ -185,6 +185,7 @@ private:
|
||||
UInt32 fCurrWindow;
|
||||
|
||||
IOInterruptEventSource *fInterruptSource;
|
||||
IOWorkLoop *fWorkLoop;
|
||||
IO80211WorkQueue *fWorkQueue;
|
||||
IONetworkInterface *fNetif;
|
||||
IOEthernetAddress fMacAddress;
|
||||
|
||||
@@ -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)`.
|
||||
Reference in new issue
Block a user