How to prevent GCC from compiling error-prone vsnprintf? - c

Is it possible to detect at compile time that the fourth argument of vsnprintf() consists of fewer than required by the third argument of vsnprintf()? I mean:
#include <stdio.h>
#include <stdarg.h>
void foo(const char* format, ...)
{
char buffer[256];
va_list args;
va_start (args, format);
vsnprintf(buffer, 256, format, args); // args may points to fewer arguments than expected in the format
va_end (args);
}
int main ()
{
foo("%d %d", 1); // the format requires two arguments, but only one is provided, so a warning from the compiler is expected here
return 0;
}
gcc -Wall main.c compiles the above without warnings

Apply an attribute to foo.
__attribute__((__format__(__printf__, 1, 2)))
void foo(const char* format, ...) {
Compile with -Werror=format.

Related

why called function can get arguments of parent-function by va_start()?

I implemented a function with variable arguments below:
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#define ERR_BUFFER_SIZE 4096
void errPrint(const char *format, ...)
{
va_list argptr;
va_start(argptr, format);
char buf[ERR_BUFFER_SIZE];
vsnprintf(buf, sizeof(buf), format, argptr);
fprintf(stderr, "%s", buf);
va_end(argptr);
}
Then I hope to implement another named "errExit()" based on the function above.
I just tried like below.It works as I hoped but I dont't think it correct.
void errExit(const char *format, ...)
{
errPrint(format);
exit(EXIT_FAILURE);
}
I tried errExit("hello,%s,%s,%s", "arg1","arg2", "arg3"); and it printed "hello,arg1,arg2,arg3" correctly.
But after I added two line code like below, it throwed error Segmentation fault.
void errExit(const char *format, ...)
{
char buf[4096];//added
strcpy(buf, format);//added
errPrint(format);
exit(EXIT_FAILURE);
}
I am very confused.
According to what I learned:
There is only one argument format in the stack of called function errPrint(). I can't believe va_start() will get arguments from the its parent-function errExit().
Since errPrint() works "correctly", why it doesn't work after I add the two lines code? It seems that the code added have no effect.
(My English is not well, hoping you everyone can stand my statement. Thank you! )
In errExit() you need to pass the variable arguments to another function via a va_list argument. As you were not, vsnprintf will eventually crash trying to access non-existing arguments on the stack.
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ERR_BUFFER_SIZE 4096
void verrPrint(const char *format, va_list ap) {
char buf[ERR_BUFFER_SIZE];
vsnprintf(buf, sizeof(buf), format, ap);
fprintf(stderr, "%s", buf);
}
void errPrint(const char *format, ...) {
va_list ap;
va_start(ap, format);
verrPrint(format, ap);
va_end(ap);
}
void errExit(const char *format, ...) {
va_list ap;
va_start(ap, format);
verrPrint(format, ap);
va_end(ap);
exit(EXIT_FAILURE);
}
int main(void) {
errPrint("What is the meaning of %s\n", "life?");
errExit("%d\n", 42);
return 0;
}
Other answers explain what you need to do. This answer is about the asm details of why it happened to work.
In your first errExit, if you look at the compiler-generated assembly for x86-64, all the incoming args would still be in registers when calling errPrint(format);. Even though you don't tell the compiler to pass them, it doesn't do anything that uses registers before calling that other function.
So it happens to work even though you didn't explicitly pass along any args beyond the first (format). It compiles as if you'd written a function that passed along its first 6 args, assuming the x86-64 System V calling convention, and that the args were all integer or pointer (such as char*).
In the C abstract machine, your code is meaningless and has undefined behaviour when errPrint makes a va_list argptr and passes it on to a function (vsnprintf) that references more than 0 elements of that list.
It might help to look at asm for a function that does pass on multiple args:
void foo(char *a, char *b, char *c, char *d, char *f);
int bar(char *a, char *b, char *c, char *d, char *f)
{
foo(a, b, c, d, f);
return 1; // some code after the call, so it can't compile to a tailcall
}
On Godbolt, compiling for Linux with GCC12:
# GCC12 -O3
bar:
# incoming args are in RDI, RSI, RDX, RCX, R8, R9 in that order
# same place a callee will look for them, so passing them on is trivial
sub rsp, 8 # re-align the stack so RSP%16 == 0
call foo
mov eax, 1 # return-value register = 1
add rsp, 8 # restore the stack pointer
ret # pop return address into RIP
Your errExit has equivalent asm before the call, so if there are register args, they get passed on even though you didn't tell the C compiler about that.
errExit:
sub rsp, 8
xor eax, eax # Variadic functions get AL = # of args passed in XMM regs
call errPrint
mov edi, 1
call exit # GCC knows exit() is noreturn
If there was any code before call errPrint, such as a call to strcpy, that would of course step on those arg-passing registers. So they have different values when call errPrint runs, except for the one arg you told the C compiler about. It will save that in a call-preserved register across a call strcpy.
If you'd compiled this for a calling convention with fewer register args, like 32-bit x86 (gcc -m32) where there are only stack args, the pointers that vsnprintf references with %s conversions be from errExit's stack frame, above any args it intended to pass. So probably a stack slot of padding for alignment, then in a debug build maybe a saved EBP, maybe even errExit's own return address.
If you're curious, use %p conversions to print the pointer values instead of trying to dereference and segfaulting.
C doesn't have a way to specify that it should pass on only the register args; if you want to pass on an unknown number of args from a ..., you have to do it with a va_list which works for any number of args, including 7 or more on x86-64 System V. That's a good thing because some targets wouldn't have any register args, so just saving/restoring the register args would pass on 0 args.
I'm showing asm only to understand why one version happened to work, not as a suggestion for anything you can do to write safe and portable C that can compile to asm like this, even if there's no other work before the call errPrint.
Thank you everyone.I have learned your from your answers.
I choose to change my code like below.
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ERR_BUFFER_SIZE 4096
void errPrint(const char *format, va_list arg_list)
{
char buf[ERR_BUFFER_SIZE];
vsnprintf(buf, sizeof(buf), format, arg_list);
fprintf(stderr, "%s", buf);
}
void errExit(const char *format, va_list arg_list)
{
errPrint(format, arg_list);
exit(EXIT_FAILURE);
}
void v_exec(void (*func)(const char *, va_list), const char *format, ...)
{
va_list arg_list;
va_start(arg_list, format);
func(format, arg_list);
va_end(arg_list);
}
int main(void)
{
v_exec(errExit,"%s%d", "hello",520);
return 0;
}

Conflicting types compiling a LD_PRELOAD wrapper

I tried to use LD_PRELOAD to hook sprintf function , so I will print to file the result of buffer:
#define _GNU_SOURCE
#include <stdio.h>
#include<dlfcn.h>
int sprintf (char * src , const char * format , char* argp)
{
int (*original_func)(char*,const char * , char*);
original_func = dlsym(RTLD_NEXT,"sprintf");
int ret = (*original_func)(src ,format,argp);
FILE* output = fopen("log.txt","a");
fprintf(output,"%s \n",src);
fclose(output);
return ret;
}
When I compile this code gcc -Wall -fPIC -shared -o my_lib.so test_ld.c -ldl
I got error
test_ld.c:5:5: error: conflicting types for ‘sprintf’
int sprintf (char * src , const char * format , char* argp)
^
In file included from test_ld.c:2:0:
/usr/include/stdio.h:364:12: note: previous declaration of ‘sprintf’ was here
extern int sprintf (char *__restrict __s,
How can I fix that?
The main problem you're having is that your prototype for sprintf doesn't match the official one. Your function has this signature:
int sprintf (char * src , const char * format , char* argp);
While the official one has:
int sprintf(char *str, const char *format, ...);
You'll need to change your function to have this signature. Once you do that, you'll need to use a va_list to get the variadic argument. Then you would use that to call vsprintf which takes an argument of this type instead of using dlsym to load sprintf.
#include <stdio.h>
#include <stdarg.h>
int sprintf (char * src , const char * format , ...)
{
va_list args;
va_start(args, format);
int ret = vsprintf(src, format, args);
va_end(args);
FILE* output = fopen("log.txt","a");
fprintf(output,"%s \n",src);
fclose(output);
return ret;
}
Alex's first solution nicely addresses one problem: the conflicting declarations of sprintf (although there is no reason to not use the same signature as in stdio.h, see dbush's answer) . However, even then there remains one large elephant in the room: sprintf is a variadic function.
That means that, whenever the wrapped program calls sprintf with anything other than one char * third argument, your output may not be correct (and may even depend on your compiler's -O level)
Calling variadic functions from variadic functions (what is essentially what you're doing here) is a known problem. Any solution will be non-portable. With gcc, you can use __buitlin_apply and tap into gccs own private way of handling argument lists:
/* sprintf.c, compile with gcc -Wall -fPIC -shared -o sprintf.so sprintf.c -ldl
and use with LD_PRELOAD=./sprintf.so <program> */
#define _GNU_SOURCE
#define sprintf xsprintf
#include <stdio.h>
#include<dlfcn.h>
#undef sprintf
# define ENOUGH 100 /* how many bytes of our call stack
to pass to the original function */
int sprintf (char *src) /* only needs the first argument */
{
void *original_func = dlsym(RTLD_NEXT,"sprintf");
void *arg = __builtin_apply_args();
void *ret = __builtin_apply((void *)original_func, arg, ENOUGH);
FILE* output = fopen("log.txt","a");
fprintf(output,"%s \n",src);
fclose(output);
__builtin_return(ret);
}
A few remarks:
In well-designed libraries variadic functions will have a non-variadic counterpart that uses one va_list argument instead of a variable number of arguments. In that case (and sprintf - vsprintf is such a case) you can use Alex's (portable) second solution with the va_* macros. If not, the solution with __builtin_apply() is the only possible, albeit gcc-specific, one.
See also: call printf using va_list
Possibly depending on the compiler version, when compiling main.c with the -O2 flag, main() will actually call __sprintf_chk() instead of sprintf() (regardless of -fno-builtin) and the wrapper won't work. To demonstrate the wrapper, compile main.c with -O0. Changing the main program to get the wrapper to work is the tail wagging the dog, of course. This shows the fragility of building wrappers: programs often don't call the library functions you expect. A ltrace <program> beforehand can save a lot of work....
You can rename the symbol in stdio, but then there's another problem, compilers like gcc use built-in implementations, unless you pass a flag like -fno-builtin, the compiler will generate the code inline in the executable, it won't link any library for functions like sprintf.
sprintf.c:
#define _GNU_SOURCE
#define sprintf xsprintf
#include <stdio.h>
#include<dlfcn.h>
#undef sprintf
int sprintf (char * src , const char * format , char* argp)
{
int (*original_func)(char*,const char * , char*);
original_func = dlsym(RTLD_NEXT,"sprintf");
int ret = (*original_func)(src ,format,argp);
FILE* output = fopen("log.txt","a");
fprintf(output,"%s \n",src);
fclose(output);
return ret;
}
main.c:
#include <stdio.h>
int main(int argc, char *argv[]) {
char buffer[80];
sprintf(buffer, "hello world");
puts(buffer);
return 0;
}
Makefile:
all: libsprintf.so main
main: main.c
gcc -Wall -O2 -fno-builtin -o main main.c
libsprintf.so: sprintf.c
gcc -Wall -O2 -shared -fPIC -fno-builtin -o libsprintf.so sprintf.c -ldl
.PHONY: clean
clean:
-rm -f main libsprintf.so
usage:
make
LD_PRELOAD=./libsprintf.so ./main
edit
sprintf.c with variadic implementation (it doesn't call sprintf):
#define _GNU_SOURCE
#define sprintf xsprintf
#include <stdio.h>
#include<dlfcn.h>
#undef sprintf
#include <stdarg.h>
int sprintf (char * src , const char * fmt , ...)
{
va_list args;
va_start(args, fmt);
int ret = vsprintf(src, fmt, args);
va_end(args);
FILE* output = fopen("log.txt","a");
fprintf(output,"%s \n", src);
fclose(output);
return ret;
}

GCC no longer implements <varargs.h>

I have to change this code fragment from varargs.h to stdarg.h, but I do not know exactly how to:
#ifndef lint
int ll_log (va_alist)
va_dcl
{
int event, result;
LLog *lp;
va_list ap;
va_start (ap);
lp = va_arg (ap, LLog *);
event = va_arg (ap, int);
result = _ll_log (lp, event, ap);
va_end (ap);
return result;
}
When I try build this, compiler says:
error "GCC no longer implements <varargs.h>."
error "Revise your code to use <stdarg.h>."
The program, which I need to compile and run, has a few similar fragments and I need to know how to change them. If you can write some example, I'll be content.
<varargs.h> is a pre-standard C header; use <stdarg.h> instead. The differences:
The function must take at least one named argument.
The function must be prototyped (using the ellipsis terminator).
The va_start macro works differently: it takes two arguments, the first being the va_list to be initialized and the second the name of the last named argument.
Example:
int ll_log (LLog *llog, ...) {
int event, result;
LLog *lp;
va_list ap;
va_start (ap, llog);
lp = llog;
event = va_arg (ap, int);
result = _ll_log (lp, event, ap);
va_end (ap);
return result;
}
Regarding va_start: gcc ignores the second argument, but not giving the correct one is not portable.
You have to include
#include <stdarg.h>
va_ Macro syntax stays the same.

vsprintf and vsnprintf [-Wformat-nonliteral] warning on Clang 5.0

i have this chunk of code
static void err_doit(int errnoflag, int level, const char *fmt, va_list ap)
{
int errno_save;
unsigned long n;
char buf[MAXLINE];
errno_save = errno;
#ifdef HAVE_VSNPRINTF
vsnprintf(buf, sizeof(buf), fmt, ap); /* this is safe */
#else
vsprintf(buf ,fmt, ap); /* this is not safe */
#endif
n = strlen(buf);
if (errnoflag)
snprintf(buf + n, sizeof(buf) - n, ": %s", strerror(errno_save));
strcat(buf, "\n");
if (daemon_proc) {
syslog(level,"%s", buf);
} else {
fflush(stdout);
fputs(buf, stderr);
fflush(stderr);
}
return;
}
when i compile it (Clang 5.0.0 with -Weverything) i obtain those warnings:
Building C object lib/netutils/CMakeFiles/netutils.dir/error.c.o
/Users/User/Desktop/project.cmake/lib/netutils/error.c:98:16: warning: format string is not a string literal [-Wformat-nonliteral]
vsprintf(buf ,fmt, ap); /* this is not safe */
^~~
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk/usr/include/secure/_stdio.h:66:57: note: expanded from
macro 'vsprintf'__builtin___vsprintf_chk (str, 0, __darwin_obsz(str), format, ap)
^
the same thing happens with this other function vsnprintf(buf, sizeof(buf), fmt, ap);
how can i fix this warning?
Thanks
Apparently the solution is to tell Clang that your vsnprintf is called within a function that implements the behaviour of the printf family of functions by, well, calling vsnprintf. You do this with an attribute, as described here:
__attribute__((__format__ (__printf__, 3, 0)))
static void err_doit(int errnoflag, int level, const char *fmt, va_list ap)
{
...
}
This checks whether the format string is a literal on the calling function. As err_doit takes already a va_list, you should specify the format on the functions that call it, too. The second number, which is 0 here, should then be the argument index of the variadic argument, .... See also this discussion.
Function attributes are a non-standard extension of gcc, which is also implemented for Clang. If you want to keep the code compiler independent, you should wrap the attributes in a macro that hides it for compilers that don't know attributes.
(Disclaimer: I couldn't check this with Clang, but it works for gcc. Edit: fixed wrong argument index in example)

Good Verbosity Macro (C99)

I'm looking to write what I would imagine is a fairly common macro. I want to emulate the repeated "-v" options on many POSIX programs by defining a bunch of macros of the following form:
#define V1(str, ...) if(optv >= 1){printf("%s: "str,prog,__VA_ARGS__);}
int main(int argc, char* argv[])
{
// ... stuff ...
int i = 1;
V1("This contains a variable: %d\n",i);
}
// Output:
// ./program: This contains a variable: 1
where optv counts the number of "-v" options found on the command line and prog contains the program name (neither shown). This works well, but the problem is that I have to use a variable. V1("Output") will generate a compiler error. I could always use V1("Output%s","") but there should be a cleaner solution.
The GNU C preprocessor has a special feature that lets you delete the trailing comma when there are no arguments filling the variadic portion by prepending the token-pasting operator ## to __VA_ARGS__:
#define V1(str, ...) if(optv < 1); else printf("%s: "str,prog, ## __VA_ARGS__)
Alternatively, if you wish to remain fully C99 compliant, you could incorporate the the format string parameter into the ellipsis, but in this instance you'll also need to refactor your code since you want to include the extra prog parameter between the format string and the varargs. Something like this might work:
#define V1(...) if(optv < 1); else myprintf(prog, __VA_ARGS__)
int myprintf(const char *prog, const char *fmt, ...)
{
// Print out the program name, then forward the rest onto printf
printf("%s: ", prog);
va_list ap;
va_start(ap, fmt);
int ret = vprintf(fmt, ap);
va_end(ap);
return ret;
}
Then, V1("Output") expands to myprintf(prog, "Output") without using any non-C99 compiler extensions.
EDIT
Also note that I inverted the if condition in the macro, due to some weird issues that can arise if you invoke the macro inside an if statement without braces—see this FAQ for a detailed explanation.
Why don't you use 2 different macros for each verbosity level; one which prints a message and variable, and one which just prints a message?
You should probably write yourself a small support function so that you can do the job cleanly:
extern void vb_print(const char *format, ...);
#define V1(...) do { if (optv >= 1) vb_print(__VA_ARGS__); } while (0)
I assume that both optv and prog are global variables. These would go into a header (you wouldn't write them out in the programs themselves, would you?).
The function can be:
#include <stdio.h>
#include <stdarg.h>
extern const char *prog;
void vb_print(const char *format, ...)
{
va_list args;
va_start(args, format);
printf("%s:", prog);
vprintf(format, args);
va_end(args);
}
There's no rocket science in there. You can tweak the system to your heart's content, allowing a choice of where the information is written, flushing the output, ensuring there's a newline at the end, etc.
Try this:
#define V1X(str, ...) if(optv >= 1) {printf("%s: "str,prog,__VA_ARGS__);} else
#define V1(...) V1X(__VA_ARGS__,0)
I believe that fixes the problem you described, and the else at the end fixed another problem.

Resources