When writing a project, I ran into a strange issue.
This is the minimal code I managed to write to recreate the issue. I am intentionally storing an actual string in the place of something else, with enough space allocated.
// #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stddef.h> // For offsetof()
typedef struct _pack{
// The type of `c` doesn't matter as long as it's inside of a struct.
int64_t c;
} pack;
int main(){
pack *p;
char str[9] = "aaaaaaaa"; // Input
size_t len = offsetof(pack, c) + (strlen(str) + 1);
p = malloc(len);
// Version 1: crash
strcpy((char*)&(p->c), str);
// Version 2: crash
strncpy((char*)&(p->c), str, strlen(str)+1);
// Version 3: works!
memcpy((char*)&(p->c), str, strlen(str)+1);
// puts((char*)&(p->c));
free(p);
return 0;
}
The above code is confusing me:
- With
gcc/clang -O0
, bothstrcpy()
andmemcpy()
works on Linux/WSL, and theputs()
below gives whatever I entered. - With
clang -O0
on OSX, the code crashes withstrcpy()
. - With
gcc/clang -O2
or-O3
on Ubuntu/Fedora/WSL, the code crashes (!!) atstrcpy()
, whilememcpy()
works well. - With
gcc.exe
on Windows, the code works well whatever the optimization level is.
Also I found some other traits of the code:
(It looks like) the minimum input to reproduce the crash is 9 bytes (including zero terminator), or
1+sizeof(p->c)
. With that length (or longer) a crash is guaranteed (Dear me ...).Even if I allocate extra space (up to 1MB) in
malloc()
, it doesn't help. The above behaviors don't change at all.strncpy()
behaves exactly the same, even with the correct length supplied to its 3rd argument.The pointer does not seem to matter. If structure member
char *c
is changed intolong long c
(orint64_t
), the behavior remains the same. (Update: changed already).The crash message doesn't look regular. A lot of extra info is given along.
I tried all these compilers and they made no difference:
- GCC 5.4.0 (Ubuntu/Fedora/OS X/WSL, all are 64-bit)
- GCC 6.3.0 (Ubuntu only)
- GCC 7.2.0 (Android, norepro???) (This is the GCC from C4droid)
- Clang 5.0.0 (Ubuntu/OS X)
- MinGW GCC 6.3.0 (Windows 7/10, both x64)
Additionally, this custom string copy function, which looks exactly like the standard one, works well with any compiler configuration mentioned above:
char* my_strcpy(char *d, const char* s){
char *r = d;
while (*s){
*(d++) = *(s++);
}
*d = '\0';
return r;
}
Questions:
- Why does
strcpy()
fail? How can it? - Why does it fail only if optimization is on?
- Why doesn't
memcpy()
fail regardless of-O
level??
*If you want to discuss about struct member access violation, pleast head over here.
Part of objdump -d
's output of a crashing executable (on WSL):
P.S. Initially I want to write a structure, the last item of which is a pointer to a dynamically allocated space (for a string). When I write the struct to file, I can't write the pointer. I must write the actual string. So I came up with this solution: force store a string in the place of a pointer.
Also please don't complain about gets()
. I don't use it in my project, but the example code above only.
abort()
from some checker code, or was it an access violation (e.g. SEH 0xC000.0005 on Windows), etc.: "Crash" is not a technical term on this level :-) – KopeckwriteFile()
(Line 56-75). I think the only way is to use avoid*
buffer. – Testify