Subject: Re: gzip buffer overflow found
To: None <current-users@netbsd.org>
From: Simon Burge <simonb@wasabisystems.com>
List: current-users
Date: 01/19/2001 16:14:03
"Jeremy C. Reed" wrote:

> This is some notes from some research. I still need to send-pr these if
> applicable. This is 1.5.1_ALPHA (i386).
> 
> Seg faults with 99999-character long argument.

Here a patch that converts most strcpy()'s to strlcpy()'s (and likewise
for strcat()).  This includes the one OpenBSD change for buffer
overflows.  I've done a bit of testing and don't appear to have broken
anything.  Anyone see any problems before I commit this?

Simon.
--

Index: gzip.c
===================================================================
RCS file: /cvsroot/gnusrc/gnu/usr.bin/gzip/gzip.c,v
retrieving revision 1.7
diff -d -p -u -r1.7 gzip.c
--- gzip.c	2000/11/17 01:31:26	1.7
+++ gzip.c	2001/01/19 04:08:11
@@ -475,7 +475,7 @@ int main (argc, argv)
     }
 #endif
 
-    strncpy(z_suffix, Z_SUFFIX, sizeof(z_suffix)-1);
+    strlcpy(z_suffix, Z_SUFFIX, sizeof(z_suffix));
     z_len = strlen(z_suffix);
 
     while ((optc = getopt_long (argc, argv, "ab:cdfhH?lLmMnNqrS:tvVZ123456789",
@@ -521,7 +521,12 @@ int main (argc, argv)
             if (*optarg == '.') optarg++;
 #endif
             z_len = strlen(optarg);
-            strcpy(z_suffix, optarg);
+	    if (z_len > sizeof(z_suffix)-1) {
+		fprintf(stderr, "%s: -S suffix too long\n", progname);
+		usage();
+		do_exit(ERROR);
+	    }
+            strlcpy(z_suffix, optarg, sizeof(z_suffix));
             break;
 	case 't':
 	    test = decompress = to_stdout = 1;
@@ -635,8 +640,8 @@ local void treat_stdin()
     if (!test && !list && (!decompress || !ascii)) {
 	SET_BINARY_MODE(fileno(stdout));
     }
-    strcpy(ifname, "stdin");
-    strcpy(ofname, "stdout");
+    strlcpy(ifname, "stdin", sizeof(ifname));
+    strlcpy(ofname, "stdout", sizeof(ofname));
 
     /* Get the time stamp on the input file. */
     time_stamp = 0; /* time unknown by default */
@@ -751,7 +756,7 @@ local void treat_file(iname)
      * without a valid gzip suffix (check done in make_ofname).
      */
     if (to_stdout && !list && !test) {
-	strcpy(ofname, "stdout");
+	strlcpy(ofname, "stdout", sizeof(ofname));
 
     } else if (make_ofname() != OK) {
 	return;
@@ -969,9 +974,9 @@ local char *get_suffix(name)
 #endif
     nlen = strlen(name);
     if (nlen <= MAX_SUFFIX+2) {
-        strcpy(suffix, name);
+        strlcpy(suffix, name, sizeof(suffix));
     } else {
-        strcpy(suffix, name+nlen-MAX_SUFFIX-2);
+        strlcpy(suffix, name+nlen-MAX_SUFFIX-2, sizeof(suffix));
     }
     strlwr(suffix);
     slen = strlen(suffix);
@@ -1006,7 +1011,7 @@ local int get_istat(iname, sbuf)
     char *dot; /* pointer to ifname extension, or NULL */
 #endif
 
-    strcpy(ifname, iname);
+    strlcpy(ifname, iname, sizeof(ifname));
 
     /* If input file exists, return OK. */
     if (do_stat(ifname, sbuf) == 0) return OK;
@@ -1028,7 +1033,7 @@ local int get_istat(iname, sbuf)
 #ifdef NO_MULTIPLE_DOTS
     dot = strrchr(ifname, '.');
     if (dot == NULL) {
-        strcat(ifname, ".");
+        strlcat(ifname, ".", sizeof(ifname));
         dot = strrchr(ifname, '.');
     }
 #endif
@@ -1042,24 +1047,26 @@ local int get_istat(iname, sbuf)
         if (*s == '.') s++;
 #endif
 #ifdef MAX_EXT_CHARS
-        strcpy(ifname, iname);
+        strlcpy(ifname, iname, sizeof(ifname));
         /* Needed if the suffixes are not sorted by increasing length */
 
-        if (*dot == '\0') strcpy(dot, ".");
+        if (*dot == '\0')
+	    strlcpy(dot, ".", sizeof(dot));
         dot[MAX_EXT_CHARS+1-strlen(s)] = '\0';
 #endif
-        strcat(ifname, s);
+        strlcat(ifname, s, sizeof(ifname));
         if (do_stat(ifname, sbuf) == 0) return OK;
 	ifname[ilen] = '\0';
     } while (*++suf != NULL);
 
     /* No suffix found, complain using z_suffix: */
 #ifdef MAX_EXT_CHARS
-    strcpy(ifname, iname);
-    if (*dot == '\0') strcpy(dot, ".");
+    strlcpy(ifname, iname, sizeof(ifname));
+    if (*dot == '\0')
+	strlcpy(dot, ".", sizeof(dot));
     dot[MAX_EXT_CHARS+1-z_len] = '\0';
 #endif
-    strcat(ifname, z_suffix);
+    strlcat(ifname, z_suffix, sizeof(ifname));
     perror(ifname);
     exit_code = ERROR;
     return ERROR;
@@ -1073,7 +1080,7 @@ local int make_ofname()
 {
     char *suff;            /* ofname z suffix */
 
-    strcpy(ofname, ifname);
+    strlcpy(ofname, ifname, sizeof(ofname));
     /* strip a version number if any and get the gzip suffix if present: */
     suff = get_suffix(ofname);
 
@@ -1094,7 +1101,7 @@ local int make_ofname()
 	/* Make a special case for .tgz and .taz: */
 	strlwr(suff);
 	if (strequ(suff, ".tgz") || strequ(suff, ".taz")) {
-	    strcpy(suff, ".tar");
+	    strcpy(suff, ".tar");	/* safe */
 	} else {
 	    *suff = '\0'; /* strip the z suffix */
 	}
@@ -1114,10 +1121,10 @@ local int make_ofname()
 #ifdef NO_MULTIPLE_DOTS
 	suff = strrchr(ofname, '.');
 	if (suff == NULL) {
-            strcat(ofname, ".");
+            strlcat(ofname, ".", sizeof(ofname));
 #  ifdef MAX_EXT_CHARS
 	    if (strequ(z_suffix, "z")) {
-		strcat(ofname, "gz"); /* enough room */
+		strlcat(ofname, "gz", sizeof(ofname)); /* enough room */
 		return OK;
 	    }
         /* On the Atari and some versions of MSDOS, name_too_long()
@@ -1130,7 +1137,7 @@ local int make_ofname()
 #  endif
         }
 #endif /* NO_MULTIPLE_DOTS */
-	strcat(ofname, z_suffix);
+	strlcat(ofname, z_suffix, sizeof(ofname));
 
     } /* decompress ? */
     return OK;
@@ -1473,7 +1480,7 @@ local void shorten_name(name)
     len = strlen(name);
     if (decompress) {
 	if (len <= 1) error("name too short");
-	name[len-1] = '\0';
+	name[0] = '\0';
 	return;
     }
     p = get_suffix(name);
@@ -1483,7 +1490,7 @@ local void shorten_name(name)
 
     /* compress 1234567890.tar to 1234567890.tgz */
     if (len > 4 && strequ(p-4, ".tar")) {
-	strcpy(p-4, ".tgz");
+	strcpy(p-4, ".tgz");	/* safe */
 	return;
     }
     /* Try keeping short extensions intact:
@@ -1510,7 +1517,8 @@ local void shorten_name(name)
 	if (trunc == NULL) error("internal error in shorten_name");
 	if (trunc[1] == '\0') trunc--; /* force truncation */
     }
-    strcpy(trunc, z_suffix);
+    /* all arguments to shorten_name are MAX_PATH_LEN long */
+    strlcpy(trunc, z_suffix, MAX_PATH_LEN - (trunc - name));
 }
 
 /* ========================================================================
@@ -1569,7 +1577,7 @@ local int check_ofname()
     /* Ask permission to overwrite the existing file */
     if (!force) {
 	char response[80];
-	strcpy(response,"n");
+	strlcpy(response, "n", sizeof(response));
 	fprintf(stderr, "%s: %s already exists;", progname, ofname);
 	if (foreground && isatty(fileno(stdin))) {
 	    fprintf(stderr, " do you wish to overwrite (y or n)? ");
@@ -1690,7 +1698,7 @@ local void treat_dir(dir)
 	}
 	len = strlen(dir);
 	if (len + NLENGTH(dp) + 1 < MAX_PATH_LEN - 1) {
-	    strcpy(nbuf,dir);
+	    strlcpy(nbuf, dir, sizeof(nbuf));
 	    if (len != 0 /* dir = "" means current dir on Amiga */
 #ifdef PATH_SEP2
 		&& dir[len-1] != PATH_SEP2
@@ -1701,7 +1709,7 @@ local void treat_dir(dir)
 	    ) {
 		nbuf[len++] = PATH_SEP;
 	    }
-	    strcpy(nbuf+len, dp->d_name);
+	    strlcpy(nbuf+len, dp->d_name, sizeof(nbuf) - len);
 	    treat_file(nbuf);
 	} else {
 	    fprintf(stderr,"%s: %s/%s: pathname too long\n",
Index: util.c
===================================================================
RCS file: /cvsroot/gnusrc/gnu/usr.bin/gzip/util.c,v
retrieving revision 1.2
diff -d -p -u -r1.2 util.c
--- util.c	1993/10/15 23:05:56	1.2
+++ util.c	2001/01/19 04:08:13
@@ -291,7 +291,7 @@ char *add_envopt(argcp, argvp, env)
     if (env == NULL) return NULL;
 
     p = (char*)xmalloc(strlen(env)+1);
-    env = strcpy(p, env);                    /* keep env variable intact */
+    env = strcpy(p, env); /* safe */         /* keep env variable intact */
 
     for (p = env; *p; nargc++ ) {            /* move through env */
 	p += strspn(p, SEPARATOR);	     /* skip leading separators */