[email protected] | dcdcfa0 | 2010-04-28 20:59:03 | [diff] [blame] | 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 |
[email protected] | f55bd486 | 2010-05-27 15:38:07 | [diff] [blame] | 32 | ASSERT_NE(0u, ::VirtualQuery(&info, &info, sizeof(info))); |
[email protected] | dcdcfa0 | 2010-04-28 20:59:03 | [diff] [blame] | 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 |
[email protected] | f55bd486 | 2010-05-27 15:38:07 | [diff] [blame] | 63 | ASSERT_NE(0u, ::VirtualQuery(curr->handler, &info, sizeof(info))); |
[email protected] | dcdcfa0 | 2010-04-28 20:59:03 | [diff] [blame] | 64 | wchar_t module_filename[MAX_PATH]; |
[email protected] | f55bd486 | 2010-05-27 15:38:07 | [diff] [blame] | 65 | ASSERT_NE(0u, ::GetModuleFileName( |
[email protected] | dcdcfa0 | 2010-04-28 20:59:03 | [diff] [blame] | 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() { |
[email protected] | 7235431 | 2010-05-01 02:10:06 | [diff] [blame] | 78 | ExceptionBarrierCustomHandler barrier; |
[email protected] | dcdcfa0 | 2010-04-28 20:59:03 | [diff] [blame] | 79 | |
| 80 | TestSEHChainSane(); |
| 81 | |
| 82 | AccessViolationCrash(); |
| 83 | |
| 84 | TestSEHChainSane(); |
| 85 | } |
| 86 | |
| 87 | #pragma warning(push) |
[email protected] | 7235431 | 2010-05-01 02:10:06 | [diff] [blame] | 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, |
[email protected] | dcdcfa0 | 2010-04-28 20:59:03 | [diff] [blame] | 93 | // then crash to invoke it. |
| 94 | __declspec(naked) void CrashOnManualSEHBarrierHandler() { |
| 95 | __asm { |
[email protected] | 7235431 | 2010-05-01 02:10:06 | [diff] [blame] | 96 | push ExceptionBarrierCallCustomHandler |
[email protected] | dcdcfa0 | 2010-04-28 20:59:03 | [diff] [blame] | 97 | push FS:0 |
| 98 | mov FS:0, esp |
| 99 | call AccessViolationCrash |
| 100 | ret |
| 101 | } |
| 102 | } |
| 103 | #pragma warning(pop) |
| 104 | |
[email protected] | 7235431 | 2010-05-01 02:10:06 | [diff] [blame] | 105 | |
[email protected] | dcdcfa0 | 2010-04-28 20:59:03 | [diff] [blame] | 106 | class ExceptionBarrierTest: public testing::Test { |
[email protected] | 7235431 | 2010-05-01 02:10:06 | [diff] [blame] | 107 | public: |
| 108 | ExceptionBarrierTest() { |
[email protected] | dcdcfa0 | 2010-04-28 20:59:03 | [diff] [blame] | 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() { |
[email protected] | 7235431 | 2010-05-01 02:10:06 | [diff] [blame] | 115 | ExceptionBarrierConfig::set_enabled(true); |
| 116 | ExceptionBarrierCustomHandler::set_custom_handler(&ExceptionHandler); |
[email protected] | dcdcfa0 | 2010-04-28 20:59:03 | [diff] [blame] | 117 | s_handled_ = false; |
| 118 | |
| 119 | TestSEHChainSane(); |
| 120 | } |
| 121 | |
| 122 | virtual void TearDown() { |
[email protected] | dcdcfa0 | 2010-04-28 20:59:03 | [diff] [blame] | 123 | TestSEHChainSane(); |
[email protected] | 7235431 | 2010-05-01 02:10:06 | [diff] [blame] | 124 | ExceptionBarrierCustomHandler::set_custom_handler(NULL); |
| 125 | ExceptionBarrierConfig::set_enabled(false); |
[email protected] | dcdcfa0 | 2010-04-28 20:59:03 | [diff] [blame] | 126 | } |
| 127 | |
| 128 | // The exception notification callback, sets the handled flag. |
| 129 | static void CALLBACK ExceptionHandler(EXCEPTION_POINTERS* ptrs) { |
| 130 | TestSEHChainSane(); |
[email protected] | dcdcfa0 | 2010-04-28 20:59:03 | [diff] [blame] | 131 | s_handled_ = true; |
| 132 | } |
| 133 | |
[email protected] | dcdcfa0 | 2010-04-28 20:59:03 | [diff] [blame] | 134 | // Flag is set by handler |
| 135 | static bool s_handled_; |
| 136 | }; |
| 137 | |
[email protected] | 7235431 | 2010-05-01 02:10:06 | [diff] [blame] | 138 | bool ExceptionBarrierTest::s_handled_ = false; |
[email protected] | dcdcfa0 | 2010-04-28 20:59:03 | [diff] [blame] | 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); |
[email protected] | 0fa0fbc | 2010-10-12 04:32:05 | [diff] [blame^] | 171 | EXPECT_EQ(&ExceptionBarrierHandler, registration.handler); |
[email protected] | dcdcfa0 | 2010-04-28 20:59:03 | [diff] [blame] | 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) { |
[email protected] | 7235431 | 2010-05-01 02:10:06 | [diff] [blame] | 271 | ExceptionBarrierCustomHandler barrier; |
[email protected] | dcdcfa0 | 2010-04-28 20:59:03 | [diff] [blame] | 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 | |
[email protected] | e898294 | 2010-04-30 19:26:08 | [diff] [blame] | 358 | } // namespace |