Heapusage

From Embedded Lab Vienna for IoT & Security
Revision as of 21:35, 16 January 2024 by TKoenig (talk | contribs) (Alternatives added)
Jump to navigation Jump to search

Summary

This documentation describes the usage of heapusage developed by Kristofer Berggren (d99kris) (https://github.com/d99kris/heapusage). With practical examples it is shown how this tool can be used to find memory leaks, double free, use after free and overflows.

Requirements

According to the GitHub documentation, heapusage can be used on Linux and macOS and has been tested on these operating systems:

  • macOS Big Sur 11.0
  • Ubuntu 20.04 LTS

Alternatives

According to the GitHub documentation, heapusage can be used on Linux and macOS and has been tested on these operating systems:

  • macOS Big Sur 11.0
  • Ubuntu 20.04 LTS


During creation of this documentation it was also successfully tested on:

  • Ubuntu 22.04.3 LTS

Installation

Step 1 - Pre-requisites

To use heapusage on Ubuntu, the following pre-requisites need to be installed:

sudo apt install git cmake build-essential

To show the source filenames and line-numbers in the call stacks, another pre-requisite can be installed optionally:

sudo apt install binutils-dev

Step 2 - Installing heapusage

To install heapusage simply clone the GitHub repository and build it:

git clone https://github.com/d99kris/heapusage
cd heapusage
mkdir -p build
cd build
cmake ..
make -s

Optionally, you can install it in the system:

sudo make install

If it is installed in the system, you can simply run it using heapusage. More information about using heapusage can be found in the Usage section.

Usage

General

Heapusage can be used as follows:

heapusage [-d] [-m minsize] [-n] [-o path] [-t tools] PROG [ARGS..]

This parameters can be used:

  • -d (optional) enables the debug mode.
  • -m <minsize> (optional) sets the minimum allocation size heapusage is enabled for. For example, using heapusage -t leak -m 50./app only shows memory leaks larger than or equal to 50 bytes.
  • -n (optional) disables symbol lookup. This leads to a faster execution time, however less information is shown in the output. You can still see for example how many bytes are leaked, but no longer in which file/function.
  • -o <path> (optional) sets an output file to write the output to, instead of stderr.
  • -t <tools> (optional) sets which tools should be used for the analysis. If not specified, the tool leak is used per default. The possible tools can be found below.
  • PROG sets the program to be analysed.
  • ARGS... (optional) are the parameters for the analysed program (PROG).

Furthermore, heapusage can be run using

heapusage --help
heapusage --version

to show an overview of the usage or the installed version.

Tools

As mentioned above, using the -t <tools> parameters, the tools to be used can be specified. The following tools can be used:

  • leak (default) detects memory allocations which are never free'd.
  • double-free detects when an already free'd buffer is free'd again.
  • use-after-free detects when an already free'd buffer is used afterwards.
  • overflow detects buffer overflows, like when an address beyond the allocated memory is accessed.
  • all enables all tools.

Multiple tools can be combined by separating them with a comma, like -t leak,double-free.

-t all is therefore the same as -t leak,double-free,use-after-free,overflow.

Examples

In the following, a simple example for each of the tools is shown. Each of these examples is saved as .c-file and compiled using gcc <filename>.c -o <output-filename>.

leak

ex1-leak.c:

#include <stdlib.h>

void* getPointer(int size) {
    return malloc(size);
}

int main() {
    void* p = getPointer(1337);
    p = getPointer(42);
    free(p);
    return 0;
}

After compiling this code it is executed using heapusage: heapusage -t leak ./ex1 This leads to the following output:

==6367== Heapusage - https://github.com/d99kris/heapusage
==6367== 
==6367== HEAP SUMMARY:
==6367==     in use at exit: 1337 bytes in 1 blocks
==6367==   total heap usage: 2 allocs, 79 frees, 1379 bytes allocated
==6367==    peak heap usage: 1379 bytes allocated
==6367== 
==6367== 1337 bytes in 1 block(s) are lost, originally allocated at:
==6367==    at 0x00007fc5942aee5c: malloc (humain.cpp:175)
==6367==    at 0x000055c4d3b9d185: getPointer
==6367==    at 0x000055c4d3b9d19d: main
==6367==    at 0x00007fc594029d90: __libc_start_call_main (libc_start_call_main.h:58)
==6367==    at 0x00007fc594029e40: __libc_start_main (libc-start.c:128)
==6367==    at 0x000055c4d3b9d0a5: _start
==6367== 
==6367== LEAK SUMMARY:
==6367==    definitely lost: 1337 bytes in 1 blocks
==6367==

It can be seen, that the second pointer initialization of 42 bytes which is free'd again is not shown here, whereas the 1337 bytes are shown as memory leak, since the are allocated and never free'd.

double-free

ex2-doubleFree.c:

#include <stdlib.h>

void* getPointer(int size) {
     return malloc(size);
}

void free1(void* p) {
     free(p);
}

void free2(void* p) {
     free(p);
}

int main() {
    void* p = getPointer(1337);
    free1(p);
    free2(p);
    return 0;
}

The similar functions free1 and free2 are used for a better demonstration of the heapusage output.

heapusage -t double-free ./ex2:

==6438== Heapusage - https://github.com/d99kris/heapusage
==6438== 
==6438== Invalid deallocation at:
==6438==    at 0x00007f16a3399f4f: free (humain.cpp:188)
==6438==    at 0x00005648bfc3f1c2: free2
==6438==    at 0x00005648bfc3f1f7: main
==6438==    at 0x00007f16a3029d90: __libc_start_call_main (libc_start_call_main.h:58)
==6438==    at 0x00007f16a3029e40: __libc_start_main (libc-start.c:128)
==6438==    at 0x00005648bfc3f0a5: _start
==6438==  Address 0x5648c0f6a190 is a block of size 1337 free'd at:
==6438==    at 0x00007f16a3399f4f: free (humain.cpp:188)
==6438==    at 0x00005648bfc3f1a3: free1
==6438==    at 0x00005648bfc3f1eb: main
==6438==    at 0x00007f16a3029d90: __libc_start_call_main (libc_start_call_main.h:58)
==6438==    at 0x00007f16a3029e40: __libc_start_main (libc-start.c:128)
==6438==    at 0x00005648bfc3f0a5: _start
==6438==  Block was alloc'd at:
==6438==    at 0x00007f16a3399e5c: malloc (humain.cpp:175)
==6438==    at 0x00005648bfc3f185: getPointer
==6438==    at 0x00005648bfc3f1db: main
==6438==    at 0x00007f16a3029d90: __libc_start_call_main (libc_start_call_main.h:58)
==6438==    at 0x00007f16a3029e40: __libc_start_main (libc-start.c:128)
==6438==    at 0x00005648bfc3f0a5: _start
==6438== 
==6438== HEAP SUMMARY:
==6438==     in use at exit: 0 bytes in 0 blocks
==6438==   total heap usage: 1 allocs, 80 frees, 1337 bytes allocated
==6438==    peak heap usage: 1337 bytes allocated
==6438== 
==6438== LEAK SUMMARY:
==6438==    definitely lost: 0 bytes in 0 blocks
==6438== 

Here it shows an invalid deallocation in the free2 function and the reason for that, because the block was already free'd in the free1 function.

use-after-free

ex3-userAfterFree.c:

#include <stdlib.h>

int* getIntPointer() {
    return (int*)malloc(sizeof(int));
}

void free1(int* p) {
    free(p);
}

void setPointerValue(int* p, int value){
    *p = value;
}

int main() {
    int *p = getIntPointer();
    free1(p);
    setPointerValue(p, 42);
    return 0;
}

heapusage -t use-after-free ./ex3:

==6458== Heapusage - https://github.com/d99kris/heapusage
==6458== 
==6458== Invalid memory access at:
==6458==    at 0x00007fe4fb042520: __restore_rt (libc_sigaction.c:0)
==6458==    at 0x0000559a8cbc71b2: setPointerValue
==6458==    at 0x0000559a8cbc71ee: main
==6458==    at 0x00007fe4fb029d90: __libc_start_call_main (libc_start_call_main.h:58)
==6458==    at 0x00007fe4fb029e40: __libc_start_main (libc-start.c:128)
==6458==    at 0x0000559a8cbc70a5: _start
==6458==  Address 0x559a8d19b000 is 0 bytes inside a block of size 4 free'd at:
==6458==    at 0x00007fe4fb3e9f4f: free (humain.cpp:188)
==6458==    at 0x0000559a8cbc7199: free1
==6458==    at 0x0000559a8cbc71dd: main
==6458==    at 0x00007fe4fb029d90: __libc_start_call_main (libc_start_call_main.h:58)
==6458==    at 0x00007fe4fb029e40: __libc_start_main (libc-start.c:128)
==6458==    at 0x0000559a8cbc70a5: _start
==6458==  Block was alloc'd at:
==6458==    at 0x00007fe4fb3e9e5c: malloc (humain.cpp:175)
==6458==    at 0x0000559a8cbc717b: getIntPointer
==6458==    at 0x0000559a8cbc71cd: main
==6458==    at 0x00007fe4fb029d90: __libc_start_call_main (libc_start_call_main.h:58)
==6458==    at 0x00007fe4fb029e40: __libc_start_main (libc-start.c:128)
==6458==    at 0x0000559a8cbc70a5: _start
==6458== 
==6458== HEAP SUMMARY:
==6458==     in use at exit: 0 bytes in 0 blocks
==6458==   total heap usage: 1 allocs, 79 frees, 4 bytes allocated
==6458==    peak heap usage: 4 bytes allocated
==6458== 
==6458== LEAK SUMMARY:
==6458==    definitely lost: 0 bytes in 0 blocks
==6458==

The output looks similar to that of double-free, however in this case it now shows an invalid memory access instead of invalid deallocation. As before, it still shows where the block was free'd before and where it was allocated in the first place.

overflow

ex4-overflow.c:

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

char* getCharBuf(int size) {
    return (char*)malloc(size);
}

void setBufferText(char* buf, char* text) {
    strcpy(buf, text);
}

int main() {
    char* buf = getCharBuf(16);
    setBufferText(buf, "0123456789ABCDEF0123456789");
    return 0;
}

heapusage -t overflow ./ex4:

==6519== Heapusage - https://github.com/d99kris/heapusage
==6519== 
==6519== Invalid memory access at:
==6519==    at 0x00007f978f842520: __restore_rt (libc_sigaction.c:0)
==6519==    at 0x00007f978f98b423: __strcpy_ssse3 (strcpy-ssse3.S:2568)
==6519==    at 0x000055717f4101ae: setBufferText
==6519==    at 0x000055717f4101e1: main
==6519==    at 0x00007f978f829d90: __libc_start_call_main (libc_start_call_main.h:58)
==6519==    at 0x00007f978f829e40: __libc_start_main (libc-start.c:128)
==6519==    at 0x000055717f4100a5: _start
==6519==  Address 0x55717fb4c000 is 0 bytes after a block of size 16 alloc'd at:
==6519==    at 0x00007f978fb7ee5c: malloc (humain.cpp:175)
==6519==    at 0x000055717f410185: getCharBuf
==6519==    at 0x000055717f4101c7: main
==6519==    at 0x00007f978f829d90: __libc_start_call_main (libc_start_call_main.h:58)
==6519==    at 0x00007f978f829e40: __libc_start_main (libc-start.c:128)
==6519==    at 0x000055717f4100a5: _start
==6519== 
==6519== HEAP SUMMARY:
==6519==     in use at exit: 16 bytes in 1 blocks
==6519==   total heap usage: 1 allocs, 78 frees, 16 bytes allocated
==6519==    peak heap usage: 16 bytes allocated
==6519== 
==6519== LEAK SUMMARY:
==6519==    definitely lost: 16 bytes in 1 blocks
==6519==

This output shows an invalid memory access, however, as opposed to before, this time it shows the error because is 0 bytes after a block of size 16. Meaning the code tries to access the memory outside of the allocated memory range.

References