I'm trying to wrap the C library functions malloc and free to detect whether there is memory leakage in my code. I extend the malloc/free functions by adding to them an fprintf to write to a file the address of the malloc/free and the size.
Compiling this code with gcc or clang gives a segmentation fault in the fopen() line.
Here is the command:
gcc -o mainapp main.c -Wall -Wextra
I placed the fopen inside the malloc and free function but also get the same issue: Segmentation fault (core dumped)
I can't find an explanation for the issue.
Here is my complete code:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#define __USE_GNU
#include <dlfcn.h>
#define TEST_MEM_LEAK 1 // a value of 1 means to join the memory leak detection, and a value of 0 means not to join
#if TEST_MEM_LEAK
typedef void *(*malloc_t)(size_t size);
malloc_t malloc_f = NULL;
typedef void (*free_t)(void *p);
free_t free_f = NULL;
int malloc_flag = 1; // It is used to prevent repeated recursion and cannot exit because the printf function will call malloc for memory allocation
int free_flag = 1;
const char* logFileName = "/home/hammamiw/Documents/HeapMonitor/allocs.log";
FILE* fp = NULL;
void initCheck()
{
fp = fopen("/home/hammamiw/Documents/HeapMonitor/allocs.log", "w");
}
void *malloc(size_t size)
{
if(malloc_flag) {
initCheck();
malloc_flag = 0; // Used to prevent printf from causing an error when calling malloc recursively
void *p = malloc_f(size);
fprintf(fp, "malloc, %lx, %lu\n", (uintptr_t)p, size);
//printf("m\n");
malloc_flag = 1; // It is used to ensure that the initial value of flag flag is consistent when malloc in this file is called again
return p;
}
else {
return malloc_f(size); // Here, the malloc function in the system library obtained by dlsym is called
}
}
void free(void *p)
{
initCheck();
if(free_flag) {
//initCheck();
free_flag = 0;
fprintf(fp, "F, %lx\n", (uintptr_t)p);
//printf("f\n");
free_f(p);
free_flag = 1;
} else {
free_f(p);
}
}
#endif
int main()
{
#if TEST_MEM_LEAK // the part from if to endif can be divided into function calls
malloc_f = dlsym(RTLD_NEXT, "malloc");
if(!malloc_f) {
printf("load malloc failed: %s\n", dlerror());
return 1;
}
free_f = dlsym(RTLD_NEXT, "free");
if(!free_f) {
printf("load free failed: %s\n", dlerror());
return 1;
}
#endif
void *p1 = malloc(10); //The malloc function in this article will be called first
void *p2 = malloc(20);
//Here, p2 is not released and there is a memory leak. Judge by checking whether the number of malloc and free times printed is the same
free(p2);
free(p1);
return 0;
}
NOTE: the code works well if I use printf instead of fprintf -> prints "f" and "m" at each free and malloc call.
Environment: Ubuntu 22.04, C language, GCC compiler version 11.3.0
You need to set malloc_flag before calling fopen(). Then you will protect against recursion not only via fprintf() but also via fopen().
Related
My goal is to wrap malloc() with my own implementation and then inject that implementation into executables using LD_PRELOAD trick for educational purposes.
I've made two files, test.c for testing the injection and wrapper.c which wraps malloc() and is compiled as a shared library.
My question is this:
Is it possible for wrapper to detect that it's caller, test is done (at the end of main()) to free it's memory (dynamic_array)?
I can't add something like deinit() to wrapper because I don't necessarily have access to caller's code like I do in the case of test.
I know it might be best to do it using RAII in C++, but what can be done in C?
// test.c
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
(void)malloc(1);
return 0;
}
Built with:
gcc -o test test.c
// wrapper.c
#define _GNU_SOURCE
#include <stdlib.h>
#include <dlfcn.h>
#include <stddef.h>
#include <string.h>
#include <unistd.h>
static void* (*real_malloc)(size_t size) = NULL;
// Untested pseudocode, but you get the idea
void *dynamic_array = NULL;
unsigned dynamic_array_idx = 0;
size_t dynamic_array_size = 100;
void add_ptr(void* ptr)
{
if (!dynamic_array) {
dynamic_array = real_malloc(dynamic_array_size );
}
if (dynamic_array_idx >= dynamic_array_size) {
dynamic_array_size *= 2;
dynamic_array = realloc(dynamic_array, dynamic_array_size);
}
dynamic_array[dynamic_array_idx++] = ptr;
}
void* malloc(size_t size)
{
if (real_malloc == NULL) {
real_malloc = dlsym(RTLD_NEXT, "malloc");
}
void* ptr = real_malloc(size);
add_ptr(ptr);
return ptr;
}
Built with:
gcc -c -fPIC -o wrapper.o wrapper.c
gcc -shared -o wrapper.so wrapper.o
Ran with:
LD_PRELOAD=wrapper.so ./test
When real_malloc is initialized, you can use atexit to designate a function to be called when the program exits. You can use this to clean up your memory.
Also, dynamic_array should have type void ** to store an array of void *.
void cleanup(void)
{
free(dynamic_array);
}
void* malloc(size_t size)
{
if (real_malloc == NULL) {
real_malloc = dlsym(RTLD_NEXT, "malloc");
atexit(cleanup); // register cleanup handler
}
void* ptr = real_malloc(size);
add_ptr(ptr);
return ptr;
}
I have a simple program let's call it a victim:
#include <stdio.h>
#include <stdlib.h>
int
main(void)
{
int *a = malloc(200);
printf("%p\n", a);
}
And I want to inject a shared object into this program that will log all the calls to the malloc function. So it will works like a memory logger:
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
void *
malloc(size_t n)
{
static void * (*real_malloc)(size_t) = NULL;
if (real_malloc == NULL) {
real_malloc = dlsym(RTLD_NEXT, "malloc");
if (real_malloc == NULL) {
#if 0
printf("MEMORY FILTER: can't find the real malloc() function: %s\n", dlerror());
#endif
exit(EXIT_FAILURE);
}
}
void *result = real_malloc(n);
#if 0
if (result != NULL)
printf("MEMORY FILTER: allocated %zu bytes at address %p.\n", n, result);
else
printf("MEMORY FILTER: failed to allocate %zu bytes.\n", n);
#endif
return result;
}
And then I run the victim as follows:
$ LD_LIBRARY_PATH=. LD_PRELOAD=libmemlog.so ./victim
0x55e0747792a0
It works fine but when I uncomment #if 0 ... #endif blocks when the printf function is called then I get a segmentation fault. This is happens because printf calls malloc inside itself and we got an endless recursion which eventually crushes.
How can I get logging work in this case?
I'm trying to use the brk() function in a C program. My goal is to use it directly (as part of a larger test) by checking the current program break (pb) with
void *current_break = sbrk(0);
executing a malloc (for testing as malloc should sometimes execute brk if the allocated space is large enough)
void* mallow_return = malloc(1);
and than directly executing brk() by using the current address + an increment (and check if this increase the pb):
int increase = 0x01;
void * newbreak = current_break + increase;
int return_value = brk(&newbreak);
My problem is, that neither with a large malloc (malloc(5000;)) nor with (aligned or unaligned) brk() call the pb is changed. When checking the errno I get a
Cannot allocate memory!error (as given bystrerror(errno)
Can anybody see why I'm not able to increase the program break in anyway?
Thanks for any hints!
(System is: Debian 10 (buster) with kernel 4.19)
Edit: As requested this is the main function with includes:
#include <unistd.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
void main(int argc, char **argv)
{
printf("[+] Get current program break\n");
void *current_break = sbrk(0);
printf("\t[+] Current break point=\t%#x\n", current_break);
printf("[+] Malloc should call brk as well\n");
void* mallow_return = malloc(1);
printf("[+] Check if malloc changes PB\n");
void *after_break = sbrk(0);
printf("\t[+] After malloc pbreak=\t%#x\n", after_break);
int increase = 0x01;
printf("\t[+] Increasing p-break direclyby %d\n", increase);
void * newbreak = current_break + increase;
printf("\t[+] Setting break point to=\t%#x\n", newbreak);
int return_value = brk(&newbreak);
//check if error was thrown
int errornumber = errno;
if (errornumber != 0)
{
printf("\t[+] Error: %s!\n", strerror(errornumber));
return -1;
}
//check if pb was set now
printf("\t[?] New program break value?\t%#x\n", sbrk(0));
printf("[?] Return value of brk: %d\n", return_value);
return;
}
(Thanks to #Antii Haapala, who posted this as a comment.)
We need to remove the ampersand here:
int return_value = brk(&newbreak);
That line should be simply
int return_value = brk(newbreak);
So I have these three files
Main.c
#include <assert.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include "support.h"
int main( void ) {
int* num1 = malloc(100);
printf("num1: %p", &num1);
}
Support.c
#include <assert.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include "support.h"
void *malloc(size_t size) {
struct block_meta *block;
if (size <= 0) {
return NULL;
}
if (!global_base) { // First call.
block = request_space(NULL, size);
if (!block) {
return NULL;
}
global_base = block;
} else {
struct block_meta *last = global_base;
block = find_free_block(&last, size);
if (!block) { // Failed to find free block.
block = request_space(last, size);
if (!block) {
return NULL;
}
} else { // Found free block
block->free = 0;
block->magic = 0x77777777;
}
}
return(block+1);
}
void free(void *ptr) {
if (!ptr) {
return;
}
struct block_meta* block_ptr = get_block_ptr(ptr);
assert(block_ptr->free == 0);
assert(block_ptr->magic == 0x77777777 || block_ptr->magic == 0x12345678);
block_ptr->free = 1;
block_ptr->magic = 0x55555555;
}
void *realloc(void *ptr, size_t size) {
if (!ptr) {
// NULL ptr. realloc should act like malloc.
return malloc(size);
}
struct block_meta* block_ptr = get_block_ptr(ptr);
if (block_ptr->size >= size) {
// We have enough space. Could free some once we implement split.
return ptr;
}
// Need to really realloc. Malloc new space and free old space.
// Then copy old data to new space.
void *new_ptr;
new_ptr = malloc(size);
if (!new_ptr) {
return NULL; // TODO: set errno on failure.
}
memcpy(new_ptr, ptr, block_ptr->size);
free(ptr);
return new_ptr;
}
void *calloc(size_t nelem, size_t elsize) {
size_t size = nelem * elsize; // TODO: check for overflow.
void *ptr = malloc(size);
memset(ptr, 0, size);
return ptr;
}
Support.h
#include <assert.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
void *malloc(size_t size);
void free(void *ptr);
void *realloc(void *ptr, size_t size);
struct block_meta {
size_t size;
struct block_meta *next;
int free;
int magic; // For debugging only. TODO: remove this in non-debug mode.
};
#define META_SIZE sizeof(struct block_meta)
void *global_base = NULL;
struct block_meta *find_free_block(struct block_meta **last, size_t size) {
struct block_meta *current = global_base;
while (current && !(current->free && current->size >= size)) {
*last = current;
current = current->next;
}
return current;
}
struct block_meta *request_space(struct block_meta* last, size_t size) {
struct block_meta *block;
block = sbrk(0);
void *request = sbrk(size + META_SIZE);
assert((void*)block == request); // Not thread safe.
if (request == (void*) -1) {
return NULL; // sbrk failed.
}
if (last) { // NULL on first request.
last->next = block;
}
block->size = size;
block->next = NULL;
block->free = 0;
block->magic = 0x12345678;
return block;
}
struct block_meta *get_block_ptr(void *ptr) {
return (struct block_meta*)ptr - 1;
}
However when I attempt to compile using
gcc -o asgn2 main.c support.c
I get the error
/tmp/ccscmcbS.o:(.bss+0x0): multiple definition of `global_base'
/tmp/ccyjhjQC.o:(.bss+0x0): first defined here
/tmp/ccscmcbS.o: In function `find_free_block':
support.c:(.text+0x0): multiple definition of `find_free_block'
/tmp/ccyjhjQC.o:main.c:(.text+0x0): first defined here
/tmp/ccscmcbS.o: In function `request_space':
support.c:(.text+0x55): multiple definition of `request_space'
/tmp/ccyjhjQC.o:main.c:(.text+0x55): first defined here
/tmp/ccscmcbS.o: In function `get_block_ptr':
support.c:(.text+0xfe): multiple definition of `get_block_ptr'
/tmp/ccyjhjQC.o:main.c:(.text+0xfe): first defined here
collect2: error: ld returned 1 exit status
I dont believe that I declared those methods more than once, also it is in a much different format than I am usually given. Not quite sure what it means.
The problem is that you have functions and globals defined (as opposed to declared) in your header file. Therefore, those functions are pulled into both main.c and support.c when they are compiled. Then during the linking phase, the linker sees multiple definitions.
Even if you had include guards, it wouldn't help in this case because that only defends against multiple definitions in a single compilation unit, not across multiple units.
Take the definitions of those function out of the header file, replace them with declarations, and put them either in support.c or in a separate .c file.
You can use the -fcommon option for gcc.
Make sure that the header is included only once, so add something like the following to the headers source code:
#ifndef _HAVE_SUPPORT_H
#define _HAVE_SUPPORT_H
// ...
// YOUR HEADER SOURCE CODE
// ...
#endif //_HAVE_SUPPORT_H
As I said this makes sure that the header is included only once, because then it defines _HAVE_SUPPORT_H. If now another source tries to include it, it will not do anything because _HAVE_SUPPRORT_H is already defined.
It also helps if you have only function declarations in the header and your 'real' functions will be in another *.c file.
Edit:
The second parts is the most important for your problem as #kaylum noticed
For me the solution was simple, downgrade to previous GCC version.
Here is comparison gcc installed on two different Ubuntu version.
GCC for ubuntu 20.04: https://packages.ubuntu.com/focal/gcc (gcc 9)
GCC for ubuntu 22.04: https://packages.ubuntu.com/jammy/gcc (gcc 11)
Because on my case, code was legacy code from about 20 years ago, then it makes sense for me to keep using old compiler.
I want a function that returns the content of a given directory. For this, I am using scandir from dirent.h. The code below compiles successfully (gcc -Wall test.c), but the last printf leads to a segmentation fault. It means that the "eps" structure (a pointer to an array of pointers to dirent structures) is still empty after the function: how can I fix this?
#include <stdlib.h>
#include <stdio.h>
#include <dirent.h>
#include <string.h>
static int myselector(const struct dirent * dir_entry)
{
char * pch = strstr(dir_entry->d_name, ".");
return pch == NULL ? 1 : 0;
}
int list_dir(char * dirname, struct dirent ** eps)
{
int nbfiles = scandir(dirname, &eps, myselector, alphasort);
if(nbfiles > 0)
{
printf("inside function: %s\n", eps[0]->d_name);
return 1;
}
else
return 0;
}
int main(int argc, char *argv[])
{
int status = 0;
struct dirent ** eps = NULL;
status = list_dir("/home", eps);
if (status)
{
puts("ok");
printf("outside function: %s\n", eps[0]->d_name);
}
return EXIT_SUCCESS;
}
You don't seem to be covering the case where scandir returns 0, i.e., empty directory. Return value of -1 is only for errors.
Because your pointer has changed, and you're looking at the wrong thing in main() :)
You're passing a pointer to a pointer to a pointer to scandir(). it's changing what the pointer to a pointer is pointing at (I know, that hurts to read ... ).
Because you're calling scandir() with &eps in your function, you lose that change outside of the function. The value of eps has changed inside your function.
To better understand this, in your current function wrap the scandir() call with printf() statements showing you what the value contained in eps is:
...
printf("%p\n", eps);
int nbfiles = scandir(dirname, &eps, myselector, alphasort);
printf("%p\n", eps);
...
To fix this change your function to:
int list_dir(char * dirname, struct dirent *** eps)
{
int nbfiles = scandir(dirname, eps, myselector, alphasort);
if(nbfiles != -1)
{
printf("inside function: %s\n", (*eps)[0]->d_name);
return 1;
}
else
return 0;
}
And call it as ...
status = list_dir("/home", &eps);
in main(). It will then work perfectly:
broach#roach-VirtualBox:~$ ./test
inside function: broach
ok
outside function: broach
Make list_dir take struct dirent *** instead of struct dirent **, get rid of the & operator in the scandir() call, and add it to the list_dir() call from main.
The first lines of list_dir() become:
int list_dir(char * dirname, struct dirent *** eps)
{
int nbfiles = scandir(dirname, eps, myselector, alphasort);
and the list_dir() call in main becomes:
status = list_dir("/home", &eps);
That way list_dir() can let scandir() modify eps from main() via its address, instead of modifying the argument, on the stack, passed to list_dir().