7.5. Electric Fence The next tool we look at is Electric Fence.[6] While it makes no attempt to find memory leaks, it does a nice job of helping programmers isolate buffer overflows. Every modern computer (including all the computers that Linux runs on) provides hardware memory protection. Linux takes advantage of this to isolate programs from each other (your vi session cannot access the memory of my gcc invocation, for example) and to share code safely among processes by making it read-only. Linux's mmap()[7] system call allows processes to take advantage of hardware memory protection, as well. [6] Available from ftp://sunsite.unc.edu/pub/Linux/devel/lang/c as well as with many distributions. [7] See Chapter 13 for details on mmap(). Electric Fence replaces the C library's normal malloc() function with a version that allocates the requested memory and (usually) allocates a section of memory immediately after this, which the process is not allowed to access! Although it may seem odd for a process to allocate memory that it is not allowed to access, doing so causes the kernel to halt the process immediately with a segmentation fault if the program tries to access the memory. By allocating the memory in this manner, Electric Fence has arranged things so that your program will be killed whenever it tries to read or write past the end of a malloc() ed buffer. For full details on using Electric Fence, consult its man page(man libefence), which is detailed. 7.5.1. Using Electric Fence One of the nicest things about Electric Fence is that it is easy to use. Simply link a program against libefence.a by running the final link step with -lefence as the final argument, and the code is ready to be debugged. Let's see what happens when we run our test program against Electric Fence: $ ./broken Electric Fence 2.2.0 Copyright (C) 1987-1999 Bruce Perens. 1: 12345 Segmentation fault (core dumped) Although Electric Fence does not tell us exactly where the problem occurred, it does make the problem itself much more obvious. Pinpointing where the problem occurred is easily done by running the program under a debugger, such as gdb. To use gdb to pinpoint the problem, build the program with debugging information by using gcc's -g flag, run gdb and tell it the name of the executable to debug, and run the program. When the program is killed, gdb shows you exactly what line caused the problem. Here is what this procedure looks like: $ gcc -ggdb -Wall -o broken broken.c -lefence $ gdb broken ... (gdb) run Starting program: /usr/src/lad/code/broken Electric Fence 2.2.0 Copyright (C) 1987-1999 Bruce Perens. 1: 12345 Program received signal SIGSEGV, Segmentation fault. 0x007948c6 in strcpy () from /lib/tls/libc.so.6 (gdb) where #0 0x007948c6 in strcpy () from /lib/tls/libc.so.6 #1 0x08048566 in broken () at broken.c:21 #2 0x08048638 in main () at broken.c:47 (gdb) Thanks to Electric Fence and gdb, we know there is a problem in file broken.c code at line 21, which is the second time strcpy() is called. 7.5.2. Memory Alignment While Electric Fence did a fine job of finding the second problem in the code namely, the strcpy() that overwrote its buffer by a large amount it did not help us at all in finding the first buffer overflow. The problem here has to do with memory alignment. Most modern CPUs require multibyte objects to start at particular offsets in the system's RAM. For example, Alpha processors require that an 8-byte long begin at an address that is evenly divisible by eight. This means that a long may appear at address 0x1000 or 0x1008, but not at 0x1005.[8] [8] Most traditional Unix systems deliver a bus error (SIGBUS) to a process that attempts to use misaligned data. The Linux kernel actually handles unaligned accesses so that the process can continue normally, but at a large performance penalty. Because of this consideration, malloc() implementations normally return memory whose first byte is aligned on the processor's word size (4 bytes on 32-bit processors and 8 bytes on 64-bit processors) to ensure the caller can store whatever data it likes into the memory. By default, Electric Fence attempts to mimic this behavior by providing a malloc() that returns only addresses that are an even multiple of sizeof(int). In most programs, such alignment is not all that important, because memory allocations are done in increments that are already based on the machine's word size, or of simple character strings, which do not have any alignment requirements (as each element is only one byte long). In the case of our test program, the first malloc() call allocated five bytes. For Electric Fence to meet its alignment restrictions, it must treat the allocation as a request for eight bytes and set up the memory with an extra three bytes of accessible space after the malloc() ed region! Small buffer overflows in this region are not caught because of this. As malloc() alignment concerns can normally be ignored and the alignment can allow buffer overflows to remain undetected, Electric Fence lets you control how alignment works through the EF_ALIGNMENT environment variable. If it is set, all malloc() results are aligned according to its value. For example, if it is set to 5, all malloc() results will be addresses evenly divisible by 5 (this probably is not a very useful value, however). To turn off memory alignment, set EF_ALIGNMENT to 1 before running your program. Under Linux, improperly aligned accesses are fixed in the kernel anyway, so although this may slow your program down substantially, it should function properly unless it has slight buffer overflows! Here is how our test program linked against Electric Fence behaved when we set EF_ALIGNMENT to 1: $ export EF_ALIGNMENT=1 $ gdb broken ... (gdb) run Starting program: /usr/src/lad/code/broken Electric Fence 2.2.0 Copyright (C) 1987-1999 Bruce Perens. Program received signal SIGSEGV, Segmentation fault. 0x002a78c6 in strcpy () from /lib/tls/libc.so.6 (gdb) where #0 0x002a78c6 in strcpy () from /lib/tls/libc.so.6 #1 0x08048522 in broken () at broken.c:15 #2 0x08048638 in main () at broken.c:47 This time it found the first buffer overflow that occurred. 7.5.3. Other Features Not only does Electric Fence help detect buffer overflows, but it also can detect buffer underruns (accessing the memory before the start of a malloc() ed buffer) and accesses to memory that has already been free() ed. If the EF_PROTECT_BELOW environment variable is set to 1, Electric Fence traps buffer underruns instead of overflows. It does this by placing an inaccessible memory region immediately before the valid memory region returned by malloc(). When it does this, it can no longer detect overflows because of the memory paging layout of most processors. The memory alignment concerns that make overflows tricky do not affect underruns, however, as in this mode, Electric Fence's malloc() always returns a memory address at the beginning of a page, which is always aligned on a word boundary. If EF_PROTECT_FREE is set to 1, free() makes the memory region passed to it inaccessible rather than return it to the free memory pool. If the program tries to access that memory at any point in the future, the kernel will detect the illegal access. Setting EF_PROTECT_FREE makes it easy to ensure that your code is not using free() ed memory at any point. 7.5.4. Limitations While Electric Fence does a nice job of finding overflows of malloc() ed buffers, it does not help at all with tracking down problems with either global or locally allocated data. It also does not make any attempt to find memory leaks, so you have to look elsewhere for help with those problems. 7.5.5. Resource Consumption Although Electric Fence is powerful, easy to use, and fast (because all the access checks are done in hardware), it does exact a price. Most processors allow the system to control access to memory only in units of a page at a time. On Intel 80x86 processors, for example, each page is 4,096 bytes in size. Because Electric Fence wants malloc() to set up two different regions for each call (one allowing access, the other allowing no access), each call to malloc() consumes a page of memory, or 4K![9] If the code being tested allocates a lot of small areas, linking the code against Electric Fence can easily increase the program's memory usage by two or three orders of magnitude! Of course, using EF_PROTECT_FREE makes this even worse because that memory is never freed. [9] On Linux/Intel and Linux/SPARC systems anyway. The page size depends on the underlying hardware architecture, and may be 16K or even larger on some systems. For systems with lots of memory relative to the size of the program you are debugging, when you are looking to find the source of a specific instance of corruption, Electric Fence may be faster than Valgrind. However, if you need to enable a gigabyte of swap space just to make Electric Fence work, then Valgrind will probably be much faster even though it is using a CPU emulator instead of the native CPU. |