C program to replace malloc, calloc and relloc using LD_PRELOAD i.e during the runtime of a process

LD_PRELOAD Theory

All about LD_PRELOAD

Normally the Linux dynamic loader ld-linux (see ld-linux(8) man page) finds and loads the shared libraries needed by a program, prepare the program to run, and then run it. The shared libraries (shared objects) are loaded in whatever order the loader needs them in order to resolve symbols.
LD_PRELOAD is an optional environmental variable containing one or more paths to shared libraries, or shared objects, that the loader will load before any other shared library including the C runtime library (libc.so) This is called preloading a library.
Preloading a library means that its functions will be used before others of the same name in later libraries. This enables library functions to be intercepted and replaced (overwritten.) As a result program behavior can be non-invasively modified, i.e. a recompile is not necessary.
For example, you could write a library which implements alternative malloc and free functionality. By preloading the new library using LD_PRELOAD the new malloc and free functions will be used rather than the corresponding standard libc functions.
Shared library paths, if there is more than one, may be separated by either colons (preferred) or spaces. Entries in LD_PRELOAD containing ’/’are treated as path names, whereas entries not containing ’/’are searched for as usual. Obviously this only affects dynamically linked – not statically linked – applications.
To avoid this mechanism being using as an attack vector for suid/sgid executable binaries, the loader ignores LD_PRELOAD if ruid != euid. For such binaries, only libraries in standard paths that are also suid/sgid will be preloaded.
Some users use LD_PRELOAD to specify libraries in nonstandard locations, but the LD_LIBRARY_PATH environmental variable is a better solution
Note:
Shared libraries specified in /etc/ld.so.preload are loaded before libraries specified by LD_PRELOAD
The libc library checks for the existing of /etc/ld.so.preload (see elf/rtld.c) and, if found, loads the listed shared libraries just as setting the environment variable LD_PRELOAD would do. The advantage of using /etc/ld.so.preload is that these shared libraries are implicitly trusted and hence the ruid != euid test does not apply. Thus the loader will load the shared objects listed in /etc/ld.so.preload even for suid/sgid executable binaries.

Danger of using dlsym with rtld_next

C program


Directory Structure

First create a project Folder "preload_alloc_library".

Create the following sub-directories and files.
1. 'bin'   ---> Directory 
2. 'src'   ---> Directory
3. Makefile  ---> File
4. exe.sh      ---->  Bash executable shell script 

Note: Just run the executable script for compilation and output generation.


Inside 'src' folder, create 2 files: 

1. preload_library.c  
2. test.c



Neelkanth_221$ cat Makefile

SRCS_DIR     := src
BUILD_DIR    := bin
CURRENT_DIR  := $(shell pwd)

PRELOAD_LIB  := $(BUILD_DIR)/libpreload.so

FINAL_BINARY := $(BUILD_DIR)/test

CC=gcc
CFLAGS=-Wall -g


all:  compile_source

compile_source:  compile_libs
        @echo '###############################################'
        @echo 'compile the test program'
        @echo '###############################################'
        $(CC) $(CFLAGS) $(SRCS_DIR)/test.c -o $(FINAL_BINARY)

compile_libs:

        $(CC) -fPIC -shared $(SRCS_DIR)/preload_library.c -o $(PRELOAD_LIB) -ldl


clean:
        rm -rf bin/*

Neelkanth_221$ cat preload_library.c

/************************************************************************************
 * This is the pre-load library, which is replace all alloc glic libray functions,
 * with new definitions on runtime.
 ************************************************************************************/
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

/*********************************************************************
 *                      FUNCTION DECLARATIONS                        *
 *********************************************************************/
/* Function pointers to hold the value of the glibc functions */

/* This needs to be in the c file.. global declaration.  */
static void* (*real_malloc)(size_t size) = NULL;
static void  (*real_free)(void *ptr) = NULL;
static void* (*real_calloc)(size_t nmemb, size_t size) = NULL;
static void*  (*real_realloc)(void *ptr, size_t size) = NULL;

static void* func_malloc(size_t c);
static void* func_calloc(size_t nmemb, size_t size);
static void* func_realloc(void *ptr, size_t size);
static void func_free(void *ptr);


/************************************************************************
  FREE
 *************************************************************************/
void org_free(void *ptr){
    printf ("GLIBC Free called\n");
    /* call the real glibc function and return the result */
    real_free =  dlsym(RTLD_NEXT, "free");
    if (real_free != NULL){
        real_free(ptr);
    }
    return;

}

static void func_free(void* p) {
    org_free(p);
    return;
}

void free(void* p) {
    printf("LD preload FREE is done here.\n");
    func_free(p);
    return;
}

/************************************************************************
  MALLOC
 ***********************************************************************/
/* Function pointers to hold the value of the glibc functions */
static void *org_malloc (size_t c)
{
    printf ("GLIBC malloc called with %zu\n", c);
    /* call the real glibc function and return the result */
    real_malloc =  dlsym(RTLD_NEXT, "malloc");
    if (real_malloc != NULL){
        return real_malloc(c);
    }

    return NULL;
}


static void* func_malloc(size_t c)
{
    char *temp = NULL;
    temp = (char *)org_malloc(c);

    return temp;
}

void *malloc (size_t c)
{
    printf ("LD_PRELOAD malloc called with %zu\n", c);
    return func_malloc(c);
}

/************************************************************************
  CALLOC
 ***********************************************************************/
static void *org_calloc (size_t nmemb, size_t size)
{
    printf ("GLIBC calloc called with %zu\n", nmemb*size);
    /* call the real glibc function and return the result */
    real_calloc =  dlsym(RTLD_NEXT, "calloc");
    if (real_calloc != NULL)
    {
        return real_calloc(nmemb, size);
    }

    return NULL;
}


static void* func_calloc(size_t nmemb, size_t size)
{
    char *temp = NULL;
    temp = (char *)org_calloc(nmemb, size);

    return temp;

}

void *calloc(size_t nmemb, size_t size)
{
    printf ("LD_PRELOAD calloc called with %zu\n", nmemb*size);
    return func_calloc(nmemb, size);
}

/************************************************************************
  RE-ALLOC
 ***********************************************************************/
static void *org_realloc(void *ptr, size_t size)
{
    printf ("GLIBC re-alloc called with %zu\n", size);
    /* call the real glibc function and return the result */
    real_realloc =  dlsym(RTLD_NEXT, "realloc");
    if (real_realloc != NULL)
    {
        return real_realloc(ptr, size);
    }

    return NULL;
}


static void* func_realloc(void *ptr, size_t size)
{

    char *temp = NULL;
    temp = (char *)org_realloc(ptr, size);

    return temp;

}

void *realloc(void *ptr, size_t size)
{
    printf ("LD_PRELOAD realloc\n");
    return func_realloc(ptr, size);
}


Neelkanth_221$ cat test.c

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main()
{

    printf("##########################  MALLOC ######################################\n");
    char *ptr = NULL;
    ptr = (char *)malloc(10 * sizeof(char));
    printf("###################################################################\n\n");

    char *ptr1;
    printf("######################## STRDUP  ###########################################\n");
    ptr1 = strdup("roger federer is the greatest of all times");
    printf("###################################################################\n\n");


    char *ptr2 = NULL;
    printf("####################### CALLOC ############################################\n");
    ptr2 = (char *)calloc(10 , sizeof(char));
    printf("###################################################################\n\n");


    char *ptr4;
    printf("######################## STRDUP  ###########################################\n");
    ptr4 = strdup("asdfkljhasdlfjkhasdlfjkhsadflh");
    printf("###################################################################\n\n");

    char *ptr3 = NULL;
    printf("####################### REALLOC #####################################\n");
    ptr3 = (char *)realloc(ptr2, 20*sizeof(char));
    printf("###################################################################\n\n");


    free(ptr);
    free(ptr1);
    free(ptr3);

    return 0;
}

Neelkanth_221$ cat exe.sh

#!/bin/bash

make clean
make
LD_PRELOAD=/home/labuser/Desktop/wrap-syscall-master/bin/libpreload.so ./bin/test


Output: Run the script

./exe.sh

rm -rf bin/*
gcc -fPIC -shared src/preload_library.c -o bin/libpreload.so -ldl
###############################################
compile the test program
###############################################
gcc -Wall -g src/test.c -o bin/test
src/test.c: In function ‘main’:
src/test.c:28:11: warning: variable ‘ptr4’ set but not used [-Wunused-but-set-variable]
     char *ptr4;
           ^
##########################  MALLOC ######################################
LD_PRELOAD malloc called with 10
GLIBC malloc called with 10
###################################################################

######################## STRDUP  ###########################################
LD_PRELOAD malloc called with 43
GLIBC malloc called with 43
###################################################################

####################### CALLOC ############################################
LD_PRELOAD calloc called with 10
GLIBC calloc called with 10
###################################################################

######################## STRDUP  ###########################################
LD_PRELOAD malloc called with 31
GLIBC malloc called with 31
###################################################################

####################### REALLOC #####################################
LD_PRELOAD realloc
GLIBC re-alloc called with 20
###################################################################

LD preload FREE is done here.
GLIBC Free called
LD preload FREE is done here.
GLIBC Free called
LD preload FREE is done here.
GLIBC Free called




A C Program to demonstrate adjacency list representation of graphs

Graphs

Basics to be understood before getting into C program:

Adjacency matrix

In graph theory and computer science, an adjacency matrix is a square matrix used to represent a finite graph. The elements of the matrix indicate whether pairs of vertices are adjacent or not in the graph.
Adjacency Matrix is a 2D array of size V x V where V is the number of vertices in a graph. 
The adjacency matrix, sometimes also called the connection matrix, of a simple labeled graph is a matrix with rows and columns labeled by graph vertices, with a 1 or 0 in position according to whether v_i and v_j are adjacent or not. For a simple graph with no self-loops, the adjacency matrix must have 0s on the diagonal. For an undirected graph, the adjacency matrix is symmetric.
The illustration above shows adjacency matrices for particular labeling of the claw graphcycle graph C_4, and complete graph K_4.

Kinds of Graphs

Various flavors of graphs have the following specializations and particulars about how they are usually drawn.

·         Undirected Graphs.

In an undirected graph, the order of the vertices in the pairs in the Edge set doesn't matter. Thus, if we view the sample graph above we could have written the Edge set as {(4,6),(4,5),(3,4),(3,2),(2,5)),(1,2)),(1,5)}. Undirected graphs usually are drawn with straight lines between the vertices.
The adjacency relation is symmetric in an undirected graph, so if u ~ v then 

it is also the case that v ~ u.

·         Directed Graphs.

In a directed graph the order of the vertices in the pairs in the edge set matters. Thus u is adjacent to v only if the pair (u,v) is in the Edge set. For directed graphs we usually use arrows for the arcs between vertices. An arrow from u to v is drawn only if (u,v) is in the Edge set. The directed graph below.

Graphs, adjacency matric and C Programming

Graph

Adjacency Matrix [derived from above graph]


A Graph can be represented in 2 ways

1. 2 Dimension array a[i][j]  ---> Memory requirement: 

Adjacency matrix representation of a graph wastes 

lot of memory space.

2. Array of linked list [which is represented below]



Array of linked list [derived from above adjacency matrix]



C program

/**********************************************************************
 * A C Program to demonstrate adjacency list  representation of graphs
 **********************************************************************/
#include <stdio.h>
#include <stdlib.h>


/* A structure to represent an adjacency list node
 * EACH NODE IN THE ADJACENCY LIST
 */
struct adj_list_node
{
    int dest;
    struct adj_list_node* next;
};

/* A structure to represent an adjacency list
 * THE 1ST NODE IN THE ROW LINKED LIST I.E ADJACENCY LIST.
 */
struct adj_list
{
    struct adj_list_node *head;
};

/* A structure to represent a graph. A graph
 * is an array of adjacency lists.
 * Size of array will be v (number of vertices
 * in graph)
 */
struct graph_ds
{
    int v;
    struct adj_list* array;
};

/*******************************************************************
 * A utility function to create a new adjacency list node
 * i.e. create a new node in each adjacency list. The next pointer
 * of the last node in the adjacency list is made to point to NULL.
 ******************************************************************/
struct adj_list_node* newadj_list_node(int dest)
{
    struct adj_list_node* new_node =
        (struct adj_list_node*) malloc(sizeof(struct adj_list_node));
    new_node->dest = dest;
    new_node->next = NULL;
    return new_node;
}

/************************************************************
 * Creates a graph of v vertices
 ************************************************************/
struct graph_ds* create_graph(int v)
{
    /* Allocate memory for graph data structure */
    struct graph_ds* graph =
        (struct graph_ds*) malloc(sizeof(struct graph_ds));
    graph->v = v;

    /* Create an array of adjacency lists. Allocate
     * memory for the 1st node each of the adjacency list
     * rows.
     * The 1st nodes in each adjacency list together form
     * the array.
     * Size of array will be v
     */
    graph->array =
        (struct adj_list*) malloc(v * sizeof(struct adj_list));

    /* Initialize each adjacency list as empty by
     * making head as NULL. The head pointer of 1st node
     * in each adjacency list(row) is made to point to
     * NULL.
     */
    int i;
    for (i = 0; i < v; ++i)
        graph->array[i].head = NULL;

    return graph;
}

/* Adds an edge to an undirected graph */
void add_edge(struct graph_ds* graph, int src, int dest)
{
    /*
     * Add an edge from src to dest. A new node is
     * added to the adjacency list of src. The node
     * is added at the begining.
     */
    printf("Add edge start\n");  

    struct adj_list_node* new_node = newadj_list_node(dest);

   /* From the Linked listed Array, choose the row linked list corresponding
    * to index of the array which is equal to "src".
    * Initially, Graph's Row Head pointer[src] points to NULL or current
    * node [old destination node, which has connection to the src node].
    * 'or'
    * When new node (new destination node which also has connection to source
    * node) is added, new_node next is made to point to current node.
    * Here, current node is the old dest node connected to src.
    * Then, update Graph's Row head pointer to point to new node.
    * i.e.
    *  Direction of addition of new node.    [right to left]
    *  [graph's row Head pointer]
    *       ^
    *       |
    *       |
    *  [new_node]--->[current_node]--->[old_node]-->[old_node]---> NULL
    */
    new_node->next = graph->array[src].head;
    printf("new_node->dest :%d\n",  new_node->dest);
    graph->array[src].head = new_node;
    printf("to index graph->array[%d].head->dest  ---> add: %d\n",
                         src, graph->array[src].head->dest);

    /*****************************************************************
     * Since graph is undirected i.e symmetrix adjacent matrix,
     * do the above for row Head pointer in linked list array whose
     * index is "dest".
     * i.e. New_node's next points to the current node connected to
     * dest. Update the row head pointer to point to the new node.
     *
     * Now, we have achieved A-->B and B-->A symmetry, in the graph.
     *****************************************************************/
    new_node = newadj_list_node(src);
    new_node->next = graph->array[dest].head;
    graph->array[dest].head = new_node;

    printf("to index graph->array[%d].head->dest  ---> add: %d\n",
                         dest, graph->array[dest].head->dest);
    printf("Add edge [%d  <----->  %d] complete\n", src, dest);  
}

/*
 * Print the adjacency list representation of graph.
 */
void print_graph(struct graph_ds* graph)
{
    int v;
    for (v = 0; v < graph->v; ++v)
    {
        struct adj_list_node* vertex_header = graph->array[v].head;
        printf("\n Adjacency list of vertex %d\n head ", v);
        while (vertex_header)
        {
            printf("-> %d", vertex_header->dest);
            vertex_header = vertex_header->next;
        }
        printf("\n");
    }
}

/******************************************************************
input: Create edges
0 --> 1 ,0 --> 4,
1 --> 2, 1 --> 3, 1 --> 4,
2 --> 3,
3 --> 4

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Adjacency Matrix representation of above.
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    0  1  2  3  4
###############
0 # 0  1  0  0  1          ----> vertex 0 is connected to vertex 4 and 1.
1 # 1  0  1  1  1          ----> vertex 1 is connected to vertex 0, 2, 3 and 4.
2 # 0  1  0  1  0          ----> vertex 2 is connected to vertex 1 and 3.
3 # 0  1  1  0  1          ----> vertex 3 is connected to vertex 1, 2 and 4.
4 # 1  1  0  1  0          ----> vertex 4 is connected to vertex 0, 1 and 3.

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Adjacency Linked list is the memory efficient way of
representing the graphs.
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
output:
 Adjacency list of vertex 0
 head -> 4-> 1

 Adjacency list of vertex 1
 head -> 4-> 3-> 2-> 0

 Adjacency list of vertex 2
 head -> 3-> 1

 Adjacency list of vertex 3
 head -> 4-> 2-> 1

 Adjacency list of vertex 4
 head -> 3-> 1-> 0

 ******************************************************************/

/* Main program to test the above functions */
int main()
{
    /* create the graph given in above figure. i.e. 5 vertices */
    int v = 5;
    /* Now, create graph */
    struct graph_ds* graph = create_graph(v);
    add_edge(graph, 0, 1);
    add_edge(graph, 0, 4);
    add_edge(graph, 1, 2);
    add_edge(graph, 1, 3);
    add_edge(graph, 1, 4);
    add_edge(graph, 2, 3);
    add_edge(graph, 3, 4);

    /* print the adjacency list representation of the above graph */
    print_graph(graph);

    return 0;
}

Output

 Neelkanth $:./a.out 
Add edge start
new_node->dest :1
to index graph->array[0].head->dest  ---> add: 1
to index graph->array[1].head->dest  ---> add: 0
Add edge [0  <----->  1] complete
Add edge start
new_node->dest :4
to index graph->array[0].head->dest  ---> add: 4
to index graph->array[4].head->dest  ---> add: 0
Add edge [0  <----->  4] complete
Add edge start
new_node->dest :2
to index graph->array[1].head->dest  ---> add: 2
to index graph->array[2].head->dest  ---> add: 1
Add edge [1  <----->  2] complete
Add edge start
new_node->dest :3
to index graph->array[1].head->dest  ---> add: 3
to index graph->array[3].head->dest  ---> add: 1
Add edge [1  <----->  3] complete
Add edge start
new_node->dest :4
to index graph->array[1].head->dest  ---> add: 4
to index graph->array[4].head->dest  ---> add: 1
Add edge [1  <----->  4] complete
Add edge start
new_node->dest :3
to index graph->array[2].head->dest  ---> add: 3
to index graph->array[3].head->dest  ---> add: 2
Add edge [2  <----->  3] complete
Add edge start
new_node->dest :4
to index graph->array[3].head->dest  ---> add: 4
to index graph->array[4].head->dest  ---> add: 3
Add edge [3  <----->  4] complete

 Adjacency list of vertex 0
 head -> 4-> 1

 Adjacency list of vertex 1
 head -> 4-> 3-> 2-> 0

 Adjacency list of vertex 2
 head -> 3-> 1

 Adjacency list of vertex 3
 head -> 4-> 2-> 1

 Adjacency list of vertex 4
 head -> 3-> 1-> 0



C program to print the PID of the sender process that sends signal to receiver process

/************************************************* C program to print the PID of the sender process which

 sends signal to receiver process *************************************************/

#include <stdio.h>
#include <signal.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

static struct sigaction siga;

static void signal_hanlder(int sig, siginfo_t *siginfo, void *context) {
    /* get pid of sender process */
    pid_t sender_pid = siginfo->si_pid;

    if(sig == SIGTERM) {
        printf("received SIGTERM. Sender process PID: %d\n", sender_pid);
        exit(0);
    }

    return;
}

int main_loop() {
    /* print pid */
    printf("process [%d] started.\n", (int)getpid());

    /* prepare sigaction */
    siga.sa_sigaction = *signal_hanlder;
    siga.sa_flags |= SA_SIGINFO; // get detail info

    /* change signal action */
    if(sigaction(SIGTERM, &siga, NULL) != 0) {
        printf("error sigaction()");
        return errno;
    }

    /* Enter into infinite loop here. */
    while(1);

    return 0;
}

int main(int argc, char *argv[]) {
    main_loop();

    return 0;
}

Output


Neelkanth_221$ gcc test123.c
Neelkanth_221$ ./a.out &
[1] 3316

Neelkanth_221$ process [3316] started.   --> pid of the current process

Neelkanth_221$
Neelkanth_221$
Neelkanth_221$ ps -elf | grep a.out
0 R labuser   3316  2868 84  80   0 -  1053 -      22:57 pts/33   00:00:02 ./a.out
0 S labuser   3318  2868  0  80   0 -  3989 pipe_w 22:57 pts/33   00:00:00 grep --color=auto a.out


Neelkanth_221$ kill -15 3316        --> from bash shell, send SIGTERM to current process.
Neelkanth_221$ received SIGTERM. Sender process PID: 2868   --> pid of the process sending signal to current process

[1]+  Done                    ./a.out
Neelkanth_221$
Neelkanth_221$ ps -elf | grep 2868
0 S labuser   2868  2867  0  80   0 -  6824 wait   19:59 pts/33   00:00:00 -bash
0 R labuser   3319  2868  0  80   0 -  5664 -      22:57 pts/33   00:00:00 ps -elf
0 S labuser   3320  2868  0  80   0 -  3989 pipe_w 22:57 pts/33   00:00:00 grep --color=auto 2868