Jegyzet
Az oldalhoz való hozzáférés engedélyezést igényel. Próbálhatod be jelentkezni vagy könyvtárat váltani.
Az oldalhoz való hozzáférés engedélyezést igényel. Megpróbálhatod a könyvtár váltását.
A felhasználói módú tartozékok (UMA) olyan DDI-k, amelyek célja a felhasználói módú memória biztonságos elérése és kezelése a kernel módú kódból. Ezek a DDI-k elhárítják azokat a gyakori biztonsági réseket és programozási hibákat, amelyek akkor fordulhatnak elő, ha a kernel módú illesztőprogramok hozzáférnek a felhasználói módú memóriához.
A felhasználói módú memóriát elérő/módosító kernel módú kódra hamarosan szükség lesz az UMA használatához.
A felhasználói módú memória kernel módból való elérésekor felmerülő lehetséges problémák
Amikor a kernel módú kódnak hozzá kell férnie a felhasználói módú memóriához, számos kihívás merül fel:
A felhasználói módú alkalmazások rosszindulatú vagy érvénytelen mutatókat adhatnak át a kernel módú kódnak. A megfelelő ellenőrzés hiánya memóriasérüléshez, összeomlásokhoz vagy biztonsági résekhez vezethet.
A felhasználói módú kód többszálú. Ennek eredményeképpen a különböző szálak módosíthatják ugyanazt a felhasználói módú memóriát a különálló kernel módú hozzáférések között, ami valószínűleg sérült kernelmemóriát eredményezhet.
A kernel-módú fejlesztők gyakran elfelejtik ellenőrizni a felhasználói módban lévő memóriát a hozzáférés előtt, ami biztonsági probléma.
A fordítók feltételezik az egyszálas végrehajtást, és esetleg elhagyhatják azokat a memóriahozzáféréseket, amelyek redundánsnak tűnhetnek. A programozók nem tudnak az ilyen optimalizálásokról, nem biztonságos kódot írhatnak.
Az alábbi kódrészletek ezeket a problémákat szemléltetik.
1. példa: Lehetséges memóriasérülés a felhasználói módban történő többszálúság miatt
A kernel módú kódnak, amely felhasználói módú memóriát kíván elérni, ezt a __try/__except blokkon belül kell megtennie annak biztosítása érdekében, hogy a memória érvényes legyen. Az alábbi kódrészlet a felhasználói módú memória elérésének tipikus mintáját mutatja be:
// User-mode structure definition
typedef struct _StructWithData {
ULONG Size;
CHAR* Data[1];
} StructWithData;
// Kernel-mode call that accesses user-mode memory
void MySysCall(StructWithData* Ptr) {
__try {
// Probe user-mode memory to ensure it's valid
ProbeForRead(Ptr, sizeof(StructWithData), 1);
// Allocate memory in the kernel
PVOID LocalData = ExAllocatePool2(POOL_FLAG_PAGED, Ptr->Size);
// Copy user-mode data into the heap allocation
RtlCopyMemory(LocalData, Ptr->Data, Ptr->Size);
} __except (…) {
// Handle exceptions
}
}
Ez a kódrészlet először a memóriát vizsgáljuk meg, ami egy fontos első, de gyakran figyelmen kívül hagyott lépés.
A kódban előforduló egyik probléma azonban a felhasználói módban zajló többszálú feldolgozás.
Ptr->Size Az ExAllocatePool2 hívása után, de az RtlCopyMemory hívása előtt is változhat, ami memóriasérülést eredményezhet a kernelben.
2. példa: A fordítóoptimalizálások miatti lehetséges problémák
Az 1. példában a többszálúsági probléma lehetséges megoldása lehet, ha a Ptr->Size-t egy helyi változóba másoljuk a foglalás és másolás előtt.
void MySysCall(StructWithData* Ptr) {
__try {
// Probe user-mode memory to ensure it's valid
ProbeForRead(Ptr, sizeof(StructWithData), 1);
// Read Ptr->Size once to avoid possible memory change in user mode
ULONG LocalSize = Ptr->Size;
// Allocate memory in the kernel
PVOID LocalData = ExAllocatePool2(POOL_FLAG_PAGED, LocalSize);
//Copy user-mode data into the heap allocation
RtlCopyMemory(LocalData, Ptr, LocalSize);
} __except (…) {}
}
Bár ez a megközelítés enyhíti a többszálas feldolgozás okozta problémát, még mindig nem biztonságos, mert a fordító nem tud több szálról, és így egyetlen végrehajtási szálat feltételez. Optimalizálásként előfordulhat, hogy a fordító már rendelkezik a veremében azzal az értékkel, amelyre Ptr->Size mutat, ezért nem készíti el a másolatot LocalSize-be.
Felhasználói módú hozzáférők megoldása
Az UMA-felület megoldja a felhasználói módú memória kernel módból való elérésekor felmerülő problémákat. Az UMA a következőt biztosítja:
Automatikus szondázás: Az explicit szondázás (ProbeForRead/ProbeForWrite) már nem szükséges, mivel az UMA összes függvénye biztosítja a címbiztonságot.
Volatilis hozzáférés: Minden UMA DDI használ illékony szemantikát a fordítóoptimalizálás megakadályozása érdekében.
Könnyű hordozhatóság: Az UMA DDI-k átfogó készlete megkönnyíti az ügyfelek számára, hogy a meglévő kódjukat az UMA DDI-k használatára portálják, biztosítva a felhasználói módú memória biztonságos és helyes elérését.
Példa az UMA DDI használatára
A korábban definiált felhasználói módú struktúra használatával az alábbi kódrészlet bemutatja, hogyan használhatja az UMA-t a felhasználói módú memória biztonságos eléréséhez.
void MySysCall(StructWithData* Ptr) {
__try {
// This UMA call probes the passed user-mode memory and does a
// volatile read of Ptr->Size to ensure it isn't optimized away by the compiler.
ULONG LocalSize = ReadULongFromUser(&Ptr->Size);
// Allocate memory in the kernel.
PVOID LocalData = ExAllocatePool2(POOL_FLAG_PAGED, LocalSize);
//This UMA call safely copies UM data into the KM heap allocation.
CopyFromUser(&LocalData, Ptr, LocalSize);
// To be safe, set LocalData->Size to be LocalSize, which was the value used
// to make the pool allocation just in case LocalData->Size was changed.
((StructWithData*)LocalData)->Size = LocalSize;
} __except (…) {}
}
UMA implementálása és használata
Az UMA interfész a Windows Driver Kit (WDK) részeként érkezik.
- A függvény deklarációi a usermode_accessors.h fejlécfájlban találhatók.
- A függvény implementációi egy umaccess.lib nevű statikus kódtárban találhatók.
Az UMA a Windows összes verzióján működik, nem csak a legújabbakon. A usermode_accessors.h és az umaccess.lib függvénydeklarációinak és implementációinak lekéréséhez a legújabb WDK-t kell használnia. Az eredményként kapott illesztőprogram a Windows régebbi verzióiban is jól fog futni.
Az Umaccess.lib biztonságos, alacsonyabb szintű implementációt biztosít az összes DDI számára. A Windows kernel UMA-t támogató verzióiban az illesztőprogramok az összes függvényüket egy biztonságosabb verzióra irányítják át, amely az ntoskrnl.exe-ben van implementálva.
A felhasználói módú kiegészítő függvényeket egy strukturált kivételkezelőben (SEH) kell végrehajtani a felhasználói módú memória elérésekor esetleges kivételek miatt.
Felhasználói módú kiegészítő DDI-k típusai
Az UMA különböző DDI-kat biztosít a felhasználói módú memóriahozzáférés különböző típusaihoz. Ezeknek a DDI-knak a többsége alapvető adattípusokhoz, például BOOLEAN, ULONG és mutatókhoz készült. Az UMA emellett DDI-kat biztosít a tömeges memória-hozzáféréshez, a sztringhossz-lekéréshez és az összekapcsolt műveletekhez.
Általános DDI-k alapvető adattípusokhoz
Az UMA hat függvényvariánst biztosít egyszerű adattípusok olvasásához és írásához. A Boolean értékekhez például a következő függvények érhetők el:
| Függvény neve | Description |
|---|---|
| ReadBooleanFromUser | Felhasználói módú memóriából származó érték beolvasása. |
| ReadBooleanFromUserAcquire | Olvasson be egy értéket felhasználói módú memóriából, amely a memóriarendezéshez szükséges megszerzési szemantikát alkalmazza. |
| ReadBooleanFromMode | Olvasás felhasználói vagy kernel módú memóriából egy módparaméter alapján. |
| WriteBooleanToUser | Írjon egy értéket a felhasználói módú memóriába. |
| WriteBooleanToUserRelease | Írjon egy értéket a felhasználói módú memóriába a memóriarendezés kiadási szemantikájának használatával. |
| WriteBooleanToMode | Írás felhasználói vagy kernel módú memóriába egy módparaméter alapján. |
A XxxFromUser olvasási függvényekhez a Forrás paraméternek a felhasználói módú virtuális címtérre (VAS) kell mutatnia. Ugyanez igaz a ReadXxxFromMode verziókban, amikor Mode == UserMode.
A ReadXxxFromMode használata esetén Mode == KernelMode a Forrás paraméternek a kernel módú VAS-ra kell mutatnia. Ha az előprocesszor definíciója, a DBG, definiálva van, a művelet gyorsan megszakad a FAST_FAIL_KERNEL_POINTER_EXPECTED kóddal.
A Write XxxToUserfüggvényben a Cél paraméternek a felhasználói módú VAS-ra kell mutatnia. Ugyanez igaz a WriteXxxToMode verziókban, amikor Mode == UserMode.
Másolási és memóriakezelési DDI-k
Az UMA függvényeket biztosít a memória másolásához és áthelyezéséhez a felhasználó és a kernel mód között, beleértve a nemtemporális és az igazított másolatok variánsait is. Ezeket a függvényeket széljegyzetek jelölik, amelyek a lehetséges SEH-kivételeket és az IRQL-követelményeket (maximális APC_LEVEL) jelzik.
Ilyenek például a CopyFromUser, a CopyToMode és a CopyFromUserToMode.
Az olyan makrók, mint a CopyFromModeAligned és a CopyFromUserAligned , a másolási művelet végrehajtása előtt az igazítási próbát is tartalmazzák a biztonság érdekében.
Az olyan makrók, mint a CopyFromUserNonTemporal és a CopyToModeNonTemporal , nemtemporális másolatokat biztosítanak, amelyek elkerülik a gyorsítótár-szennyezést.
Strukturált olvasási/írási makrók
A módok közötti olvasási és írási struktúrák makrói biztosítják a típuskompatibilitást és az igazítást, és méret- és módparaméterekkel hívják meg a segédfüggvényeket. Ilyenek például a WriteStructToMode, a ReadStructFromUser és a hozzájuk igazított változatok.
Memória kitöltési és nullázási függvények
A DDI-k a használói vagy üzemmódbeli címterekben lévő memória kitöltésére vagy nullázására szolgálnak, ahol paraméterekkel meghatározható a célhely, hossza, kitöltési értéke és módja. Ezek a függvények SEH- és IRQL-széljegyzeteket is hordoznak.
Ilyen például a FillUserMemory és a ZeroModeMemory.
Összekapcsolt műveletek
Az UMA olyan összekapcsolt műveleteket tartalmaz az atomi memóriához való hozzáféréshez, amelyek nélkülözhetetlenek a szálbiztos memóriakezeléshez egyidejű környezetekben. A DDI-k a 32 bites és a 64 bites értékekhez is elérhetők, a felhasználók vagy üzemmódok memóriáját megcélzó verziókkal.
Ilyenek például az InterlockedCompareExchangeToUser, az InterlockedOr64ToMode és az InterlockedAndToUser.
Sztringhosszúságú DDI-k
A felhasználói vagy módbeli memóriából biztonságosan meghatározható karakterlánchosszokat meghatározó függvények az ANSI-t és a széles karakterű sztringeket is támogatják. Ezek a függvények úgy vannak kialakítva, hogy kivételeket dobjanak a nem biztonságos memóriahozzáférés esetén, és IRQL korlátozások vonatkoznak rájuk.
Ilyenek például a StringLengthFromUser és WideStringLengthFromMode.
Nagy egész szám- és Unicode-karakterlánc-hozzáférők
Az UMA DDI-ket biztosít LARGE_INTEGER, ULARGE_INTEGER és UNICODE_STRING típusok olvasásához és írásához a felhasználó és a mód memóriája között. A variánsok olyan szemantikákat szereznek be és bocsátanak ki, amelyek biztonsági és helyességi módparaméterekkel rendelkeznek.
Ilyen például a ReadLargeIntegerFromUser, WriteUnicodeStringToMode és WriteULargeIntegerToUser.
Szemantikák beszerzése és kiadása
Egyes architektúrákban, például az ARM-ben a processzor átrendezheti a memóriahozzáféréseket. Az általános DDI-k mindegyike rendelkezik beszerzés/kiadás implementációval, ha garantálnia kell, hogy a memóriahozzáférések nem lesznek átrendezve a felhasználói módú hozzáféréshez.
- Az 'acquire' szemantikák megakadályozzák a betöltési műveletek átrendezését más memóriaműveletek viszonylatában.
- A kiadás szemantikája megakadályozza a tárolás átrendezését más memóriaműveletekhez képest.
Ilyenek például a ReadULongFromUserAcquire és a WriteULongToUserRelease UMA-ban történő beolvasási és kiadási szemantikák.
További információ: Szemantikák beszerzése és kiadása.
Ajánlott eljárások
- A felhasználói módú memória kernelkódból való elérésekor mindig használjon UMA DDI-kat.
-
A kivételek kezelése a megfelelő
__try/__exceptblokkokkal. - Használjon módalapú DDI-kat , ha a kód felhasználói és kernel módú memóriát is kezelhet.
- Fontolja meg az elérési/felszabadítási szemantikát, ha a memóriarendezés fontos az adott esetben.
- A konzisztencia biztosítása érdekében ellenőrizze a másolt adatokat, miután a kernelmemóriára másolta őket.
Jövőbeli hardvertámogatás
A felhasználói módú tartozékokat úgy tervezték, hogy támogassák a jövőbeli hardveres biztonsági funkciókat, például:
- SMAP (Felügyeleti módú hozzáférés-megelőzés): Megakadályozza, hogy a kernelkód hozzáférjen a felhasználói módú memóriához, kivéve a kijelölt függvényeket, például az UMA DDI-kat.
- ARM PAN (Privileged Access Never): Hasonló védelem az ARM-architektúrákon.
Az UMA DDI-k következetes használatával az illesztőprogramok kompatibilisek lesznek ezekkel a biztonsági fejlesztésekkel, ha a jövőbeli Windows-verziókban engedélyezve vannak.