0

I'm trying to force a buffer overflow to change the value of a variable. The idea is to overflow var_a to change var_b in the following code, which runs Contiki-NG operating system in an ARM Cortex-M4:

#include "contiki.h"
#include "board.h"
#include <dev/leds.h>

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

PROCESS(main_process, "main_process");

AUTOSTART_PROCESSES(&main_process);

PROCESS_THREAD(main_process, ev, data) {
    uint8_t data_buffer[16] = {
        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
        0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
    };

    uint32_t var_b;
    uint8_t var_a[4];

    PROCESS_BEGIN();

    var_b = 0;
    printf("var_b = %08lx\n", var_b);

    memcpy(var_a, data_buffer, 8);
    printf("var_b = %08lx\n", var_b);

    if (var_b) {
        leds_arch_set(LEDS_RED);
    }

    PROCESS_END();
}

The problem is that overflow is not affecting var_b, but data_buffer. I used a debugger to check the addresses of the local variables in the process, and got the following:

enter image description here

Looking at this explains why the overflow is affecting to data_buffer, since it is located right after var_a. But what I didn't expect was that the address of var_b is <outofscope>, which suggests that this variable might be allocated in other memory region different from the current stack frame.

What's the reason for this happening when allocating var_b? Is there any way I can make it local?

Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265
Jaime_mc2
  • 673
  • 5
  • 18
  • 4
    Overflowing a buffer result in undefined behaviour. The compiler is free to arrange the layout of variables. You need to check the generated assembly code, then you can know what's going on. – Jabberwocky Sep 22 '22 at 09:37
  • 2
    The compiler can do lots of optimization as long as the result looks as if it did what you were asking for. It would be perfectly fine to remove that whole variable and just use value 0 directly for the function calls. To avoid that you might try to call some dummy function where you pass the address of `var_b` – Gerhardh Sep 22 '22 at 09:37
  • ... or adding using the `volatile` keyword: `volatile uint32_t var_b;`, which guarantees that the variable won't be optimized away and that it is read read time. – Jabberwocky Sep 22 '22 at 09:40
  • 2
    In addition to what has already been said, another reasonably common optimisation is that local variables are not actually allocated storage on the stack, if the generated code can just leave the variable in a register. Printing out `&var_b` should be another way of forcing the allocation of space on the stack. – cooperised Sep 22 '22 at 10:03

2 Answers2

0

A couple suggestions:

  1. You could try disable compiler optimization with -O0 cflags.
  2. Add volatile
Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265
wangkai
  • 56
  • 1
0

But what I didn't expect was that the address of var_b is <outofscope>, which suggests that this variable might be allocated in other memory region different from the current stack frame.

What's the reason for this happening when allocating var_b? Is there any way I can make it local?

When it comes to debugging, there's really no other way to read variables except to compile with -ggdb -O0. ie: turn ON debug flags with -g or -ggdb or similar, and turn OFF optimization with -O0. You must do both. See my answer here: What's the difference between a compiler's -O0 option and -Og option?

Memory pools and pointer arithmetic

Whatever it is you're trying to do is very confusing. Some questions for you to think about:

  1. Why are you trying to "overflow a buffer" to change variable var_b? Why don't you just change var_b directly?
  2. memcpy(var_a, data_buffer, 8); copies the first 8 bytes from data_buffer into var_a. Since var_a is only 4 bytes, the first 4 bytes go into it successfully, and then the latter 4 bytes have undefined behavior by writing out of the bounds of the variable. Why don't you just write the first 4 bytes of data_buffer into var_a and the next 4 bytes into var_b?
  3. If you think writing past the bounds of var_a should write into var_b, why did you put var_b first instead of var_a first? You did:
    uint32_t var_b;
    uint8_t var_a[4];
    
    The compiler can do whatever it wants when determining where to place variables in memory, so there's no guarantee here, but it seems more logical that you would have at least put var_a first, like this:
    uint8_t var_a[4];
    uint32_t var_b;
    
    Why didn't you?

Alright, that about covers it. Let's go over some things.

1. If you want to have guaranteed relative locations of variables, force it via a memory pool!

No matter what order you write your variables, the compiler is not compelled to abide by that order nor location, unless you use memory pools or otherwise manually specify the address for a given variable, such as a hardware register.

// How to force a memory layout of 4 bytes of `uint8_t var_a[4]` followed by 4
// bytes of `uint32_t var_b` (8 bytes total)

#define MEM_POOL_SIZE 8 // 8 bytes


// Step 1. Create an 8 byte memory pool. Choose **one** of the following
// options:

// Option 1: 8 byte memory pool **on the stack** (statically allocated)
uint8_t mem_pool[MEM_POOL_SIZE];

// Option 2: 8 byte memory pool **on the heap** (dynamically allocated)
uint8_t* mem_pool = malloc(MEM_POOL_SIZE);
if (mem_pool == NULL)
{
    // do error handling here: out of memory
}

// Option 3: 8 byte memory pool **neither on the stack nor the heap**
// (`static` makes it take global RAM not allocated for either)
static uint8_t mem_pool[MEM_POOL_SIZE];


// Step 2: point `var_a` and `var_b` into the memory pool. Voila! They now
// magically take this pool of memory, with `var_b` **guaranteed** to be 
// right after `var_a`. 

uint8_t* var_a = mem_pool; // 4 byte array of uint8_t
// Note: the `+ 4` is pointer math: since `mem_pool` is a ptr to `uint8_t`, 
// this moves forward `4*sizeof(uint8_t)` bytes.  
uint32_t* var_b = mem_pool + 4; 


// Step 3: use the variables! Here are some examples:

// write some bytes into the `var_a` array
var_a[0] = 0x01;
var_a[1] = 0x02;
var_a[2] = 0x03;
var_a[3] = 0x04;

// write a value into `var_b`
*var_b = 12345;

// write the first 4 bytes of data_buffer into `var_a` and the next 4 into
// `var_b`
memcpy(var_a, data_buffer, MEM_POOL_SIZE);

2. Just write the first 4 bytes of data_buffer into var_a and the next 4 bytes into var_b directly

See my questions at the top of my answer. I don't understand what you are doing, or why. If you want to write the first 4 bytes of data_buffer into var_a and the next 4 bytes into var_b, just do that! No need for any undefined-behavior "overflow" tricks, nor memory pools!

uint8_t var_a[4];
uint32_t var_b;

uint8_t data_buffer[16] = {
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
};

memcpy(var_a, data_buffer, 4);
// remember, the `+ 4` is pointer arithmetic, and moves forward 4 x the size
// of the thing being pointed to, which is `uint8_t` (1 byte) in this case
memcpy(&var_b, data_buffer + 4, 4);

// Or (better), same thing as just above, but written more-clearly:
memcpy(var_a, data_buffer, sizeof(var_a);
memcpy(&var_b, data_buffer + sizeof(var_a), sizeof(var_b));
halfer
  • 19,824
  • 17
  • 99
  • 186
Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265