O O Ø O O O O
Secure Programming: strcpy and you
This paper is also a perennial favorite. Though written in 1998, it talks about a programming construct that is consistently misused even today; the infernal strcpy. To date, the only update since it was written was the addition of a pointer to OpenBSD's strlcpy.
Most of the bugs that are found in software today are both obvious and preventable. Although techniques for avoiding these common mistakes have been around for years, many programmers continue to produce insecure and buggy software.
Let's face it. The C programming language doesn't help much here. It's very easy to fall into some of the traps that C digs for hapless programmers. Learning to recognize and avoid these pitfalls is an important step to producing secure and reliable software.
This is the first in a series of papers describing some of the most common programming errors that lead to exploitable bugs in software.
Bug #1 - strcpy and Environment Variables
One of the more common programming activities (at least in the Unix environment) is the extraction of information from an environment variable. The most obvious way to do this is as follows:
#include <stdlib.h>
void main() {
char buf[512];
strcpy(buf,getenv("HOME"));
printf("Your Home: %s",buf);
}
Unfortunately, obvious isn't good. Using this quick and dirty method, you've just created a buffer overflow. If the contents of the HOME variable are larger than 512 characters, the strcpy will happily write past the end of the buf variable, overwriting whatever happened to be on the stack before it. If your application is setuid root, your system is as good as compromised. [If you're interested in how, see Aleph One's famous Smashing the Stack for Fun and Profit paper.]
strcpy opens us up to this type of buffer overflow because of the way strings are handled in the C programming language. In C, strings are expected to be null-terminated. That is, the final character of any string is always the null character. strcpy, then, blindly copies all characters starting at the address of the source string into the destination until it reaches a null.
There are a number of ways to avoid this problem. Dynamically allocating all your strings is a possibility. Unfortunately, in certain types of applications it can lead to resource starvation or denial of service attacks. Ironically, many of the most common workarounds are bugs in themselves.
Bug #2 - The strncpy workaround
Consider the following workaround. Many programmers will replace the strcpy with a strncpy, which copies at most n characters of the string, avoiding the buffer overrun issue.
#include <stdlib.h>
void main() {
char buf[512];
strncpy(buf,getenv("HOME"),512);
printf("Your Home: %s",buf);
}
If you think back to the way strings work in C, you will see why this is also an error. If the HOME variable contains 512 (or more) characters, 512 characters are copied into the variable called buf. This "string" is not null-terminated. If the contents of buf are handed to any function or routine expecting to see a standard C string, the routine will look beyond the end of buf (and into an arbitrary location in memory) until a null character is found.
Bug #3 - Terminating your strncpy
To correct the previous error, many programmers will simply force the null-termination of the buffer. This is illustrated below:
#include <stdlib.h>
void main() {
char buf[512];
strncpy(buf,getenv("HOME"),512);
buf[sizeof(buf)-1]=0;
printf("Your Home: %s",buf);
}
While this approach does take care of the problems with large HOME variables, it misses another very important issue; that is, the behavior of the getenv function. If the given environment variable (HOME) does not exist, getenv returns NULL. On many platforms, performing a strcpy or strncpy from a null pointer results in either a segmentation fault, or garbage retrieved from memory address 0.
Another issue to consider is that of truncation. Is it permissible to simply truncate a variable that is too long? In the case of file/path names, the answer is almost inevitably no. Truncating a path name can lead to the processing of the wrong file. In most other cases, however, truncation is a viable option.
A Better Way
If truncation is an option, a better solution is probably the following. In this case, both the existence and length of the HOME variable are tested before a copy is performed, and a memcpy is used in place of a strcpy 1:
#include <stdlib.h>
#define BUFSIZE 512
void main() {
char buf[BUFSIZE];
char *env;
int len;
env=getenv("HOME");
if (env != NULL) {
len=strlen(env);
if (len >= BUFSIZE)
len=BUFSIZE-1;
memcpy(buf,env,len);
buf[len]=0;
printf("Your Home: %s\n",buf);
} else {
printf("No Home\n");
}
}
Clearly, this fragment of code is much larger than the first one. If you find yourself performing this kind of activity frequently, this functionality can be encapsulated into a single cover function. (Mark Whitis has an excellent one called safe_strncpy. OpenBSD has a an even more interesting one called strlcpy). Although there may be a small performace hit with this additional function call. most of us will agree that writing secure and bug-free software is well-worth the penalty.
1 - Here a memcpy is used in place of a strncpy. strncpy always fills the target buffer with nulls, which some people consider wasteful for large buffers. Others, however, find this zeroing useful for preventing information leakage. You be the judge.
Kjell Wooding