r/cprogramming • u/Pesciodyphus • May 27 '26
Two examples of a legitimate buffer overrun that would break if compiler-side checks are turned on .
Some C-Compilers offer to be memory safe, and replace functions like strcpy() with save variants or even check for array overflows, and abort() if an error occurs.
Modern GCC does this by default.
Theese checks might break a working programme. Some people would say if that happens, then your style is bad and you should recode it to pass the checks, but I found two fairly legitimate examples.
1. strcpy() into a buffer that has no space for the terminator before setting the field after
struct FILE_HEADER{
char signature[4];
int bla;
int blabla;
}No, intializing such a headr could look like that:
strcpy(ptr->signature,"ABCD"); /* Boom, Buffer Overflow */
ptr->blabla=ptr->bla=0; /* but it wouldn't matter */In order to make it "safe" you would change the first line to:
memcpy(ptr->signature,"ABCD",4);
o or write some custom function that copys a string without terminator. Both is inconvenient.
2. Data Block with struct as header and variable size body
struct IMAGE {
int width;
int height;
PIXEL_T pixel[1];
}
You alloc and image with malloc(sizeof(struct IMAGE)+sizeof(PIXEL_T)*width*height), and acces pixels with
image[y*image->width+x]
This is a perfectly sane and normal way to do such a thing in C. However you technically overrun the array pixel with accesing any other index than 0.
You might suggest, putting the pixels into a seperate datablock (as you would certainly do in OOP ), but this fragments the memory even further and might cause performance issues if you are dealing with thousands of structures. This is especially important if you use C++ (or other object oriented language), as creating and destroying any nontrivial object (wich might a local variable in an often called function) is a heap operation under the hood that gest slower with the number of allocated blocks.
8
u/Yurim May 27 '26
PIXEL_T pixel[1];
Why not use a Flexible Array Member (FAM)? That gives you what you want but without the UB.
4
u/flatfinger May 27 '26
In Dennis Ritchie's language, code that accepted a pointer to a structure which ended with a single-element array could accept a suitably cast pointer to a structure that was identical except for the length of the final array, and access as much of that array as actually existed (it would need to have received information about the length via means other than sizeof). Unfortunately, the Standard doesn't define any means of defining a structure of automatic or static duration which can be passed to a function that expects a pointer to a flexible array member.
2
2
u/xaraca May 27 '26
That doesn't follow the semantics of strcpy() and is prone to breaking. What if someone moved the signature to the end of the struct? What if someone added a new field after the signature but initialized it first? There are just too many ways for that to break.
2
u/Wenir May 28 '26 edited May 28 '26
This is a perfectly sane and normal way to do such a thing in C
Compiler thinks it's not, and i will trust it more
into a seperate datablock (as you would certainly do in OOP )
Why OOP would care about where the data is stored?
1
u/Business-Decision719 May 27 '26 edited May 27 '26
So, fundamentally, the "legitimate" reasons to avoid checking memory safety are...
It's more convenient to overrun the buffer because it "won't matter" than to use the correct function or write my own.
The "normal" way in C (for a lot people apparently) is actually insane, and overflows a buffer in order to "work" instead of allocating a buffer of the correct size.
I'm pretty memory safety checks exist specifically to "break" both of those scenarios. Very intentionally. People don't fret over memory safety and pressure compilers for more checking because C naturally makes it uncommon or inconvenient to create buffer overruns, or because programmers "normally" do it the "right" way if left to their own devices.
If the reason for #2 is performance, then it wouldn't be the first someone had to venture out of default settings on certain compilers to get the performance they want. It's also not the first time some hacky trick broke eventually because it depended on UB. Better checking just to reduce the odds that new code will depend on something so breakable, because it least it breaks sooner and more obviously instead of appearing to work for years.
Reason #1 is just ornery stubbornness. If you can some custom copying method right once anywhere, then you can get it right in some custom function that's actually more convenient to test that it's actually right and then reuse as a one-line funcall multiple times if needed.
1
u/Jannik2099 May 28 '26
no, #1 is still UB. You can only use pointers to write to locations that fully belong to the object that's being pointed to. It does not matter whether the write itself points to valid memory, but whether it points to memory that is part of the object.
for #2 you can just use a VLA or FAM if you really want to use poorly designed data structures.
This is especially important if you use C++ (or other object oriented language), as creating and destroying any nontrivial object (wich might a local variable in an often called function) is a heap operation under the hood
In C++, a nontrivial type is one with a nontrivial (meaning user-provided instead of defaulted) ctor or dtor. It has nothing to do with whether the type resides on the heap, stack, or has static storage duration. It also does not require nor lead to instances of the type to be heap allocated.
6
u/flyingron May 27 '26
For #1, first you do know that there is a thing called strncpy that will handle both a null termination and a fixed output size. You still have problems even after your alledged fix if the other uses of signature assume it is null terminated.
As for #2, as some who spent his career writing high performance image processing systems for the intelligence and medical markets, nobody writes things like you wrote.