From owner-freebsd-hackers Thu Sep 18 10:30:37 1997 Return-Path: Received: (from root@localhost) by hub.freebsd.org (8.8.7/8.8.7) id KAA21247 for hackers-outgoing; Thu, 18 Sep 1997 10:30:37 -0700 (PDT) Received: from citadel.cdsec.com (citadel.cdsec.com [192.96.22.18]) by hub.freebsd.org (8.8.7/8.8.7) with ESMTP id KAA21232 for ; Thu, 18 Sep 1997 10:30:27 -0700 (PDT) Received: (from nobody@localhost) by citadel.cdsec.com (8.8.5/8.6.9) id TAA18982; Thu, 18 Sep 1997 19:35:35 +0200 (SAT) Received: by citadel via recvmail id 18949; Thu Sep 18 19:35:08 1997 by gram.cdsec.com (8.8.5/8.8.5) id TAA00606; Thu, 18 Sep 1997 19:22:11 +0200 (SAT) From: Graham Wheeler Message-Id: <199709181722.TAA00606@cdsec.com> Subject: Re: Bug in malloc/free (was: Memory leak in getservbyXXX?) To: phk@critter.freebsd.dk (Poul-Henning Kamp) Date: Thu, 18 Sep 1997 19:22:10 +0200 (SAT) Cc: gram@gram.cdsec.com (Graham Wheeler), hackers@freebsd.org In-Reply-To: <10666.874602560@critter.freebsd.dk> from "Poul-Henning Kamp" at Sep 18, 97 07:09:20 pm X-Mailer: ELM [version 2.4 PL25-h4.1] MIME-Version: 1.0 Content-Type: text/plain; charset=US-ASCII Content-Transfer-Encoding: 7bit Sender: owner-freebsd-hackers@freebsd.org X-Loop: FreeBSD.org Precedence: bulk Howdy again > >the problem only started when we moved to from FreeBSD 2.1 to 2.2.2, > >is why I suggested that the bug may be in the malloc code. Let me state > >quite clearly though that this is a suggestion, certainly not an accusation. > >I'm just running out of things to suspect... > > I'm not taking it as such, don't worry :-) It's just that my work on > phkmalloc has taught me far more about how subtle malloc related problems > can manifest themselves, and I now realize why Purify costs what it does. > If I'd written it, I wouldn't charge any less. Well, then maybe you'll find the code below useful. It isn't Purify, but then it doesn't cost anything near the price 8-). I'd use it myself now, except that these days I write everything in C++. Mostly I'm happy about that, but not right now... ------------ gwdebug.h --------------------------------------------------- /* * A drop-in debugging library for C * * (c) 1994 by Graham Wheeler, All Rights Reserved * * This code may be freely redistributed, provided that it is * not modified in any way. If you make changes to this code * that you feel are useful, and would like to see them * incorporated, please send mail to gram@cdsec.com */ #ifndef __GWDEBUG_H__ #define __GWDEBUG_H__ #ifdef GW_DEBUG /* Memory debugging */ /* undefine them first in case we included heap.h */ #undef malloc #undef calloc #undef realloc #undef free extern void *my_malloc(unsigned n, char *f, int l); extern void *my_calloc(unsigned n, char *f, int l); extern void *my_realloc(void *p, unsigned n, char *f, int l); extern void my_free(void *p, char *f, int l); #define malloc(n) my_malloc(n, __FILE__, __LINE__) #define calloc(n) my_calloc(n, __FILE__, __LINE__) #define realloc(p,n) my_realloc(p, n, __FILE__, __LINE__) #define free(p) my_free(p, __FILE__, __LINE__) /* String and memory debugging */ extern char *my_memcpy(char *d, int hint, int limit, char *s, int shint, int flag, char *f, int l); extern char *my_strcpy(char *d, int hint, int limit, char *s, int shint, int flag, char *f, int l); extern char *my_memset(char *d, int hint, int limit, char c, char *f, int l); extern int my_memcmp(char *s1, int siz1, char *s2, int siz2, int n, int flag, char *f, int l); extern int my_strcmp(char *s1, int siz1, char *s2, int siz2, int n, int flag, char *f, int l); extern int my_strlen(char *s, int siz, char *f, int l); extern char *my_strdup(char *s, int siz, int n, char *f, int l); extern char *my_strstr(char *s1, int siz1, char *s2, int siz2, char *f, int l); extern char *my_strpbrk(char *s1, int siz1, char *s2, int siz2, char *f, int l); extern char *my_strchr(char *s, int siz, int c, char *f, int l); extern char *my_strrchr(char *s, int siz, int c, char *f, int l); extern int my_strspn(char *s1, int siz1, char *s2, int siz2, char *f, int l); extern int my_strcspn(char *s1, int siz1, char *s2, int siz2, char *f, int l); #define strcpy(d,s) my_strcpy(d, sizeof(d), 0, s, sizeof(s), 0, __FILE__, __LINE__) #define stpcpy(d,s) my_strcpy(d, sizeof(d), 0, s, sizeof(s), 2, __FILE__, __LINE__) #define strncpy(d,s,n) my_strcpy(d, sizeof(d), n, s, sizeof(s), 0, __FILE__, __LINE__) #define strcat(d,s) my_strcpy(d, sizeof(d), 0, s, sizeof(s), 4, __FILE__, __LINE__) #define strncat(d,s,n) my_strcpy(d, sizeof(d), n, s, sizeof(s), 4, __FILE__, __LINE__) #define memcpy(d,s,n) my_memcpy(d, sizeof(d), n, s, sizeof(s), 0, __FILE__, __LINE__) #define memmove(d,s,n) my_memcpy(d, sizeof(d), n, s, sizeof(s), 1, __FILE__, __LINE__) #define memset(d,c,n) my_memset(d, sizeof(d), n, c, __FILE__, __LINE__) #define strcmp(s1,s2) my_strcmp(s1, sizeof(s1), s2, sizeof(s2), 0, 0, __FILE__, __LINE__) #define strncmp(s1,s2,n) my_strcmp(s1, sizeof(s1), s2, sizeof(s2), n, 0, __FILE__, __LINE__) #define memcmp(s1,s2,n) my_memcmp(s1, sizeof(s1), s2, sizeof(s2), n, 1, __FILE__, __LINE__) #define strlen(s) my_strlen(s, sizeof(s), __FILE__, __LINE__) #define strdup(s) my_strdup(s, sizeof(s), __FILE__, __LINE__) #define strstr(s1,s2) my_strstr(s1, sizeof(s1), s2, sizeof(s2), __FILE__, __LINE__) #define strpbrk(s1,s2) my_strpbrk(s1, sizeof(s1), s2, sizeof(s2), __FILE__, __LINE__) #define strchr(s,c) my_strchr(s, sizeof(s1), c, __FILE__, __LINE__) #define strrchr(s,c) my_strrchr(s, sizeof(s1), c, __FILE__, __LINE__) #define strspn(s1,s2) my_strspn(s1, sizeof(s1), s2, sizeof(s2), __FILE__, __LINE__) #define strcspn(s1,s2) my_strcspn(s1, sizeof(s1), s2, sizeof(s2), __FILE__, __LINE__) /* File debugging */ extern FILE *my_fopen(char *n, char *m, char *f, int l); extern int my_fclose(FILE *fp, char *f, int l); extern int my_open(char *n, int m, int a, char *f, int l); extern int my_close(int h, char *f, int l); extern int my_dup(int h, char *f, int l); extern int my_read(int h, void *buf, unsigned len, int hint, char *f, int l); extern size_t my_fread(void *buf, size_t size, size_t n, FILE *fp, int space_avail, char *f, int l); extern char *my_fgets(void *buf, int n, FILE *fp, int space_avail, char *f, int l); #define fopen(n,m) my_fopen(n,m,__FILE__,__LINE__) #define fclose(f) my_fclose(f,__FILE__,__LINE__) #define open(n,m,a) my_open(n,m,a,__FILE__,__LINE__) #define close(h) my_close(h,__FILE__,__LINE__) #define dup(h) my_dup(h,__FILE__,__LINE__) #define read(h,b,n) my_read(h, b, n, sizeof(b), __FILE__, __LINE__) #define fread(b,s,n,f) my_fread(b, s, n, f, sizeof(b), __FILE__, __LINE__) #define fgets(b,n,f) my_fgets(b, n, f, sizeof(b), __FILE__, __LINE__) #endif /* GW_DEBUG */ #endif /* __GWDEBUG_H__ */ ---------- gwdebug.c ------------------------------------------------------- /* * A drop-in debugging library for C v2.0 * * (c) 1994 by Graham Wheeler, All Rights Reserved * * This code may be freely redistributed, provided that it is * not modified in any way. If you make changes to this code * that you feel are useful, and would like to see them * incorporated, please send mail to gram@cdsec.com * * Only change since v1.1 is that DOS support has been removed. * * This library provides drop-in wrappers for the following * common C library routines: * * Memory allocation routines: * malloc(), calloc(), free(), realloc() * * String and memory routines: * strcpy(), strncpy(), memcpy(), memset(), memmove(), * strcmp(), strncmp(), memcmp(), strlen(), strdup(), * strstr(), strpbrk(), stpcpy(), strcat(), strncat(), * strchr(), strrchr(), strspn(), strcspn() * * File I/O routines: * open(), close(), dup(), fopen(), fclose(), * read(), fread(), fgets() * * The library catches: * * - failed mallocs/callocs/reallocs and bad frees * - memory and file leaks * - some bad parameters passed to these routines * * In addition: * * - some overruns of dynamic, global and auto buffers are caught, * reported and recovered from (memcpy, memset, * strcpy, strncpy, stpcpy, read, fread, fgets) * - some potential overruns are warned about * - attempts to memcpy/strcpy/etc uninitialised memory is reported * - all overruns of dynamic buffers are reported when the memory * is freed. Overruns of less than 5 bytes will not clobber the * heap. * * To use the library, link this file with your application. * Add a line `#include "gwdebug.h" to each source file of * your application, AFTER any other #includes. * Then compile with GW_DEBUG defined to use the library. * * You can define DEBUG_LOG to be a log file name; else * standard error is used. * */ #ifdef GW_DEBUG #include #include #include #include #include #include static FILE *logfile = NULL; static char *store_name(char *name); static void my_initialise(void); static void my_report(void); /*******************************/ /* Memory allocation debugging */ /*******************************/ #define MAGIC (0x24681357L) /* Freeing a blk_info header will usually result in the memory manager using the first few bytes to store the block on the free list. The fields at the start of blk_info have been chosen to be the ones we don't mind being clobbered. */ typedef struct { long magic; void *next; long nbytes; char *file; short line; } blk_info; #define GET_BLKP(p, o) (( (blk_info *)(p) ) + (o)) #define GET_BLK(p) (blk_info *)GET_BLKP(p, -1) #define GET_DATA(p) ((void *)GET_BLKP(p, 1)) #define BUMPSIZE(n) ((n)+sizeof(blk_info) + sizeof(long)) #define SET_ENDMAGIC(p,n) *(long *)( ((char *)p)+n ) = MAGIC #define TST_ENDMAGIC(p,n) (*(long *)( ((char *)p)+n ) == MAGIC) void my_free(void *p, char *f, int l); static void *heap_list_head = NULL; void my_memory_report(int is_last) { void *p; /* walk dat list... */ p = heap_list_head; if (p) { fprintf(logfile,is_last ? "MEMORY LEAKS:\n" : "Allocated Memory Blocks:\n"); while (p) { blk_info *bp = GET_BLK(p); fprintf(logfile,"\tSize %8ld File %16s Line %d\n", bp->nbytes, bp->file, bp->line); p = bp->next; } } } static void log_alloc(void *rtn, unsigned long n, char *f, int l) { char *savedname; blk_info *bp = (blk_info *)rtn; assert(rtn); if (!logfile) my_initialise(); /* Get the pointer that is returned to the user */ rtn = GET_DATA(rtn); /* Save the size information */ bp->nbytes = n; /* Prepend to front of heap list */ bp->next = heap_list_head; heap_list_head = rtn; /* Save file name and line number, and put in bounding magic markers */ bp->file = store_name(f); bp->line = l; bp->magic = MAGIC; SET_ENDMAGIC(rtn, n); } void *my_calloc(unsigned n, char *f, int l) { /* Allocate the memory with space enough for our info */ void *rtn = calloc(BUMPSIZE(n), 1); log_alloc((void *)rtn, (unsigned long)n, f, l); return GET_DATA(rtn); } void *my_malloc(unsigned n, char *f, int l) { void *rtn = my_calloc(n,f,l); if (n >= sizeof(long)) *((unsigned long *)rtn) = MAGIC; /* mark as unitialised */ return rtn; } static int log_free(void *p, char *f, int l) { void *tmp, *last = NULL; blk_info *bp; if (!logfile) my_initialise(); /* Search the list for the block */ tmp = heap_list_head; while (tmp) { bp = GET_BLK(tmp); if (tmp == p) break; if (bp->magic != MAGIC) goto error; else { last = tmp; tmp = bp->next; } } if (tmp) { if (!TST_ENDMAGIC(p, bp->nbytes)) fprintf(logfile,"Block of size %ld allocated at %s, line %d, freed at %s, line %d, has been overrun\n", bp->nbytes, bp->file, bp->line, f, l); /* Unlink from chain */ if (last==NULL) heap_list_head = bp->next; else (GET_BLK(last))->next = bp->next; /* save who freed */ bp->file = store_name(f); bp->line = l; /* trash contents */ if (bp->nbytes >= sizeof(long)) *((unsigned long *)GET_DATA(bp)) = MAGIC; return 0; } else bp = GET_BLK(p); error: fprintf(logfile,"Bad call to free from file %s, line %d\n",f,l); /* Next check can cause seg violation on some UNIXes; if so change 1 to 0 */ #if 1 if (bp && bp->magic==MAGIC) fprintf(logfile,"Possibly freed before at %s, line %d, size %ld\n", bp->file, bp->line, bp->nbytes); #endif return -1; } void my_free(void *p, char *f, int l) { if (log_free((void *)p, f, l) == 0) free(GET_BLK(p)); } static blk_info *my_find_block(void *p) { blk_info *bp = GET_BLK(p); void *tmp = heap_list_head; /* Search the list for the block. We don't just check for the magic number under UNIX as this can cause a segmentation violation */ while (tmp) { if ((void *)tmp == (void *)p) return GET_BLK(p); tmp = GET_BLK(tmp)->next; } return NULL; } static int my_sizehint(void *p, int size) { void *tmp; blk_info *bp; if (p == NULL) return -1; bp = my_find_block(p); if (bp) { assert(size == sizeof(char *)); return (int)(bp->nbytes); } else if (size == sizeof(char *)) return -1; /* no clues */ else return size; } /*****************************/ /* String function debugging */ /*****************************/ /* flags for additional checks */ #define INIT1 1 /* arg s1 must be initialised */ #define INIT2 2 /* arg s2 must be initialised */ #define NULLT 4 /* must be initialised with NUL-terminated strings */ /* Validate a single pointer arg */ static int my_init_check(char *p, int space, int nul) { blk_info *bp = my_find_block(p); if (bp) { if (bp->nbytes>=sizeof(long) && *((unsigned long *)p)==MAGIC) return 0; return 1; } else if (space >= 0 && nul) { int i; for (i = 0; i < space; i++) if (p[i] == 0) return 1; return 0; } return 1; /* we don't know, so we assume OK */ } static int my_validate1(char *name, char *s, int space, char *f, int l, unsigned flags) { if (s==NULL) { fprintf(logfile,"%s(NULL) at file %s, line %d\n", name, f, l); return -1; } else if (flags & INIT1) { if (my_init_check(s, space, (flags&NULLT)!=0) == 0) { fprintf(logfile,"%s(uninitialised) at file %s, line %d\n", name, f, l); return -1; } } return 0; } /* Validate a pair of pointer args */ static int my_validate2(char *name, char *s1, int siz1, char *s2, int siz2, char *f, int l, unsigned flags) { /* Just validate arguments */ if (s1==NULL && s2==NULL) fprintf(logfile,"%s(NULL,NULL) at file %s, line %d\n", name, f, l); else if (s1==NULL) fprintf(logfile,"%s(NULL,...) at file %s, line %d\n", name, f, l); else if (s2==NULL) fprintf(logfile,"%s(...,NULL) at file %s, line %d\n", name, f, l); else if ((flags&INIT1) && my_init_check(s1, siz1, (flags&NULLT)!=0) == 0) fprintf(logfile,"%s(invalid,...) at file %s, line %d\n", name, f, l); else if ((flags&INIT2) && my_init_check(s2, siz2, (flags&NULLT)!=0) == 0) fprintf(logfile,"%s(...,invalid) at file %s, line %d\n", name, f, l); else return 0; return -1; } /* Handler for strcpy, stpcpy, strncpy, strcat, strncat, memcpy, and memmove */ char *my_copy(char *d, int space_avail, int limit, char *s, int sspace, int flag, char *f, int l, unsigned strflags) { char *rtn = d; int space_needed, i, dlen; if (!logfile) my_initialise(); space_avail = my_sizehint(d, space_avail); sspace = my_sizehint(s, sspace); if (my_validate2(strflags?"String copy":"Mem copy", d, space_avail, s, sspace, f,l,INIT2|strflags)) return d; /* how much space do we need? */ space_needed = limit ? limit : (strflags ? (strlen(s)+1) : space_avail); /* Are we cat'ing? If so, how long is the existing stuff? */ dlen = (flag&4) ? strlen(d) : 0; /* space enuf? */ limit = space_needed; if (space_avail >= 0 && space_needed > (space_avail-dlen)) { fprintf(logfile,"String/memory copy overrun at file %s, line %d - truncating\n\ttarget space %d, source length %d\n", f, l, (space_avail-dlen), space_needed); limit = space_avail-dlen; } else if (space_avail >= 0 && sspace >= 0 && sspace > (space_avail-dlen)) fprintf(logfile,"Potential string/memory copy overrun at file %s, line %d\n\ttarget space %d, source size %d\n", f, l, (space_avail-dlen), sspace); d += dlen; if (s=d && (flag&1)==0) /* forward copy would clobber source */ fprintf(logfile,"String/memory copy clobber in %s, line %d\n", f, l); memmove(d,s,limit); return rtn + ( (flag & 2) ? (limit-1) : 0); /* hax for stpcpy */ } char *my_memcpy(char *d, int space_avail, int limit, char *s, int sspace, int flag, char *f, int l) { return my_copy(d, space_avail, limit, s, sspace, flag, f, l, 0); } char *my_strcpy(char *d, int space_avail, int limit, char *s, int sspace, int flag, char *f, int l) { return my_copy(d, space_avail, limit, s, sspace, flag, f, l, NULLT); } char *my_memset(char *d, int space_avail, int limit, char c, char *f, int l) { char *rtn = d; int space_needed, i; if (!logfile) my_initialise(); /* how much space do we have? */ space_avail = my_sizehint(d, space_avail); if (my_validate1("memset", d, space_avail, f, l, 0)) goto done; /* space enuf? */ if (space_avail >= 0 && limit > space_avail) { fprintf(logfile,"memset overrun at file %s, line %d - truncating\n\ttarget length %d, source length %d\n", f, l, space_avail, limit); limit = space_avail; } memset(d,limit,c); done: return rtn; } static int my_compare(char *s1, int siz1, char *s2, int siz2, int n, int flag, char *f, int l, unsigned strflags) { if (!logfile) my_initialise(); /* Just validate arguments */ siz1 = my_sizehint(s1, siz1); siz2 = my_sizehint(s2, siz2); if (my_validate2("memcmp", s1, siz1, s2, siz2, f, l, INIT1|INIT2|strflags)) return ((s1?1:0)-(s2?1:0)); if (n==0) return strcmp(s1,s2); else if (flag) return strncmp(s1,s2,n); else return memcmp(s1,s2,n); } int my_memcmp(char *s1, int siz1, char *s2, int siz2, int n, int flag, char *f, int l) { return my_compare(s1, siz1, s2, siz2, n, flag, f, l, 0); } int my_strcmp(char *s1, int siz1, char *s2, int siz2, int n, int flag, char *f, int l) { return my_compare(s1, siz1, s2, siz2, n, flag, f, l, NULLT); } int my_strlen(char *s, int siz, char *f, int l) { if (!logfile) my_initialise(); siz = my_sizehint(s, siz); return my_validate1("strlen", s, siz, f, l, INIT1|NULLT) ? 0 : strlen(s); } char *my_strdup(char *s, int siz, char *f, int l) { if (!logfile) my_initialise(); siz = my_sizehint(s, siz); if (my_validate1("strdup", s, siz, f, l, INIT1|NULLT)==0) { int n = strlen(s)+1; char *rtn = my_calloc(n,f,l); assert(rtn); my_memcpy(rtn, n, n, s, n, 0, f, l); return rtn; } return NULL; } char *my_strstr(char *s1, int siz1, char *s2, int siz2, char *f, int l) { if (!logfile) my_initialise(); siz1 = my_sizehint(s1, siz1); siz2 = my_sizehint(s2, siz2); return my_validate2("strstr", s1, siz1, s2, siz2, f, l, INIT1|INIT2|NULLT) ? NULL : strstr(s1,s2); } char *my_strpbrk(char *s1, int siz1, char *s2, int siz2, char *f, int l) { if (!logfile) my_initialise(); siz1 = my_sizehint(s1, siz1); siz2 = my_sizehint(s2, siz2); return my_validate2("strpbrk", s1, siz1, s2, siz2, f, l, INIT1|INIT2|NULLT) ? NULL : strpbrk(s1,s2); } char *my_strchr(char *s, int siz, int c, char *f, int l) { if (!logfile) my_initialise(); siz = my_sizehint(s, siz); return my_validate1("strchr", s, siz, f, l, INIT1|NULLT) ? NULL : strchr(s,c); } char *my_strrchr(char *s, int siz, int c, char *f, int l) { if (!logfile) my_initialise(); siz = my_sizehint(s, siz); return my_validate1("strrchr", s, siz, f, l, INIT1|NULLT) ? NULL : strrchr(s,c); } int my_strspn(char *s1, int siz1, char *s2, int siz2, char *f, int l) { if (!logfile) my_initialise(); siz1 = my_sizehint(s1, siz1); siz2 = my_sizehint(s2, siz2); return my_validate2("strspn", s1, siz1, s2, siz2, f, l, INIT1|INIT2|NULLT) ? 0 : strspn(s1,s2); } int my_strcspn(char *s1, int siz1, char *s2, int siz2, char *f, int l) { if (!logfile) my_initialise(); siz1 = my_sizehint(s1, siz1); siz2 = my_sizehint(s2, siz2); return my_validate2("strcspn", s1, siz1, s2, siz2, f, l, INIT1|INIT2|NULLT) ? 0 : strcspn(s1,s2); } /***************************/ /* File function debugging */ /***************************/ #ifndef MAX_FILES #define MAX_FILES 64 #endif typedef struct { char *name; char *fname; int line; FILE *fp; } file_info_t; static file_info_t file_info[MAX_FILES]; #include FILE *my_fopen(char *n, char *m, char *f, int l) { FILE *rtn; if (!logfile) my_initialise(); rtn = fopen(n,m); if (rtn) { int h = fileno(rtn); if (file_info[h].line>0) /* shouldn't happen unless system fopen is broken */ fprintf(logfile,"%2d (%s) fopened at %s, line %d was already opened at %s, line %d\n", h, n, f, l, file_info[h].fname, file_info[h].line); #ifdef GW_TRACE else fprintf(logfile,"File %2d (%s) fopened at %s, line %d\n", h, n, f, l); #endif file_info[h].name = store_name(n); file_info[h].fname = store_name(f); file_info[h].line = l; file_info[h].fp = rtn; } else fprintf(logfile,"fopen of %s at %s, line %d failed!\n", n, f, l); return rtn; } int my_fclose(FILE *fp, char *f, int l) { if (!logfile) my_initialise(); if (fp) { int h = fileno(fp); if (file_info[h].line <= 0) fprintf(logfile,"Bad close(%d) at %s, line %d; already closed at %s, line %d\n", h, f, l, file_info[h].fname, -file_info[h].line); else { #ifdef GW_TRACE fprintf(logfile,"File %2d (%s) opened at %s, line %d fclosed at %s, line %d\n", h, file_info[h].name, file_info[h].fname, file_info[h].line, f, l); #endif file_info[h].fname = store_name(f); file_info[h].line = -l; return fclose(fp); } } else fprintf(logfile,"Illegal fclose(NULL) by %s line %d\n", f, l); return 0; } int my_open(char *n, int m, int a, char *f, int l) { int rtn; if (!logfile) my_initialise(); rtn = open(n,m,a); if (rtn >= 0) { if (file_info[rtn].line>0) /* shouldn't happen unless system fopen is broken */ fprintf(logfile,"%2d (%s) fopened at %s, line %d was already open at %s, line %d\n", rtn, n, f, l, file_info[rtn].fname, file_info[rtn].line); #ifdef GW_TRACE else fprintf(logfile,"File %2d (%s) fopened at %s, line %d\n", rtn, n, f, l); #endif file_info[rtn].name = store_name(n); file_info[rtn].fname = store_name(f); file_info[rtn].line = l; file_info[rtn].fp = NULL; } else fprintf(logfile,"open of %s at %s, line %d failed!\n\t(%s)\n", n, f, l, strerror(errno)); return rtn; } int my_close(int h, char *f, int l) { if (!logfile) my_initialise(); if (h>=0) { if (file_info[h].line <= 0) fprintf(logfile,"Bad close(%d) by %s, line %d; already closed at %s, line %d\n", h, f, l, file_info[h].fname, -file_info[h].line); else { #ifdef GW_TRACE fprintf(logfile,"File %2d (%s) opened at %s, line %d, fclosed at %s, line %d\n", h, file_info[h].name, file_info[h].fname, file_info[h].line, f, l); #endif file_info[h].fname = store_name(f); file_info[h].line = -l; return close(h); } } else fprintf(logfile,"Illegal close(%d) by %s line %d\n", h, f, l); return 0; } int my_dup(int h, char *f, int l) { int rtn = -1; if (!logfile) my_initialise(); if (h>=0) { if (file_info[h].line <= 0) fprintf(logfile,"Illegal dup(%d) by %s line %d\n", h, f, l); else { rtn = dup(h); if (rtn >= 0) { #ifdef GW_TRACE fprintf(logfile,"File %2d (%s) opened at %s, line %d, dup'ed to %d at %s, line %d\n", h, file_info[h].name, file_info[h].fname, file_info[h].line, rtn, f, l); #endif file_info[rtn].name = file_info[h].name; file_info[rtn].fname = store_name(f); file_info[rtn].line = l; file_info[rtn].fp = NULL; } else fprintf(logfile,"dup(%d) by %s line %d failed!\n\t(%s)\n", h, f, l, strerror(errno)); } } else fprintf(logfile,"Illegal dup(%d) by %s line %d!\n", h, f, l); return rtn; } static int my_readcheck(char *name, void *buf, unsigned len, int space_avail, char *f, int l) { if (buf==NULL) { fprintf(logfile,"%s with NULL buffer by %s line %d!\n", name, f, l); return 0; } space_avail = my_sizehint(buf, space_avail); if (space_avail >= 0 && space_avail < len) { fprintf(logfile,"%s with count (%d) > available space (5d) by %s line %d!\n", name, len, space_avail, f, l); return space_avail; } else return len; } int my_read(int h, void *buf, unsigned len, int space_avail, char *f, int l) { if (!logfile) my_initialise(); len = my_readcheck("read", buf, len, space_avail, f, l); return len ? read(h,buf,len) : 0; } size_t my_fread(void *buf, size_t size, size_t n, FILE *fp, int space_avail, char *f, int l) { if (!logfile) my_initialise(); if (size==0) { fprintf(logfile,"fread with size zero at %s line %d!\n", f, l); return 0; } n = my_readcheck("fread", buf, n*size, space_avail, f, l) / size; return n ? fread(buf,size,n,fp) : 0; } char *my_fgets(void *buf, int n, FILE *fp, int space_avail, char *f, int l) { if (!logfile) my_initialise(); n = my_readcheck("fgets", buf, n, space_avail, f, l); return n ? fgets(buf,n,fp) : buf; } static void my_file_report(int is_last) { int i, done_heading=0; for (i=0;i<40;i++) { if (file_info[i].line>0) { if (is_last && !done_heading) { done_heading = 1; fprintf(logfile,"FILE LEAKS:\n"); } fprintf(logfile,"\tFile `%s' (handle %d) opened at %s, line %d\n", file_info[i].name,i,file_info[i].fname, file_info[i].line); } } } /*************************/ /* Building on the past! */ /*************************/ void *my_realloc(void *p, unsigned n, char *f, int l) { void *rtn = my_calloc(n,f,l); if (p) { my_memcpy(rtn, n, 0, p, 0, 0, f, l); my_free(p,f,l); } return rtn; } /**********************/ /* Managed name store */ /**********************/ #ifndef MAX_NAMES #define MAX_NAMES 32 #endif #ifndef MAX_NAME_LEN #define MAX_NAME_LEN 16 #endif static char file_names[MAX_NAMES][MAX_NAME_LEN]; static int filename_index = 0; static char *store_name(char *name) { int filename_index; for (filename_index=0 ; filename_index #include #include #include #include #include #include "gwdebug.h" typedef void *heap_ptr; void leakTest(void) { (void)malloc(10); } void badFreeTest(void) { free((void *)26304); } void doubleFreeTest(void) { heap_ptr p = malloc(10); free(p); free(p); } void overrunTest1(void) { heap_ptr p = malloc(10); char *d = (char *)p, *s = "Hello World"; while ((*d++ = *s++) != 0); free(p); } void overrunTest2(void) { heap_ptr *p = malloc(10); strcpy((char *)p, "Hello world"); free(p); } void overrunTest3(void) { char dest[8]; strcpy(dest,"Hello world"); strncpy(dest,"Hello world",8); strncpy(dest,"Hello world",9); } void fileTest(void) { FILE *fp; int h = open("junk", O_CREAT, S_IWRITE), h2; close(h); h2 = dup(h); close(h); close(h2); fp = fopen("leak", "w"); (void)fp; } void initTest(void) { /* the tests here marked ** will only succeed is junk[] has no zero bytes in it after being created on the stack; luck of the draw, unfortunately */ heap_ptr *p = malloc(10); char junk[10]; /**/(void)strlen(junk); (void)strlen((char *)p); (void)strcpy(junk, (char *)p); (void)memcpy(junk, (char *)p, 10); /**/(void)strcpy((char *)p, junk); (void)memcpy((char *)p, junk, 10); /* this is undetectable */ free(p); } void main(void) { int ch = 0; while (ch != 'q') { printf("Enter:\n\tq - to quit\n\t1 - for leak test\n"); printf("\t2 - for overrun test 1\n\t3 - for overrun test 2\n"); printf("\t4 - for overrun test 3\n"); printf("\t5 - for bad free\n\t6 - for double free\n"); printf("\t7 - for file test\n\t8 - for uninit test\n"); ch=getchar(); switch(ch) { case '1': leakTest(); break; case '2': overrunTest1(); break; case '3': overrunTest2(); break; case '4': overrunTest3(); break; case '5': badFreeTest(); break; case '6': doubleFreeTest(); break; case '7': fileTest(); break; case '8': initTest(); break; } while (getchar() != '\n'); } } --------- Makefile ------------------------------------------------------ DEBUG=-DGW_DEBUG #DEBUG= CC=cc CFLAGS=-g $(DEBUG) LOG=\"gwdebug.log\" all: gwtest gwtest: gwtest.o gwdebug.o $(CC) $(CFLAGS) gwtest.o gwdebug.o -o gwtest gwtest.o: gwtest.c gwdebug.h $(CC) -c $(CFLAGS) gwtest.c gwdebug.o: gwdebug.c gwdebug.h $(CC) -c $(CFLAGS) -DDEBUG_LOG=$(LOG) gwdebug.c -- Dr Graham Wheeler E-mail: gram@cdsec.com Citadel Data Security Phone: +27(21)23-6065/6/7 Internet/Intranet Network Specialists Mobile: +27(83)-253-9864 Firewalls/Virtual Private Networks Fax: +27(21)24-3656 Data Security Products WWW: http://www.cdsec.com/