Buffer overflows are a big one. Nothing in C is range-checked by default, so it's very easy to overwrite a buffer. There's a standard library function, gets()
, that cannot be stopped from overflowing the buffer, and should almost never be used.
There are some implementation-level techniques to hinder exploitation, such as scrambling heap blocks, but that won't stop buffer overflows in local buffers, which can often do interesting things like change the address a function will return to.
There is no good general solution in C. Many library functions have versions that will limit the amount they will write. although calculating that can be clumsy. There's software that can detect heap buffer overflows in test, as long as the appropriate test is run, and stack overflow will often show up as a crash in testing. Other than that, it's a matter of careful coding and code review.
A related issue is the problem of writing into a buffer too small by one character, forgetting that a C string that's n characters long requires n+1 characters in memory, because of the '\0'
terminator. If the attacker can manage to store a string without the terminator, any C function expecting a string will continue processing until it hits a zero byte, which could result in copying or outputting more information than is desired (or hitting protected memory for a DOS attack). The solution, again, is awareness, care, and code reviews.
There's another risk with the printf()
family. If you ever write char * str; ... printf(str);
, you're setting yourself up for problems if str
contains a '%' when printed. The %n
format directive allows printf()
to write into memory. The solution is printf("%s", str);
or puts(str);
. (Also, use the C99 snprintf()
instead of sprintf()
.)
Using unsigned integers, particularly as loop indexes, can cause problems. If you assign a small negative value to an unsigned, you get a large positive value. That can undermine things like processing only N instances of something, or in limited functions like strncpy()
. Examine all unsigned integers. You might want to avoid unsigned short
, since a large value in one of those will convert to a large positive value in an int
.
Don't forget that a character constant, in C, is actually an int
. Writing something like char c; while((c = getchar()) != EOF) ...
can easily fail, since EOF
won't be representable in a char
.
There's a lot more characteristic C mistakes I can think of, but these could cause security problems.