standards/66531: _gettemp uses a far smaller set of filenames than documented and doesn't range check inputs

Jim Luther luther.j at apple.com
Tue May 11 09:00:44 PDT 2004


>Number:         66531
>Category:       standards
>Synopsis:       _gettemp uses a far smaller set of filenames than documented and doesn't range check inputs
>Confidential:   no
>Severity:       non-critical
>Priority:       medium
>Responsible:    freebsd-standards
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Tue May 11 09:00:40 PDT 2004
>Closed-Date:
>Last-Modified:
>Originator:     Jim Luther
>Release:        Mac OS X 10.3.3
>Organization:
Apple Computer, Inc.
>Environment:
Darwin luthji.apple.com 7.3.0 Darwin Kernel Version 7.3.0: Fri Mar  5 14:22:55 PST 2004; root:xnu/xnu-517.3.15.obj~4/RELEASE_PPC  Power Macintosh powerpc
>Description:
_gettemp is the common subroutine used by mktemp(), mkstemp(), mkstemps(), and mkdtemp() to get temporary file names, create temporary files, and create temporary directories. I found a couple of bugs in it:

1 - The suffix length (slen) parameter passed into it is not range changed -- it could be longer than the basename of the path, or it could be negative. This could cause mkstemps() to possibly create a temporary file in the wrong directory if path[strlen(path)] - slen is an 'X" character and the random character chosen happens to match another directory. For example:
    char template[] = "/X/template";
    suffixlen = 9;
    fd = mkstemps(template, suffixlen);
In the current code, the 'X' in the template would be replaced with a random character and if there were a directory in "/" with that character name, a file named "template" would be created in that directory.

If slen is negative, it could corrupt memory outside of the path buffer.

2 - If there's a filename collision, it only cycles through a subset of the possible filenames -- it does not try all permutations. The current code increments the characters until it hits the end of the padchar array -- it does not wrap around to the original random character. So, if by chance, this loop in _getttemp():
    /* Fill space with random characters */
    while (trv >= path && *trv == 'X') {
        rand = arc4random() % (sizeof(padchar) - 1);
        *trv-- = padchar[rand];
    }
happened to get random numbers that replaced all of the 'X' characters with 'z' (the last character in padchar array), the collision code would exit without trying any other filenames.

>How-To-Repeat:
I used this test program to detect the problem and ensure it is fixed:

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/syslimits.h>

#define XX_MAX 3844 /* maximum on UFS volume (62 * 62) */
//#define XX_MAX 1296 /* maximum on HFS case-insensitive volume (36 * 36) */

int mkstemp_test(void)
{
	char mkstemp_template[] = "/gettemp_test_dir/file_XX";
	char template[NAME_MAX];
	int i;
	int fd;

	i = 0;
	while ( 1 )
	{
		strcpy(template, mkstemp_template);
		fd = mkstemp(template);
		if ( fd >= 0 )
		{
			close(fd);
			if ( i >= XX_MAX )
			{
				fprintf(stdout, "mkstemp_test failed: created too many files?!?\n");
				return ( EXIT_FAILURE );
			}
		}
		else
		{
			if ( i == XX_MAX )
			{
				fprintf(stdout, "mkstemp_test passed\n");
				return ( EXIT_SUCCESS );
			}
			else
			{
				fprintf(stdout, "mkstemp_test failed when creating file #%d\n", i + 1);
				return ( EXIT_FAILURE );
			}
		}
		++i;
	}
}

int mkstemps_test(void)
{
	char mkstemps_template[] = "/gettemp_test_dir/file_XX.test";
	char template[NAME_MAX];
	int i;
	int fd;

	strcpy(template, mkstemps_template);
	fd = mkstemps(template, 13);
	if ( fd >= 0 )
	{
		fprintf(stdout, "mkstemps_test failed: should have detected too long suffixlen parameter\n");
		return ( EXIT_FAILURE );
	}

	strcpy(template, mkstemps_template);
	fd = mkstemps(template, -1);
	if ( fd >= 0 )
	{
		fprintf(stdout, "mkstemps_test failed: should have detected negative suffixlen parameter\n");
		return ( EXIT_FAILURE );
	}
	
	i = 0;
	while ( 1 )
	{
		strcpy(template, mkstemps_template);
		fd = mkstemps(template, 5);
		if ( fd >= 0 )
		{
			close(fd);
			if ( i >= XX_MAX )
			{
				fprintf(stdout, "mkstemps_test failed: created too many files?!?\n");
				return ( EXIT_FAILURE );
			}
		}
		else
		{
			if ( i == XX_MAX )
			{
				fprintf(stdout, "mkstemps_test passed\n");
				return ( EXIT_SUCCESS );
			}
			else
			{
				fprintf(stdout, "mkstemps_test failed when creating file #%d\n", i + 1);
				return ( EXIT_FAILURE );
			}
		}
		++i;
	}
}

int mkdtemp_test(void)
{
	char mkdtemp_template[] = "/gettemp_test_dir/dir_XX";
	char template[NAME_MAX];
	int i;
	char *result;

	i = 0;
	while ( 1 )
	{
		strcpy(template, mkdtemp_template);
		result = mkdtemp(template);
		if ( result != NULL )
		{
			if ( i >= XX_MAX )
			{
				fprintf(stdout, "mkdtemp_test failed: created too many directories?!?\n");
				return ( EXIT_FAILURE );
			}
		}
		else
		{
			if ( i == XX_MAX )
			{
				fprintf(stdout, "mkdtemp_test passed\n");
				return ( EXIT_SUCCESS );
			}
			else
			{
				fprintf(stdout, "mkdtemp_test failed when creating directory #%d\n", i + 1);
				return ( EXIT_FAILURE );
			}
		}
		++i;
	}
}

int main (int argc, const char * argv[])
{
	int err;
		
	err = mkdir("/gettemp_test_dir", 0700);
	if ( err == 0 )
	{
		err = mkstemp_test();
		if ( !err )
		{
			err = mkstemps_test();
			if ( !err )
			{
				err = mkdtemp_test();
				if ( !err )
				{
					exit(EXIT_SUCCESS);
				}
			}
		}
	}
	else
	{
		fprintf(stderr, "mkdir could not create '/gettemp_test_dir': delete it and try again\n");
	}
	exit(EXIT_FAILURE);
}

>Fix:
Here's the diffs from the patch used to fix this in Mac OS X. The file patched was (using the FreeBSD path) src/lib/libc/stdio/mktemp.c, v 1.28 so the diffs should work for FreeBSD, too.

Index: stdio/FreeBSD/mktemp.c
===================================================================
RCS file: /cvs/root/Libc/stdio/FreeBSD/mktemp.c,v
retrieving revision 1.2
retrieving revision 1.2.44.2
diff -u -d -b -r1.2 -r1.2.44.2
--- mktemp.c    2003/05/20 22:22:42     1.2
+++ mktemp.c    2004/03/16 23:05:15     1.2.44.2
@@ -106,13 +106,14 @@
        int domkdir;
        int slen;
 {
-       char *start, *trv, *suffp;
+       char *start, *trv, *suffp, *carryp;
        char *pad;
        struct stat sbuf;
        int rval;
        uint32_t rand;
+       char carrybuf[NAME_MAX];
 
-       if (doopen != NULL && domkdir) {
+       if ((doopen != NULL && domkdir) || slen < 0) {
                errno = EINVAL;
                return (0);
        }
@@ -122,7 +123,7 @@
        trv -= slen;
        suffp = trv;
        --trv;
-       if (trv < path) {
+       if (trv < path || NULL != strchr(suffp, '/')) {
                errno = EINVAL;
                return (0);
        }
@@ -134,6 +135,9 @@
        }
        start = trv + 1;
 
+       /* save first combination of random characters */
+       memcpy(carrybuf, start, suffp - start);
+
        /*
         * check the target directory.
         */
@@ -170,14 +174,25 @@
                        return (errno == ENOENT);
 
                /* If we have a collision, cycle through the space of filenames */
-               for (trv = start;;) {
-                       if (*trv == '\0' || trv == suffp)
-                               return (0);
+               for (trv = start, carryp = carrybuf;;) {
+                       /* have we tried all possible permutations? */
+                       if (trv == suffp)
+                               return (0); /* yes - exit with EEXIST */
                        pad = strchr(padchar, *trv);
-                       if (pad == NULL || *++pad == '\0')
-                               *trv++ = padchar[0];
-                       else {
-                               *trv++ = *pad;
+                       if (pad == NULL) {
+                               /* this should never happen */
+                               errno = EIO;
+                               return (0);
+                       }
+                       /* increment character */
+                       *trv = (*++pad == '\0') ? padchar[0] : *pad;
+                       /* carry to next position? */
+                       if (*trv == *carryp) {
+                               /* increment position and loop */
+                               ++trv;
+                               ++carryp;
+                       } else {
+                               /* try with new name */
                                break;
                        }
                }

>Release-Note:
>Audit-Trail:
>Unformatted:


More information about the freebsd-standards mailing list