Share via


More Fun with Integers

Just a quick note this morning to share something I found while finishing up SafeInt 3.0. This is something more helpful with 64-bit porting than with general security, though it does have some security side effects. Warning - heavy C++ programming geek content ahead -

If you declare a function like so:

bool Foo( unsigned long ul )

And then pass in a size_t, you'll be truncating when you start compiling the code as 64-bit. The compiler will warn you about this, but if you had done:

unsigned long cbSomething;
bool Baz( unsigned short s );

And then passed cbSomething to Baz, it gets truncated, and the compiler won't be any help about it. Similarly, if we passed a signed short to Foo, it gets sign extended, then cast to unsigned, potentially changing the value. The compiler won't warn about that, either. Given that integer mayhem figures strongly in both security issues and is job #1 in terms of doing a port to 64-bit, this standard behavior is less than ideal.

One of the things people asked for in SafeInt was a way to make a boxed version that wouldn't blow up unless you unboxed an invalid object. We have a version of this working internally, and I decided to make it part of SafeInt 3.0. This meant I needed to rewrite a whole lot of the internal functions to work like this:

// Boxed version
template< typename T, typename U>
bool Op( const T& t, const U& u, T& ret ) throw()

// regular SafeInt version
template< typename T, typename U>
void Op( const T& t, const U& u, T& ret )

You might wonder why I'm passing references instead of passing the integers by value. I did this largely to make 64-bit types perform a little better on 32-bit platforms, though if you read the standard carefully, it says those get passed as pointers under the covers anway. At any rate, it is slightly more desciptive C++, it seems to inline just a little bit more nicely, and I definitely had to make the return value a reference, so why not be consistent?

As I found out once I started doing the work of getting the test harness to compile, if you pass something by reference that isn't the right _size_, the compiler will not only complain, it will throw ERRORS, and you get to think about what you're doing, and whether you want to really be truncating or extending. It did complain about some things that were benign, and I had to create a couple of internal temporary variables when I needed to modify the input, but it also found some real bugs.

Any time I can get the compiler to tell me about bugs, as opposed to having to step through the code trying to figure out why something went sideways, or worse, stepping through the code trying to figure out why the instruction at 0x41414141 isn't executing because there's an exploit, then I'll take the compiler every time.

Comments

  • Anonymous
    March 26, 2007
    I wondere since then compiler does not warn about truncation? I am using VC 7.1 and it does great by saying: main.cpp(13) : warning C4244: 'argument' : conversion from 'unsigned long' to 'unsigned short', possible loss of data for this code: #include <iostream> #include <limits> using namespace std; bool Baz( unsigned short s ){ cout << s; return true; } int main(){ unsigned long x = numeric_limits<unsigned long>::max(); Baz(x); return 0; }

  • Anonymous
    March 26, 2007
    You're compiling at /W4 or /Wall. It will warn you about truncation, and IIRC, in the earlier compilers, it warns for C++ when it does not for C, though I don't see any difference in the most recent compiler. What it will not warn about is expansion, or signed-unsigned mis-match in a function call, nor does it warn about truncation or mismatch on assignment. If you use const references, any size difference will cause an error, though it still won't help with signed-unsigned.