Home | History | Annotate | Download | only in test
      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(&registration, ExceptionBarrierHandler);
    170   EXPECT_EQ(GetTopRegistration(), &registration);
    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(&registration);
    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