Strange behaviour of free() after memory allocation violation - c

Not so long ago I was hunting a bug in some big number library I was writing, it costed me quite a while. The problem was that I violated the memory bounds of some structure member, but instead of a segmentation fault or just a plain crash, it did something unexpected (at least I did not expect it). Let me introduce a example:
segmentation_fault.c
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <signal.h>
#define N 100 /* arbitrary large number */
typedef unsigned char byte;
void exitError(char *);
void segmentationFaultSignalHandler(int);
sig_atomic_t segmentationFaultFlag = 0;
int main(void)
{
int i, memorySize = 0;
byte *memory;
if (setvbuf(stdout, NULL, _IONBF, 0))
exitError("setvbuf() failed");
if (signal(SIGSEGV, segmentationFaultSignalHandler) == SIG_ERR)
exitError("signal() failed");
for (i = 0; i < N; ++i)
{
printf("Before malloc()\n");
if ((memory = malloc(++memorySize * sizeof(byte))) == NULL)
exitError("allocation failed");
printf("After malloc()\n");
printf("Before segmentation fault\n");
memory[memorySize] = 0x0D; /* segmentation fault */
if (segmentationFaultFlag)
exitError("detected segmentation fault");
printf("After segmentation fault\n");
printf("Before free()\n");
free(memory);
printf("After free()\n");
}
return 0;
}
void segmentationFaultSignalHandler(int signal)
{
segmentationFaultFlag = 1;
}
void exitError(char *errorMessage)
{
printf("ERROR: %s, errno=%d.\n", errorMessage, errno);
exit(1);
}
As we can see the line memory[memorySize] = 0x0D; is clearly violating the memory bounds given by malloc(), but it does not crash or raise a signal (I know according to ISO C99 / ISO C11 the signal handling is implementation defined and does not have to raise at all when violating memory bounds). It moves on printing the lines After segmentation fault, Before free() and After free(), but after a couple of iterations later it crashes, always at free() (printing After segmentation fault and Before free(), but not After free()). I was wondering what causes this behavior and what is the best way to detect memory access violations (I'm ashamed, but I always kinda used printfs to determine where a program crashed, but sure there must be better tools to do that) as it is very hard to detect (most often it does not crash at the code of violation, but, as in the example, later in the code, when trying to do something with this memory again). Surely I should be able to free this memory as I allocated it right and did not modify the pointer.

You only can detect violations in an faked enviroment.
In the case, you violate the Memory you gained from the system, you can't belive anything anymore. As all what happens now is undefined behaving and you CAN'T expect what will happen, as there isn't any rule.
So if you want to check a program for memory leacks or some read/write violation. you have to write a program which gets a memory area which belongs to it and you give a part of the area to the "to be checked" program. You have to inspect the process and keep track on where it is writing and reading into our memory and you have to use the other part of the memory to check for was it allowed to read write there or not (i.e. In your faked enviroment by setting some FLAGS and check they got changed or not).
Because if the program is leaving the area you own. you can't be sure about you will detect this behaving or not.
So You have to make your own memory managemend to check such behaving.

When reading or writing in memory you don't own you get undefined behavior.
This doesn't always result in segmentation fault. In practise it is much more likely that the code will corrupt some other data and your program will crash at some other place which makes it hard to debug.
In this example you wrote to an invalid heap address. It's likely that you will corrupt some internal heap structures which makes it likely that the program will crash on any following malloc or free calls.
There are tools that check your heap usage and can tell you if you write out of your bounds. I like and would recommend valgrind for linux and gflags for windows.

When malloc is returning a pointer to a chunk of memory, it uses some additional information about this pointer (like the size of allocated space). This information is usually stored on addresses right before the returned pointer. Also very often, malloc can return a pointer to bigger chunk than you asked for. In consequence addresses before and after the pointer are valid. You can write there without provoking segmentation fault or other system error. However, if you write there you may overwrite the data malloc needs for correct freeing of the memory. The behavior of subsequent calls of malloc and free is undefined since this point.

Related

Under what circumstances does a segmentation fault occur? [duplicate]

What is a segmentation fault? Is it different in C and C++? How are segmentation faults and dangling pointers related?
Segmentation fault is a specific kind of error caused by accessing memory that “does not belong to you.” It’s a helper mechanism that keeps you from corrupting the memory and introducing hard-to-debug memory bugs. Whenever you get a segfault you know you are doing something wrong with memory – accessing a variable that has already been freed, writing to a read-only portion of the memory, etc. Segmentation fault is essentially the same in most languages that let you mess with memory management, there is no principal difference between segfaults in C and C++.
There are many ways to get a segfault, at least in the lower-level languages such as C(++). A common way to get a segfault is to dereference a null pointer:
int *p = NULL;
*p = 1;
Another segfault happens when you try to write to a portion of memory that was marked as read-only:
char *str = "Foo"; // Compiler marks the constant string as read-only
*str = 'b'; // Which means this is illegal and results in a segfault
Dangling pointer points to a thing that does not exist anymore, like here:
char *p = NULL;
{
char c;
p = &c;
}
// Now p is dangling
The pointer p dangles because it points to the character variable c that ceased to exist after the block ended. And when you try to dereference dangling pointer (like *p='A'), you would probably get a segfault.
It would be worth noting that segmentation fault isn't caused by directly accessing another process memory (this is what I'm hearing sometimes), as it is simply not possible. With virtual memory every process has its own virtual address space and there is no way to access another one using any value of pointer. Exception to this can be shared libraries which are same physical address space mapped to (possibly) different virtual addresses and kernel memory which is even mapped in the same way in every process (to avoid TLB flushing on syscall, I think). And things like shmat ;) - these are what I count as 'indirect' access. One can, however, check that they are usually located long way from process code and we are usually able to access them (this is why they are there, nevertheless accessing them in a improper way will produce segmentation fault).
Still, segmentation fault can occur in case of accessing our own (process) memory in improper way (for instance trying to write to non-writable space). But the most common reason for it is the access to the part of the virtual address space that is not mapped to physical one at all.
And all of this with respect to virtual memory systems.
A segmentation fault is caused by a request for a page that the process does not have listed in its descriptor table, or an invalid request for a page that it does have listed (e.g. a write request on a read-only page).
A dangling pointer is a pointer that may or may not point to a valid page, but does point to an "unexpected" segment of memory.
To be honest, as other posters have mentioned, Wikipedia has a very good article on this so have a look there. This type of error is very common and often called other things such as Access Violation or General Protection Fault.
They are no different in C, C++ or any other language that allows pointers. These kinds of errors are usually caused by pointers that are
Used before being properly initialised
Used after the memory they point to has been realloced or deleted.
Used in an indexed array where the index is outside of the array bounds. This is generally only when you're doing pointer math on traditional arrays or c-strings, not STL / Boost based collections (in C++.)
According to Wikipedia:
A segmentation fault occurs when a
program attempts to access a memory
location that it is not allowed to
access, or attempts to access a memory
location in a way that is not allowed
(for example, attempting to write to a
read-only location, or to overwrite
part of the operating system).
Segmentation fault is also caused by hardware failures, in this case the RAM memories. This is the less common cause, but if you don't find an error in your code, maybe a memtest could help you.
The solution in this case, change the RAM.
edit:
Here there is a reference: Segmentation fault by hardware
Wikipedia's Segmentation_fault page has a very nice description about it, just pointing out the causes and reasons. Have a look into the wiki for a detailed description.
In computing, a segmentation fault (often shortened to segfault) or access violation is a fault raised by hardware with memory protection, notifying an operating system (OS) about a memory access violation.
The following are some typical causes of a segmentation fault:
Dereferencing NULL pointers – this is special-cased by memory management hardware
Attempting to access a nonexistent memory address (outside process's address space)
Attempting to access memory the program does not have rights to (such as kernel structures in process context)
Attempting to write read-only memory (such as code segment)
These in turn are often caused by programming errors that result in invalid memory access:
Dereferencing or assigning to an uninitialized pointer (wild pointer, which points to a random memory address)
Dereferencing or assigning to a freed pointer (dangling pointer, which points to memory that has been freed/deallocated/deleted)
A buffer overflow.
A stack overflow.
Attempting to execute a program that does not compile correctly. (Some compilers will output an executable file despite the presence of compile-time errors.)
Segmentation fault occurs when a process (running instance of a program) is trying to access read-only memory address or memory range which is being used by other process or access the non-existent (invalid) memory address.
Dangling Reference (pointer) problem means that trying to access an object or variable whose contents have already been deleted from memory, e.g:
int *arr = new int[20];
delete arr;
cout<<arr[1]; //dangling problem occurs here
In simple words: segmentation fault is the operating system sending a signal to the program
saying that it has detected an illegal memory access and is prematurely terminating the program to prevent
memory from being corrupted.
There are several good explanations of "Segmentation fault" in the answers, but since with segmentation fault often there's a dump of the memory content, I wanted to share where the relationship between the "core dumped" part in Segmentation fault (core dumped) and memory comes from:
From about 1955 to 1975 - before semiconductor memory - the dominant technology in computer memory used tiny magnetic doughnuts strung on copper wires. The doughnuts were known as "ferrite cores" and main memory thus known as "core memory" or "core".
Taken from here.
"Segmentation fault" means that you tried to access memory that you do not have access to.
The first problem is with your arguments of main. The main function should be int main(int argc, char *argv[]), and you should check that argc is at least 2 before accessing argv[1].
Also, since you're passing in a float to printf (which, by the way, gets converted to a double when passing to printf), you should use the %f format specifier. The %s format specifier is for strings ('\0'-terminated character arrays).
Simple meaning of Segmentation fault is that you are trying to access some memory which doesn't belong to you. Segmentation fault occurs when we attempt to read and/or write tasks in a read only memory location or try to freed memory. In other words, we can explain this as some sort of memory corruption.
Below I mention common mistakes done by programmers that lead to Segmentation fault.
Use scanf() in wrong way(forgot to put &).
int num;
scanf("%d", num);// must use &num instead of num
Use pointers in wrong way.
int *num;
printf("%d",*num); //*num should be correct as num only
//Unless You can use *num but you have to point this pointer to valid memory address before accessing it.
Modifying a string literal(pointer try to write or modify a read only memory.)
char *str;
//Stored in read only part of data segment
str = "GfG";
//Problem: trying to modify read only memory
*(str+1) = 'n';
Try to reach through an address which is already freed.
// allocating memory to num
int* num = malloc(8);
*num = 100;
// de-allocated the space allocated to num
free(num);
// num is already freed there for it cause segmentation fault
*num = 110;
Stack Overflow -: Running out of memory on the stack
Accessing an array out of bounds'
Use wrong format specifiers when using printf() and scanf()'
Consider the following snippets of Code,
SNIPPET 1
int *number = NULL;
*number = 1;
SNIPPET 2
int *number = malloc(sizeof(int));
*number = 1;
I'd assume you know the meaning of the functions: malloc() and sizeof() if you are asking this question.
Now that that is settled,
SNIPPET 1 would throw a Segmentation Fault Error.
while SNIPPET 2 would not.
Here's why.
The first line of snippet one is creating a variable(*number) to store the address of some other variable but in this case it is initialized to NULL.
on the other hand,
The second line of snippet two is creating the same variable(*number) to store the address of some other and in this case it is given a memory address(because malloc() is a function in C/C++ that returns a memory address of the computer)
The point is you cannot put water inside a bowl that has not been bought OR a bowl that has been bought but has not been authorized for use by you.
When you try to do that, the computer is alerted and it throws a SegFault error.
You should only face this errors with languages that are close to low-level like C/C++. There is an abstraction in other High Level Languages that ensure you do not make this error.
It is also paramount to understand that Segmentation Fault is not language-specific.
There are enough definitions of segmentation fault, I would like to quote few examples which I came across while programming, which might seem like silly mistakes, but will waste a lot of time.
You can get a segmentation fault in below case while argument type mismatch in printf:
#include <stdio.h>
int main(){
int a = 5;
printf("%s",a);
return 0;
}
output : Segmentation Fault (SIGSEGV)
When you forgot to allocate memory to a pointer, but try to use it.
#include <stdio.h>
typedef struct{
int a;
} myStruct;
int main(){
myStruct *s;
/* few lines of code */
s->a = 5;
return 0;
}
output : Segmentation Fault (SIGSEGV)
In computing, a segmentation fault or access violation is a fault, or failure condition, raised by hardware with memory protection,
notifying an operating system the software has attempted to access a
restricted area of memory. -WIKIPEDIA
You might be accessing the computer memory with the wrong data type. Your case might be like the code below:
#include <stdio.h>
int main(int argc, char *argv[]) {
char A = 'asd';
puts(A);
return 0;
}
'asd' -> is a character chain rather than a single character char data type. So, storing it as a char causes the segmentation fault. Stocking some data at the wrong position.
Storing this string or character chain as a single char is trying to fit a square peg in a round hole.
Terminated due to signal: SEGMENTATION FAULT (11)
Segm. Fault is the same as trying to breath in under water, your lungs were not made for that. Reserving memory for an integer and then trying to operate it as another data type won't work at all.
Segmentation fault occurs when a process (running instance of a program) is trying to access a read-only memory address or memory range which is being used by another process or access the non-existent memory address.
seg fault,when type gets mismatched
A segmentation fault or access violation occurs when a program attempts to access a memory location that is not exist, or attempts to access a memory location in a way that is not allowed.
/* "Array out of bounds" error
valid indices for array foo
are 0, 1, ... 999 */
int foo[1000];
for (int i = 0; i <= 1000 ; i++)
foo[i] = i;
Here i[1000] not exist, so segfault occurs.
Causes of segmentation fault:
it arise primarily due to errors in use of pointers for virtual memory addressing, particularly illegal access.
De-referencing NULL pointers – this is special-cased by memory management hardware.
Attempting to access a nonexistent memory address (outside process’s address space).
Attempting to access memory the program does not have rights to (such as kernel structures in process context).
Attempting to write read-only memory (such as code segment).

C - Accessing data AFTER memory has been free()ed?

I'm reading a lot about malloc() and free() in Standard C. As I understand it, you malloc() for some memory exactly once and then you free() that same memory exactly once. It may be bad practice, but I understand that after you malloc() memory, you can define multiple pointers to it. And once you free() any of those pointers, the allocated memory is de-allocated?
Consider this toy example:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
char* p = (char*)malloc(10 * sizeof(char)); // allocate memory
int* q = (int*)p; // pointer to the same block of memory
*p = 'A'; // Input some data
printf("TEST:: %c %d\n", *p, *q); // Everything's ok so far...
free(p); // free() my allocated memory?
sleep(10); // wait
printf("%c\n", *q); // q now points to de-allocated memory
// shouldn't this segfault?
free(q); // *** SEGFAULTS HERE ***
return 0;
}
Output is:
[Linux]$ ./a.out
TEST:: A 65
*** Error in `./a.out': double free or corruption (fasttop): 0x0000000001ac4010 ***
======= Backtrace: =========
...lots of backtrack info...
So I assume that when I free() the first pointer, the memory is considered free()ed, but the data value(s) I wrote in this block of memory are still "there", which is why I can access them via the second pointer?
(I'm not proposing that this is a good idea, I'm trying to understand the logic of the system.)
When you malloc memory, you're given a pointer to some space, and when you free it, you're giving it back to the system. Often, you can still access this memory, but using memory after you have freed it is VERY BAD.
The exact behavior is undefined, but on most systems you can either continue to access the memory, or you get a segfault.
One interesting experiment you can try is to try and malloc more memory after you free'd that pointer. On most systems I've tried, you get the same block back (which is a problem, if you were relying on the data being there in the freed block). Your program would end up using both pointers, but since they point to the same physical data, you'll be overwriting your own data!
The reason for this is that when you malloc data (depending on the malloc implementation of course), malloc first requests a block of data from the operating system (typically much larger than the malloc request), and malloc will give you a segment of that memory. You'll be able to access any part of the memory malloc originally got from the operating system though, since to the operating system, it's all memory your program is internally using. When you make a free, you're telling the malloc system that the memory is free, and can be given back to the program later on.
Writing outside of the malloc area is very dangerous because
It can segfault, depending on your c implementation
You can overwrite metadata structures malloc is relying on, which causes VERY BAD PROBLEMS when you free/malloc more data later on
If you are interested in learning more, I would recommend running your program through valgrind, a leak detector, to get a better picture of what's freed/not freed.
PS: On systems without an OS, you most likely wont get a segfault at all, and you'll be able to wite anywhere willy nilly. The OS is responsible for triggering a segfault (when you write/read to memory you don't have access to, like kernel or protected memory)
If you are interested in learning more, you should try to write your own malloc, and/or read/learn about the memory management operating systems do.
The crash in your code is due to double free. Appendix J.2 of C11 says that behaviour is undefined for example when:
The pointer argument to the free or realloc function does not match a pointer earlier returned by a memory management function, or the space has been deallocated by a call to free or realloc (7.22.3.3, 7.22.3.5).
However it is possible to write code that will crash on Linux just by reading a value from memory that was just freed.
In glibc + Linux there are two different mechanisms of memory allocations. One uses the brk/sbrk to resize the data segment, and the other uses the mmap system call to ask the operating system to give large chunks of memory. The former is used for small allocations, like your 10 characters above, and mmap for large chunks. So you might get a crash by even accessing the memory just after free:
#include <stdio.h>
#include <stdlib.h>
int main(){
char* p = malloc(1024 * 1024);
printf("%d\n", *p);
free(p);
printf("%d\n", *p);
}
And finally, the C11 standard says that the behaviour is undefined even when
The value of a pointer that refers to space deallocated by a call to the free or realloc function is used (7.22.3).
This means that after not only that dereferencing the pointer (*p) has undefined behaviour, but also that it is not safe to use the pointer in any other way, even doing p == NULL has UB. This follows from C11 6.2.4p2 that says:
The value of a pointer becomes indeterminate when the object it points to (or just past) reaches the end of its lifetime.

How can I use this unallocated memory?

How come I don't get any error using the following program?
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]){
char *pStr = (char*) malloc(25);
free(pStr);
strcpy(pStr, "foo");
printf("%s\n", pStr);
return 0;
}
Shouldn't free(pStr) stop me from writing to that address? Don't I have to allocate the space again before I can use it?
free doesn't prevent you from doing anything as long as the thing is syntactically correct. So you are still more than welcome to copy to a char* just as you could do if you had never allocated the memory in the first place. But this is undefined behavior, and is liable (read: likely) to cause your program to crash or do something wrong without warning.
For example, if your compiler does some optimizations, it might reorder some instructions in order to save time. Since you have freed the memory, the compiler might think that it is safe to allocate memory in that location for some other data that will be created later. If the optimizer moves that allocation and write to before your strcpy here, you could overwrite the object that is stored there.
Consider this code:
int main(int arc, char *argv[]){
char* pStr = (char*) malloc(25);
free(pStr);
strcpy(pStr, "foo");
printf("%s\n", pStr);
int* a = (int*) malloc(sizeof(int));
*a = 36;
printf("%d\n", *a);
}
Since you wrote to unallocated memory, you can't be sure what either of the printfs will display. The 36 might possibly have overwritten some of the "foo" string. The "foo" string might have overwritten the value 36 that a points to. Or maybe neither of them affects the other and your program runs seemingly just fine until you change the name of some variable and recompile and for some reason everything is messed up even though you didn't change anything.
Moral of the story: you are correct that you should not write to freed memory; however, you are incorrect that you cannot write to freed memory. C does not check this condition and assumes that you are trying to do something fancy. If you know exactly how your compiler optimizes and where it allocates memory when malloc is called, you might know that writing to unallocated memory is safe in a particular case, and C does not prevent you from doing that. But for the 99.999% of the time that you don't know everything, just don't do it.
It is an undefined behavior. And what really happens is implementation specific.
In practice, free very often mark the freed memory block as reusable for future malloc-s.
See also this ...
As other answers pointed it's undefined behavior, thus compiler is not obligated for any diagnostics. However if you have modern version of gcc (>= 4.8) or clang, then AddressSanitizer might be helpful in case of this "use-after-free" bug:
$ gcc -ansi -pedantic -fsanitize=address check.c
$ ./a.out
=================================================================
==2193== ERROR: AddressSanitizer: heap-use-after-free on address 0x60060000efe0 at pc 0x4009a8 bp 0x7fff62e22bc0 sp 0x7fff62e22bb8
...
The common defensive programming "trick" is to assign NULL right after free() call:
free(pStr), pStr = NULL;
With it It's likely to get "Segmentation fault" with pStr dereference on GNU/Linux, but there is no guarantee on that.
Understand the answer to your question you need to understand the process of memory allocation. In a generic sense, malloc/free are library functions. They manage a memory pool that is allocated from operating system services.
[at the risk of oversimplification]
Your first malloc finds an empty pool. It then calls an operating system system service to add pages to the process that get added to the pool. Malloc then returns a block of memory from that pool.
Calling free returns the block to the pool. It remains a valid block of memory in the pool.
If you access the free'd address, the memory is still there. However, you are #$#$ing up malloc's pool of memory. That kind of access is going to eventually byte you in the #$#$.

what happens to array elements after the original array is reallocated?

#include <stdio.h>
#include <stdlib.h>
int main()
{
int *a;
a = (int *)malloc(100*sizeof(int));
int i=0;
for (i=0;i<100;i++)
{
a[i] = i+1;
printf("a[%d] = %d \n " , i,a[i]);
}
a = (int*)realloc(a,75*sizeof(int));
for (i=0;i<100;i++)
{
printf("a[%d] = %d \n " , i,a[i]);
}
free(a);
return 0;
}
In this program I expected the program to give me a segmentation fault because im trying to access an element of an array which is freed using realloc() . But then the output is pretty much the same except for a few final elements !
So my doubt is whether the memory is actually getting freed ? What exactly is happening ?
The way realloc works is that it guarantees that a[0]..a[74] will have the same values after the realloc as they did before it.
However, the moment you try to access a[75] after the realloc, you have undefined behaviour. This means that the program is free to behave in any way it pleases, including segfaulting, printing out the original values, printing out some random values, not printing anything at all, launching a nuclear strike, etc. There is no requirement for it to segfault.
So my doubt is whether the memory is actually getting freed?
There is absolutely no reason to think that realloc is not doing its job here.
What exactly is happening?
Most likely, the memory is getting freed by shrinking the original memory block and not wiping out the now unused final 25 array elements. As a result, the undefined behaviour manifests itself my printing out the original values. It is worth noting that even the slightest changes to the code, the compiler, the runtime library, the OS etc could make the undefined behaviour manifest itself differently.
You may get a segmentation fault, but you may not. The behaviour is undefined, which means anything can happen, but I'll attempt to explain what you might be experiencing.
There's a mapping between your virtual address space and physical pages, and that mapping is usually in pages of 4096 bytes at least (well, there's virtual memory also, but lets ignore that for the moment).
You get a segmentation fault if you attempt to address virtual address space that doesn't map to a physical page. So your call to realloc may not have resulted in a physical page being returned to the system, so it's still mapped to you program and can be used. However a following call to malloc could use that space, or it could be reclaimed by the system at any time. In the former case you'd possibly overwrite another variable, in the latter case you'll segfault.
Accessing an array beyond its bounds is undefined behaviour. You might encounter a runtime error. Or you might not. The memory manager may well have decided to re-use the original block of memory when you re-sized. But there's no guarantee of that. Undefined behaviour means that you cannot reason about or predict what will happen. There's no grounds for you to expect anything to happen.
Simply put, don't access beyond the end of the array.
Some other points:
The correct main declaration here is int main(void).
Casting the value returned by malloc is not needed and can mask errors. Don't do it.
Always store the return value of realloc into a separate variable so that you can detect NULL being returned and so avoid losing and leaking the original block.

pointer in c to structure variable

Hi I have following code
#include <stdio.h>
#include <conio.h>
typedef struct test
{
int a;
int b;
int c[10];
}tester;
typedef struct done
{
tester* t;
int nn;
}doner;
void main()
{
doner d;
d.t = (tester*)malloc(sizeof(d.t));
d.t->a = 10;
d.t->c[0] = 10;
printf("%d\n", d.t->a);
getch();
return;
}
I think the statement:
d.t = (tester*)malloc(sizeof(d.t));
is incorrect it should be:
d.t = (tester*)malloc(sizeof(tester));
but when I run this code it is not crashing please let me the why is this.
The fact that it is not crashing is because it has undefined behavior. The correct code is the second one, but no need for casting.
d.t = malloc(sizeof(tester));
Also, You need to free the malloc'ed pointer.
On many system, the heap is not checked when writing to the malloc'ed buffer, but only when freeing the allocated memory. In such case, you will probably get some kind of crash when you free the memory.
The fact that it's not crashing is a big reason why these sort of memory allocation bugs are so insidious and hard to detect. Your program only allocates the one structure, and doesn't fill it up, so the fact that it runs past the amount of memory allocated to it doesn't affect anything else.
If your program made more use of dynamically-allocated memory, then either the calls to malloc/free would trigger a crash because your structure overwrote the heap's linking metadata, or other parts of the program writing to their own malloc'ed data would overwrite your structure. Either way, not pretty.
Yes, you're right. It should be sizeof(tester), because d.t is just a pointer.
Now if you write sizeof(d.t) you invoke Undefined Behavior which is not a guarantee of crash. The program may run correctly, run incorrectly, crash, or order a pizza. There is no guarantee of what will happen with a program that has undefined behavior, even prior to the construct that leads to it.
As freeing the malloced memory - in this small sample program you don't need to worry about it, because the system will free the memory after your program exits, but in general you should try to free whatever you've allocated so as to avoid memory leaks in larger programs.
By default, the linker asks the OS to allocate 1MiB of (stack) memory for the program at the start-up. Your program doesn't crash because all of the references are still in the same memory (same address space) which was reserved by the OS for your program. Technically you haven't allocated that memory, but as the pointers are still in the valid memory range so your program can access it.
This is just like, in most cases, you can write to d.t->c[10] (although valid indexes are 0-9)
Crashes occur when pointers are used which correspond to memory locations outside the allocated memory. Google Page Fault for detailed understanding, if you are interested.

Resources