You need a way to protect a function that accepts a variable number of arguments from reading more arguments than were passed to the function.
Our solution for dealing with a variable number of arguments is
actually two solutions. The interface for both solutions is
identical, however. Instead of calling va_arg( )
,
you should call spc_next_varg(
)
, listed later in this section. Note, however,
that the signature for the two functions is different. The code:
my_int_arg = va_arg(ap, int);
becomes:
spc_next_varg(ap, int, my_int_arg);
The biggest difference from using variable argument functions is how
you need to make the calls when using this solution. If you can
guarantee that your code will be compiled only by GCC and will always
be running on an x86 processor (or another processor to which you can
port the first solution), you can make calls to the function using
spc_next_varg( )
in the normal way. Otherwise, you
will need to use the
VARARG_CALL_x
macros, where x
is
the number of arguments that you will be passing to the function,
including both fixed and variable.
#include <stdarg.h> #include <stdio.h> #if defined(_ _GNUC_ _) && defined(i386) /* NOTE: This is valid only using GCC on an x86 machine */ #define spc_next_varg(ap, type, var) \ do { \ unsigned int _ _frame; \ _ _frame = *(unsigned int *)_ _builtin_frame_address(0); \ if ((unsigned int)(ap) = = _ _frame - 16) { \ fprintf(stderr, "spc_next_varg( ) called too many times!\n"); \ abort( ); \ } \ (var) = va_arg((ap), (type)); \ } while (0) #define VARARG_CALL_1(func, a1) \ func((a1)) #define VARARG_CALL_2(func, a1, a2) \ func((a1), (a2)) #define VARARG_CALL_3(func, a1, a2, a3) \ func((a1), (a2), (a3)) #define VARARG_CALL_4(func, a1, a2, a3, a4) \ func((a1), (a2), (a3), (a4)) #define VARARG_CALL_5(func, a1, a2, a3, a4, a5) \ func((a1), (a2), (a3), (a4), (a5)) #define VARARG_CALL_6(func, a1, a2, a3, a4, a5, a6) \ func((a1), (a2), (a3), (a4), (a5), (a6)) #define VARARG_CALL_7(func, a1, a2, a3, a4, a5, a6, a7) \ func((a1), (a2), (a3), (a4), (a5), (a6), (a7)) #define VARARG_CALL_8(func, a1, a2, a3, a4, a5, a6, a7, a8) \ func((a1), (a2), (a3), (a4), (a5), (a6), (a7), (a8)) #else /* NOTE: This should work on any machine with any compiler */ #define VARARG_MAGIC 0xDEADBEEF #define spc_next_varg(ap, type, var) \ do { \ (var) = va_arg((ap), (type)); \ if ((int)(var) = = VARARG_MAGIC) { \ fprintf(stderr, "spc_next_varg( ) called too many times!\n"); \ abort( ); \ } \ } while (0) #define VARARG_CALL_1(func, a1) \ func((a1), VARARG_MAGIC) #define VARARG_CALL_2(func, a1, a2) \ func((a1), (a2), VARARG_MAGIC) #define VARARG_CALL_3(func, a1, a2, a3) \ func((a1), (a2), (a3), VARARG_MAGIC) #define VARARG_CALL_4(func, a1, a2, a3, a4) \ func((a1), (a2), (a3), (a4), VARARG_MAGIC) #define VARARG_CALL_5(func, a1, a2, a3, a4, a5) \ func((a1), (a2), (a3), (a4), (a5), VARARG_MAGIC) #define VARARG_CALL_6(func, a1, a2, a3, a4, a5, a6) \ func((a1), (a2), (a3), (a4), (a5), (a6), VARARG_MAGIC) #define VARARG_CALL_7(func, a1, a2, a3, a4, a5, a6, a7) \ func((a1), (a2), (a3), (a4), (a5), (a6), (a7), VARARG_MAGIC) #define VARARG_CALL_8(func, a1, a2, a3, a4, a5, a6, a7, a8) \ func((a1), (a2), (a3), (a4), (a5), (a6), (a7), (a8), VARARG_MAGIC) #endif
Both C and C++ allow the definition of functions that take a variable
number of arguments. The header file
stdarg.h
defines three macros,[1]
va_start(
)
,
va_arg( )
, and va_end(
)
, that
can be used to obtain the arguments in the variable argument list.
First, you must call the macro va_start( )
,
possibly followed by an arbitrary number of calls to va_arg(
)
, and finally, you must call va_end( )
.
A function that takes a variable number of arguments does not know
the number of arguments present or the type of each argument in the
argument list; the function must therefore have some other way of
knowing how many arguments should be present, so as to not make too
many calls to va_arg( )
. In fact, the ANSI C
standard does not define the behavior that occurs should
va_arg( )
be called too many times. Often, the
behavior is to keep returning data from the stack until a hardware
exception occurs, which will crash your program, of course.
Calling va_arg( )
too many times can have
disastrous effects. In Recipe 13.2, we
discussed format string attacks against the printf
family of functions. One particularly dangerous format specifier is
%n
, which causes the number of bytes written so
far to the output destination (whether it's a string
via sprintf( )
, or a file via fprintf(
)
) to be written into the next argument in the variable
argument list. For example:
int x; printf("hello, world%n\n", &x);
In this example code, the integer value 12 would be written into the
variable x
. Imagine what would happen if no
argument were present after the format string, and the return address
were the next thing on the stack: an attacker could overwrite the
return address, possibly resulting in arbitrary code execution.
There is no easy way to protect the printf
family
of functions against this type of attack, except to properly sanitize
input that could eventually make its way down into a call to one of
the printf
family of functions. However, it is
possible to protect variable argument functions that you write
against possible mistakes that would leave the code vulnerable to
such an attack.
The first solution we've presented is compiler- and
processor-specific because it makes use of a GCC-specific built-in
function, _ _builtin_frame_address(
)
, and of knowledge of how the stack is
organized on an x86 based processor to determine where the arguments
pushed by the caller end. With a small amount of effort, this
solution can likely be ported to some other processors as well, but
the non-x86 systems on which we have tested do not work (in
particular, this trick does not work on Apple Mac G3 or G4 systems).
This solution also requires that you do not compile your program
using the optimization option to omit the frame pointer,
-fomit-frame-pointer
, because it depends on having
the frame pointer available.
The second solution we have presented should work with any compiler
and processor combination. It works by adding an extra argument
passed to the function that is a
"magic" value. When
spc_next_varg( )
gets the next argument by calling
va_arg( )
itself, it checks to see whether the
value of the argument matches the
"magic" value. The need to add this
extra "magic" argument is the
reason for the VARARG_CALL_x
macros. We have
chosen a magic value of 0xDEADBEEF
here, but if a
legitimate argument with that value might be used, it can easily be
changed to something else. Certainly, the code provided here could
also be easily modified to allow different
"magic" values to be used for
different function calls.
Finally, note that both implementations of spc_next_varg(
)
print an error message to stderr
and
call abort( )
to terminate the program
immediately. Handling this error condition differently in your own
program may take the form of throwing an exception if you are using
the code in C++, or calling a special handler function. Anything
except allowing the function to proceed can be done here. The error
should not necessarily be treated as fatal, but it certainly is
serious.
[1] The
ANSI C standard dictates that va_start( )
,
va_arg( )
, and va_end( )
must
be macros. However, it does not place any requirements on their
expansion. Some implementations may simply expand the macros to
built-in function calls (GCC does this). Others may be expressions
performing pointer arithmetic (Microsoft Visual C++ does this).
Others still may provide some completely different kind of
implementation for the macros.