4

I have a void *, call it data, whose length I know, but is not null 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.

pythonic metaphor
  • 10,296
  • 18
  • 68
  • 110

4 Answers4

10

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
  • 16,630
  • 5
  • 41
  • 63
6

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
  • 74,820
  • 18
  • 121
  • 166
1

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
  • 40,729
  • 5
  • 57
  • 110
0

The "workaround" is to use memcpy():

size_t validlen = n < sizeof line ? n : (sizeof line - 1);
memcpy(line, data, validlen);
line[validlen] = '\0';
caf
  • 233,326
  • 40
  • 323
  • 462