Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Benutzermodus-Accessoren (UMA) sind eine Reihe von DDIs, die für den sicheren Zugriff und die Manipulation des Benutzermodusspeichers aus Kernelmodus-Code ausgelegt sind. Diese DDIs behandeln häufige Sicherheitsrisiken und Programmierfehler, die auftreten können, wenn Kernelmodustreiber auf den Benutzermodusspeicher zugreifen.
Kernelmoduscode, der auf den Benutzermodusspeicher zugreift/bearbeitet, wird bald benötigt, um UMA zu verwenden.
Mögliche Probleme beim Zugriff auf den Speicher des Benutzermodus aus dem Kernelmodus
Wenn Kernelmoduscode auf den Arbeitsspeicher des Benutzermodus zugreifen muss, treten mehrere Herausforderungen auf:
Anwendungen im Benutzermodus können böswillige oder ungültige Zeiger an Kernelmoduscode übergeben. Fehlende ordnungsgemäße Überprüfung kann zu Speicherbeschädigungen, Abstürze oder Sicherheitsrisiken führen.
Benutzermoduscode ist multithreaded. Daher können verschiedene Threads denselben Benutzermodusspeicher zwischen separaten Kernelmoduszugriffen ändern, was möglicherweise zu beschädigtem Kernelspeicher führt.
Kernelmodusentwickler vergessen häufig, den Speicher des Benutzermodus zu testen, bevor Sie darauf zugreifen. Dies ist ein Sicherheitsproblem.
Compiler gehen von einer Einzelthreadausführung aus und optimieren möglicherweise, was als redundanter Speicherzugriff angezeigt wird. Programmierer, die solche Optimierungen nicht kennen, können unsicheren Code schreiben.
Die folgenden Codeausschnitte veranschaulichen diese Probleme.
Beispiel 1: Mögliche Speicherbeschädigung aufgrund von Multithreading im Benutzermodus
Kernelmoduscode, der auf den Speicher des Benutzermodus zugreifen muss, muss dies innerhalb eines __try/__except Blocks tun, um sicherzustellen, dass der Speicher gültig ist. Der folgende Codeausschnitt zeigt ein typisches Muster für den Zugriff auf den Arbeitsspeicher im Benutzermodus:
// 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
}
}
Dieser Codeausschnitt überprüft zuerst den Speicher, der ein wichtiger, aber häufig übersehener Schritt ist.
Ein Problem, das in diesem Code auftreten kann, liegt jedoch an Multithreading im Benutzermodus. Insbesondere kann sich Ptr->Size nach dem Aufruf von ExAllocatePool2, aber vor dem Aufruf von RtlCopyMemory ändern, was möglicherweise zu Speicherbeschädigung im Kernel führt.
Beispiel 2: Mögliche Probleme aufgrund von Compileroptimierungen
Ein Versuch, das Multithreading-Problem in Beispiel 1 zu beheben, könnte darin bestehen, vor der Zuweisung und Kopie in eine lokale Variable zu kopieren Ptr->Size :
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 (…) {}
}
Während dieser Ansatz das Durch Multithreading verursachte Problem entschärft, ist es immer noch nicht sicher, da der Compiler nicht über mehrere Threads informiert ist und daher von einem einzelnen Ausführungsthread ausgeht. Als Optimierung erkennt der Compiler möglicherweise, dass er bereits eine Kopie des Werts, auf den Ptr->Size zeigt, auf seinem Stapel hat und daher die Kopie nach LocalSize nicht ausführt.
Benutzermodus-Accessorenlösung
Die UMA-Schnittstelle löst die Probleme beim Zugriff auf den Benutzermodusspeicher aus dem Kernelmodus. UMA bietet Folgendes:
Automatisches Probing: Explizites Probing (ProbeForRead/ProbeForWrite) ist nicht mehr erforderlich, da alle UMA-Funktionen die Adresssicherheit gewährleisten.
Veränderliche Zugriffe: Alle UMA-DDIs verwenden veränderliche Semantik, um Compileroptimierungen zu verhindern.
Einfache Portierbarkeit: Der umfassende Satz von UMA-DDIs erleichtert Es Kunden, ihren vorhandenen Code zur Verwendung von UMA DDIs zu portieren, um sicherzustellen, dass auf den Arbeitsspeicher im Benutzermodus sicher und ordnungsgemäß zugegriffen wird.
Beispiel für die Verwendung von UMA DDI
Mithilfe der zuvor definierten Benutzermodusstruktur veranschaulicht der folgende Codeausschnitt, wie UMA zum sicheren Zugriff auf den Benutzermodusspeicher verwendet wird.
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-Implementierung und -Verwendung
Die UMA-Schnittstelle wird als Teil des Windows Driver Kit (WDK) ausgeliefert:
- Die Funktionsdeklarationen befinden sich in der Headerdatei usermode_accessors.h .
- Die Funktionsimplementierungen befinden sich in einer statischen Bibliothek namens "umaccess.lib".
UMA funktioniert auf allen Versionen von Windows, nicht nur auf dem neuesten. Sie müssen die neuesten WDK verwenden, um die Funktionsdeklarationen und Implementierungen von usermode_accessors.h bzw. umaccess.lib abzurufen. Der resultierende Treiber wird in älteren Versionen von Windows einwandfrei ausgeführt.
Umaccess.lib bietet eine sichere, down-level-Implementierung für alle DDIs. Bei UMA-fähigen Versionen des Windows-Kernels werden treiber alle ihre Funktionen auf eine sicherere Version umgeleitet, die in ntoskrnl.exeimplementiert ist.
Alle Accessorfunktionen im Benutzermodus müssen in einem strukturierten Ausnahmehandler (SEH) ausgeführt werden, da potenzielle Ausnahmen beim Zugriff auf den Arbeitsspeicher des Benutzermodus auftreten.
Typen von Benutzermodus-Accessor-DDIs
UMA stellt verschiedene DDIs für verschiedene Arten des Benutzermodusspeicherzugriffs bereit. Die meisten dieser DDIs sind für grundlegende Datentypen wie BOOLEAN, ULONG und Zeiger vorgesehen. Darüber hinaus stellt UMA DDIs für den Massenspeicherzugriff, den Abruf von Zeichenfolgenlängen und die verriegelten Vorgänge bereit.
Generische DDIs für grundlegende Datentypen
UMA bietet sechs Funktionsvarianten zum Lesen und Schreiben einfacher Datentypen. Die folgenden Funktionen sind z. B. für BOOLESCHE Werte verfügbar:
| Funktionsname | Description |
|---|---|
| ReadBooleanFromUser | Lesen sie einen Wert aus dem Arbeitsspeicher des Benutzermodus. |
| ReadBooleanFromUserAcquire | Lesen Sie einen Wert aus dem Benutzermodusspeicher mit Acquire-Semantik zur Speicherordnung. |
| ReadBooleanFromMode | Lesezugriff vom Benutzermodus oder vom Kernelmodusspeicher basierend auf einem Modusparameter. |
| WriteBooleanToUser | Schreiben Sie einen Wert in den Arbeitsspeicher des Benutzermodus. |
| WriteBooleanToUserRelease | Schreiben Sie einen Wert in den Arbeitsspeicher im Benutzermodus mit Freigabesemantik für die Speicherordnung. |
| WriteBooleanToMode | Schreiben Sie basierend auf einem Modusparameter entweder in den Benutzermodus- oder Kernelmodusspeicher. |
Für ReadXxxFromUser-Funktionen muss der Source-Parameter auf den virtuellen Adressraum im Benutzermodus (User-Mode Virtual Address Space, VAS) verweisen. Das gleiche gilt in den Versionen von ReadXxxFromMode , wenn Mode == UserMode.
Bei ReadXxxFromModeMode == KernelMode muss der Source-Parameter auf die Kernelmodus-VAS zeigen. Wenn die Präprozessordefinition DBG definiert ist, wird der Vorgang mit dem FAST_FAIL_KERNEL_POINTER_EXPECTED-Code schnell beendet.
In den Funktionen WriteXxxToUser muss der Destination-Parameter auf die VAS des Benutzermodus verweisen. Das gleiche gilt in den WriteXxxToMode-Versionen, wenn Mode == UserMode.
Kopieren und Speicherverwaltung von DDIs
UMA stellt Funktionen zum Kopieren und Verschieben des Speichers zwischen Benutzer- und Kernelmodi bereit, einschließlich Varianten für nichttemporale und ausgerichtete Kopien. Diese Funktionen sind mit Anmerkungen gekennzeichnet, die potenzielle SEH-Ausnahmen und IRQL-Anforderungen (max. APC_LEVEL) angeben.
Beispiele sind CopyFromUser, CopyToMode und CopyFromUserToMode.
Makros wie "CopyFromModeAligned " und "CopyFromUserAligned " umfassen die Ausrichtungsprobung zur Sicherheit, bevor sie den Kopiervorgang ausführen.
Makros wie CopyFromUserNonTemporal und CopyToModeNonTemporal stellen nichttemporale Kopien bereit, die die Cacheverschmutzung vermeiden.
Makros für Lese-/Schreibzugriff auf Strukturen
Makros zum Lesen und Schreiben von Strukturen zwischen Modi stellen die Typkompatibilität und -ausrichtung, das Aufrufen von Hilfsfunktionen mit Größen- und Modusparametern sicher. Beispiele sind WriteStructToMode, ReadStructFromUser und ihre angepassten Varianten.
Füll- und Nullspeicherfunktionen
DDIs werden bereitgestellt, um Speicher in Benutzer- oder Modusadressräumen auf null zu setzen oder aufzufüllen, wobei Parameter das Ziel, die Länge, den Füllwert und den Modus bestimmen. Diese Funktionen tragen auch SEH- und IRQL-Anmerkungen.
Beispiele sind FillUserMemory und ZeroModeMemory.
Verriegelte Vorgänge
UMA umfasst verzahnte Vorgänge für den atomischen Speicherzugriff, die für threadsichere Speichermanipulationen in gleichzeitigen Umgebungen unerlässlich sind. DDIs werden sowohl für 32-Bit- als auch für 64-Bit-Werte bereitgestellt, wobei Versionen für den Benutzer- oder Modusspeicher vorgesehen sind.
Beispiele sind InterlockedCompareExchangeToUser, InterlockedOr64ToMode und InterlockedAndToUser.
Zeichenfolgenlänge DDIs
Funktionen zum sicheren Ermitteln von Zeichenfolgenlängen aus dem Benutzer- oder Modusspeicher sind enthalten, die sowohl ANSI- als auch Breite-Zeichen-Zeichenfolgen unterstützen. Diese Funktionen sind so konzipiert, dass sie Ausnahmen bei unsicherem Speicherzugriff auslösen und durch IRQL-Bedingungen eingeschränkt sind.
Beispiele sind StringLengthFromUser und WideStringLengthFromMode.
Große ganzzahlige und Unicode-Zeichenfolgen-Zugriffsobjekte
UMA bietet DDIs zum Lesen und Schreiben von LARGE_INTEGER-, ULARGE_INTEGER- und UNICODE_STRING-Typen zwischen Benutzer- und Modusspeicher. Varianten haben Erwerbs- und Freigabesemantik mit Parameteroptionen zur Sicherheit und Korrektheit.
Beispiele sind ReadLargeIntegerFromUser, WriteUnicodeStringToMode und WriteULargeIntegerToUser.
Erwerben und Freigeben der Semantik
Bei einigen Architekturen wie ARM kann die CPU Speicherzugriffe neu anordnen. Die generischen DDIs verfügen alle über eine Acquire/Release-Implementierung, wenn Sie eine Garantie benötigen, dass Speicherzugriffe für den Benutzermoduszugriff nicht neu angeordnet sind.
- Die Erwerbssemantik verhindert das Neuanordnen von Ladeoperationen im Verhältnis zu anderen Speichervorgängen.
- Freigabesemantik verhindert die Neuanordnung des Speichers relativ zu anderen Speichervorgängen.
Beispiele für die Erwerbs- und Freigabesemantik in UMA sind ReadULongFromUserAcquire und WriteULongToUserRelease.
Weitere Informationen finden Sie unter Acquire and Release Semantics.
Bewährte Methoden
- Verwenden Sie IMMER UMA-DDIs beim Zugriff auf den Speicher des Benutzermodus über Kernelcode.
-
Behandeln sie Ausnahmen mit entsprechenden
__try/__exceptBlöcken. - Verwenden Sie modusbasierte DDIs , wenn Ihr Code sowohl den Benutzermodus als auch den Kernelmodusspeicher verarbeiten kann.
- Erwägen Sie die Semantik von Erwerben/Freigeben, wenn die Speicherordnung für Ihren Anwendungsfall wichtig ist.
- Überprüfen Sie kopierte Daten nach dem Kopieren in den Kernelspeicher, um die Konsistenz sicherzustellen.
Zukünftige Hardwareunterstützung
Benutzermodus-Accessoren sind so konzipiert, dass zukünftige Hardwaresicherheitsfeatures wie folgt unterstützt werden:
- SMAP (Supervisor Mode Access Prevention): Verhindert, dass Kernelcode auf den Benutzermodusspeicher zugreift, außer über bestimmte Funktionen wie UMA DDIs.
- ARM PAN (Privileged Access Never): Ähnlicher Schutz für ARM-Architekturen.
Durch die konsistente Verwendung von UMA DDIs sind Treiber mit diesen Sicherheitsverbesserungen kompatibel, wenn sie in zukünftigen Windows-Versionen aktiviert sind.