Skip to content

Commit 0bf06a3

Browse files
committed
Add fuzz testing
1 parent 51b13a1 commit 0bf06a3

21 files changed

Lines changed: 701 additions & 1 deletion

File tree

.github/workflows/fuzz-testing.yml

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
name: Fuzz Testing
2+
3+
on:
4+
# Run daily at 2 AM UTC
5+
schedule:
6+
- cron: '0 2 * * *'
7+
8+
# Allow manual trigger
9+
workflow_dispatch:
10+
inputs:
11+
duration:
12+
description: 'Fuzz test duration (e.g., 10m, 1h)'
13+
required: false
14+
default: '15m'
15+
16+
permissions:
17+
contents: read
18+
19+
jobs:
20+
fuzz:
21+
runs-on: ubuntu-latest
22+
name: Fuzz Testing
23+
24+
steps:
25+
- uses: actions/checkout@v5
26+
27+
- name: Set up JDK 21
28+
uses: actions/setup-java@v5
29+
with:
30+
java-version: 21
31+
distribution: 'corretto'
32+
33+
- name: Cache compiled buildscripts
34+
uses: actions/cache@v4
35+
with:
36+
key: ${{ runner.os }}-gradle-${{ hashFiles('buildSrc/**/*.kts') }}
37+
path: |
38+
./buildSrc/build
39+
40+
- name: Setup Gradle
41+
uses: gradle/actions/setup-gradle@v5
42+
with:
43+
cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
44+
gradle-home-cache-includes: |
45+
caches
46+
47+
- name: Restore fuzz corpus cache
48+
uses: actions/cache/restore@v4
49+
with:
50+
key: fuzz-corpus-${{ github.run_id }}
51+
restore-keys: |
52+
fuzz-corpus-
53+
path: |
54+
**/.cifuzz-corpus/
55+
**/src/fuzz/resources/corpus/
56+
57+
- name: Run all fuzz tests
58+
run: |
59+
DURATION="${{ github.event.inputs.duration || '1h' }}"
60+
./gradlew fuzz -Pfuzz.maxDuration=$DURATION --stacktrace
61+
62+
- name: Save fuzz corpus cache
63+
uses: actions/cache/save@v4
64+
if: always()
65+
with:
66+
key: fuzz-corpus-${{ github.run_id }}
67+
path: |
68+
**/.cifuzz-corpus/
69+
**/src/fuzz/resources/corpus/
70+
71+
- name: Upload fuzz test reports
72+
uses: actions/upload-artifact@v5
73+
if: always()
74+
with:
75+
name: fuzz-test-reports
76+
path: '**/build/reports/jazzer/**'
77+
78+
- name: Upload crash artifacts
79+
uses: actions/upload-artifact@v5
80+
if: failure()
81+
with:
82+
name: fuzz-crashes
83+
path: |
84+
**/crash-*
85+
**/leak-*
86+
**/timeout-*

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,8 @@ build
1919
.settings
2020

2121
smithy-java-core/out
22+
23+
.cifuzz-corpus
24+
crash-*
25+
26+
mise.toml

aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/GeneratedSchemaIndex.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import java.util.HashMap;
99
import java.util.Map;
10+
import java.util.function.Consumer;
1011
import software.amazon.smithy.java.core.schema.Schema;
1112
import software.amazon.smithy.java.core.schema.SchemaIndex;
1213
import software.amazon.smithy.model.shapes.ShapeId;
@@ -33,4 +34,9 @@ public final class GeneratedSchemaIndex extends SchemaIndex {
3334
public Schema getSchema(ShapeId id) {
3435
return SCHEMA_MAP.get(id);
3536
}
37+
38+
@Override
39+
public void visit(Consumer<Schema> visitor) {
40+
SCHEMA_MAP.values().forEach(visitor);
41+
}
3642
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Configure fuzz testing for codec modules using Jazzer
2+
3+
configure<SourceSetContainer> {
4+
val main by getting
5+
val test by getting
6+
7+
// Create a separate "fuzz" source set for fuzz tests
8+
create("fuzz") {
9+
compileClasspath += main.output + configurations["testRuntimeClasspath"] + configurations["testCompileClasspath"]
10+
runtimeClasspath += output + compileClasspath + test.runtimeClasspath + test.output
11+
}
12+
}
13+
14+
// Add fuzz-test-harness dependency to the fuzz source set
15+
dependencies {
16+
"fuzzImplementation"(project(":fuzz-test-harness"))
17+
}
18+
19+
// Create the fuzz test task
20+
tasks.register<Test>("fuzz") {
21+
description = "Run fuzz tests using Jazzer"
22+
group = "verification"
23+
24+
val fuzzSourceSet = project.the<SourceSetContainer>()["fuzz"]
25+
testClassesDirs = fuzzSourceSet.output.classesDirs
26+
classpath = fuzzSourceSet.runtimeClasspath
27+
28+
useJUnitPlatform()
29+
30+
// Fork for each test class to ensure isolation
31+
setForkEvery(1)
32+
33+
// Memory settings
34+
maxHeapSize = "2048m"
35+
minHeapSize = "2048m"
36+
37+
// Enable Jazzer fuzzing mode
38+
environment("JAZZER_FUZZ", "1")
39+
40+
// Jazzer configuration
41+
systemProperty("jazzer.valueprofile", "1")
42+
systemProperty("jazzer.coverage_report", "${project.layout.buildDirectory}/reports/jazzer")
43+
44+
systemProperty("jazzer.instrumentation_includes", "software.amazon.smithy.java.**")
45+
46+
val corpusDir = "${project.projectDir}/src/fuzz/resources/corpus"
47+
if (file(corpusDir).exists()) {
48+
systemProperty("jazzer.internal.arg.0", corpusDir)
49+
}
50+
51+
val maxDuration = project.findProperty("fuzz.maxDuration") ?: "1h"
52+
systemProperty("jazzer.max_duration", maxDuration)
53+
}

codecs/cbor-codec/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
plugins {
22
id("smithy-java.module-conventions")
3+
id("smithy-java.fuzz-test")
34
`java-test-fixtures`
45
}
56

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package software.amazon.smithy.java.cbor;
7+
8+
import software.amazon.smithy.java.core.serde.Codec;
9+
import software.amazon.smithy.java.fuzz.CodecDeserializationFuzzTestBase;
10+
11+
/**
12+
* Fuzz tests for Rpcv2CborCodec with various configurations.
13+
*/
14+
class DeserializationFuzzTest {
15+
16+
static class DefaultTest extends CodecDeserializationFuzzTestBase {
17+
18+
@Override
19+
protected Codec codecToFuzz() {
20+
return Rpcv2CborCodec.builder().build();
21+
}
22+
}
23+
24+
}

codecs/cbor-codec/src/main/java/software/amazon/smithy/java/cbor/CborDeserializer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ public <T> void readList(Schema schema, T state, ListMemberConsumer<T> consumer)
445445

446446
@Override
447447
public int containerSize() {
448-
return parser.collectionSize();
448+
return -1; //parser.collectionSize(); FIXME : Renable once we have some bounds checking, otherwise a small payload can trigger a huge allocation
449449
}
450450

451451
@Override

codecs/json-codec/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
plugins {
44
id("smithy-java.module-conventions")
5+
id("smithy-java.fuzz-test")
56
alias(libs.plugins.shadow)
67
}
78

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package software.amazon.smithy.java.json;
7+
8+
import software.amazon.smithy.java.core.serde.Codec;
9+
import software.amazon.smithy.java.fuzz.CodecDeserializationFuzzTestBase;
10+
11+
/**
12+
* Fuzz tests for JsonCodec with various configurations.
13+
*/
14+
class DeserializationFuzzTest {
15+
16+
static class DefaultTest extends CodecDeserializationFuzzTestBase {
17+
18+
@Override
19+
protected Codec codecToFuzz() {
20+
return JsonCodec.builder().build();
21+
}
22+
}
23+
24+
}

codecs/xml-codec/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
plugins {
22
id("smithy-java.module-conventions")
3+
id("smithy-java.fuzz-test")
34
}
45

56
description = "This module provides XML functionality"

0 commit comments

Comments
 (0)