[ Team LiB ] |
13.1 Performing Error Handling13.1.1 ProblemMany security vulnerabilities are possible as a consequence of a programmer's omitting proper error handling. Developers find it extremely taxing to have to check error conditions continually. The unfortunate result is that these conditions often go forgotten. 13.1.2 SolutionIf you have the luxury of designing an API, design it in such a way that it minimizes the amount of error handling that is required, if at all possible. In addition, try to design APIs so that failures are not potentially critical if they go unhandled. Otherwise, appropriate exception handling can help you ensure that no errors that go unhandled will propagate dangerous error conditions. Use wrappers to convert functions that may fail with a traditional error code, so that they instead use exception handling. 13.1.3 DiscussionThere are plenty of situations in which assuming that a function returns successfully leads to a security vulnerability. One simple example is the case of using a secure random number generator to fill a buffer with random bytes. If the return value indicates failure, it's likely that no randomness was put into the buffer. If the programmer does not check the return code, predictable data will be used. In general, those functions that are not directly security-critical when their return value goes unchecked are often indirect security problems. (This can often happen with memory allocation functions, for example.) At the very least, such problems are often denial of service risks when they lead to a crash. One solution to this problem is to ensure that you always check return values from functions. That approach works in theory, but it is very burdensome on the programmer and also hard to validate. A more practical answer is to use exception handling. Using exception handling, any error conditions that the programmer does not explicitly handle will cause the program to terminate (which is generally a good idea, unless the premature termination somehow causes an insecure state). The problem with exception handling is that it does not solve the denial of service problem. If a developer forgets to handle a particular exception, the program will generally still terminate. Of course, the entire program can be wrapped by an exception handler that restarts the program or performs a similar action. In C++, exception handling is built into the language and should be familiar to many programmers. We will illustrate via example: try { somefunc( ); } catch (MyException &e) { // Recover from error type MyException. } catch (int e) { // Recover if we got an integer exception code. } The try block designates code we would like to execute that may throw an exception. It also says that if the code does throw an exception, the following catch blocks may be able to handle the exception. If an exception is not handled by one of the specified catch blocks, there may be some calling code that catches the exception. If no code wants to catch the exception, the program will abort. In C++, the catch block used is selected based on the static type of the exception thrown. Generally, if the exception is not a primitive type, we use the & to indicate that the exception value should be passed to the handler by reference instead of being copied. To raise an exception, we use the throw keyword: throw 12; // Throw an integer as an error. You can throw arbitrary objects in C++. Exception handling essentially acts as an alternate return mechanism, designed particularly for conditions that signify an abnormal state. You can also perform exception handling in C using macros. The safe string-handling library from Recipe 3.4 includes an exception-handling library named XXL. This exception-handling library is also available separately at http://www.zork.org/xxl/. The XXL library only allows you to throw integer exception codes. However, when throwing an exception, you may also pass arbitrary data in a void pointer. The XXL syntax attempts to look as much like C++ as possible, but is necessarily different because of the limitations of C. Here is an example: #include "xxl.h" /* Get definitions for exception handling. */ void sample(void) { TRY { somefunc( ); } CATCH(1) { /* Handle exception code 1. */ } CATCH(2) { /* Handle exception code 2. */ } EXCEPT { /* Handle all other exceptions... if you don't do this, they get propogated up to previous callers. */ } FINALLY { /* This code always gets called after an exception handler, even if no * exception gets thrown, or you raise a new exception. Additionally, if no * handler catches the error, this code runs before the exception gets * propogated. */ } END_TRY; There are a number of significant differences between XXL and C++ exception handling:
Once you have an exception-handling mechanism in place, we recommend that you avoid calling functions that can return an error when they fail. For example, consider the malloc( ) function, which can return NULL and set errno to ENOMEM when it fails (which only happens when not enough memory is available to complete the request). If you think you will simply want to bail whenever the process is out of memory, you could use the following wrapper: #include <stdlib.h> void *my_malloc(size_t sz) { void *res = malloc(sz); if (!res) { /* We could, instead, call an out of memory handler. */ fprintf(stderr, "Critical: out of memory! Aborting.\n"); abort( ); } return res; } If you prefer to give programmers the chance to handle the problem, you could throw an exception. In such a case, we recommend using the standard errno values as exception codes and using positive integers above 256 for application-specific exceptions. #include <stdlib.h> #include <errno.h> #include <xxl.h> #define EXCEPTION_OUT_OF_MEMORY (ENOMEM) void *my_malloc(size_t sz) { void *res = malloc(sz); /* We pass the amount of memory requested as extra data. */ if (!res) RAISE(EXCEPTION_OUT_OF_MEMORY, (void *)sz); return res; } 13.1.4 See AlsoXXL exception handling library for C: http://www.zork.org/xxl/ |
[ Team LiB ] |