Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8028804

Deflater.deflateBytes() may produce corrupted output on Deflater level/strategy change

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Unresolved
    • Icon: P4 P4
    • tbd
    • 7, 8
    • core-libs
    • None
    • Fix Understood
    • generic
    • generic

      Deflater.deflateBytes() may cause corrupted output when being called after Deflater level or strategy have been changed.

       

      In Deflater.c, deflateBytes(), zlib deflateParams() is called after level or strategy changed. deflateParams() will attempt to flush all outstanding output. It will do this by calling - once, a single time - deflate(..., Z_BLOCK). This may not be enough if the output buffer is too small to hold the whole output; in that case, output will not have been completely flushed before the compression parameters are changed. The result may be that the compressed data are corrupted.

      The following tests shows the problem:

      /* Copyright (c) 2001-2007 by SAP AG, Walldorf, Germany.
       */

      import java.io.ByteArrayOutputStream;
      import java.util.Random;
      import java.util.zip.DataFormatException;
      import java.util.zip.Deflater;
      import java.util.zip.Inflater;

      /**
       * This test stresses the Deflater's ability to change compression
       * parameters while compressing; the resulting z stream should be
       * valid and not corrupted.
       *
       * @author Thomas Stuefe
       */
      public class DeflaterFlushTest {
          
          public static void main(String[] args) {
            new DeflaterFlushTest().testDeflateParams();
          }

          public DeflaterFlushTest() {
          }
          
          private static int durationSeconds = 120;
          
          private boolean compareByteArrays(byte[] a, byte[] b) {
              if (a.length != b.length) {
                  System.out.println("Lengths differ: " + a.length + " vs " + b.length + ".");
                  return false;
              }
              for (int i = 0; i < a.length; i ++) {
                  if (a[i] != b[i]) {
                      System.out.println("Different bytes at pos " + i + ".");
                      return false;
                  }
              }
              return true;
          }
          

          private byte[] uncompress(byte[] compressed) throws DataFormatException {
              ByteArrayOutputStream bos = new ByteArrayOutputStream();
              byte[] tmp = new byte[512];
              Inflater inf = new Inflater();
              inf.setInput(compressed);
              while (!inf.finished()) {
                  int len = 0;
                  len = inf.inflate(tmp);
                  bos.write(tmp, 0, len);
              }
              return bos.toByteArray();
          }
          
          public void testDeflateParams() {
              
              Random r = new Random();
              
              final long t1 = System.currentTimeMillis();
              final long tend = t1 + (durationSeconds * 1000); // 2 minutes max
              int run = 0;
              int errors = 0;
              
              // given a large input block of textlike random data, compress this data
              // using Deflater in steps of 0-4096 bytes.
              // Each Step there is a high chance of switching compression level and
              // thus enforcing a flush onto the Deflater.
              // Deflation itself uses a very small (100 bytes) output array which mimicks
              // the 512 byte temp array in the DeflaterOutputStream.
              // This means that if compression is changed and a flush is enforced,
              // the chance is good that this flush needs several attempts because the
              // output buffer will be too small to hold the whole flush output.
              //
              // This in turn triggers the bug in the Deflater where deflateParams()
              // cannot fully flush the current block and nevertheless changes compression
              // parameters.
              while (run < 10 || System.currentTimeMillis() < tend) {
                  
                  run ++;
                 
                  byte original[] = new byte[r.nextInt(0x100000) + 100];
                  for (int i = 0; i < original.length; i ++) {
                      original[i] = (byte)(r.nextInt(95) + 32);
               // original[i] = (byte)(r.nextInt());
                  }
                  int ipos = 0;
                  byte tmp[] = new byte[0x80];
                  
                  ByteArrayOutputStream bos = new ByteArrayOutputStream();
                  
                  Deflater def = new Deflater();
                  
                  while (ipos < original.length) {
                      
                      final int[] compressionLevels = {0, 2, 9};
                      int nextCompressionLevel = compressionLevels[r.nextInt(3)];
                      
                      def.setLevel(nextCompressionLevel);
                      
                      int nextlen = r.nextInt(4096);
                      if (ipos + nextlen > original.length) {
                          nextlen = original.length - ipos;
                      }
                      def.setInput(original, ipos, nextlen);
                      ipos += nextlen;
                      
                      while (!def.needsInput()) {
                          int len = def.deflate(tmp);
                          bos.write(tmp, 0, len);
                      }
                      
                  }

                  def.finish();
                  while (!def.finished()) {
                      int len = def.deflate(tmp);
                      bos.write(tmp, 0, len);
                  }

                  byte[] compressed = bos.toByteArray();
                  
                  byte[] inflated = null;
                  boolean wasok = true;
                  
                  try {
                      inflated = uncompress(compressed);
                  } catch (DataFormatException e) {
                      System.out.println("DataFormatException " + e.getMessage());
                      wasok = false;
                  }
                  
                  if (inflated != null) {
                      wasok = compareByteArrays(inflated, original);
                  }
                  
                  if (!wasok) {
                      errors ++;
                      System.out.println(errors + " Errors in " + run + " runs.");
                  }
                  
              }
              
              if (errors > 0) {
                  System.out.println("One or more errors.");
              }
              
          }
          
          
      }

            sherman Xueming Shen
            simonis Volker Simonis
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated: