Not
Åtkomst till denna sida kräver auktorisation. Du kan prova att logga in eller byta katalog.
Åtkomst till denna sida kräver auktorisation. Du kan prova att byta katalog.
Användarlägesåtkomster (UMA) är en uppsättning DDI:er som är utformade för att på ett säkert sätt komma åt och manipulera minne i användarläge från kernellägeskod. Dessa DDI:er hanterar vanliga säkerhetsrisker och programmeringsfel som kan uppstå när drivrutiner i kernelläge får åtkomst till minne i användarläge.
Kernellägeskod som kommer åt/manipulerar användarlägesminnet kommer snart att krävas för att använda UMA.
Möjliga problem vid åtkomst till minne i användarläge från kernelläge
När kernellägeskod behöver komma åt minne i användarläge uppstår flera utmaningar:
Program i användarläge kan skicka skadliga eller ogiltiga pekare till kod i kernelläge. Brist på korrekt validering kan leda till minnesskada, krascher eller säkerhetsrisker.
Användarlägeskod är flertrådad. Därför kan olika trådar ändra samma minne i användarläge mellan separata kernellägesåtkomster till den, vilket kan leda till skadat kernelminne.
Utvecklare i kernelläge glömmer ofta att avsöka minne i användarläge innan de får åtkomst till det, vilket är ett säkerhetsproblem.
Kompilatorer antar ensamtrådad körning och kan optimera bort vad som verkar vara redundanta minnesåtkomster. Programmerare som inte känner till sådana optimeringar kan skriva osäker kod.
Följande kodfragment illustrerar dessa problem.
Exempel 1: Möjlig minnesskada på grund av multitrådning i användarläge
Kernel-lägeskod som behöver komma åt minne i användarläge måste göra det inom ett __try/__except block för att säkerställa att minnet är giltigt. Följande kodfragment visar ett typiskt mönster för åtkomst till minne i användarläge:
// 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
}
}
Det här kodfragmentet avsöker minnet först, vilket är ett viktigt första men ofta förbisedda steg.
Ett problem som kan uppstå i den här koden beror dock på multitrådning i användarläge.
Ptr->Size Mer specifikt kan ändras efter anropet till ExAllocatePool2 men före anropet till RtlCopyMemory, vilket kan leda till minnesskada i kerneln.
Exempel 2: Möjliga problem på grund av kompilatoroptimeringar
Ett försök att åtgärda multitrådsproblemet i exempel 1 kan vara att kopiera Ptr->Size till en lokal variabel före allokeringen och kopiera:
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 (…) {}
}
Även om den här metoden minskar problemet som orsakas av multitrådning är det fortfarande inte säkert eftersom kompilatorn inte känner till flera trådar och därför förutsätter en enda körningstråd. Som en optimering kan kompilatorn se att den redan har en kopia av värdet som Ptr->Size pekar på på stacken och därför inte gör kopian till LocalSize.
Lösning för användarlägesåtkomster
UMA-gränssnittet löser de problem som uppstår vid åtkomst till minne i användarläge från kernelläge. UMA tillhandahåller:
Automatisk avsökning: Explicit avsökning (ProbeForRead/ProbeForWrite) krävs inte längre, eftersom alla UMA-funktioner säkerställer adresssäkerheten.
Flyktig åtkomst: Alla UMA DDIs använder flyktiga semantik för att förhindra kompilatoroptimeringar.
Enkel portabilitet: Den omfattande uppsättningen UMA DDIs gör det enkelt för kunder att portera sin befintliga kod för att använda UMA DDIs, vilket säkerställer att minnet i användarläge nås på ett säkert och korrekt sätt.
Exempel med UMA DDI
Med hjälp av den tidigare definierade användarlägesstrukturen visar följande kodfragment hur du använder UMA för att på ett säkert sätt komma åt minne i användarläge.
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-implementering och användning
UMA-gränssnittet levereras som en del av Windows Driver Kit (WDK):
- Funktionsdeklarationerna finns i huvudfilen usermode_accessors.h .
- Funktionsimplementeringarna finns i ett statiskt bibliotek med namnet umaccess.lib.
UMA fungerar på alla versioner av Windows, inte bara den senaste. Du måste använda den senaste WDK:n för att hämta funktionsdeklarationer och implementeringar från usermode_accessors.h respektive umaccess.lib. Den resulterande drivrutinen körs bra på äldre versioner av Windows.
Umaccess.lib tillhandahåller en säker implementering på nednivå för alla DDI:er. På UMA-medvetna versioner av Windows-kerneln omdirigeras alla deras funktioner till en säkrare version som implementeras i ntoskrnl.exe.
Alla accessorfunktioner i användarläge måste köras inom en strukturerad undantagshanterare (SEH) på grund av potentiella undantag vid åtkomst till minne i användarläge.
Typer av användarlägestillgångs-DDI:er
UMA tillhandahåller olika DDI:er för olika typer av minnesåtkomst i användarläge. De flesta dessa DDI:er är för grundläggande datatyper, till exempel BOOLEAN, ULONG och pekare. Dessutom tillhandahåller UMA DDIs för bulkminnesåtkomst, stränglängdshämtning och låsta operationer.
Allmänna DDI:er för grundläggande datatyper
UMA tillhandahåller sex funktionsvarianter för att läsa och skriva enkla datatyper. Följande funktioner är till exempel tillgängliga för BOOLESKA värden:
| Funktionsnamn | Description |
|---|---|
| ReadBooleanFromUser | Läs ett värde från användarlägesminnet. |
| ReadBooleanFromUserAcquire | Läs ett värde från användarlägesminnet med hämta semantik för minnesordning. |
| ReadBooleanFromMode | Läs från antingen användarläge eller kernellägesminne baserat på en lägesparameter. |
| WriteBooleanToUser | Skriv ett värde till minne i användarläge. |
| WriteBooleanToUserRelease | Skriv ett värde till användarlägesminnet med versionssemantik för minnesordning. |
| WriteBooleanToMode | Skriv till antingen användarläge eller kernellägesminne baserat på en lägesparameter. |
För funktionerna ReadXxxFromUser måste parametern Source peka på det virtuella adressutrymmet i användarläge (VAS). Det samma gäller för ReadXxxFromMode-versionerna när Mode == UserMode.
För ReadXxxFromMode, när Mode == KernelMode måste parametern Source peka på VAS i kernelläge. Om förprocessordefinitionen DBG definieras, misslyckas operationen snabbt med koden FAST_FAIL_KERNEL_POINTER_EXPECTED.
I funktionerna WriteXxxToUser måste målparametern peka på användarlägets VAS. Detsamma gäller i WriteXxxToMode-versionerna när Mode == UserMode.
DDI:er för kopiering och minnesmanipulering
UMA tillhandahåller funktioner för att kopiera och flytta minne mellan användar- och kernellägen, inklusive varianter för icke-temporala och justerade kopior. Dessa funktioner markeras med anteckningar som anger potentiella SEH-undantag och IRQL-krav (max APC_LEVEL).
Exempel är CopyFromUser, CopyToMode och CopyFromUserToMode.
Makron som CopyFromModeAligned och CopyFromUserAligned inkluderar justeringsavsökning för säkerhet innan kopieringsåtgärden utförs.
Makron som CopyFromUserNonTemporal och CopyToModeNonTemporal tillhandahåller icke-temporala kopior som undviker cacheföroreningar.
Strukturera läs-/skriv-makron
Makron för att läsa och skriva strukturer mellan lägen säkerställer typkompatibilitet och justering, anropar hjälpfunktioner med storlek och lägesparametrar. Exempel är WriteStructToMode, ReadStructFromUser och deras justerade varianter.
Fyllnings- och nollminnesfunktioner
DDI:er tillhandahålls för att fylla eller noll minne i användar- eller lägesadressutrymmen, med parametrar som anger mål, längd, fyllningsvärde och läge. Dessa funktioner har också SEH- och IRQL-anteckningar.
Exempel är FillUserMemory och ZeroModeMemory.
Sammankopplade åtgärder
UMA innehåller sammankopplade åtgärder för atomisk minnesåtkomst, som är viktiga för trådsäkra minnesmanipuleringar i samtidiga miljöer. DDI:er tillhandahålls för både 32-bitars- och 64-bitarsvärden, med versioner som riktar sig till användar- eller lägesminne.
Exempel är InterlockedCompareExchangeToUser, InterlockedOr64ToMode och InterlockedAndToUser.
DDI:er för stränglängd
Funktioner för att fastställa stränglängder på ett säkert sätt från användar- eller lägesminnet ingår, med stöd för både ANSI- och wide-character-strängar. Dessa funktioner är utformade för att generera undantag vid osäker minnesåtkomst och är IRQL-begränsade.
Exempel är StringLengthFromUser och WideStringLengthFromMode.
Stora heltals- och Unicode-strängåtkomster
UMA tillhandahåller DDI:er för att läsa och skriva LARGE_INTEGER, ULARGE_INTEGER och UNICODE_STRING typer mellan användar- och lägesminne. Varianter har hämtat och släppt semantik med lägesparametrar för säkerhet och korrekthet.
Exempel är ReadLargeIntegerFromUser, WriteUnicodeStringToMode och WriteULargeIntegerToUser.
Förvärva och frigöra semantik
I vissa arkitekturer, till exempel ARM, kan processorn ändra ordning på minnesåtkomster. De generiska DDI:erna har en implementation av acquire/release om du behöver garantin för att minnesåtkomster inte omordnas vid användarlägesåtkomst.
- Hämta semantik förhindrar omordning av belastningen i förhållande till andra minnesåtgärder.
- Versionssemantik förhindrar omordning av arkivet i förhållande till andra minnesåtgärder.
Exempel på hämta och släppa semantik i UMA är ReadULongFromUserAcquire och WriteULongToUserRelease.
Mer information finns i Hämta och släppa semantik.
Metodtips
- Använd alltid UMA DDIs vid åtkomst till minne i användarläge från kernelkod.
-
Hantera undantag med lämpliga
__try/__exceptblock. - Använd lägesbaserade DDI:er när koden kan hantera både användarläge och kernellägesminne.
- Överväg att hämta/släppa semantik när minnesordning är viktigt för ditt användningsfall.
- Verifiera kopierade data när du har kopierat dem till kernelminnet för att säkerställa konsekvens.
Framtida maskinvarustöd
Användarlägesåtkomster är utformade för att stödja framtida maskinvarusäkerhetsfunktioner som:
- SMAP (Åtkomstskydd för övervakarläge): Förhindrar att kernelkod får åtkomst till minne i användarläge förutom via avsedda funktioner som UMA DDIs.
- ARM PAN (Privileged Access Never): Liknande skydd för ARM-arkitekturer.
Genom att använda UMA-DDI:er konsekvent är drivrutinerna kompatibla med dessa säkerhetsförbättringar när de aktiveras i framtida Windows-versioner.