One of the more common types of attacks directed at Linux systems comes in the form of buffer overflows, which have accounted for more than 50 percent of CERT advisories. The attacks can be quite difficult to guard against since they usually involve software flaws. The vulnerabilities reside within programs themselves and are caused when a section of memory is overwritten.
Preventing these attacks has historically involved the modification of the source code and recompilation. However, Libsafe offers another way to deal with these dangerous flaws. Libsafe is a dynamically loadable library that intercepts calls to unsafe functions and processes them so that hackers can’t hijack the process and run the code of their choice. The most valuable aspect of Libsafe is that it can help you guard your Linux systems against buffer overflow vulnerabilities that have yet to be discovered.
Understanding buffer overflows
The main purpose of an attack using buffer overflow techniques is to gain access to privileged user space on a target machine. Buffer overflows can also crash a program or even cause system instability due to vulnerabilities in the software itself.
In a buffer overflow, a section of memory corresponding to a variable used by a program is overwritten. Buffer overflows have been found in all sorts of system programs and daemons such as syslogd, Sendmail, Apache, WU-FTPD, and BIND, to name but a few. Since information security has become more and more of a concern in IT, methods for avoiding, diagnosing, and documenting these exploits have improved. Yet, despite security audits and careful programming, some bugs remain present in many software programs and are not discovered until later.
Let’s consider an example of a buffer overflow. When a user connects to an FTP server, the daemon displays a prompt requesting a username. We’ll assume that the program is expecting a string of no more than 256 characters and is not programmed to perform an argument check. The program will work fine—that is, until a malicious individual decides to test its weaknesses by passing it a 257-character string. Now, the allocated memory space doesn’t have enough room, and the next portion up gets written over. This may not sound bad at first, but its effects can be surprising. It all depends on what is in the memory space that has been overwritten, how much of it was overwritten, and what it was overwritten with. This can be impossible to determine beforehand and can cause strange things to happen.
Buffer overflows can also be used in what are called “stack-smashing” attacks, where someone can execute his or her own code on a target system. When a program is executed, it uses an area of memory called the stack. The stack stores function arguments and local variables, among other things. If a particular variable that resides in the stack is susceptible to a buffer overflow, a hacker can use this information to gain access to the system.
Similar to buffer overflows are “format string” exploits. These also attempt to access out-of-bounds memory space to gain access to a Linux system. One way to do this is to pass special formatting characters to a print command that doesn’t do any format checking. In this manner, the special characters can actually reference memory space and cause program instability. Once again, this falls into the programmers’ hands and can be avoided with good coding practices. The C function sprintf(string, “%s”); is an example of good practice, while sprintf(string); would be considered unsafe, as it does not provide formatting information.
It’s important to remember that a hacker needs to compromise a program that is run as root to get a root shell (which is almost always the goal of the hacker). In recent years, there has been a migration from running programs as root to using a separate user account. Many programs will create a user specifically for running program tasks and will avoid using root as much as possible. In this fashion, even if a program is compromised, the user will not have total control of the system.
A closer look at Libsafe
Libsafe is a system library that intercepts calls to specific unsafe functions and handles them securely. This allows it to handle precompiled executables, meaning that manually editing the source and recompiling (or waiting for the maintainer to do this) is not necessary. Also, and possibly more important, it will work on bugs in software programs that have not been discovered yet. It can do this because it intercepts all calls to a particular function, performs the task, and sends back the information without the calling program’s knowledge.
Even if a program has been written using bad techniques, Libsafe will stop it from possibly being exploited. It will do this systemwide and will be transparent to the programs themselves. The main idea is to set an upper limit on the size of the buffer that is used in a particular function. Although this can’t be done at compilation time, it can be done when the function is actually called. Libsafe checks the current stack and sets a realistic limit so that the buffer can’t be overwritten.
Libsafe currently handles these unsafe functions:
strcpy(char *dest, const char *src)
strpcpy(char *dest, const char *src)
wcscpy(wchar_t *dest, const wchar_t *src)
wcpcpy(wchar_t *dest, const wchar_t *src)
strcat(char *dest, const char *src)
wcscat(wchar_t *dest, const wchar_t *src)
scanf(const char *format, …)
realpath(char *path, char resolved_path)
sprintf(char *str, const char *format, …)
These are the more common ones that are problematic in C and C++ programs in a Linux environment. Most programmers should already be aware of issues coming from buffer overflows. But mistakes can and will be made, which is understandable, especially with larger programs containing a thousand or more lines of code. Libsafe provides an excellent way to safeguard against unsafe programming practices and does so with very little process latency.
Getting Libsafe up and running
You can obtain Libsafe here. Multiple packages are provided, including source, RPM, Mandrake, and Debian versions. Let’s look at how to install from source and configure your system. After downloading the source package, unpack and install it as follows:
tar xpfz libsafe-2.0-16.tgz
This will make and copy the library into the appropriate directory. The make install will ask if you want to install on a systemwide basis. This is recommended and will automatically modify /etc/ld.so.preload to provide support for Libsafe. The library file will be installed into your library directory, which by default is /lib/libsafe.so.2. Once this is done, there is really nothing else to do. Libsafe will intercept buffer overflows directed at any monitored functions, the process will be killed, and an error will be logged in /var/log/secure. The entry will look something like this:
July 26 22:42:21 labrat.example.com : Detected an attempt to write across stack boundary.
July 26 22:42:21 labrat.example.com libsafe: Terminating /home/tn8273/scripts/stack_smasher
July 26 22:42:22 labrat.example.com libsafe: sprintf()
If you want Libsafe to ignore any programs, you can list them in /etc/lib-safe.exclude one per line using their full path. This will prevent Libsafe from interrupting the process if you are working on a particular program.
Buffer overflows and format string vulnerabilities make up a large number of the security attacks on Linux systems each year. They are mainly caused by unsafe programming practices and can remain hidden for some time. While the problems can be found and corrected, the process can be lengthy—and that could be too late for some systems. This is where Libsafe comes into play.
Libsafe provides secure alternatives to unsafe functions by intercepting the calls and handling them through its own library. In this fashion, your system can be protected from bugs that haven’t yet been found while requiring no modification or compilation of source code. Libsafe is another great tool in the fight against systems attackers.