Skip to content

Commit

Permalink
Add fuzz testing that was used to find previous fixes
Browse files Browse the repository at this point in the history
This fuzz testing and seed corpus helped validate for protocol flaws in decoding TDF's.
This testing is time consuming, and Jazzer sometimes has some weird IO blocking behavior that is not actually indicative of a flaw.  For that reason this is not part of CI, and instead is run through `fuzz.sh` when needed.
  • Loading branch information
jentfoo committed Nov 27, 2024
1 parent 2a343d7 commit d055f7d
Show file tree
Hide file tree
Showing 16 changed files with 230 additions and 14 deletions.
11 changes: 11 additions & 0 deletions sdk/fuzz.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash
set -e

tests=("fuzzNanoTDF", "fuzzTDF", "fuzzZipRead")
base_seed_dir="src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/"

for test in "${tests[@]}"; do
seed_dir="${base_seed_dir}${test}"
echo "Running $test fuzzing with seeds from $seed_dir"
mvn verify -P fuzz -Djazzer.testDir=$seed_dir
done
124 changes: 123 additions & 1 deletion sdk/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
<version>0.7.5</version>
</parent>
<packaging>jar</packaging>
<properties>
<jazzer.version>0.22.1</jazzer.version>
<jazzer.baseurl>https://github.com/CodeIntelligenceTesting/jazzer/releases/download/v${jazzer.version}</jazzer.baseurl>
</properties>
<dependencies>
<!-- Logging Dependencies -->
<dependency>
Expand Down Expand Up @@ -121,6 +125,18 @@
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.code-intelligence</groupId>
<artifactId>jazzer-api</artifactId>
<version>${jazzer.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.code-intelligence</groupId>
<artifactId>jazzer-junit</artifactId>
<version>${jazzer.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
Expand Down Expand Up @@ -307,4 +323,110 @@
</plugin>
</plugins>
</build>
</project>
<!--profile to execute fuzz test -->
<profiles>
<profile>
<id>fuzz</id>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<properties>
<skipTests>true</skipTests>
</properties>
<build>
<plugins>
<!-- Plugin to download and unpack the Jazzer agent -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>download-and-unpack-jazzer</id>
<phase>process-test-classes</phase>
<configuration>
<target>
<condition property="jazzer.os" value="windows">
<os family="windows"/>
</condition>
<condition property="jazzer.os" value="macos">
<os family="mac"/>
</condition>
<condition property="jazzer.os" value="linux">
<os family="unix"/>
</condition>
<echo message="Detected OS: ${jazzer.os}"/>
<mkdir dir="${project.build.directory}/jazzer"/>
<get src="${jazzer.baseurl}/jazzer-${jazzer.os}.tar.gz" dest="${project.build.directory}/jazzer/jazzer.tar.gz"/>
<untar compression="gzip" src="${project.build.directory}/jazzer/jazzer.tar.gz" dest="${project.build.directory}/jazzer"/>
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>

<!-- Copy dependencies to a directory for Jazzer to reference -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.4.0</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>process-test-classes</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/dependency-jars</outputDirectory>
<includeScope>test</includeScope>
</configuration>
</execution>
</executions>
</plugin>

<!-- Plugin to execute Jazzer -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>run-jazzer-fuzzing</id>
<phase>verify</phase>
<configuration>
<target>
<path id="project.classpath">
<pathelement location="${project.build.directory}/classes"/>
<pathelement location="${project.build.directory}/test-classes"/>
<fileset dir="${project.build.directory}/dependency-jars">
<include name="**/*.jar"/>
</fileset>
</path>
<pathconvert property="project.classpath.string" pathsep="${path.separator}">
<path refid="project.classpath"/>
</pathconvert>
<property environment="env"/>

<chmod file="${project.build.directory}/jazzer/jazzer" perm="777"/>

<exec executable="bash">
<arg value="-c"/>
<arg value="if [ -z &quot;${JAVA_HOME}&quot; ]; then JAVA_HOME=$(dirname $(dirname $(which java))); fi; DYLD_LIBRARY_PATH=$(find &quot;${JAVA_HOME}&quot; -type d | grep 'libexec/openjdk.jdk/Contents/Home/lib/server' 2&gt;/dev/null | head -n 1); if [ -z &quot;${DYLD_LIBRARY_PATH}&quot; ]; then DYLD_LIBRARY_PATH=&quot;${JAVA_HOME}/lib/server&quot;; fi; export DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}; ${project.build.directory}/jazzer/jazzer --cp='${project.classpath.string}' --target_class='io.opentdf.platform.sdk.Fuzzing' --instrumentation_includes='io.opentdf.platform.sdk.**' ${jazzer.testDir}"/>
</exec>
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
69 changes: 69 additions & 0 deletions sdk/src/test/java/io/opentdf/platform/sdk/Fuzzing.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package io.opentdf.platform.sdk;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;

import com.code_intelligence.jazzer.api.FuzzedDataProvider;
import com.code_intelligence.jazzer.junit.FuzzTest;
import com.google.gson.JsonParseException;
import com.nimbusds.jose.JOSEException;

import io.opentdf.platform.sdk.TDF.FailedToCreateGMAC;
import io.opentdf.platform.sdk.TDF.Reader;

public class Fuzzing {
private static final String testDuration = "600s";
private static final OutputStream ignoreOutputStream = new OutputStream() {
@Override
public void write(int b) {
// ignored
}

@Override
public void write(byte b[], int off, int len) {
// ignored
}
};

@FuzzTest(maxDuration=testDuration)
public void fuzzNanoTDF(FuzzedDataProvider data) throws IOException {
byte[] fuzzBytes = data.consumeRemainingAsBytes();
NanoTDF nanoTDF = new NanoTDF();
nanoTDF.readNanoTDF(ByteBuffer.wrap(fuzzBytes), ignoreOutputStream, NanoTDFTest.kas);
}

@FuzzTest(maxDuration=testDuration)
public void fuzzTDF(FuzzedDataProvider data) throws FailedToCreateGMAC, NoSuchAlgorithmException, IOException, JOSEException, ParseException, DecoderException {
byte[] fuzzBytes = data.consumeRemainingAsBytes();
byte[] key = new byte[32]; // use consistent zero key for performance and so fuzz can relate to seed
var assertionVerificationKeys = new Config.AssertionVerificationKeys();
assertionVerificationKeys.defaultKey = new AssertionConfig.AssertionKey(AssertionConfig.AssertionKeyAlg.HS256, key);
Config.TDFReaderConfig readerConfig = Config.newTDFReaderConfig(
Config.withAssertionVerificationKeys(assertionVerificationKeys));
TDF tdf = new TDF();

try {
Reader reader = tdf.loadTDF(new SeekableInMemoryByteChannel(fuzzBytes), TDFTest.kas, readerConfig);

reader.readPayload(ignoreOutputStream);
} catch (SDKException | InvalidZipException | JsonParseException | IOException | IllegalArgumentException e) {
// expected failure cases
}
}

@FuzzTest(maxDuration=testDuration)
public void fuzzZipRead(FuzzedDataProvider data) {
byte[] fuzzBytes = data.consumeRemainingAsBytes();
try {
ZipReaderTest.testReadingZipChannel(new SeekableInMemoryByteChannel(fuzzBytes), false);
} catch (InvalidZipException | IllegalArgumentException | JsonParseException | IOException e) {
// cases which are expected with invalid fuzzed inputs
}
}
}
2 changes: 1 addition & 1 deletion sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public class NanoTDFTest {

private static final String KID = "r1";

private static SDK.KAS kas = new SDK.KAS() {
protected static SDK.KAS kas = new SDK.KAS() {
@Override
public void close() throws Exception {
}
Expand Down
38 changes: 26 additions & 12 deletions sdk/src/test/java/io/opentdf/platform/sdk/ZipReaderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,13 @@

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.SeekableByteChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
Expand All @@ -32,21 +36,31 @@ public class ZipReaderTest {
public void testReadingExistingZip() throws Exception {
try (RandomAccessFile raf = new RandomAccessFile("src/test/resources/sample.txt.tdf", "r")) {
var fileChannel = raf.getChannel();
var zipReader = new ZipReader(fileChannel);
var entries = zipReader.getEntries();
ZipReaderTest.testReadingZipChannel(fileChannel, true);
}
}

protected static void testReadingZipChannel(SeekableByteChannel fileChannel, boolean test) throws IOException {
var zipReader = new ZipReader(fileChannel);
var entries = zipReader.getEntries();
if (test) {
assertThat(entries.size()).isEqualTo(2);
for (var entry: entries) {
var stream = new ByteArrayOutputStream();
if (entry.getName().endsWith(".json")) {
entry.getData().transferTo(stream);
var data = stream.toString(StandardCharsets.UTF_8);
var gson = new GsonBuilder()
.registerTypeAdapter(Manifest.class, new ManifestDeserializer())
.create();
var map = gson.fromJson(data, Map.class);

}
for (var entry: entries) {
var stream = new ByteArrayOutputStream();
if (entry.getName().endsWith(".json")) {
entry.getData().transferTo(stream);
var data = stream.toString(StandardCharsets.UTF_8);
var gson = new GsonBuilder()
.registerTypeAdapter(Manifest.class, new ManifestDeserializer())
.create();
var map = gson.fromJson(data, Map.class);

if (test) {
assertThat(map.get("encryptionInformation")).isNotNull();
}
} else if (!test) {
entry.getData().transferTo(stream); // still invoke getData logic
}
}
}
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 comments on commit d055f7d

Please sign in to comment.