Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

288 lines
10 KiB

  1. /***
  2. *eh3valid.c - Validate the registration node for _except_handler3
  3. *
  4. * Copyright (c) 2002, Microsoft Corporation. All rights reserved.
  5. *
  6. *Purpose:
  7. * Defines _ValidateEH3RN used to guard against hacker attacks which
  8. * attempt to use _except_handler3 to sidestep the .sxdata OS checks.
  9. *
  10. *Revision History:
  11. * 03-18-02 PML File created
  12. * 04-27-02 PML Perf: keep list of valid scopetables (vs7#522476)
  13. *
  14. *******************************************************************************/
  15. #include <windows.h>
  16. typedef struct _SCOPETABLE_ENTRY {
  17. DWORD EnclosingLevel;
  18. PVOID FilterFunc;
  19. PVOID HandlerFunc;
  20. } SCOPETABLE_ENTRY, *PSCOPETABLE_ENTRY;
  21. typedef struct _EH3_EXCEPTION_REGISTRATION {
  22. //
  23. // These are at negative offsets from the struct start:
  24. //
  25. // DWORD SavedESP;
  26. // PEXCEPTION_POINTERS XPointers;
  27. //
  28. // Common to all exception registration nodes:
  29. //
  30. struct _EH3_EXCEPTION_REGISTRATION *Next;
  31. PVOID ExceptionHandler;
  32. //
  33. // Private to _except_handler3's registration node:
  34. //
  35. PSCOPETABLE_ENTRY ScopeTable;
  36. DWORD TryLevel;
  37. } EH3_EXCEPTION_REGISTRATION, *PEH3_EXCEPTION_REGISTRATION;
  38. #define SAVED_ESP(pRN) (((PVOID *)pRN)[-2])
  39. #define EMPTY_LEVEL ((DWORD)-1)
  40. #define SUCCESS (1)
  41. #define FAILURE (0)
  42. #define OPTIONAL_FAILURE (-1)
  43. #define PAGE_SIZE 0x1000 // x86 uses 4K pages
  44. #define VALID_SIZE 16
  45. static PVOID rgValidPages[VALID_SIZE];
  46. static int nValidPages;
  47. static LONG lModifying; // nonzero if rgValidPages being modified
  48. /***
  49. *int _ValidateEH3RN - check validity of _except_handler3 registration node
  50. *
  51. *Purpose:
  52. * Attempt to intercept hacker attacks that try to use an artificial
  53. * _except_handler3 registration node to exploit a buffer overrun or
  54. * other security bug to inject exploit code.
  55. *
  56. *Entry:
  57. * pRN - pointer to _except_handler3 exception registration node
  58. *
  59. *Return:
  60. * >0 All checks passed, scopetable is validated.
  61. * 0 A required check failed and the exception should be rejected.
  62. * <0 An optional check failed and the exception should be rejected if
  63. * operating under these stricter tests.
  64. *
  65. * The optional checks only permit scopetables which are found inside
  66. * MEM_IMAGE pages that are currently unwritable, or started that way
  67. * according to the section descriptors.
  68. *
  69. *******************************************************************************/
  70. int _ValidateEH3RN(PEH3_EXCEPTION_REGISTRATION pRN)
  71. {
  72. PNT_TIB pTIB;
  73. PSCOPETABLE_ENTRY pScopeTable;
  74. DWORD level;
  75. int nFilters;
  76. MEMORY_BASIC_INFORMATION mbi;
  77. PIMAGE_DOS_HEADER pDOSHeader;
  78. PIMAGE_NT_HEADERS pNTHeader;
  79. PIMAGE_OPTIONAL_HEADER pOptHeader;
  80. DWORD rvaScopeTable;
  81. PIMAGE_SECTION_HEADER pSection;
  82. unsigned int iSection;
  83. PVOID pScopePage, pTmp;
  84. int iValid, iValid2;
  85. //
  86. // Scopetable pointer must be DWORD aligned
  87. //
  88. pScopeTable = pRN->ScopeTable;
  89. if (((DWORD_PTR)pScopeTable & 0x3) != 0)
  90. return FAILURE;
  91. //
  92. // Scopetable cannot be located on the stack
  93. //
  94. __asm {
  95. mov eax, fs:offset NT_TIB.Self
  96. mov pTIB, eax
  97. }
  98. if ((PVOID)pScopeTable >= pTIB->StackLimit &&
  99. (PVOID)pScopeTable < pTIB->StackBase)
  100. return FAILURE;
  101. //
  102. // If not nested in guarded block, then nothing left to check
  103. //
  104. if (pRN->TryLevel == EMPTY_LEVEL)
  105. return SUCCESS;
  106. //
  107. // Ensure all scopetable entries up to current try level are properly
  108. // nested (parent level must be the empty state or a lower level than
  109. // the one being checked).
  110. //
  111. nFilters = 0;
  112. for (level = 0; level <= pRN->TryLevel; ++level)
  113. {
  114. DWORD enclosing = pScopeTable[level].EnclosingLevel;
  115. if (enclosing != EMPTY_LEVEL && enclosing >= level)
  116. return FAILURE;
  117. if (pScopeTable[level].FilterFunc != NULL)
  118. ++nFilters;
  119. }
  120. //
  121. // If the scopetable had any __except filters, make sure the saved ESP
  122. // pointer is on the stack below the registration node
  123. //
  124. if (nFilters != 0 &&
  125. (SAVED_ESP(pRN) < pTIB->StackLimit ||
  126. SAVED_ESP(pRN) >= (PVOID)pRN) )
  127. return FAILURE;
  128. //
  129. // Before validating the scopetable pointer, check if we've already
  130. // validated a pointer on the same page, to avoid the expensive call
  131. // to VirtualQuery. If the page is found in the list of valid pages,
  132. // move it to the front of the list.
  133. //
  134. pScopePage = (PVOID)((DWORD_PTR)pScopeTable & ~(PAGE_SIZE - 1));
  135. for (iValid = 0; iValid < nValidPages; ++iValid)
  136. {
  137. if (rgValidPages[iValid] == pScopePage)
  138. {
  139. // Found - move entry to start of valid list, unless some other
  140. // thread is already updating the table
  141. if (iValid > 0 && InterlockedExchange(&lModifying, 1) == 0)
  142. {
  143. if (rgValidPages[iValid] != pScopePage)
  144. {
  145. // Entry has been moved by another thread; find it
  146. for (iValid = nValidPages - 1; iValid >= 0; --iValid)
  147. if (rgValidPages[iValid] == pScopePage)
  148. break;
  149. if (iValid < 0)
  150. {
  151. // Entry no longer on list, add it back
  152. if (nValidPages < VALID_SIZE)
  153. ++nValidPages;
  154. iValid = nValidPages - 1;
  155. }
  156. else if (iValid == 0)
  157. {
  158. // Entry already moved to correct position
  159. InterlockedExchange(&lModifying, 0);
  160. return SUCCESS;
  161. }
  162. }
  163. for (iValid2 = 0; iValid2 <= iValid; ++iValid2)
  164. {
  165. // Move elements before found entry back a position
  166. // and store entry in 1st position
  167. pTmp = rgValidPages[iValid2];
  168. rgValidPages[iValid2] = pScopePage;
  169. pScopePage = pTmp;
  170. }
  171. InterlockedExchange(&lModifying, 0);
  172. }
  173. return SUCCESS;
  174. }
  175. }
  176. //
  177. // It's an optional failure if the scopetable is not located inside an
  178. // image. If the scopetable is in an image, it must not be in a writable
  179. // section. First check if the memory is not currently marked writable.
  180. //
  181. if (VirtualQuery(pScopeTable, &mbi, sizeof mbi) == 0 ||
  182. mbi.Type != MEM_IMAGE)
  183. return OPTIONAL_FAILURE;
  184. if ((mbi.Protect & (PAGE_READWRITE |
  185. PAGE_WRITECOPY |
  186. PAGE_EXECUTE_READWRITE |
  187. PAGE_EXECUTE_WRITECOPY)) == 0)
  188. goto exit_success;
  189. //
  190. // Scopetable is inside an image, but in memory marked writable. Still
  191. // might be OK, if the memory started unwritable but was later changed
  192. // by VirtualProtect. See if we're in a normal NT PE executable image,
  193. // and if yes, find the image section holding the scopetable and check
  194. // its characteristics.
  195. //
  196. // This code assumes that calling VirtualQuery on a pointer anywhere inside
  197. // an image will return an AllocationBase equal to the start of the image,
  198. // i.e. a single VirtualAlloc was used to allocate the entire image range.
  199. // If we don't see a PE executable as expected, treat it as an optional
  200. // failure.
  201. //
  202. pDOSHeader = (PIMAGE_DOS_HEADER)mbi.AllocationBase;
  203. if (pDOSHeader->e_magic != IMAGE_DOS_SIGNATURE)
  204. return OPTIONAL_FAILURE;
  205. pNTHeader = (PIMAGE_NT_HEADERS)((PBYTE)pDOSHeader + pDOSHeader->e_lfanew);
  206. if (pNTHeader->Signature != IMAGE_NT_SIGNATURE)
  207. return OPTIONAL_FAILURE;
  208. pOptHeader = (PIMAGE_OPTIONAL_HEADER)&pNTHeader->OptionalHeader;
  209. if (pOptHeader->Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC)
  210. return OPTIONAL_FAILURE;
  211. //
  212. // Looks like a valid PE executable, find the section holding the
  213. // scopetable. We make no assumptions here about the sort order of the
  214. // section descriptors (though they always appear to be sorted by
  215. // ascending section RVA).
  216. //
  217. rvaScopeTable = (DWORD)((PBYTE)pScopeTable - (PBYTE)pDOSHeader);
  218. for (iSection = 0, pSection = IMAGE_FIRST_SECTION(pNTHeader);
  219. iSection < pNTHeader->FileHeader.NumberOfSections;
  220. ++iSection, ++pSection)
  221. {
  222. if (rvaScopeTable >= pSection->VirtualAddress &&
  223. rvaScopeTable < pSection->VirtualAddress +
  224. pSection->Misc.VirtualSize)
  225. //
  226. // Scopetable section found, return SUCCESS if not writable
  227. //
  228. if (pSection->Characteristics & IMAGE_SCN_MEM_WRITE)
  229. return FAILURE;
  230. goto exit_success;
  231. }
  232. //
  233. // Scopetable never found in any section, issue an optional failure
  234. //
  235. return OPTIONAL_FAILURE;
  236. exit_success:
  237. //
  238. // Record the validated scopetable page in the valid list. Only allow one
  239. // thread at a time to modify the list, and discard any attempted updates
  240. // from other threads.
  241. //
  242. if (InterlockedExchange(&lModifying, 1) != 0)
  243. // another thread is already updating the table, skip this update
  244. return SUCCESS;
  245. for (iValid = nValidPages; iValid > 0; --iValid)
  246. if (rgValidPages[iValid - 1] == pScopePage)
  247. break;
  248. if (iValid == 0)
  249. {
  250. // normal case - page not found in table, add it as first entry
  251. // If page found, it was just added, so don't bother updating table
  252. iValid = min(VALID_SIZE-1, nValidPages);
  253. for (iValid2 = 0; iValid2 <= iValid; ++iValid2)
  254. {
  255. pTmp = rgValidPages[iValid2];
  256. rgValidPages[iValid2] = pScopePage;
  257. pScopePage = pTmp;
  258. }
  259. if (nValidPages < VALID_SIZE)
  260. ++nValidPages;
  261. }
  262. InterlockedExchange(&lModifying, 0);
  263. return SUCCESS;
  264. }