diff --git a/src/main/java/com/ning/compress/lzf/impl/UnsafeChunkDecoder.java b/src/main/java/com/ning/compress/lzf/impl/UnsafeChunkDecoder.java index 8472cf2..9b9787e 100644 --- a/src/main/java/com/ning/compress/lzf/impl/UnsafeChunkDecoder.java +++ b/src/main/java/com/ning/compress/lzf/impl/UnsafeChunkDecoder.java @@ -89,35 +89,49 @@ public final void decodeChunk(byte[] in, int inPos, int inEnd, byte[] out, int o main_loop: do { + if (inPos >= inEnd) { + throw new LZFException("Corrupt data: truncated block"); + } int ctrl = in[inPos++] & 255; while (ctrl < LZFChunk.MAX_LITERAL) { // literal run(s) + final int literalLength = ctrl + 1; + if (inPos > inEnd - literalLength || outPos > outEnd - literalLength) { + throw new LZFException("Corrupt data: truncated block"); + } if (outPos > outputEnd32 || inPos > inputEnd32) { - System.arraycopy(in, inPos, out, outPos, ctrl+1); + System.arraycopy(in, inPos, out, outPos, literalLength); } else { copyUpTo32(in, inPos, out, outPos, ctrl); } - ++ctrl; - inPos += ctrl; - outPos += ctrl; + inPos += literalLength; + outPos += literalLength; if (outPos >= outEnd) { break main_loop; } + // The literal run may end exactly at inEnd, but another byte is required for the next control token. + if (inPos >= inEnd) { + throw new LZFException("Corrupt data: truncated block"); + } ctrl = in[inPos++] & 255; } // back reference int len = ctrl >> 5; ctrl = -((ctrl & 0x1f) << 8) - 1; - // short back reference? 2 bytes; run lengths of 2 - 8 bytes + // short back reference? 2 bytes; run lengths of 3 - 8 bytes if (len < 7) { + if (inPos >= inEnd) { + throw new LZFException("Corrupt data: truncated block"); + } ctrl -= in[inPos++] & 255; + final int copyLength = len + 2; + if (outPos > outEnd - copyLength || outPos + ctrl < outPosStart) { + throw new LZFException("Invalid back reference"); + } if (ctrl < -7 && outPos < outputEnd8) { // non-overlapping? can use efficient bulk copy - if (outPos + ctrl < outPosStart) { - throw new LZFException("Invalid back reference"); - } final long rawOffset = BYTE_ARRAY_OFFSET + outPos; unsafe.putLong(out, rawOffset, unsafe.getLong(out, rawOffset + ctrl)); // moveLong(out, outPos, outEnd, ctrl); - outPos += len+2; + outPos += copyLength; continue; } // otherwise, byte-by-byte @@ -125,17 +139,20 @@ public final void decodeChunk(byte[] in, int inPos, int inEnd, byte[] out, int o continue; } // long back reference: 3 bytes, length of up to 264 bytes + if (inPos > inEnd - 2) { + throw new LZFException("Corrupt data: truncated block"); + } len = (in[inPos++] & 255) + 9; ctrl -= in[inPos++] & 255; + if (outPos > outEnd - len || outPos + ctrl < outPosStart) { + throw new LZFException("Invalid back reference"); + } // First: ovelapping case can't use default handling, off line. if ((ctrl > -9) || (outPos > outputEnd32)) { outPos = copyOverlappingLong(out, outPos, ctrl, len-9); continue; } // but non-overlapping is simple - if (outPos + ctrl < outPosStart) { - throw new LZFException("Invalid back reference"); - } if (len <= 32) { copyUpTo32(out, outPos+ctrl, outPos, len-1); outPos += len; diff --git a/src/main/java/com/ning/compress/lzf/impl/VanillaChunkDecoder.java b/src/main/java/com/ning/compress/lzf/impl/VanillaChunkDecoder.java index 7f99f3c..38fb5bd 100644 --- a/src/main/java/com/ning/compress/lzf/impl/VanillaChunkDecoder.java +++ b/src/main/java/com/ning/compress/lzf/impl/VanillaChunkDecoder.java @@ -50,82 +50,34 @@ public void decodeChunk(byte[] in, int inPos, byte[] out, int outPos, int outEnd public final void decodeChunk(byte[] in, int inPos, int inEnd, byte[] out, int outPos, int outEnd) throws LZFException { - do { + final int outStart = outPos; + while (outPos < outEnd) { + if (inPos >= inEnd) { + throw new LZFException("Corrupt data: truncated block"); + } int ctrl = in[inPos++] & 255; if (ctrl < LZFChunk.MAX_LITERAL) { // literal run - switch (ctrl) { - case 31: - out[outPos++] = in[inPos++]; - case 30: - out[outPos++] = in[inPos++]; - case 29: - out[outPos++] = in[inPos++]; - case 28: - out[outPos++] = in[inPos++]; - case 27: - out[outPos++] = in[inPos++]; - case 26: - out[outPos++] = in[inPos++]; - case 25: - out[outPos++] = in[inPos++]; - case 24: - out[outPos++] = in[inPos++]; - case 23: - out[outPos++] = in[inPos++]; - case 22: - out[outPos++] = in[inPos++]; - case 21: - out[outPos++] = in[inPos++]; - case 20: - out[outPos++] = in[inPos++]; - case 19: - out[outPos++] = in[inPos++]; - case 18: - out[outPos++] = in[inPos++]; - case 17: - out[outPos++] = in[inPos++]; - case 16: - out[outPos++] = in[inPos++]; - case 15: - out[outPos++] = in[inPos++]; - case 14: - out[outPos++] = in[inPos++]; - case 13: - out[outPos++] = in[inPos++]; - case 12: - out[outPos++] = in[inPos++]; - case 11: - out[outPos++] = in[inPos++]; - case 10: - out[outPos++] = in[inPos++]; - case 9: - out[outPos++] = in[inPos++]; - case 8: - out[outPos++] = in[inPos++]; - case 7: - out[outPos++] = in[inPos++]; - case 6: - out[outPos++] = in[inPos++]; - case 5: - out[outPos++] = in[inPos++]; - case 4: - out[outPos++] = in[inPos++]; - case 3: - out[outPos++] = in[inPos++]; - case 2: - out[outPos++] = in[inPos++]; - case 1: - out[outPos++] = in[inPos++]; - case 0: - out[outPos++] = in[inPos++]; + int literalLen = ctrl + 1; + if (inPos > inEnd - literalLen || outPos > outEnd - literalLen) { + throw new LZFException("Corrupt data: truncated block"); } + System.arraycopy(in, inPos, out, outPos, literalLen); + inPos += literalLen; + outPos += literalLen; continue; } // back reference int len = ctrl >> 5; ctrl = -((ctrl & 0x1f) << 8) - 1; if (len < 7) { // 2 bytes; length of 3 - 8 bytes + if (inPos >= inEnd) { + throw new LZFException("Corrupt data: truncated block"); + } ctrl -= in[inPos++] & 255; + final int copyLength = len + 2; + if (outPos > outEnd - copyLength || outPos + ctrl < outStart) { + throw new LZFException("Invalid back reference"); + } out[outPos] = out[outPos++ + ctrl]; out[outPos] = out[outPos++ + ctrl]; switch (len) { @@ -146,9 +98,16 @@ public final void decodeChunk(byte[] in, int inPos, int inEnd, byte[] out, int o } // long version (3 bytes, length of up to 264 bytes) + if (inPos > inEnd - 2) { + throw new LZFException("Corrupt data: truncated block"); + } len = in[inPos++] & 255; ctrl -= in[inPos++] & 255; - + final int copyLength = len + 9; + if (outPos > outEnd - copyLength || outPos + ctrl < outStart) { + throw new LZFException("Invalid back reference"); + } + // First: if there is no overlap, can just use arraycopy: if ((ctrl + len) < -9) { len += 9; @@ -192,7 +151,7 @@ public final void decodeChunk(byte[] in, int inPos, int inEnd, byte[] out, int o case 1: out[outPos] = out[outPos++ + ctrl]; } - } while (outPos < outEnd); + } // sanity check to guard against corrupt data: if (inPos != inEnd) { diff --git a/src/test/java/com/ning/compress/lzf/TestLZFDecoder.java b/src/test/java/com/ning/compress/lzf/TestLZFDecoder.java index fb7dd0e..d5c46e6 100644 --- a/src/test/java/com/ning/compress/lzf/TestLZFDecoder.java +++ b/src/test/java/com/ning/compress/lzf/TestLZFDecoder.java @@ -50,6 +50,27 @@ public void testUnsafeValidation() { assertThrows(ArrayIndexOutOfBoundsException.class, () -> decoder.decodeChunk(array, goodStart, goodEnd, array, goodStart, array.length + 1)); } + @Test + public void testMalformedShortBackReference() { + byte[] inputWithTrailingByte = new byte[] { + LZFChunk.BYTE_Z, LZFChunk.BYTE_V, LZFChunk.BLOCK_TYPE_COMPRESSED, + 0, 3, 0, (byte) 0x8f, 0, 4, 0x50, 0x53 + }; + byte[] truncatedInput = new byte[] { + LZFChunk.BYTE_Z, LZFChunk.BYTE_V, LZFChunk.BLOCK_TYPE_COMPRESSED, + 0, 3, 0, (byte) 0x8f, 0, 4, 0x50 + }; + + assertMalformedShortBackReference(inputWithTrailingByte, ChunkDecoderFactory.safeInstance()); + assertMalformedShortBackReference(inputWithTrailingByte, ChunkDecoderFactory.optimalInstance()); + assertMalformedShortBackReference(truncatedInput, ChunkDecoderFactory.safeInstance()); + assertMalformedShortBackReference(truncatedInput, ChunkDecoderFactory.optimalInstance()); + } + + private void assertMalformedShortBackReference(byte[] input, ChunkDecoder decoder) { + assertThrows(LZFException.class, () -> decoder.decodeChunk(input, 7, 10, new byte[143], 0, 143)); + } + /* /////////////////////////////////////////////////////////////////////// // Second-level test methods