Buffer Overflow
{LANG_NAVORIGIN} Reference
a buffer overflow is an anomalous condition where a program somehow writes
data beyond the allocated end of a buffer in memory. Buffer overflows
usually arise as a consequence of a bug and the use of languages such as C
or C++ that are not "memory-safe". One consequence of the overflow is that
valid data can be overwritten as a result. Buffer overflows are also a
commonly exploited computer security risk—since program control data often
sits in the memory areas adjacent to data buffers, by means of a buffer
overflow condition the computer can be made to execute arbitrary (and
potentially malicious) code that is fed to the buggy program as data.
Security ramifications
A program which takes advantage of a vulnerability to subvert another
program's security is called an exploit and is usually intended to gain
access to superuser or otherwise escalated privileges. A buffer overflow
exploit works by feeding the program specially crafted input content that
is designed to overflow the allocated data storage buffer and change the
data that follows the buffer in memory.
For example, imagine a hypothetical program that executes with superuser
privileges in order to perform some system administrative function such as
changing a user password. If the program fails to ensure that the length of
the new password entered is equal to or smaller than the data buffer
allocated for its storage, then any overflow data will simply be written
over whatever happens to be after the data buffer. If this post-buffer area
is executable code, a malicious user can insert machine language
instructions that can perform any function normally requiring root
privileges — add users, delete users, change passwords, alter or delete any
file, etc.
Properly written programs ought to check the length of input data, to
ensure that it is not larger than the allocated data buffer, but this is
frequently overlooked, especially by novice programmers. Buffer overflows
are most easily exploited when the data buffer is in the program stack,
since this can lead directly to an alteration of the program's execution
path.
Determining the exploitability of a buffer overflow vulnerability can be
difficult even for experienced programmers, since it involves a lot of high
and low level knowledge of the architecture internals and the target
program. Overflows of as little as a single byte beyond the end of a buffer
have proved to be exploitable.
Generally, the buffer overflow problem is caused by careless programming.
Avoiding them is still a manual process as most formal verification systems
have yet proven unattainable in modern programming languages. Buffer
overflows are common only in programs written in relatively low-level
programming languages, such as assembly language, C, and C++ which require
the programmer to manually manage the size of allocated memory. Many
programming languages such as Java and Lisp manage memory allocation
automatically, and use a combination of run time checking and static
analysis to make it difficult or impossible to code a buffer overflow bug.
Perl provides for automatic resizing of the arrays to avoid buffer
overflows. However, runtime systems and libraries for such languages may
still occasionally have buffer overflows due to internal implementation
errors in these checking systems.
Description
A more technical view of this would be best explained by using C or C++ as
an example. This is based around x86 architectures, but works for many
others.
Basically, when a dynamic buffer or automatic array is allocated in a
function, it is allocated at function call time on the stack. Writing data
to the buffer can write beyond it on the stack. Also, the stack grows from
bottom up (or from right to left, depending on perspective). Here, (DATA)
(DATA) (...) represents the existing stack, and (NEWDATA) is some new value
the CPU has pushed onto the stack:
(NEWDATA)(DATA)(DATA)(...)
When a C program executes a subroutine, it pushes the return address onto
the stack, so the subroutine knows where to return control to when it has
finished:
(ADDR)(DATA)(DATA)(...)
When a dynamic buffer is allocated, the stack grows left by however big the
buffer is. So, if a function starts with a 'char a[10]' declaration, the
result is:
(a.........)(ADDR)(DATA)(DATA)(...)
At the end of the function, the buffers are deallocated, everything pushed
is popped, and a RET operation is called. This pops the return address off
the stack and jumps there, shifting program control back to wherever the
subroutine was called from.
Suppose that 10 byte buffer is intended to hold user input (a new password,
for example.) If the program fails to specifically check the number of
characters the user has entered, and writes 14 bytes to the buffer, the
extra data will clobber the return address, overwriting part of it with the
extra data. This changes where program control will go to continue
execution when the subroutine has finished.
If the user is not malicious and enters more than 10 characters, the extra
data will effectively be random, and will probably end up making the return
address point to an area in memory which is not under the control of the
currently executing program, causing a segmentation fault on Unix
architectures (or an analogous error on other operating systems) when the
RET instruction attempts to jump control there.
If a technically inclined user is malicious, they can ensure that the extra
data does indeed point to a valid memory address, causing program control
to be shifted to this location of their choosing, and potentially executing
whatever arbitrary code the user has caused to be in that location with
whatever privileges the currently executing program has.
Example
The following is C source code. Once compiled, the program can be used to
generate buffer overflow errors. The first command-line argument is taken
as the text with which to fill the buffer.
/* overflow.c - demonstrates the buffer overflow process */
#include
#include
int main(int argc, char *argv[])
{
char buffer[10];
if(argc < 2)
{
fprintf(stderr, "USAGE: %s stringn", argv[0]);
return 1;
}
strcpy(&buffer, argv[1]);
return 0;
}
After compiling the program, try it with a few test cases. Strings of 9 or
fewer characters will not cause a buffer overflow. Strings of 10 or more
characters will cause an overflow, however they may not result in a
segmentation fault.
This program could be rewritten as follows, using strncpy effectively.
/* better.c - demonstrates how to fix the problem */
#include
#include
#define BUFFER_SIZE 10
int main(int argc, char *argv[])
{
char buffer[BUFFER_SIZE];
if(argc < 2)
{
fprintf(stderr, "USAGE: %s stringn", argv[0]);
return 1;
}
strncpy(&buffer, argv[1], BUFFER_SIZE);
buffer[BUFFER_SIZE - 1] = '