Check uncompressed size before decompressing a zip file. Fixes 33058 (dotnet/corefx...
authorbuyaa-n <bunamnan@microsoft.com>
Wed, 26 Jun 2019 17:37:12 +0000 (10:37 -0700)
committerGitHub <noreply@github.com>
Wed, 26 Jun 2019 17:37:12 +0000 (10:37 -0700)
Account uncompressed size while decompressing a zip file. Fixes 33058

I am sure now the fix is good enough for merging, if anyone has more question/comment please let me know/comment

Commit migrated from https://github.com/dotnet/corefx/commit/cece0df13a74d188f96b08ae5af0cd4cd60f8f76

src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateManaged/DeflateManagedStream.cs
src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateManaged/InflaterManaged.cs
src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateManaged/InputBuffer.cs
src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateManaged/OutputWindow.cs
src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateZLib/DeflateStream.cs
src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateZLib/Inflater.cs
src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs
src/libraries/System.IO.Compression/src/System/IO/Compression/ZipBlocks.cs
src/libraries/System.IO.Compression/tests/ZipArchive/zip_InvalidParametersAndStrangeFiles.cs

index ab3fde0..c7b9caa 100644 (file)
@@ -27,27 +27,27 @@ namespace System.IO.Compression
         private bool _wroteBytes;
 
         // A specific constructor to allow decompression of Deflate64
-        internal DeflateManagedStream(Stream stream, ZipArchiveEntry.CompressionMethodValues method)
+        internal DeflateManagedStream(Stream stream, ZipArchiveEntry.CompressionMethodValues method, long uncompressedSize)
         {
             if (stream == null)
                 throw new ArgumentNullException(nameof(stream));
             if (!stream.CanRead)
                 throw new ArgumentException(SR.NotSupported_UnreadableStream, nameof(stream));
 
-            InitializeInflater(stream, false, null, method);
+            InitializeInflater(stream, false, null, method, uncompressedSize);
         }
 
         /// <summary>
         /// Sets up this DeflateManagedStream to be used for Inflation/Decompression
         /// </summary>
-        internal void InitializeInflater(Stream stream, bool leaveOpen, IFileFormatReader reader = null, ZipArchiveEntry.CompressionMethodValues method = ZipArchiveEntry.CompressionMethodValues.Deflate)
+        internal void InitializeInflater(Stream stream, bool leaveOpen, IFileFormatReader reader = null, ZipArchiveEntry.CompressionMethodValues method = ZipArchiveEntry.CompressionMethodValues.Deflate, long uncompressedSize = -1)
         {
             Debug.Assert(stream != null);
             Debug.Assert(method == ZipArchiveEntry.CompressionMethodValues.Deflate || method == ZipArchiveEntry.CompressionMethodValues.Deflate64);
             if (!stream.CanRead)
                 throw new ArgumentException(SR.NotSupported_UnreadableStream, nameof(stream));
 
-            _inflater = new InflaterManaged(reader, method == ZipArchiveEntry.CompressionMethodValues.Deflate64 ? true : false);
+            _inflater = new InflaterManaged(reader, method == ZipArchiveEntry.CompressionMethodValues.Deflate64 ? true : false, uncompressedSize);
 
             _stream = stream;
             _mode = CompressionMode.Decompress;
index ddc5e99..cf59a8b 100644 (file)
@@ -64,10 +64,12 @@ namespace System.IO.Compression
         private readonly byte[] _codeLengthTreeCodeLength;
         private readonly bool _deflate64;
         private HuffmanTree _codeLengthTree;
+        private readonly long _uncompressedSize;
+        private long _currentInflatedCount;
 
         private IFileFormatReader _formatReader; // class to decode header and footer (e.g. gzip)
 
-        internal InflaterManaged(IFileFormatReader reader, bool deflate64)
+        internal InflaterManaged(IFileFormatReader reader, bool deflate64, long uncompressedSize)
         {
             _output = new OutputWindow();
             _input = new InputBuffer();
@@ -75,6 +77,7 @@ namespace System.IO.Compression
             _codeList = new byte[HuffmanTree.MaxLiteralTreeElements + HuffmanTree.MaxDistTreeElements];
             _codeLengthTreeCodeLength = new byte[HuffmanTree.NumberOfCodeLengthTreeElements];
             _deflate64 = deflate64;
+            _uncompressedSize = uncompressedSize;
             if (reader != null)
             {
                 _formatReader = reader;
@@ -105,7 +108,25 @@ namespace System.IO.Compression
             int count = 0;
             do
             {
-                int copied = _output.CopyTo(bytes, offset, length);
+                int copied = 0;
+                if (_uncompressedSize == -1)
+                {
+                    copied = _output.CopyTo(bytes, offset, length);
+                }
+                else
+                {
+                    if (_uncompressedSize > _currentInflatedCount)
+                    {
+                        length = Math.Min(length, (int)(_uncompressedSize - _currentInflatedCount));
+                        copied = _output.CopyTo(bytes, offset, length);
+                        _currentInflatedCount += copied;
+                    }
+                    else
+                    {
+                        _state = InflaterState.Done;
+                        _output.ClearBytesUsed();
+                    }
+                }
                 if (copied > 0)
                 {
                     if (_hasFormatReader)
index 2cfe31f..e17b2e8 100644 (file)
@@ -176,11 +176,13 @@ namespace System.IO.Compression
             Debug.Assert(offset >= 0);
             Debug.Assert(length >= 0);
             Debug.Assert(offset <= buffer.Length - length);
-            Debug.Assert(_start == _end);
 
-            _buffer = buffer;
-            _start = offset;
-            _end = offset + length;
+            if (_start == _end)
+            {
+                _buffer = buffer;
+                _start = offset;
+                _end = offset + length;
+            }
         }
 
         /// <summary>Skip n bits in the buffer.</summary>
index df3cf01..55adf60 100644 (file)
@@ -25,6 +25,11 @@ namespace System.IO.Compression
         private int _end;       // this is the position to where we should write next byte
         private int _bytesUsed; // The number of bytes in the output window which is not consumed.
 
+        internal void ClearBytesUsed()
+        {
+            _bytesUsed = 0;
+        }
+        
         /// <summary>Add a byte to output window.</summary>
         public void Write(byte b)
         {
index b1e4936..a433df3 100644 (file)
@@ -23,6 +23,10 @@ namespace System.IO.Compression
         private int _activeAsyncOperation; // 1 == true, 0 == false
         private bool _wroteBytes;
 
+        internal DeflateStream(Stream stream, CompressionMode mode, long uncompressedSize) : this(stream, mode, leaveOpen: false, ZLibNative.Deflate_DefaultWindowBits, uncompressedSize)
+        {
+        }
+
         public DeflateStream(Stream stream, CompressionMode mode) : this(stream, mode, leaveOpen: false)
         {
         }
@@ -45,7 +49,7 @@ namespace System.IO.Compression
         /// Internal constructor to check stream validity and call the correct initialization function depending on
         /// the value of the CompressionMode given.
         /// </summary>
-        internal DeflateStream(Stream stream, CompressionMode mode, bool leaveOpen, int windowBits)
+        internal DeflateStream(Stream stream, CompressionMode mode, bool leaveOpen, int windowBits, long uncompressedSize = -1)
         {
             if (stream == null)
                 throw new ArgumentNullException(nameof(stream));
@@ -53,7 +57,7 @@ namespace System.IO.Compression
             switch (mode)
             {
                 case CompressionMode.Decompress:
-                    InitializeInflater(stream, leaveOpen, windowBits);
+                    InitializeInflater(stream, leaveOpen, windowBits, uncompressedSize);
                     break;
 
                 case CompressionMode.Compress:
@@ -79,13 +83,13 @@ namespace System.IO.Compression
         /// <summary>
         /// Sets up this DeflateStream to be used for Zlib Inflation/Decompression
         /// </summary>
-        internal void InitializeInflater(Stream stream, bool leaveOpen, int windowBits)
+        internal void InitializeInflater(Stream stream, bool leaveOpen, int windowBits, long uncompressedSize)
         {
             Debug.Assert(stream != null);
             if (!stream.CanRead)
                 throw new ArgumentException(SR.NotSupported_UnreadableStream, nameof(stream));
 
-            _inflater = new Inflater(windowBits);
+            _inflater = new Inflater(windowBits, uncompressedSize);
 
             _stream = stream;
             _mode = CompressionMode.Decompress;
@@ -988,6 +992,10 @@ namespace System.IO.Compression
                     {
                         break;
                     }
+                    if (_deflateStream._inflater.Finished())
+                    {
+                        break;
+                    }
                 }
             }
 
@@ -1023,6 +1031,10 @@ namespace System.IO.Compression
                     {
                         break;
                     }
+                    if (_deflateStream._inflater.Finished())
+                    {
+                        break;
+                    }
                 }
             }
 
index 672baad..6d91636 100644 (file)
@@ -21,19 +21,22 @@ namespace System.IO.Compression
         private int _windowBits;                            // The WindowBits parameter passed to Inflater construction
         private ZLibNative.ZLibStreamHandle _zlibStream;    // The handle to the primary underlying zlib stream
         private GCHandle _inputBufferHandle;                // The handle to the buffer that provides input to _zlibStream
+        private readonly long _uncompressedSize;
+        private long _currentInflatedCount;
 
         private object SyncLock => this;                    // Used to make writing to unmanaged structures atomic
 
         /// <summary>
         /// Initialized the Inflater with the given windowBits size
         /// </summary>
-        internal Inflater(int windowBits)
+        internal Inflater(int windowBits, long uncompressedSize = -1)
         {
             Debug.Assert(windowBits >= MinWindowBits && windowBits <= MaxWindowBits);
             _finished = false;
             _isDisposed = false;
             _windowBits = windowBits;
             InflateInit(windowBits);
+            _uncompressedSize = uncompressedSize;
         }
 
         public int AvailableOutput => (int)_zlibStream.AvailOut;
@@ -83,16 +86,23 @@ namespace System.IO.Compression
             // State is valid; attempt inflation
             try
             {
-                int bytesRead;
-                if (ReadInflateOutput(bufPtr, length, ZLibNative.FlushCode.NoFlush, out bytesRead) == ZLibNative.ErrorCode.StreamEnd)
+                int bytesRead = 0;
+                if (_uncompressedSize == -1)
                 {
-                    if (!NeedsInput() && IsGzipStream() && _inputBufferHandle.IsAllocated)
+                    ReadOutput(bufPtr, length, out bytesRead);
+                }
+                else
+                {
+                    if (_uncompressedSize > _currentInflatedCount)
                     {
-                        _finished = ResetStreamForLeftoverInput();
+                        length = Math.Min(length, (int)(_uncompressedSize - _currentInflatedCount));
+                        ReadOutput(bufPtr, length, out bytesRead);
+                        _currentInflatedCount += bytesRead;
                     }
                     else
                     {
                         _finished = true;
+                        _zlibStream.AvailIn = 0;
                     }
                 }
                 return bytesRead;
@@ -107,6 +117,21 @@ namespace System.IO.Compression
             }
         }
 
+        private unsafe void ReadOutput(byte* bufPtr, int length, out int bytesRead)
+        {
+            if (ReadInflateOutput(bufPtr, length, ZLibNative.FlushCode.NoFlush, out bytesRead) == ZLibNative.ErrorCode.StreamEnd)
+            {
+                if (!NeedsInput() && IsGzipStream() && _inputBufferHandle.IsAllocated)
+                {
+                    _finished = ResetStreamForLeftoverInput();
+                }
+                else
+                {
+                    _finished = true;
+                }
+            }
+        }
+
         /// <summary>
         /// If this stream has some input leftover that hasn't been processed then we should
         /// check if it is another GZip file concatenated with this one.
index b7183a0..340b652 100644 (file)
@@ -646,10 +646,10 @@ namespace System.IO.Compression
             switch (CompressionMethod)
             {
                 case CompressionMethodValues.Deflate:
-                    uncompressedStream = new DeflateStream(compressedStreamToRead, CompressionMode.Decompress);
+                    uncompressedStream = new DeflateStream(compressedStreamToRead, CompressionMode.Decompress, _uncompressedSize);
                     break;
                 case CompressionMethodValues.Deflate64:
-                    uncompressedStream = new DeflateManagedStream(compressedStreamToRead, CompressionMethodValues.Deflate64);
+                    uncompressedStream = new DeflateManagedStream(compressedStreamToRead, CompressionMethodValues.Deflate64, _uncompressedSize);
                     break;
                 case CompressionMethodValues.Stored:
                 default:
@@ -751,10 +751,21 @@ namespace System.IO.Compression
                     return false;
                 }
                 _archive.ArchiveStream.Seek(_offsetOfLocalHeader, SeekOrigin.Begin);
-                if (!ZipLocalFileHeader.TrySkipBlock(_archive.ArchiveReader))
+                if (needToUncompress && !needToLoadIntoMemory)
                 {
-                    message = SR.LocalFileHeaderCorrupt;
-                    return false;
+                    if (!ZipLocalFileHeader.TryValidateBlock(_archive.ArchiveReader, this))
+                    {
+                        message = SR.LocalFileHeaderCorrupt;
+                        return false;
+                    }
+                }
+                else
+                {
+                    if (!ZipLocalFileHeader.TrySkipBlock(_archive.ArchiveReader))
+                    {
+                        message = SR.LocalFileHeaderCorrupt;
+                        return false;
+                    }
                 }
                 // when this property gets called, some duplicated work
                 if (OffsetOfCompressedData + _compressedSize > _archive.ArchiveStream.Length)
@@ -1250,7 +1261,7 @@ namespace System.IO.Compression
         }
 
         [Flags]
-        private enum BitFlagValues : ushort { DataDescriptor = 0x8, UnicodeFileName = 0x800 }
+        internal enum BitFlagValues : ushort { DataDescriptor = 0x8, UnicodeFileName = 0x800 }
 
         internal enum CompressionMethodValues : ushort { Stored = 0x0, Deflate = 0x8, Deflate64 = 0x9, BZip2 = 0xC, LZMA = 0xE }
     }
index 63cdcc4..bb96755 100644 (file)
@@ -429,6 +429,109 @@ namespace System.IO.Compression
 
             return true;
         }
+
+        public static bool TryValidateBlock(BinaryReader reader, ZipArchiveEntry entry)
+        {
+            const int OffsetToFilename = 26; // from the point after the signature
+
+            if (reader.ReadUInt32() != SignatureConstant)
+                return false;
+
+            if (reader.BaseStream.Length < reader.BaseStream.Position + OffsetToFilename)
+                return false;
+
+            uint minimumVersion = reader.ReadUInt16();
+            uint dataDescriptorBit = reader.ReadUInt16() & (uint)ZipArchiveEntry.BitFlagValues.DataDescriptor;
+            reader.BaseStream.Seek(10, SeekOrigin.Current); // skipping bytes used for Compression method (2 bytes), last modification time and date (4 bytes) and CRC (4 bytes)
+            long compressedSize = reader.ReadUInt32();
+            long uncompressedSize = reader.ReadUInt32();
+            int filenameLength = reader.ReadUInt16();
+            uint extraFieldLength = reader.ReadUInt16();
+
+            if (reader.BaseStream.Length < reader.BaseStream.Position + filenameLength + extraFieldLength)
+                return false;
+
+            reader.BaseStream.Seek(filenameLength, SeekOrigin.Current);  // skipping Filename
+            long endExtraFields = reader.BaseStream.Position + extraFieldLength;
+            
+            if (dataDescriptorBit == 0)
+            {
+                bool isUncompressedSizeInZip64 = uncompressedSize == ZipHelper.Mask32Bit;
+                bool isCompressedSizeInZip64 = compressedSize == ZipHelper.Mask32Bit;
+                Zip64ExtraField zip64;
+
+                // Ideally we should also check if the minimumVersion is 64 bit or above, but there could zip files for which this version is not set correctly
+                if (isUncompressedSizeInZip64 || isCompressedSizeInZip64)
+                {
+                    zip64 = Zip64ExtraField.GetJustZip64Block(new SubReadStream(reader.BaseStream, reader.BaseStream.Position, extraFieldLength), isUncompressedSizeInZip64, isCompressedSizeInZip64, false, false);
+
+                    if (zip64.UncompressedSize != null)
+                    {
+                        uncompressedSize = zip64.UncompressedSize.Value;
+                    }
+
+                    if (zip64.CompressedSize != null)
+                    {
+                        compressedSize = zip64.CompressedSize.Value;
+                    }
+                }
+
+                reader.BaseStream.AdvanceToPosition(endExtraFields);
+            }
+            else
+            {
+                if (reader.BaseStream.Length < reader.BaseStream.Position + extraFieldLength + entry.CompressedLength + 4)
+                {
+                    return false;
+                }
+
+                reader.BaseStream.Seek(extraFieldLength + entry.CompressedLength, SeekOrigin.Current); // seek to end of compressed file from which Data descriptor starts             
+                uint dataDescriptorSignature = reader.ReadUInt32();
+                bool wasDataDescriptorSignatureRead = false;
+                int seekSize = 0;
+                if (dataDescriptorSignature == DataDescriptorSignature)
+                {
+                    wasDataDescriptorSignatureRead = true;
+                    seekSize = 4;
+                }
+
+                bool is64bit = minimumVersion > (uint)ZipVersionNeededValues.Zip64;
+                seekSize += (is64bit ? 8 : 4) * 2;   // if Zip64 read by 8 bytes else 4 bytes 2 times (compressed and uncompressed size)
+
+                if (reader.BaseStream.Length < reader.BaseStream.Position + seekSize)
+                {
+                    return false;
+                }
+
+                // dataDescriptorSignature is optional, if it was the DataDescriptorSignature we need to skip CRC 4 bytes else we can assume CRC is alreadyskipped
+                if (wasDataDescriptorSignatureRead)
+                    reader.BaseStream.Seek(4, SeekOrigin.Current);
+
+                if (is64bit)
+                {
+                    compressedSize = reader.ReadInt64();
+                    uncompressedSize = reader.ReadInt64();
+                }
+                else
+                {
+                    compressedSize = reader.ReadInt32();
+                    uncompressedSize = reader.ReadInt32();
+                }
+                reader.BaseStream.Seek( -seekSize - entry.CompressedLength - 4,  SeekOrigin.Current); // Seek back to the beginning of compressed stream
+            }
+
+            if (entry.CompressedLength != compressedSize)
+            {
+                return false;
+            }
+
+            if (entry.Length != uncompressedSize)
+            {
+                return false;
+            }
+
+            return true;
+        }
     }
 
     internal struct ZipCentralDirectoryFileHeader
index e7c0318..c222165 100644 (file)
@@ -3,14 +3,19 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.IO;
+using System.Text;
 using System.Threading.Tasks;
 using Xunit;
+using Xunit.Sdk;
 
 namespace System.IO.Compression.Tests
 {
     public class zip_InvalidParametersAndStrangeFiles : ZipFileTestBase
     {
+        private static readonly int s_bufferSize = 10240;
+        private static readonly string s_tamperedFileName = "binary.wmv";
         private static void ConstructorThrows<TException>(Func<ZipArchive> constructor, string Message) where TException : Exception
         {
             try
@@ -126,6 +131,425 @@ namespace System.IO.Compression.Tests
             }
         }
 
+        [Fact]
+        [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Fix not shipped for full framework.")]
+        public static async Task ZipArchiveEntry_CorruptedStream_ReadMode_CopyTo_UpToUncompressedSize()
+        {
+            MemoryStream stream = await LocalMemoryStream.readAppFileAsync(zfile("normal.zip"));
+
+            int nameOffset = PatchDataRelativeToFileName(Encoding.ASCII.GetBytes(s_tamperedFileName), stream, 8);  // patch uncompressed size in file header
+            PatchDataRelativeToFileName(Encoding.ASCII.GetBytes(s_tamperedFileName), stream, 22, nameOffset + s_tamperedFileName.Length); // patch in central directory too
+
+            using (ZipArchive archive = new ZipArchive(stream, ZipArchiveMode.Read))
+            {
+                ZipArchiveEntry e = archive.GetEntry(s_tamperedFileName);
+                using (MemoryStream ms = new MemoryStream())
+                using (Stream source = e.Open())
+                {
+                    source.CopyTo(ms);
+                    Assert.Equal(e.Length, ms.Length);     // Only allow to decompress up to uncompressed size
+                    byte[] buffer = new byte[s_bufferSize];
+                    Assert.Equal(0, source.Read(buffer, 0, buffer.Length)); // shouldn't be able read more                        
+                    ms.Seek(0, SeekOrigin.Begin);
+                    int read;
+                    while ((read = ms.Read(buffer, 0, buffer.Length)) != 0)
+                    { // No need to do anything, just making sure all bytes readable
+                    }
+                    Assert.Equal(ms.Position, ms.Length); // all bytes must be read
+                }
+            }
+        }
+
+        [Fact]
+        [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Fix not shipped for full framework.")]
+        public static async Task ZipArchiveEntry_CorruptedStream_ReadMode_Read_UpToUncompressedSize()
+        {
+            MemoryStream stream = await LocalMemoryStream.readAppFileAsync(zfile("normal.zip"));
+
+            int nameOffset = PatchDataRelativeToFileName(Encoding.ASCII.GetBytes(s_tamperedFileName), stream, 8);  // patch uncompressed size in file header
+            PatchDataRelativeToFileName(Encoding.ASCII.GetBytes(s_tamperedFileName), stream, 22, nameOffset + s_tamperedFileName.Length); // patch in central directory too
+
+            using (ZipArchive archive = new ZipArchive(stream, ZipArchiveMode.Read))
+            {
+                ZipArchiveEntry e = archive.GetEntry(s_tamperedFileName);
+                using (MemoryStream ms = new MemoryStream())
+                using (Stream source = e.Open())
+                {
+                    byte[] buffer = new byte[s_bufferSize];
+                    int read;
+                    while ((read = source.Read(buffer, 0, buffer.Length)) != 0)
+                    {
+                        ms.Write(buffer, 0, read);
+                    }
+                    Assert.Equal(e.Length, ms.Length);     // Only allow to decompress up to uncompressed size
+                    Assert.Equal(0, source.Read(buffer, 0, s_bufferSize)); // shouldn't be able read more 
+                    ms.Seek(0, SeekOrigin.Begin);
+                    while ((read = ms.Read(buffer, 0, buffer.Length)) != 0)
+                    { // No need to do anything, just making sure all bytes readable from output stream
+                    }
+                    Assert.Equal(ms.Position, ms.Length); // all bytes must be read
+                }
+            }
+        }
+
+        [Fact]
+
+        public static void ZipArchiveEntry_CorruptedStream_EnsureNoExtraBytesReadOrOverWritten()
+        {
+            MemoryStream stream = populateStream().Result;
+
+            int nameOffset = PatchDataRelativeToFileName(Encoding.ASCII.GetBytes(s_tamperedFileName), stream, 8);  // patch uncompressed size in file header
+            PatchDataRelativeToFileName(Encoding.ASCII.GetBytes(s_tamperedFileName), stream, 22, nameOffset + s_tamperedFileName.Length); // patch in central directory too
+
+            using (ZipArchive archive = new ZipArchive(stream, ZipArchiveMode.Read))
+            {
+                ZipArchiveEntry e = archive.GetEntry(s_tamperedFileName);
+                using (Stream source = e.Open())
+                {
+                    byte[] buffer = new byte[e.Length + 20];
+                    Array.Fill<byte>(buffer, 0xDE);
+                    int read;
+                    int offset = 0;
+                    int length = buffer.Length;
+
+                    while ((read = source.Read(buffer, offset, length)) != 0)
+                    {
+                        offset += read;
+                        length -= read;
+                    }
+                    for (int i = offset; i < buffer.Length; i++)
+                    {
+                        Assert.Equal(0xDE, buffer[i]);
+                    }
+                }
+            }
+        }
+
+        private static async Task<MemoryStream> populateStream()
+        {
+            return await LocalMemoryStream.readAppFileAsync(zfile("normal.zip"));
+        }
+
+        [Fact]
+        [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Deflate64 zip support is a netcore feature not available on full framework.")]
+        public static async Task Zip64ArchiveEntry_CorruptedStream_CopyTo_UpToUncompressedSize()
+        {
+            MemoryStream stream = await LocalMemoryStream.readAppFileAsync(compat("deflate64.zip"));
+
+            int nameOffset = PatchDataRelativeToFileName(Encoding.ASCII.GetBytes(s_tamperedFileName), stream, 8);  // patch uncompressed size in file header
+            PatchDataRelativeToFileName(Encoding.ASCII.GetBytes(s_tamperedFileName), stream, 22, nameOffset + s_tamperedFileName.Length); // patch in central directory too
+
+            using (ZipArchive archive = new ZipArchive(stream, ZipArchiveMode.Read))
+            {
+                ZipArchiveEntry e = archive.GetEntry(s_tamperedFileName);
+                using (var ms = new MemoryStream())
+                using (Stream source = e.Open())
+                {
+                    source.CopyTo(ms);
+                    Assert.Equal(e.Length, ms.Length);     // Only allow to decompress up to uncompressed size
+                    ms.Seek(0, SeekOrigin.Begin);
+                    int read;
+                    byte[] buffer = new byte[s_bufferSize];
+                    while ((read = ms.Read(buffer, 0, buffer.Length)) != 0)
+                    { // No need to do anything, just making sure all bytes readable
+                    }
+                    Assert.Equal(ms.Position, ms.Length); // all bytes must be read
+                }
+            }
+        }
+
+        [Fact]
+        public static async Task ZipArchiveEntry_CorruptedStream_UnCompressedSizeBiggerThanExpected_NothingShouldBreak()
+        {
+            MemoryStream stream = await LocalMemoryStream.readAppFileAsync(zfile("normal.zip"));
+
+            int nameOffset = PatchDataRelativeToFileNameFillBytes(Encoding.ASCII.GetBytes(s_tamperedFileName), stream, 8);  // patch uncompressed size in file header
+            PatchDataRelativeToFileNameFillBytes(Encoding.ASCII.GetBytes(s_tamperedFileName), stream, 22, nameOffset + s_tamperedFileName.Length); // patch in central directory too
+
+            using (ZipArchive archive = new ZipArchive(stream, ZipArchiveMode.Read))
+            {
+                ZipArchiveEntry e = archive.GetEntry(s_tamperedFileName);
+                using (MemoryStream ms = new MemoryStream())
+                using (Stream source = e.Open())
+                {
+                    source.CopyTo(ms);
+                    Assert.True(e.Length > ms.Length);           // Even uncompressed size is bigger than decompressed size there should be no error
+                    Assert.True(e.CompressedLength < ms.Length);
+                }
+            }
+        }
+
+        [Fact]
+        [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Deflate64 zip support is a netcore feature not available on full framework.")]
+        public static async Task Zip64ArchiveEntry_CorruptedFile_Read_UpToUncompressedSize()
+        {
+            MemoryStream stream = await LocalMemoryStream.readAppFileAsync(compat("deflate64.zip"));
+
+            int nameOffset = PatchDataRelativeToFileName(Encoding.ASCII.GetBytes(s_tamperedFileName), stream, 8);  // patch uncompressed size in file header
+            PatchDataRelativeToFileName(Encoding.ASCII.GetBytes(s_tamperedFileName), stream, 22, nameOffset + s_tamperedFileName.Length); // patch in central directory too
+
+            using (ZipArchive archive = new ZipArchive(stream, ZipArchiveMode.Read))
+            {
+                ZipArchiveEntry e = archive.GetEntry(s_tamperedFileName);
+                using (var ms = new MemoryStream())
+                using (Stream source = e.Open())
+                {
+                    byte[] buffer = new byte[s_bufferSize];
+                    int read;
+                    while ((read = source.Read(buffer, 0, buffer.Length)) != 0)
+                    {
+                        ms.Write(buffer, 0, read);
+                    }
+                    Assert.Equal(e.Length, ms.Length);     // Only allow to decompress up to uncompressed size
+                    Assert.Equal(0, source.Read(buffer, 0, buffer.Length)); // Shouldn't be readable more
+                }
+            }
+        }
+
+        [Fact]
+        [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Fix not shipped for full framework.")]
+        public static async Task ZipArchive_CorruptedLocalHeader_UncompressedSize_NotMatchWithCentralDirectory()
+        {
+            MemoryStream stream = await LocalMemoryStream.readAppFileAsync(zfile("normal.zip"));
+
+            PatchDataRelativeToFileName(Encoding.ASCII.GetBytes(s_tamperedFileName), stream, 8);  // patch uncompressed size in file header
+
+            using (ZipArchive archive = new ZipArchive(stream, ZipArchiveMode.Read))
+            {
+                ZipArchiveEntry e = archive.GetEntry(s_tamperedFileName);
+                Assert.Throws<InvalidDataException>(() => e.Open());
+            }
+        }
+
+        [Fact]
+        [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Fix not shipped for full framework.")]
+        public static async Task ZipArchive_CorruptedLocalHeader_CompressedSize_NotMatchWithCentralDirectory()
+        {
+            MemoryStream stream = await LocalMemoryStream.readAppFileAsync(zfile("normal.zip"));
+
+            PatchDataRelativeToFileName(Encoding.ASCII.GetBytes(s_tamperedFileName), stream, 12);  // patch compressed size in file header
+
+            using (ZipArchive archive = new ZipArchive(stream, ZipArchiveMode.Read))
+            {
+                ZipArchiveEntry e = archive.GetEntry(s_tamperedFileName);
+                Assert.Throws<InvalidDataException>(() => e.Open());
+            }
+        }
+
+        [Fact]
+        [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Full Framework does not allow unseekable streams.")]
+        public static async Task ZipArchive_Unseekable_Corrupted_FileDescriptor_NotMatchWithCentralDirectory()
+        {
+            using (var s = new MemoryStream())
+            {
+                var testStream = new WrappedStream(s, false, true, false, null);
+                await CreateFromDir(zfolder("normal"), testStream, ZipArchiveMode.Create);
+              
+                PatchDataDescriptorRelativeToFileName(Encoding.ASCII.GetBytes(s_tamperedFileName), s, 8);  // patch uncompressed size in file descriptor
+
+                using (ZipArchive archive = new ZipArchive(s, ZipArchiveMode.Read))
+                {
+                    ZipArchiveEntry e = archive.GetEntry(s_tamperedFileName);
+                    Assert.Throws<InvalidDataException>(() => e.Open());
+                }
+            }
+        }
+
+        [Fact]
+        public static async Task UpdateZipArchive_AppendTo_CorruptedFileEntry()
+        {
+            MemoryStream stream = await StreamHelpers.CreateTempCopyStream(zfile("normal.zip"));
+            int updatedUncompressedLength = 1310976;
+            string append = "\r\n\r\nThe answer my friend, is blowin' in the wind.";
+            byte[] data = Encoding.ASCII.GetBytes(append);
+            long oldCompressedSize = 0;
+
+            int nameOffset = PatchDataRelativeToFileName(Encoding.ASCII.GetBytes(s_tamperedFileName), stream, 8);  // patch uncompressed size in file header
+            PatchDataRelativeToFileName(Encoding.ASCII.GetBytes(s_tamperedFileName), stream, 22, nameOffset + s_tamperedFileName.Length); // patch in central directory too
+
+            using (ZipArchive archive = new ZipArchive(stream, ZipArchiveMode.Update, true))
+            {
+                ZipArchiveEntry e = archive.GetEntry(s_tamperedFileName);
+                oldCompressedSize = e.CompressedLength;
+                using (Stream s = e.Open())
+                {
+                    Assert.Equal(updatedUncompressedLength, s.Length);
+                    s.Seek(0, SeekOrigin.End);                  
+                    s.Write(data, 0, data.Length);
+                    Assert.Equal(updatedUncompressedLength + data.Length, s.Length);
+                }
+            }
+
+            using (ZipArchive modifiedArchive = new ZipArchive(stream, ZipArchiveMode.Read))
+            {
+                ZipArchiveEntry e = modifiedArchive.GetEntry(s_tamperedFileName);
+                using (Stream s = e.Open())
+                using (var ms = new MemoryStream())
+                {
+                    await s.CopyToAsync(ms, s_bufferSize);
+                    Assert.Equal(updatedUncompressedLength + data.Length, ms.Length);
+                    ms.Seek(updatedUncompressedLength, SeekOrigin.Begin);
+                    byte[] read = new byte[data.Length];
+                    ms.Read(read, 0, data.Length);
+                    Assert.Equal(append, Encoding.ASCII.GetString(read));
+                }
+                Assert.True(oldCompressedSize > e.CompressedLength); // old compressed size must be reduced by Uncomressed size limit
+            }
+        }
+
+        [Fact]
+        public static async Task UpdateZipArchive_OverwriteCorruptedEntry()
+        {
+            MemoryStream stream = await StreamHelpers.CreateTempCopyStream(zfile("normal.zip"));
+            int updatedUncompressedLength = 1310976;
+            string overwrite = "\r\n\r\nThe answer my friend, is blowin' in the wind.";
+            byte[] data = Encoding.ASCII.GetBytes(overwrite);
+
+            int nameOffset = PatchDataRelativeToFileName(Encoding.ASCII.GetBytes(s_tamperedFileName), stream, 8);  // patch uncompressed size in file header
+            PatchDataRelativeToFileName(Encoding.ASCII.GetBytes(s_tamperedFileName), stream, 22, nameOffset + s_tamperedFileName.Length); // patch in central directory too
+
+            using (ZipArchive archive = new ZipArchive(stream, ZipArchiveMode.Update, true))
+            {
+                ZipArchiveEntry e = archive.GetEntry(s_tamperedFileName);
+                string fileName = zmodified(Path.Combine("overwrite", "first.txt"));
+                var file = FileData.GetFile(fileName);
+
+                using (var s = new MemoryStream(data))
+                using (Stream es = e.Open())
+                {
+                    Assert.Equal(updatedUncompressedLength, es.Length);
+                    es.SetLength(0);
+                    await s.CopyToAsync(es, s_bufferSize);
+                    Assert.Equal(data.Length, es.Length);
+                }
+            }
+
+            using (ZipArchive modifiedArchive = new ZipArchive(stream, ZipArchiveMode.Read))
+            {
+                ZipArchiveEntry e = modifiedArchive.GetEntry(s_tamperedFileName);
+                using (Stream s = e.Open())
+                using (var ms = new MemoryStream())
+                {
+                    await s.CopyToAsync(ms, s_bufferSize);
+                    Assert.Equal(data.Length, ms.Length);
+                    Assert.Equal(overwrite, Encoding.ASCII.GetString(ms.GetBuffer(), 0, data.Length));
+                }
+            }
+        }
+
+        [Fact]
+        public static async Task UpdateZipArchive_AddFileTo_ZipWithCorruptedFile()
+        {
+            string addingFile = "added.txt";
+            MemoryStream stream = await StreamHelpers.CreateTempCopyStream(zfile("normal.zip"));
+            MemoryStream file = await StreamHelpers.CreateTempCopyStream(zmodified(Path.Combine("addFile", addingFile)));
+
+            int nameOffset = PatchDataRelativeToFileName(Encoding.ASCII.GetBytes(s_tamperedFileName), stream, 8);  // patch uncompressed size in file header
+            PatchDataRelativeToFileName(Encoding.ASCII.GetBytes(s_tamperedFileName), stream, 22, nameOffset + s_tamperedFileName.Length); // patch in central directory too          
+
+            using (ZipArchive archive = new ZipArchive(stream, ZipArchiveMode.Update, true))
+            {
+                ZipArchiveEntry e = archive.CreateEntry(addingFile);
+                using (Stream es = e.Open())
+                {
+                    file.CopyTo(es);
+                }
+            }
+
+            using (ZipArchive modifiedArchive = new ZipArchive(stream, ZipArchiveMode.Read))
+            {
+                ZipArchiveEntry e = modifiedArchive.GetEntry(s_tamperedFileName);
+                using (Stream s = e.Open())
+                using (var ms = new MemoryStream())
+                {
+                    await s.CopyToAsync(ms, s_bufferSize);
+                    Assert.Equal(e.Length, ms.Length);  // tampered file should read up to uncompressed size
+                }
+
+                ZipArchiveEntry addedEntry = modifiedArchive.GetEntry(addingFile);
+                Assert.NotNull(addedEntry);
+                Assert.Equal(addedEntry.Length, file.Length);
+
+                using (Stream s = addedEntry.Open())
+                { // Make sure file content added correctly
+                    int read = 0;
+                    byte[] buffer1 = new byte[1024];
+                    byte[] buffer2 = new byte[1024];
+                    file.Seek(0, SeekOrigin.Begin);
+
+                    while ((read = s.Read(buffer1, 0, buffer1.Length)) != 0 )
+                    {
+                        file.Read(buffer2, 0, buffer2.Length);
+                        Assert.Equal(buffer1, buffer2);
+                    }
+                }     
+            }
+        }
+
+        private static int PatchDataDescriptorRelativeToFileName(byte[] fileNameInBytes, MemoryStream packageStream, int distance, int start = 0)
+        {
+            byte[] dataDescriptorSignature = BitConverter.GetBytes(0x08074B50);
+            byte[] buffer = packageStream.GetBuffer();
+            int startOfName = FindSequenceIndex(fileNameInBytes, buffer, start);
+            int startOfDataDescriptor = FindSequenceIndex(dataDescriptorSignature, buffer, startOfName);
+            var startOfUpdatingData = startOfDataDescriptor + distance;
+
+            // updating 4 byte data
+            buffer[startOfUpdatingData] = 0;
+            buffer[startOfUpdatingData + 1] = 1;
+            buffer[startOfUpdatingData + 2] = 20;
+            buffer[startOfUpdatingData + 3] = 0;
+
+            return startOfName;
+        }
+
+        private static int PatchDataRelativeToFileName(byte[] fileNameInBytes, MemoryStream packageStream, int distance, int start = 0)
+        {
+            var buffer = packageStream.GetBuffer();
+            var startOfName = FindSequenceIndex(fileNameInBytes, buffer, start);
+            var startOfUpdatingData = startOfName - distance;
+
+            // updating 4 byte data
+            buffer[startOfUpdatingData] = 0;
+            buffer[startOfUpdatingData + 1] = 1;
+            buffer[startOfUpdatingData + 2] = 20;
+            buffer[startOfUpdatingData + 3] = 0;
+
+            return startOfName;
+        }
+
+        private static int PatchDataRelativeToFileNameFillBytes(byte[] fileNameInBytes, MemoryStream packageStream, int distance, int start = 0)
+        {
+            var buffer = packageStream.GetBuffer();
+            var startOfName = FindSequenceIndex(fileNameInBytes, buffer, start);
+            var startOfUpdatingData = startOfName - distance;
+
+            // updating 4 byte data
+            buffer[startOfUpdatingData] = 255;
+            buffer[startOfUpdatingData + 1] = 255;
+            buffer[startOfUpdatingData + 2] = 255;
+            buffer[startOfUpdatingData + 3] = 0;
+
+            return startOfName;
+        }
+
+        private static int FindSequenceIndex(byte[] searchItem, byte[] whereToSearch, int startIndex = 0)
+        {
+            for (int start = startIndex; start < whereToSearch.Length - searchItem.Length; ++start)
+            {
+                int searchIndex = 0;
+                while (searchIndex < searchItem.Length && searchItem[searchIndex] == whereToSearch[start + searchIndex])
+                {
+                    ++searchIndex;
+                }
+                if (searchIndex == searchItem.Length)
+                {
+                    return start;
+                }
+            }
+            return -1;
+        }
+
         [Theory]
         [InlineData("CDoffsetOutOfBounds.zip")]
         [InlineData("EOCDmissing.zip")]