New To Programming? Say Hello To Memory Corruption

By | 01/07/2013

Every process in any operating system would use memory to store its code and data. Same is the case with processes in Linux. However, the onus is on the programmer to see if the memory is being properly used or not. This is essential, as memory issues lead to copious bugs and bizarre behaviours. As the program memory store data values, which are associated to the logic and flow of the process, memory corruption may take place. That is, if a memory gets overwritten or a pointer pointing to a memory gets altered.

In this article, we shall discuss memory corruption issues in Linux programming and the various ways to debug and resolve them.

NOTE - Though memory corruption can be directly associated with programming language but its debugging techniques (debugging tools etc) may depend upon the OS you are using. All the examples and commands used in this article are tested on Ubuntu Linux.

 

Memory Corruption in Linux

Memory corruption as the name suggests, is when some memory is being modified unintentionally, which leads to segmentation faults (crashes) or unexpected results. The memory getting corrupted is pretty much silent and we see the effect at a much later time. Hence, the memory corruption will always get detected at a later point of time, whenever the corrupted memory is accessed. This compounds the error situation as it becomes hard to figure out the precise point, where memory got overwritten. We shall see more on how to debug such scenarios in the later sections of the article.

If we wish to segregate, we can classify memory corruption into following three categories

1. Global memory corruption

It is not hard to guess what it is, global memory corruption is when a global variable memory gets altered in a wrong way. That means, the data segment memory of the process image in the RAM gets modified unintentionally. One may think, how is that possible? Well, it is possible if an array is written out of bounds or a pointer misused to write to an invalid memory area.

Let us see one of such example through a program.

In the following C program, we have a global data array and a global integer variable. The data array of size ‘MAX’ is supposed to store all the squares, of all numbers in sequence till the ‘endval’ variable value. That is, the program will square the sequential numbers till ‘endval’ value and store them in the array.

This is how the source code looks like.

#include <stdio.h>
#define MAX 6
int arrdata[MAX];
int endval;
int main()
{
   int i = 0;
   endval = 12;
   for (i = MAX; (endval) && (i >= 0) ; i--, endval--)
   {
      arrdata[i] = endval * endval;
   }

   printf("Values are \n"l);
   for (i = 0; i < MAX; i++)
   {
      printf("\t %d\n", arrdata[i]);
   }
   return 0;
}

 Trusting the programmer, lets compile and run it

$ gcc -Wall memcorrupt.c -o memcorrupt
$./memcorrupt

This is what we get

Values are
19044
19321
19600
19881
20164
20449

 Wierd!! isn’t it? There seem to be a bug in the code. For sure, we don’t expect such numbers as our output. With the ‘endval’ as 12, the last number should be square of ‘12’ i.e. 144. Then, what went wrong? We need to debug it and find out whats happening wrong.

First of all, let us check the value of the variable ‘endval’ just before our main squaring loop. We can do it by using gdb debugger available for linux. The gdb is the GNU debugger, very convenient, quick and efficient to use. More details are here. If one wants to pass using gdb debugger, printf() statements can also do the same task of checking onto the variable values.

Prior to we start the executable with gdb, the executable has to be built with debug information included. This is done by ‘-g’ option of ‘gcc’ compiler. (Read more about gcc compilation process)

$ gcc  -g  -Wall memcorrupt.c -o memcorrupt

Further, run the executable through gdb, as,

$ gdb ./memcorrupt

We get the gdb prompt as shown below,

GNU gdb (Ubuntu/Linaro 7.2-1ubuntu11) 7.2
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/rupali/programs/memcorruption/memcorrupt...done.
(gdb)

Add a breakpoint at the first line of the squaring for loop (for me, it is line 11) and initiate the run

(gdb) break 11
Breakpoint 1 at 0x80483df: file memcorrupt.c, line 11.
(gdb) run
Starting program: /home/rupali/programs/memcorr/memcorrupt
Breakpoint 1, main () at memcorrupt.c:11
11        for (; (endval) && (i >= 0) ; i--, endval--)

 Thereafter, once the breakpoint gets hit, check the value of the variable ‘endval’.

(gdb) print endval
$1 = 12

Hmmm… the endval is correct as per the assignment. Now, lets step to the next statement and recheck the value..

(gdb) step
13             arrdata[i] = endval * endval;
(gdb) print endval
$2 = 12

The value is still fine. Lets move on to step to the next statement where the array element value has been computed and we again check the value of endval along with the array’s computed element

(gdb) step
11        for (; (endval) && (i >= 0) ; i--, endval--)
(gdb) print arrdata[i]
$3 = 144
(gdb) print endval
$4 = 144

The array element value looks fine, but what changed the ‘endval’ variable? Clearly this is the case of global memory corruption. We had no inkling with running through the program as where what made this memory alteration. There could be following reasons :

  • Array index overflowed
  • Array index underflowed (write at a negative index)
  • A pointer value used to write to an invalid memory location.

We can safely overrule the reason (3) as no pointers involved in our program. Array overflow/underflow could be a possibility. Check the loop where we write to the array

   for (i = MAX; (endval) && (i >= 0) ; i--, endval--)
   {
        arrdata[i] = endval * endval;
   }

To start with it writes index = ‘MAX’, Oh-O… caught it. Since the array index starts with ‘0’, the ending index would be ‘MAX -1’ and not ‘MAX’. Writing to the index ‘MAX’ would be leading to an array overflow.

Therefore, lets rectify the initialization in the loop and re-run the program

   for (i = MAX - 1; (endval) && (i >= 0) ; i--, endval--)
   {
        arrdata[i] = endval * endval;
   }

Re-compiling and running gives us

$ gcc -Wall memcorrupt.c -o memcorrupt
$ ./memcorrupt
Values are
     49
     64
     81
     100
     121
     144

It just worked !!!

 So in the above thorough exercise of detecting a memory corruption and debugging the same, we came to know how it happens and the way it leads to totally unexpected results. Also note how, that detection of real cause of memory corruption can be not so straightforward. This was the simplest form of, and highly fictitious example code. In real world programming, it sometimes becomes highly complicated to find the precise location of memory corruption. Approach is the key.

2. Stack corruption

The Linux system stack related to any process stores all the local variables, passed arguments and function return addresses. Hence, stack corruption is the most hazardous memory corruption in terms of diagnosis. It mostly lead to crashes though even unexpected behaviours can also be expected.

Talking about the reasons causing stack corruption, here I have tried to list down two of the major ones:

    • Buffer overrun within a function
    • Stack overflow

Buffer overrun means, a local buffer or an argument passed is written out of bound, leading to overwriting at some unintended portion of the system stack. Let us see how that happens in the form of a source code. Now we shall write a C code, where we pass an argument in a method, and then intentionally overflow the argument buffer to observe the aftereffects of stack corruption.

#include <stdio.h>
#include <string.h>
#define LEN 6

void cpyPrint(char *str)
{
   char aBuf[LEN];
   strcpy(aBuf, str);

   printf("String is %s\n", aBuf);
}

int main()
{
   char *aStr = "MyLinux";

   cpyPrint(aStr);

   return 0;
}

Compiling the program should be okay

$ gcc -Wall stack_argsover.c -o stack_argsover

We would be more interested to see how it runs,

$./stack_argsover
String is MyLinux
*** stack smashing detected ***: ./stack_argsover terminated
======= Backtrace: =========
/lib/i386-linux-gnu/libc.so.6(__fortify_fail+0x50)[0xb7832370]
/lib/i386-linux-gnu/libc.so.6(+0xec31a)[0xb783231a]
./stack_argsover[0x8048492]
./stack_argsover[0x80484b1]
/lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xe7)[0xb775ce37]
./stack_argsover[0x80483b1]
======= Memory map: ========
08048000-08049000 r-xp 00000000 08:01 1847115    /home/rupali/programs/memcorr/stack_argsover
08049000-0804a000 r--p 00000000 08:01 1847115    /home/rupali/programs/memcorr/stack_argsover
0804a000-0804b000 rw-p 00001000 08:01 1847115    /home/rupali/programs/memcorr/stack_argsover
08560000-08581000 rw-p 00000000 00:00 0          [heap]
b7713000-b772d000 r-xp 00000000 08:01 3407939    /lib/i386-linux-gnu/libgcc_s.so.1
b772d000-b772e000 r--p 00019000 08:01 3407939    /lib/i386-linux-gnu/libgcc_s.so.1
b772e000-b772f000 rw-p 0001a000 08:01 3407939    /lib/i386-linux-gnu/libgcc_s.so.1
b7745000-b7746000 rw-p 00000000 00:00 0
b7746000-b78a7000 r-xp 00000000 08:01 3411970    /lib/i386-linux-gnu/libc-2.13.so
b78a7000-b78a8000 ---p 00161000 08:01 3411970    /lib/i386-linux-gnu/libc-2.13.so
b78a8000-b78aa000 r--p 00161000 08:01 3411970    /lib/i386-linux-gnu/libc-2.13.so
b78aa000-b78ab000 rw-p 00163000 08:01 3411970    /lib/i386-linux-gnu/libc-2.13.so
b78ab000-b78ae000 rw-p 00000000 00:00 0
b78c3000-b78c6000 rw-p 00000000 00:00 0
b78c6000-b78c7000 r-xp 00000000 00:00 0          [vdso]
b78c7000-b78e3000 r-xp 00000000 08:01 3411962    /lib/i386-linux-gnu/ld-2.13.so
b78e3000-b78e4000 r--p 0001b000 08:01 3411962    /lib/i386-linux-gnu/ld-2.13.so
b78e4000-b78e5000 rw-p 0001c000 08:01 3411962    /lib/i386-linux-gnu/ld-2.13.so
bf884000-bf8a5000 rw-p 00000000 00:00 0          [stack]
Aborted (core dumped)

4 thoughts on “New To Programming? Say Hello To Memory Corruption

  1. hackerspace

    The problem you mention here is a problem with the code.

    What does Linux have to do with it ? It’ll be the same in any OS in the world.

    Nothing more than a article bait. I wonder who edits the articles here.

    Reply
    1. Rupali Post author

      Thanks for the comment.
      Well, the purpose of this article is to make the readers understand the memory corruption before we learn about the various tools aiding to tackle it. Memory corruption is a crucial issue during programming on any OS. That does not mean, Linux users need not know it.
      The article still tries to give it a linux flavor by debugging using linux utilities, before we move on to the memory corruption tools in part II

      Hope you like the part II of this article which is http://mylinuxbook.com/debugging-linux-memory-corruption/

      Thanks,

      Reply
  2. dothebart

    You should definitely mention valgrind.org, which is one of the best tools around to detect & fix such corruptions.

    Reply
    1. Rupali Post author

      Thanks for the suggestion.
      Apologize if it wasn’t clear, but the article mentions in the ‘conclusion’ that memory corruption tools will be discussed in the next part.

      The part II which is http://mylinuxbook.com/debugging-linux-memory-corruption/ discusses about a few of the memory corruption detection tools including valgrind.
      Hope you like it.

      Thanks,

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *