├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── src ├── backtrace.c ├── backtrace.h ├── canary.c ├── canary.h ├── chunk.c ├── chunk.h ├── config.c ├── config.h ├── display.c ├── display.h ├── libc_malloc.c ├── libc_malloc.h ├── libdheap.c └── libdheap.h └── tests ├── buffer_overflow.c ├── buffer_underflow.c ├── child.c ├── double_free.c ├── execve.c ├── system.c └── test.c /.gitignore: -------------------------------------------------------------------------------- 1 | # Temporary files 2 | *~ 3 | \#* 4 | .*\#* 5 | 6 | # Build files 7 | libdheap.so 8 | 9 | # Test files 10 | test 11 | buffer_overflow 12 | buffer_underflow 13 | double_free 14 | child 15 | execve 16 | system -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | Copyright © 2017 Dhaval Kapil 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the “Software”), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SRC_DIR = ./src 2 | TEST_DIR = ./tests 3 | ROOT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) 4 | 5 | SHARED_LIB = libdheap.so 6 | 7 | TEST_FILES = test buffer_overflow buffer_underflow double_free child execve system 8 | 9 | CC = gcc 10 | CFLAGS = -shared -Bsymbolic -Wl,--no-as-needed -ldl -fPIC -Wall -Werror 11 | 12 | all: library tests 13 | 14 | library: 15 | $(CC) $(CFLAGS) $(SRC_DIR)/*.c -o ${SHARED_LIB} 16 | 17 | tests: $(TEST_FILES) 18 | 19 | %: $(TEST_DIR)/%.c 20 | $(CC) -o $@ $< 21 | 22 | runtests: tests 23 | for file in $(TEST_FILES); do \ 24 | echo $$file; \ 25 | ./$$file; \ 26 | done 27 | 28 | rundtests: tests library 29 | for file in $(TEST_FILES); do \ 30 | echo $$file; \ 31 | LD_PRELOAD=$(ROOT_DIR)/${SHARED_LIB} LIBDHEAP_EXIT_ON_CLOSE=1 ./$$file; \ 32 | done 33 | 34 | clean: 35 | rm $(SHARED_LIB) 36 | for file in $(TEST_FILES); do \ 37 | rm $$file; \ 38 | done 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libdheap 2 | 3 | A shared (dynamic) library that can be transparently injected into different processes to detect memory corruption in glibc heap. 4 | 5 | It works by intercepting and wrapping over libc's malloc() and free() while maintaining information about various chunks in an intermediate data storage (also on the heap). Also, canaries are added before and after heap chunks to detect overflows. 6 | 7 | ``` 8 | +------------------+ +---------------------+ 9 | | | malloc(), free() | | 10 | | User | ----------------------------> | libdheap | 11 | | Process | <---------------------------- | (injected library) | 12 | | | intercepted | | 13 | +------------------+ +---------------------+ 14 | | ^ | ^ 15 | | | __libc_** | | 16 | | | +------------------------+ | 17 | | | | +------------------------+ 18 | | | | | 19 | | | v | 20 | | | +---------------+ +------------+-----------+ 21 | | | printf(), etc. | | chunks | | | 22 | | +------------------ | glibc | ------------> | user | libdheap | 23 | +-------------------> | library | <------------ | data | data | 24 | | | | | | 25 | +---------------+ +------------+-----------+ 26 | ^ | Heap Memory 27 | | | 28 | | | brk(), mmap() 29 | | | 30 | ------------------------------------------------------------------------- 31 | | | Operating System 32 | | v 33 | +---------------+ 34 | | | 35 | | kernel | 36 | | | 37 | +---------------+ 38 | ``` 39 | 40 | ## Features 41 | 42 | * Runs directly on compiled code. Ideal for detecting errors in programs whose source code is unavailable 43 | * Detects **invalid frees** including double frees 44 | * Detects if malloc returns a **chunk which overlaps** with an already allocated chunk 45 | * Detects any kind of **buffer based overflow or underflow**. This also detects many 'use after free' vulnerabilities 46 | * Dynamic library, can be **attached to any process** (provided required permissions are available) 47 | * Displays the **stack trace** (the function call history) on detecting any of the above errors 48 | 49 | ## Installation 50 | 51 | This library is _not_ portable and works only with glibc. 52 | 53 | To install, clone this repository and `cd` to it: 54 | 55 | ```sh 56 | git clone https://github.com/DhavalKapil/libdheap 57 | ``` 58 | 59 | Run `make`: 60 | 61 | ```sh 62 | make 63 | ``` 64 | 65 | The shared library will be generated: `libdheap.so` 66 | 67 | ## Usage 68 | 69 | To run any program with `libdheap`, load the library using `LD_PRELOAD` environment variable. 70 | 71 | ```sh 72 | LD_PRELOAD=/path/to/libdheap.so gedit 73 | ``` 74 | 75 | `libdheap` will output any error/log to standard error by default. You might want to redirect the output to some external file. 76 | 77 | ```sh 78 | [LIBDHEAP LOG] : Freeing non allocated chunk! 79 | [LIBDHEAP LOG] : Printing Stack Trace ====> 80 | [LIBDHEAP LOG] : 0x400604 81 | [LIBDHEAP LOG] : 0x2b3b8016ff45 82 | [LIBDHEAP LOG] : <==== End of Stack Trace 83 | ``` 84 | 85 | `libdheap` allows setting two configuration options (through environment variables) as follow: 86 | 87 | 1. `LIBDHEAP_DEBUG`: If 1, will output debugging statements along with errors and logs. 88 | 2. `LIBDHEAP_EXIT_ON_ERROR`: If 1, will exit the instant it detects any memory corruption error. 89 | 90 | By default, both are set to 0. Use the following command to configure: 91 | 92 | ```sh 93 | LD_PRELOAD=/path/to/libdheap.so LIBDHEAP_DEBUG=1 gedit 94 | ``` 95 | 96 | Note: If debugging is enabled, it is advised to redirect output to an external file (`libdheap` outputs a **lot** of things). Also, this library is not developed for using in production, since it slows the application by approximately 5 times. 97 | 98 | ## Implementation details 99 | 100 | * Uses a custom stack tracer (by jumping around the memory using the frame pointer). Existing stack tracers don't work as they are themselves dependent upon 'malloc', 'free', etc. 101 | * Uses AVL trees for storing chunks as non overlapping sorted intervals. 102 | 103 | ## Contribution 104 | 105 | Feel free to [file issues](https://github.com/DhavalKapil/libdheap/issues) and submit [pull requests](https://github.com/DhavalKapil/libdheap/pulls) – contributions are welcome. 106 | 107 | ## License 108 | 109 | libdheap is licensed under the [MIT license](https://dhaval.mit-license.org/2017/license.txt). -------------------------------------------------------------------------------- /src/backtrace.c: -------------------------------------------------------------------------------- 1 | #include "backtrace.h" 2 | #include "display.h" 3 | 4 | #include 5 | 6 | extern FILE *libdheap_log; 7 | extern void *__libc_stack_end; 8 | 9 | /** 10 | * Assumes a stack layout where stack frames are linked using frame pointers 11 | * 12 | * +-----------------+ +-----------------+ 13 | * FP -> | previous FP --------> | previous FP ------> 14 | * | | | | 15 | * | return address | | return address | 16 | * +-----------------+ +-----------------+ 17 | * 18 | * Stack is growing upwards, so addresses grow downwards 19 | * Assuming pointers to the above can be stored in size_t data type 20 | * Tested on 32 bit and 64 bit machines, works fine for now 21 | */ 22 | 23 | void set_backtrace (void **backtrace, unsigned int start, unsigned int len) { 24 | size_t *current_frame_address; 25 | size_t *current_return_address; 26 | unsigned int i; 27 | 28 | if (len == 0) { 29 | return; 30 | } 31 | 32 | current_frame_address = __builtin_frame_address(0); 33 | for (i = 0; i < (start + len); i++) { 34 | if (current_frame_address >= (size_t *)&i && 35 | current_frame_address <= (size_t *)__libc_stack_end) { 36 | // current_frame_address falls in range of 'i' (lowest possible stack 37 | // address) and __libc_stack_end (highest possible stack address) 38 | current_return_address = (size_t *)*(current_frame_address + 1); 39 | current_frame_address = (size_t *)*(current_frame_address); 40 | } else { 41 | // End of stack frames 42 | current_return_address = NULL; 43 | } 44 | if (i >= start) { 45 | backtrace[i - start] = current_return_address; 46 | } 47 | } 48 | } 49 | 50 | void print_backtrace (void **backtrace, unsigned int len) { 51 | unsigned int i; 52 | display_log(libdheap_log, "Printing Stack Trace ====>"); 53 | for (i = 0; i < len; i++) { 54 | if (backtrace[i] == NULL) { 55 | break; 56 | } 57 | display_log(libdheap_log, "\t\t%p", backtrace[i]); 58 | } 59 | display_log(libdheap_log, "<==== End of Stack Trace\n"); 60 | } 61 | -------------------------------------------------------------------------------- /src/backtrace.h: -------------------------------------------------------------------------------- 1 | /** 2 | * This implements functions to print the stack trace of the process 3 | * to debug errors. The original 'backtrace' defined by GNU uses malloc 4 | * internally that causes an infinite recursion, so a custom version is written. 5 | */ 6 | 7 | #ifndef _BACKTRACE_GUARD_H 8 | #define _BACKTRACE_GUARD_H 9 | 10 | /** 11 | * Fills up an array with trace of function calls 12 | * 13 | * @param backtrace The array to fill 14 | * @param start The starting index to go back in trace 15 | * @param len The total number of addresses to fill up 16 | * 17 | * start=0 will start filling directly from the call to this function 18 | * Invalid entries are filled with NULL 19 | */ 20 | void set_backtrace (void **backtrace, unsigned int start, unsigned int len); 21 | 22 | /** 23 | * Prints the backtrace uptil a certail length. Stops on NULL 24 | * 25 | * @param backtrace The array to print 26 | * @param len The length of the array 27 | */ 28 | void print_backtrace (void **backtrace, unsigned int len); 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /src/canary.c: -------------------------------------------------------------------------------- 1 | #include "canary.h" 2 | #include "display.h" 3 | 4 | #include 5 | #include 6 | 7 | extern FILE *libdheap_log; 8 | 9 | /** 10 | * Returns a pointer to the begin canary of a chunk 11 | * 12 | * @param ch The required chunk 13 | * 14 | * @return The canary * 15 | */ 16 | static inline canary *get_begin_canary (struct chunk *ch) { 17 | return (canary *)((char *)ch->ptr + sizeof(size_t)); 18 | } 19 | 20 | /** 21 | * Returns a pointer to the end canary of a chunk 22 | * 23 | * @param ch The required chunk 24 | * 25 | * @return The canary * 26 | */ 27 | static inline canary *get_end_canary (struct chunk *ch) { 28 | return (canary *)((char *)ch->ptr + 29 | sizeof (size_t) + 30 | ch->requested_size - 31 | sizeof(canary)); 32 | } 33 | 34 | // Implementing functions defined in header file 35 | 36 | size_t get_padded_size (size_t size) { 37 | return (size + 2*sizeof(canary)); 38 | } 39 | 40 | canary generate_canary () { 41 | canary c; 42 | size_t size; 43 | unsigned char *p; 44 | int fd; 45 | 46 | fd = open("/dev/urandom", O_RDONLY); 47 | if (fd >= 0) { 48 | size = read(fd, &c, sizeof(canary)); 49 | close(fd); 50 | if (size == sizeof(canary)) { 51 | return c; 52 | } 53 | } 54 | // Some error 55 | // Create a canary difficult to overflow 56 | c = 0; 57 | p = (unsigned char *)&c; 58 | p[sizeof(c) - 1] = '\n'; 59 | p[sizeof(c) - 2] = '\0'; 60 | return c; 61 | } 62 | 63 | void guard_chunk (struct chunk *ch) { 64 | ch->begin_guard = generate_canary(); 65 | ch->end_guard = generate_canary(); 66 | // Now putting guards inside chunk 67 | *get_begin_canary(ch) = ch->begin_guard; 68 | *get_end_canary(ch) = ch->end_guard; 69 | } 70 | 71 | int check_canary (struct chunk *ch) { 72 | if (ch->begin_guard != *get_begin_canary(ch)) { 73 | display_log(libdheap_log, "Buffer underflow detected in heap chunk"); 74 | return 0; 75 | } else if (ch->end_guard != *get_end_canary(ch)) { 76 | display_log(libdheap_log, "Buffer overflow detected in heap chunk"); 77 | return 0; 78 | } 79 | return 1; 80 | } 81 | -------------------------------------------------------------------------------- /src/canary.h: -------------------------------------------------------------------------------- 1 | /** 2 | * This implements functionality to generate and test stack canaries. 3 | * Internally it uses /dev/urandom as the pseudo random generator 4 | */ 5 | #ifndef _CANARY_GUARD_H 6 | #define _CANARY_GUARD_H 7 | 8 | #include 9 | 10 | // size_t is generally equal to the size of a (void *) pointer. 11 | // Having canaries of this size generally help in keeping alignment 12 | typedef size_t canary; 13 | 14 | #include "chunk.h" 15 | 16 | /** 17 | * Returns the padded size after accomodating required canaries 18 | * 19 | * @param size The size requested 20 | * 21 | * @return The padded size 22 | */ 23 | size_t get_padded_size (size_t size); 24 | 25 | /** 26 | * Generates a (pseudo)random canary value 27 | * 28 | * @return The canary 29 | */ 30 | canary generate_canary (); 31 | 32 | /** 33 | * Adds canary guards to a chunk 34 | * 35 | * @param ch The chunk to be guarded 36 | */ 37 | void guard_chunk (struct chunk *ch); 38 | 39 | /** 40 | * Checks whether canary is intact for a chunk or not 41 | * 42 | * @param ch The chunk to be checked 43 | * 44 | * @return 1 if canary is intact, 0 otherwise 45 | */ 46 | int check_canary (struct chunk *ch); 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /src/chunk.c: -------------------------------------------------------------------------------- 1 | #include "chunk.h" 2 | #include "libc_malloc.h" 3 | 4 | #include 5 | 6 | /** 7 | * Returns a node structure wrapping a chunk 8 | * 9 | * @param ch The chunk 10 | * 11 | * @return The node structure 12 | */ 13 | struct node *create_node (struct chunk *ch) { 14 | struct node *n = libc_malloc(sizeof(struct node)); 15 | if (n == NULL) { 16 | return NULL; 17 | } 18 | n->ch = ch; 19 | n->height = 1; 20 | n->left = NULL; 21 | n->right = NULL; 22 | return n; 23 | } 24 | 25 | /** 26 | * Get max of two numbers 27 | */ 28 | int max (int n1, int n2) { 29 | return n1 > n2 ? n1 : n2; 30 | } 31 | 32 | /** 33 | * Get height, returns 0 for NULL nodes 34 | */ 35 | int height (struct node *root) { 36 | return root ? root->height : 0; 37 | } 38 | 39 | /** 40 | * Adjusts height 41 | */ 42 | void adjust_height (struct node *root) { 43 | root->height = 1 + max(height(root->left), height(root->right)); 44 | } 45 | 46 | /** 47 | * Rotates right 48 | */ 49 | struct node *rotate_right (struct node *root) { 50 | struct node *temp = root->left; 51 | 52 | root->left = temp->right; 53 | temp->right = root; 54 | 55 | adjust_height(root); 56 | adjust_height(temp); 57 | 58 | return temp; 59 | } 60 | 61 | /** 62 | * Rotates left 63 | */ 64 | struct node *rotate_left (struct node *root) { 65 | struct node *temp = root->right; 66 | 67 | root->right = temp->left; 68 | temp->left = root; 69 | 70 | adjust_height(root); 71 | adjust_height(temp); 72 | 73 | return temp; 74 | } 75 | 76 | /** 77 | * Balances a subtree 78 | * 79 | * @param root The root node 80 | * 81 | * @return The new/updated root node 82 | */ 83 | struct node *balance (struct node *root) { 84 | adjust_height(root); 85 | 86 | if ( (height(root->left) - height(root->right)) == 2) { 87 | if ( height(root->left->right) > height(root->left->left) ) { 88 | root->left = rotate_left(root->left); 89 | } 90 | return rotate_right(root); 91 | } else if ( (height(root->right) - height(root->left)) == 2) { 92 | if ( height(root->right->left) > height(root->right->right) ) { 93 | root->right = rotate_right(root->right); 94 | } 95 | return rotate_left(root); 96 | } 97 | return root; 98 | } 99 | 100 | /** 101 | * Finds minimum node in subtree 102 | * 103 | * @param root The root of the subtree 104 | * 105 | * @return The minimum node 106 | */ 107 | struct node *find_minimum_node (struct node *root) { 108 | if (root->left != NULL) { 109 | return find_minimum_node(root->left); 110 | } else { 111 | return root; 112 | } 113 | } 114 | 115 | /** 116 | * Removes the minimum node from a subtree 117 | * This only removes from the tree, does not free the memory 118 | * 119 | * @param root The root of the subtree 120 | * 121 | * @return The root of the new subtree 122 | */ 123 | struct node *remove_minimum_node (struct node *root) { 124 | if (root->left == NULL) { 125 | return root->right; 126 | } 127 | root->left = remove_minimum_node(root->left); 128 | return balance(root); 129 | } 130 | 131 | /** 132 | * Same as insert_chunk but manages nodes 133 | */ 134 | struct node *insert_node (struct node *root, struct node *n) { 135 | if (root == NULL) { 136 | root = n; 137 | return root; 138 | } 139 | 140 | if (n->ch->ptr < root->ch->ptr) { 141 | root->left = insert_node(root->left, n); 142 | } else if ( n->ch->ptr > root->ch->ptr) { 143 | root->right = insert_node(root->right, n); 144 | } 145 | 146 | return balance(root); 147 | } 148 | 149 | /** 150 | * Same as remove_chunk but manages nodes 151 | */ 152 | struct node *remove_node (struct node *root, struct node *n) { 153 | struct node *left; 154 | struct node *right; 155 | struct node *min_node; 156 | 157 | if (n->ch->ptr < root->ch->ptr) { 158 | root->left = remove_node(root->left, n); 159 | } else if (n->ch->ptr > root->ch->ptr) { 160 | root->right = remove_node(root->right, n); 161 | } else { 162 | // Removing 'root' from its subtree 163 | left = root->left; 164 | right = root->right; 165 | libc_free(root); 166 | 167 | if (right == NULL) { 168 | return left; 169 | } 170 | 171 | min_node = find_minimum_node(right); 172 | min_node->right = remove_minimum_node(right); 173 | min_node->left = left; 174 | 175 | return balance(min_node); 176 | } 177 | 178 | return balance(root); 179 | } 180 | 181 | /** 182 | * Same as find_node but manages nods 183 | */ 184 | struct node *find_node_in_tree (struct node *root, void *ptr) { 185 | if (root == NULL) { 186 | return NULL; 187 | } 188 | 189 | if (ptr < root->ch->ptr) { 190 | return find_node_in_tree (root->left, ptr); 191 | } else if (ptr > root->ch->ptr) { 192 | return find_node_in_tree (root->right, ptr); 193 | } else { 194 | // Found node 195 | return root; 196 | } 197 | } 198 | 199 | /** 200 | * Same as check_overlap but manages nodes 201 | */ 202 | int check_overlap_in_nodes (struct node *root, struct chunk *ch) { 203 | if (root == NULL) { 204 | return 0; 205 | } 206 | 207 | if ( CHUNK_END(ch) <= CHUNK_BEGIN(root->ch) ) { 208 | return check_overlap_in_nodes(root->left, ch); 209 | } else if ( CHUNK_BEGIN(ch) >= CHUNK_END(root->ch) ) { 210 | return check_overlap_in_nodes(root->right, ch); 211 | } else { 212 | // Chunk ch overlaps with chunk root->ch!! 213 | return 1; 214 | } 215 | } 216 | 217 | // Implementing functions defined in header file 218 | 219 | void init_chunks (chunks_storage *chunks) { 220 | chunks->root = NULL; 221 | } 222 | 223 | int insert_chunk (chunks_storage *chunks, struct chunk *ch) { 224 | struct node *n; 225 | 226 | n = create_node (ch); 227 | if (n == NULL) { 228 | return 0; 229 | } 230 | 231 | if (chunks->root == NULL) { 232 | // This is the first insert operation 233 | // Tree is empty 234 | chunks->root = n; 235 | return 1; 236 | } 237 | 238 | chunks->root = insert_node(chunks->root, n); 239 | return 1; 240 | } 241 | 242 | void remove_chunk (chunks_storage *chunks, struct chunk *ch) { 243 | struct node *n; 244 | 245 | if (chunks->root == NULL) { 246 | return; 247 | } 248 | 249 | n = find_node(chunks, ch->ptr); 250 | chunks->root = remove_node(chunks->root, n); 251 | } 252 | 253 | struct node *find_node (chunks_storage *chunks, void *ptr) { 254 | return find_node_in_tree (chunks->root, ptr); 255 | } 256 | 257 | int check_overlap (chunks_storage *chunks, struct chunk *ch) { 258 | if (chunks->root == NULL) { 259 | return 0; 260 | } 261 | 262 | return check_overlap_in_nodes (chunks->root, ch); 263 | } 264 | 265 | void print_ptrs (struct node *root) { 266 | if (root == NULL) { 267 | return; 268 | } 269 | fprintf(stderr, "%p ( ", root->ch->ptr); 270 | print_ptrs(root->left); 271 | fprintf(stderr, " ) ( "); 272 | print_ptrs(root->right); 273 | fprintf(stderr, " ) "); 274 | } 275 | -------------------------------------------------------------------------------- /src/chunk.h: -------------------------------------------------------------------------------- 1 | /** 2 | * This implements functions to manage allocated chunks. Internally, they are 3 | * stored in AVL tree to allow faster lookups and inserts. 4 | * It is assumed that the chunks do not overlap within this data structure. 5 | * Therefore, special checks needed to be made while doing insertions. 6 | */ 7 | 8 | #ifndef _CHUNK_GUARD_H 9 | #define _CHUNK_GUARD_H 10 | 11 | // Forward declaring chunk for canary 12 | struct chunk; 13 | 14 | #include "canary.h" 15 | 16 | #include 17 | 18 | /** 19 | * The chunk representation 20 | * The ptr here points to the begining of the chunk (size in malloc_chunk) 21 | * Canary is put after requested_size instead of allocated_size to detect 22 | * more errors. The size of the chunk requested is automatically adjusted 23 | * to add space for the two canaries. 24 | */ 25 | struct chunk { 26 | void *ptr; 27 | size_t requested_size; 28 | size_t allocated_size; 29 | canary begin_guard; 30 | canary end_guard; // At the end of requested size 31 | }; 32 | 33 | // The node representation 34 | struct node { 35 | struct chunk *ch; 36 | int height; 37 | struct node *left; 38 | struct node *right; 39 | }; 40 | 41 | // Another name for a root node 42 | // During balancing, the AVL tree's root will change 43 | // Hence, a double pointer 44 | typedef struct { 45 | struct node *root; 46 | } chunks_storage; 47 | 48 | #define CHUNK_BEGIN(ch) (ch->ptr) 49 | #define CHUNK_END(ch) ((void *)((char *)(ch->ptr) + ch->allocated_size)) 50 | 51 | /** 52 | * Initializes storage 53 | * 54 | * @param chunks The storage object 55 | */ 56 | void init_chunks (chunks_storage *chunks); 57 | 58 | /** 59 | * Inserts a new chunk 60 | * 61 | * @param chunks The storage object 62 | * @param ch The chunk to be inserted 63 | * 64 | * @return 1 if insert is successful 65 | */ 66 | int insert_chunk (chunks_storage *chunks, struct chunk *ch); 67 | 68 | /** 69 | * Removes a chunk 70 | * Assumes that the chunk is already in storage 71 | * The caller needs to check for this 72 | * 73 | * @param chunks The storage object 74 | * @param ch The chunk to be removed 75 | */ 76 | void remove_chunk (chunks_storage *chunks, struct chunk *ch); 77 | 78 | /** 79 | * Retrieves a node from the storage corresponding to a pointer 80 | * The pointer has to be at the start of the node 81 | * 82 | * @param chunks The storage object 83 | * @param ptr The pointer to be searched for 84 | * 85 | * @return The node containing that pointer 86 | */ 87 | struct node *find_node (chunks_storage *chunks, void *ptr); 88 | 89 | /** 90 | * Checks whether memory area is overlapping with existing chunks or not 91 | * 92 | * @param chunks The storage object 93 | * @param ch The chunk that needs to be checked for overlap 94 | * 95 | * @return 1 if it is overlapping 96 | */ 97 | int check_overlap (chunks_storage *chunks, struct chunk *ch); 98 | 99 | /** 100 | * Debug function used to print contents of AVL tree in 'in order traversal' 101 | * This function is not even used in DEBUG mode. This is only for testing 102 | * purposes. 103 | * 104 | * @param root The root of the tree 105 | */ 106 | void print_ptrs (struct node *root); 107 | 108 | #endif 109 | -------------------------------------------------------------------------------- /src/config.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | /** 5 | * Returns LIBDHEAP_DEBUG 6 | */ 7 | int is_libdheap_debug (void) { 8 | char *debug; 9 | debug = getenv("LIBDHEAP_DEBUG"); 10 | if (debug && !strcmp(debug, "1")) { 11 | return 1; 12 | } else { 13 | return 0; 14 | } 15 | } 16 | 17 | /** 18 | * Returns LIBDHEAP_EXIT_ON_ERROR 19 | */ 20 | int is_libdheap_exit_on_error (void) { 21 | char *exit_on_error; 22 | exit_on_error = getenv("LIBDHEAP_EXIT_ON_ERROR"); 23 | if (exit_on_error && !strcmp(exit_on_error, "1")) { 24 | return 1; 25 | } else { 26 | return 0; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | /** 2 | * This header file manages the configuration of libdheap 3 | */ 4 | 5 | #ifndef _CONFIG_GUARD_H 6 | #define _CONFIG_GUARD_H 7 | 8 | /** 9 | * Configurations 10 | * These can be configured by setting the corresponding 11 | * environment variable. 12 | * 13 | * Whether to output debugging statements or not (by default 0) 14 | * LIBDHEAP_DEBUG = 0/1 15 | * 16 | * Whether to crash the program (exit) on finding a memory corruption 17 | * or not (by default 0) 18 | * LIBDHEAP_EXIT_ON_ERROR 19 | */ 20 | 21 | /** 22 | * Returns LIBDHEAP_DEBUG 23 | */ 24 | int is_libdheap_debug (void); 25 | 26 | /** 27 | * Returns LIBDHEAP_EXIT_ON_ERROR 28 | */ 29 | int is_libdheap_exit_on_error (void); 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /src/display.c: -------------------------------------------------------------------------------- 1 | #include "display.h" 2 | #include "config.h" 3 | 4 | #include 5 | #include 6 | 7 | void display_error (const char *msg, ...) { 8 | va_list argptr; 9 | va_start(argptr, msg); 10 | fprintf(stderr, "[LIBDHEAP ERROR] : "); 11 | vfprintf(stderr, msg, argptr); 12 | va_end(argptr); 13 | fprintf(stderr, "\n"); 14 | } 15 | 16 | void display_log (FILE *stream, const char *log, ...) { 17 | va_list argptr; 18 | va_start(argptr, log); 19 | fprintf(stream, "[LIBDHEAP LOG] : "); 20 | vfprintf(stream, log, argptr); 21 | va_end(argptr); 22 | fprintf(stream, "\n"); 23 | } 24 | 25 | void display_debug (FILE *stream, const char *msg, ...) { 26 | va_list argptr; 27 | 28 | if (!is_libdheap_debug()) 29 | return; 30 | 31 | va_start(argptr, msg); 32 | fprintf(stream, "[LIBDHEAP DEBUG] : "); 33 | vfprintf(stream, msg, argptr); 34 | va_end(argptr); 35 | fprintf(stream, "\n"); 36 | } 37 | -------------------------------------------------------------------------------- /src/display.h: -------------------------------------------------------------------------------- 1 | /** 2 | * This handles printing messages to various streams 3 | */ 4 | #ifndef _DISPLAY_GUARD_H 5 | #define _DISPLAY_GUARD_H 6 | 7 | #include 8 | 9 | /** 10 | * Displays error messages 11 | * 12 | * @param msg The error message 13 | */ 14 | void display_error (const char *msg, ...); 15 | 16 | /** 17 | * Outputs log to a particular stream 18 | * 19 | * @param stream The output file stream 20 | * @param log The log message 21 | */ 22 | void display_log (FILE *stream, const char *log, ...); 23 | 24 | /** 25 | * Outputs debug statements to a particular stream 26 | * 27 | * @param stream The output file stream 28 | * @param msh The debug message 29 | */ 30 | void display_debug (FILE *stream, const char *msg, ...); 31 | 32 | #endif -------------------------------------------------------------------------------- /src/libc_malloc.c: -------------------------------------------------------------------------------- 1 | #include "libc_malloc.h" 2 | 3 | #include 4 | 5 | extern void *__libc_malloc (size_t size); 6 | extern void *__libc_free (void *ptr); 7 | 8 | void *libc_malloc (size_t size) { 9 | return __libc_malloc(size); 10 | } 11 | 12 | void libc_free (void *ptr) { 13 | __libc_free(ptr); 14 | } 15 | 16 | int libc_execve (const char *filename, char *const argv[], char *const envp[]) { 17 | int (*real_execve) (const char *, char *const [], char *const []); 18 | real_execve = dlsym(RTLD_NEXT, "execve"); 19 | return real_execve(filename, argv, envp); 20 | } 21 | -------------------------------------------------------------------------------- /src/libc_malloc.h: -------------------------------------------------------------------------------- 1 | /** 2 | * This implements an interface for the original memory allocator functions. 3 | * Internally, it uses __libc_malloc and __libc_free. 'dylsm' causes issues 4 | * because it internally uses calloc. 5 | * 6 | * This also implements an interface for execve to propagate libdheap 7 | * environment variable further. 8 | */ 9 | 10 | #ifndef _LIBC_MALLOC_GUARD_H 11 | #define _LIBC_MALLOC_GUARD_H 12 | 13 | #define _GNU_SOURCE 14 | 15 | #include 16 | 17 | void *libc_malloc (size_t size); 18 | 19 | void libc_free (void *ptr); 20 | 21 | int libc_execve (const char *filename, char *const argv[], char *const envp[]); 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /src/libdheap.c: -------------------------------------------------------------------------------- 1 | #include "libdheap.h" 2 | #include "backtrace.h" 3 | #include "libc_malloc.h" 4 | #include "chunk.h" 5 | #include "canary.h" 6 | #include "display.h" 7 | #include "config.h" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #define BACKTRACE_LEN 10 14 | 15 | // The storage arena for all allocated chunks 16 | chunks_storage chunks; 17 | // Whether the initialization is done or not. Needed because 18 | // initialize it called many times (mostly unpredictable) 19 | static int libdheap_initialized = 0; 20 | // FILE stream pointer for logging 21 | FILE *libdheap_log; 22 | // Mutex lock 23 | pthread_mutex_t libdheap_mutex = PTHREAD_MUTEX_INITIALIZER; 24 | 25 | void initialize (void) { 26 | init_chunks(&chunks); 27 | libdheap_log = stderr; 28 | libdheap_initialized = 1; 29 | } 30 | 31 | void *malloc (size_t size) { 32 | void *ptr; 33 | struct chunk *ch; 34 | void *backtrace[BACKTRACE_LEN]; 35 | 36 | /** 37 | * Initialization 38 | */ 39 | if (!libdheap_initialized) { 40 | initialize(); 41 | } 42 | 43 | display_debug(libdheap_log, "malloc(%d)", (int)size); 44 | 45 | pthread_mutex_lock(&libdheap_mutex); 46 | // Adjust size for canary 47 | size = get_padded_size(size); 48 | ptr = libc_malloc(size); 49 | display_debug(libdheap_log, 50 | "Allocating chunk: %p %p", 51 | (ptr + sizeof(canary)), &(chunks.root)); 52 | if (ptr == NULL) { 53 | // Memory full 54 | display_log(libdheap_log, 55 | "No memory available for chunk, returning NULL"); 56 | pthread_mutex_unlock(&libdheap_mutex); 57 | return NULL; 58 | } 59 | 60 | ch = libc_malloc(sizeof(struct chunk)); 61 | if (ch == NULL) { 62 | // Memory full 63 | display_log(libdheap_log, 64 | "No memory available for chunk structure, returning NULL"); 65 | libc_free(ptr); 66 | pthread_mutex_unlock(&libdheap_mutex); 67 | return NULL; 68 | } 69 | ch->ptr = (struct chunk *)(ptr - sizeof(size_t)); // To accomodate size 70 | ch->requested_size = size; // Includes padding for canary 71 | ch->allocated_size = *((size_t *)ch->ptr) & ~(0x1 | 0x2 | 0x4); 72 | guard_chunk(ch); 73 | 74 | // Check for overlap 75 | if (check_overlap(&chunks, ch)) { 76 | // Memory corruption detected 77 | display_log(libdheap_log, 78 | "Chunk returned by malloc overlaps with existing chunks!"); 79 | set_backtrace(backtrace, 1, BACKTRACE_LEN); 80 | print_backtrace(backtrace, BACKTRACE_LEN); 81 | if (is_libdheap_exit_on_error()) { 82 | exit(1); 83 | } else { 84 | pthread_mutex_unlock(&libdheap_mutex); 85 | return NULL; 86 | } 87 | } 88 | if (!insert_chunk(&chunks, ch)) { 89 | display_log(libdheap_log, 90 | "Couldn't insert in storage, returning NULL"); 91 | libc_free(ptr); 92 | libc_free(ch); 93 | pthread_mutex_unlock(&libdheap_mutex); 94 | return NULL; 95 | } 96 | 97 | pthread_mutex_unlock(&libdheap_mutex); 98 | return (ptr + sizeof(canary)); 99 | } 100 | 101 | void free (void *ptr) { 102 | void *ch_ptr; 103 | struct node *n; 104 | struct chunk *ch; 105 | void *backtrace[BACKTRACE_LEN]; 106 | 107 | /** 108 | * Initialization 109 | */ 110 | if (!libdheap_initialized) { 111 | initialize(); 112 | } 113 | 114 | display_debug(libdheap_log, "free(%p)", ptr); 115 | if (ptr == NULL) { 116 | return; 117 | } 118 | display_debug(libdheap_log, "Freeing chunk: %p", ptr); 119 | pthread_mutex_lock(&libdheap_mutex); 120 | 121 | ptr -= sizeof(canary); 122 | ch_ptr = ptr - sizeof(size_t); // To accomodate size 123 | n = find_node(&chunks, ch_ptr); 124 | if (n == NULL) { 125 | // Freeing invalid chunk 126 | // Possibly a double free 127 | display_log(libdheap_log, "Freeing non allocated chunk!"); 128 | set_backtrace(backtrace, 1, BACKTRACE_LEN); 129 | print_backtrace(backtrace, BACKTRACE_LEN); 130 | if (is_libdheap_exit_on_error()) { 131 | exit(1); 132 | } else { 133 | pthread_mutex_unlock(&libdheap_mutex); 134 | return; 135 | } 136 | } 137 | 138 | ch = n->ch; 139 | // Check heap canary 140 | if (!check_canary(ch)) { 141 | // Canary modified 142 | display_log(libdheap_log, "Inconsistent heap canary!"); 143 | set_backtrace(backtrace, 1, BACKTRACE_LEN); 144 | print_backtrace(backtrace, BACKTRACE_LEN); 145 | if (is_libdheap_exit_on_error()) { 146 | exit(1); 147 | } else { 148 | pthread_mutex_unlock(&libdheap_mutex); 149 | return; 150 | } 151 | } 152 | 153 | remove_chunk(&chunks, ch); 154 | 155 | // Freeing storage related chunks 156 | libc_free(ch); 157 | libc_free(ptr); 158 | pthread_mutex_unlock(&libdheap_mutex); 159 | } 160 | 161 | void *calloc (size_t nmemb, size_t size) { 162 | void *ptr = malloc(nmemb * size); 163 | if (ptr == NULL) { 164 | return NULL; 165 | } 166 | memset(ptr, 0, nmemb * size); 167 | return ptr; 168 | } 169 | 170 | void *realloc (void *ptr, size_t size) { 171 | void *new_ptr; 172 | size_t ptr_size; 173 | size_t min_size; 174 | 175 | display_debug(libdheap_log, "realloc(%p, %d)", ptr, (int)size); 176 | 177 | if (ptr == NULL) { 178 | return malloc(size); 179 | } 180 | 181 | if (size == 0) { 182 | free(ptr); 183 | return NULL; 184 | } 185 | 186 | new_ptr = malloc(size); 187 | 188 | if (new_ptr != NULL) { 189 | ptr_size = malloc_usable_size(ptr); 190 | min_size = ptr_size > size ? size : ptr_size; 191 | memcpy(new_ptr, ptr, min_size); 192 | } 193 | 194 | free(ptr); 195 | return new_ptr; 196 | } 197 | 198 | size_t malloc_usable_size (void *ptr) { 199 | // For our purposes, usable size = requested size 200 | struct node *n; 201 | void *backtrace[BACKTRACE_LEN]; 202 | 203 | /** 204 | * Initialization 205 | */ 206 | if (!libdheap_initialized) { 207 | initialize(); 208 | } 209 | 210 | display_debug(libdheap_log, "malloc_usable_size(%p)", ptr); 211 | 212 | ptr -= sizeof(canary) + sizeof(size_t); 213 | n = find_node(&chunks, ptr); 214 | if (n == NULL) { 215 | display_log(libdheap_log, 216 | "Invalid chunk passed to malloc_usable_size! %p", 217 | &(chunks.root)); 218 | set_backtrace(backtrace, 1, BACKTRACE_LEN); 219 | print_backtrace(backtrace, BACKTRACE_LEN); 220 | if (is_libdheap_exit_on_error()) { 221 | exit(1); 222 | } else { 223 | return 0; 224 | } 225 | } 226 | 227 | return n->ch->requested_size; 228 | } 229 | 230 | int execve (const char *filename, char *const argv[], char *const envp[]) { 231 | char *libdheap_path, *libdheap_env; 232 | int len_env, i, libdheap_path_len; 233 | char **new_envp; 234 | 235 | /** 236 | * Initialization 237 | */ 238 | if (!libdheap_initialized) { 239 | initialize(); 240 | } 241 | 242 | display_log(libdheap_log, "execve(%s,...)", filename); 243 | if (envp == 0) { 244 | len_env = 0; 245 | } else { 246 | for (len_env = 0;envp[len_env]!=0;len_env++); 247 | } 248 | 249 | new_envp = (char **)malloc(sizeof(char *)*(len_env+2)); 250 | if (!new_envp) { 251 | display_log(libdheap_log, "Unable to allocate memory for new_envp"); 252 | exit(1); 253 | } 254 | 255 | libdheap_path = getenv("LD_PRELOAD"); 256 | libdheap_path_len = strlen(libdheap_path); 257 | if (!libdheap_path) { 258 | display_log(libdheap_log, "No LD_PRELOAD environment variable present"); 259 | exit(1); 260 | } 261 | 262 | libdheap_env = malloc(strlen("LD_PRELOAD=") + libdheap_path_len + 1); 263 | if (!libdheap_env) { 264 | display_log(libdheap_log, "Unable to allocate memory for libdheap_env\n"); 265 | exit(1); 266 | } 267 | 268 | sprintf(libdheap_env, "LD_PRELOAD=%s", libdheap_path); 269 | new_envp[0] = libdheap_env; 270 | for (i=0;i 5 | 6 | /** 7 | * Initializer function 8 | */ 9 | void initialize (void); 10 | 11 | // Redefining wrappers over memory management functions 12 | // ANSI functions 13 | void *malloc (size_t size); 14 | void free (void *ptr); 15 | void *calloc (size_t nmemb, size_t size); 16 | void *realloc (void *ptr, size_t size); 17 | 18 | // Redefining additional functions 19 | size_t malloc_usable_size (void *ptr); 20 | 21 | // Redefining execve to propagate libdheap env variable 22 | int execve (const char *filename, char *const argv[], char *const envp[]); 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /tests/buffer_overflow.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Test file 3 | * Attempts buffer overflow 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | int main() { 10 | char *ptr = malloc(10); 11 | strcpy(ptr, "A big string"); 12 | free(ptr); 13 | return 0; 14 | } 15 | -------------------------------------------------------------------------------- /tests/buffer_underflow.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Test file 3 | * Attempts buffer underflow 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | struct chunk_struct { 10 | size_t prev_size; 11 | size_t size; 12 | char buf[10]; 13 | }; 14 | 15 | int main() { 16 | struct chunk_struct *fake_chunk; 17 | size_t *ptr = malloc(100); 18 | ptr[-1] = 0x51; 19 | 20 | // This bypasses "free(): invalid next size (fast)" security check 21 | // of glibc. 22 | fake_chunk = (struct chunk_struct *)((char *)(ptr-2) + 0x51); 23 | fake_chunk->size = 0x51; 24 | 25 | free(ptr); 26 | return 0; 27 | } 28 | -------------------------------------------------------------------------------- /tests/child.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Test file 3 | * Spawns a child and attempts a double free 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | int main() { 11 | int pid; 12 | int wstatus; 13 | char *p1, *p2; 14 | 15 | pid = fork(); 16 | if (pid == 0) { 17 | // Child 18 | p1 = malloc(20); 19 | p2 = malloc(20); 20 | free(p1); 21 | free(p2); 22 | free(p1); 23 | return 0; 24 | } 25 | // Parent 26 | waitpid(pid, &wstatus, 0); 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /tests/double_free.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Test file 3 | * Tries to attempt a double free 4 | */ 5 | 6 | #include 7 | 8 | int main() { 9 | void *ptr[100]; 10 | int i; 11 | 12 | for (i = 0;i<100;i++) { 13 | ptr[i] = malloc(45); 14 | } 15 | 16 | for (i = 0;i<100;i++) { 17 | free(ptr[i]); 18 | } 19 | 20 | free(ptr[56]); 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /tests/execve.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Test file 3 | * Executes a vulnerable double free program using execve 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #define VULN_PROG "./double_free" 12 | 13 | int main() { 14 | char *argv[2]; 15 | argv[0] = VULN_PROG; 16 | argv[1] = 0; 17 | int err = execve(VULN_PROG, argv, 0); 18 | if (err == -1) { 19 | fprintf(stderr, "Error: %s\n", strerror(errno)); 20 | } 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /tests/system.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Test file 3 | * Executes a vulnerable double free program using system 4 | */ 5 | 6 | #include 7 | 8 | #define VULN_PROG "./double_free" 9 | 10 | int main() { 11 | system(VULN_PROG); 12 | return 0; 13 | } 14 | -------------------------------------------------------------------------------- /tests/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | void *ptr[1000]; 6 | int i = 0; 7 | for (;i<1000;i++) { 8 | ptr[i] = malloc(10); 9 | } 10 | for(i=0;i<500;i++) { 11 | free(ptr[i]); 12 | } 13 | for(i=0;i<500;i++) { 14 | ptr[i] = malloc(20); 15 | } 16 | for(i=0;i<1000;i++) { 17 | free(ptr[i]); 18 | } 19 | return 0; 20 | } 21 | --------------------------------------------------------------------------------