Abstract: This lecture is about developing good habits and learning techniques that prevent errors in security software.
secSoftware.ppt
| FormalMethodsSecurityPM.ppt
| SPLINT_Oct.ppt |
This article is part of
Internet Security Lectures
"All problems fall into one of two categories: Those that can and those that cannot be easily solved. For instance, some of the denial of service attacks ... are a result of the IP protocol's design. Short of implementing a new protocol ..., not much can be done beyond stopgap measures that make particular attacks less effective.
Other difficult problems include network sniffing and spoofing. These result from security-related information being sent in the clear over networks. Then there is the general authentication problem. The difficulty with authentication is that the lowest common denominator is user names and passwords, and that method is generally not sufficient.
Unfortunately, solving these problems requires new hardware, new software, and user training, ... Over the longer term, protocols like IPV6 and IPsec will resolve many of these issues. Of course they may create new ones. ...
The solvable problems are the result of poor planning, programming, and implementation. These can be solved by software vendors ... to improve their coding methodologies. ... "
"... Is code getting better? You could assume that the security
holes in
operating systems are the result of poor coding way back when, and that
new code
and coding methods do not have the same problem. You would be wrong.
Consider
Windows NT and its sorry security state. Or look in our own back yard
at
Solaris. Bugs in admintool, NIS+, the volume manager, procfs,
PPP, PAM, and the PCI bus drivers ... prove the point." [Peter Galvin,
Pete's Wicked World column, Sun World Magazine, 1998]
In June 2000, GSA Federal Chief Information Officers Council listed the "The Ten Most Critical Internet Security Threats":
The full report (available at http://www.itpolicy.gsa.gov/itleaders/toptenthreats.htm) is worth studying, and system administrators are urged to follow the procedures given there in securing their systems. For our immediate use here, observe how many of these are due to programming errors. Items 1-6, and 9 are programming errors. Items 7, 8, and 10 are configuration errors. Note that none of the top ten are due to design errors in TCP/IP.
For the purpose of this article, let us define robustness as being crash proof, and hang-proof no matter what the inputs are. Crash is unexpected termination. A hang is unexpected non-termination. Two classes of being hung are: infinite looping, and waiting for an event that will not occur. Infinite looping consumes heavily the CPU time. Waiting for a non-occurring event consumes almost no resources. Note that infinite recursion will lead to a crash via resource exhaustion.
Yes, but the cost of developing such software is so high that neither customers not companies are willing to spend. It is also the case, that placing high importance on security will lead to these programs being inefficient. In the next few paragraphs of this section, we ignore both these issues (cost and efficiency) and speculate on the possibility of developing secure software.
Keep Interactions Minimal. You often need to check how each pair of subsystems interacts, and possibly even each subset of subsystems. For example, interactions between the password checker and the page-fault mechanism.
Least Common Mechanisms. The assumptions originally made in shared code may no longer be valid. Eaxmple 1: Netscape's LiveConnect allows Java and Javascript and the browser to talk to each other. But Java and Javascript have different ways to get at the same information, and also different security policies. A malicious Java applet could cooperate with a malicious Javascript page to communicate information neither could have communicated alone. Example 2. Windows exports an easy interface to IE's HTML-rendering code. Mail clients like Eudora, Outlook Express, among other programs, use this interface to display HTML-formatted email. By default, parsing of Java and Javascript (J-Script) are enabled. However, the HTML-rendering code "thinks'' that Java and J-Script are unsafe when loaded from the Internet, but safe when loaded from local disk. The email is loaded from local disk!
Many programs do not check if enough resources will be available. What happens if there is not enough memory and some allocations fail? What happens if the program runs out of fille descriptors? What happens if the program cannot fork()?
Error handling and reporting is an essential part of any programming paradigm. Delicate handling of and recovery from error conditions is an absolute necessity, especially in a third party library.
What was originally a minor annoyance, or sometimes even a convenience, can become a security hole when a program is run in a different context. 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 do not. But suppose you start using it to view documents from other 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.
assert(3) is a macro that accepts a single argument which it treats as a Boolean expression. If the expression evaluates to false, the assert macro prints an error message and terminates the program. Assertions are useful in the developmental stages of programs when verbose error handling is not in place or when a grievous error condition that normally should not happen occurs. One must use assertions, but exiting abruptly even after reporting an error is not acceptable. If a grievous error condition is detected, the code should return error codes to the caller, and let it decide what to do. Code should be able to handle grievous errors well enough to be able to exit gracefully from the top level (if possible). Also, never use structured exception handling as a substitute for writing solid code in the first place.
sprintf(), fscanf(), scanf(), vsprintf(),
realpath(), getopt(), getpass(), streadd(), strecpy(), strtrns(), gets(),
strcpy(), and strcat() chdir())
to an appropriate directory at program start. If creating a new file,
use O_EXCL and O_CREAT flags to assure that the
file does not already exist. Do not create files in world-writable
directories. Use lstat() to make sure a file is not a
link, if appropriate. Set limit values to disable creation of a core
file if the program fails. If using temporary files, consider using tmpfile()
or mktemp() system calls to create them (although most mktemp()
library calls have problematic race conditions).system() and popen() system
callssetuid or setgid shell
scripts getservbyname()
instead. Do not assume connections from low-numbered ports are
legitimate or trustworthy. Do not assume the source IP address is
legitimate. Place timeouts and load level limits on incoming
network-oriented read request. Place timeouts on outgoing
network-oriented write requests. lint.
Have internal consistency-checking code. Use your
compiler wisely. With gcc, use -Wall -ansi -pedantic flags. Use
safe libraries. There is a large body of technical literature that advocates designing software by first writing formal specifications capturing the all requirements of the software yet to be constructed. Subsequent steps are systematic refinements of such specifications yielding several levels of designs ultimately producing the source code in a programming language. The writings of Turing-award winners Dijkstra and Hoare are heavily influential in this regard. (For an elementary introduction, read Prabhaker Mateti, "Practical Advice on Writing Pre- Post-Conditions for Real Programs," Lecture Notes, May 1998.) Unfortunately, the open literature has documented only small programs that are so developed. Most of the academic computer science community as well as the industry believes that such development is astronomically expensive and even then not necessarily qualitatively "better."
In recent years, more practical uses of the above methodology have emerged. A tool, called splint (www.splint.org) , can analyze large amounts of C source code at speed comparable to that of a typical compiler and flag a variety of notorious errors that made secure software succumb to such attacks as buffer overflow.
The following programs must be robust: The OS kernel.
All setuid
and setgid programs. All daemons that accept network
connections.
If the kernel has security holes, no amount of checking of system programs is going to make the system secure from attack. However, relatively few kernel bugs are being found and exploited these days.
setuid root programs allow users to gain root
privileges.
Daemons allow users to access the system without first getting
authenticated. A
network daemon may answer a network request and process it under the
daemon's
privileges, not a user's. Therefore, this is another way for users to
increase
access, or even gain initial access, to the target system.
In terms of security, kernels are relatively bug-free because of the
limited
interfaces available to attack. For instance, Solaris has 210+ system
calls
(check /usr/include/sys/syscall.h), and Linux about 190.
Compare
that to the thousands of points a hacker has available to attack:
sockets,
files, devices, and programs.
Check for "rws----" permissions to see if an executable is setuid
root. Run find / -perm +4000 -print to locate all setuid
files. Add
"-user root" in order to find just those that elevate to root.
SYNOPSIS
#include <unistd.h>
int setuid(uid_t uid);
int setgid(gid_t gid);
DESCRIPTION
setuid() sets the real-user-ID (ruid), effective-user-ID (euid),
and/or saved-user-ID (suid) of the calling process. The super-user's
euid is zero.
Under Linux, setuid is implemented like the POSIX version
with the _POSIX_SAVED_IDS feature. This allows a setuid
(other than root) program to drop all of its user privi
leges, do some un-privileged work, and then re-engage the
original effective user ID in a secure manner.
If the user is root or the program is setuid root, special
care must be taken. The setuid function checks the effec
tive uid of the caller and if it is the superuser, all
process related user ID's are set to uid. After this has
occurred, it is impossible for the program to regain root
privileges.
Thus, a setuid-root program wishing to temporarily drop
root privileges, assume the identity of a non-root user,
and then regain root privileges afterwards cannot use
setuid. You can accomplish this with the (non-POSIX, BSD)
call setuid.
The following conditions govern setuid's behavior:
o If the euid is zero, setuid() sets the ruid, euid, and suid to
uid.
o If the euid is not zero, but the argument uid is equal to the
ruid or the suid, setuid() sets the euid to uid; the ruid and
suid remain unchanged. (If a set-user-ID program is not
running as super-user, it can change its euid to match its
ruid and reset itself to the previous euid value.)
o If euid is not zero, but the argument uid is equal to the
euid, and the calling process is a member of a group that has
the PRIV_SETRUGID privilege (see privgrp(4)), setuid() sets
the ruid to uid; the euid and suid remain unchanged.
setgid() sets the real-group-ID (rgid), effective-group-ID (egid),
and/or saved-group-ID (sgid) of the calling process. The following
conditions govern setgid()'s behavior:
o If euid is zero, setgid() sets the rgid and egid to gid.
o If euid is not zero, but the argument gid is equal to the rgid
or the sgid, setgid() sets the egid to gid; the rgid and sgid
remain unchanged.
o If euid is not zero, but the argument gid is equal to the
egid, and the calling process is a member of a group that has
the PRIV_SETRUGID privilege (see privgrp(4)), setgid() sets
the rgid to gid; the egid and sgid remain unchanged.
RETURN VALUE
Upon successful completion, setuid() and setgid() returned 0.
On error, -1 is returned, and errno is set appropriately.
Like most man pages, the descriptions of this most famous syscall vary from Unix to Unix. Here is a typical entry. As you can see, it is quite unclear, and many programmers do not study other carefully written setuid programs.
Recommended Reading:
Simson Garfinkel, Gene Spafford Practical Unix and Internet Security, 2nd edition (April 1996), O'Reilly & Associates; ISBN: 1565921488. Errata: http://www.oreilly.com/catalog/puis/errata/ Chapter 23: Writing Secure SUID and Network Programs.
This lab is exempt from the usual rule: All work should be carried out in Operating Systems and Internet Security (OSIS) Lab, 429 Russ. Use any of the PCs numbered 23 to 30. No other WSU facilities are allowed.
Objective: Get you to think about secure programming some more,
and make you familiar with the splint tool from
www.splint.org .
export PATH=$PATH:/home/pmateti/CEG429/splint-3.1.1/binA copy of the Splint Users Manual
export LARCH_PATH=.:/home/pmateti/CEG429/splint-3.1.1/lib
export LCLIMPORTDIR=/home/pmateti/CEG429/splint-3.1.1/imports
/home/pmateti/CEG429/splint-3.1.1/doc/manual.pdf is available
in the Lab. Read it sufficiently so that you can do the following.splint on exploit4.c of Aleph
One. Revise the code of exploit4.c, and adjust the
flags of splint so that all errors and warnings shown by
splint are gone.sudo. Build it as
usual. Check that it "works."splint, with no (except for include-related)
flags, collectively on all the source code files of sudo. Select
three messages regarding source code errors generated by splint, and
explain the messages and the causes for their generation.BishopExercises1019.txt,
exploit4Revised.c,and sudoExperience.txt. Please insert a
line of dashes (hyphens) between these files as a visual separator.These lecture materials are gleaned from many sources. All are presented after careful reading. In some cases, I may have neglected proper attribution. I assure the reader it is not because I claim authorship. Indeed, in the lectures there is hardly any thing new that I have contributed. Suggestions for improvement are always welcome.
| Copyright © 2008 pmateti@wright.edu |