Valgrind reports uninitialized value inside standard library (vfprintf.c)
I have a function that does vsnsprintf
in a temporary buffer in an object created on the stack.
In the constructor of the object, I initialize the first character of the buffer to zero.
Valgrind complains about an uninitialized value created on the stack invfprintf.c
Complete working example below followed by valgrind output
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
struct tmp_buf
{
tmp_buf() { *b = 0; }
mutable char b[1024];
};
char const* va_stack_str(const char* format, va_list ap, const tmp_buf& b = tmp_buf())
{
vsnprintf(b.b, sizeof(b.b), format, ap);
return b.b;
}
char const* stack_str(const char* format, ...)
{
va_list ap;
va_start(ap, format);
const char* str = va_stack_str(format, ap);
va_end(ap);
return str;
}
int main()
{
printf("%s", stack_str("hello %s", "world"));
return 0;
}
The app works as expected, but running it through valgrind complains about uninitialized values
My valgrind team valgrind --leak-check=full --track-origins=yes --quiet
Valgrind output:
==30513== Conditional jump or move depends on uninitialised value(s)
==30513== at 0x4E828F3: vfprintf (vfprintf.c:1661)
==30513== by 0x4E8B388: printf (printf.c:33)
==30513== by 0x400A73: main (main.cpp:28)
==30513== Uninitialised value was created by a stack allocation
==30513== at 0x4E80BF6: vfprintf (vfprintf.c:235)
==30513==
==30513== Syscall param write(buf) points to uninitialised byte(s)
==30513== at 0x4F233B0: __write_nocancel (syscall-template.S:81)
==30513== by 0x4EB0A82: _IO_file_write@@GLIBC_2.2.5 (fileops.c:1261)
==30513== by 0x4EB1F5B: _IO_do_write@@GLIBC_2.2.5 (fileops.c:538)
==30513== by 0x4EB3ADD: _IO_flush_all_lockp (genops.c:848)
==30513== by 0x4EB3C39: _IO_cleanup (genops.c:1013)
==30513== by 0x4E730FA: __run_exit_handlers (exit.c:95)
==30513== by 0x4E73194: exit (exit.c:104)
==30513== by 0x4E58ECB: (below main) (libc-start.c:321)
==30513== Address 0x4025000 is not stack'd, malloc'd or (recently) free'd
==30513== Uninitialised value was created by a stack allocation
==30513== at 0x4E80BF6: vfprintf (vfprintf.c:235)
Changing the constructor tmp_buf
to the memset
entire buffer doesn't change the valgrind output
tmp_buf() { memset(b, 0, sizeof(b)); }
source to share
While I'm not very familiar with Valgrind, I see a clear problem in your code and can offer my best guess as to why Valgrind is complaining the way it is.
First, the problem:
The function va_stack_str
returns a pointer to an b
argument member b
that is of type const tmp_buf&
. Since this function does not control the lifetime of the object that this argument refers to, it returns a pointer whose validity can only be guaranteed until the end of the complete expression in which it is called. If the argument is b
initialized with a temporary one (this is how it is used stack_str
), then the end of the full expression exactly coincides with the duration of the correct pointer.
The function stack_str
continues to store the pointer returned va_stack_str
in a local variable str
and then returns it. By this time, the full expression in which it was called has va_stack_str
ended, so the pointer is hanging - it points to a buffer that was allocated on the stack but has since been freed.
The code works, probably because that part of the stack where the buffer existed was not overwritten by the time it was read and therefore still contains the contents of what used to be a buffer.
Why I think valgrind is releasing "uninitialized values":
vfprintf
of course allocates some stack space for local variables, some of which are probably allocated in the same stack folder, which used to be the buffer we asked to print. When it vfprintf
uses this buffer (the one we passed it in), Valgrind sees this memory not as our original buffer (which was freed), but as the address of the local variables allocated vfprintf
.
My guess is that one of these local variables is uninitialized at the point at which it is vfprintf
looking at the buffer we passed to it looking for a terminating NULL character. In this case, it parses memory pointing to its own uninitialized local variable, which usually won't happen because it vfprintf
initializes it later, but before it intends to use it. vfprintf
expects you to pass in a pointer to your allocated buffer, not the one that will eventually point to its own local variables!
source to share