• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
GetTopRegistration()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
TestSEHChainSane()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 
AccessViolationCrash()71 void AccessViolationCrash() {
72   volatile char* null = NULL;
73   *null = '\0';
74 }
75 
76 // A simple crash over the exception barrier
CrashOverExceptionBarrier()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.
CrashOnManualSEHBarrierHandler()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:
ExceptionBarrierTest()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
SetUp()114   virtual void SetUp() {
115     ExceptionBarrierConfig::set_enabled(true);
116     ExceptionBarrierCustomHandler::set_custom_handler(&ExceptionHandler);
117     s_handled_ = false;
118 
119     TestSEHChainSane();
120   }
121 
TearDown()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.
ExceptionHandler(EXCEPTION_POINTERS * ptrs)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 
TestExceptionExceptionBarrierHandler()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 
TEST_F(ExceptionBarrierTest,RegisterUnregister)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 
TEST_F(ExceptionBarrierTest,ExceptionBarrierHandler)189 TEST_F(ExceptionBarrierTest, ExceptionBarrierHandler) {
190   EXPECT_TRUE(TestExceptionExceptionBarrierHandler());
191   EXPECT_TRUE(s_handled_);
192 }
193 
TestExceptionBarrier()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 
TEST_F(ExceptionBarrierTest,HandlesAccessViolationException)206 TEST_F(ExceptionBarrierTest, HandlesAccessViolationException) {
207   TestExceptionBarrier();
208   EXPECT_TRUE(s_handled_);
209 }
210 
RecurseAndCrash(int depth)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.
TestRegularChaining(EXCEPTION_REGISTRATION * top)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 
RecurseAndCrashOverBarrier(int depth,bool crash)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.
TestExceptionBarrierChaining(EXCEPTION_REGISTRATION * top)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 
TestChaining()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.
TEST_F(ExceptionBarrierTest,SEHChainIsSaneAfterException)354 TEST_F(ExceptionBarrierTest, SEHChainIsSaneAfterException) {
355   EXPECT_TRUE(TestChaining());
356 }
357 
358 }  // namespace
359