Logging, printing, debugging; using printf(), and it’s sibling functions, is much like using a universal remote. It’s power comes from the fact that you can specify any amount of parameters to be printed. This is due to the usage of a variadic parameter as represented by an ellipsis (…). Now, with power comes a great deal of responsibility and through it, a great deal of hardship if the power is exploited.
Recently, I was reading some code and spotted that many developers do not use printf() or it’s derivations properly. I, too, on occasion, have incorrectly given printf() a format string with an incorrect number or type of parameters to print. Many of which end up being benign, such as specifying a “%d” when you print a pointer or specifying a variable to print and not providing one at all. Many of these errors can go unnoticed and may not even crash the program. Which makes you wonder, why is it so silent?
Variadic parameters are handled by walking the current stack frame from the start of the function upward. By specifying a token, such as “%s%d”, tells the function to expect a string pointer and an integer as parameters. It then uses several macros, such as va_args, to walk the stack and pull out the variables to print. When something is expected but never received, this opens the door to errors and exploits.
As an example, by using printf() with “%d %x” and by only specifying one integer, printf will continue to walk the stack and print whatever precedes the integer as a hex value (%x). Why is this a bad thing? Well, knowing that the calling function may have local variables on the stack, a clever hacker could use this to print data that appears on the stack prior to the call to printf(). It is unlikely that any significant data is stored on the stack prior to printf(), but it may happen. This article has a great explanation of the many facets of this exploit. A further example would be to specify “%s” as the format and not provide a value to print. It will cause the function to read an address as if a string, looking for a NULL terminator (which may not appear within the bounds of the current stack frame).
Moving away from the exploit, how can one defend against improper usage of a printf-style function. GCC has a built-in countermeasure for printf-style functions, in which a format string and variadic parameters are used. It also supplies you with an attribute that can be tacked onto the end of a function declaration, which prompts the compiler to verify input. Do note that this check is not as in-depth as one would hope. It will not warn you when “%d” is specified for an unsigned int (where a %u would be used). One must still remain vigilant. Microsoft has annotations, which are similar to GCC’s attributes and accomplish the same task.
Other countermeasures include the usage of stack canaries to thwart runaway string printing and writing to buffers outside the current stack frame. If this subject interests you, I recommend reading the 24 Deadly Sins of Software Development.