Subject: patch around get_suffix

From: John SETH Thielemann <[email-masked]>
Date: Sat, 17 Aug 2024 03:48:53 +0000

To whom it may concern;

  Spent some time chasing a red herring around the'get_suffix' function. 
The patch included aims to simplify the logic considerably.

There are a number of interesting points to note:

   Under normal situations (".gz") (could be modified by using -S) the 
predicate:
<code>
         if (z_len < suflen && strequ (z_suffix, *suf + suflen - z_len))
</code>

   Will never be true. Modifications to the suffix string, possibly at 
build time,
or on the command line will trigger the boolean to get set, executing:
<code>
     known_suffixes[suffix_of_builtin
                    ? sizeof known_suffixes / sizeof *known_suffixes - 2
                    : 0] = z_lower;
</code>

   This will drop the duplicated pointer into the next-to-last element of 
the array,
rather than the front. The const qualifier on  known_suffixes on reading 
that it is const but this is not the case.

    strlen is a size_t, there may potentially be sign extension issues. 
It could be possible to craft a pointer as an argument to a mapped 
memory region if it was large enough to trigger a sign extension error, 
however it's extremely unlikely.

Take care,
John SETH Thielemann
JOSTALY Technologies
https://www.jostaly.com
223-231-3511

diff -Naur gzip-1.12.vanilla/gzip.c gzip-1.12.patch/gzip.c
--- gzip-1.12.vanilla/gzip.c	2022-03-31 01:32:53.000000000 +0000
+++ gzip-1.12.patch/gzip.c	2024-08-16 17:51:58.393769421 +0000
@@ -313,7 +313,7 @@
 local void treat_stdin  (void);
 local void treat_file   (char *iname);
 local int create_outfile (void);
-local char *get_suffix  (char *name);
+local const char *get_suffix  (char *name);
 local int  open_input_file (char *iname, struct stat *sbuf);
 local void discard_input_bytes (size_t nbytes, unsigned int flags);
 local int  make_ofname  (void);
@@ -1169,59 +1169,24 @@
  * .??z suffix as indicating a compressed file; some people use .xyz
  * to denote volume data.
  */
-local char *get_suffix(name)
-    char *name;
-{
-    int nlen, slen;
-    char suffix[MAX_SUFFIX+3]; /* last chars of name, forced to lower case */
-    static char const *known_suffixes[] =
-       {NULL, ".gz", ".z", ".taz", ".tgz", "-gz", "-z", "_z",
+
+static char const *known_suffixes[] =
+	{NULL, ".gz", ".z", ".taz", ".tgz", "-gz", "-z", "_z",
 #ifdef MAX_EXT_CHARS
-          "z",
+	"z",
 #endif
-        NULL, NULL};
+	NULL, NULL};
+
+local const char *get_suffix(name)
+    char *name;
+{
     char const **suf;
-    bool suffix_of_builtin = false;
 
-    /* Normally put Z_SUFFIX at the start of KNOWN_SUFFIXES, but if it
-       is a suffix of one of them, put it at the end.  */
     for (suf = known_suffixes + 1; *suf; suf++)
-      {
-        size_t suflen = strlen (*suf);
-        if (z_len < suflen && strequ (z_suffix, *suf + suflen - z_len))
-          {
-            suffix_of_builtin = true;
-            break;
-          }
-      }
-
-    char *z_lower = xstrdup(z_suffix);
-    strlwr(z_lower);
-    known_suffixes[suffix_of_builtin
-                   ? sizeof known_suffixes / sizeof *known_suffixes - 2
-                   : 0] = z_lower;
-    suf = known_suffixes + suffix_of_builtin;
-
-    nlen = strlen(name);
-    if (nlen <= MAX_SUFFIX+2) {
-        strcpy(suffix, name);
-    } else {
-        strcpy(suffix, name+nlen-MAX_SUFFIX-2);
-    }
-    strlwr(suffix);
-    slen = strlen(suffix);
-    char *match = NULL;
-    do {
-       int s = strlen(*suf);
-       if (slen > s && ! ISSLASH (suffix[slen - s - 1])
-           && strequ(suffix + slen - s, *suf)) {
-           match = name+nlen-s;
-           break;
-       }
-    } while (*++suf != NULL);
-    free(z_lower);
+        if (z_len <= strnlen(*suf, 4) && strequ (z_suffix, *suf))
+			return *suf;
 
-    return match;
+    return NULL;
 }

@@ -1386,7 +1351,13 @@
  */
 local int make_ofname()
 {
-    char *suff;            /* ofname z suffix */
+    const char *suff;            /* ofname z suffix */
+	const size_t len = strnlen(ifname, MAX_PATH_LEN);
+
+	if (len >= MAX_PATH_LEN) {
+		WARN((stderr, "%s: invalid length on input file name\n"));
+		return WARNING;
+	}
 
     strcpy(ofname, ifname);
     /* strip a version number if any and get the gzip suffix if present: */
@@ -1407,59 +1378,18 @@
             }
             return WARNING;
         }
-        /* Make a special case for .tgz and .taz: */
-        strlwr(suff);
-        if (strequ(suff, ".tgz") || strequ(suff, ".taz")) {
-            strcpy(suff, ".tar");
-        } else {
-            *suff = '\0'; /* strip the z suffix */
-        }
-        /* ofname might be changed later if infile contains an original name */
 
-    } else if (suff && ! force) {
-        /* Avoid annoying messages with -r (see treat_dir()) */
-        if (verbose || (!recursive && !quiet)) {
-            /* Don't use WARN, as it affects exit status.  */
-            fprintf (stderr, "%s: %s already has %s suffix -- unchanged\n",
-                     program_name, ifname, suff);
-        }
-        return WARNING;
+		ofname[len - strnlen(suff, 4)] = 0;
     } else {
-        save_orig_name = 0;
-
-#ifdef NO_MULTIPLE_DOTS
-        suff = strrchr(ofname, '.');
-        if (suff == NULL) {
-            if (sizeof ofname <= strlen (ofname) + 1)
-                goto name_too_long;
-            strcat(ofname, ".");
-#  ifdef MAX_EXT_CHARS
-            if (strequ(z_suffix, "z")) {
-                if (sizeof ofname <= strlen (ofname) + 2)
-                    goto name_too_long;
-                strcat(ofname, "gz"); /* enough room */
-                return OK;
-            }
-        /* On the Atari and some versions of MSDOS,
-         * ENAMETOOLONG does not work correctly.  So we
-         * must truncate here.
-         */
-        } else if (strlen(suff)-1 + z_len > MAX_SUFFIX) {
-            suff[MAX_SUFFIX+1-z_len] = '\0';
-            save_orig_name = 1;
-#  endif
-        }
-#endif /* NO_MULTIPLE_DOTS */
-        if (sizeof ofname <= strlen (ofname) + z_len)
-            goto name_too_long;
-        strcat(ofname, z_suffix);
-
-    } /* decompress ? */
-    return OK;
+		const size_t suffLen = strnlen(suff, 4);
+		if ((len + suffLen) >= MAX_PATH_LEN) {
+			WARN((stderr, "%s: Append of suffix to input file name > MAX_PATH_LEN\n"));
+			return WARNING;
+		}
+		strncat(ofname, suff, suffLen);
+	}
 
- name_too_long:
-    WARN ((stderr, "%s: %s: file name too long\n", program_name, ifname));
-    return WARNING;
+	return OK;
 }
 
 /* Discard NBYTES input bytes from the input, or up through the next
@@ -1820,9 +1750,7 @@
     char *name;
 {
     int len;                 /* length of name without z_suffix */
-    char *trunc = NULL;      /* character to be truncated */
     int plen;                /* current part length */
-    int min_part = MIN_PART; /* current minimum part length */
     char *p;
 
     len = strlen(name);
@@ -1836,38 +1764,8 @@
     if (! p)
       gzip_error ("can't recover suffix\n");
     *p = '\0';
-    save_orig_name = 1;
-
-    /* compress 1234567890.tar to 1234567890.tgz */
-    if (len > 4 && strequ(p-4, ".tar")) {
-        strcpy(p-4, ".tgz");
-        return;
-    }
-    /* Try keeping short extensions intact:
-     * 1234.678.012.gz -> 123.678.012.gz
-     */
-    do {
-        p = last_component (name);
-        while (*p) {
-            plen = strcspn(p, PART_SEP);
-            p += plen;
-            if (plen > min_part) trunc = p-1;
-            if (*p) p++;
-        }
-    } while (trunc == NULL && --min_part != 0);
-
-    if (trunc != NULL) {
-        do {
-            trunc[0] = trunc[1];
-        } while (*trunc++);
-        trunc--;
-    } else {
-        trunc = strrchr(name, PART_SEP[0]);
-        if (!trunc)
-          gzip_error ("internal error in shorten_name");
-        if (trunc[1] == '\0') trunc--; /* force truncation */
-    }
-    strcpy(trunc, z_suffix);
+	plen = strnlen(p, 4);
+	name[len - plen] = '\0';
 }
 
 /* ========================================================================