Fix heap overflow in HTTP protocol handling (CVE-2017-13090)

* src/retr.c (fd_read_body): Stop processing on negative chunk size

Reported-by: Antti Levomäki, Christian Jalio, Joonas Pihlaja from Forcepoint
Reported-by: Juhani Eronen from Finnish National Cyber Security Centre

fd_read_body (const char *downloaded_filename, int fd, FILE *out, wgint toread, wgint startpos,

              wgint *qtyread, wgint *qtywritten, double *elapsed, int flags,
              FILE *out2)
{
  int ret = 0;
#undef max
#define max(a,b) ((a) > (b) ? (a) : (b))
  int dlbufsize = max (BUFSIZ, 8 * 1024);
  char *dlbuf = xmalloc (dlbufsize);

  struct ptimer *timer = NULL;
  double last_successful_read_tm = 0;

  /* The progress gauge, set according to the user preferences. */
  void *progress = NULL;

  /* Non-zero if the progress gauge is interactive, i.e. if it can
     continually update the display.  When true, smaller timeout
     values are used so that the gauge can update the display when
     data arrives slowly. */
  bool progress_interactive = false;

  bool exact = !!(flags & rb_read_exactly);

  /* Used only by HTTP/HTTPS chunked transfer encoding.  */
  bool chunked = flags & rb_chunked_transfer_encoding;
  wgint skip = 0;

  /* How much data we've read/written.  */
  wgint sum_read = 0;
  wgint sum_written = 0;
  wgint remaining_chunk_size = 0;

#ifdef HAVE_LIBZ
  /* try to minimize the number of calls to inflate() and write_data() per
     call to fd_read() */
  unsigned int gzbufsize = dlbufsize * 4;
  char *gzbuf = NULL;
  z_stream gzstream;

  if (flags & rb_compressed_gzip)
    {
      gzbuf = xmalloc (gzbufsize);
      if (gzbuf != NULL)
        {
          gzstream.zalloc = zalloc;
          gzstream.zfree = zfree;
          gzstream.opaque = Z_NULL;
          gzstream.next_in = Z_NULL;
          gzstream.avail_in = 0;

          #define GZIP_DETECT 32 /* gzip format detection */
          #define GZIP_WINDOW 15 /* logarithmic window size (default: 15) */
          ret = inflateInit2 (&gzstream, GZIP_DETECT | GZIP_WINDOW);
          if (ret != Z_OK)
            {
              xfree (gzbuf);
              errno = (ret == Z_MEM_ERROR) ? ENOMEM : EINVAL;
              ret = -1;
              goto out;
            }
        }
      else
        {
          errno = ENOMEM;
          ret = -1;
          goto out;
        }
    }
#endif

  if (flags & rb_skip_startpos)
    skip = startpos;

  if (opt.show_progress)
    {
      const char *filename_progress;
      /* If we're skipping STARTPOS bytes, pass 0 as the INITIAL
         argument to progress_create because the indicator doesn't
         (yet) know about "skipping" data.  */
      wgint start = skip ? 0 : startpos;
      if (opt.dir_prefix)
        filename_progress = downloaded_filename + strlen (opt.dir_prefix) + 1;
      else
        filename_progress = downloaded_filename;
      progress = progress_create (filename_progress, start, start + toread);
      progress_interactive = progress_interactive_p (progress);
    }

  if (opt.limit_rate)
    limit_bandwidth_reset ();

  /* A timer is needed for tracking progress, for throttling, and for
     tracking elapsed time.  If either of these are requested, start
     the timer.  */
  if (progress || opt.limit_rate || elapsed)
    {
      timer = ptimer_new ();
      last_successful_read_tm = 0;
    }

  /* Use a smaller buffer for low requested bandwidths.  For example,
     with --limit-rate=2k, it doesn't make sense to slurp in 16K of
     data and then sleep for 8s.  With buffer size equal to the limit,
     we never have to sleep for more than one second.  */
  if (opt.limit_rate && opt.limit_rate < dlbufsize)
    dlbufsize = opt.limit_rate;

  /* Read from FD while there is data to read.  Normally toread==0
     means that it is unknown how much data is to arrive.  However, if
     EXACT is set, then toread==0 means what it says: that no data
     should be read.  */
  while (!exact || (sum_read < toread))
    {
      int rdsize;
      double tmout = opt.read_timeout;

      if (chunked)
        {
          if (remaining_chunk_size == 0)
            {
              char *line = fd_read_line (fd);
              char *endl;
              if (line == NULL)
                {
                  ret = -1;
                  break;
                }
              else if (out2 != NULL)
                fwrite (line, 1, strlen (line), out2);

              remaining_chunk_size = strtol (line, &endl, 16);
              xfree (line);

              if (remaining_chunk_size == 0)
                {
                  ret = 0;
                  line = fd_read_line (fd);
                  if (line == NULL)
                    ret = -1;
                  else
                    {
                      if (out2 != NULL)
                        fwrite (line, 1, strlen (line), out2);
                      xfree (line);
                    }
                  break;
                }
            }

          rdsize = MIN (remaining_chunk_size, dlbufsize);
        }
      else
        rdsize = exact ? MIN (toread - sum_read, dlbufsize) : dlbufsize;

      if (progress_interactive)
        {
          /* For interactive progress gauges, always specify a ~1s
             timeout, so that the gauge can be updated regularly even
             when the data arrives very slowly or stalls.  */
          tmout = 0.95;
          if (opt.read_timeout)
            {
              double waittm;
              waittm = ptimer_read (timer) - last_successful_read_tm;
              if (waittm + tmout > opt.read_timeout)
                {
                  /* Don't let total idle time exceed read timeout. */
                  tmout = opt.read_timeout - waittm;
                  if (tmout < 0)
                    {
                      /* We've already exceeded the timeout. */
                      ret = -1, errno = ETIMEDOUT;
                      break;
                    }
                }
            }
        }
      ret = fd_read (fd, dlbuf, rdsize, tmout);

      if (progress_interactive && ret < 0 && errno == ETIMEDOUT)
        ret = 0;                /* interactive timeout, handled above */
      else if (ret <= 0)
        break;                  /* EOF or read error */

      if (progress || opt.limit_rate || elapsed)
        {
          ptimer_measure (timer);
          if (ret > 0)
            last_successful_read_tm = ptimer_read (timer);
        }

      if (ret > 0)
        {
          int write_res;

          sum_read += ret;

#ifdef HAVE_LIBZ
          if (gzbuf != NULL)
            {
              int err;
              int towrite;
              gzstream.avail_in = ret;
              gzstream.next_in = (unsigned char *) dlbuf;

              do
                {
                  gzstream.avail_out = gzbufsize;
                  gzstream.next_out = (unsigned char *) gzbuf;

                  err = inflate (&gzstream, Z_NO_FLUSH);

                  switch (err)
                    {
                    case Z_MEM_ERROR:
                      errno = ENOMEM;
                      ret = -1;
                      goto out;
                    case Z_NEED_DICT:
                    case Z_DATA_ERROR:
                      errno = EINVAL;
                      ret = -1;
                      goto out;
                    case Z_STREAM_END:
                      if (exact && sum_read != toread)
                        {
                          DEBUGP(("zlib stream ended unexpectedly after "
                                  "%ld/%ld bytes\n", sum_read, toread));
                        }
                    }

                  towrite = gzbufsize - gzstream.avail_out;
                  write_res = write_data (out, out2, gzbuf, towrite, &skip,
                                          &sum_written);
                  if (write_res < 0)
                    {
                      ret = (write_res == -3) ? -3 : -2;
                      goto out;
                    }
                }
              while (gzstream.avail_out == 0);
            }
          else
#endif
            {
              write_res = write_data (out, out2, dlbuf, ret, &skip,
                                      &sum_written);
              if (write_res < 0)
                {
                  ret = (write_res == -3) ? -3 : -2;
                  goto out;
                }
            }

          if (chunked)
            {
              remaining_chunk_size -= ret;
              if (remaining_chunk_size == 0)
                {
                  char *line = fd_read_line (fd);
                  if (line == NULL)
                    {
                      ret = -1;
                      break;
                    }
                  else
                    {
                      if (out2 != NULL)
                        fwrite (line, 1, strlen (line), out2);
                      xfree (line);
                    }
                }
            }
        }

      if (opt.limit_rate)
        limit_bandwidth (ret, timer);

      if (progress)
        progress_update (progress, ret, ptimer_read (timer));
#ifdef WINDOWS
      if (toread > 0 && opt.show_progress)
        ws_percenttitle (100.0 *
                         (startpos + sum_read) / (startpos + toread));
#endif
    }
  if (ret < -1)
    ret = -1;

 out:
  if (progress)
    progress_finish (progress, ptimer_read (timer));

  if (elapsed)
    *elapsed = ptimer_read (timer);
  if (timer)
    ptimer_destroy (timer);

#ifdef HAVE_LIBZ
  if (gzbuf != NULL)
    {
      int err = inflateEnd (&gzstream);
      if (ret >= 0)
        {
          /* with compression enabled, ret must be 0 if successful */
          if (err == Z_OK)
            ret = 0;
          else
            {
              errno = EINVAL;
              ret = -1;
            }
        }
      xfree (gzbuf);

      if (gzstream.total_in != sum_read)
        {
          DEBUGP(("zlib read size differs from raw read size (%lu/%lu)\n",
                  gzstream.total_in, sum_read));
        }
    }
#endif

  if (qtyread)
    *qtyread += sum_read;
  if (qtywritten)
    *qtywritten += sum_written;

  xfree (dlbuf);

  return ret;
}

The retr.c:fd_read_body() function is called when processing OK responses. When the response is sent chunked in wget before 1.19.2, the chunk parser uses strtol() to read each chunk's length, but doesn't check that the chunk length is a non-negative number. The code then tries to read the chunk in pieces of 8192 bytes by using the MIN() macro, but ends up passing the negative chunk length to retr.c:fd_read(). As fd_read() takes an int argument, the high 32 bits of the chunk length are discarded, leaving fd_read() with a completely attacker controlled length argument. The attacker can corrupt malloc metadata after the allocated buffer.


fd_read_body (const char *downloaded_filename, int fd, FILE *out, wgint toread, wgint startpos,

              wgint *qtyread, wgint *qtywritten, double *elapsed, int flags,
              FILE *out2)
{
  int ret = 0;
#undef max
#define max(a,b) ((a) > (b) ? (a) : (b))
  int dlbufsize = max (BUFSIZ, 8 * 1024);
  char *dlbuf = xmalloc (dlbufsize);

  struct ptimer *timer = NULL;
  double last_successful_read_tm = 0;

  /* The progress gauge, set according to the user preferences. */
  void *progress = NULL;

  /* Non-zero if the progress gauge is interactive, i.e. if it can
     continually update the display.  When true, smaller timeout
     values are used so that the gauge can update the display when
     data arrives slowly. */
  bool progress_interactive = false;

  bool exact = !!(flags & rb_read_exactly);

  /* Used only by HTTP/HTTPS chunked transfer encoding.  */
  bool chunked = flags & rb_chunked_transfer_encoding;
  wgint skip = 0;

  /* How much data we've read/written.  */
  wgint sum_read = 0;
  wgint sum_written = 0;
  wgint remaining_chunk_size = 0;

#ifdef HAVE_LIBZ
  /* try to minimize the number of calls to inflate() and write_data() per
     call to fd_read() */
  unsigned int gzbufsize = dlbufsize * 4;
  char *gzbuf = NULL;
  z_stream gzstream;

  if (flags & rb_compressed_gzip)
    {
      gzbuf = xmalloc (gzbufsize);
      if (gzbuf != NULL)
        {
          gzstream.zalloc = zalloc;
          gzstream.zfree = zfree;
          gzstream.opaque = Z_NULL;
          gzstream.next_in = Z_NULL;
          gzstream.avail_in = 0;

          #define GZIP_DETECT 32 /* gzip format detection */
          #define GZIP_WINDOW 15 /* logarithmic window size (default: 15) */
          ret = inflateInit2 (&gzstream, GZIP_DETECT | GZIP_WINDOW);
          if (ret != Z_OK)
            {
              xfree (gzbuf);
              errno = (ret == Z_MEM_ERROR) ? ENOMEM : EINVAL;
              ret = -1;
              goto out;
            }
        }
      else
        {
          errno = ENOMEM;
          ret = -1;
          goto out;
        }
    }
#endif

  if (flags & rb_skip_startpos)
    skip = startpos;

  if (opt.show_progress)
    {
      const char *filename_progress;
      /* If we're skipping STARTPOS bytes, pass 0 as the INITIAL
         argument to progress_create because the indicator doesn't
         (yet) know about "skipping" data.  */
      wgint start = skip ? 0 : startpos;
      if (opt.dir_prefix)
        filename_progress = downloaded_filename + strlen (opt.dir_prefix) + 1;
      else
        filename_progress = downloaded_filename;
      progress = progress_create (filename_progress, start, start + toread);
      progress_interactive = progress_interactive_p (progress);
    }

  if (opt.limit_rate)
    limit_bandwidth_reset ();

  /* A timer is needed for tracking progress, for throttling, and for
     tracking elapsed time.  If either of these are requested, start
     the timer.  */
  if (progress || opt.limit_rate || elapsed)
    {
      timer = ptimer_new ();
      last_successful_read_tm = 0;
    }

  /* Use a smaller buffer for low requested bandwidths.  For example,
     with --limit-rate=2k, it doesn't make sense to slurp in 16K of
     data and then sleep for 8s.  With buffer size equal to the limit,
     we never have to sleep for more than one second.  */
  if (opt.limit_rate && opt.limit_rate < dlbufsize)
    dlbufsize = opt.limit_rate;

  /* Read from FD while there is data to read.  Normally toread==0
     means that it is unknown how much data is to arrive.  However, if
     EXACT is set, then toread==0 means what it says: that no data
     should be read.  */
  while (!exact || (sum_read < toread))
    {
      int rdsize;
      double tmout = opt.read_timeout;

      if (chunked)
        {
          if (remaining_chunk_size == 0)
            {
              char *line = fd_read_line (fd);
              char *endl;
              if (line == NULL)
                {
                  ret = -1;
                  break;
                }
              else if (out2 != NULL)
                fwrite (line, 1, strlen (line), out2);

              remaining_chunk_size = strtol (line, &endl, 16);
              xfree (line);

              if (remaining_chunk_size < 0)
                {
                  ret = -1;
                  break;
                }

              if (remaining_chunk_size == 0)
                {
                  ret = 0;
                  line = fd_read_line (fd);
                  if (line == NULL)
                    ret = -1;
                  else
                    {
                      if (out2 != NULL)
                        fwrite (line, 1, strlen (line), out2);
                      xfree (line);
                    }
                  break;
                }
            }

          rdsize = MIN (remaining_chunk_size, dlbufsize);
        }
      else
        rdsize = exact ? MIN (toread - sum_read, dlbufsize) : dlbufsize;

      if (progress_interactive)
        {
          /* For interactive progress gauges, always specify a ~1s
             timeout, so that the gauge can be updated regularly even
             when the data arrives very slowly or stalls.  */
          tmout = 0.95;
          if (opt.read_timeout)
            {
              double waittm;
              waittm = ptimer_read (timer) - last_successful_read_tm;
              if (waittm + tmout > opt.read_timeout)
                {
                  /* Don't let total idle time exceed read timeout. */
                  tmout = opt.read_timeout - waittm;
                  if (tmout < 0)
                    {
                      /* We've already exceeded the timeout. */
                      ret = -1, errno = ETIMEDOUT;
                      break;
                    }
                }
            }
        }
      ret = fd_read (fd, dlbuf, rdsize, tmout);

      if (progress_interactive && ret < 0 && errno == ETIMEDOUT)
        ret = 0;                /* interactive timeout, handled above */
      else if (ret <= 0)
        break;                  /* EOF or read error */

      if (progress || opt.limit_rate || elapsed)
        {
          ptimer_measure (timer);
          if (ret > 0)
            last_successful_read_tm = ptimer_read (timer);
        }

      if (ret > 0)
        {
          int write_res;

          sum_read += ret;

#ifdef HAVE_LIBZ
          if (gzbuf != NULL)
            {
              int err;
              int towrite;
              gzstream.avail_in = ret;
              gzstream.next_in = (unsigned char *) dlbuf;

              do
                {
                  gzstream.avail_out = gzbufsize;
                  gzstream.next_out = (unsigned char *) gzbuf;

                  err = inflate (&gzstream, Z_NO_FLUSH);

                  switch (err)
                    {
                    case Z_MEM_ERROR:
                      errno = ENOMEM;
                      ret = -1;
                      goto out;
                    case Z_NEED_DICT:
                    case Z_DATA_ERROR:
                      errno = EINVAL;
                      ret = -1;
                      goto out;
                    case Z_STREAM_END:
                      if (exact && sum_read != toread)
                        {
                          DEBUGP(("zlib stream ended unexpectedly after "
                                  "%ld/%ld bytes\n", sum_read, toread));
                        }
                    }

                  towrite = gzbufsize - gzstream.avail_out;
                  write_res = write_data (out, out2, gzbuf, towrite, &skip,
                                          &sum_written);
                  if (write_res < 0)
                    {
                      ret = (write_res == -3) ? -3 : -2;
                      goto out;
                    }
                }
              while (gzstream.avail_out == 0);
            }
          else
#endif
            {
              write_res = write_data (out, out2, dlbuf, ret, &skip,
                                      &sum_written);
              if (write_res < 0)
                {
                  ret = (write_res == -3) ? -3 : -2;
                  goto out;
                }
            }

          if (chunked)
            {
              remaining_chunk_size -= ret;
              if (remaining_chunk_size == 0)
                {
                  char *line = fd_read_line (fd);
                  if (line == NULL)
                    {
                      ret = -1;
                      break;
                    }
                  else
                    {
                      if (out2 != NULL)
                        fwrite (line, 1, strlen (line), out2);
                      xfree (line);
                    }
                }
            }
        }

      if (opt.limit_rate)
        limit_bandwidth (ret, timer);

      if (progress)
        progress_update (progress, ret, ptimer_read (timer));
#ifdef WINDOWS
      if (toread > 0 && opt.show_progress)
        ws_percenttitle (100.0 *
                         (startpos + sum_read) / (startpos + toread));
#endif
    }
  if (ret < -1)
    ret = -1;

 out:
  if (progress)
    progress_finish (progress, ptimer_read (timer));

  if (elapsed)
    *elapsed = ptimer_read (timer);
  if (timer)
    ptimer_destroy (timer);

#ifdef HAVE_LIBZ
  if (gzbuf != NULL)
    {
      int err = inflateEnd (&gzstream);
      if (ret >= 0)
        {
          /* with compression enabled, ret must be 0 if successful */
          if (err == Z_OK)
            ret = 0;
          else
            {
              errno = EINVAL;
              ret = -1;
            }
        }
      xfree (gzbuf);

      if (gzstream.total_in != sum_read)
        {
          DEBUGP(("zlib read size differs from raw read size (%lu/%lu)\n",
                  gzstream.total_in, sum_read));
        }
    }
#endif

  if (qtyread)
    *qtyread += sum_read;
  if (qtywritten)
    *qtywritten += sum_written;

  xfree (dlbuf);

  return ret;
}


Do not crash when ncbytes is larger than the buffer size

This fixes CVE-2018-5345

cdata_read (cdata_t *cd, guint8 res_data, gint comptype,
            GDataInputStream *in, GCancellable *cancellable, GError **error)

{
    gboolean success = FALSE;
    int ret, zret = Z_OK;
    gint compression = comptype & GCAB_COMPRESSION_MASK;
    guint8 *buf = compression == GCAB_COMPRESSION_NONE ? cd->out : cd->in;
    guint32 datacsum;
    guint32 checksum_tmp;
    guint8 sizecsum[4];
    guint16 nbytes_le;

    if (compression > GCAB_COMPRESSION_MSZIP &&
        compression != GCAB_COMPRESSION_LZX) {
        g_set_error (error, GCAB_ERROR, GCAB_ERROR_NOT_SUPPORTED,
                     "unsupported compression method %d", compression);
        return FALSE;
    }

    R4 (cd->checksum);
    R2 (cd->ncbytes);
    R2 (cd->nubytes);
    RN (cd->reserved, res_data);
    RN (buf, cd->ncbytes);

    datacsum = compute_checksum(buf, cd->ncbytes, 0);
    nbytes_le = GUINT16_TO_LE (cd->ncbytes);
    memcpy (&sizecsum[0], &nbytes_le, 2);
    nbytes_le = GUINT16_TO_LE (cd->nubytes);
    memcpy (&sizecsum[2], &nbytes_le, 2);
    checksum_tmp = compute_checksum (sizecsum, sizeof(sizecsum), datacsum);
    if (cd->checksum != checksum_tmp) {
        if (_enforce_checksum ()) {
            g_set_error_literal (error, GCAB_ERROR, GCAB_ERROR_INVALID_DATA,
                                 "incorrect checksum detected");
            return FALSE;
        }
        if (g_getenv ("GCAB_DEBUG"))
            g_debug ("CDATA checksum 0x%08x", (guint) checksum_tmp);
    }

    if (g_getenv ("GCAB_DEBUG")) {
        g_debug ("CDATA");
        P4 (cd, checksum);
        P2 (cd, ncbytes);
        P2 (cd, nubytes);
        if (res_data)
            PN (cd, reserved, res_data);
        PND (cd, buf, 64);
    }

    if (compression == GCAB_COMPRESSION_LZX) {
        if (cd->fdi.alloc == NULL) {
            cd->fdi.alloc = g_malloc;
            cd->fdi.free = g_free;
            cd->decomp.fdi = &cd->fdi;
            cd->decomp.inbuf = cd->in;
            cd->decomp.outbuf = cd->out;
            cd->decomp.comptype = compression;

            ret = LZXfdi_init((comptype >> 8) & 0x1f, &cd->decomp);
            if (ret < 0)
                goto end;
        }

        ret = LZXfdi_decomp (cd->ncbytes, cd->nubytes, &cd->decomp);
        if (ret < 0)
            goto end;
    }

    if (compression == GCAB_COMPRESSION_MSZIP) {
        if (cd->in[0] != 'C' || cd->in[1] != 'K')
            goto end;

        cd->decomp.comptype = compression;
        z_stream *z = &cd->z;

        z->avail_in = cd->ncbytes - 2;
        z->next_in = cd->in + 2;
        z->avail_out = cd->nubytes;
        z->next_out = cd->out;
        z->total_out = 0;

        if (!z->opaque) {
            z->zalloc = zalloc;
            z->zfree = zfree;
            z->opaque = cd;

            zret = inflateInit2 (z, -MAX_WBITS);
            if (zret != Z_OK)
                goto end;
        }

        while (1) {
            zret = inflate (z, Z_BLOCK);
            if (zret == Z_STREAM_END)
                break;
            if (zret != Z_OK)
                goto end;
        }

        g_warn_if_fail (z->avail_in == 0);
        g_warn_if_fail (z->avail_out == 0);
        if (z->avail_in != 0 || z->avail_out != 0)
            goto end;

        zret = inflateReset (z);
        if (zret != Z_OK)
            goto end;

        zret = inflateSetDictionary (z, cd->out, cd->nubytes);
        if (zret != Z_OK)
            goto end;
    }

    success = TRUE;

end:
    if (zret != Z_OK)
        g_set_error (error, GCAB_ERROR, GCAB_ERROR_FAILED,
                     "zlib failed: %s", zError (zret));

    if (error != NULL && *error == NULL && !success)
        g_set_error (error, GCAB_ERROR, GCAB_ERROR_FAILED,
                     "Invalid cabinet chunk");

    return success;
}

A stack-based buffer overflow within GNOME gcab through 0.7.4 can be exploited by malicious attackers to cause a crash or, potentially, execute arbitrary code via a crafted .cab file.


cdata_read (cdata_t *cd, guint8 res_data, gint comptype,
            GDataInputStream *in, GCancellable *cancellable, GError **error)

{
    gboolean success = FALSE;
    int ret, zret = Z_OK;
    gint compression = comptype & GCAB_COMPRESSION_MASK;
    gsize buf_sz;
    guint8 *buf = NULL;
    guint32 datacsum;
    guint32 checksum_tmp;
    guint8 sizecsum[4];
    guint16 nbytes_le;

    /* decompress directly into ->out for no decompression */
    switch (compression) {
    case GCAB_COMPRESSION_NONE:
        buf = cd->out;
        buf_sz = sizeof(cd->out);
        break;
    case GCAB_COMPRESSION_MSZIP:
    case GCAB_COMPRESSION_LZX:
        buf = cd->in;
        buf_sz = sizeof(cd->in);
        break;
    default:
        g_set_error (error, GCAB_ERROR, GCAB_ERROR_NOT_SUPPORTED,
                     "unsupported compression method %d", compression);
        break;
    }
    if (buf == NULL)
        return FALSE;

    R4 (cd->checksum);
    R2 (cd->ncbytes);
    if (cd->ncbytes > buf_sz) {
        g_set_error (error, GCAB_ERROR, GCAB_ERROR_INVALID_DATA,
                     "tried to decompress %" G_GUINT16_FORMAT " bytes "
                     "into buffer of size %" G_GSIZE_FORMAT,
                     cd->ncbytes, buf_sz);
        return FALSE;
    }
    R2 (cd->nubytes);
    RN (cd->reserved, res_data);
    RN (buf, cd->ncbytes);

    datacsum = compute_checksum(buf, cd->ncbytes, 0);
    nbytes_le = GUINT16_TO_LE (cd->ncbytes);
    memcpy (&sizecsum[0], &nbytes_le, 2);
    nbytes_le = GUINT16_TO_LE (cd->nubytes);
    memcpy (&sizecsum[2], &nbytes_le, 2);
    checksum_tmp = compute_checksum (sizecsum, sizeof(sizecsum), datacsum);
    if (cd->checksum != checksum_tmp) {
        if (_enforce_checksum ()) {
            g_set_error_literal (error, GCAB_ERROR, GCAB_ERROR_INVALID_DATA,
                                 "incorrect checksum detected");
            return FALSE;
        }
        if (g_getenv ("GCAB_DEBUG"))
            g_debug ("CDATA checksum 0x%08x", (guint) checksum_tmp);
    }

    if (g_getenv ("GCAB_DEBUG")) {
        g_debug ("CDATA");
        P4 (cd, checksum);
        P2 (cd, ncbytes);
        P2 (cd, nubytes);
        if (res_data)
            PN (cd, reserved, res_data);
        PND (cd, buf, 64);
    }

    if (compression == GCAB_COMPRESSION_LZX) {
        if (cd->fdi.alloc == NULL) {
            cd->fdi.alloc = g_malloc;
            cd->fdi.free = g_free;
            cd->decomp.fdi = &cd->fdi;
            cd->decomp.inbuf = cd->in;
            cd->decomp.outbuf = cd->out;
            cd->decomp.comptype = compression;

            ret = LZXfdi_init((comptype >> 8) & 0x1f, &cd->decomp);
            if (ret < 0)
                goto end;
        }

        ret = LZXfdi_decomp (cd->ncbytes, cd->nubytes, &cd->decomp);
        if (ret < 0)
            goto end;
    }

    if (compression == GCAB_COMPRESSION_MSZIP) {
        if (cd->in[0] != 'C' || cd->in[1] != 'K')
            goto end;

        cd->decomp.comptype = compression;
        z_stream *z = &cd->z;

        z->avail_in = cd->ncbytes - 2;
        z->next_in = cd->in + 2;
        z->avail_out = cd->nubytes;
        z->next_out = cd->out;
        z->total_out = 0;

        if (!z->opaque) {
            z->zalloc = zalloc;
            z->zfree = zfree;
            z->opaque = cd;

            zret = inflateInit2 (z, -MAX_WBITS);
            if (zret != Z_OK)
                goto end;
        }

        while (1) {
            zret = inflate (z, Z_BLOCK);
            if (zret == Z_STREAM_END)
                break;
            if (zret != Z_OK)
                goto end;
        }

        g_warn_if_fail (z->avail_in == 0);
        g_warn_if_fail (z->avail_out == 0);
        if (z->avail_in != 0 || z->avail_out != 0)
            goto end;

        zret = inflateReset (z);
        if (zret != Z_OK)
            goto end;

        zret = inflateSetDictionary (z, cd->out, cd->nubytes);
        if (zret != Z_OK)
            goto end;
    }

    success = TRUE;

end:
    if (zret != Z_OK)
        g_set_error (error, GCAB_ERROR, GCAB_ERROR_FAILED,
                     "zlib failed: %s", zError (zret));

    if (error != NULL && *error == NULL && !success)
        g_set_error (error, GCAB_ERROR, GCAB_ERROR_FAILED,
                     "Invalid cabinet chunk");

    return success;
}


plug-ins: TGA 16-bit RGB (without alpha bit) is also valid.

According to some spec on the web, 16-bit RGB is also valid. In this
case, the last bit is simply ignored (at least that's how it is
implemented right now).

load_image (const gchar  *filename,
            GError      **error)
{
  FILE     *fp;
  tga_info  info;
  guchar    header[18];
  guchar    footer[26];
  guchar    extension[495];
  long      offset;
  gint32    image_ID = -1;

  gimp_progress_init_printf (_("Opening '%s'"),
                             gimp_filename_to_utf8 (filename));

  fp = g_fopen (filename, "rb");

  if (! fp)
    {
      g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
                   _("Could not open '%s' for reading: %s"),
                   gimp_filename_to_utf8 (filename), g_strerror (errno));
      return -1;
    }

  /* Is file big enough for a footer? */
  if (!fseek (fp, -26L, SEEK_END))
    {
      if (fread (footer, sizeof (footer), 1, fp) != 1)
        {
          g_message (_("Cannot read footer from '%s'"),
                     gimp_filename_to_utf8 (filename));
          return -1;
        }
      else if (memcmp (footer + 8, magic, sizeof (magic)) == 0)
        {
          /* Check the signature. */

          offset = (footer[0]          +
                    footer[1] * 256L   +
                    footer[2] * 65536L +
                    footer[3] * 16777216L);

          if (offset != 0)
            {
              if (fseek (fp, offset, SEEK_SET) ||
                  fread (extension, sizeof (extension), 1, fp) != 1)
                {
                  g_message (_("Cannot read extension from '%s'"),
                             gimp_filename_to_utf8 (filename));
                  return -1;
                }
              /* Eventually actually handle version 2 TGA here */
            }
        }
    }

  if (fseek (fp, 0, SEEK_SET) ||
      fread (header, sizeof (header), 1, fp) != 1)
    {
      g_message (_("Cannot read header from '%s'"),
                 gimp_filename_to_utf8 (filename));
      return -1;
    }

  switch (header[2])
    {
    case 1:
      info.imageType        = TGA_TYPE_MAPPED;
      info.imageCompression = TGA_COMP_NONE;
      break;
    case 2:
      info.imageType        = TGA_TYPE_COLOR;
      info.imageCompression = TGA_COMP_NONE;
      break;
    case 3:
      info.imageType        = TGA_TYPE_GRAY;
      info.imageCompression = TGA_COMP_NONE;
      break;

    case 9:
      info.imageType        = TGA_TYPE_MAPPED;
      info.imageCompression = TGA_COMP_RLE;
      break;
    case 10:
      info.imageType        = TGA_TYPE_COLOR;
      info.imageCompression = TGA_COMP_RLE;
      break;
    case 11:
      info.imageType        = TGA_TYPE_GRAY;
      info.imageCompression = TGA_COMP_RLE;
      break;

    default:
      info.imageType = 0;
    }

  info.idLength     = header[0];
  info.colorMapType = header[1];

  info.colorMapIndex  = header[3] + header[4] * 256;
  info.colorMapLength = header[5] + header[6] * 256;
  info.colorMapSize   = header[7];

  info.xOrigin = header[8]  + header[9] * 256;
  info.yOrigin = header[10] + header[11] * 256;
  info.width   = header[12] + header[13] * 256;
  info.height  = header[14] + header[15] * 256;

  info.bpp       = header[16];
  info.bytes     = (info.bpp + 7) / 8;
  info.alphaBits = header[17] & 0x0f; /* Just the low 4 bits */
  info.flipHoriz = (header[17] & 0x10) ? 1 : 0;
  info.flipVert  = (header[17] & 0x20) ? 0 : 1;

  /* hack to handle some existing files with incorrect headers, see bug #306675 */
  if (info.alphaBits == info.bpp)
    info.alphaBits = 0;

  /* hack to handle yet another flavor of incorrect headers, see bug #540969 */
  if (info.alphaBits == 0)
    {
      if (info.imageType == TGA_TYPE_COLOR && info.bpp == 32)
        info.alphaBits = 8;

      if (info.imageType == TGA_TYPE_GRAY && info.bpp == 16)
        info.alphaBits = 8;
    }

  switch (info.imageType)
    {
      case TGA_TYPE_MAPPED:
        if (info.bpp != 8)
          {
            g_message ("Unhandled sub-format in '%s' (type = %u, bpp = %u)",
                       gimp_filename_to_utf8 (filename),
                       info.imageType, info.bpp);
            return -1;
          }
        break;
      case TGA_TYPE_COLOR:
        if ((info.bpp != 15 && info.bpp != 16 &&
             info.bpp != 24 && info.bpp != 32)      ||
            ((info.bpp == 15 || info.bpp == 24) &&
             info.alphaBits != 0)                   ||
            (info.bpp == 16 && info.alphaBits != 1) ||
            (info.bpp == 32 && info.alphaBits != 8))
          {
            g_message ("Unhandled sub-format in '%s' (type = %u, bpp = %u, alpha = %u)",
                       gimp_filename_to_utf8 (filename),
                       info.imageType, info.bpp, info.alphaBits);
            return -1;
          }
        break;
      case TGA_TYPE_GRAY:
        if (info.bpp != 8 &&
            (info.alphaBits != 8 || (info.bpp != 16 && info.bpp != 15)))
          {
            g_message ("Unhandled sub-format in '%s' (type = %u, bpp = %u)",
                       gimp_filename_to_utf8 (filename),
                       info.imageType, info.bpp);
            return -1;
          }
        break;

      default:
        g_message ("Unknown image type %u for '%s'",
                   info.imageType, gimp_filename_to_utf8 (filename));
        return -1;
    }

  /* Plausible but unhandled formats */
  if (info.bytes * 8 != info.bpp && info.bpp != 15)
    {
      g_message ("Unhandled sub-format in '%s' (type = %u, bpp = %u)",
                 gimp_filename_to_utf8 (filename),
                 info.imageType, info.bpp);
      return -1;
    }

  /* Check that we have a color map only when we need it. */
  if (info.imageType == TGA_TYPE_MAPPED && info.colorMapType != 1)
    {
      g_message ("Indexed image has invalid color map type %u",
                 info.colorMapType);
      return -1;
    }
  else if (info.imageType != TGA_TYPE_MAPPED && info.colorMapType != 0)
    {
      g_message ("Non-indexed image has invalid color map type %u",
                 info.colorMapType);
      return -1;
    }

  /* Skip the image ID field. */
  if (info.idLength && fseek (fp, info.idLength, SEEK_CUR))
    {
      g_message ("File '%s' is truncated or corrupted",
                 gimp_filename_to_utf8 (filename));
      return -1;
    }

  image_ID = ReadImage (fp, &info, filename);

  fclose (fp);

  return image_ID;
}

In GIMP 2.8.22, there is a heap-based buffer over-read in ReadImage in plug-ins/common/file-tga.c (related to bgr2rgb.part.1) via an unexpected bits-per-pixel value for an RGBA image.


load_image (const gchar  *filename,
            GError      **error)
{
  FILE     *fp;
  tga_info  info;
  guchar    header[18];
  guchar    footer[26];
  guchar    extension[495];
  long      offset;
  gint32    image_ID = -1;

  gimp_progress_init_printf (_("Opening '%s'"),
                             gimp_filename_to_utf8 (filename));

  fp = g_fopen (filename, "rb");

  if (! fp)
    {
      g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
                   _("Could not open '%s' for reading: %s"),
                   gimp_filename_to_utf8 (filename), g_strerror (errno));
      return -1;
    }

  /* Is file big enough for a footer? */
  if (!fseek (fp, -26L, SEEK_END))
    {
      if (fread (footer, sizeof (footer), 1, fp) != 1)
        {
          g_message (_("Cannot read footer from '%s'"),
                     gimp_filename_to_utf8 (filename));
          return -1;
        }
      else if (memcmp (footer + 8, magic, sizeof (magic)) == 0)
        {
          /* Check the signature. */

          offset = (footer[0]          +
                    footer[1] * 256L   +
                    footer[2] * 65536L +
                    footer[3] * 16777216L);

          if (offset != 0)
            {
              if (fseek (fp, offset, SEEK_SET) ||
                  fread (extension, sizeof (extension), 1, fp) != 1)
                {
                  g_message (_("Cannot read extension from '%s'"),
                             gimp_filename_to_utf8 (filename));
                  return -1;
                }
              /* Eventually actually handle version 2 TGA here */
            }
        }
    }

  if (fseek (fp, 0, SEEK_SET) ||
      fread (header, sizeof (header), 1, fp) != 1)
    {
      g_message (_("Cannot read header from '%s'"),
                 gimp_filename_to_utf8 (filename));
      return -1;
    }

  switch (header[2])
    {
    case 1:
      info.imageType        = TGA_TYPE_MAPPED;
      info.imageCompression = TGA_COMP_NONE;
      break;
    case 2:
      info.imageType        = TGA_TYPE_COLOR;
      info.imageCompression = TGA_COMP_NONE;
      break;
    case 3:
      info.imageType        = TGA_TYPE_GRAY;
      info.imageCompression = TGA_COMP_NONE;
      break;

    case 9:
      info.imageType        = TGA_TYPE_MAPPED;
      info.imageCompression = TGA_COMP_RLE;
      break;
    case 10:
      info.imageType        = TGA_TYPE_COLOR;
      info.imageCompression = TGA_COMP_RLE;
      break;
    case 11:
      info.imageType        = TGA_TYPE_GRAY;
      info.imageCompression = TGA_COMP_RLE;
      break;

    default:
      info.imageType = 0;
    }

  info.idLength     = header[0];
  info.colorMapType = header[1];

  info.colorMapIndex  = header[3] + header[4] * 256;
  info.colorMapLength = header[5] + header[6] * 256;
  info.colorMapSize   = header[7];

  info.xOrigin = header[8]  + header[9] * 256;
  info.yOrigin = header[10] + header[11] * 256;
  info.width   = header[12] + header[13] * 256;
  info.height  = header[14] + header[15] * 256;

  info.bpp       = header[16];
  info.bytes     = (info.bpp + 7) / 8;
  info.alphaBits = header[17] & 0x0f; /* Just the low 4 bits */
  info.flipHoriz = (header[17] & 0x10) ? 1 : 0;
  info.flipVert  = (header[17] & 0x20) ? 0 : 1;

  /* hack to handle some existing files with incorrect headers, see bug #306675 */
  if (info.alphaBits == info.bpp)
    info.alphaBits = 0;

  /* hack to handle yet another flavor of incorrect headers, see bug #540969 */
  if (info.alphaBits == 0)
    {
      if (info.imageType == TGA_TYPE_COLOR && info.bpp == 32)
        info.alphaBits = 8;

      if (info.imageType == TGA_TYPE_GRAY && info.bpp == 16)
        info.alphaBits = 8;
    }

  switch (info.imageType)
    {
      case TGA_TYPE_MAPPED:
        if (info.bpp != 8)
          {
            g_message ("Unhandled sub-format in '%s' (type = %u, bpp = %u)",
                       gimp_filename_to_utf8 (filename),
                       info.imageType, info.bpp);
            return -1;
          }
        break;
      case TGA_TYPE_COLOR:
        if ((info.bpp != 15 && info.bpp != 16 &&
             info.bpp != 24 && info.bpp != 32)      ||
            ((info.bpp == 15 || info.bpp == 24) &&
             info.alphaBits != 0)                   ||
            (info.bpp == 16 && info.alphaBits != 1 &&
             info.alphaBits != 0)                   ||
            (info.bpp == 32 && info.alphaBits != 8))
          {
            g_message ("Unhandled sub-format in '%s' (type = %u, bpp = %u, alpha = %u)",
                       gimp_filename_to_utf8 (filename),
                       info.imageType, info.bpp, info.alphaBits);
            return -1;
          }
        break;
      case TGA_TYPE_GRAY:
        if (info.bpp != 8 &&
            (info.alphaBits != 8 || (info.bpp != 16 && info.bpp != 15)))
          {
            g_message ("Unhandled sub-format in '%s' (type = %u, bpp = %u)",
                       gimp_filename_to_utf8 (filename),
                       info.imageType, info.bpp);
            return -1;
          }
        break;

      default:
        g_message ("Unknown image type %u for '%s'",
                   info.imageType, gimp_filename_to_utf8 (filename));
        return -1;
    }

  /* Plausible but unhandled formats */
  if (info.bytes * 8 != info.bpp && info.bpp != 15)
    {
      g_message ("Unhandled sub-format in '%s' (type = %u, bpp = %u)",
                 gimp_filename_to_utf8 (filename),
                 info.imageType, info.bpp);
      return -1;
    }

  /* Check that we have a color map only when we need it. */
  if (info.imageType == TGA_TYPE_MAPPED && info.colorMapType != 1)
    {
      g_message ("Indexed image has invalid color map type %u",
                 info.colorMapType);
      return -1;
    }
  else if (info.imageType != TGA_TYPE_MAPPED && info.colorMapType != 0)
    {
      g_message ("Non-indexed image has invalid color map type %u",
                 info.colorMapType);
      return -1;
    }

  /* Skip the image ID field. */
  if (info.idLength && fseek (fp, info.idLength, SEEK_CUR))
    {
      g_message ("File '%s' is truncated or corrupted",
                 gimp_filename_to_utf8 (filename));
      return -1;
    }

  image_ID = ReadImage (fp, &info, filename);

  fclose (fp);

  return image_ID;
}


Bug 739133 - (CVE-2017-17785) Heap overflow while parsing FLI files.

It is possible to trigger a heap overflow while parsing FLI files. The
RLE decoder is vulnerable to out of boundary writes due to lack of
boundary checks.

The variable "framebuf" points to a memory area which was allocated
with fli_header->width * fli_header->height bytes. The RLE decoder
therefore must never write beyond that limit.

If an illegal frame is detected, the parser won't stop, which means
that the next valid sequence is properly parsed again. This should
allow GIMP to parse FLI files as good as possible even if they are
broken by an attacker or by accident.

While at it, I changed the variable xc to be of type size_t, because
the multiplication of width and height could overflow a 16 bit type.

Signed-off-by: Tobias Stoeckmann <tobias@stoeckmann.org>

void fli_read_brun(FILE *f, s_fli_header *fli_header, unsigned char *framebuf)
{
	unsigned short yc;
	unsigned char *pos;
	for (yc=0; yc < fli_header->height; yc++) {
		unsigned short xc, pc, pcnt;
		pc=fli_read_char(f);
		xc=0;
		pos=framebuf+(fli_header->width * yc);
		for (pcnt=pc; pcnt>0; pcnt--) {
			unsigned short ps;
			ps=fli_read_char(f);
			if (ps & 0x80) {
				unsigned short len;
				for (len=-(signed char)ps; len>0; len--) {
					pos[xc++]=fli_read_char(f);
				}
			} else {
				unsigned char val;
				val=fli_read_char(f);
				memset(&(pos[xc]), val, ps);
				xc+=ps;
			}
		}
	}
}

In GIMP 2.8.22, there is a heap-based buffer overflow in the fli_read_brun function in plug-ins/file-fli/fli.c.


void fli_read_brun(FILE *f, s_fli_header *fli_header, unsigned char *framebuf)
{
	unsigned short yc;
	unsigned char *pos;
	for (yc=0; yc < fli_header->height; yc++) {
		unsigned short pc, pcnt;
		size_t n, xc;
		pc=fli_read_char(f);
		xc=0;
		pos=framebuf+(fli_header->width * yc);
		n=(size_t)fli_header->width * (fli_header->height-yc);
		for (pcnt=pc; pcnt>0; pcnt--) {
			unsigned short ps;
			ps=fli_read_char(f);
			if (ps & 0x80) {
				unsigned short len;
				for (len=-(signed char)ps; len>0 && xc<n; len--) {
					pos[xc++]=fli_read_char(f);
				}
			} else {
				unsigned char val;
				size_t len;
				len=MIN(n-xc,ps);
				val=fli_read_char(f);
				memset(&(pos[xc]), val, len);
				xc+=len;
			}
		}
	}
}


get_cdtext_generic (void *p_user_data)
{
  generic_img_private_t *p_env = p_user_data;
  uint8_t *p_cdtext_data = NULL;
  size_t  len;

  if (!p_env) return NULL;

  if (p_env->b_cdtext_error) return NULL;

  if (NULL == p_env->cdtext) {
    p_cdtext_data = read_cdtext_generic (p_env);

    if (NULL != p_cdtext_data) {
      len = CDIO_MMC_GET_LEN16(p_cdtext_data)-2;
      p_env->cdtext = cdtext_init();

      if(len <= 0 || 0 != cdtext_data_init (p_env->cdtext, &p_cdtext_data[4], len)) {
        p_env->b_cdtext_error = true;
        free(p_env->cdtext);
        p_env->cdtext = NULL;
      }

      free(p_cdtext_data);
    }
  }

  return p_env->cdtext;
}

An issue was discovered in GNU libcdio before 2.0.0. There is a double free in get_cdtext_generic() in lib/driver/_cdio_generic.c.


get_cdtext_generic (void *p_user_data)
{
  generic_img_private_t *p_env = p_user_data;
  uint8_t *p_cdtext_data = NULL;
  size_t  len;

  if (!p_env) return NULL;

  if (p_env->b_cdtext_error) return NULL;

  if (NULL == p_env->cdtext) {
    p_cdtext_data = read_cdtext_generic (p_env);

    if (NULL != p_cdtext_data) {
      len = CDIO_MMC_GET_LEN16(p_cdtext_data)-2;
      p_env->cdtext = cdtext_init();

      if(len <= 0 || 0 != cdtext_data_init (p_env->cdtext, &p_cdtext_data[4], len)) {
        p_env->b_cdtext_error = true;
        cdtext_destroy (p_env->cdtext);
        p_env->cdtext = NULL;
      }

      free(p_cdtext_data);
    }
  }

  return p_env->cdtext;
}


Fix cookie injection (CVE-2018-0494)

* src/http.c (resp_new): Replace \r\n by space in continuation lines

Fixes #53763
 "Malicious website can write arbitrary cookie entries to cookie jar"

HTTP header parsing left the \r\n from continuation line intact.
The Set-Cookie code didn't check and could be tricked to write
\r\n into the cookie jar, allowing a server to generate cookies at will.

resp_new (const char *head)
{
  const char *hdr;
  int count, size;

  struct response *resp = xnew0 (struct response);
  resp->data = head;

  if (*head == '\0')
    {
      /* Empty head means that we're dealing with a headerless
         (HTTP/0.9) response.  In that case, don't set HEADERS at
         all.  */
      return resp;
    }

  /* Split HEAD into header lines, so that resp_header_* functions
     don't need to do this over and over again.  */

  size = count = 0;
  hdr = head;
  while (1)
    {
      DO_REALLOC (resp->headers, size, count + 1, const char *);
      resp->headers[count++] = hdr;

      /* Break upon encountering an empty line. */
      if (!hdr[0] || (hdr[0] == '\r' && hdr[1] == '\n') || hdr[0] == '\n')
        break;

      /* Find the end of HDR, including continuations. */
      do
        {
          const char *end = strchr (hdr, '\n');
          if (end)
            hdr = end + 1;
          else
            hdr += strlen (hdr);
        }
      while (*hdr == ' ' || *hdr == '\t');
    }
  DO_REALLOC (resp->headers, size, count + 1, const char *);
  resp->headers[count] = NULL;

  return resp;
}

GNU Wget before 1.19.5 is prone to a cookie injection vulnerability in the resp_new function in http.c via a \r\n sequence in a continuation line.


resp_new (char *head)
{
  char *hdr;
  int count, size;

  struct response *resp = xnew0 (struct response);
  resp->data = head;

  if (*head == '\0')
    {
      /* Empty head means that we're dealing with a headerless
         (HTTP/0.9) response.  In that case, don't set HEADERS at
         all.  */
      return resp;
    }

  /* Split HEAD into header lines, so that resp_header_* functions
     don't need to do this over and over again.  */

  size = count = 0;
  hdr = head;
  while (1)
    {
      DO_REALLOC (resp->headers, size, count + 1, const char *);
      resp->headers[count++] = hdr;

      /* Break upon encountering an empty line. */
      if (!hdr[0] || (hdr[0] == '\r' && hdr[1] == '\n') || hdr[0] == '\n')
        break;

      /* Find the end of HDR, including continuations. */
      for (;;)
        {
          char *end = strchr (hdr, '\n');

          if (end)
            hdr = end + 1;
          else
            hdr += strlen (hdr);

          if (*hdr != ' ' && *hdr != '\t')
            break;

          // continuation, transform \r and \n into spaces
          *end = ' ';
          if (end > head && end[-1] == '\r')
            end[-1] = ' ';
        }
    }
  DO_REALLOC (resp->headers, size, count + 1, const char *);
  resp->headers[count] = NULL;

  return resp;
}


wget: implement TLS verification with ENABLE_FEATURE_WGET_OPENSSL

When ENABLE_FEATURE_WGET_OPENSSL is enabled, correctly implement TLS
verification by default. And only ignore verification errors, if
--no-check-certificate was passed.

Also note, that previously OPENSSL implementation did not implement
TLS verification, nor printed any warning messages that verification
was not performed.

Bug-Ubuntu: https://bugs.launchpad.net/bugs/1879533

CVE-2018-1000500

Signed-off-by: Dimitri John Ledkov <xnox@ubuntu.com>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>

static int spawn_https_helper_openssl(const char *host, unsigned port)
{
	char *allocated = NULL;
	char *servername;
	int sp[2];
	int pid;
	IF_FEATURE_WGET_HTTPS(volatile int child_failed = 0;)

	if (socketpair(AF_UNIX, SOCK_STREAM, 0, sp) != 0)
		/* Kernel can have AF_UNIX support disabled */
		bb_simple_perror_msg_and_die("socketpair");

	if (!strchr(host, ':'))
		host = allocated = xasprintf("%s:%u", host, port);
	servername = xstrdup(host);
	strrchr(servername, ':')[0] = '\0';

	fflush_all();
	pid = xvfork();
	if (pid == 0) {
		/* Child */
		char *argv[8];

		close(sp[0]);
		xmove_fd(sp[1], 0);
		xdup2(0, 1);
		/*
		 * openssl s_client -quiet -connect www.kernel.org:443 2>/dev/null
		 * It prints some debug stuff on stderr, don't know how to suppress it.
		 * Work around by dev-nulling stderr. We lose all error messages :(
		 */
		xmove_fd(2, 3);
		xopen("/dev/null", O_RDWR);
		memset(&argv, 0, sizeof(argv));
		argv[0] = (char*)"openssl";
		argv[1] = (char*)"s_client";
		argv[2] = (char*)"-quiet";
		argv[3] = (char*)"-connect";
		argv[4] = (char*)host;
		/*
		 * Per RFC 6066 Section 3, the only permitted values in the
		 * TLS server_name (SNI) field are FQDNs (DNS hostnames).
		 * IPv4 and IPv6 addresses, port numbers are not allowed.
		 */
		if (!is_ip_address(servername)) {
			argv[5] = (char*)"-servername";
			argv[6] = (char*)servername;
		}

		BB_EXECVP(argv[0], argv);
		xmove_fd(3, 2);
# if ENABLE_FEATURE_WGET_HTTPS
		child_failed = 1;
		xfunc_die();
# else
		bb_perror_msg_and_die("can't execute '%s'", argv[0]);
# endif
		/* notreached */
	}

	/* Parent */
	free(servername);
	free(allocated);
	close(sp[1]);
# if ENABLE_FEATURE_WGET_HTTPS
	if (child_failed) {
		close(sp[0]);
		return -1;
	}
# endif
	return sp[0];
}

Busybox contains a Missing SSL certificate validation vulnerability in The "busybox wget" applet that can result in arbitrary code execution. This attack appear to be exploitable via Simply download any file over HTTPS using "busybox wget https://compromised-domain.com/important-file".


static int spawn_https_helper_openssl(const char *host, unsigned port)
{
	char *allocated = NULL;
	char *servername;
	int sp[2];
	int pid;
	IF_FEATURE_WGET_HTTPS(volatile int child_failed = 0;)

	if (socketpair(AF_UNIX, SOCK_STREAM, 0, sp) != 0)
		/* Kernel can have AF_UNIX support disabled */
		bb_simple_perror_msg_and_die("socketpair");

	if (!strchr(host, ':'))
		host = allocated = xasprintf("%s:%u", host, port);
	servername = xstrdup(host);
	strrchr(servername, ':')[0] = '\0';

	fflush_all();
	pid = xvfork();
	if (pid == 0) {
		/* Child */
		char *argv[9];

		close(sp[0]);
		xmove_fd(sp[1], 0);
		xdup2(0, 1);
		/*
		 * openssl s_client -quiet -connect www.kernel.org:443 2>/dev/null
		 * It prints some debug stuff on stderr, don't know how to suppress it.
		 * Work around by dev-nulling stderr. We lose all error messages :(
		 */
		xmove_fd(2, 3);
		xopen("/dev/null", O_RDWR);
		memset(&argv, 0, sizeof(argv));
		argv[0] = (char*)"openssl";
		argv[1] = (char*)"s_client";
		argv[2] = (char*)"-quiet";
		argv[3] = (char*)"-connect";
		argv[4] = (char*)host;
		/*
		 * Per RFC 6066 Section 3, the only permitted values in the
		 * TLS server_name (SNI) field are FQDNs (DNS hostnames).
		 * IPv4 and IPv6 addresses, port numbers are not allowed.
		 */
		if (!is_ip_address(servername)) {
			argv[5] = (char*)"-servername";
			argv[6] = (char*)servername;
		}
		if (!(option_mask32 & WGET_OPT_NO_CHECK_CERT)) {
			argv[7] = (char*)"-verify_return_error";
		}

		BB_EXECVP(argv[0], argv);
		xmove_fd(3, 2);
# if ENABLE_FEATURE_WGET_HTTPS
		child_failed = 1;
		xfunc_die();
# else
		bb_perror_msg_and_die("can't execute '%s'", argv[0]);
# endif
		/* notreached */
	}

	/* Parent */
	free(servername);
	free(allocated);
	close(sp[1]);
# if ENABLE_FEATURE_WGET_HTTPS
	if (child_failed) {
		close(sp[0]);
		return -1;
	}
# endif
	return sp[0];
}


Don't save user/pw with --xattr

Also the Referer info is reduced to scheme+host+port.

* src/ftp.c (getftp): Change params of set_file_metadata()
* src/http.c (gethttp): Change params of set_file_metadata()
* src/xattr.c (set_file_metadata): Remove user/password from origin URL,
  reduce Referer value to scheme/host/port.
* src/xattr.h: Change prototype of set_file_metadata()

set_file_metadata (const char *origin_url, const char *referrer_url, FILE *fp)
{
  /* Save metadata about where the file came from (requested, final URLs) to
   * user POSIX Extended Attributes of retrieved file.
   *
   * For more details about the user namespace see
   * [http://freedesktop.org/wiki/CommonExtendedAttributes] and
   * [http://0pointer.de/lennart/projects/mod_mime_xattr/].
   */
  int retval = -1;

  if (!origin_url || !fp)
    return retval;

  retval = write_xattr_metadata ("user.xdg.origin.url", escnonprint_uri (origin_url), fp);
  if ((!retval) && referrer_url)
    retval = write_xattr_metadata ("user.xdg.referrer.url", escnonprint_uri (referrer_url), fp);

  return retval;
}

set_file_metadata in xattr.c in GNU Wget before 1.20.1 stores a file's origin URL in the user.xdg.origin.url metadata attribute of the extended attributes of the downloaded file, which allows local users to obtain sensitive information (e.g., credentials contained in the URL) by reading this attribute, as demonstrated by getfattr. This also applies to Referer information in the user.xdg.referrer.url metadata attribute. According to 2016-07-22 in the Wget ChangeLog, user.xdg.origin.url was partially based on the behavior of fwrite_xattr in tool_xattr.c in curl.


set_file_metadata (const struct url *origin_url, const struct url *referrer_url, FILE *fp)
{
  /* Save metadata about where the file came from (requested, final URLs) to
   * user POSIX Extended Attributes of retrieved file.
   *
   * For more details about the user namespace see
   * [http://freedesktop.org/wiki/CommonExtendedAttributes] and
   * [http://0pointer.de/lennart/projects/mod_mime_xattr/].
   */
  int retval = -1;
  char *value;

  if (!origin_url || !fp)
    return retval;

  value = url_string (origin_url, URL_AUTH_HIDE);
  retval = write_xattr_metadata ("user.xdg.origin.url", escnonprint_uri (value), fp);
  xfree (value);

  if (!retval && referrer_url)
    {
	  struct url u;

	  memset(&u, 0, sizeof(u));
      u.scheme = referrer_url->scheme;
      u.host = referrer_url->host;
      u.port = referrer_url->port;

      value = url_string (&u, 0);
      retval = write_xattr_metadata ("user.xdg.referrer.url", escnonprint_uri (value), fp);
      xfree (value);
    }

  return retval;
}


Add bounds check in in_escape state of JSON parser.

static int json_internal_read_object(const char *cp,
				     const struct json_attr_t *attrs,
				     const struct json_array_t *parent,
				     int offset,
				     const char **end)
{
    enum
    { init, await_attr, in_attr, await_value, in_val_string,
	in_escape, in_val_token, post_val, post_array
    } state = 0;
#ifdef CLIENTDEBUG_ENABLE
    char *statenames[] = {
	"init", "await_attr", "in_attr", "await_value", "in_val_string",
	"in_escape", "in_val_token", "post_val", "post_array",
    };
#endif /* CLIENTDEBUG_ENABLE */
    char attrbuf[JSON_ATTR_MAX + 1], *pattr = NULL;
    char valbuf[JSON_VAL_MAX + 1], *pval = NULL;
    bool value_quoted = false;
    char uescape[5];		/* enough space for 4 hex digits and a NUL */
    const struct json_attr_t *cursor;
    int substatus, n, maxlen = 0;
    unsigned int u;
    const struct json_enum_t *mp;
    char *lptr;

    if (end != NULL)
	*end = NULL;	/* give it a well-defined value on parse failure */

    /* stuff fields with defaults in case they're omitted in the JSON input */
    for (cursor = attrs; cursor->attribute != NULL; cursor++)
	if (!cursor->nodefault) {
	    lptr = json_target_address(cursor, parent, offset);
	    if (lptr != NULL)
		switch (cursor->type) {
		case t_integer:
		    memcpy(lptr, &cursor->dflt.integer, sizeof(int));
		    break;
		case t_uinteger:
		    memcpy(lptr, &cursor->dflt.uinteger, sizeof(unsigned int));
		    break;
		case t_short:
		    memcpy(lptr, &cursor->dflt.shortint, sizeof(short));
		    break;
		case t_ushort:
		    memcpy(lptr, &cursor->dflt.ushortint,
		           sizeof(unsigned short));
		    break;
		case t_time:
		case t_real:
		    memcpy(lptr, &cursor->dflt.real, sizeof(double));
		    break;
		case t_string:
		    if (parent != NULL
			&& parent->element_type != t_structobject
			&& offset > 0)
			return JSON_ERR_NOPARSTR;
		    lptr[0] = '\0';
		    break;
		case t_boolean:
		    memcpy(lptr, &cursor->dflt.boolean, sizeof(bool));
		    break;
		case t_character:
		    lptr[0] = cursor->dflt.character;
		    break;
		case t_object:	/* silences a compiler warning */
		case t_structobject:
		case t_array:
		case t_check:
		case t_ignore:
		    break;
		}
	}

    json_debug_trace((1, "JSON parse of '%s' begins.\n", cp));

    /* parse input JSON */
    for (; *cp != '\0'; cp++) {
	json_debug_trace((2, "State %-14s, looking at '%c' (%p)\n",
			  statenames[state], *cp, cp));
	switch (state) {
	case init:
	    if (isspace((unsigned char) *cp))
		continue;
	    else if (*cp == '{')
		state = await_attr;
	    else {
		json_debug_trace((1,
				  "Non-WS when expecting object start.\n"));
#ifndef JSON_MINIMAL
		if (end != NULL)
		    *end = cp;
#endif /* JSON_MINIMAL */
		return JSON_ERR_OBSTART;
	    }
	    break;
	case await_attr:
	    if (isspace((unsigned char) *cp))
		continue;
	    else if (*cp == '"') {
		state = in_attr;
		pattr = attrbuf;
#ifndef JSON_MINIMAL
		if (end != NULL)
		    *end = cp;
#endif /* JSON_MINIMAL */
	    } else if (*cp == '}')
		break;
	    else {
		json_debug_trace((1, "Non-WS when expecting attribute.\n"));
#ifndef JSON_MINIMAL
		if (end != NULL)
		    *end = cp;
#endif /* JSON_MINIMAL */
		return JSON_ERR_ATTRSTART;
	    }
	    break;
	case in_attr:
	    if (pattr == NULL)
		/* don't update end here, leave at attribute start */
		return JSON_ERR_NULLPTR;
	    if (*cp == '"') {
		*pattr++ = '\0';
		json_debug_trace((1, "Collected attribute name %s\n",
				  attrbuf));
		for (cursor = attrs; cursor->attribute != NULL; cursor++) {
		    json_debug_trace((2, "Checking against %s\n",
				      cursor->attribute));
		    if (strcmp(cursor->attribute, attrbuf) == 0)
			break;
		}
		if (cursor->attribute == NULL) {
		    json_debug_trace((1,
				      "Unknown attribute name '%s'"
                                      " (attributes begin with '%s').\n",
				      attrbuf, attrs->attribute));
		    /* don't update end here, leave at attribute start */
		    return JSON_ERR_BADATTR;
		}
		state = await_value;
		if (cursor->type == t_string)
		    maxlen = (int)cursor->len - 1;
		else if (cursor->type == t_check)
		    maxlen = (int)strlen(cursor->dflt.check);
		else if (cursor->type == t_time || cursor->type == t_ignore)
		    maxlen = JSON_VAL_MAX;
		else if (cursor->map != NULL)
		    maxlen = (int)sizeof(valbuf) - 1;
		pval = valbuf;
	    } else if (pattr >= attrbuf + JSON_ATTR_MAX - 1) {
		json_debug_trace((1, "Attribute name too long.\n"));
		/* don't update end here, leave at attribute start */
		return JSON_ERR_ATTRLEN;
	    } else
		*pattr++ = *cp;
	    break;
	case await_value:
	    if (isspace((unsigned char) *cp) || *cp == ':')
		continue;
	    else if (*cp == '[') {
		if (cursor->type != t_array) {
		    json_debug_trace((1,
				      "Saw [ when not expecting array.\n"));
#ifndef JSON_MINIMAL
		    if (end != NULL)
			*end = cp;
#endif /* JSON_MINIMAL */
		    return JSON_ERR_NOARRAY;
		}
		substatus = json_read_array(cp, &cursor->addr.array, &cp);
		if (substatus != 0)
		    return substatus;
		state = post_array;
	    } else if (cursor->type == t_array) {
		json_debug_trace((1,
				  "Array element was specified, but no [.\n"));
#ifndef JSON_MINIMAL
		if (end != NULL)
		    *end = cp;
#endif /* JSON_MINIMAL */
		return JSON_ERR_NOBRAK;
	    } else if (*cp == '"') {
		value_quoted = true;
		state = in_val_string;
		pval = valbuf;
	    } else {
		value_quoted = false;
		state = in_val_token;
		pval = valbuf;
		*pval++ = *cp;
	    }
	    break;
	case in_val_string:
	    if (pval == NULL)
		/* don't update end here, leave at value start */
		return JSON_ERR_NULLPTR;
	    if (*cp == '\\')
		state = in_escape;
	    else if (*cp == '"') {
		*pval++ = '\0';
		json_debug_trace((1, "Collected string value %s\n", valbuf));
		state = post_val;
	    } else if (pval > valbuf + JSON_VAL_MAX - 1
		       || pval > valbuf + maxlen) {
		json_debug_trace((1, "String value too long.\n"));
		/* don't update end here, leave at value start */
		return JSON_ERR_STRLONG;	/*  */
	    } else
		*pval++ = *cp;
	    break;
	case in_escape:
	    if (pval == NULL)
		/* don't update end here, leave at value start */
		return JSON_ERR_NULLPTR;
	    switch (*cp) {
	    case 'b':
		*pval++ = '\b';
		break;
	    case 'f':
		*pval++ = '\f';
		break;
	    case 'n':
		*pval++ = '\n';
		break;
	    case 'r':
		*pval++ = '\r';
		break;
	    case 't':
		*pval++ = '\t';
		break;
	    case 'u':
                cp++;                   /* skip the 'u' */
		for (n = 0; n < 4 && isxdigit(*cp); n++)
		    uescape[n] = *cp++;
                uescape[n] = '\0';      /* terminate */
		--cp;
		if (1 != sscanf(uescape, "%4x", &u)) {
		    return JSON_ERR_BADSTRING;
                }
		*pval++ = (char)u;  /* will truncate values above 0xff */
		break;
	    default:		/* handles double quote and solidus */
		*pval++ = *cp;
		break;
	    }
	    state = in_val_string;
	    break;
	case in_val_token:
	    if (pval == NULL)
		/* don't update end here, leave at value start */
		return JSON_ERR_NULLPTR;
	    if (isspace((unsigned char) *cp) || *cp == ',' || *cp == '}') {
		*pval = '\0';
		json_debug_trace((1, "Collected token value %s.\n", valbuf));
		state = post_val;
		if (*cp == '}' || *cp == ',')
		    --cp;
	    } else if (pval > valbuf + JSON_VAL_MAX - 1) {
		json_debug_trace((1, "Token value too long.\n"));
		/* don't update end here, leave at value start */
		return JSON_ERR_TOKLONG;
	    } else
		*pval++ = *cp;
	    break;
	    /* coverity[unterminated_case] */
	case post_val:
	    /*
	     * We know that cursor points at the first spec matching
	     * the current attribute.  We don't know that it's *the*
	     * correct spec; our dialect allows there to be any number
	     * of adjacent ones with the same attrname but different
	     * types.  Here's where we try to seek forward for a
	     * matching type/attr pair if we're not looking at one.
	     */
	    for (;;) {
		int seeking = cursor->type;
		if (value_quoted && (cursor->type == t_string
                    || cursor->type == t_time))
		    break;
		if ((strcmp(valbuf, "true")==0 || strcmp(valbuf, "false")==0)
			&& seeking == t_boolean)
		    break;
		if (isdigit((unsigned char) valbuf[0])) {
		    bool decimal = strchr(valbuf, '.') != NULL;
		    if (decimal && seeking == t_real)
			break;
		    if (!decimal && (seeking == t_integer
                                     || seeking == t_uinteger))
			break;
		}
		if (cursor[1].attribute==NULL)	/* out of possiblities */
		    break;
		if (strcmp(cursor[1].attribute, attrbuf)!=0)
		    break;
		++cursor;
	    }
	    if (value_quoted
		&& (cursor->type != t_string && cursor->type != t_character
		    && cursor->type != t_check && cursor->type != t_time
		    && cursor->type != t_ignore && cursor->map == 0)) {
		json_debug_trace((1, "Saw quoted value when expecting"
                                  " non-string.\n"));
		return JSON_ERR_QNONSTRING;
	    }
	    if (!value_quoted
		&& (cursor->type == t_string || cursor->type == t_check
		    || cursor->type == t_time || cursor->map != 0)) {
		json_debug_trace((1, "Didn't see quoted value when expecting"
                                  " string.\n"));
		return JSON_ERR_NONQSTRING;
	    }
	    if (cursor->map != 0) {
		for (mp = cursor->map; mp->name != NULL; mp++)
		    if (strcmp(mp->name, valbuf) == 0) {
			goto foundit;
		    }
		json_debug_trace((1, "Invalid enumerated value string %s.\n",
				  valbuf));
		return JSON_ERR_BADENUM;
	      foundit:
		(void)snprintf(valbuf, sizeof(valbuf), "%d", mp->value);
	    }
	    lptr = json_target_address(cursor, parent, offset);
	    if (lptr != NULL)
		switch (cursor->type) {
		case t_integer:
		    {
			int tmp = atoi(valbuf);
			memcpy(lptr, &tmp, sizeof(int));
		    }
		    break;
		case t_uinteger:
		    {
			unsigned int tmp = (unsigned int)atoi(valbuf);
			memcpy(lptr, &tmp, sizeof(unsigned int));
		    }
		    break;
		case t_short:
		    {
			short tmp = atoi(valbuf);
			memcpy(lptr, &tmp, sizeof(short));
		    }
		    break;
		case t_ushort:
		    {
			unsigned short tmp = (unsigned int)atoi(valbuf);
			memcpy(lptr, &tmp, sizeof(unsigned short));
		    }
		    break;
		case t_time:
		    {
			double tmp = iso8601_to_unix(valbuf);
			memcpy(lptr, &tmp, sizeof(double));
		    }
		    break;
		case t_real:
		    {
			double tmp = safe_atof(valbuf);
			memcpy(lptr, &tmp, sizeof(double));
		    }
		    break;
		case t_string:
		    if (parent != NULL
			&& parent->element_type != t_structobject
			&& offset > 0)
			return JSON_ERR_NOPARSTR;
		    (void)strlcpy(lptr, valbuf, cursor->len);
		    break;
		case t_boolean:
		    {
			bool tmp = (strcmp(valbuf, "true") == 0);
			memcpy(lptr, &tmp, sizeof(bool));
		    }
		    break;
		case t_character:
		    if (strlen(valbuf) > 1)
			/* don't update end here, leave at value start */
			return JSON_ERR_STRLONG;
		    else
			lptr[0] = valbuf[0];
		    break;
		case t_ignore:	/* silences a compiler warning */
		case t_object:	/* silences a compiler warning */
		case t_structobject:
		case t_array:
		    break;
		case t_check:
		    if (strcmp(cursor->dflt.check, valbuf) != 0) {
			json_debug_trace((1, "Required attribute value %s"
                                          " not present.\n",
					  cursor->dflt.check));
			/* don't update end here, leave at start of attribute */
			return JSON_ERR_CHECKFAIL;
		    }
		    break;
		}
	    __attribute__ ((fallthrough));
	case post_array:
	    if (isspace((unsigned char) *cp))
		continue;
	    else if (*cp == ',')
		state = await_attr;
	    else if (*cp == '}') {
		++cp;
		goto good_parse;
	    } else {
		json_debug_trace((1, "Garbage while expecting comma or }\n"));
#ifndef JSON_MINIMAL
		if (end != NULL)
		    *end = cp;
#endif /* JSON_MINIMAL */
		return JSON_ERR_BADTRAIL;
	    }
	    break;
	}
    }

  good_parse:
    /* in case there's another object following, consume trailing WS */
    while (isspace((unsigned char) *cp))
	++cp;
    if (end != NULL)
	*end = cp;
    json_debug_trace((1, "JSON parse ends.\n"));
    return 0;
}

gpsd versions 2.90 to 3.17 and microjson versions 1.0 to 1.3, an open source project, allow a stack-based buffer overflow, which may allow remote attackers to execute arbitrary code on embedded platforms via traffic on Port 2947/TCP or crafted JSON inputs.


static int json_internal_read_object(const char *cp,
				     const struct json_attr_t *attrs,
				     const struct json_array_t *parent,
				     int offset,
				     const char **end)
{
    enum
    { init, await_attr, in_attr, await_value, in_val_string,
	in_escape, in_val_token, post_val, post_array
    } state = 0;
#ifdef CLIENTDEBUG_ENABLE
    char *statenames[] = {
	"init", "await_attr", "in_attr", "await_value", "in_val_string",
	"in_escape", "in_val_token", "post_val", "post_array",
    };
#endif /* CLIENTDEBUG_ENABLE */
    char attrbuf[JSON_ATTR_MAX + 1], *pattr = NULL;
    char valbuf[JSON_VAL_MAX + 1], *pval = NULL;
    bool value_quoted = false;
    char uescape[5];		/* enough space for 4 hex digits and a NUL */
    const struct json_attr_t *cursor;
    int substatus, n, maxlen = 0;
    unsigned int u;
    const struct json_enum_t *mp;
    char *lptr;

    if (end != NULL)
	*end = NULL;	/* give it a well-defined value on parse failure */

    /* stuff fields with defaults in case they're omitted in the JSON input */
    for (cursor = attrs; cursor->attribute != NULL; cursor++)
	if (!cursor->nodefault) {
	    lptr = json_target_address(cursor, parent, offset);
	    if (lptr != NULL)
		switch (cursor->type) {
		case t_integer:
		    memcpy(lptr, &cursor->dflt.integer, sizeof(int));
		    break;
		case t_uinteger:
		    memcpy(lptr, &cursor->dflt.uinteger, sizeof(unsigned int));
		    break;
		case t_short:
		    memcpy(lptr, &cursor->dflt.shortint, sizeof(short));
		    break;
		case t_ushort:
		    memcpy(lptr, &cursor->dflt.ushortint,
		           sizeof(unsigned short));
		    break;
		case t_time:
		case t_real:
		    memcpy(lptr, &cursor->dflt.real, sizeof(double));
		    break;
		case t_string:
		    if (parent != NULL
			&& parent->element_type != t_structobject
			&& offset > 0)
			return JSON_ERR_NOPARSTR;
		    lptr[0] = '\0';
		    break;
		case t_boolean:
		    memcpy(lptr, &cursor->dflt.boolean, sizeof(bool));
		    break;
		case t_character:
		    lptr[0] = cursor->dflt.character;
		    break;
		case t_object:	/* silences a compiler warning */
		case t_structobject:
		case t_array:
		case t_check:
		case t_ignore:
		    break;
		}
	}

    json_debug_trace((1, "JSON parse of '%s' begins.\n", cp));

    /* parse input JSON */
    for (; *cp != '\0'; cp++) {
	json_debug_trace((2, "State %-14s, looking at '%c' (%p)\n",
			  statenames[state], *cp, cp));
	switch (state) {
	case init:
	    if (isspace((unsigned char) *cp))
		continue;
	    else if (*cp == '{')
		state = await_attr;
	    else {
		json_debug_trace((1,
				  "Non-WS when expecting object start.\n"));
#ifndef JSON_MINIMAL
		if (end != NULL)
		    *end = cp;
#endif /* JSON_MINIMAL */
		return JSON_ERR_OBSTART;
	    }
	    break;
	case await_attr:
	    if (isspace((unsigned char) *cp))
		continue;
	    else if (*cp == '"') {
		state = in_attr;
		pattr = attrbuf;
#ifndef JSON_MINIMAL
		if (end != NULL)
		    *end = cp;
#endif /* JSON_MINIMAL */
	    } else if (*cp == '}')
		break;
	    else {
		json_debug_trace((1, "Non-WS when expecting attribute.\n"));
#ifndef JSON_MINIMAL
		if (end != NULL)
		    *end = cp;
#endif /* JSON_MINIMAL */
		return JSON_ERR_ATTRSTART;
	    }
	    break;
	case in_attr:
	    if (pattr == NULL)
		/* don't update end here, leave at attribute start */
		return JSON_ERR_NULLPTR;
	    if (*cp == '"') {
		*pattr++ = '\0';
		json_debug_trace((1, "Collected attribute name %s\n",
				  attrbuf));
		for (cursor = attrs; cursor->attribute != NULL; cursor++) {
		    json_debug_trace((2, "Checking against %s\n",
				      cursor->attribute));
		    if (strcmp(cursor->attribute, attrbuf) == 0)
			break;
		}
		if (cursor->attribute == NULL) {
		    json_debug_trace((1,
				      "Unknown attribute name '%s'"
                                      " (attributes begin with '%s').\n",
				      attrbuf, attrs->attribute));
		    /* don't update end here, leave at attribute start */
		    return JSON_ERR_BADATTR;
		}
		state = await_value;
		if (cursor->type == t_string)
		    maxlen = (int)cursor->len - 1;
		else if (cursor->type == t_check)
		    maxlen = (int)strlen(cursor->dflt.check);
		else if (cursor->type == t_time || cursor->type == t_ignore)
		    maxlen = JSON_VAL_MAX;
		else if (cursor->map != NULL)
		    maxlen = (int)sizeof(valbuf) - 1;
		pval = valbuf;
	    } else if (pattr >= attrbuf + JSON_ATTR_MAX - 1) {
		json_debug_trace((1, "Attribute name too long.\n"));
		/* don't update end here, leave at attribute start */
		return JSON_ERR_ATTRLEN;
	    } else
		*pattr++ = *cp;
	    break;
	case await_value:
	    if (isspace((unsigned char) *cp) || *cp == ':')
		continue;
	    else if (*cp == '[') {
		if (cursor->type != t_array) {
		    json_debug_trace((1,
				      "Saw [ when not expecting array.\n"));
#ifndef JSON_MINIMAL
		    if (end != NULL)
			*end = cp;
#endif /* JSON_MINIMAL */
		    return JSON_ERR_NOARRAY;
		}
		substatus = json_read_array(cp, &cursor->addr.array, &cp);
		if (substatus != 0)
		    return substatus;
		state = post_array;
	    } else if (cursor->type == t_array) {
		json_debug_trace((1,
				  "Array element was specified, but no [.\n"));
#ifndef JSON_MINIMAL
		if (end != NULL)
		    *end = cp;
#endif /* JSON_MINIMAL */
		return JSON_ERR_NOBRAK;
	    } else if (*cp == '"') {
		value_quoted = true;
		state = in_val_string;
		pval = valbuf;
	    } else {
		value_quoted = false;
		state = in_val_token;
		pval = valbuf;
		*pval++ = *cp;
	    }
	    break;
	case in_val_string:
	    if (pval == NULL)
		/* don't update end here, leave at value start */
		return JSON_ERR_NULLPTR;
	    if (*cp == '\\')
		state = in_escape;
	    else if (*cp == '"') {
		*pval++ = '\0';
		json_debug_trace((1, "Collected string value %s\n", valbuf));
		state = post_val;
	    } else if (pval > valbuf + JSON_VAL_MAX - 1
		       || pval > valbuf + maxlen) {
		json_debug_trace((1, "String value too long.\n"));
		/* don't update end here, leave at value start */
		return JSON_ERR_STRLONG;	/*  */
	    } else
		*pval++ = *cp;
	    break;
	case in_escape:
	    if (pval == NULL)
		/* don't update end here, leave at value start */
		return JSON_ERR_NULLPTR;
	    else if (pval > valbuf + JSON_VAL_MAX - 1
		       || pval > valbuf + maxlen) {
		json_debug_trace((1, "String value too long.\n"));
		/* don't update end here, leave at value start */
		return JSON_ERR_STRLONG;	/*  */
	    }
	    switch (*cp) {
	    case 'b':
		*pval++ = '\b';
		break;
	    case 'f':
		*pval++ = '\f';
		break;
	    case 'n':
		*pval++ = '\n';
		break;
	    case 'r':
		*pval++ = '\r';
		break;
	    case 't':
		*pval++ = '\t';
		break;
	    case 'u':
                cp++;                   /* skip the 'u' */
		for (n = 0; n < 4 && isxdigit(*cp); n++)
		    uescape[n] = *cp++;
                uescape[n] = '\0';      /* terminate */
		--cp;
		if (1 != sscanf(uescape, "%4x", &u)) {
		    return JSON_ERR_BADSTRING;
                }
		*pval++ = (unsigned char)u;  /* will truncate values above 0xff */
		break;
	    default:		/* handles double quote and solidus */
		*pval++ = *cp;
		break;
	    }
	    state = in_val_string;
	    break;
	case in_val_token:
	    if (pval == NULL)
		/* don't update end here, leave at value start */
		return JSON_ERR_NULLPTR;
	    if (isspace((unsigned char) *cp) || *cp == ',' || *cp == '}') {
		*pval = '\0';
		json_debug_trace((1, "Collected token value %s.\n", valbuf));
		state = post_val;
		if (*cp == '}' || *cp == ',')
		    --cp;
	    } else if (pval > valbuf + JSON_VAL_MAX - 1) {
		json_debug_trace((1, "Token value too long.\n"));
		/* don't update end here, leave at value start */
		return JSON_ERR_TOKLONG;
	    } else
		*pval++ = *cp;
	    break;
	    /* coverity[unterminated_case] */
	case post_val:
	    /*
	     * We know that cursor points at the first spec matching
	     * the current attribute.  We don't know that it's *the*
	     * correct spec; our dialect allows there to be any number
	     * of adjacent ones with the same attrname but different
	     * types.  Here's where we try to seek forward for a
	     * matching type/attr pair if we're not looking at one.
	     */
	    for (;;) {
		int seeking = cursor->type;
		if (value_quoted && (cursor->type == t_string
                    || cursor->type == t_time))
		    break;
		if ((strcmp(valbuf, "true")==0 || strcmp(valbuf, "false")==0)
			&& seeking == t_boolean)
		    break;
		if (isdigit((unsigned char) valbuf[0])) {
		    bool decimal = strchr(valbuf, '.') != NULL;
		    if (decimal && seeking == t_real)
			break;
		    if (!decimal && (seeking == t_integer
                                     || seeking == t_uinteger))
			break;
		}
		if (cursor[1].attribute==NULL)	/* out of possiblities */
		    break;
		if (strcmp(cursor[1].attribute, attrbuf)!=0)
		    break;
		++cursor;
	    }
	    if (value_quoted
		&& (cursor->type != t_string && cursor->type != t_character
		    && cursor->type != t_check && cursor->type != t_time
		    && cursor->type != t_ignore && cursor->map == 0)) {
		json_debug_trace((1, "Saw quoted value when expecting"
                                  " non-string.\n"));
		return JSON_ERR_QNONSTRING;
	    }
	    if (!value_quoted
		&& (cursor->type == t_string || cursor->type == t_check
		    || cursor->type == t_time || cursor->map != 0)) {
		json_debug_trace((1, "Didn't see quoted value when expecting"
                                  " string.\n"));
		return JSON_ERR_NONQSTRING;
	    }
	    if (cursor->map != 0) {
		for (mp = cursor->map; mp->name != NULL; mp++)
		    if (strcmp(mp->name, valbuf) == 0) {
			goto foundit;
		    }
		json_debug_trace((1, "Invalid enumerated value string %s.\n",
				  valbuf));
		return JSON_ERR_BADENUM;
	      foundit:
		(void)snprintf(valbuf, sizeof(valbuf), "%d", mp->value);
	    }
	    lptr = json_target_address(cursor, parent, offset);
	    if (lptr != NULL)
		switch (cursor->type) {
		case t_integer:
		    {
			int tmp = atoi(valbuf);
			memcpy(lptr, &tmp, sizeof(int));
		    }
		    break;
		case t_uinteger:
		    {
			unsigned int tmp = (unsigned int)atoi(valbuf);
			memcpy(lptr, &tmp, sizeof(unsigned int));
		    }
		    break;
		case t_short:
		    {
			short tmp = atoi(valbuf);
			memcpy(lptr, &tmp, sizeof(short));
		    }
		    break;
		case t_ushort:
		    {
			unsigned short tmp = (unsigned int)atoi(valbuf);
			memcpy(lptr, &tmp, sizeof(unsigned short));
		    }
		    break;
		case t_time:
		    {
			double tmp = iso8601_to_unix(valbuf);
			memcpy(lptr, &tmp, sizeof(double));
		    }
		    break;
		case t_real:
		    {
			double tmp = safe_atof(valbuf);
			memcpy(lptr, &tmp, sizeof(double));
		    }
		    break;
		case t_string:
		    if (parent != NULL
			&& parent->element_type != t_structobject
			&& offset > 0)
			return JSON_ERR_NOPARSTR;
		    (void)strlcpy(lptr, valbuf, cursor->len);
		    break;
		case t_boolean:
		    {
			bool tmp = (strcmp(valbuf, "true") == 0);
			memcpy(lptr, &tmp, sizeof(bool));
		    }
		    break;
		case t_character:
		    if (strlen(valbuf) > 1)
			/* don't update end here, leave at value start */
			return JSON_ERR_STRLONG;
		    else
			lptr[0] = valbuf[0];
		    break;
		case t_ignore:	/* silences a compiler warning */
		case t_object:	/* silences a compiler warning */
		case t_structobject:
		case t_array:
		    break;
		case t_check:
		    if (strcmp(cursor->dflt.check, valbuf) != 0) {
			json_debug_trace((1, "Required attribute value %s"
                                          " not present.\n",
					  cursor->dflt.check));
			/* don't update end here, leave at start of attribute */
			return JSON_ERR_CHECKFAIL;
		    }
		    break;
		}
	    __attribute__ ((fallthrough));
	case post_array:
	    if (isspace((unsigned char) *cp))
		continue;
	    else if (*cp == ',')
		state = await_attr;
	    else if (*cp == '}') {
		++cp;
		goto good_parse;
	    } else {
		json_debug_trace((1, "Garbage while expecting comma or }\n"));
#ifndef JSON_MINIMAL
		if (end != NULL)
		    *end = cp;
#endif /* JSON_MINIMAL */
		return JSON_ERR_BADTRAIL;
	    }
	    break;
	}
    }

  good_parse:
    /* in case there's another object following, consume trailing WS */
    while (isspace((unsigned char) *cp))
	++cp;
    if (end != NULL)
	*end = cp;
    json_debug_trace((1, "JSON parse ends.\n"));
    return 0;
}


* src/iri.c (do_conversion): Remove unneeded debug lines

do_conversion (const char *tocode, const char *fromcode, char const *in_org, size_t inlen, char **out)
{
  iconv_t cd;
  /* sXXXav : hummm hard to guess... */
  size_t len, done, outlen;
  int invalid = 0, tooshort = 0;
  char *s, *in, *in_save;

  cd = iconv_open (tocode, fromcode);
  if (cd == (iconv_t)(-1))
    {
      logprintf (LOG_VERBOSE, _("Conversion from %s to %s isn't supported\n"),
                 quote (fromcode), quote (tocode));
      *out = NULL;
      return false;
    }

  /* iconv() has to work on an unescaped string */
  in_save = in = xstrndup (in_org, inlen);
  url_unescape_except_reserved (in);
  inlen = strlen(in);

  len = outlen = inlen * 2;
  *out = s = xmalloc (outlen + 1);
  done = 0;

  DEBUGP (("iconv %s -> %s\n", tocode, fromcode));

  for (;;)
    {
      DEBUGP (("iconv outlen=%d inlen=%d\n", outlen, inlen));
      if (iconv (cd, (ICONV_CONST char **) &in, &inlen, out, &outlen) != (size_t)(-1) &&
          iconv (cd, NULL, NULL, out, &outlen) != (size_t)(-1))
        {
          *out = s;
          *(s + len - outlen - done) = '\0';
          xfree(in_save);
          iconv_close(cd);
          IF_DEBUG
          {
            /* not not print out embedded passwords, in_org might be an URL */
            if (!strchr(in_org, '@') && !strchr(*out, '@'))
              debug_logprintf ("converted '%s' (%s) -> '%s' (%s)\n", in_org, fromcode, *out, tocode);
            else
              debug_logprintf ("logging suppressed, strings may contain password\n");
          }
          return true;
        }

      /* Incomplete or invalid multibyte sequence */
      if (errno == EINVAL || errno == EILSEQ)
        {
          if (!invalid)
            logprintf (LOG_VERBOSE,
                      _("Incomplete or invalid multibyte sequence encountered\n"));

          invalid++;
          **out = *in;
          in++;
          inlen--;
          (*out)++;
          outlen--;
        }
      else if (errno == E2BIG) /* Output buffer full */
        {
          logprintf (LOG_VERBOSE,
                    _("Reallocate output buffer len=%d outlen=%d inlen=%d\n"), len, outlen, inlen);
          tooshort++;
          done = len;
          len = done + inlen * 2;
          s = xrealloc (s, len + 1);
          *out = s + done - outlen;
          outlen += inlen * 2;
        }
      else /* Weird, we got an unspecified error */
        {
          logprintf (LOG_VERBOSE, _("Unhandled errno %d\n"), errno);
          break;
        }
    }

    xfree(in_save);
    iconv_close(cd);
    IF_DEBUG
    {
      /* not not print out embedded passwords, in_org might be an URL */
      if (!strchr(in_org, '@') && !strchr(*out, '@'))
        debug_logprintf ("converted '%s' (%s) -> '%s' (%s)\n", in_org, fromcode, *out, tocode);
      else
        debug_logprintf ("logging suppressed, strings may contain password\n");
    }
    return false;
}

Buffer overflow in GNU Wget 1.20.1 and earlier allows remote attackers to cause a denial-of-service (DoS) or may execute an arbitrary code via unspecified vectors.


do_conversion (const char *tocode, const char *fromcode, char const *in_org, size_t inlen, char **out)
{
  iconv_t cd;
  /* sXXXav : hummm hard to guess... */
  size_t len, done, outlen;
  int invalid = 0, tooshort = 0;
  char *s, *in, *in_save;

  cd = iconv_open (tocode, fromcode);
  if (cd == (iconv_t)(-1))
    {
      logprintf (LOG_VERBOSE, _("Conversion from %s to %s isn't supported\n"),
                 quote (fromcode), quote (tocode));
      *out = NULL;
      return false;
    }

  /* iconv() has to work on an unescaped string */
  in_save = in = xstrndup (in_org, inlen);
  url_unescape_except_reserved (in);
  inlen = strlen(in);

  len = outlen = inlen * 2;
  *out = s = xmalloc (outlen + 1);
  done = 0;

  for (;;)
    {
      if (iconv (cd, (ICONV_CONST char **) &in, &inlen, out, &outlen) != (size_t)(-1) &&
          iconv (cd, NULL, NULL, out, &outlen) != (size_t)(-1))
        {
          *out = s;
          *(s + len - outlen - done) = '\0';
          xfree(in_save);
          iconv_close(cd);
          IF_DEBUG
          {
            /* not not print out embedded passwords, in_org might be an URL */
            if (!strchr(in_org, '@') && !strchr(*out, '@'))
              debug_logprintf ("converted '%s' (%s) -> '%s' (%s)\n", in_org, fromcode, *out, tocode);
            else
              debug_logprintf ("logging suppressed, strings may contain password\n");
          }
          return true;
        }

      /* Incomplete or invalid multibyte sequence */
      if (errno == EINVAL || errno == EILSEQ)
        {
          if (!invalid)
            logprintf (LOG_VERBOSE,
                      _("Incomplete or invalid multibyte sequence encountered\n"));

          invalid++;
          **out = *in;
          in++;
          inlen--;
          (*out)++;
          outlen--;
        }
      else if (errno == E2BIG) /* Output buffer full */
        {
          tooshort++;
          done = len;
          len = done + inlen * 2;
          s = xrealloc (s, len + 1);
          *out = s + done - outlen;
          outlen += inlen * 2;
        }
      else /* Weird, we got an unspecified error */
        {
          logprintf (LOG_VERBOSE, _("Unhandled errno %d\n"), errno);
          break;
        }
    }

    xfree(in_save);
    iconv_close(cd);
    IF_DEBUG
    {
      /* not not print out embedded passwords, in_org might be an URL */
      if (!strchr(in_org, '@') && !strchr(*out, '@'))
        debug_logprintf ("converted '%s' (%s) -> '%s' (%s)\n", in_org, fromcode, *out, tocode);
      else
        debug_logprintf ("logging suppressed, strings may contain password\n");
    }
    return false;
}


media: technisat-usb2: break out of loop at end of buffer

Ensure we do not access the buffer beyond the end if no 0xff byte
is encountered.

Reported-by: syzbot+eaaaf38a95427be88f4b@syzkaller.appspotmail.com
Signed-off-by: Sean Young <sean@mess.org>
Reviewed-by: Kees Cook <keescook@chromium.org>
Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>

static int technisat_usb2_get_ir(struct dvb_usb_device *d)
{
	struct technisat_usb2_state *state = d->priv;
	u8 *buf = state->buf;
	u8 *b;
	int ret;
	struct ir_raw_event ev;

	buf[0] = GET_IR_DATA_VENDOR_REQUEST;
	buf[1] = 0x08;
	buf[2] = 0x8f;
	buf[3] = MINIMUM_IR_BIT_TRANSITION_TICK_COUNT;
	buf[4] = MAXIMUM_IR_BIT_TIME_TICK_COUNT;

	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
		return -EAGAIN;
	ret = usb_control_msg(d->udev, usb_sndctrlpipe(d->udev, 0),
			GET_IR_DATA_VENDOR_REQUEST,
			USB_TYPE_VENDOR | USB_DIR_OUT,
			0, 0,
			buf, 5, 500);
	if (ret < 0)
		goto unlock;

	buf[1] = 0;
	buf[2] = 0;
	ret = usb_control_msg(d->udev, usb_rcvctrlpipe(d->udev, 0),
			GET_IR_DATA_VENDOR_REQUEST,
			USB_TYPE_VENDOR | USB_DIR_IN,
			0x8080, 0,
			buf, 62, 500);

unlock:
	mutex_unlock(&d->i2c_mutex);

	if (ret < 0)
		return ret;

	if (ret == 1)
		return 0; /* no key pressed */

	/* decoding */
	b = buf+1;

#if 0
	deb_rc("RC: %d ", ret);
	debug_dump(b, ret, deb_rc);
#endif

	ev.pulse = 0;
	while (1) {
		ev.pulse = !ev.pulse;
		ev.duration = (*b * FIRMWARE_CLOCK_DIVISOR * FIRMWARE_CLOCK_TICK) / 1000;
		ir_raw_event_store(d->rc_dev, &ev);

		b++;
		if (*b == 0xff) {
			ev.pulse = 0;
			ev.duration = 888888*2;
			ir_raw_event_store(d->rc_dev, &ev);
			break;
		}
	}

	ir_raw_event_handle(d->rc_dev);

	return 1;
}

drivers/media/usb/dvb-usb/technisat-usb2.c in the Linux kernel through 5.2.9 has an out-of-bounds read via crafted USB device traffic (which may be remote via usbip or usbredir).


static int technisat_usb2_get_ir(struct dvb_usb_device *d)
{
	struct technisat_usb2_state *state = d->priv;
	struct ir_raw_event ev;
	u8 *buf = state->buf;
	int i, ret;

	buf[0] = GET_IR_DATA_VENDOR_REQUEST;
	buf[1] = 0x08;
	buf[2] = 0x8f;
	buf[3] = MINIMUM_IR_BIT_TRANSITION_TICK_COUNT;
	buf[4] = MAXIMUM_IR_BIT_TIME_TICK_COUNT;

	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
		return -EAGAIN;
	ret = usb_control_msg(d->udev, usb_sndctrlpipe(d->udev, 0),
			GET_IR_DATA_VENDOR_REQUEST,
			USB_TYPE_VENDOR | USB_DIR_OUT,
			0, 0,
			buf, 5, 500);
	if (ret < 0)
		goto unlock;

	buf[1] = 0;
	buf[2] = 0;
	ret = usb_control_msg(d->udev, usb_rcvctrlpipe(d->udev, 0),
			GET_IR_DATA_VENDOR_REQUEST,
			USB_TYPE_VENDOR | USB_DIR_IN,
			0x8080, 0,
			buf, 62, 500);

unlock:
	mutex_unlock(&d->i2c_mutex);

	if (ret < 0)
		return ret;

	if (ret == 1)
		return 0; /* no key pressed */

	/* decoding */

#if 0
	deb_rc("RC: %d ", ret);
	debug_dump(buf + 1, ret, deb_rc);
#endif

	ev.pulse = 0;
	for (i = 1; i < ARRAY_SIZE(state->buf); i++) {
		if (buf[i] == 0xff) {
			ev.pulse = 0;
			ev.duration = 888888*2;
			ir_raw_event_store(d->rc_dev, &ev);
			break;
		}

		ev.pulse = !ev.pulse;
		ev.duration = (buf[i] * FIRMWARE_CLOCK_DIVISOR *
			       FIRMWARE_CLOCK_TICK) / 1000;
		ir_raw_event_store(d->rc_dev, &ev);
	}

	ir_raw_event_handle(d->rc_dev);

	return 1;
}


EXTRACTOR_dvi_extract_method (struct EXTRACTOR_ExtractContext *ec)
{
  unsigned int klen;
  uint32_t pos;
  uint32_t opos;
  unsigned int len;
  unsigned int pageCount;
  char pages[16];
  void *buf;
  unsigned char *data;
  uint64_t size;
  uint64_t off;
  ssize_t iret;

  if (40 >= (iret = ec->read (ec->cls, &buf, 1024)))
    return;
  data = buf;
  if ( (data[0] != 247) ||
       (data[1] != 2) )
    return;                /* cannot be DVI or unsupported version */
  klen = data[14];
  size = ec->get_size (ec->cls);
  if (size > 16 * 1024 * 1024)
    return; /* too large */
  if (NULL == (data = malloc ((size_t) size)))
    return; /* out of memory */
  memcpy (data, buf, iret);
  off = iret;
  while (off < size)
    {
      if (0 >= (iret = ec->read (ec->cls, &buf, 16 * 1024)))
	{
	  free (data);
	  return;
	}
      memcpy (&data[off], buf, iret);
      off += iret;
    }
  pos = size - 1;
  while ( (223 == data[pos]) &&
	  (pos > 0) )
    pos--;
  if ( (2 != data[pos]) ||
       (pos < 40) )
    goto CLEANUP;
  pos--;
  pos -= 4;
  /* assert pos at 'post_post tag' */
  if (data[pos] != 249)
    goto CLEANUP;
  opos = pos;
  pos = getIntAt (&data[opos + 1]);
  if ( (pos + 25 > size) ||
       (pos + 25 < pos) )
    goto CLEANUP;
  /* assert pos at 'post' command */
  if (data[pos] != 248)
    goto CLEANUP;
  pageCount = 0;
  opos = pos;
  pos = getIntAt (&data[opos + 1]);
  while (1)
    {
      if (UINT32_MAX == pos)
        break;
      if ( (pos + 45 > size) ||
	   (pos + 45 < pos) )
	goto CLEANUP;
      if (data[pos] != 139)     /* expect 'bop' */
	goto CLEANUP;
      pageCount++;
      opos = pos;
      pos = getIntAt (&data[opos + 41]);
      if (UINT32_MAX == pos)
        break;
      if (pos >= opos)
	goto CLEANUP;           /* invalid! */
    }
  /* ok, now we believe it's a dvi... */
  snprintf (pages,
	    sizeof (pages),
	    "%u",
	    pageCount);
  if (0 != ec->proc (ec->cls,
		     "dvi",
		     EXTRACTOR_METATYPE_PAGE_COUNT,
		     EXTRACTOR_METAFORMAT_UTF8,
		     "text/plain",
		     pages,
		     strlen (pages) + 1))
    goto CLEANUP;
  if (0 != ec->proc (ec->cls,
		     "dvi",
		     EXTRACTOR_METATYPE_MIMETYPE,
		     EXTRACTOR_METAFORMAT_UTF8,
		     "text/plain",
		     "application/x-dvi",
		     strlen ("application/x-dvi") + 1))
    goto CLEANUP;
  {
    char comment[klen + 1];

    comment[klen] = '\0';
    memcpy (comment, &data[15], klen);
    if (0 != ec->proc (ec->cls,
		       "dvi",
		       EXTRACTOR_METATYPE_COMMENT,
		       EXTRACTOR_METAFORMAT_C_STRING,
		       "text/plain",
		       comment,
		       klen + 1))
      goto CLEANUP;
  }
  /* try to find PDF/ps special */
  pos = opos;
  while ( (size >= 100) &&
	  (pos < size - 100) )
    {
      switch (data[pos])
        {
        case 139:              /* begin page 'bop', we typically have to skip that one to
                                   find the zzz's */
          pos += 45;            /* skip bop */
          break;
        case 239:              /* zzz1 */
          len = data[pos + 1];
          if (pos + 2 + len < size)
            if (0 != parseZZZ ((const char *) data, pos + 2, len, ec->proc, ec->cls))
	      goto CLEANUP;
          pos += len + 2;
          break;
        case 240:              /* zzz2 */
          len = getShortAt (&data[pos + 1]);
          if (pos + 3 + len < size)
            if (0 != parseZZZ ((const char *) data, pos + 3, len, ec->proc, ec->cls))
	      goto CLEANUP;
          pos += len + 3;
          break;
        case 241:              /* zzz3, who uses that? */
          len = (getShortAt (&data[pos + 1])) + 65536 * data[pos + 3];
          if (pos + 4 + len < size)
            if (0 != parseZZZ ((const char *) data, pos + 4, len, ec->proc, ec->cls))
	      goto CLEANUP;
          pos += len + 4;
          break;
        case 242:              /* zzz4, hurray! */
          len = getIntAt (&data[pos + 1]);
          if (pos + 1 + len < size)
            if (0 != parseZZZ ((const char *) data, pos + 5, len, ec->proc, ec->cls))
	      goto CLEANUP;
          pos += len + 5;
          break;
        default:               /* unsupported opcode, abort scan */
	  goto CLEANUP;
        }
    }
 CLEANUP:
  free (data);
}

GNU Libextractor through 1.9 has a heap-based buffer over-read in the function EXTRACTOR_dvi_extract_method in plugins/dvi_extractor.c.


EXTRACTOR_dvi_extract_method (struct EXTRACTOR_ExtractContext *ec)
{
  unsigned int klen;
  uint32_t pos;
  uint32_t opos;
  unsigned int len;
  unsigned int pageCount;
  char pages[16];
  void *buf;
  unsigned char *data;
  uint64_t size;
  uint64_t off;
  ssize_t iret;

  if (40 >= (iret = ec->read (ec->cls, &buf, 1024)))
    return;
  data = buf;
  if ( (data[0] != 247) ||
       (data[1] != 2) )
    return;                /* cannot be DVI or unsupported version */
  klen = data[14];
  size = ec->get_size (ec->cls);
  if (size > 16 * 1024 * 1024)
    return; /* too large */
  if (klen + 15 > size)
    return; /* malformed klen */
  if (NULL == (data = malloc ((size_t) size)))
    return; /* out of memory */
  memcpy (data, buf, iret);
  off = iret;
  while (off < size)
    {
      if (0 >= (iret = ec->read (ec->cls, &buf, 16 * 1024)))
        {
          free (data);
          return;
        }
      memcpy (&data[off], buf, iret);
      off += iret;
    }
  pos = size - 1;
  while ( (223 == data[pos]) &&
          (pos > 0) )
    pos--;
  if ( (2 != data[pos]) ||
       (pos < 40) )
    goto CLEANUP;
  pos--;
  pos -= 4;
  /* assert pos at 'post_post tag' */
  if (data[pos] != 249)
    goto CLEANUP;
  opos = pos;
  pos = getIntAt (&data[opos + 1]);
  if ( (pos + 25 > size) ||
       (pos + 25 < pos) )
    goto CLEANUP;
  /* assert pos at 'post' command */
  if (data[pos] != 248)
    goto CLEANUP;
  pageCount = 0;
  opos = pos;
  pos = getIntAt (&data[opos + 1]);
  while (1)
    {
      if (UINT32_MAX == pos)
        break;
      if ( (pos + 45 > size) ||
	   (pos + 45 < pos) )
        goto CLEANUP;
      if (data[pos] != 139)     /* expect 'bop' */
        goto CLEANUP;
      pageCount++;
      opos = pos;
      pos = getIntAt (&data[opos + 41]);
      if (UINT32_MAX == pos)
        break;
      if (pos >= opos)
	goto CLEANUP;           /* invalid! */
    }
  /* ok, now we believe it's a dvi... */
  snprintf (pages,
            sizeof (pages),
            "%u",
            pageCount);
  if (0 != ec->proc (ec->cls,
                     "dvi",
                     EXTRACTOR_METATYPE_PAGE_COUNT,
                     EXTRACTOR_METAFORMAT_UTF8,
                     "text/plain",
                     pages,
                     strlen (pages) + 1))
    goto CLEANUP;
  if (0 != ec->proc (ec->cls,
                     "dvi",
                     EXTRACTOR_METATYPE_MIMETYPE,
                     EXTRACTOR_METAFORMAT_UTF8,
                     "text/plain",
                     "application/x-dvi",
                     strlen ("application/x-dvi") + 1))
    goto CLEANUP;
  {
    char comment[klen + 1];

    comment[klen] = '\0';
    memcpy (comment, &data[15], klen);
    if (0 != ec->proc (ec->cls,
                       "dvi",
                       EXTRACTOR_METATYPE_COMMENT,
                       EXTRACTOR_METAFORMAT_C_STRING,
                       "text/plain",
                       comment,
                       klen + 1))
      goto CLEANUP;
  }
  /* try to find PDF/ps special */
  pos = opos;
  while ( (size >= 100) &&
          (pos < size - 100) )
    {
      switch (data[pos])
        {
        case 139:              /* begin page 'bop', we typically have to skip that one to
                                   find the zzz's */
          pos += 45;            /* skip bop */
          break;
        case 239:              /* zzz1 */
          len = data[pos + 1];
          if ( (pos + 2 + len < size) &&
               (0 != parseZZZ ((const char *) data, pos + 2, len, ec->proc, ec->cls)) )
              goto CLEANUP;
          pos += len + 2;
          break;
        case 240:              /* zzz2 */
          len = getShortAt (&data[pos + 1]);
          if ( (pos + 3 + len < size) &&
               (0 != parseZZZ ((const char *) data, pos + 3, len, ec->proc, ec->cls)) )
            goto CLEANUP;
          pos += len + 3;
          break;
        case 241:              /* zzz3, who uses that? */
          len = (getShortAt (&data[pos + 1])) + 65536 * data[pos + 3];
          if ( (pos + 4 + len < size) &&
               (0 != parseZZZ ((const char *) data, pos + 4, len, ec->proc, ec->cls)) )
              goto CLEANUP;
          pos += len + 4;
          break;
        case 242:              /* zzz4, hurray! */
          len = getIntAt (&data[pos + 1]);
          if ( (pos + 1 + len < size) &&
               (0 != parseZZZ ((const char *) data, pos + 5, len, ec->proc, ec->cls)) )
            goto CLEANUP;
          pos += len + 5;
          break;
        default:               /* unsupported opcode, abort scan */
          goto CLEANUP;
        }
    }
 CLEANUP:
  free (data);
}


CVE-2020-1730: Fix a possible segfault when zeroing AES-CTR key

Fixes T213

Signed-off-by: Andreas Schneider <asn@cryptomilk.org>
Reviewed-by: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>

static void aes_ctr_cleanup(struct ssh_cipher_struct *cipher){
    explicit_bzero(cipher->aes_key, sizeof(*cipher->aes_key));
    SAFE_FREE(cipher->aes_key);
}

A flaw was found in libssh versions before 0.8.9 and before 0.9.4 in the way it handled AES-CTR (or DES ciphers if enabled) ciphers. The server or client could crash when the connection hasn't been fully initialized and the system tries to cleanup the ciphers when closing the connection. The biggest threat from this vulnerability is system availability.


static void aes_ctr_cleanup(struct ssh_cipher_struct *cipher){
    if (cipher != NULL) {
        if (cipher->aes_key != NULL) {
            explicit_bzero(cipher->aes_key, sizeof(*cipher->aes_key));
        }
        SAFE_FREE(cipher->aes_key);
    }
}