Bugtraq mailing list archives

Re: Format String Attacks


From: Ajax <ajax () FIREST0RM ORG>
Date: Fri, 22 Sep 2000 14:48:55 -0500

Yeah, i thought varargs got passed on the stack.  Apologies, it's been a
long week, but I think I'm still right, kinda...

When the compiler constructs the va_list that ap will point to, it simply
makes the last element NULL, which can (ought) never be a valid pointer to
anything.  ap then looks like:

      methinks this is more suitable for comp.lang.c, but what the heck...
we'll let Elias moderate this. :=)

      You cannot assume anything about va_list based on its definition or
prototypes. va_list and va_*() macros are to be treated as black-boxes. Use
them, but do not assume anything about how they are implemented or how it
works.

I'll quote you on that in a second.

For example, on the x86, all args are passed on the stack. If you're calling
printf("help %i %s %f\n", 123, "abc", 2.35432):

                      <=== remainder of the stack
---------------
|  2.3542     |               <=== 3rd vararg, a float
---------------
| char "abc\0"|               <=== 2nd vararg, a ptr to a character string
---------------
|   123       |               <=== first varable arg, in this case an integer
---------------
| const char* |               <=== first arg of printf(), a ptr to the fmt string
---------------
| ret address |     <=== return address, duh
---------------
|   .....     |     <=== SP upon entry to printf()


As you can see, there are no pointers to the integer or float vararg. varargs
is similar to an array of Visual Basic-like variant types, but since one
can't have an array of voids in C, the closest thing is an array of void
pointers.

In this example, when you call va_start(), "ap" is set to point at the first
vararg, which is the integer 123. When you fetch that int using va_arg()
macro, it increments "ap" by the size of the integer.

So, we're supposed to treat va* as black boxes, right?

The compiler emits the code that pushes everything onto the stack.  It
could emit some code that keeps a count of, if not the number of objects
it pushed, then at least the size of the array for perverse x86-like
architectures.  Maybe put this in some process- or thread-specific
copy-on-write page that the C library maintains on process entry.  Make
va_arg decrement the counter.  Implement va_last accordingly, maybe
passing it the expected "next type".  Even if we cons our own va_list up,
the compiler still has to push everything, so it can always keep something
of a count.

This should not break any ABI, since the count variable is not kept in a
register or in the stack.  You can always use #ifdef.  It might require
modifying libgcc or crt0.o a bit, but we shouldn't _need_ to touch any
system library.  And we can always make the new vararg entry code
dependent on the presence of va_last, should that happen to break
something.

.over.enthusiastic.
-=:[ ajax (firest0rm)


Current thread: