CS 208 s22 — Dynamic Memory Allocation: malloc and free

Table of Contents

1 Starting Lab 3

Like lab 2, your goal is to analyze compile programs in order to devise the right input strings. In this case, the compiled programs have buffer overflow vulnerabilites. The string you input can overflow a char array on the stack and overwrite the return address, as well as writing other data to the stack as necessary for the attack.

1.1 Getting a target

Go to http://awb66333.mathcs.carleton.edu:15513/, enter your Carleton username and email, download the tar file. This lab must be done on Linux, and working on mantis is probably the easiest option. Connect to mantis and upload the tar file. Extract it using the terminal.

1.2 Phases

The target tar file includes two vulnerable programs ctarget and rtarget. You can analyze the assembly code for these using objdump and gdb, though the only relevant function is getbuf which contains the buffer overflow vulnerability you will exploit. The real work of the lab is thinking carefully about how the stack and ret instructions work, and designing the right input strings.

The lab consists of five phases, three for ctarget and two for rtarget. The first (ctarget phase 1) is a simple stack-smashing attack (i.e., just overwrite the return address to execute the desired function). The second and third (ctarget phases 2 and 3) require code injection (i.e., putting your own code on the stack via buffer overflow and causing it to execute). The final two (rtarget phases 4 and 5) involve a technique called return-oriented programming (see the writeup for a description). Progress will be automatically recorded, like lab 2 (requires a campus-internet connection). See http://awb66333.mathcs.carleton.edu:15513/progress for live progress, run ctarget and rtarget with -q to work offline.

1.3 Tools

1.3.1 hex2raw

You will want to write specific hex values to the stack. Your input, however, is an ASCII string. If you want to write the bytes 40, 51, and 2b to the stack, your input will need to include '@', 'Q', and '+'. The tool hex2raw has been provided to do this conversion for you. You put the hex values you want to be written to the buffer in a text file (e.g., type 40 51 2b into ctarget.phase1), and then run

cat ctarget.phase1 | ./hex2raw | ./ctarget

to convert the hex values into the corresponding ASCII and use that as input to ctarget. See the lab writeup for more information.

1.3.2 Generating machine code

For ctarget levels 2 and 3 you will need to write the hex values for specific assembly instructions to the stack. See the lab writeup for how to generate these hex values from a file with hand-written assembly.

2 Introduction

// static global data, size fixed at compile time, exists for the lifetime of the program
int array[1024]; 
void foo(int n) {
    // stack-allocated data, known lifetime (deallocated on return)
    int tmp;
    int local_array[n]; // some versions of C allow dynamically-sized stack allocation

    // dynamic (heap) data, size and lifetime known only at runtime
    int* dyn = (int*) malloc(n * sizeof(int));
    // good practices:
    //    sizeof makes code more portable
    //    void* is implicitly cast into any pointer type; explicit typecast will help you
    //      catch coding errors when pointer types don’t match
}

Two big questions we'll focus on for the next two weeks:

  1. how do we manage the scarce resource of physical memory while providing all processes as much virtual memory as they need?
    • implemented by the operating system kernel and hardware
  2. how do we handle malloc and free quickly and efficiently?
    • implemented by the C library

Lab 4 will task you with doing 2., so we will start there.

3 Dynamic Memory Allocation

We don't always know how much space we will need for certain data structures, etc. ahead of time. Hardcoded sizes everywhere can lead to problems and become a maintenance nightmare. Hence, we wany the ability to allocate memory as we go along (dynamically)

3.1 Type of Allocators

  • Allocator organizes heap as a collection of variable-sized blocks, which are either allocated or free
  • explicit allocators: require manual requests and frees (malloc package in C)
  • implicit allocators: unused blocks are automatically detected and freed (garbage collection)

3.1.1 malloc package

  • malloc does no initialization, use calloc to request zero-initialized memory
    • returns pointer to the beginning of allocated block, NULL indicates failed request
    • typically 16-byte aligned on x86-64
  • change the size of previously allocated block with realloc
  • underneath, allocation can use mmap and munmap or use the void *sbrk(intptr_t incr) function
    • sbrk grows or shrinks the heap by adding incr to the kernel's brk pointer, returns old value of brk

heapmap.png

  • free takes a pointer to the beginning of a block previously allocated by malloc, calloc, or realloc
    • marks that memory as free and available for future allocations
    • behavior undefined on other arguments, no indication of error

3.3 Allocator Goals and Requirements

3.3.1 Requirements

  • handling arbitrary request sequences: cannot make any assumptions about the sequencing of allocate and free requests
  • making immediate responses to requests: no reordering or batching of requests to improve performance
  • using only the heap: allocator's internal data structures must also be stored on the heap
  • aligning blocks: blocks need to meet alignment requirements for any type of data they might hold
  • not modifying allocated blocks: cannot modify or move blocks when they are allocated

3.3.2 Goals

  1. maximizing throughput: maximize requests per second
  2. maximizing memory utilization: use the greatest possible fraction of the space allocated for the heap. Peak utilization metric is the maximum such fraction achieved over the course of \(n\) requests.
  3. these are in tension: respond to a request faster vs respond with a better choice of block

3.4 Fragmenataion

  • internal fragmentation: allocated block is larger than the amount requested (due to minimum block size or alignment requirements)

vm_internal_frag.png

  • external fragmentation: when allocation/free pattern leaves "holes" between blocks
    • symptom: there is enough total free memory to satisfy a request, but no single free block is large enough
    • example: if final request on spreadsheet example were for 48 bytes
    • difficult to quantify as it depends on future requests

3.5 Implementation Issues

  • free block organization: how to keep track of them
  • placement: which free block should we choose to fulfill a request
  • splitting: what do we do with the remainder of a free block after part of it is allocated
  • coalescing: what do we do when a block is freed

4 Quick Checks

Which of the following is NOT a source of internal fragmentation?1

  1. Placement policy (e.g., returning a larger block than requested)
  2. Padding for alignment purposes
  3. Pattern of future requests
  4. Memory added to blocks in order to maintain heap data structures

Which of the following statements is FALSE?2

  1. Temporary arrays should not be allocated on the heap
  2. malloc returns the address of a block that is filled with arbitrary data
  3. Memory utilization is affected by both internal and external fragmentation
  4. An allocation failure will cause your program to stop

5 Practice

Assume the heap starts at address 0x1000 with no allocated blocks. brk is at 0x1100. The requests below arrive in the order they are given. The system will allocate a 16-byte aligned block in the first available space after the start of the heap. For each request, give the block size and the address returned to the application.3

  1. malloc(18)
  2. malloc(24)
  3. malloc(10)
  4. malloc(32)
  5. malloc(33)

Does the system need to call sbrk at any point?

Footnotes:

1

The pattern of future requests is not a source of internal fragmentation

2

The false statement is an allocation failure will cause your program to stop. When malloc fails, it doesn't crash, but returns NULL. It is the program's job to check this return value and act accordingly.

3
  1. Block size of 32, returns 0x1000
  2. Block size of 32, returns 0x1020
  3. Block size of 16, returns 0x1040
  4. Block size of 32, returns 0x1050
  5. Block size of 48, returns 0x1070

All of these fit below 0x1100, so a call to sbrk is not needed.