As mentioned by Theo de Raadt, Michael Shields, Tim Newsham, and
others the only real solution to buffer overflows is fixing the
defective programs. Anything else is a stopgap measure. This by no
means implies you should not use one or more of the alternatives as
fall back methods. The more defenses the better. Just don't depend
on them.
Again the thing to do is fix the offending code. The OpenBSD
project and some other teams have done a great job in this area.
They have systematically gone through their code base looking for
possible vulnerabilities. Not only have the fixed dozens of possible
holes, at the same time they have made their software more reliable.
Reliability and security go hand in hand.
Another option is to use a language that implements bounds
checking such as Ada, Java, or Perl. Richard Jones and Paul Kelly have
developed patches to gcc that implement some bounds checking in C
( http://www-ala.doc.ic.ac.uk/~phjk/BoundsChecking.html ). There also
exists commercial products such as Purify that attempt to help you
identify this and other bugs.
BSDI, OpenBSD and other operating systems have also modified the
linker to generate warning messages when a program uses dangerous
functions. This is no panacea since overflows not only occur in
standard libraries nor are all uses of dangerous functions incorrect,
but they will flag a program as suspicious.
The C compiler and/or lint could be modified to attempt to catch
obvious buffer overflows. The disadvantage is that not all possible
overflows can be detected and such a modifications are not trivial.
It's been suggested that you could modify the dangerous functions
in the C library to perform a stack integrity check before returning.
If the check does not pass, it would print a warning message and exit.
The downside is that this method will not catch all buffer overflows
and would introduce a performance penalty. Alexandre Snarskii has
developed just such a patch for FreeBSD.
( ftp://ftp.lucky.net/pub/unix/local/libc-letter )
The security axiom of "least privilege" would insure that even if
a program is compromised the privilege obtained by doing so is
minimal. This is the case in Trusted Systems or systems implementing
POSIX.1e (formerly POSIX.6) capabilities. Thomas Ptacek has developed
kernel patches for FreeBSD that shifted the privilege of binding to a
privileged port from root to a new uid. There is also a project
underway to make Linux POSIX.1e compliant. If such facilities are not
available the programs should be coded in such as way as to use their
privilege in small, easy to audit, sections of code and then discard
them as soon as possible by means of setreuid(2).
Marking the stack non-executable will catch most cut and paste
exploits but may break certain programs under some architectures
and will not catch exploits that do not depend on an executable stack.
As an example the Internet worm fingerd overflow overwrote the string
"/usr/bin/finger" with "/bin/sh" in the data segment. Casper Dik
developed a script that modifies the Solaris kernel so that the stack
no longer has execute permission. Solar Designer made available a
patch for Linux that performs a similar functionality. Search the
BugTraq archives for both of them.
Other more obscure solutions involve architecture changes as noted
by Shawn Instenes. Such as if only the CALL instructions could put
a return address on the stack, or having the OS enforce a "stack
window" which limits where you can write in the stack. Or as pointed
out by Zygo Blaxell you could allocate automatic variables on the heap
or some other place that is not the stack. David Holland noted that
new architectures could simply have multiple stacks: one for call frames,
and one for automatic storage.
It was suggested that the kernel should check in the exec system
call to determine if a shell was being executed. Such a check would
be of little use as the attacker may execute a file of any name in
the filesystem. This path of thinking does lead to an interesting
defense that I believe would be of great use and should be
implemented. Most daemons and utilities will not exec a program.
Most will simply fork. In such cases a system call that disabled any
further calls to exec would stop all exploits that attempted to use
the exec system call. Instead the kernel could print a warning
message and call exit. This scheme could be extended to define an
interface for a program to disable any system call that it did not
need thus reducing the attackers options.
To conclude, as we all know security is a risk management game.
There is no such thing as 100% security. Even if you perform code
reviews and QA testing bugs will creep into software. So your best
bet is a multilayered approach to security. Would you rather have a
vendor's or programmer's word that the code you are running in your
security sensitive installation is safe, or recompile said code using
a compiler that checks for buffer overflows and inserts bounds
checking code, and run it in a machine that marks its stack
non-executable and that allows you to turn off unnecessary system
calls? I think the choice is obvious.
Aleph One / aleph1@dfw.net
http://underground.org/
KeyID 1024/948FD6B5
Fingerprint EE C9 E8 AA CB AF 09 61 8C 39 EA 47 A8 6A B8 01