An HTML version of this document is at
.
How to find security holes
==========================
Note: I haven't found any security holes, so take this
with a pillar of salt. Also, this document is poorly
organized. Suggestions for better organization and
terminology are welcomed
with open arms. Reports of any errors are urgently
needed.
If a program has a bug in it that manifests under
extreme circumstances, then normally,
it's a minor annoyance. Usually, you can just avoid
the extreme circumstances, and the bug isn't a problem.
You could duplicate the effect of tickling the bug
by writing your own program, if you wanted to.
But sometimes programs sit on security boundaries. They
take input from other programs that don't have the same
access that they do.
Some examples: your mailreader takes input from anyone
you get mail from, and it has access to your display,
which they probably don't. The TCP/IP stack of any
computer connected to the Internet takes input from anyone
on the Internet, and usually has access to everything on
the computer, which most people on the Internet certainly
don't.
Any program that does such things has to be careful. If
it has any bugs in it, it could potentially end up allowing
other people -- untrusted people -- to do things they're
not allowed to do. A bug that has this property is called
a "hole", or more formally, a "vulnerability".
Here are some common categories of holes.
Psychological problems
----------------------
When you're writing a normal piece of software, your purpose
is to make certain things possible, if the user does things
correctly. When you're writing a security-sensitive piece
of software, you also have to make certain things *impossible*,
no matter what any untrusted user does. This means that
certain parts of your program must function properly under a
wide range of circumstances.
Cryptologists and real-time programmers are familiar with
doing things this way.
Most other programmers aren't, and habits of mind from their
normal-software work tend to make their software insecure.
Change of role hole
-------------------
A lot of holes come from running programs in different
environments. What was originally a minor annoyance -- or
sometimes even a convenience -- becomes a security hole.
For example, suppose you have a PostScript interpreter that
was originally intended to let you preview your documents
before printing them. This is not a security-sensitive role;
the PostScript interpreter doesn't have any capabilities that
you don't. But suppose you start using it to view documents
from other people, people you don't know, even untrustworthy
people. Suddenly, the presence of PostScript's file access
operators becomes a threat! Someone can send you a document
which will delete all your files -- or possibly stash copies
of your files someplace they can get at them.
This is the source of the vulnerabilities in most Unixes' TCP/IP
stacks -- they were developed on a network where essentially
everyone on the network was trustworthy, and now they're deployed
on a network where there are many people who aren't.
This is also the problem with Sendmail. Until it went through
an audit, it was a constant source of holes.
At a more subtle level, functions that are perfectly safe
when they don't cross trust boundaries can be a disaster when
they do. gets() is a perfect example. If you use gets() in a
situation where you control the input, you just provide a buffer
bigger than anything you expect to input, and you're fine.
If you accidentally crash the program by giving it too much
input, the fix is "don't do that" -- or maybe expand the buffer
and recompile.
But when the data is coming from an untrusted source, gets() can
overflow the buffer and cause the program to do literally anything.
Crashing is the most common result, but you can often carefully
craft data that will cause the program to run it as executable code.
Which brings us to . . .
Buffer-overflow holes
---------------------
A buffer overflow occurs when you write a string (usually a string
of characters) into an array, and keep on writing past the end of
the array, overwriting whatever happened to be after the array.
Security-problem buffer-overflows can arise in several situations:
- when reading input directly into a buffer;
- when copying input from a large buffer to a smaller one;
- when doing other processing of input into a string buffer.
Remember, it's not a security hole if the input is already trusted --
it's just a potential annoyance.
This is particularly nasty in most Unix environments; if the array
is a local variable in some function, it's likely that the return
address is somewhere after it on the stack. This seems to be the
fashionable hole to exploit; thousands and thousands of holes of
this nature have been found in the last couple of years.
Even buffers in other places can sometimes be overflowed to produce
security holes -- particularly if they're near function pointers
or credential information.
Things to look for:
- dangerous functions without any bounds-checking: strcpy, strlen,
strcat, sprintf, gets;
- dangerous functions with bounds-checking: strncpy,
snprintf -- some of these will neglect to write a NULL at
the end of a string, which can result in later copying of the
result to include other data -- possibly sensitive data -- and
possibly crashing the program; this problem does not exist
with strncat, and I'm not clear on whether it exists in
snprintf, but it definitely exists with strncpy;
- misuse of strncat, which can result in writing a null byte one past
the end of the array;
- security-sensitive programs crashing -- any crash comes from a
pointer bug, and perhaps the majority of pointer bugs in
production code are from buffer overflows.
- Try feeding security-sensitive programs big inputs -- in environment
variables (if environment variables are untrusted), in
command-line parameters (if command-line parameters are
untrusted), in untrusted files they read,
on untrusted network connections. If they parse input
into chunks,
try making some of the chunks enormous. Watch for crashes.
If you see crashes, see if the address at which the program
crashed looks like a piece of your input.
- incorrect bounds-checking. If the bounds-checking is scattered
through hundreds of lines of code, instead of being
centralized in two or three places, there's an extremely
good chance that some of it is wrong.
A blanket solution is to compile all security-sensitive programs with
bounds-checking enabled.
The first work I know of on bounds-checking for gcc was done by Richard
W. M. Jones and Paul Kelly, and is at
.
Greg McGary did some other work. Announcement:
.
Richard Jones and Herman ten Brugge did other work. Announcement:
.
Greg compares different approaches in
.
Confused deputies
-----------------
When you give a filename to a regular program to open, the program
asks the OS to open the file. Since the program is running with
your privileges, if you're not supposed to be able to open the file,
the OS refuses. No problem.
But if you give a filename to a security-sensitive program -- a
CGI script, a setuid program, a setgid program, any network server --
it can't necessarily rely on the OS's built-in automatic protections.
That's because it can do some things you can't. In the case of a
web server, what it can do that you can't may be pretty minimal, but
it's likely that it can at least read some files with private info.
Most such programs do some kind of checking on the data they receive.
They often fall into one of several pitfalls:
- They check it in a time-dependent fashion that you can race. If
a program first stat()s a file to see if you have permission to
write it, and then (assuming you do) open()s it, it's possible you
might be able to change the file to be something you don't have
permission to write to in the meantime. (One possible solution is
to stat() or lstat() the file before opening it, open it in a
nondestructive fashion, then fstat() the open fd, then compare to
see if you've got the same file you stat()ed. Credit Eric Allman,
via Keith Bostic and BUGTRAQ.)
- They check it by parsing the filename, but they parse the filename
differently than the OS. This has been a problem with lots of Microsoft
OS web servers; the OS does some fairly sophisticated parsing on the
filename to figure out what file it's actually referencing. Web servers
look at the filename to determine what kind of access you have to it;
often, you have access to run particular types of file (based on
filename parsing), but not to
read them. If the default access lets you read a file, then changing
the filename so that the web server thinks it's a different kind of
file, but the OS parses the filename to point to the same file, will
give you the ability to read the file.
This is a double-parsing problem, which we'll get into later, and
also stems from fail-openness.
- They check it in an extremely complex way that has holes in it,
due to the original author not understanding the program.
- They don't bother to check it at all, which is rather common.
- They check it in a simple way that has holes in it. For example,
many older Unix web servers would let you download any file in
someone's public_html directory (unless the OS barred them). But
if you made a symlink or hardlink to someone else's private files,
it was possible to download them if the web server had permission
to do so.
At any rate, programs that have privileges you don't usually fail
to limit what they do on your behalf to just what they're supposed
to do. setfsuid(), setreuid(), etc., can help.
Another problem is that frequently, standard libraries look in
environment variables for files to open, and aren't smart enough
to drop privileges while doing this. (Really, they can't be.)
So we're forced to resort to parsing the filename to see if it
looks reasonable.
Some OSes dump core with the wrong privileges, too, and if you
can make a setuid program crash, you can overwrite a file that
the program's owner would be able to overwrite. (Dumping core
with the user's privileges often results in the user being able
to read data from the core file that they wouldn't be able to
read normally.)
Fail-openness
-------------
Most security-sensitive systems fail to do the right thing under
some circumstances. They can fail in two different ways:
- They can allow access when they shouldn't; this is called fail-open.
- They can refuse access when they shouldn't; this is called fail-closed.
As an example, an electronic door lock that locks the door by holding
it closed with a massive electromagnet is fail-open when the power
goes out -- when the electromagnet has no power, the door will open
easily. An electronic door lock that locks the door with a
spring-loaded deadbolt that is pulled out of the way with a solenoid
is fail-closed -- when the solenoid has no power, it's impossible
to pull back the deadbolt.
CGI scripts commonly execute other programs, passing them user data
on their command lines. In order to avoid having this data interpreted
by the shell (on a Unix system) as instructions to execute other
programs, access other files, etc., the CGI script removes unusual
characters -- things like '<', '|', ' ', '"', etc. You can do this
in a fail-open way by having a list of "bad characters" that get
removed. Then, if you forgot one, it's a security hole. You can do
it in a fail-closed way by having a list of "good characters" that
don't get removed. Then, if you forgot one, it's an inconvenience.
An example of this (in Perl) is at
.
Fail-closed systems are a lot less convenient than fail-open ones,
if they fail frequently. They're also a lot more likely to be
secure.
Essentially every program I've seen to secure a Mac or Microsoft OS
desktop computer has been fail-open -- if you can somehow disable the
program, you have full access to the computer. By contrast, if you
disable the Unix 'login' program, you have no access to the computer.
Resource starvation
-------------------
Lots of programs are written with the pervasive assumption that
enough resources will be available. (See Psychological Problems,
above.) Many programs don't even think about what will happen if
not enough resources are available, and sometimes they do the
wrong thing.
So look to see
- what happens if there's not enough memory and some allocations fail,
usually returning NULL from malloc or new
- if it's possible for untrusted users to use up all the resources
(which can be a denial-of-service problem even if the program
handles it without allowing intrusions; this problem is
endemic throughout most software, though)
- what happens if the program runs out of fds (and whether it's
possible) -- open() will return -1
- what happens if the program can't fork(), or if its child dies
during initialization due to resource starvation
Trusting untrustworthy channels
-------------------------------
If you send passwords in cleartext over an Ethernet LAN with untrusted
people on it, if you create a world-writable file and later try to
read back data from that file, if you create a file in /tmp with
O_TRUNC but not O_EXCL, etc., you're trusting an untrustworthy
intermediary to do what you want it to. If an attacker can subvert
the untrustworthy channel, they may be able to deny you service by
altering data in the channel, they may be able to alter the data
without you noticing (causing bad things to happen -- if the attacker
makes that file in /tmp a symlink to a trusted file, you may end up
destroying the contents of a privileged file instead of just creating
a temporary file. gcc has some bugs of this kind, too, which can
lead to an attacker inserting arbitrary code into programs you compile.)
and even if they can't do these things, they may be able to read data
they shouldn't.
Silly defaults
--------------
If there are non-obvious, but insecure, defaults, it's likely that
people will leave them alone. For example, if you unpack an rpm
and create some configuration files world-writable, you're not
likely to notice unless you're actively looking for security holes.
This means that most people who unpack the rpm will have a security
hole on their system.
Big interfaces
--------------
If the security interface is small, it is much more likely to be secure
than if it is large. This is just common sense -- if I have one door
people can enter my house through, I'm pretty likely to remember to lock
it before I go to bed. If I have five doors in different parts of the
house, all of which lead to the outside, I'm much more likely to forget
one of them.
Thus, network servers tend to be much more
secure than setuid programs. Setuid programs get all sorts of things
from untrustworthy sources -- environment variables, file descriptors,
virtual memory mappings, command-line arguments, and probably file input,
too. Network servers just get network-socket input (and possibly file
input).
qmail is an example of a small security interface. Only a small part
of qmail (though much more than ten lines, contrary to what I
previously said on the linux-security-audit mailing list) runs as "root".
The rest runs either as special
qmail users, or as the mail recipient.
Internally to qmail, the buffer-overflow checking is centralized in
two small functions, and all of the functions used to modify strings
use these functions to check. This is another example of a small
security interface -- the chance that some part of the checking is
wrong is much smaller.
The more network daemons you run, the bigger the security interface between
the Internet and your machine.
If you have a firewall, the security interface between your network and
the Internet is reduced to one machine.
The difference between viewing an untrusted HTML page and viewing an
untrusted JavaScript page is also one of interface size; the routines
in the JavaScript interpreter are large and complex compared to the
routines in the HTML renderer.
Frequently exploited programs
-----------------------------
Programs that have been frequently exploited in the past are likely
to have holes in them in the future, and should sometimes just be
replaced. /bin/mail was replaced in BSD with mail.local for this
reason.
If you're auditing, auditing such programs extra thoroughly is an
excellent idea, but sometimes it's better just to rewrite them, or
not to use them in the first place.
Poorly-defined security compartments
------------------------------------
Any secure system is divided into security compartments. For example,
my Linux system has numerous compartments known as "users", and a
compartment known as the "kernel", as well as a compartment known as
the "network" -- which is divided into subcompartments known as
"network connections". There are well-defined trust relationships
between these different compartments, which are based on system
setup and authentication. (My user, kragen, trusts my network connection
after I send my password over it, for example.)
The trust relationships must be enforced at every interface between
security compartments. If you're running a library terminal, you
probably want the terminal to have access only to the library database
(and read-only, at that.). You want to deny them access to the Unix
shell altogether. I'm not sure how to finish this paragraph -- I'm
sure you can see what I'm getting at, though.
Mirabilis ICQ trusts the whole Internet to send it correct user
identifications. Obviously, this is not secure.
At one point, tcp_wrappers trusted data it got from reverse DNS
lookups, handing it to a shell. (It no longer does.)
Netscape Communicator would sometimes insert a user-entered FTP
password into the URL in the history list, when using squid as a
proxy. JavaScript programs and other web servers can see this URL.
Neglected cases
---------------
Distrust logic. if-else and switch-case statements are dangerous,
because they're hard to test. If you can find a branch of the code
that no one has ever run, it's likely to be wrong. If you can find a
logical dataflow combination -- for example, if there are two routines,
each of which does one of two things, and the output from the first of
which gets fed into the second, giving four combinations -- that hasn't
been tested, it may also be a hole.
Look at elses on ifs. Look at default: in switch statements. Make
sure they're fail-closed.
gcc -pg -a causes the program to produce a bb.out file that may be
helpful in determining how effective your tests are at covering all
branches of the code.
I believe this has been the source of many of the recent IP
denial-of-service problems.
Just plain stupid
-----------------
Lots of people trust code that only a few people have reviewed. If the
code to a piece of software has only been read by a few people, it's
likely that it has lots of bugs in it; if the code is
security-critical, it's likely to break security. The recent 3Com
debacle, in which all of their CoreBuilder and SuperStack II hubs were
revealed to have "secret" backdoor passwords which were revealed to
customers in emergencies, is a perfect example.
This should not be a major issue for the Linux security audit.
Problems with this document
---------------------------
Several of the categories overlap greatly.
It was written without any practical experience; thus the relative
importance I give to different things may be silly, and I may have
left out something important altogether. Also, parts of it are
poorly thought out.
Nevertheless, I think it may be a useful primer for people who are
participating in the Linux security audit without much previous
experience in security auditing.
Information of interest to those interested in writing secure software
----------------------------------------------------------------------
David A. Wheeler has developed a document for programmers titled
``Secure Programming for Linux HOWTO,'' which is now included in the
Linux Documentation Project.
You can get a copy at
.
SunWorld Online has an article on Designing
Secure Software. While Sun doesn't have the world's best reputation for
security, this article is worthwhile.
BUGTRAQ announces new Unix security holes on a daily basis, with full details.
geek-girl.com keeps some archives that go back to 1993. This is a very useful resource to learn
about new security holes, or look up particular old security holes. It's a
terrible resource for getting a list of security holes, though.
Adam Shostack has posted some good code-review guidelines (apparently
used by some company to review code to run on their firewall) at
.
Cops comes with a setuid(7) man page, which is HTMLized at
, and includes
guidelines for finding and preventing insecurities in setuid programs.
John Cochran of EDS pointed me to the AUSCERT programming checklist: