Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Mark all Jazzer inputs as binary, to avoid bytes in them being misinterpreted as line terminators and being
# changed on checkout
/src/test/resources/**/*Inputs/** binary
31 changes: 27 additions & 4 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ on:
branches:
- master
paths-ignore:
- "README.md"
- "VERSION.txt"
- 'README.md'
- 'VERSION.txt'
pull_request:
branches:
- master
Expand All @@ -21,13 +21,13 @@ jobs:
matrix:
java_version: ['8', '11', '17', '21']
env:
JAVA_OPTS: "-XX:+TieredCompilation -XX:TieredStopAtLevel=1"
JAVA_OPTS: '-XX:+TieredCompilation -XX:TieredStopAtLevel=1'
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set up JDK
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
with:
distribution: "temurin"
distribution: 'temurin'
java-version: ${{ matrix.java_version }}
cache: 'maven'
- name: Build
Expand All @@ -42,3 +42,26 @@ jobs:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./target/site/jacoco/jacoco.xml
flags: unittests

# TODO: Maybe consider caching Jazzer `.cifuzz-corpus` directory if that improves fuzzing performance?
# But could become outdated when fuzz test methods are changed
fuzz:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set up JDK
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
with:
distribution: 'temurin'
java-version: 17
cache: 'maven'
- name: Run tests
id: fuzz-tests
# Don't run with `-q`, to see fuzzing progress
run: ./mvnw -B -ff -ntp --activate-profiles fuzz test
- name: Upload fuzz test inputs
if: always() && steps.fuzz-tests.outcome == 'failure'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: fuzz-test-inputs
path: src/test/resources/**/*Inputs/**
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ test-output
server/logs
runtime
logs

# Jazzer fuzzing corpus
/.cifuzz-corpus/
106 changes: 106 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.code-intelligence</groupId>
<artifactId>jazzer-junit</artifactId>
<version>0.28.0</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down Expand Up @@ -236,6 +242,106 @@
</build>

<profiles>
<!--
Profile for fuzzing, setting ENV variable and configuring individual fuzz methods to run
Should be run separately from regular build because it disables normal unit tests and only runs fuzz tests
-->
<profile>
<id>fuzz</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<environmentVariables>
<!-- Enable Jazzer fuzzing mode, see https://github.com/CodeIntelligenceTesting/jazzer?tab=readme-ov-file#fuzzing-mode -->
<JAZZER_FUZZ>1</JAZZER_FUZZ>
</environmentVariables>
<failIfNoTests>true</failIfNoTests>
</configuration>
<!--
Currently have to list all fuzz tests separately, see
https://github.com/CodeIntelligenceTesting/jazzer/issues/599
-->
<executions>
<!-- Skip default execution -->
<execution>
<id>default-test</id>
<configuration>
<skipTests>true</skipTests>
</configuration>
</execution>
<!-- Explicitly list separate fuzz test methods -->
<execution>
<id>fuzz-TestFuzzUnsafeLZF#decode</id>
<goals>
<goal>test</goal>
</goals>
<configuration>
<test>TestFuzzUnsafeLZF#decode</test>
</configuration>
</execution>
<execution>
<id>fuzz-TestFuzzUnsafeLZF#roundtrip</id>
<goals>
<goal>test</goal>
</goals>
<configuration>
<test>TestFuzzUnsafeLZF#roundtrip</test>
</configuration>
</execution>
<execution>
<id>fuzz-TestFuzzUnsafeLZF#encode</id>
<goals>
<goal>test</goal>
</goals>
<configuration>
<test>TestFuzzUnsafeLZF#encode</test>
</configuration>
</execution>
<execution>
<id>fuzz-TestFuzzUnsafeLZF#encodeAppend</id>
<goals>
<goal>test</goal>
</goals>
<configuration>
<test>TestFuzzUnsafeLZF#encodeAppend</test>
</configuration>
</execution>
<execution>
<id>fuzz-TestFuzzUnsafeLZF#inputStreamRead</id>
<goals>
<goal>test</goal>
</goals>
<configuration>
<test>TestFuzzUnsafeLZF#inputStreamRead</test>
</configuration>
</execution>
<execution>
<id>fuzz-TestFuzzUnsafeLZF#inputStreamSkip</id>
<goals>
<goal>test</goal>
</goals>
<configuration>
<test>TestFuzzUnsafeLZF#inputStreamSkip</test>
</configuration>
</execution>
<execution>
<id>fuzz-TestFuzzUnsafeLZF#outputStream</id>
<goals>
<goal>test</goal>
</goals>
<configuration>
<test>TestFuzzUnsafeLZF#outputStream</test>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>

<profile>
<id>release-sign-artifacts</id>
<activation>
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/com/ning/compress/BufferRecycler.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.ning.compress;

import java.lang.ref.SoftReference;
import java.util.Arrays;

/**
* Simple helper class to encapsulate details of basic buffer
Expand Down Expand Up @@ -66,6 +67,8 @@ public byte[] allocEncodingBuffer(int minSize)
public void releaseEncodeBuffer(byte[] buffer)
{
if (_encodingBuffer == null || (buffer != null && buffer.length > _encodingBuffer.length)) {
// Clear the buffer to protect against bugs which might leak the content during next use
Arrays.fill(buffer, (byte) 0);
_encodingBuffer = buffer;
}
}
Expand All @@ -84,6 +87,8 @@ public byte[] allocOutputBuffer(int minSize)
public void releaseOutputBuffer(byte[] buffer)
{
if (_outputBuffer == null || (buffer != null && buffer.length > _outputBuffer.length)) {
// Clear the buffer to protect against bugs which might leak the content during next use
Arrays.fill(buffer, (byte) 0);
_outputBuffer = buffer;
}
}
Expand All @@ -102,6 +107,8 @@ public int[] allocEncodingHash(int suggestedSize)
public void releaseEncodingHash(int[] buffer)
{
if (_encodingHash == null || (buffer != null && buffer.length > _encodingHash.length)) {
// Clear the buffer to protect against bugs which might leak the content during next use
Arrays.fill(buffer, 0);
_encodingHash = buffer;
}
}
Expand All @@ -126,6 +133,8 @@ public byte[] allocInputBuffer(int minSize)
public void releaseInputBuffer(byte[] buffer)
{
if (_inputBuffer == null || (buffer != null && buffer.length > _inputBuffer.length)) {
// Clear the buffer to protect against bugs which might leak the content during next use
Arrays.fill(buffer, (byte) 0);
_inputBuffer = buffer;
}
}
Expand All @@ -144,6 +153,8 @@ public byte[] allocDecodeBuffer(int size)
public void releaseDecodeBuffer(byte[] buffer)
{
if (_decodingBuffer == null || (buffer != null && buffer.length > _decodingBuffer.length)) {
// Clear the buffer to protect against bugs which might leak the content during next use
Arrays.fill(buffer, (byte) 0);
_decodingBuffer = buffer;
}
}
Expand Down
30 changes: 24 additions & 6 deletions src/main/java/com/ning/compress/lzf/ChunkDecoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ public ChunkDecoder() { }
public final byte[] decode(final byte[] inputBuffer) throws LZFException
{
byte[] result = new byte[calculateUncompressedSize(inputBuffer, 0, inputBuffer.length)];
decode(inputBuffer, 0, inputBuffer.length, result);
int decodedLen = decode(inputBuffer, 0, inputBuffer.length, result);
assert decodedLen == result.length;
return result;
}

Expand All @@ -49,7 +50,8 @@ public final byte[] decode(final byte[] inputBuffer) throws LZFException
public final byte[] decode(final byte[] inputBuffer, int inputPtr, int inputLen) throws LZFException
{
byte[] result = new byte[calculateUncompressedSize(inputBuffer, inputPtr, inputLen)];
decode(inputBuffer, inputPtr, inputLen, result);
int decodedLen = decode(inputBuffer, inputPtr, inputLen, result);
assert decodedLen == result.length;
return result;
}

Expand Down Expand Up @@ -78,9 +80,9 @@ public int decode(final byte[] sourceBuffer, int inPtr, int inLength,
int outPtr = 0;
int blockNr = 0;

final int end = inPtr + inLength - 1; // -1 to offset possible end marker
final int endMinusOne = inPtr + inLength - 1; // -1 to offset possible end marker

while (inPtr < end) {
while (inPtr < endMinusOne) {
// let's do basic sanity checks; no point in skimping with these checks
if (sourceBuffer[inPtr] != LZFChunk.BYTE_Z || sourceBuffer[inPtr+1] != LZFChunk.BYTE_V) {
throw new LZFException("Corrupt input data, block #"+blockNr+" (at offset "+inPtr+"): did not start with 'ZV' signature bytes");
Expand All @@ -101,10 +103,15 @@ public int decode(final byte[] sourceBuffer, int inPtr, int inLength,
_reportArrayOverflow(targetBuffer, outPtr, uncompLen);
}
inPtr += 2;
decodeChunk(sourceBuffer, inPtr, targetBuffer, outPtr, outPtr+uncompLen);
decodeChunk(sourceBuffer, inPtr, inPtr + len, targetBuffer, outPtr, outPtr+uncompLen);
outPtr += uncompLen;
}
inPtr += len;

// Fail if more input than expected was consumed, respectively if `inLength` does not include full block
if (inPtr > endMinusOne + 1) {
throw new LZFException("Corrupt input data, block #" + blockNr + " is incomplete");
}
++blockNr;
}
return outPtr;
Expand All @@ -122,13 +129,24 @@ public int decode(final byte[] sourceBuffer, int inPtr, int inLength,
*/
public abstract int decodeChunk(final InputStream is, final byte[] inputBuffer, final byte[] outputBuffer)
throws IOException;

/**
* Main decode method for individual chunks.
*/
public abstract void decodeChunk(byte[] in, int inPos, byte[] out, int outPos, int outEnd)
throws LZFException;

/**
* Main decode method for individual chunks.
*
* <p>For backward compatibility this method just delegates to {@link #decodeChunk(byte[], int, byte[], int, int)},
* ignoring the {@code inEnd} parameter. Subclasses should override it and consider the {@code inEnd} parameter.
*/
public void decodeChunk(byte[] in, int inPos, int inEnd, byte[] out, int outPos, int outEnd)
throws LZFException {
decodeChunk(in, inPos, out, outPos, outEnd);
}

/**
* @return If positive number, number of bytes skipped; if -1, end-of-stream was
* reached; otherwise, amount of content
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/ning/compress/lzf/LZFUncompressor.java
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ private final void _uncompress(byte[] src, int srcOffset, int len) throws IOExce
if (_decodeBuffer == null) {
_decodeBuffer = _recycler.allocDecodeBuffer(LZFChunk.MAX_CHUNK_LEN);
}
_decoder.decodeChunk(src, srcOffset, _decodeBuffer, 0, _uncompressedLength);
_decoder.decodeChunk(src, srcOffset, srcOffset + len, _decodeBuffer, 0, _uncompressedLength);
_handler.handleData(_decodeBuffer, 0, _uncompressedLength);
}

Expand Down
Loading