Hello,

I have found 2 bugs in compress(1) using AFL++ fuzzing.
The patches below are just examples, feel free to solve another way.

1) fd double-close on invalid input

When gz_ropen() or zip_ropen() fail to parse the input header,
they call gz_close()/zip_close() which closes the fd.  The caller
dodecompress() then closes the same fd again.

Call path for the zip case:

  dodecompress()          opens file, gets ifd
    check_method(ifd)     reads magic bytes
    zip_ropen(ifd, ...)   stores fd in s->z_fd
      get_header()        fails on malformed input
      zip_close()         closes s->z_fd (== ifd), frees s
      returns NULL
    close(ifd)            double-close

Same pattern exists in gz_ropen() via gz_close().  z_ropen() is
not affected because it does not close the fd on failure.

Fix: on header parse failure, just call inflateEnd() and free the
allocated state instead of calling the full close function.  This
leaves fd ownership with the caller.

2) uint16_t underflow in zip extra field parsing

In get_header(), extlen is uint16_t.  The EF_TIME, EF_IZUNIX, and
EF_ZIP64 handlers subtract from extlen without bounds checking.
A crafted zip with a small extlen but EF_TIME or EF_IZUNIX extra
fields causes extlen to wrap around to ~65K, making the parser
read far past the extra field area.

Example with extlen=5 and an EF_TIME field with the mtime flag:

  extlen = 5
  read sig + fieldlen:   extlen -= 4   -> extlen = 1
  EF_TIME: read flags:   extlen--      -> extlen = 0
  mtime flag set:        extlen -= 4   -> extlen = 65532 (wrap)
  while (extlen >= 4) continues for ~16K more iterations

Fix: reject fields where fieldlen exceeds extlen, and check
fieldlen before reading optional sub-fields in EF_TIME and
EF_IZUNIX handlers.

Index: usr.bin/compress/gzopen.c
===================================================================
RCS file: /cvs/src/usr.bin/compress/gzopen.c,v
retrieving revision 1.35
diff -u -p -r1.35 gzopen.c
--- usr.bin/compress/gzopen.c   18 Jun 2022 03:23:19 -0000      1.35
+++ usr.bin/compress/gzopen.c
@@ -138,8 +138,9 @@ gz_ropen(int fd, char *name, int gotmagi

        /* read the .gz header */
        if (get_header(s, name, gotmagic) != 0) {
-               gz_close(s, NULL, NULL, NULL);
-               s = NULL;
+               (void)inflateEnd(&s->z_stream);
+               free(s);
+               return NULL;
        }

        return s;
Index: usr.bin/compress/zipopen.c
===================================================================
RCS file: /cvs/src/usr.bin/compress/zipopen.c,v
retrieving revision 1.1
diff -u -p -r1.1 zipopen.c
--- usr.bin/compress/zipopen.c  22 Oct 2022 14:41:27 -0000      1.1
+++ usr.bin/compress/zipopen.c
@@ -193,6 +193,10 @@ get_header(struct zip_state *s, char *na
                s->z_hlen += 4;
                extlen -= 4;

+               /* Field extends past the extra field area. */
+               if ((uint16_t)fieldlen > extlen)
+                       break;
+
                switch (sig) {
                case EF_ZIP64:
                        /* 64-bit file sizes */
@@ -206,12 +210,14 @@ get_header(struct zip_state *s, char *na
                        break;
                case EF_TIME:
                        /* UTC timestamps */
+                       if (fieldlen < 1)
+                               break;
                        if ((c = get_byte(s)) == EOF)
                                break;
                        s->z_hlen++;
                        extlen--;
                        fieldlen--;
-                       if (c & 1) {
+                       if ((c & 1) && fieldlen >= 4) {
                                got_mtime = 1;
                                s->z_time = get_uint32(s);
                                s->z_hlen += 4;
@@ -223,6 +229,8 @@ get_header(struct zip_state *s, char *na
                        /* We prefer EF_TIME if it is present. */
                        if (got_mtime)
                                break;
+                       if (fieldlen < 8)
+                               break;

                        /* skip atime, store mtime. */
                        (void)get_uint32(s);
@@ -276,8 +284,9 @@ zip_ropen(int fd, char *name, int gotmag

        /* Read the zip header. */
        if (get_header(s, name, gotmagic) != 0) {
-               zip_close(s, NULL, NULL, NULL);
-               s = NULL;
+               (void)inflateEnd(&s->z_stream);
+               free(s);
+               return NULL;
        }

        return s;

Reply via email to