possible buffer overflow vulnerability for va_list in C? - c

I have the following code:
int ircsocket_print(char *message, ...)
{
char buffer[512];
int iError;
va_list va;
va_start(va, message);
vsprintf(buffer, message, va);
va_end(va);
send(ircsocket_connection, buffer, strlen(buffer), 0);
return 1;
}
And I wanted to know if this code is vulerable to buffer overflows by providing char arrays with a size > 512 to the variables list? And if so - How can I fix this?
thank you.

Yes, it is vulnerable.
You can implement your function this way:
int ircsocket_print(char *message, ...)
{
char buf[512];
char *buffer;
int len;
va_list va;
buffer = buf;
va_start(va, message);
len = vsnprintf(buffer, 512, message, va);
va_end(va);
if (len >= 512)
{
buffer = (char*)malloc(len + 1);
va_start(va, message);
len = vsnprintf(buffer, len + 1, message, va);
va_end(va);
}
send(ircsocket_connection, buffer, len, 0);
if (buffer != buf)
free(buffer);
return 1;
}

Yes, it is vulnerable.
Simply use vsnprintf instead:
vsnprintf(buffer, sizeof(buffer), message, va);

As everyone else noted, the basic answer to your question is "Yes, you are vulnerable to buffer overflows".
In C99, you could use a VLA:
void ircsocket_print(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
int len = vsnprintf(0, 0, message, args);
va_end(args);
char buffer[len+1];
va_start(args, fmt);
int len = vsnprintf(buffer, len+1, message, args);
va_end(args);
send(ircsocket_connection, buffer, len, 0);
}
Note the idiom that calls vsnprintf() once with length 0 (the second zero) to obtain the length required, then calls it a second time to format the data into the buffer. Also note the careful resetting of args after each call to vsnprintf(); that is required by the C standard:
ยง7.15 <stdarg.h>
If access to the varying arguments is
desired, the called function shall declare an object (generally referred to as ap in this
subclause) having type va_list. The object ap may be passed as an argument to
another function; if that function invokes the va_arg macro with parameter ap, the
value of ap in the calling function is indeterminate and shall be passed to the va_end
macro prior to any further reference to ap.
One downside to this formulation is that it takes a pessimistic view and unconditionally calls vsnprintf() twice. You might prefer to take an optimistic view (most of the time, 512 will be enough), and only allocate more space if the first call shows that it is insufficient.
One more downside to the use of a VLA like this is that if you run out of space for the local variable buffer, your code may never get a chance to recover. You must judge how serious a problem that is. If it is an issue, use explicit memory allocation (malloc()) instead:
char buffer = malloc(len+1);
if (buffer == 0)
return; // Report error?
...second vsnprintf() and send()...
free(buffer);
Since your function only ever returned the constant 1, there is no obvious reason to make it a function returning anything - if it is a function returning void, the calling code does not need any check on the returned value. OTOH, maybe you should be returning the result of the send() call (and possibly an error indication if the malloc() fails).
I also made the format (message) parameter into a const char *; neither this function nor the ones it calls modify the format string.

If you're on Linux you could use vasprintf() which will allocate a buffer of the correct size.
If you need portability you can use vsnprintf() to avoid buffer overflows.

Related

How to make a wrapper for sprintf that returns the string result?

I would like to be able to pass formatted strings to other functions, like my error handler:
error_handler(str_format("error code %d in graphics function: %s", code, error));
My first idea was to use sprintf, but it does not return the result, so you have to add extra lines:
char msg = [100];
sprintf(msg, "error code %d in graphics function: %s", code, error)
error_handler(msg);
And if the error message is bigger than the size of msg then there could be problems.
So I thought I could make a wrapping function like this:
char* str_format (char* format, ...) {
char* output = malloc(100);
va_list args;
va_start(args, format);
vsnprintf(output, 100, format, args);
va_end(args);
return output;
}
But I still have the problem of not knowing the needed size for output. So I thought I could use sizeof to get the size of format and all the args, but there's no way to detect how many args there are unless you manually pass a number of args every time. I could read the format string and look for '%' symbols and detect each placeholder and type, but this seems to be too complicated, just to get the return value of sprintf.
I know there is a function called snprintf that will be safe and limit the number of chars but there is no vsnprintf in c
EDIT: #1 changed output to malloc #2 there is a vsnprintf function after all, so I changed to that. So then the question is down to if there is an easier way than this type of wrapper function. Thank you
Is there an easy way to get sprintf to return the formatted string?
Typically, you use two calls to vsnprintf for this -- the first call with no buffer to get the size needed and a second call to fill the buffer
char* str_format (char* format, ...) {
va_list args;
va_start(args, format);
int len = vsnprintf(0, 0, format, args);
va_end(args);
char *output = malloc(len+1);
va_start(args, format);
vsnprintf(output, len+1, format, args);
va_end(args);
return output;
}
Note that the caller will need to free the allocated pointer (or it will leak)
So I thought I could make a wrapping function like this:
/* !!! DON'T DO THIS !!! */
char* str_format (char* format, ...) {
char output[100];
va_list args;
va_start(args, format);
vsprintf(output, format, args);
va_end(args);
return output;
}
This is bad for multiple reasons: first, as you say you don't know the size of the output. Most importantly however, you cannot possibly return a variable defined locally (like output).
To know the size of the output, you can first make a "dummy" call to vsnprintf with a size of 0, which according to the manual should simply calculate and return the needed size for the output buffer.
As per returning a valid pointer, you could make this work in two different ways:
Allocate a buffer with malloc, then return a pointer to the allocated buffer. This could be annoying since you would need to always free() the allocated buffer, and since you want to use your function like error_handler(str_format(...)) you lose reference to the returned buffer, so you either free() it in error_handler() and always assume that error_handler() will take malloc'd objects, or you need a temporary variable.
Use a static buffer. This however has the problem of needing to be pre-allocated with a constant size at compile time. You could choose this option and limit your error message size. One other limitation of this approach is that only one error at a time can be formatted (i.e. this is not thread safe), as you would be overwriting the same static buffer each time. This is a pretty common among C libraries as it is painless and easy to implement, but only really applicable if you have reasonably-sized error messages.
Option 1 (error checking omitted):
char* str_format(char* format, ...) {
char *buf;
int size;
va_list args;
va_start(args, format);
size = vsnprintf(NULL, 0, format, args) + 1;
va_end(args);
buf = malloc(size);
va_start(args, format);
vsnprintf(NULL, size, format, args);
va_end(args);
return buf;
}
Option 2:
char* str_format(char* format, ...) {
static char buf[1024]; // fixed at compile time
va_list args;
va_start(args, format);
vsnprintf(NULL, 1024, format, args);
va_end(args);
return buf;
}
Note that for vsnprintf you need to define at least one of the following feature test macros, as specified by the manual:
#define _XOPEN_SOURCE 500
// or
#define _ISOC99_SOURCE
// or
#define _BSD_SOURCE // old glibc <= 2.19

C - Variable Argument with Sprintf?

I have a function;
void foo(const char* format, ...)
{
char buffer[1080];
// Supposed way to handle C Variable Arguments?
va_list argptr;
va_start(argptr, format);
sprintf(buffer, format, argptr);
va_end(argptr);
printf_s("%s.\n", buffer);
}
int main()
{
int val = 53;
foo("%d", val);
}
Everytime I run this, I get MASSIVE numbers that change every during each run. 12253360, 5306452, etc. I don't understand why.
Is it something with my sprintf call or is it the way I'm doing the va_list argptr;? Is my buffer too large?
Thanks.
The technique you are apparently attempting to use suggests that you need vsprintf (or, better, vsnprintf)
va_list argptr;
va_start(argptr, format);
vsnprintf(buffer, sizeof buffer, format, argptr);
va_end(argptr);
That's the very reason such functions from v... group exist in standard library.
Calling sprintf the way you do it makes no sense at all - it cannot be used with an externally supplied va_list.
If you want to implement a variable-sized buffer you can do it as follows
void foo(const char* format, ...)
{
static char *buffer;
static size_t buffer_size;
va_list argptr;
va_start(argptr, format);
int length = vsnprintf(buffer, buffer_size, format, argptr);
va_end(argptr);
if (length + 1 > buffer_size)
{
buffer_size = length + 1;
buffer = realloc(buffer, buffer_size);
/* Yes, `realloc` should be done differently to properly handle
possible failures. But that's beside the point in this context */
va_start(argptr, format);
vsnprintf(buffer, buffer_size, format, argptr);
va_end(argptr);
}
printf("%s.\n", buffer);
}
You can, of course, change the memory management strategy to something different, like use a fixed local buffer of 512 bytes in the first call, and then use a temporary dynamically allocated buffer in the second call only if 512 proves to be insufficient. And so on...

C variadic wrapper

To output formatted debug output, I've written a wrapper for vsfprint. Now, I wanted to allocate exactly enough memory for the output buffer, instead of just claiming a random high buffer size (it's a small embedded platform (ESP8266)). For that I iterate through the variable arguments until a NULL is found.
This works fine, provided that I don't forget to add a (char *)NULL parameter to every call. So, I thought, let create another wrapper, a function that just relays all arguments and adds a (char *) NULL parameter:
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h> // malloc
void write_log(const char *format, ...) {
char* buffdyn;
va_list args;
// CALC. MEMORY
size_t len;
char *p;
if(format == NULL)
return;
len = strlen(format);
va_start(args, format);
while((p = va_arg(args, char *)) != NULL)
len += strlen(p);
va_end(args);
// END CALC. MEMORY
// ALLOCATE MEMORY
buffdyn = malloc(len + 1); /* +1 for trailing \0 */
if(buffdyn == NULL) {
printf("Not enough memory to process message.");
return;
}
va_start(args, format);
//vsnprintf = Write formatted data from variable argument list to sized buffer
vsnprintf(buffdyn, len, format, args);
va_end(args);
printf("%s\r\n",buffdyn);
free(buffdyn);
}
void write_log_wrapper(const char *format, ...) {
va_list arg;
va_start(arg, format);
write_log(format,arg,(char *)NULL);
va_end(arg);
}
int main()
{
const char* sDeviceName = "TEST123";
const char* sFiller1 = "12345678";
write_log_wrapper("Welcome to %s%s", sDeviceName,sFiller1);
write_log("Welcome to %s%s", sDeviceName,sFiller1, (char *)NULL);
return 0;
}
Calling the write_log() function directly works fine (if you don't forget the NULL parameter). Calling the write_log_wrapper() function will only display the first paramter, and then adds a "(nu" (garbage?) to the output.
What am I doing wrong? Is this a good way to approach what I'm aiming to do in the first place?
Thanks.
To determine how big a buffer is needed to hold the output string, you need to fully parse the entire format string and actually expand the arguments.
You can either do it yourself, duplicating all the processing of printf() and its ilk and hoping to not make any mistakes, or you can use vsnprintf() - first to determine the size, and then to actually expand the inputs to one output string.
#define FIXED_SIZE 64
void write_log(const char *format, ...)
{
// set up a fixed-size buffer and a pointer to it
char fixedSizeBuffer[ FIXED_SIZE ];
char *outputBuffer = fixedSizeBuffer;
// no dynamic buffer yet
char *dynamicBuffer = NULL;
// get the variable args
va_list args1;
va_start( args1, format );
// need to copy the args even though we won't know if we
// need them until after we use the first set
va_list args2;
va_copy( args2, args1 );
// have to call vsnprintf at least once - might as well use a small
// fixed-size buffer just in case the final string fits in it
int len = vsnprintf( fixedSizeBuffer, sizeof( fixedSizeBuffer ), format, args1 );
va_end( args1 );
// it didn't fit - get a dynamic buffer, expand the string, and
// point the outputBuffer pointer at the dynamic buffer so later
// processing uses the right string
if ( len > sizeof( fixedSizeBuffer ) )
{
dynamicBuffer = malloc( len + 1 );
vsnprintf( dynamicBuffer, len + 1, format, args2 );
outputBuffer = dynamicBuffer;
}
va_end( args2 );
// do something with outputBuffer
free( dynamicBuffer );
return;
}
What am I doing wrong?
Passing a va_list arg
write_log(format, arg, (char *)NULL);
is not the same as passing several char*
write_log("Welcome to %s%s", sDeviceName, sFiller1, (char *)NULL);
You won't get around to pass a sentinel marking the end of the parameters passed, that is a (char*) NULL or whatever you decide to use.
Alternatives would be to
pass the number of arguments explicitly, perhaps as 2nd parameter
parse the format string for conversion specifiers, in fact mimicking what printf does.
If you want just to ensure that all calls receive a setinel at the end, use a macro:
#define WRITE_LOG(...) write_log(__VA_ARGS__, (char*)0)
This ensures that there is always an extra 0 at the end.
Also be careful with NULL. It is underspecified in the C standard to what expressions this resolves. Common cases are 0 and (void*)0. So on 64bit architectures these may have different width (32 bit for the first, 64 bit for the second). It can be deadly for a variadic function to receive the wrong width here. Therefore I used (char*)0 which is the type that your function seems to expect. (But (void*)0 would also do in this special case.)

Substitute or workaround for asprintf on AIX

I'm trying to build python-kerberos on AIX. kerberospw.c uses a call to asprintf, but from what Google is telling me, asprintf does not exist on AIX.
I saw http://www.koders.com/c/fidAA9B130D588302673A28B568430A83131B7734C0.aspx?s=windows.h, which looks like I could create a stand-in asprintf, but I don't know where this would go or how I would #include it in kerberospw.c.
Is there a way I can use the koders.com example or some other code to "fake" asprintf? Can I just include the asprintf function as shown in kerberospw.c? I am not a C coder, but
asprintf (char **resultp, const char *format, ...)
doesn't look like a valid signature to me with the dots at the end. The relevant line from kerberospw.c is below
asprintf(&message, "%.*s: %.*s",
(int) result_code_string.length,
(char *) result_code_string.data,
(int) result_string.length,
(char *) result_string.data);
I realize I could contact the author of python-kerberos, but a) I think it would be helpful to have a potential patch if I did so, and b) there might be other software I run across that uses asprintf, and it would be nice to have a workaround.
The asprintf is a variation of the printf family of function that allocate a buffer to hold the memory for the formatted string and return it. It is a function with a variable number of argument (hence the ... in the declaration that is valid C code). You can find a description here.
It can be reimplemented relatively easily if the vsnprintf is functioning correctly (ie, return an error if the buffer is too small to hold the formatted string).
Here is such an implementation:
#include <stdarg.h>
int asprintf(char **ret, const char *format, ...)
{
va_list ap;
*ret = NULL; /* Ensure value can be passed to free() */
va_start(ap, format);
int count = vsnprintf(NULL, 0, format, ap);
va_end(ap);
if (count >= 0)
{
char* buffer = malloc(count + 1);
if (buffer == NULL)
return -1;
va_start(ap, format);
count = vsnprintf(buffer, count + 1, format, ap);
va_end(ap);
if (count < 0)
{
free(buffer);
return count;
}
*ret = buffer;
}
return count;
}
Building on Sylvain's answer, here is a simple implementation with both asprintf() and vasprintf() because where you need one, you usually end up needing the other too. And, given the va_copy() macro from C99, it is easy to implement asprintf() in terms of vasprintf(). Indeed, when writing varargs functions, it is very often helpful to have them in pairs, one with the ellipsis notation and one with the va_list argument in place of the ellipsis, and you trivially implement the former in terms of the latter.
This leads to the code:
int vasprintf(char **ret, const char *format, va_list args)
{
va_list copy;
va_copy(copy, args);
/* Make sure it is determinate, despite manuals indicating otherwise */
*ret = NULL;
int count = vsnprintf(NULL, 0, format, args);
if (count >= 0)
{
char *buffer = malloc(count + 1);
if (buffer == NULL)
count = -1;
else if ((count = vsnprintf(buffer, count + 1, format, copy)) < 0)
free(buffer);
else
*ret = buffer;
}
va_end(copy); // Each va_start() or va_copy() needs a va_end()
return count;
}
int asprintf(char **ret, const char *format, ...)
{
va_list args;
va_start(args, format);
int count = vasprintf(ret, format, args);
va_end(args);
return(count);
}
The tricky part of using these functions in a system where they are not provided is deciding where the functions should be declared. Ideally, they'd be in <stdio.h>, but then you wouldn't need to write them. So, you have to have some other header which includes <stdio.h> but declares these functions if they are not declared in <stdio.h>. And, ideally, the code should semi-automatically detect this. Maybe the header is "missing.h", and contains (in part):
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */
#include <stdio.h>
#include <stdarg.h>
#ifndef HAVE_ASPRINTF
extern int asprintf(char **ret, const char *format, ...);
extern int vasprintf(char **ret, const char *format, va_list args);
#endif /* HAVE_ASPRINTF */
Also, note that this man page for asprintf() says that the return value in the pointer is indeterminate in case of error. Other man pages, including the one referenced in the question, indicate that it is explicitly set to NULL on error. The C Standard committee document (n1337.pdf) does not specify the error behaviour on lack of memory.
If using asprintf(), do not assume that the pointer is initialized if the function fails.
If implementing asprintf(), ensure that the pointer is set to null on error to give deterministic behaviour.
I came here looking for a quick implementation for Windows and Linux which set the return pointer to NULL on failure.
Jonathan Leffler's answer looked to be the better one, but then I noticed it doesn't set -1 when malloc fails.
I did more searching and came across this discussion of implementing asprintf, which then enlightened me that Jonathan and Sylvain both did not handle overflow correctly either.
I now recommend this solution provided with the aforementioned discussion, which seems to be cover all the important platforms and apparently handles every failure scenario correctly.
Here an implementation that doesn't call snprintf() twice in most of the cases. I omitted the includes and defines as shown in other responses.
As it should be, define the asprintf() as a call to vasprintf()
int asprintf(char **dst, const char * pcFormat, ...)
{
va_list ap;
va_start(ap, pcFormat);
int len = vasprintf(dst, pcFormat, ap);
va_end(ap);
return len;
}
We preallocate a buffer to an predefined appropriate size and only in case of overflow call vsnprintf() a second time. The rationale being that s*printf() function are considered very heavy and overallocating memory being acceptable.
int vasprintf(char **dst, const char * pcFormat, va_list ap)
{
int len = 512; /* Worked quite well on our project */
int allocated = 0;
va_list ap_copy;
char *buff = NULL;
while(len >= allocated) {
free(buff);
buff = malloc(len+1);
if(buff) {
allocated = len+1;
va_copy(ap_copy, ap);
len = vsnprintf(buff, len+1, pcFormat, ap_copy);
va_end(ap_copy);
}
else /* malloc() failed */
return -1;
}
*dst = buff;
return len;
}
EDIT: I replaced the realloc() call by a simple malloc() as it is cheaper. In the case of overflow a free()/malloc() pair costs less than realloc() because of its internal hidden memcpy(). As we overwrite the whole buffer anyway with the subsequent call to vsnprintf() there is no point in that copy.

Creating C formatted strings (not printing them)

I have a function that accepts a string, that is:
void log_out(char *);
In calling it, I need to create a formatted string on the fly like:
int i = 1;
log_out("some text %d", i);
How do I do this in ANSI C?
Only, since sprintf() returns a int, this means that I have to write at least 3 commands, like:
char *s;
sprintf(s, "%d\t%d", ix, iy);
log_out(s);
Any way to shorten this?
Use sprintf. (This is NOT safe, but OP asked for an ANSI C answer. See the comments for a safe version.)
int sprintf ( char * str, const char * format, ... );
Write formatted data to string Composes a string with the same text
that would be printed if format was used on printf, but instead of
being printed, the content is stored as a C string in the buffer
pointed by str.
The size of the buffer should be large enough to contain the entire
resulting string (see snprintf for a safer version).
A terminating null character is automatically appended after the
content.
After the format parameter, the function expects at least as many
additional arguments as needed for format.
Parameters:
str
Pointer to a buffer where the resulting C-string is stored. The buffer
should be large enough to contain the resulting string.
format
C string that contains a format string that follows the same
specifications as format in printf (see printf for details).
... (additional arguments)
Depending on the format string, the function may expect a sequence of
additional arguments, each containing a value to be used to replace a
format specifier in the format string (or a pointer to a storage
location, for n). There should be at least as many of these arguments
as the number of values specified in the format specifiers. Additional
arguments are ignored by the function.
Example:
// Allocates storage
char *hello_world = (char*)malloc(13 * sizeof(char));
// Prints "Hello world!" on hello_world
sprintf(hello_world, "%s %s!", "Hello", "world");
If you have a POSIX-2008 compliant system (any modern Linux), you can use the safe and convenient asprintf() function: It will malloc() enough memory for you, you don't need to worry about the maximum string size. Use it like this:
char* string;
if(0 > asprintf(&string, "Formatting a number: %d\n", 42)) return error;
log_out(string);
free(string);
This is the minimum effort you can get to construct the string in a secure fashion. The sprintf() code you gave in the question is deeply flawed:
There is no allocated memory behind the pointer. You are writing the string to a random location in memory!
Even if you had written
char s[42];
you would be in deep trouble, because you can't know what number to put into the brackets.
Even if you had used the "safe" variant snprintf(), you would still run the danger that your strings gets truncated. When writing to a log file, that is a relatively minor concern, but it has the potential to cut off precisely the information that would have been useful. Also, it'll cut off the trailing endline character, gluing the next log line to the end of your unsuccessfully written line.
If you try to use a combination of malloc() and snprintf() to produce correct behavior in all cases, you end up with roughly twice as much code than I have given for asprintf(), and basically reprogram the functionality of asprintf().
If you are looking at providing a wrapper of log_out() that can take a printf() style parameter list itself, you can use the variant vasprintf() which takes a va_list as an argument. Here is a perfectly safe implementation of such a wrapper:
//Tell gcc that we are defining a printf-style function so that it can do type checking.
//Obviously, this should go into a header.
void log_out_wrapper(const char *format, ...) __attribute__ ((format (printf, 1, 2)));
void log_out_wrapper(const char *format, ...) {
char* string;
va_list args;
va_start(args, format);
if(0 > vasprintf(&string, format, args)) string = NULL; //this is for logging, so failed allocation is not fatal
va_end(args);
if(string) {
log_out(string);
free(string);
} else {
log_out("Error while logging a message: Memory allocation failed.\n");
}
}
It sounds to me like you want to be able to easily pass a string created using printf-style formatting to the function you already have that takes a simple string. You can create a wrapper function using stdarg.h facilities and vsnprintf() (which may not be readily available, depending on your compiler/platform):
#include <stdarg.h>
#include <stdio.h>
// a function that accepts a string:
void foo( char* s);
// You'd like to call a function that takes a format string
// and then calls foo():
void foofmt( char* fmt, ...)
{
char buf[100]; // this should really be sized appropriately
// possibly in response to a call to vsnprintf()
va_list vl;
va_start(vl, fmt);
vsnprintf( buf, sizeof( buf), fmt, vl);
va_end( vl);
foo( buf);
}
int main()
{
int val = 42;
foofmt( "Some value: %d\n", val);
return 0;
}
For platforms that don't provide a good implementation (or any implementation) of the snprintf() family of routines, I've successfully used a nearly public domain snprintf() from Holger Weiss.
Don't use sprintf.
It will overflow your String-Buffer and crash your Program.
Always use snprintf
If you have the code to log_out(), rewrite it. Most likely, you can do:
static FILE *logfp = ...;
void log_out(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vfprintf(logfp, fmt, args);
va_end(args);
}
If there is extra logging information needed, that can be printed before or after the message shown. This saves memory allocation and dubious buffer sizes and so on and so forth. You probably need to initialize logfp to zero (null pointer) and check whether it is null and open the log file as appropriate - but the code in the existing log_out() should be dealing with that anyway.
The advantage to this solution is that you can simply call it as if it was a variant of printf(); indeed, it is a minor variant on printf().
If you don't have the code to log_out(), consider whether you can replace it with a variant such as the one outlined above. Whether you can use the same name will depend on your application framework and the ultimate source of the current log_out() function. If it is in the same object file as another indispensable function, you would have to use a new name. If you cannot work out how to replicate it exactly, you will have to use some variant like those given in other answers that allocates an appropriate amount of memory.
void log_out_wrapper(const char *fmt, ...)
{
va_list args;
size_t len;
char *space;
va_start(args, fmt);
len = vsnprintf(0, 0, fmt, args);
va_end(args);
if ((space = malloc(len + 1)) != 0)
{
va_start(args, fmt);
vsnprintf(space, len+1, fmt, args);
va_end(args);
log_out(space);
free(space);
}
/* else - what to do if memory allocation fails? */
}
Obviously, you now call the log_out_wrapper() instead of log_out() - but the memory allocation and so on is done once. I reserve the right to be over-allocating space by one unnecessary byte - I've not double-checked whether the length returned by vsnprintf() includes the terminating null or not.
Verified and Summary:
sprintf vs asprintf
asprintf = malloc + sprintf
sample code
sprintf
int largeEnoughBufferLen = 20;
char *someStr = (char*)malloc(largeEnoughBufferLen * sizeof(char));
sprintf(someStr, "formatted string: %s %s!", "Hello", "world");
// do what you want for formatted string: someStr
free(someStr);
asprintf
char *someStr;
int formattedStrResult = asprintf(&someStr, "formatted string: %s %s!", "Hello", "world");
if(formattedStrResult > 0){
// do what you want for formatted string: someStr
free(someStr);
} else {
// some error
}
I haven't done this, so I'm just going to point at the right answer.
C has provisions for functions that take unspecified numbers of operands, using the <stdarg.h> header. You can define your function as void log_out(const char *fmt, ...);, and get the va_list inside the function. Then you can allocate memory and call vsprintf() with the allocated memory, format, and va_list.
Alternately, you could use this to write a function analogous to sprintf() that would allocate memory and return the formatted string, generating it more or less as above. It would be a memory leak, but if you're just logging out it may not matter.
http://www.gnu.org/software/hello/manual/libc/Variable-Arguments-Output.html gives the following example to print to stderr. You can modify it to use your log function instead:
#include <stdio.h>
#include <stdarg.h>
void
eprintf (const char *template, ...)
{
va_list ap;
extern char *program_invocation_short_name;
fprintf (stderr, "%s: ", program_invocation_short_name);
va_start (ap, template);
vfprintf (stderr, template, ap);
va_end (ap);
}
Instead of vfprintf you will need to use vsprintf where you need to provide an adequate buffer to print into.

Resources