1. Stack Overflow

    A few days ago, my dear friend Adrian Kosmaczewski published a wonderful article about C and C++ as the foundation of a good Cocoa coder. He was kind enough to let me read a draft, and i have to say that reading his excellent review of the memory architecture, I remind the olds days exploiting buffer overflows for fun. Fun? you may ask, and yes, it is fun to try to understand the inner workings of a complex machine. Now, this is also a terrible security risk. so let’s talk about how to avoid them.

    I encourage anyone to read the Adrian’s article I’ve refer in the previous paragraph to understand how the memory is deployed. Basically, the OS will keep different kind of data in different sections of RAM. For highly efficient storage of limited amounts of data, we have the stack, here the data is read and automatically removed from memory. For data that needs to be stored for more time, there is the heap. The heaps allows data to be read in any order, and it is not cleaned unless we instruct the system to do that.

    The basic idea of a stack buffer overflow is to write to a memory address outside the intended data structure. This kind of behavior is almost always triggered by a pernicious bug (and believe me, this kind of bugs is really hard to diagnose), but it is also possible to inject executable code into the running program and hijack it (no, I have never done that, and I will not do that again in the future).

    The most popular method to exploit a stack overflow is to overwrite the function return address with a pointer to data that the attacker has controlled.

    Because heaps are not used to store executable data, the exploitability of them might be not so exiting. And the very nature of heaps (they are no linear), makes them harder to exploit. So why to bother? Besides the obvious overwriting of data (set a NO to YES, to gain administrative privileges in a system, for instance), many running environments (including Objective-C) stores a table of function pointers in the heap. By overwriting one of these addresses, we are back to a world when an attacher can execute his or her own code.

    The sad truth is that, whenever an app ask input from a user, there is a potential for the user to enter data that might exploits these issues. For instance, if the input is larger than expected, and the program fails to truncates it, the data will overwrite other data in the memory.

    The first line of defense to any security problem in a Unix-like operating system is to run with the lowest privileges available to perform an action. If you don’t need administrative privileges, then don’t ask for them.

    And since most of the inputs are going to be strings, beware of them. For instance, avoid at all cost functions that merely writes the entire string into memory, without checking the length (strcpy), or the ones that truncates the string to the appropriate length, but fails to add the the terminating null character (strncpy).

    Use this Instead of this
    strlcat strcat
    strlcpy strcpy
    strlcat strncat
    strlcpy strncpy
    snprintf sprintf
    vsnprintf vsprintf
    fgets gets

    There is even an easier way to deal with strings, let other do the nasty thing, and use higher level interfaces. For instance, whenever possible, use NSString and you are safe. Remember that you can easily convert NSStrings to C strings to pass to pure C routines, and if you are working with Foundation, then you have the option to use the toll-free bridged CFString.

    If you are going to introduce a buffer size, don’t you ever hardcode the value, use a #define instead. It is easy to forget to change an instance of your hardcoded value. And don’t you ever use a signed type. signed types allocates a large chunk to store negative values in the upper half of the range, so we are allowing attackers to use larger values (**). If the buffer size is calculated dynamically, it should be tested against minimum and maximum boundaries. For instance, malloc(0) will returns a pointer to a small block, and then the code will run without producing any error if the attacker manage to produce a 0 in mod 2^32.

    size_t bytes = n * m;
    if ( n > 0 && m > 0 && int_max/n >= m ) {
        ... /* allocate “bytes” space */
    }
    

    ** The arithmetic is quite simple, a negative number is represented by inverting all the bits of the binary representation and adding 1, and then a 1 in the most-significant bit to indicate its negative nature. Thus:

    0x7fffffff = 2147483647
    0x80000000 = -2147483648
    

    Therefore:

    int 2147483647 + 1 = - 2147483648
    

Notes

  1. volonbolon posted this