WEBlog -- Wouter's Eclectic Blog

Wed, 16 Dec 2009

Grrr

struct {
	char str[4];
	char sc1;
	char str2[3];
	char sc2;
} foo __attribute__((packed));

snprintf(foo.str, 5, "%04X", data);
foo.sc1 = ';';
snprintf(foo.str2, 4, "%03X", otherdata);
foo.sc2 = ';';

Yes, I know that both snprintf() calls in the above snippet will overflow their immediate buffer. Yet this code is safe; the network protocol for which this code is written does not actually need nor expect NUL bytes; instead, it wants semicolons. I could of course use a "char foo[9]" rather than a struct as above, but I find this to be slightly more convenient than to count offsets.

However, this code does not work with glibc, because the buffer overflow detection kicks in.

Solution:

char buf[5];

snprintf(buf, 5, "%04X", data);
memcpy(foo.str, buf, 4);

In other words: add a stupid and useless memcpy, because someone thinks they're smarter than me. Stupid morons.

Reply...
S:No struct and no counting of offsets (at least not manually)
A:jan2600
E:
D:2009-12-16 15:16:46.528944

char foo[9];
char *p = foo;

p += snprintf(p, 5, "%04X", data);
*p++ = ';';
p += snprintf(p, 4, "%03X", otherdata);
*p++ = ';';
S:Alignment?
A:Philip Paeps
E:philip@paeps.cx
D:2009-12-17 10:22:55.801663
A 9-byte struct? Seriously? :-(
Why packed? Why not let the compiler pad for you?
S:Re: Alignment?
A:Wouter Verhelst
E:
D:2009-12-20 15:27:26.577545
Because this is sent over the wire. It *needs* to be packed, otherwise we implement the protocol incorrectly.

Also, this is an example. The real, actual struct is similar, but different.
S:Don't reply on memory layout
A:someone
E:
D:2009-12-21 02:21:51.711611
Do not rely on memory layout when sending data over the wire. Create a byte array with the right data in it and send that out, anything else is stupid.
S:Union?
A:Koterpillar
E:
D:2009-12-17 20:21:30.58017
struct {
union {
struct {
char str[4];
char sc1;
}
char for_sprintf[5];
}
S:
A:Zack
E:
D:2009-12-17 20:40:46.427865
Why not just

char foo[10];
snprintf(foo, 10, "%04X;%03X;", data, otherdata);
S:
A:Jim
E:
D:2009-12-17 21:04:52.310372
Instead of the stupid and useless code, just take the minor inconvenience that you mentioned where you just size the array properly:

struct {
char str[5];
char str2[4];
} foo __attribute__((packed));

If you still need access to sc1 and sc2 independently, use a union or whatever.
S:union to the rescue!
A:Jeroen Massar
E:jeroen@unfix.org
D:2009-12-17 21:23:04.833952
Try it with a Union, something like:

union
{
struct {
char str[5];
char str[4];
} str;
struct {
char str[4];
char sc1;
char str2[3];
char sc2;
} chars;
} foo __attribute__((packed));

snprintf(foo.str.str, 5, "%04X", data);
foo.chars.sc1 = ';';
snprintf(foo.str.str2, 4, "%03X", otherdata);
foo.chars.sc2 = ';';

yes, not ideal, but that allows you to keep on using glibc's excellent overflow checking help.
S:works fine for me...
A:Joost Yervante Damad
E:joost@damad.be
D:2009-12-18 06:47:38.205252
#include

void main() {

struct {
char str[4];
char sc1;
char str2[3];
char sc2;
} __attribute__((__packed__)) foo;

int data, otherdata;

data = 5;
otherdata = 42;

snprintf(foo.str, 5, "%04X", data);
foo.sc1 = ';';
snprintf(foo.str2, 4, "%03X", otherdata);
foo.sc2 = ';';

}
S:Why so complicated?
A:Capt. Obvious
E:
D:2009-12-18 09:00:48.692953
char foo[9];
snprintf(foo, sizeof(foo), "%04X;%03X", data, otherdata);
foo[8] = ';';
S:slice between the bytes, luke!
A:Barak A. Pearlmutter
E:bap@debian.org
D:2009-12-18 17:29:31.987593
Why not make a union, with the struct being one element, and a char[5] or whatever being the other. Then you can snprintf into the char[5] side of the union, and it will show up in the struct, just like you want. In other words, actually tell the compiler what you mean: sometimes you want to treat this hunk of memory as one big char[], and sometimes as a struct. (Or you could just cast the pointer passed to snprintf to char*, if you don't want to fiddle w/ the type definitions.)
S:How is this bug even possible?
A:Ken Bloom
E:
D:2009-12-21 01:31:25.800241
I tried to reproduce this (with the following code) I found that the __attribute__((packed)) was getting ignored because it's in the wrong order. It needs to come before foo, not after.


#include
#include

struct {
char str[4];
char sc1;
char str2[3];
char sc2;
} foo __attribute__((packed));

int main(int argc, char** argv){
snprintf(foo.str, 5, "%04X", 1000000);
foo.sc1 = ';';
snprintf(foo.str2, 4, "%03X", 100000);
foo.sc2 = ';';
return 0;
}


test.c:9: warning: ‘packed’ attribute ignored
S:Re: How is this bug even possible?
A:Wouter Verhelst
E:
D:2009-12-23 11:57:43.445191
I should perhaps clarify something.

The code above is a reworked example of the actual code that I'm using. This is because there's no good reason to dump a whole privately-used network protocol on the public internetz, and the format of the struct wasn't essential to the discussion at hand (why else do you think I'd be using "foo" as a variable name?)

The actual code did not have the declaration and use of the struct in the same line of code, so apparently I messed up there; but it was a correct and working packed struct that hit this problem.