3.2.1. Process Memory Layout

A typical process runs within its own virtual address space, which is distinct from the virtual address spaces of other processes. The virtual address space typically contains four distinct types of content:

Each distinct type of content typically occupies one or several continuous blocks of memory within the virtual address space. The initial placement of these blocks is managed by the loader of the operating system, the content of these blocks is managed by the process owning them.

The blocks that contain executable code and static data are of little interest from the process memory management point of view as their layout is determined by the compiler and does not change during process execution. The blocks that contain stack and heap, however, change during process execution and merit further attention.

While the blocks containing the executable code and static data are fixed in size, the blocks containing the heap and the stack may need to grow as the process owning them executes. The need for growth is difficult to predict during the initial placement of the blocks. To avoid restricting the growth by placing either heap or stack too close to other blocks, they are typically placed near the opposite ends of the process virtual address space with an empty space between them. The heap block is then grown upwards and the stack block downwards as necessary.

When multiple blocks of memory within the virtual address space need to grow as the process owning them executes, the initial placement of the blocks becomes a problem. This can be partially alleviated by using hardware that supports large virtual addresses, where enough empty space can be set aside between the blocks without exhausting the virtual address space, or by using hardware that supports segmentation, where blocks can be moved in the virtual address space as necessary.

3.2.1.1. Example: Virtual Address Space Of A Linux Process

In Linux, the location of blocks of memory within the virtual address space of a process is exported by the virtual memory manager of the operating system in the maps file of the proc filesystem.

> cat /proc/self/maps
00111000-00234000 r-xp 00000000 03:01 3653725    /lib/libc-2.3.5.so
00234000-00236000 r-xp 00123000 03:01 3653725    /lib/libc-2.3.5.so
00236000-00238000 rwxp 00125000 03:01 3653725    /lib/libc-2.3.5.so
00238000-0023a000 rwxp 00238000 00:00 0
007b5000-007cf000 r-xp 00000000 03:01 3653658    /lib/ld-2.3.5.so
007cf000-007d0000 r-xp 00019000 03:01 3653658    /lib/ld-2.3.5.so
007d0000-007d1000 rwxp 0001a000 03:01 3653658    /lib/ld-2.3.5.so
008ed000-008ee000 r-xp 008ed000 00:00 0          [vdso]
08048000-0804d000 r-xp 00000000 03:01 3473470    /bin/cat
0804d000-0804e000 rw-p 00004000 03:01 3473470    /bin/cat
09ab8000-09ad9000 rw-p 09ab8000 00:00 0          [heap]
b7d88000-b7f88000 r--p 00000000 03:01 6750409    /usr/lib/locale/locale-archive
b7f88000-b7f89000 rw-p b7f88000 00:00 0
b7f96000-b7f97000 rw-p b7f96000 00:00 0
bfd81000-bfd97000 rw-p bfd81000 00:00 0          [stack]

The example shows the location of blocks of memory within the virtual address space of the cat command. The first column of the example shows the address of the blocks, the second column shows the flags, the third, fourth, fifth and sixth columns show the offset, device, inode and name of the file that is mapped into the block, if any. The blocks that contain executable code are easily distinguished by the executable flag. Similarly, the blocks that contain read-only and read-write static data are easily distinguished by the readable and writeable flags and the file that is mapped into the block. Finally, the blocks with the readable and writeable flags but no file contain the heap and the stack.

The address of the blocks is often randomized to prevent buffer overflow attacks on the process. The attacks are carried out by supplying the process with an input that will cause the process to write past the end of the buffer allocated for the input. When the buffer is a locally allocated variable, it resides on the stack and being able to write past the end of the buffer means being able to modify return addresses that also reside on the stack. The attack can therefore overwrite some of the input buffers with malicious machine code instructions to be executed and overwrite some of the return addresses to point to the malicious machine code instructions. The process will then unwittingly execute the malicious machine code instructions by returning to the modified return address. Randomizing the addresses of the blocks makes this attack more difficult.