CSE-376 (Spring 2009) Homework Assignment #2 (Handout #6, version 1) Due Monday, March 16, 2009, 11:59pm (This assignment is worth 10% of your grade.) *** PURPOSE: To become familiar with many common C bugs involving pointers and memory corruptions. This assignment has two parts. In part 1, you will be asked to write code that triggers these problems. You will be asked to write a series of small *BAD* C programs, each of which exhibits and "tickles" a particular C bug. In part 2, you will be fixing a number of real problems in an actual C program. Total expected length of C code written is 300-500 lines. Each of the 20+ test programs you have to write can be done in just a few lines of C code. *** TASK: For the purpose of this assignment, we have set up a special version of the GNU C compiler called "Bounds-Checking GCC" (bcc). This compiler tests for various additional problems such as accessing unallocated memory, using a freed pointer, and more. You can find information about BCC on the Web (http://www-ala.doc.ic.ac.uk/~phjk/BoundsChecking.html and others). Note: when I use the term "object" below, I'm not referring to anything object-oriented, but to any type of "thing" (data type) that C can refer to; it can be an int, an int*, a struct, a pointer to a struct, etc. ** Part 1 (66 points out of 100): Your task is to write a series of small C programs, each of which will test an individual bug. Consider this program: main() { char buf[10]; strcpy(buf, "abcdefghij"); } If you compiled it with the regular gcc, it will compile just "fine" and may even run ok (in some cases, smart libc/malloc libraries catch certain problems before BCC could). However, the above program runs of the end of the 'buf' string. If you compile this program with "bcc", and then run it, you will get a special error message that helps you to identify the bug and fix it: $ bcc foo.c gcc -fbounds-checking foo.c $ ./a.out Bounds Checking GCC v gcc-3.0.1-3.0 Copyright (C) 1995 Richard W.M. Jones Bounds Checking comes with ABSOLUTELY NO WARRANTY. For details see file `COPYING' that should have come with the source to this program. Bounds Checking is free software, and you are welcome to redistribute it under certain conditions. See the file `COPYING' for details. For more information, set GCC_BOUNDS_OPTS to `-help' foo.c:4:Bounds error: strcpy with this destination string and size 11 would overrun the end of the object's allocated memory. foo.c:4: Pointer value: 0xbffff4b0 foo.c:4: Object `buf': foo.c:4: Address in memory: 0xbffff4b0 .. 0xbffff4b9 foo.c:4: Size: 10 bytes foo.c:4: Element size: 1 bytes foo.c:4: Number of elements: 10 foo.c:4: Created at: foo.c, line 3 foo.c:4: Storage class: stack Aborted (core dumped) You should write individual, separate small C programs to test the following conditions (note that some of these conditions may be very similar and the same code may satisfy several conditions): 01: Check the index of a statically-allocated array when overflowing 02: Check the index of a statically-allocated array when under-flowing 03: Check the index of a dynamically-allocated array when overflowing 04: Check the index of a dynamically-allocated array when under-flowing 05: freeing an illegal zero (0) address 06: freeing a NULL pointer (which really is a zero...) 07: freeing a non-heap memory 08: double-freeing memory 09: realloc'ing a non-heap memory 10: using dynamic memory after it has been free()'ed 11: using illegal pointer addresses (using "0") 12: same as #11, but using small positive/negative integers. 13: using pointers that are not aligned on word boundaries 14: partially overlapping memcpy() arguments 15: comparing pointers to objects of different/incompatible types 16: creating an illegal pointer when using pointer arithmetic 17: reading uninitialized memory 18-19: memcpy passed a NULL pointer in any of its args (2 programs) 20: mempcy passed a NULL pointer but with zero copy length 21: referring to an automatic stack object that has been popped from the stack (the object is not valid in the scope it is being referenced) 22: referring to an "unchecked static object" that is not part of your bcc-compiled code. Hint: have one object file compiled with bcc, another with gcc, one program refers to a symbol/variable in another, link the two, and run them. Note that the term 'static' here is not directly related to C static variables; it's an internal gcc/bcc compiler terminology to mean a long-lasting object such as a global variable. Your code should be well documented, properly styled and indented. As much as possible, your code should try and compile cleanly with export GCC_BOUNDS_OPTS=-warn-all bcc -Wall -Werror foo.c on two systems: a-solaris10s and a-rh9. That is, each test case will be a single C source file that compiles cleanly on both Unix systems. Only during the execution of the program, should the bug manifest itself. (The 'export' command above turns on all of the checks that BCC knows about. It MUST be used or else BCC won't check for much, if any.) One important goal is that you are not required to write long and complex code for any of the tests. The key issue is to write SMALL pieces of code that clearly exhibit each bug/problem. In many cases the solution may appear to you very simple (it is! :-) On the other hand, the programs should try to compile and run just fine for as many of the programs you have (e.g., without a core dump or segmentation violation) preferably using: gcc -Wall -Werror foo.c ./a.out In other words, the code you write should be hopefully undetected by normal gcc (either during compile time or run time), and only detected by bcc. Note: you will find that on certain OSs, gcc and/or glibc have gotten smart enough to catch errors that normally only bcc would have caught. In those cases, it'll be impossible to have a program "perfectly" compiling and running with gcc, but the bug only caught with bcc. That's OK: you should describe in the README file what experiences you had such as these, and you will qualify for full credit. ** Part 2: Makefile (16 points) Write a Makefile with the following targets: + prog-XX-bcc (where XX is a number of a test): to build program XX using bcc. + prog-XX-gcc (where XX is a number of a test): to build program XX using gcc. + run-XX-bcc (where XX is a number of a test): to run program XX which was built using bcc. + run-XX-gcc (where XX is a number of a test): to run program XX which was built using gcc. + all-bcc: to build all tests using bcc. + all-gcc: to build all tests using gcc. + all: to build all tests (gcc and bcc) (this should be the default target). + run-bcc: to run all test programs which were built using bcc. + run-gcc: to run all test programs which were built using gcc. + run: to run all tests programs (built using both bcc and gcc). + clean: to cleanup all temp/object files which can be rebuilt. + depend: to build a dependency set of files automatically, which will get included in the makefile if they exist. You can assume GNU Make and GNU Gcc syntax (so check the gcc man page for -MD options). Your makefile should be as small and well-written as possible. Avoid writing repeated rules as much as possible. Use variables when and where it makes sense. And use "%" rules for Makefiles as well. Note: this describes what you need to do for your Makefile for part 1. For the remaining parts, you will also have to add targets to your Makefile, which you should document in the README carefully. *** Part 3 (18 points): Write your own simple malloc-debugging "library" (a .c file you can compile and link with another program). This library should export AT LEAST these two functions: xmalloc() and xfree(), which should have the same functionality as the usual malloc/free functions. In turn, xmalloc and xfree should use malloc and free, respectively. xmalloc and xfree should track what memory objects are being allocated and freed, and should warn when these conditions occur: double free, double malloc (a memory leak), trying to free something in the middle of an allocated memory chunk, and memory leaks. By "warn" I mean that they should "fprintf(stderr)" a descriptive error message saying what went wrong (optionally, you can also exit the program with an appropriate non-zero exit status). Hint: think about the API of your library and how you would detect leaks or any other unfreed memory (extend the API as needed, but keep it simple). Your debugging library should be put into a file called xmalloc.c. Write a program or programs that link with your xmalloc.o and then exercise each of the "bad things" that your library catches. *** SUBMISSION (applicable to extra credit as well) Your CVS-based submission should include the following files - a README file describing what each test program does (in parts 1 and 3). - a series of test programs named prog-01.c, prog-02.c, ... prog-17.c, etc. Please carefully follow the names of these files and their capitalization. (Note that for program 22, you may need several separate .c files.) - Any other source/header files you need for part 3 (and Extra Credit). - a Makefile (See the class Web page for CVS submission guidelines.) ** EXTRA CREDIT (up to 8 points, optional for all students): [4 pts] Learn how to use the ElectricFence library on a-rh9 (already installed, called libefence). Write a series (at least four) of small programs, each one demonstrating a separate feature of ElectricFence: reusing free()'ed memory, buffer overrun, underrun, memory poisoning, etc. Your programs should compile and run with gcc, but fail to run if linked with ElectricFence. Add appropriate rules to the Makefile and explain your programs in the README. [4 pts] Learn how to use the libumem library on a-solaris10s. See the man page for umem_debug. Write a series (at least four) of small programs, each one demonstrating a separate feature of libumem: reusing free()'ed memory, buffer overrun, underrun, memory poisoning, etc. Your programs should compile and run with gcc, but fail to run if linked with libumem. Add appropriate rules to the Makefile and explain your programs in the README. Good Luck.