/*** *eh3valid.c - Validate the registration node for _except_handler3 * * Copyright (c) 2002, Microsoft Corporation. All rights reserved. * *Purpose: * Defines _ValidateEH3RN used to guard against hacker attacks which * attempt to use _except_handler3 to sidestep the .sxdata OS checks. * *Revision History: * 03-18-02 PML File created * 04-27-02 PML Perf: keep list of valid scopetables (vs7#522476) * *******************************************************************************/ #include typedef struct _SCOPETABLE_ENTRY { DWORD EnclosingLevel; PVOID FilterFunc; PVOID HandlerFunc; } SCOPETABLE_ENTRY, *PSCOPETABLE_ENTRY; typedef struct _EH3_EXCEPTION_REGISTRATION { // // These are at negative offsets from the struct start: // // DWORD SavedESP; // PEXCEPTION_POINTERS XPointers; // // Common to all exception registration nodes: // struct _EH3_EXCEPTION_REGISTRATION *Next; PVOID ExceptionHandler; // // Private to _except_handler3's registration node: // PSCOPETABLE_ENTRY ScopeTable; DWORD TryLevel; } EH3_EXCEPTION_REGISTRATION, *PEH3_EXCEPTION_REGISTRATION; #define SAVED_ESP(pRN) (((PVOID *)pRN)[-2]) #define EMPTY_LEVEL ((DWORD)-1) #define SUCCESS (1) #define FAILURE (0) #define OPTIONAL_FAILURE (-1) #define PAGE_SIZE 0x1000 // x86 uses 4K pages #define VALID_SIZE 16 static PVOID rgValidPages[VALID_SIZE]; static int nValidPages; static LONG lModifying; // nonzero if rgValidPages being modified /*** *int _ValidateEH3RN - check validity of _except_handler3 registration node * *Purpose: * Attempt to intercept hacker attacks that try to use an artificial * _except_handler3 registration node to exploit a buffer overrun or * other security bug to inject exploit code. * *Entry: * pRN - pointer to _except_handler3 exception registration node * *Return: * >0 All checks passed, scopetable is validated. * 0 A required check failed and the exception should be rejected. * <0 An optional check failed and the exception should be rejected if * operating under these stricter tests. * * The optional checks only permit scopetables which are found inside * MEM_IMAGE pages that are currently unwritable, or started that way * according to the section descriptors. * *******************************************************************************/ int _ValidateEH3RN(PEH3_EXCEPTION_REGISTRATION pRN) { PNT_TIB pTIB; PSCOPETABLE_ENTRY pScopeTable; DWORD level; int nFilters; MEMORY_BASIC_INFORMATION mbi; PIMAGE_DOS_HEADER pDOSHeader; PIMAGE_NT_HEADERS pNTHeader; PIMAGE_OPTIONAL_HEADER pOptHeader; DWORD rvaScopeTable; PIMAGE_SECTION_HEADER pSection; unsigned int iSection; PVOID pScopePage, pTmp; int iValid, iValid2; // // Scopetable pointer must be DWORD aligned // pScopeTable = pRN->ScopeTable; if (((DWORD_PTR)pScopeTable & 0x3) != 0) return FAILURE; // // Scopetable cannot be located on the stack // __asm { mov eax, fs:offset NT_TIB.Self mov pTIB, eax } if ((PVOID)pScopeTable >= pTIB->StackLimit && (PVOID)pScopeTable < pTIB->StackBase) return FAILURE; // // If not nested in guarded block, then nothing left to check // if (pRN->TryLevel == EMPTY_LEVEL) return SUCCESS; // // Ensure all scopetable entries up to current try level are properly // nested (parent level must be the empty state or a lower level than // the one being checked). // nFilters = 0; for (level = 0; level <= pRN->TryLevel; ++level) { DWORD enclosing = pScopeTable[level].EnclosingLevel; if (enclosing != EMPTY_LEVEL && enclosing >= level) return FAILURE; if (pScopeTable[level].FilterFunc != NULL) ++nFilters; } // // If the scopetable had any __except filters, make sure the saved ESP // pointer is on the stack below the registration node // if (nFilters != 0 && (SAVED_ESP(pRN) < pTIB->StackLimit || SAVED_ESP(pRN) >= (PVOID)pRN) ) return FAILURE; // // Before validating the scopetable pointer, check if we've already // validated a pointer on the same page, to avoid the expensive call // to VirtualQuery. If the page is found in the list of valid pages, // move it to the front of the list. // pScopePage = (PVOID)((DWORD_PTR)pScopeTable & ~(PAGE_SIZE - 1)); for (iValid = 0; iValid < nValidPages; ++iValid) { if (rgValidPages[iValid] == pScopePage) { // Found - move entry to start of valid list, unless some other // thread is already updating the table if (iValid > 0 && InterlockedExchange(&lModifying, 1) == 0) { if (rgValidPages[iValid] != pScopePage) { // Entry has been moved by another thread; find it for (iValid = nValidPages - 1; iValid >= 0; --iValid) if (rgValidPages[iValid] == pScopePage) break; if (iValid < 0) { // Entry no longer on list, add it back if (nValidPages < VALID_SIZE) ++nValidPages; iValid = nValidPages - 1; } else if (iValid == 0) { // Entry already moved to correct position InterlockedExchange(&lModifying, 0); return SUCCESS; } } for (iValid2 = 0; iValid2 <= iValid; ++iValid2) { // Move elements before found entry back a position // and store entry in 1st position pTmp = rgValidPages[iValid2]; rgValidPages[iValid2] = pScopePage; pScopePage = pTmp; } InterlockedExchange(&lModifying, 0); } return SUCCESS; } } // // It's an optional failure if the scopetable is not located inside an // image. If the scopetable is in an image, it must not be in a writable // section. First check if the memory is not currently marked writable. // if (VirtualQuery(pScopeTable, &mbi, sizeof mbi) == 0 || mbi.Type != MEM_IMAGE) return OPTIONAL_FAILURE; if ((mbi.Protect & (PAGE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY)) == 0) goto exit_success; // // Scopetable is inside an image, but in memory marked writable. Still // might be OK, if the memory started unwritable but was later changed // by VirtualProtect. See if we're in a normal NT PE executable image, // and if yes, find the image section holding the scopetable and check // its characteristics. // // This code assumes that calling VirtualQuery on a pointer anywhere inside // an image will return an AllocationBase equal to the start of the image, // i.e. a single VirtualAlloc was used to allocate the entire image range. // If we don't see a PE executable as expected, treat it as an optional // failure. // pDOSHeader = (PIMAGE_DOS_HEADER)mbi.AllocationBase; if (pDOSHeader->e_magic != IMAGE_DOS_SIGNATURE) return OPTIONAL_FAILURE; pNTHeader = (PIMAGE_NT_HEADERS)((PBYTE)pDOSHeader + pDOSHeader->e_lfanew); if (pNTHeader->Signature != IMAGE_NT_SIGNATURE) return OPTIONAL_FAILURE; pOptHeader = (PIMAGE_OPTIONAL_HEADER)&pNTHeader->OptionalHeader; if (pOptHeader->Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC) return OPTIONAL_FAILURE; // // Looks like a valid PE executable, find the section holding the // scopetable. We make no assumptions here about the sort order of the // section descriptors (though they always appear to be sorted by // ascending section RVA). // rvaScopeTable = (DWORD)((PBYTE)pScopeTable - (PBYTE)pDOSHeader); for (iSection = 0, pSection = IMAGE_FIRST_SECTION(pNTHeader); iSection < pNTHeader->FileHeader.NumberOfSections; ++iSection, ++pSection) { if (rvaScopeTable >= pSection->VirtualAddress && rvaScopeTable < pSection->VirtualAddress + pSection->Misc.VirtualSize) // // Scopetable section found, return SUCCESS if not writable // if (pSection->Characteristics & IMAGE_SCN_MEM_WRITE) return FAILURE; goto exit_success; } // // Scopetable never found in any section, issue an optional failure // return OPTIONAL_FAILURE; exit_success: // // Record the validated scopetable page in the valid list. Only allow one // thread at a time to modify the list, and discard any attempted updates // from other threads. // if (InterlockedExchange(&lModifying, 1) != 0) // another thread is already updating the table, skip this update return SUCCESS; for (iValid = nValidPages; iValid > 0; --iValid) if (rgValidPages[iValid - 1] == pScopePage) break; if (iValid == 0) { // normal case - page not found in table, add it as first entry // If page found, it was just added, so don't bother updating table iValid = min(VALID_SIZE-1, nValidPages); for (iValid2 = 0; iValid2 <= iValid; ++iValid2) { pTmp = rgValidPages[iValid2]; rgValidPages[iValid2] = pScopePage; pScopePage = pTmp; } if (nValidPages < VALID_SIZE) ++nValidPages; } InterlockedExchange(&lModifying, 0); return SUCCESS; }