Carried Nelson's fix from DBM_1_6_BRANCH (revision 3.15.2.5) to the trunk:
authorwtchang%redhat.com
Thu, 10 Mar 2005 22:04:02 +0000
changeset 5527 f8d78a4b61aa9405cdf92c95402d5d9a42935690
parent 5526 b74db8a01588d471bc12f4b02cc9b5245322252c
child 5530 0cd80afabc117c1655e5dc1a97e8cad95987628f
push idunknown
push userunknown
push dateunknown
bugs230159
Carried Nelson's fix from DBM_1_6_BRANCH (revision 3.15.2.5) to the trunk: The error paths for __hash_open() had leaks, double-frees, and potential crashes from using free'd memory. This patch fixes them. Bug 230159. r=wtc. sr=relyea.
dbm/src/hash.c
--- a/dbm/src/hash.c
+++ b/dbm/src/hash.c
@@ -139,27 +139,29 @@ extern DB *
 {
 	HTAB *hashp=NULL;
 	struct stat statbuf;
 	DB *dbp;
 	int bpages, hdrsize, new_table, nsegs, save_errno;
 
 	if ((flags & O_ACCMODE) == O_WRONLY) {
 		errno = EINVAL;
-		RETURN_ERROR(ENOMEM, error0);
+		return NULL;
 	}
 
 	/* zero the statbuffer so that
 	 * we can check it for a non-zero
 	 * date to see if stat succeeded
 	 */
 	memset(&statbuf, 0, sizeof(struct stat));
 
-	if (!(hashp = (HTAB *)calloc(1, sizeof(HTAB))))
-		RETURN_ERROR(ENOMEM, error0);
+	if (!(hashp = (HTAB *)calloc(1, sizeof(HTAB)))) {
+		errno = ENOMEM;
+		return NULL;
+	}
 	hashp->fp = NO_FILE;
 	if(file)
 		hashp->filename = strdup(file);
 
 	/*
 	 * Even if user wants write only, we need to be able to read
 	 * the actual file, so we need to open it read/write. But, the
 	 * field in the hashp structure needs to be accurate so that
@@ -179,105 +181,66 @@ extern DB *
 		/* check for a zero length file and delete it
 	 	 * if it exists
 	 	 */
 		new_table = 1;
 	}
 	hashp->file_size = statbuf.st_size;
 
 	if (file) {				 
-
 #if defined(_WIN32) || defined(_WINDOWS) || defined (macintosh)  || defined(XP_OS2)
 		if ((hashp->fp = DBFILE_OPEN(file, flags | O_BINARY, mode)) == -1)
-			RETURN_ERROR(errno, error0);
+			RETURN_ERROR(errno, error1);
 #else
- 	if ((hashp->fp = open(file, flags, mode)) == -1)
-		RETURN_ERROR(errno, error0);
-	(void)fcntl(hashp->fp, F_SETFD, 1);
-/* We can't use fcntl because of NFS bugs. SIGH */
-#if 0
-    {
-	struct flock fl;
-	memset(&fl, 0, sizeof(fl));
-	fl.l_type = F_WRLCK;
-	if (fcntl(hashp->fp, F_SETLK, &fl) < 0) {
-#ifdef DEBUG
-	    fprintf(stderr, "unable to open %s because it's locked (flags=0x%x)\n", file, flags);
-#endif
-	    RETURN_ERROR(EACCES, error1);
-	}
-    }
-#endif
-
+		if ((hashp->fp = open(file, flags, mode)) == -1)
+			RETURN_ERROR(errno, error1);
+		(void)fcntl(hashp->fp, F_SETFD, 1);
 #endif
 	}
 	if (new_table) {
 		if (!init_hash(hashp, file, (HASHINFO *)info))
 			RETURN_ERROR(errno, error1);
 	} else {
 		/* Table already exists */
 		if (info && info->hash)
 			hashp->hash = info->hash;
 		else
 			hashp->hash = __default_hash;
 
 		hdrsize = read(hashp->fp, (char *)&hashp->hdr, sizeof(HASHHDR));
-#if BYTE_ORDER == LITTLE_ENDIAN
-		swap_header(hashp);
-#endif
 		if (hdrsize == -1)
 			RETURN_ERROR(errno, error1);
 		if (hdrsize != sizeof(HASHHDR))
 			RETURN_ERROR(EFTYPE, error1);
+#if BYTE_ORDER == LITTLE_ENDIAN
+		swap_header(hashp);
+#endif
 		/* Verify file type, versions and hash function */
 		if (hashp->MAGIC != HASHMAGIC)
 			RETURN_ERROR(EFTYPE, error1);
 #define	OLDHASHVERSION	1
 		if (hashp->VERSION != HASHVERSION &&
 		    hashp->VERSION != OLDHASHVERSION)
 			RETURN_ERROR(EFTYPE, error1);
 		if (hashp->hash(CHARKEY, sizeof(CHARKEY)) != hashp->H_CHARKEY)
 			RETURN_ERROR(EFTYPE, error1);
-		if (hashp->NKEYS < 0) {
-		    /*
-		    ** OOPS. Old bad database from previously busted
-		    ** code. Blow it away.
-		    */
-		    close(hashp->fp);
-		    if (remove(file) < 0) {
-#if defined(DEBUG) && defined(XP_UNIX)
-			fprintf(stderr,
-				"WARNING: You have an old bad cache.db file"
-				" '%s', and I couldn't remove it!\n", file);
-#endif
-		    } else {
-#if defined(DEBUG) && defined(XP_UNIX)
-			fprintf(stderr,
-				"WARNING: I blew away your %s file because"
-				" it was bad due to a recently fixed bug\n",
-				file);
-#endif
-		    }
-		    RETURN_ERROR(ENOENT, error0);
-		}
+		if (hashp->NKEYS < 0) /* Old bad database. */
+			RETURN_ERROR(EFTYPE, error1);
 
 		/*
 		 * Figure out how many segments we need.  Max_Bucket is the
 		 * maximum bucket number, so the number of buckets is
 		 * max_bucket + 1.
 		 */
 		nsegs = (hashp->MAX_BUCKET + 1 + hashp->SGSIZE - 1) /
 			 hashp->SGSIZE;
 		hashp->nsegs = 0;
 		if (alloc_segs(hashp, nsegs))
-			/*
-			 * If alloc_segs fails, table will have been destroyed
-			 * and errno will have been set.
-			 */
-			RETURN_ERROR(ENOMEM, error0);
+			/* If alloc_segs fails, errno will have been set.  */
+			RETURN_ERROR(errno, error1);
 		/* Read in bitmaps */
 		bpages = (hashp->SPARES[hashp->OVFL_POINT] +
 		    (hashp->BSIZE << BYTE_SHIFT) - 1) >>
 		    (hashp->BSHIFT + BYTE_SHIFT);
 
 		hashp->nmaps = bpages;
 		(void)memset(&hashp->mapp[0], 0, bpages * sizeof(uint32 *));
 	}
@@ -291,68 +254,35 @@ extern DB *
 	hashp->new_file = new_table;
 #ifdef macintosh
 	hashp->save_file = file && !(hashp->flags & O_RDONLY);
 #else
 	hashp->save_file = file && (hashp->flags & O_RDWR);
 #endif
 	hashp->cbucket = -1;
 	if (!(dbp = (DB *)malloc(sizeof(DB)))) {
-		save_errno = errno;
-		hdestroy(hashp);
-		errno = save_errno;
-		RETURN_ERROR(ENOMEM, error0);
+		RETURN_ERROR(ENOMEM, error1);
 	}
 	dbp->internal = hashp;
 	dbp->close = hash_close;
 	dbp->del = hash_delete;
 	dbp->fd = hash_fd;
 	dbp->get = hash_get;
 	dbp->put = hash_put;
 	dbp->seq = hash_seq;
 	dbp->sync = hash_sync;
 	dbp->type = DB_HASH;
 
-#if 0
-#if defined(DEBUG) && !defined(_WINDOWS)
-{
-extern int MKLib_trace_flag;
-
-  if(MKLib_trace_flag)
-	(void)fprintf(stderr,
-"%s\n%s%lx\n%s%d\n%s%d\n%s%d\n%s%d\n%s%d\n%s%d\n%s%d\n%s%d\n%s%d\n%s%x\n%s%x\n%s%d\n%s%d\n",
-	    "init_htab:",
-	    "TABLE POINTER   ", (unsigned long) hashp,
-	    "BUCKET SIZE     ", hashp->BSIZE,
-	    "BUCKET SHIFT    ", hashp->BSHIFT,
-	    "DIRECTORY SIZE  ", hashp->DSIZE,
-	    "SEGMENT SIZE    ", hashp->SGSIZE,
-	    "SEGMENT SHIFT   ", hashp->SSHIFT,
-	    "FILL FACTOR     ", hashp->FFACTOR,
-	    "MAX BUCKET      ", hashp->MAX_BUCKET,
-	    "OVFL POINT	     ", hashp->OVFL_POINT,
-	    "LAST FREED      ", hashp->LAST_FREED,
-	    "HIGH MASK       ", hashp->HIGH_MASK,
-	    "LOW  MASK       ", hashp->LOW_MASK,
-	    "NSEGS           ", hashp->nsegs,
-	    "NKEYS           ", hashp->NKEYS);
-}
-#endif
-#endif /* 0 */
 #ifdef HASH_STATISTICS
 	hash_overflows = hash_accesses = hash_collisions = hash_expansions = 0;
 #endif
 	return (dbp);
 
 error1:
-	if (hashp != NULL)
-		(void)close(hashp->fp);
-
-error0:
-	free(hashp);
+	hdestroy(hashp);
 	errno = save_errno;
 	return (NULL);
 }
 
 static int
 hash_close(DB *dbp)
 {
 	HTAB *hashp;
@@ -415,21 +345,21 @@ init_hash(HTAB *hashp, const char *file,
 
 #if !defined(_WIN32) && !defined(_WINDOWS) && !defined(macintosh) && !defined(VMS) && !defined(XP_OS2)
 #if defined(__QNX__) && !defined(__QNXNTO__)
 		hashp->BSIZE = 512; /* preferred blk size on qnx4 */
 #else
 		hashp->BSIZE = statbuf.st_blksize;
 #endif
 
-       	/* new code added by Lou to reduce block
-       	 * size down below MAX_BSIZE
-       	 */
-       	if (hashp->BSIZE > MAX_BSIZE)
-       		hashp->BSIZE = MAX_BSIZE;
+		/* new code added by Lou to reduce block
+		 * size down below MAX_BSIZE
+		 */
+		if (hashp->BSIZE > MAX_BSIZE)
+			hashp->BSIZE = MAX_BSIZE;
 #endif
 		hashp->BSHIFT = __log2((uint32)hashp->BSIZE);
 	}
 
 	if (info) {
 		if (info->bsize) {
 			/* Round pagesize up to power of 2 */
 			hashp->BSHIFT = __log2(info->bsize);
@@ -449,25 +379,25 @@ init_hash(HTAB *hashp, const char *file,
 			if (info->lorder != BIG_ENDIAN &&
 			    info->lorder != LITTLE_ENDIAN) {
 				errno = EINVAL;
 				return (NULL);
 			}
 			hashp->LORDER = info->lorder;
 		}
 	}
-	/* init_htab should destroy the table and set errno if it fails */
+	/* init_htab sets errno if it fails */
 	if (init_htab(hashp, nelem))
 		return (NULL);
 	else
 		return (hashp);
 }
 /*
- * This calls alloc_segs which may run out of memory.  Alloc_segs will destroy
- * the table and set errno, so we just pass the error information along.
+ * This calls alloc_segs which may run out of memory.  Alloc_segs will 
+ * set errno, so we just pass the error information along.
  *
  * Returns 0 on No Error
  */
 static int
 init_htab(HTAB *hashp, int nelem)
 {
 	register int nbuckets, nsegs;
 	int l2;
@@ -1150,43 +1080,37 @@ extern uint32
 	n = hashp->hash(k, len);
 	bucket = n & hashp->HIGH_MASK;
 	if (bucket > (uint32)hashp->MAX_BUCKET)
 		bucket = bucket & hashp->LOW_MASK;
 	return (bucket);
 }
 
 /*
- * Allocate segment table.  On error, destroy the table and set errno.
+ * Allocate segment table.  On error, set errno.
  *
  * Returns 0 on success
  */
 static int
 alloc_segs(
 	HTAB *hashp,
 	int nsegs)
 {
 	register int i;
 	register SEGMENT store;
 
-	int save_errno;
-
 	if ((hashp->dir =
 	    (SEGMENT *)calloc((size_t)hashp->DSIZE, sizeof(SEGMENT *))) == NULL) {
-		save_errno = errno;
-		(void)hdestroy(hashp);
-		errno = save_errno;
+		errno = ENOMEM;
 		return (-1);
 	}
 	/* Allocate segments */
 	if ((store =
 	    (SEGMENT)calloc((size_t)nsegs << hashp->SSHIFT, sizeof(SEGMENT))) == NULL) {
-		save_errno = errno;
-		(void)hdestroy(hashp);
-		errno = save_errno;
+		errno = ENOMEM;
 		return (-1);
 	}
 	for (i = 0; i < nsegs; i++, hashp->nsegs++)
 		hashp->dir[i] = &store[i << hashp->SSHIFT];
 	return (0);
 }
 
 #if BYTE_ORDER == LITTLE_ENDIAN