views:

100

answers:

4

I have a void *, call if data which length I know, but is not terminated. I make a call like this snprintf(line, sizeof(line), "%*s", n, (const char*)data) where n is the known length. Almost always this works, but occasionally it results in a segfault.

Whenever the segfault occurs, the back trace says the problem is inside strlen. And when I print data inside gdb, I see something like this

(gdb) p n
$1 = 88
(gdb) p (const char*) data
$2 = 0x1d752fa8
"JASDF" ... "ADS"<Address 0x1d753000 out of bounds>
(gdb) p 0x1d753000-0x1d752fa8
$3 = 88

data is indeed 88 characters, but isn't null terminated, in fact, it seems that it lies right up against a segment. My guess is that snprintf is always called strlen on data and I usually get lucky in that even though data isn't null terminated, there is a \0 before I hit the segment and then occasionally I get unlucky and it is. Is that right? If so, what's the work around?

This is what the stack trace looks like

#0  0x0000003c8927839e in strlen () from /lib64/libc.so.6
#1  0x0000003c89246749 in vfprintf () from /lib64/libc.so.6
#2  0x0000003c8926941a in vsnprintf () from /lib64/libc.so.6
#3  0x0000003c8924d0a3 in snprintf () from /lib64/libc.so.6

EDIT To answer my own question about the work around, strncpy is a more appropriate function to call. I used snprintf by habit.

+5  A: 

Looks like you're right. There's no guarantee that printf won't call strlen, even if it doesn't necessarily have to in a given context. You're lying by providing something that isn't a C string as the parameter for an %s formatting specifier, and so you've broken the contract of printf. Undefined behavior results.

Tyler McHenry
`%s` supports non-0 terminated strings: `%.*s`
Dummy00001
The real problem is his format string is wrong, use `"%.*s"` instead of `"%*s"`
nos
+1  A: 

I think you analysis is correct. If the buffer is not null terminated, then the strlen call will read until it finds a \0. If it runs past the end of the segment (and the next segment is not valid), then it will produce an exception.

The solution is to either null-terminate it or put it in another buffer that you can null terminate.

Mark Wilkins
see my response below. formatting already supports non-0 terminated strings.
Dummy00001
A: 

The "workaround" is to use memcpy():

size_t validlen = n < sizeof line ? n : (sizeof line - 1);
memcpy(line, data, validlen);
line[validlen] = '\0';
caf
"%.*s" is the way to do it - no redundant copying required.
Dummy00001
There is no redundant copying in my answer - there is only a *single* copy, using `memcpy()` instead of `snprintf()`.
caf
+4  A: 

snprintf(line, sizeof(line), "%*s", n, (const char*)data)

data isn't zero terminated? Then you are doing it wrong.

snprintf(line, sizeof(line), "%.*s", n, (const char*)data);

Notice the dot.

In case of strings, the first * in the *.* if for the desired output (on screen) length - input length is the second *. man printf for more.

And obviously in the case of %*s formatting might call strlen(), since it needs to know whether it needs to pad the output and how to pad it.

Dummy00001
You're right. The man page is pretty explicit in discussing non null terminating strings and the precision modifier.
pythonic metaphor