Structured exception handling (SEH) is a useful set of functionality included in the Win32 API that lets C code handle errors in your application in much the same way that C++ handles exceptions. (To learn more about exception handling with C++ code, read the Win32 software development kit—SDK—topic "Using Structured Exception Handling with C++."
Exception handling in C consists of three blocks: try, finally, and except. You encase any code for which you want to handle exceptions in a __try block; any code you place in a __finally block always executes no matter how you exit the __try block; and the __except block lets you customize how you handle exceptions. Let’s look at a quick example.
buf2 = malloc(30);
__except( MyHandler( GetExceptionCode() ))
printf( "Ended up in the handler - whoops!\n" );
if( buf2 != NULL )
free( buf2 );
If an exception occurs while any of the code in the __try block is executing, the OS starts looking for an exception handler. Because most code doesn’t have its own exception handler, the stack unwinds until you hit the handler that the OS puts around your application for you, and you see the familiar pop-up message telling you that your application has an unhandled exception at a certain address. I provided a handler in the code example, and the next code to execute will be your MyHandler() function, which takes the exception code as an int, and is expected to return an int. There are three possible returns:
- EXCEPTION_CONTINUE_EXECUTION—this return ignores the exception and continues execution at the point where the exception occurred. You rarely want to use this option.
- EXCEPTION_CONTINUE_SEARCH—this return tells the OS that you don’t know how to deal with this exception and to start looking for a higher-level exception handler.
- EXCEPTION_EXECUTE_HANDLER—this return causes the code in your __except block to execute. One good way to use this return is to log extended debugging information that you can use to help diagnose problems that only occur in the field. If you want, you can now throw the exception to a higher-level exception handler by calling RaiseException().
Regardless of how you handle the exception, any time the code exits the __try block, the __finally block executes. In the code example, you use the __finally block to make sure that you never leave the function without freeing the memory you allocated (which is about all this particular example does correctly). In my last article , I used a goto statement to ensure that no matter where I needed to exit a fairly complicated function, that the code cleaned up all the memory I’d allocated. If I’d wanted to be a little more elegant, I could have enclosed my code in a __try block, then used a __leave to cause the code to jump into the __finally block. In most cases, these two constructs are equivalent. One benefit is that your code reviewer will usually look at __try, __finally, and __leave and think you’re pretty smart. But the real and useful benefit to using this approach over goto is that no matter how you leave the __try block—whether it is just by getting to the end of the section, an explicit __leave call, or by raising an exception—the __finally block will always execute. If you have opened Server Message Block (SMB) connections or accessed some other system resource (e.g., a shared mutex or semaphore), you’ll get a chance to at least try to clean up shared resources. Remember that exceptions can occur in a system API or a third-party library, so the problem might not even be in your code. This type of construct also lets you implement the same type of cleanup in C as destructors allow for in C++.
Now that I’ve explained how SEH works and why you should use it, let’s look at when you shouldn’t use it and why. In my article on buffer overruns, I explained how attackers can use code like that in the code example to overwrite the stack and control your application. Now that you have enclosed the offending code in a try-except block, you’re safe—right? Not at all! One problem is that the exception handler pointer also resides on the stack. Anything that can overwrite your return address can also overwrite your exception handler pointer. A buffer overrun just gives attackers two ways to make your code do what they like—either on exit to the function or at a point where an exception occurs.
When I was working on the ISS Scanner, we used SEH to help us find problems that we could only reproduce at the customer’s site, and the exception dumps often took me right to the line of code that was producing the error. However, don’t assume that SEH will help you protect your code against an attacker. Although SEH has many benefits, it's no substitute for writing solid, secure code in the first place. You can't use SEH in place of proper error checking and attention to detail. Ideally, you should use both to make your code more robust.