1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "gtest/gtest.h" 6 #include "chrome_frame/exception_barrier.h" 7 8 namespace { 9 10 // retrieves the top SEH registration record 11 __declspec(naked) EXCEPTION_REGISTRATION* GetTopRegistration() { 12 __asm { 13 mov eax, FS:0 14 ret 15 } 16 } 17 18 // This function walks the SEH chain and attempts to ascertain whether it's 19 // sane, or rather tests it for any obvious signs of insanity. 20 // The signs it's capable of looking for are: 21 // # Is each exception registration in bounds of our stack 22 // # Is the registration DWORD aligned 23 // # Does each exception handler point to a module, as opposed to 24 // e.g. into the stack or never-never land. 25 // # Do successive entries in the exception chain increase 26 // monotonically in address 27 void TestSEHChainSane() { 28 // get the skinny on our stack segment 29 MEMORY_BASIC_INFORMATION info = { 0 }; 30 // Note that we pass the address of the info struct just as a handy 31 // moniker to anything at all inside our stack allocation 32 ASSERT_NE(0u, ::VirtualQuery(&info, &info, sizeof(info))); 33 34 // The lower bound of our stack. 35 // We use the address of info as a lower bound, this assumes that if this 36 // function has an SEH handler, it'll be higher up in our invocation 37 // record. 38 EXCEPTION_REGISTRATION* limit = 39 reinterpret_cast<EXCEPTION_REGISTRATION*>(&info); 40 // the very top of our stack segment 41 EXCEPTION_REGISTRATION* top = 42 reinterpret_cast<EXCEPTION_REGISTRATION*>( 43 reinterpret_cast<char*>(info.BaseAddress) + info.RegionSize); 44 45 EXCEPTION_REGISTRATION* curr = GetTopRegistration(); 46 // there MUST be at least one registration 47 ASSERT_TRUE(NULL != curr); 48 49 EXCEPTION_REGISTRATION* prev = NULL; 50 const EXCEPTION_REGISTRATION* kSentinel = 51 reinterpret_cast<EXCEPTION_REGISTRATION*>(0xFFFFFFFF); 52 for (; kSentinel != curr; prev = curr, curr = curr->prev) { 53 // registrations must increase monotonically 54 ASSERT_TRUE(curr > prev); 55 // Check it's in bounds 56 ASSERT_GE(top, curr); 57 ASSERT_LT(limit, curr); 58 59 // check for DWORD alignment 60 ASSERT_EQ(0, (reinterpret_cast<UINT_PTR>(prev) & 0x00000003)); 61 62 // find the module hosting the handler 63 ASSERT_NE(0u, ::VirtualQuery(curr->handler, &info, sizeof(info))); 64 wchar_t module_filename[MAX_PATH]; 65 ASSERT_NE(0u, ::GetModuleFileName( 66 reinterpret_cast<HMODULE>(info.AllocationBase), 67 module_filename, ARRAYSIZE(module_filename))); 68 } 69 } 70 71 void AccessViolationCrash() { 72 volatile char* null = NULL; 73 *null = '\0'; 74 } 75 76 // A simple crash over the exception barrier 77 void CrashOverExceptionBarrier() { 78 ExceptionBarrierCustomHandler barrier; 79 80 TestSEHChainSane(); 81 82 AccessViolationCrash(); 83 84 TestSEHChainSane(); 85 } 86 87 #pragma warning(push) 88 // Inline asm assigning to 'FS:0' : handler not registered as safe handler 89 // This warning is in error (the compiler can't know that we register the 90 // handler as a safe SEH handler in an .asm file) 91 #pragma warning(disable:4733) 92 // Hand-generate an SEH frame implicating the ExceptionBarrierCallCustomHandler, 93 // then crash to invoke it. 94 __declspec(naked) void CrashOnManualSEHBarrierHandler() { 95 __asm { 96 push ExceptionBarrierCallCustomHandler 97 push FS:0 98 mov FS:0, esp 99 call AccessViolationCrash 100 ret 101 } 102 } 103 #pragma warning(pop) 104 105 106 class ExceptionBarrierTest: public testing::Test { 107 public: 108 ExceptionBarrierTest() { 109 } 110 111 // Install an exception handler for the ExceptionBarrier, and 112 // set the handled flag to false. This allows us to see whether 113 // the ExceptionBarrier gets to handle the exception 114 virtual void SetUp() { 115 ExceptionBarrierConfig::set_enabled(true); 116 ExceptionBarrierCustomHandler::set_custom_handler(&ExceptionHandler); 117 s_handled_ = false; 118 119 TestSEHChainSane(); 120 } 121 122 virtual void TearDown() { 123 TestSEHChainSane(); 124 ExceptionBarrierCustomHandler::set_custom_handler(NULL); 125 ExceptionBarrierConfig::set_enabled(false); 126 } 127 128 // The exception notification callback, sets the handled flag. 129 static void CALLBACK ExceptionHandler(EXCEPTION_POINTERS* ptrs) { 130 TestSEHChainSane(); 131 s_handled_ = true; 132 } 133 134 // Flag is set by handler 135 static bool s_handled_; 136 }; 137 138 bool ExceptionBarrierTest::s_handled_ = false; 139 140 bool TestExceptionExceptionBarrierHandler() { 141 TestSEHChainSane(); 142 __try { 143 CrashOnManualSEHBarrierHandler(); 144 return false; // not reached 145 } __except(EXCEPTION_ACCESS_VIOLATION == GetExceptionCode() ? 146 EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { 147 TestSEHChainSane(); 148 return true; 149 } 150 151 return false; // not reached 152 } 153 154 typedef EXCEPTION_DISPOSITION 155 (__cdecl* ExceptionBarrierHandlerFunc)( 156 struct _EXCEPTION_RECORD* exception_record, 157 void* establisher_frame, 158 struct _CONTEXT* context, 159 void* reserved); 160 161 TEST_F(ExceptionBarrierTest, RegisterUnregister) { 162 // Assert that registration modifies the chain 163 // and the registered record as expected 164 EXCEPTION_REGISTRATION* top = GetTopRegistration(); 165 ExceptionBarrierHandlerFunc handler = top->handler; 166 EXCEPTION_REGISTRATION* prev = top->prev; 167 168 EXCEPTION_REGISTRATION registration; 169 ::RegisterExceptionRecord(®istration, ExceptionBarrierHandler); 170 EXPECT_EQ(GetTopRegistration(), ®istration); 171 EXPECT_EQ(&ExceptionBarrierHandler, registration.handler); 172 EXPECT_EQ(top, registration.prev); 173 174 // test the whole chain for good measure 175 TestSEHChainSane(); 176 177 // Assert that unregistration restores 178 // everything as expected 179 ::UnregisterExceptionRecord(®istration); 180 EXPECT_EQ(top, GetTopRegistration()); 181 EXPECT_EQ(handler, top->handler); 182 EXPECT_EQ(prev, top->prev); 183 184 // and again test the whole chain for good measure 185 TestSEHChainSane(); 186 } 187 188 189 TEST_F(ExceptionBarrierTest, ExceptionBarrierHandler) { 190 EXPECT_TRUE(TestExceptionExceptionBarrierHandler()); 191 EXPECT_TRUE(s_handled_); 192 } 193 194 bool TestExceptionBarrier() { 195 __try { 196 CrashOverExceptionBarrier(); 197 } __except(EXCEPTION_ACCESS_VIOLATION == GetExceptionCode() ? 198 EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { 199 TestSEHChainSane(); 200 return true; 201 } 202 203 return false; 204 } 205 206 TEST_F(ExceptionBarrierTest, HandlesAccessViolationException) { 207 TestExceptionBarrier(); 208 EXPECT_TRUE(s_handled_); 209 } 210 211 void RecurseAndCrash(int depth) { 212 __try { 213 __try { 214 if (0 == depth) 215 AccessViolationCrash(); 216 else 217 RecurseAndCrash(depth - 1); 218 219 TestSEHChainSane(); 220 } __except(EXCEPTION_CONTINUE_SEARCH) { 221 TestSEHChainSane(); 222 } 223 } __finally { 224 TestSEHChainSane(); 225 } 226 } 227 228 // This test exists only for comparison with TestExceptionBarrierChaining, and 229 // to "document" how the SEH chain is manipulated under our compiler. 230 // The two tests are expected to both fail if the particulars of the compiler's 231 // SEH implementation happens to change. 232 bool TestRegularChaining(EXCEPTION_REGISTRATION* top) { 233 // This test relies on compiler-dependent details, notably we rely on the 234 // compiler to generate a single SEH frame for the entire function, as 235 // opposed to e.g. generating a separate SEH frame for each __try __except 236 // statement. 237 EXCEPTION_REGISTRATION* my_top = GetTopRegistration(); 238 if (my_top == top) 239 return false; 240 241 __try { 242 // we should have the new entry in effect here still 243 if (GetTopRegistration() != my_top) 244 return false; 245 } __except(EXCEPTION_EXECUTE_HANDLER) { 246 return false; 247 } 248 249 __try { 250 AccessViolationCrash(); 251 return false; // not reached 252 } __except(EXCEPTION_EXECUTE_HANDLER) { 253 // and here 254 if (GetTopRegistration() != my_top) 255 return false; 256 } 257 258 __try { 259 RecurseAndCrash(10); 260 return false; // not reached 261 } __except(EXCEPTION_EXECUTE_HANDLER) { 262 // we should have unrolled to our frame by now 263 if (GetTopRegistration() != my_top) 264 return false; 265 } 266 267 return true; 268 } 269 270 void RecurseAndCrashOverBarrier(int depth, bool crash) { 271 ExceptionBarrierCustomHandler barrier; 272 273 if (0 == depth) { 274 if (crash) 275 AccessViolationCrash(); 276 } else { 277 RecurseAndCrashOverBarrier(depth - 1, crash); 278 } 279 } 280 281 // Test that ExceptionBarrier doesn't molest the SEH chain, neither 282 // for regular unwinding, nor on exception unwinding cases. 283 // 284 // Note that while this test shows the ExceptionBarrier leaves the chain 285 // sane on both those cases, it's not clear that it does the right thing 286 // during first-chance exception handling. I can't think of a way to test 287 // that though, because first-chance exception handling is very difficult 288 // to hook into and to observe. 289 static bool TestExceptionBarrierChaining(EXCEPTION_REGISTRATION* top) { 290 TestSEHChainSane(); 291 292 // This test relies on compiler-dependent details, notably we rely on the 293 // compiler to generate a single SEH frame for the entire function, as 294 // opposed to e.g. generating a separate SEH frame for each __try __except 295 // statement. 296 // Unfortunately we can't use ASSERT macros here, because they create 297 // temporary objects and the compiler doesn't grok non POD objects 298 // intermingled with __try and other SEH constructs. 299 EXCEPTION_REGISTRATION* my_top = GetTopRegistration(); 300 if (my_top == top) 301 return false; 302 303 __try { 304 // we should have the new entry in effect here still 305 if (GetTopRegistration() != my_top) 306 return false; 307 } __except(EXCEPTION_EXECUTE_HANDLER) { 308 return false; // Not reached 309 } 310 311 __try { 312 CrashOverExceptionBarrier(); 313 return false; // Not reached 314 } __except(EXCEPTION_EXECUTE_HANDLER) { 315 // and here 316 if (GetTopRegistration() != my_top) 317 return false; 318 } 319 TestSEHChainSane(); 320 321 __try { 322 RecurseAndCrashOverBarrier(10, true); 323 return false; // not reached 324 } __except(EXCEPTION_EXECUTE_HANDLER) { 325 // we should have unrolled to our frame by now 326 if (GetTopRegistration() != my_top) 327 return false; 328 } 329 TestSEHChainSane(); 330 331 __try { 332 RecurseAndCrashOverBarrier(10, false); 333 334 // we should have unrolled to our frame by now 335 if (GetTopRegistration() != my_top) 336 return false; 337 } __except(EXCEPTION_EXECUTE_HANDLER) { 338 return false; // not reached 339 } 340 TestSEHChainSane(); 341 342 // success. 343 return true; 344 } 345 346 static bool TestChaining() { 347 EXCEPTION_REGISTRATION* top = GetTopRegistration(); 348 349 return TestRegularChaining(top) && TestExceptionBarrierChaining(top); 350 } 351 352 // Test that the SEH chain is unmolested by exception barrier, both under 353 // regular unroll, and under exception unroll. 354 TEST_F(ExceptionBarrierTest, SEHChainIsSaneAfterException) { 355 EXPECT_TRUE(TestChaining()); 356 } 357 358 } // namespace 359