/* * FILE: * fortify.c * * DESCRIPTION: * A fortified shell for malloc, realloc, calloc and free. * To use Fortify, each source file will need to #include "fortify.h". To * enable Fortify, define the symbol FORTIFY. If FORTIFY is not defined, it * will compile away to nothing. If you do not have stdout available, you may * wish to set an alternate output function. See Fortify_SetOutputFunc(), * below. * You will also need to link in fortify.o * * None of the functions in this file should really be called * directly; they really should be called through the macros * defined in fortify.h * */ #ifdef FORTIFY #include #include #include #include #include #define __FORTIFY_C__ /* So fortify.h knows to not define the fortify macros */ #include "fortify.h" #include "ufortify.h" /* the user's options */ struct Header { char *File; /* The sourcefile of the caller */ unsigned long Line; /* The sourceline of the caller */ size_t Size; /* The size of the malloc'd block */ struct Header *Prev, /* List pointers */ *Next; int Scope; int Checksum; /* For validating the Header structure; see ChecksumHeader() */ }; static int CheckBlock(struct Header *h, char *file, unsigned long line); static int CheckFortification(unsigned char *ptr, unsigned char value, size_t size); static void SetFortification(unsigned char *ptr, unsigned char value, size_t size); static void OutputFortification(unsigned char *ptr, unsigned char value, size_t size); static int IsHeaderValid(struct Header *h); static void MakeHeaderValid(struct Header *h); static int ChecksumHeader(struct Header *h); static int IsOnList(struct Header *h); static void OutputHeader(struct Header *h); static void OutputMemory(struct Header *h); static void st_DefaultOutput(char *String) { printf(String); } static struct Header *st_Head = 0; /* Head of alloc'd memory list */ static OutputFuncPtr st_Output = st_DefaultOutput; /* Output function for errors */ static char st_Buffer[256]; /* Temporary buffer for sprintf's */ static int st_Disabled = 0; /* If true, Fortify is inactive */ static int st_MallocFailRate = 0; /* % of the time to fail mallocs */ static char *st_LastVerifiedFile = "unknown"; static unsigned long st_LastVerifiedLine = 0; static int st_Scope = 0; static void OutputLastVerifiedPoint(void); /* * Fortify_malloc() - Allocates a block of memory, with extra bits for * misuse protection/detection. * * Features: * + Adds the malloc'd memory onto Fortify's own private list. * (With a checksum'd header to detect corruption of the memory list) * + Places sentinals on either side of the user's memory with * known data in them, to detect use outside of the bounds * of the block * + Initializes the malloc'd memory to some "nasty" value, so code * can't rely on it's contents. * + Can check all sentinals on every malloc. * + Can generate a warning message on a malloc fail. * + Can randomly "fail" at a set fail rate */ void *FORTIFY_STORAGE Fortify_malloc(size_t size, char *file, unsigned long line) { unsigned char *ptr; struct Header *h; FORTIFY_LOCK(); if(st_Disabled) { ptr = malloc(size); FORTIFY_UNLOCK(); return(ptr); } #ifdef CHECK_ALL_MEMORY_ON_MALLOC Fortify_CheckAllMemory(file, line); #endif if(size == 0) { #ifdef WARN_ON_ZERO_MALLOC sprintf(st_Buffer, "\nFortify: %s.%ld\n malloc(0) attempted failed\n", file, line); st_Output(st_Buffer); #endif FORTIFY_UNLOCK(); return(0); } if(st_MallocFailRate > 0) { if(rand() % 100 < st_MallocFailRate) { #ifdef WARN_ON_FALSE_FAIL sprintf(st_Buffer, "\nFortify: %s.%ld\n malloc(%ld) \"false\" failed\n", file, line, (unsigned long)size); st_Output(st_Buffer); #endif FORTIFY_UNLOCK(); return(0); } } /* * malloc the memory, including the space for the header and fortification * buffers */ #ifdef WARN_ON_SIZE_T_OVERFLOW { size_t private_size = sizeof(struct Header) + FORTIFY_BEFORE_SIZE + size + FORTIFY_AFTER_SIZE; if(private_size < size) /* Check to see if the added baggage is larger than size_t */ { sprintf(st_Buffer, "\nFortify: %s.%ld\n malloc(%ld) has overflowed size_t.\n", file, line, (unsigned long)size); st_Output(st_Buffer); FORTIFY_UNLOCK(); return(0); } } #endif ptr = malloc(sizeof(struct Header) + FORTIFY_BEFORE_SIZE + size + FORTIFY_AFTER_SIZE); if(!ptr) { #ifdef WARN_ON_MALLOC_FAIL sprintf(st_Buffer, "\nFortify: %s.%ld\n malloc(%ld) failed\n", file, line, (unsigned long)size); st_Output(st_Buffer); #endif FORTIFY_UNLOCK(); return(0); } /* * Initialize and validate the header */ h = (struct Header *)ptr; h->Size = size; h->File = file; h->Line = line; h->Next = st_Head; h->Prev = 0; h->Scope = st_Scope; if(st_Head) { st_Head->Prev = h; MakeHeaderValid(st_Head); } st_Head = h; MakeHeaderValid(h); /* * Initialize the fortifications */ SetFortification(ptr + sizeof(struct Header), FORTIFY_BEFORE_VALUE, FORTIFY_BEFORE_SIZE); SetFortification(ptr + sizeof(struct Header) + FORTIFY_BEFORE_SIZE + size, FORTIFY_AFTER_VALUE, FORTIFY_AFTER_SIZE); #ifdef FILL_ON_MALLOC /* * Fill the actual user memory */ SetFortification(ptr + sizeof(struct Header) + FORTIFY_BEFORE_SIZE, FILL_ON_MALLOC_VALUE, size); #endif /* * We return the address of the user's memory, not the start of the block, * which points to our magic cookies */ FORTIFY_UNLOCK(); return(ptr + sizeof(struct Header) + FORTIFY_BEFORE_SIZE); } /* * Fortify_free() - This free must be used for all memory allocated with * Fortify_malloc(). * * Features: * + Pointers are validated before attempting a free - the pointer * must point to a valid malloc'd bit of memory. * + Detects attempts at freeing the same block of memory twice * + Can clear out memory as it is free'd, to prevent code from using * the memory after it's been freed. * + Checks the sentinals of the memory being freed. * + Can check the sentinals of all memory. */ void FORTIFY_STORAGE Fortify_free(void *uptr, char *file, unsigned long line) { unsigned char *ptr = (unsigned char *)uptr - sizeof(struct Header) - FORTIFY_BEFORE_SIZE; struct Header *h = (struct Header *)ptr; FORTIFY_LOCK(); if(st_Disabled) { free(uptr); FORTIFY_UNLOCK(); return; } #ifdef CHECK_ALL_MEMORY_ON_FREE Fortify_CheckAllMemory(file, line); #endif #ifdef PARANOID_FREE if(!IsOnList(h)) { sprintf(st_Buffer, "\nFortify: %s.%ld\n Invalid pointer, corrupted header, or possible free twice\n", file, line); st_Output(st_Buffer); OutputLastVerifiedPoint(); goto fail; } #endif if(!CheckBlock(h, file, line)) goto fail; /* * Remove the block from the list */ if(h->Prev) { if(!CheckBlock(h->Prev, file, line)) goto fail; h->Prev->Next = h->Next; MakeHeaderValid(h->Prev); } else st_Head = h->Next; if(h->Next) { if(!CheckBlock(h->Next, file, line)) goto fail; h->Next->Prev = h->Prev; MakeHeaderValid(h->Next); } #ifdef FILL_ON_FREE /* * Nuke out all memory that is about to be freed */ SetFortification(ptr, FILL_ON_FREE_VALUE, sizeof(struct Header) + FORTIFY_BEFORE_SIZE + h->Size + FORTIFY_AFTER_SIZE); #endif /* * And do the actual free */ free(ptr); FORTIFY_UNLOCK(); return; fail: sprintf(st_Buffer, " free(%p) failed\n", uptr); st_Output(st_Buffer); FORTIFY_UNLOCK(); } /* * Fortify_realloc() - Uses Fortify_malloc() and Fortify_free() to implement * realloc(). * * Features: * + The realloc'd block is ALWAYS moved. * + The pointer passed to realloc() is verified in the same way that * Fortify_free() verifies pointers before it frees them. * + All the Fortify_malloc() and Fortify_free() protection */ void *FORTIFY_STORAGE Fortify_realloc(void *ptr, size_t new_size, char *file, unsigned long line) { void *new_ptr; struct Header *h = (struct Header *) ((unsigned char *)ptr - sizeof(struct Header) - FORTIFY_BEFORE_SIZE); if(st_Disabled) { FORTIFY_LOCK(); new_ptr = realloc(ptr, new_size); FORTIFY_UNLOCK(); return(new_ptr); } if(!ptr) return(Fortify_malloc(new_size, file, line)); FORTIFY_LOCK(); if(!IsOnList(h)) { sprintf(st_Buffer, "\nFortify: %s.%ld\n Invalid pointer or corrupted header passed to realloc\n", file, line); st_Output(st_Buffer); goto fail; } if(!CheckBlock(h, file, line)) goto fail; new_ptr = Fortify_malloc(new_size, file, line); if(!new_ptr) { FORTIFY_UNLOCK(); return(0); } if(h->Size < new_size) memcpy(new_ptr, ptr, h->Size); else memcpy(new_ptr, ptr, new_size); Fortify_free(ptr, file, line); FORTIFY_UNLOCK(); return(new_ptr); fail: sprintf(st_Buffer, " realloc(%p, %ld) failed\n", ptr, (unsigned long)new_size); st_Output(st_Buffer); FORTIFY_UNLOCK(); return (NULL); } /* * Fortifty_calloc() - Uses Fortify_malloc() to implement calloc(). Much * the same protection as Fortify_malloc(). */ void *FORTIFY_STORAGE Fortify_calloc(size_t num, size_t size, char *file, unsigned long line) { void *ptr; ptr = Fortify_malloc(num * size, file, line); if(ptr) memset(ptr, 0, num * size); return(ptr); } /* * Fortify_CheckPointer() - Returns true if the uptr points to a valid * piece of Fortify_malloc()'d memory. The memory must be on the malloc'd * list, and it's sentinals must be in tact. * If anything is wrong, an error message is issued. * * (Note - if fortify is disabled, this function always returns true). */ int FORTIFY_STORAGE Fortify_CheckPointer(void *uptr, char *file, unsigned long line) { unsigned char *ptr = (unsigned char *)uptr - sizeof(struct Header) - FORTIFY_BEFORE_SIZE; int r; if(st_Disabled) return(1); FORTIFY_LOCK(); if(!IsOnList((struct Header *)ptr)) { sprintf(st_Buffer, "\nFortify: %s.%ld\n Invalid pointer or corrupted header detected (%p)\n", file, line, uptr); st_Output(st_Buffer); FORTIFY_UNLOCK(); return(0); } r = CheckBlock((struct Header *)ptr, file, line); FORTIFY_UNLOCK(); return r; } /* * Fortify_SetOutputFunc(OutputFuncPtr Output) - Sets the function used to * output all error and diagnostic messages by fortify. The output function * takes a single unsigned char * argument, and must be able to handle newlines. * The function returns the old pointer. */ Fortify_OutputFuncPtr FORTIFY_STORAGE Fortify_SetOutputFunc(Fortify_OutputFuncPtr Output) { OutputFuncPtr Old = st_Output; st_Output = (OutputFuncPtr)Output; return((Fortify_OutputFuncPtr)Old); } /* * Fortify_SetMallocFailRate(int Percent) - Fortify_malloc() will make the * malloc attempt fail this Percent of the time, even if the memory is * available. Useful to "stress-test" an application. Returns the old * value. The fail rate defaults to 0. */ int FORTIFY_STORAGE Fortify_SetMallocFailRate(int Percent) { int Old = st_MallocFailRate; st_MallocFailRate = Percent; return(Old); } /* * Fortify_CheckAllMemory() - Checks the sentinals of all malloc'd memory. * Returns the number of blocks that failed. * * (If Fortify is disabled, this function always returns 0). */ int FORTIFY_STORAGE Fortify_CheckAllMemory(char *file, unsigned long line) { struct Header *curr = st_Head; int count = 0; if(st_Disabled) return(0); FORTIFY_LOCK(); while(curr) { if(!CheckBlock(curr, file, line)) count++; curr = curr->Next; } if(file) { st_LastVerifiedFile = file; st_LastVerifiedLine = line; } FORTIFY_UNLOCK(); return(count); } /* Fortify_EnterScope - enters a new Fortify scope level. * returns the new scope level. */ int FORTIFY_STORAGE Fortify_EnterScope(char *file, unsigned long line) { return(++st_Scope); } /* Fortify_LeaveScope - leaves a Fortify scope level, * also prints a memory dump of all non-freed memory that was allocated * during the scope being exited. */ int FORTIFY_STORAGE Fortify_LeaveScope(char *file, unsigned long line) { struct Header *curr = st_Head; int count = 0; unsigned long size = 0; if(st_Disabled) return(0); FORTIFY_LOCK(); st_Scope--; while(curr) { if(curr->Scope > st_Scope) { if(count == 0) { sprintf(st_Buffer, "\nFortify: Memory Dump at %s.%ld\n", file, line); st_Output(st_Buffer); OutputLastVerifiedPoint(); sprintf(st_Buffer, "%11s %8s %s\n", "Address", "Size", "Allocator"); st_Output(st_Buffer); } OutputHeader(curr); count++; size += curr->Size; } curr = curr->Next; } if(count) { sprintf(st_Buffer, "%11s %8ld bytes overhead\n", "and", (unsigned long)(count * (sizeof(struct Header) + FORTIFY_BEFORE_SIZE + FORTIFY_AFTER_SIZE))); st_Output(st_Buffer); sprintf(st_Buffer,"%11s %8ld bytes in %d blocks\n", "total", size, count); st_Output(st_Buffer); } FORTIFY_UNLOCK(); return(count); } /* * Fortify_OutputAllMemory() - Outputs the entire list of currently * malloc'd memory. For each malloc'd block is output it's Address, * Size, and the SourceFile and Line that allocated it. * * If there is no memory on the list, this function outputs nothing. * * It returns the number of blocks on the list, unless fortify has been * disabled, in which case it always returns 0. */ int FORTIFY_STORAGE Fortify_OutputAllMemory(char *file, unsigned long line) { struct Header *curr = st_Head; int count = 0; unsigned long size = 0; if(st_Disabled) return(0); FORTIFY_LOCK(); if(curr) { sprintf(st_Buffer, "\nFortify: Memory Dump at %s.%ld\n", file, line); st_Output(st_Buffer); OutputLastVerifiedPoint(); sprintf(st_Buffer, "%11s %8s %s\n", "Address", "Size", "Allocator"); st_Output(st_Buffer); while(curr) { OutputHeader(curr); count++; size += curr->Size; curr = curr->Next; } sprintf(st_Buffer, "%11s %8ld bytes overhead\n", "and", (unsigned long)(count * (sizeof(struct Header) + FORTIFY_BEFORE_SIZE + FORTIFY_AFTER_SIZE))); st_Output(st_Buffer); sprintf(st_Buffer,"%11s %8ld bytes in %d blocks\n", "total", size, count); st_Output(st_Buffer); } FORTIFY_UNLOCK(); return(count); } /* Fortify_DumpAllMemory(Scope) - Outputs the entire list of currently * new'd memory within the specified scope. For each new'd block is output * it's Address, Size, the SourceFile and Line that allocated it, a hex dump * of the contents of the memory and an ascii dump of printable characters. * * If there is no memory on the list, this function outputs nothing. * * It returns the number of blocks on the list, unless Fortify has been * disabled, in which case it always returns 0. */ int FORTIFY_STORAGE Fortify_DumpAllMemory(int scope, char *file, unsigned long line) { struct Header *curr = st_Head; int count = 0; unsigned long size = 0; if(st_Disabled) return(0); FORTIFY_LOCK(); while(curr) { if(curr->Scope >= scope) { if(count == 0) { sprintf(st_Buffer, "\nFortify: Memory Dump at %s.%ld\n", file, line); st_Output(st_Buffer); OutputLastVerifiedPoint(); sprintf(st_Buffer, "%11s %8s %s\n", "Address", "Size", "Allocator"); st_Output(st_Buffer); } OutputHeader(curr); OutputMemory(curr); st_Output("\n"); count++; size += curr->Size; } curr = curr->Next; } if(count) { sprintf(st_Buffer, "%11s %8ld bytes overhead\n", "and", (unsigned long)(count * (sizeof(struct Header) + FORTIFY_BEFORE_SIZE + FORTIFY_AFTER_SIZE))); st_Output(st_Buffer); sprintf(st_Buffer,"%11s %8ld bytes in %d blocks\n", "total", size, count); st_Output(st_Buffer); } FORTIFY_UNLOCK(); return(count); } /* * Fortify_Disable() - This function provides a mechanism to disable Fortify * without recompiling all the sourcecode. It can only be called, though, * when there is no memory on the Fortify malloc'd list. (Ideally, at the * start of the program before any memory has been allocated). If you * call this function when there IS memory on the Fortify malloc'd list, * it will issue an error, and fortify will not be disabled. */ int FORTIFY_STORAGE Fortify_Disable(char *file, unsigned long line) { int result; FORTIFY_LOCK(); if(st_Head) { sprintf(st_Buffer, "Fortify: %s.%d\n", file, (int)line); st_Output(st_Buffer); st_Output(" Fortify_Disable failed\n"); st_Output(" (because there is memory on the Fortify memory list)\n"); Fortify_OutputAllMemory(file, line); result = 0; } else { st_Disabled = 1; result = 1; } FORTIFY_UNLOCK(); return(result); } /* * Check a block's header and fortifications. */ static int CheckBlock(struct Header *h, char *file, unsigned long line) { unsigned char *ptr = (unsigned char *)h; int result = 1; if(!IsHeaderValid(h)) { sprintf(st_Buffer, "\nFortify: %s.%ld\n Invalid pointer or corrupted header detected (%p)\n", file, line, ptr + sizeof(struct Header) + FORTIFY_BEFORE_SIZE); st_Output(st_Buffer); OutputLastVerifiedPoint(); return(0); } if(!CheckFortification(ptr + sizeof(struct Header), FORTIFY_BEFORE_VALUE, FORTIFY_BEFORE_SIZE)) { sprintf(st_Buffer, "\nFortify: %s.%ld\n Memory overrun detected before block\n", file, line); st_Output(st_Buffer); sprintf(st_Buffer," (%p,%ld,%s.%ld)\n", ptr + sizeof(struct Header) + FORTIFY_BEFORE_SIZE, (unsigned long)h->Size, h->File, h->Line); st_Output(st_Buffer); OutputFortification(ptr + sizeof(struct Header), FORTIFY_BEFORE_VALUE, FORTIFY_BEFORE_SIZE); OutputLastVerifiedPoint(); result = 0; } if(!CheckFortification(ptr + sizeof(struct Header) + FORTIFY_BEFORE_SIZE + h->Size, FORTIFY_AFTER_VALUE, FORTIFY_AFTER_SIZE)) { sprintf(st_Buffer, "\nFortify: %s.%ld\n Memory overrun detected after block\n", file, line); st_Output(st_Buffer); sprintf(st_Buffer," (%p,%ld,%s.%ld)\n", ptr + sizeof(struct Header) + FORTIFY_BEFORE_SIZE, (unsigned long)h->Size, h->File, h->Line); st_Output(st_Buffer); OutputFortification(ptr + sizeof(struct Header) + FORTIFY_BEFORE_SIZE + h->Size, FORTIFY_AFTER_VALUE, FORTIFY_AFTER_SIZE); OutputLastVerifiedPoint(); result = 0; } return(result); } /* * Checks if the _size_ bytes from _ptr_ are all set to _value_ */ static int CheckFortification(unsigned char *ptr, unsigned char value, size_t size) { while(size--) if(*ptr++ != value) return(0); return(1); } /* * Set the _size_ bytes from _ptr_ to _value_. */ static void SetFortification(unsigned char *ptr, unsigned char value, size_t size) { memset(ptr, value, size); } /* * Output the corrupted section of the fortification */ /* Output the corrupted section of the fortification */ static void OutputFortification(unsigned char *ptr, unsigned char value, size_t size) { unsigned long offset, column; char ascii[17]; st_Output("Address Offset Data"); offset = 0; column = 0; while(offset < size) { if(column == 0) { sprintf(st_Buffer, "\n%8p %8d ", ptr, (int)offset); st_Output(st_Buffer); } sprintf(st_Buffer, "%02x ", *ptr); st_Output(st_Buffer); ascii[ column ] = isprint( *ptr ) ? (char)(*ptr) : (char)(' '); ascii[ column + 1 ] = '\0'; ptr++; offset++; column++; if(column == 16) { st_Output( " \"" ); st_Output( ascii ); st_Output( "\"" ); column = 0; } } if ( column != 0 ) { while ( column ++ < 16 ) { st_Output( " " ); } st_Output( " \"" ); st_Output( ascii ); st_Output( "\"" ); } st_Output("\n"); } /* * Returns true if the supplied pointer does indeed point to a real Header */ static int IsHeaderValid(struct Header *h) { return(!ChecksumHeader(h)); } /* * Updates the checksum to make the header valid */ static void MakeHeaderValid(struct Header *h) { h->Checksum = 0; h->Checksum = -ChecksumHeader(h); } /* * Calculate (and return) the checksum of the header. (Including the Checksum * variable itself. If all is well, the checksum returned by this function should * be 0. */ static int ChecksumHeader(struct Header *h) { int c, checksum, *p; for(c = 0, checksum = 0, p = (int *)h; c < sizeof(struct Header)/sizeof(int); c++) checksum += *p++; return(checksum); } /* * Examines the malloc'd list to see if the given header is on it. */ static int IsOnList(struct Header *h) { struct Header *curr; curr = st_Head; while(curr) { if(curr == h) return(1); curr = curr->Next; } return(0); } /* * Hex and ascii dump the memory */ static void OutputMemory(struct Header *h) { OutputFortification((unsigned char*)h + sizeof(struct Header) + FORTIFY_BEFORE_SIZE, 0, h->Size); } /* * Output the header... */ static void OutputHeader(struct Header *h) { sprintf(st_Buffer, "%11p %8ld %s.%ld (%d)\n", (unsigned char*)h + sizeof(struct Header) + FORTIFY_BEFORE_SIZE, (unsigned long)h->Size, h->File, h->Line, h->Scope); st_Output(st_Buffer); } static void OutputLastVerifiedPoint() { sprintf(st_Buffer, "\nLast Verified point: %s.%ld\n", st_LastVerifiedFile, st_LastVerifiedLine); st_Output(st_Buffer); } #endif /* FORTIFY */