Security-specific Programming Errors (Part 3)
Thomas Biege
Table of Contents
Temporary Files
Besides memory overflow, errors in the manner in which temporary
files are handled are the most common reason for security
problems.
Generally, temporary files are created in directories that can
be written to by everybody. (In Unix: /tmp,
/var/tmp). To prevent files in public directories from being
deleted, Unix, for example, sets a filesystem flag (chmod
o+t /tmp), which means that users can only remove their own files
from the directory. The flag does not offer protection for
subdirectories. In other words, if the subdirectory can be written to
by everybody, then everything in the directory can be modified,
too. The t flag therefore has to be set explicitly for every
directory. The directory /tmp/soffice.tmp, which is created
by StarOffice, is a good example of this error.
Besides illegal deletion of files, there is also the risk of
confidential files being exposed to prying eyes (e.g., with older
versions of Acrobat Reader and WordPerfect) and race
conditions or link attacks. The following non-atomic
and insecure code segments are often used to create temporary
files:
Wrong:
[...]
FILE *tmp_datei;
[...]
tmp_datei = tmpfile();
[...]
or:
[...]
char *tmp_name;
int tmpfd;
tmp_name = tmpnam(NULL);
if( (tmpfd = open(tmp_name, O_RDWR | O_CREAT)) < 0)
[EXIT]
unlink(tmp_name);
/*
** remove the filename so that the file is not left on the
** file system when the program code completes or when
** the file descriptor is closed.
*/
[...]
Both file creation methods are insecure as they are non-atomic and
follow symbolic links. In the second example, it is not the
tmpnam(3) function that is responsible for the security risk,
but the subsequent open(2) command. Functions like
tmpnam(3), tempnam(3) or mktemp(3) only ensure
that the file name does not exist at the time when it is called. But
between the creation of the name and opening the file, an attacker can
create a link with exactly the same name and open(2) follows
the link (this is known as a race condition).
Right:
[...]
int tmpfd;
[...]
if( (tmpfd = mkstemp("/tmp/MyTempFile.XXXXXX")) < 0)
[Exit]
fchmod(tmpfd, 0600);
[...]
Mkstemp(3) creates a unique name and opens it in a secure
manner. Unfortunately, the disadvantage of this solution is that
mkstemp(3) is not part of the POSIX standard
(BSD4.3) and old versions set the access rights to 0666, which
means that anybody can write to the file and read from it. To create
code that is both secure and portable, open(2) must be used and
invoked with the right parameters.
[...]
int tmp_fd;
FILE *tmp_stream;
char tmp_name;
[...]
if((tmp_name = tmpnam(NULL)) == NULL)
[Exit]
if((tmp_fd = open(tmp_name, O_RDWR|O_CREAT|O_EXCL, 0600)) < 0)
{
fprintf(stderr, "Possible link attack detected!\n");
exit(-1);
}
/*
** We want to use the stream-I/O functions in stdio.h
**
*/
if( (tmp_stream = fdopen(tmp_fd, "rw")) < 0)
[Exit]
[...]
Format Bugs
The so-called format(ing) bug is another type of bug that leads to
security problems. This bug was only discovered recently and has been
lying dormant and unnoticed in a lot of software packages for several
years.
The bug affects any program that passes data from an untrusted
source to functions with variable parameter length as a format
string. (In other words, all printf(3)-type functions).
Wrong:
[...]
snprintf(buf, sizeof(buf), UntrustedUserDataBuffer);
[...]
Right:
[...]
snprintf(buf, sizeof(buf), "%s", UntrustedUserDataBuffer);
[...]
Of course, it is also possible to use strncat(3) or similar.
To better understand the bug, you need to know how the variable
number of parameters is handled in C.
The variable parameter number is simply defined with three dots
("...") in the function declaration.
For example:
int my_sprintf(char *buffer, char
*format_string, ...);
The function processes the unknown number of arguments with the
macros va_start(3), va_arg(3) and va_end(3),
which are defined in stdarg.h.
For example:
int my_sprintf(char *buffer, char *format_string, ...)
{
va_list arg_ptr; /* argument pointer */
short ShortValue;
[...]
va_start(arg_ptr, format_string);
/*
** set arg_ptr to first optional
** argument.
*/
[...]
while(format_string != NULL)
{
[...]
ShortValue = va_arg(arg_ptr, short);
/* va_arg() returns the short value */
[...]
++format_string;
/* next character in the format string */
}
va_end(arg_ptr);
[...]
}
In order to gain access to the next optional parameter,
va_arg(3) simply works its way backward up the stack (addresses
are decremented). If the attacker has the chance to define the
format string himself, he can specify format tags within
the format string even when there are no format
variables around.
For example:
[...]
#define MAX_BUF 1024;
[...]
char Buffer[MAX_BUF];
char UntrustedUserDataBuffer[MAX_BUF];
[...]
getDataFromNetwork(UntrustedUserDataBuffer, MAX_BUF);
[...]
my_sprintf(Buffer, UntrustedUserDataBuffer);
/*
** UntrustedUserDataBuffer contains, for example,
** "Good night! %s %p %p %s %p"
** There is no format variable for the format tags!
*/
[...]
Even if there are no format variables, va_arg(3)
simply goes up the stack to get the values for the placeholders. The
stack frames of other functions are also searched, of
course. For this reason, the attacker can not only cause the program
to crash, but he can also read confidential/valuable information,
change the value of local variables or even execute code by
manipulating function pointers, jump addresses or saved IP values. To
make changes within the process memory, the format variable
"%n" is required. With its help, the (not real) position is
written in the format string at a specific address, which is
specified by a format variable.
Format bugs can be avoided as follows:
my_sprintf(Buffer, "%s", UntrustedUserDataBuffer);
This prevents the format tags from being parsed yet again.
|