String handling is one of the most error-prone aspects of programming in C and C . Errors in dealing with strings account for most of the buffer overruns that result in security problems. In many languages, a string is an elementary type, and several of the issues that cause problems in C and C , such as buffer overruns and problems with illegal pointers, do not occur as easily in these other languages. Perhaps if C had been written with a string type, we might have fewer problems with strings.
Let's examine strings and take a look at three C library calls that can compromise the security of your code. Do not despair, I'll also introduce you to the Standard Template Library (STL) and explain how it can help you avoid some of THESE SECURITY. As I Pointed Out Last Time, The Contents of this Column AssumeT The Reader Has A Basic Familiarity with Programming IN C.
What's a string?
A string is a series of characters ending character that lets the program know with a null ( '/ 0') where to terminate the string. A Unicode string is a series of wide characters (WCHAR) that also terminates with a null character. At the lower levels (eg, kernel level) of Windows 2000 (Win2K) and Windows NT, a UNICODE_STRING type often represents strings. This structure maintains information about the length of the string and the maximum size of the buffer. Dealing with kernel-level code is beyond the scope of this article, but you should be aware that this approach represents another way of string handling. Almost without exception, the C library calls, which deal with single-byte characters, have equivalents to properly deal with Unicode strings, and THE SAME PITFALLS Apply to Both Single-byte and Unicode Strings. Let's begin by Examing Some of the available library calls, starting with strcpy (). Fun with strcpy ()
THE FIRST LIBRARY CALL, STRCPY (), IS Defined AS
Char * STRCPY (Char * DEST, Const Char * src);
A quick look at how C and C implement this function and a little thought about what parameters are not passed into this function gives a good view of the problems that can occur. What happens if src of dest is null? Ker-boom, it throws an unhandled exception or overwrites the stack. What if the string that src points to is longer than the dest buffer can hold? You'll overwrite past the end of the dest buffer, and if dest is a static buffer, declared on the stack Like BUF in the Following EXAMPLE:
// this is the wrong way void foo (char * INP) {char Buf [25];
STRCPY (BUF, INP); // DO more processing of buf}
Buffer overruns are the sort of thing an attacker loves to find in your code Once inp fills up buf, it starts overwriting the stack and can usually cause your program to execute whatever code the attacker wants Consider a related problem:.. What if inp isn 't null-terminated? Now, our not-very-bright strcpy () function takes everything in inp and stuffs it into buf, past the end of buf, and keeps going until it triggers an exception handler. For these reasons, many programmers Ban strcpy () from their applications.fortunately, you can improve the situation and still users Use struffpy ():
Void Bar (Char * INP) {Char BUF [25];
// first check to see reason INP ILLEGAL - ILLEGAL - IF you don't do this, strlen // call below blows Up if (inP == null) {Assert (false); Printf ("Cannot Process a null pointer !!! / N "); Return;}
// use <, not <= - That Way you have room for a Termination Character IF (Strlen (InP) The first thing you need to do is determine whether inp is a legal string; if not, you need to throw an assert (if you're in a debug build) to let the programmer know that a problem exists in the calling function You. 've just eliminated one gotcha. Next, check to see whether the string is too long for your buffer, and complain if it is. Since strlen () also blows up when passed a null pointer, you'll want to check for that condition before checking the string length. Note the use of the sizeof () operator, which helps keep you from making mistakes if you later decide to change the size of buf-this operator automatically takes into account any such changes. As a last point, you need to reduce the length of the inp string to be one character less than the size of the buffer to leave room for the null character.So, what can go wrong? The most likely problem you'll encounter is that inp really is not Null-Terminated, And As A Result, The strlen () Call Will Blow Up. Another Problem is what That inp pointer might not be valid-checking for NULL is nice, but the pointer might still be illegal. For example, the pointer might point into kernel space, point too low into user space (<64KB), or be complete junk. However, if you do too much checking, your code will run slowly, so you have to make some compromises However, as Steve Maguire points out in his book Writing Solid code. (ISBN: 1556155514), if you're running around dereferencing null pointers, Execution speed is the least of your worries. So, what does this code do right? If you get any obvious errors or enter a string that's too long, you'll fail gracefully, note the error, and return execution to the caller. A more complete example would return unique errors to the Caller, But I'VE SIMPLIFIED THE CODE IN this example? STRNCPY () Better? The Second Library Call, Strncpy (), IS Defined AS Char * STRNCPY (Char * DEST, Const Char * src, size_t count); On the surface, this one looks better than strcpy () - at least it wants to know how many characters you'd like to stuff into the buffer However, when you take a closer look, you see that it still has problems For.. Example, Strncpy () STILL DOESN '' TOENG, AND IF you Lie To It About The Character Count, Things Can Get Ugly Fast. Let's Look At Some Code To Illustrate ITS Usage: Void Baz (Const Char * INP) {Char BUF [25]; // Always Check The Validity of Your Inputs if (INP == NULL) {Assert (false); Printf ("Yuck! You're Passing a null Pointer! / n"); return;} STRNCPY (BUF, INP, SIZEOF (BUF) -1); // You Always Have to Remember to Null Terminate BUF [SizeOf (BUF) -1] = '/ 0'; // do more processing return;} On the Face of Things, this function looks better. You don't have to determine WHETER INP is Too long, and you won't overwrite the buffer-you'll just write one less byte Than the buffer can hold. It also HAS ............. ... First, you have an additional step of ensuring that your buffer is null-terminated. Many programmers do not read the fine print and forget this important step. If inp is too long, the function will not write the string's terminating null character in the buffer. If the function does write the terminating null character, you've just wasted an instruction.Second, you need to consider the return of this function-all it gives you is a pointer to the destination string, and it does not Reserve a value for an error. imagine you've decided That if INTO The Buffer, INP IS JUNK AND YOULD RETURN An Error (Which I Recommend IN MOSES). Using Strncpy (), You CAN 'T Easily Determine This Error, Although I've Seen Various Tricks That Work, Such AS // do this firstbuf [sizeof (buf) -1] = '/ 0'; // Tell Strncpy That It Can Write INTO The Whole BufferstrNCPY (BUF, INP, SIZEOF (BUF)); // if the string was too long, this will be overwrittenif (BUF [SizeOf (BUF) -1]! = '/ 0') {Printf ("INP STRING TOON! / N"); Return;} With this modification, you can armor the string handler against everything except some fairly unusual pointer errors. If you think this function seems like a lot of work to get a few characters safely into a buffer, you're absolutely right. _Snprintf () to the rescue THIRD LIBRARY CALL, _SNPRINTF (), Makes a Lot of the code we've been examining Easier to write and less error-prone. _Snprintf () is defined as INT _SNPRINTF (Char * Buffer, Size_t Count, Const Char * Format [, Argument] ...); _Snprintf () is also more Versatile Than The Other Two Library calls, and you can do a lot of otherwise tricky string handling here. for example, void foobar (const char * INP) {char Buf [25]; // Check for Illegal INPUTS IF (INP == NULL) {Assert (false); Printf ("Yuck! You're Passing a null Pointer! / n"); return;} IF (_SnPrintf (BUF, SIZEOF (BUF) -1, "% S", INP <0) {Printf ("Input String Too Long! / N); Return;} else {// ALWAYS NULL TERMINATE BUF [SIZEOF (BUF) -1] = '/ 0';} // do more processing return; Note that you still have to determine whether inp is a valid pointer, and you always have to remember to handle the case where inp is exactly the size that you can place into buf and use sizeof (buf) -1, not the entire size of BUF. i Find this Code a Lot Easier To Read and Und, A Fact That Other Program WHO Have to Work ON Your Code Will AppReciate. However, none of this is free. _Snprintf () is more versatile (eg, you can use it to convert Unicode to and from single-byte), but it comes with more overhead. For example, if performance is extremely critical, such as in an embedded system, you might not want to use _snprintf () Another problem with this library call is that it is not ANSI standard;. as a result, the implementation varies between Windows-based and UNIX-based platforms If portability is. a concern, this problem can be sticky because not all UNIX (or Linux) systems offer this function, and those that do implement it in different ways. Some implementations return the number of bytes that you need in your buffer if an error occurs, and some implementations always null-terminate. If portability is a concern, verify how every OS you'll support deals with this concern, and consider wrapping it. When you wrap a function, you create a function that behaves the same to the outside world, But Hides the Differences Between OSS. FO R Example, int my_snprintf (char * buffer, size_t count, const char * format [, argument] ...) {iDef win32do things the windows the unix wa # Endif} When you compile this code under NT or UNIX, it works as it should-the rest of the application does not have to include the #ifdef stuff everywhere we need to do the same thing. In this case, we'd create a My_sprintf () Which is actually Quite Difficult Because of the variable number of arguments to _snprintf. STL and STRING As it turns out, C and the STL are a great help because under the new ANSI C specification, string is a standard data type and many common jobs related to strings have well-implemented methods that help Let's look at the code.: Void Barbaz (Const Char * INP) {String Str; // Check for ILLEGAL INPUTS IF (INP == NULL) {Assert (false); Printf ("Yuck! You're Passing a Null Pointer! / N"); Return } // this is easy! Str = INP; // now Check to see if it is too long, or had nothing in it ip (Str.Length ()> 25 || str.empty ()) {Printf (" Input string invalid / n "); Return;} // do more processing} Although this code doesn't address the case where INP isn't Terminated, The Following Line Will: Str.copy (INP, 26); How Your Code Handles Long Strings Depends on Whather You Want To Enforce A 25-Character Limit Or Just SELECTED This Limit Because It Was Convenient. I've shown you some of the perils of string handling, and the compromises you encounter using three C calls and a portion of the STL. Improper string handling frequently results in security problems, and I hope this information will help you avoid letting your code Become Part of An Attack On Some's Computer.