Another Look at the SafeInt Class

 

David LeBlanc
Microsoft Office

May 2, 2005

Summary: David LeBlanc revisits the SafeInt Class and discusses relevant updates since his last article on this topic. (4 printed pages)

A note from Michael Howard   My good friend and co-author David LeBlanc has probably done more research into integer overflow vulnerabilities than anyone on the planet. In this article, he offers up more issues and more words of wisdom into this very serious security defect.

View the sample code for this article.

Some SafeInt History

Over the past year or so, integers have continued to show us some new tricks. For example, here's an interesting bug:

template <typename T>
unsigned _int64 AbsVal(T t)
{
   if(t < 0)
      return (unsigned _int64)-t;

   return t;
}

Where's the bug? Recall that in my original article, Integer Handling with the C++ SafeInt Class, I pointed out that nearly every operator can cast the result type into a different type than what you started with, and the unary negation operator is no exception. For any integer less than 32-bits, or a signed 32-bit integer, the return type of –t would be an int.

If the incoming value is the smallest 8-bit signed integer, which would be -128, writing –t would be equivalent to writing -(int)t. Let's step through it and see what happens:

t = -128 = 0x80
(int)t = -128 = 0xffffff80
-(int)t = 0x00000080 = 128

So far, so good, but what if the input type was the minimum int? The implicit cast results is an int, but negating the minimum integer results in the same value, and worse yet, it's still negative! Let's take a look at what happens:

t = (int)0x80000000
-t = (int)0x80000000
(unsigned _int64)-t = (unsigned _int64)(_int64)-t
                               = (unsigned _int64)0xffffffff80000000
                               = 0xffffffff80000000

The answer we wanted was 0x80000000, but that isn't what we have. This is an example of how implicit casts and signed to unsigned casts can gang up on you to create problematical code.

Along with finding a few new bugs, this update to SafeInt contains a number of improvements:

  • Better support for using Win32 exceptions, or creating your own exception handling mechanism.
  • Casting operators for all the basic types. This means that you can pass a SafeInt into a function call, and it will do the right thing.
  • Better inlining because the empty destructor is now removed. Read the header comments for more details.
  • Replaced MaxInt, MinInt, and several other functions with an IntTraits class. This will result in much better debug performance.
  • Instead of throwing exceptions inside the code, a function call is used to handle exceptions. This gives you much more flexibility, and results in much smaller optimized code.
  • Binary operators have been re-written to better handle corner cases.
  • Incorrect return sign in the case of negative inputs to the modulus operator have been fixed.
  • Numerous corner-case bugs are fixed in both the division and modulus operators for the case of U op SafeInt<T>.
  • Limited support for pointer arithmetic has been added.
  • Support for operations of type U op= SafeInt<T> has been added. This is the one exception to the rule that SafeInt should be contagious.

The biggest change is that the class has been largely rewritten to take advantage of partial template specialization. Partial template specialization is something we can use to get the compiler to pick the right set of code to run at compile time instead of having lots of compile-time constants in our code. The Visual Studio 7.0 and later compilers have support for partial template specialization. Let's use the bug I showed you at the beginning of the article as an example. The first thing we want to do is create a class containing an enum where the value of the enum is resolved at compile time. We'll do this in the AbsValueMethod class below.

template <typename T>
class AbsValueMethod
{
public:
   enum
   {
      method = (T)-1 > 0 ? 0 : //all unsigned cases
                 sizeof(T) == 4 ? 1 : //problem case of signed int32
             2; //all other signed ints
   };
};

As you can see, the value of method is set to 0, 1, or 2, depending on whether we have the easy unsigned case, the problem case of a signed 32-bit int, or the less problematical case of any other size int. Now let's look at how we can use this class:

//forward declaration
template <typename T, int> class AbsValueHelper;

//easy case of unsigned
template <typename T> class AbsHelper template <typename T, 0>
{
public:
   static unsigned _int64 Abs(T t)
   {
      return (unsigned _int64)t;
   }
};

//specialized version for 32-bit ints
template <typename T> class AbsHelper template <typename T, 1>
{
public:
   static unsigned _int64 Abs(T t)
   {
      if(t < 0)
         return (unsigned _int64)(unsigned _int32)-t;

      return (unsigned _int64)t;
   }
};

//all other signed ints
template <typename T> class AbsHelper template <typename T, 2>
{
public:
   static unsigned _int64 Abs(T t)
   {
      if(t < 0)
         return (unsigned _int64)-t;

      return (unsigned _int64)t;
   }
};

As you can see, we now have a class that will contain exactly the right code for each type, and we won't waste time evaluating compile-time constants in our debug code. When you step into each function, it's also obvious what's happening, and it isn't cluttered up with a maze of compile-time conditionals. Finally, our resulting correct function is below.

template <typename T>
unsigned _int64 AbsVal(T t)
{
   return AbsHelper<T, AbsValueMethod<T>::method>::Abs(t);
}

Conclusion

That covers the updates to the SafeInt class since my last article. I'd like to thank Hannes Reuscher, Chris White, and Atin Bansal for helping make this a better class, and especially Atin for writing a very solid test rig to validate correctness of the class.

David LeBlanc is a Security Architect in the Office group at Microsoft, and is the coauthor of Writing Secure Code, now in its second edition. He has worked on software and network security throughout his professional career.