From e81ac49d8e72df66e8d66086e56c1ade8b26c909 Mon Sep 17 00:00:00 2001 From: FerrumBrain Date: Tue, 28 Jan 2025 11:52:14 +0100 Subject: [PATCH 01/20] First iteration + ProtobufTests --- .../kotlinx/fuzz/jazzer/JazzerEngine.kt | 12 +- kotlinx.fuzz.test/build.gradle.kts | 4 +- .../kotlin/kotlinx/fuzz/test/ProtobufTests.kt | 521 ++++++++++++++++++ 3 files changed, 533 insertions(+), 4 deletions(-) create mode 100644 kotlinx.fuzz.test/src/test/kotlin/kotlinx/fuzz/test/ProtobufTests.kt diff --git a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerEngine.kt b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerEngine.kt index c8fa86fd..0fd4e05b 100644 --- a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerEngine.kt +++ b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerEngine.kt @@ -9,12 +9,11 @@ import com.code_intelligence.jazzer.utils.Log import java.lang.invoke.MethodHandles import java.lang.reflect.Method import java.util.concurrent.atomic.AtomicReference -import kotlin.io.path.ExperimentalPathApi -import kotlin.io.path.createTempDirectory -import kotlin.io.path.deleteRecursively import kotlin.reflect.jvm.javaMethod import kotlinx.fuzz.KFuzzConfig import kotlinx.fuzz.KFuzzEngine +import java.nio.file.Paths +import kotlin.io.path.* @Suppress("unused") class JazzerEngine(private val config: KFuzzConfig) : KFuzzEngine { @@ -41,9 +40,16 @@ class JazzerEngine(private val config: KFuzzConfig) : KFuzzEngine { val libFuzzerArgs = mutableListOf("fake_argv0") val corpusDir = createTempDirectory("jazzer-corpus") + val reproducerPath = Paths.get(Opt.reproducerPath.get(), method.declaringClass.name, method.name).toAbsolutePath() + if (!reproducerPath.exists()) { + reproducerPath.createDirectories() + } + libFuzzerArgs += corpusDir.toString() libFuzzerArgs += "-max_total_time=${config.maxSingleTargetFuzzTime.inWholeSeconds}" libFuzzerArgs += "-rss_limit_mb=${jazzerConfig.libFuzzerRssLimit}" + libFuzzerArgs += "-artifact_prefix=${reproducerPath.toAbsolutePath()}/" + libFuzzerArgs += "${reproducerPath.toAbsolutePath()}" val atomicFinding = AtomicReference() FuzzTargetRunner.registerFatalFindingHandlerForJUnit { finding -> diff --git a/kotlinx.fuzz.test/build.gradle.kts b/kotlinx.fuzz.test/build.gradle.kts index 54d26a83..f1013515 100644 --- a/kotlinx.fuzz.test/build.gradle.kts +++ b/kotlinx.fuzz.test/build.gradle.kts @@ -4,6 +4,7 @@ import kotlin.time.Duration.Companion.seconds plugins { kotlin("jvm") version "2.0.21" id("kotlinx.fuzz") + kotlin("plugin.serialization") version "2.0.20" } repositories { @@ -13,11 +14,12 @@ repositories { dependencies { testImplementation(kotlin("test")) // adds green arrow in IDEA (no idea why) testRuntimeOnly("kotlinx.fuzz:kotlinx.fuzz.jazzer") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.7.3") } fuzzConfig { instrument = listOf("kotlinx.fuzz.test.**") - maxSingleTargetFuzzTime = 10.seconds + maxSingleTargetFuzzTime = 3600.seconds } kotlin { diff --git a/kotlinx.fuzz.test/src/test/kotlin/kotlinx/fuzz/test/ProtobufTests.kt b/kotlinx.fuzz.test/src/test/kotlin/kotlinx/fuzz/test/ProtobufTests.kt new file mode 100644 index 00000000..acb07e14 --- /dev/null +++ b/kotlinx.fuzz.test/src/test/kotlin/kotlinx/fuzz/test/ProtobufTests.kt @@ -0,0 +1,521 @@ +@file:Suppress("UNCHECKED_CAST", "VARIABLE_WITH_REDUNDANT_INITIALIZER") +package kotlinx.fuzz.test + +import kotlinx.fuzz.* +import org.junit.jupiter.api.Assertions.assertEquals +import kotlinx.serialization.* +import kotlinx.serialization.protobuf.* + +object ProtobufTests { + enum class TestEnum(val size: Int, val value: String) { + FIRST(size = 1, value = "first"), + SECOND(size = 2, value = "second"), + THIRD(size = 3, value = "third"), + FOURTH(size = 4, value = "fourth"), + FIFTH(size = 5, value = "fifth"), + SIXTH(size = 6, value = "sixth"), + SEVENTH(size = 7, value = "seventh"), + EIGHTH(size = 8, value = "eighth"), + NINTH(size = 9, value = "ninth"), + TENTH(size = 10, value = "tenth"), + ELEVENTH(size = 11, value = "eleventh"), + } + + + private fun checkCauses(e: Throwable, pred: (String?) -> Boolean): Boolean { + if (pred(e.message)) return true + if (e.cause == null) return false + return checkCauses(e.cause!!, pred) + } + + private fun handleIllegalArgumentException(e: IllegalArgumentException, bytes: ByteArray) { + if (e.message != null && + (e.message == "Cannot read polymorphic value before its type token" || + e.message!!.startsWith("Polymorphic value has not been read for class")) || + e.message!!.matches(Regex("startIndex: .+ > endIndex: .+")) || + (e.message == "Polymorphic value has not been read for class null")) return + throw e + } + + private fun handleSerializationException(e: SerializationException, bytes: ByteArray) { + if (e.message == null) { + throw e + } + + if (e.message!!.startsWith("Unexpected EOF") || + e.message == "'null' is not allowed for not-null properties" || + e.message == "Input stream is malformed: Varint too long (exceeded 64 bits)" || + e.message == "Input stream is malformed: Varint too long (exceeded 32 bits)" || + e.message!!.matches(Regex(".+ is not allowed as the protobuf field number in .+, the input bytes may have been corrupted")) || + checkCauses(e) { s -> s == "Unsupported start group or end group wire type: INVALID(-1)" } || + checkCauses(e) { s -> s != null && s.matches(Regex("Expected wire type .+, but found .+")) } || + checkCauses(e) { s -> s != null && s.startsWith("Unexpected negative length:") } || + e.message!!.matches(Regex(".+ is not allowed as the protobuf field number in .+, the input bytes may have been corrupted")) || + e.message!!.matches(Regex("Unexpected .+ value: .+")) || + e.message == "Element 'value' is missing" || + e.message == "Element 'key' is missing" || + e.message == "Field 'value' is required for type with serial name 'org.plan.research.ProtobufMessageInt', but it was missing" || + checkCauses(e) {s -> s != null && s.matches(Regex(".+ is not among valid .+ enum proto numbers")) } + ) return + + if (e.message!!.matches(Regex("""Serializer for subclass .+ is not found in the polymorphic scope of .+""", RegexOption.DOT_MATCHES_ALL))) return + + throw e + } + + private fun KFuzzer.generateProtobufMessage( + clazz: Class, + maxStrLength: Int, + maxDepth: Int + ): ProtobufMessage { + assert(clazz in CLASSES) + val res = ProtobufMessage( + intFieldDefault = intOrNull(), + intFieldFixed = intOrNull(), + intFieldSigned = intOrNull(), + floatField = floatOrNull(), + doubleField = doubleOrNull(), + stringField = asciiStringOrNull(maxStrLength), + booleanField = booleanOrNull(), + enumField = if (boolean()) pick(TestEnum.entries) else null, + nestedMessageField = if (maxDepth > 0 && boolean()) generateProtobufMessage( + clazz, + maxStrLength, + maxDepth - 1, + ) else null, + oneOfField = if (boolean()) if (boolean()) FirstOption(int()) else SecondOption(double()) else null, + listField = generateProtobufList(clazz, maxStrLength, maxDepth - 1), + packedListField = generateProtobufList(clazz, maxStrLength, maxDepth - 1), + mapField = generateProtobufMap(clazz, maxStrLength, maxDepth - 1), + packedMapField = generateProtobufMap(clazz, maxStrLength, maxDepth - 1), + ) + + if (boolean()) res.longField = long() + + return res + } + + @Serializable + data class ListInt(val value: List) + + @Serializable + data class MapStringInt(val value: Map) + + @Serializable + data class ProtobufMessageInt(val value: ProtobufMessage) + + private val CLASSES = listOf( + Int::class.java, + Long::class.java, + Float::class.java, + Double::class.java, + String::class.java, + Boolean::class.java, + TestEnum::class.java, + OneOfType::class.java, + ListInt::class.java, + MapStringInt::class.java, + ProtobufMessageInt::class.java + ) + + private fun KFuzzer.generateProtobufMap( + clazz: Class, + maxStrLength: Int, + maxDepth: Int + ): Map = + when (clazz) { + Int::class.java -> buildMap { + repeat(int(0..maxStrLength)) { + put(string(maxStrLength), int() as T) + } + } + + Long::class.java -> buildMap { + repeat(int(0..maxStrLength)) { + put(string(maxStrLength), long() as T) + } + } + + Float::class.java -> buildMap { + repeat(int(0..maxStrLength)) { + put(string(maxStrLength), float() as T) + } + } + + Double::class.java -> buildMap { + repeat(int(0..maxStrLength)) { + put(string(maxStrLength), double() as T) + } + } + + String::class.java -> buildMap { + repeat(int(0..maxStrLength)) { + put(string(maxStrLength), asciiString(maxStrLength) as T) + } + } + + Boolean::class.java -> buildMap { + repeat(int(0..maxStrLength)) { + put(string(maxStrLength), boolean() as T) + } + } + + TestEnum::class.java -> buildMap { + repeat(int(0..maxStrLength)) { + put( + string(maxStrLength), + TestEnum.entries[int(0..TestEnum.entries.lastIndex)] as T + ) + } + } + + OneOfType::class.java -> buildMap { + repeat(int(0..maxStrLength)) { + put( + string(maxStrLength), + (if (boolean()) FirstOption(int()) else SecondOption(double())) as T + ) + } + } + + ListInt::class.java -> if (maxDepth > 0) { + buildMap { + repeat(int(0..maxStrLength)) { + put( + string(maxStrLength), ListInt( + generateProtobufList( + Int::class.java, + maxStrLength, + maxDepth - 1 + ) + ) as T + ) + } + } + } else emptyMap() + + MapStringInt::class.java -> if (maxDepth > 0) { + buildMap { + repeat(int(0..maxStrLength)) { + put( + string(maxStrLength), MapStringInt( + generateProtobufMap( + Int::class.java, + maxStrLength, + maxDepth - 1 + ) + ) as T + ) + } + } + } else emptyMap() + + ProtobufMessageInt::class.java -> if (maxDepth > 0) { + buildMap { + repeat(int(0..maxStrLength)) { + put( + string(maxStrLength), ProtobufMessageInt( + generateProtobufMessage( + Int::class.java, + maxStrLength, + maxDepth - 1 + ) + ) as T + ) + } + } + } else emptyMap() + + else -> error("Unexpected") + } + + private fun KFuzzer.generateProtobufList( + clazz: Class, + maxStrLength: Int, + maxDepth: Int + ): List = + when (clazz) { + Int::class.java -> MutableList(int(0..maxStrLength)) { int() as T } + Long::class.java -> MutableList(int(0..maxStrLength)) { long() as T } + Float::class.java -> MutableList(int(0..maxStrLength)) { float() as T } + Double::class.java -> MutableList(int(0..maxStrLength)) { double() as T } + String::class.java -> MutableList(int(0..maxStrLength)) { asciiString(maxStrLength) as T } + Boolean::class.java -> MutableList(int(0..maxStrLength)) { boolean() as T } + TestEnum::class.java -> MutableList(int(0..maxStrLength)) { + TestEnum.entries[int(0..TestEnum.entries.lastIndex)] as T + } + + OneOfType::class.java -> MutableList(int(0..maxStrLength)) { + (if (boolean()) FirstOption(int()) else SecondOption(double())) as T + } + + ListInt::class.java -> if (maxDepth > 0) { + MutableList(int(0..maxStrLength)) { + ListInt( + generateProtobufList( + Int::class.java, + maxStrLength, + maxDepth - 1, + ) + ) as T + } + } else emptyList() + + MapStringInt::class.java -> if (maxDepth > 0) { + MutableList(int(0..maxStrLength)) { + MapStringInt( + generateProtobufMap( + Int::class.java, + maxStrLength, + maxDepth - 1 + ) + ) as T + } + } else emptyList() + + ProtobufMessageInt::class.java -> if (maxDepth > 0) { + MutableList(int(0..maxStrLength)) { + ProtobufMessageInt( + generateProtobufMessage( + Int::class.java, + maxStrLength, + maxDepth - 1 + ) + ) as T + } + } else emptyList() + + else -> error("Unexpected") + } + + + @Serializable + @OptIn(ExperimentalSerializationApi::class) + data class ProtobufMessage( + @ProtoType(ProtoIntegerType.DEFAULT) + val intFieldDefault: Int?, + @ProtoType(ProtoIntegerType.FIXED) + val intFieldFixed: Int?, + @ProtoType(ProtoIntegerType.SIGNED) + val intFieldSigned: Int?, + var longField: Long = 5, + val floatField: Float?, + val doubleField: Double?, + val stringField: String?, + val booleanField: Boolean?, + val listField: List = emptyList(), + @ProtoPacked val packedListField: List = emptyList(), + val mapField: Map = emptyMap(), + @ProtoPacked val packedMapField: Map = emptyMap(), + val nestedMessageField: ProtobufMessage?, + val enumField: TestEnum?, + @ProtoOneOf val oneOfField: OneOfType?, + ) + + @Serializable + sealed interface OneOfType + + @OptIn(ExperimentalSerializationApi::class) + @Serializable + @JvmInline + value class FirstOption(@ProtoNumber(1000) val valueInt: Int) : OneOfType + + @OptIn(ExperimentalSerializationApi::class) + @Serializable + @JvmInline + value class SecondOption(@ProtoNumber(1001) val valueDouble: Double) : OneOfType + + @OptIn(ExperimentalSerializationApi::class) + @KFuzzTest + fun protoBufDecodeFromByteArray(data: KFuzzer) { + val serializer = ProtoBuf { encodeDefaults = data.boolean() } + when (CLASSES[data.int(0..CLASSES.lastIndex)]) { + Int::class.java -> { + val bytes = data.remainingAsByteArray() + if (bytes.isEmpty()) return + var message: ProtobufMessage? = null + try { + message = serializer.decodeFromByteArray>(bytes) + if (bytes.size > 10 && serializer.encodeToByteArray(message).size > 10) assertEquals(bytes, serializer.encodeToByteArray(message)) + } catch (e: SerializationException) { + handleSerializationException(e, bytes) + } catch (e: IllegalArgumentException) { + handleIllegalArgumentException(e, bytes) + } catch (_: IndexOutOfBoundsException) {} catch(_: MissingFieldException) { + } catch (e: AssertionError) { + throw e + } + } + + Long::class.java -> { + val bytes = data.remainingAsByteArray() + if (bytes.isEmpty()) return + var message: ProtobufMessage? = null + try { + message = serializer.decodeFromByteArray>(bytes) + if (bytes.size > 10 && serializer.encodeToByteArray(message).size > 10) assertEquals(bytes, serializer.encodeToByteArray(message)) + } catch (e: SerializationException) { + handleSerializationException(e, bytes) + } catch (e: IllegalArgumentException) { + handleIllegalArgumentException(e, bytes) + } catch (_: IndexOutOfBoundsException) {} catch(_: MissingFieldException) { + } catch (e: AssertionError) { + throw e + } + } + + Float::class.java -> { + val bytes = data.remainingAsByteArray() + if (bytes.isEmpty()) return + var message: ProtobufMessage? = null + try { + message = serializer.decodeFromByteArray>(bytes) + if (bytes.size > 10 && serializer.encodeToByteArray(message).size > 10) assertEquals(bytes, serializer.encodeToByteArray(message)) + } catch (e: SerializationException) { + handleSerializationException(e, bytes) + } catch (e: IllegalArgumentException) { + handleIllegalArgumentException(e, bytes) + } catch (_: IndexOutOfBoundsException) {} catch(_: MissingFieldException) { + } catch (e: AssertionError) { + throw e + } + } + + Double::class.java -> { + val bytes = data.remainingAsByteArray() + if (bytes.isEmpty()) return + var message: ProtobufMessage? = null + try { + message = serializer.decodeFromByteArray>(bytes) + if (bytes.size > 10 && serializer.encodeToByteArray(message).size > 10) assertEquals(bytes, serializer.encodeToByteArray(message)) + } catch (e: SerializationException) { + handleSerializationException(e, bytes) + } catch (e: IllegalArgumentException) { + handleIllegalArgumentException(e, bytes) + } catch (_: IndexOutOfBoundsException) {} catch(_: MissingFieldException) { + } catch (e: AssertionError) { + throw e + } + } + + String::class.java -> { + val bytes = data.remainingAsByteArray() + if (bytes.isEmpty()) return + var message: ProtobufMessage? = null + try { + message = serializer.decodeFromByteArray>(bytes) + if (bytes.size > 10 && serializer.encodeToByteArray(message).size > 10) assertEquals(bytes, serializer.encodeToByteArray(message)) + } catch (e: SerializationException) { + handleSerializationException(e, bytes) + } catch (e: IllegalArgumentException) { + handleIllegalArgumentException(e, bytes) + } catch (_: IndexOutOfBoundsException) {} catch(_: MissingFieldException) { + } catch (e: AssertionError) { + throw e + } + } + + Boolean::class.java -> { + val bytes = data.remainingAsByteArray() + if (bytes.isEmpty()) return + var message: ProtobufMessage? = null + try { + message = serializer.decodeFromByteArray>(bytes) + if (bytes.size > 10 && serializer.encodeToByteArray(message).size > 10) assertEquals(bytes, serializer.encodeToByteArray(message)) + } catch (e: SerializationException) { + handleSerializationException(e, bytes) + } catch (e: IllegalArgumentException) { + handleIllegalArgumentException(e, bytes) + } catch (_: IndexOutOfBoundsException) {} catch(_: MissingFieldException) { + } catch (e: AssertionError) { + throw e + } + } + + TestEnum::class.java -> { + val bytes = data.remainingAsByteArray() + if (bytes.isEmpty()) return + var message: ProtobufMessage? = null + try { + message = serializer.decodeFromByteArray>(bytes) + if (bytes.size > 10 && serializer.encodeToByteArray(message).size > 10) assertEquals(bytes, serializer.encodeToByteArray(message)) + } catch (e: SerializationException) { + handleSerializationException(e, bytes) + } catch (e: IllegalArgumentException) { + handleIllegalArgumentException(e, bytes) + } catch (_: IndexOutOfBoundsException) {} catch(_: MissingFieldException) { + } catch (e: AssertionError) { + throw e + } + } + + OneOfType::class.java -> { + val bytes = data.remainingAsByteArray() + if (bytes.isEmpty()) return + var message: ProtobufMessage? = null + try { + message = serializer.decodeFromByteArray>(bytes) + if (bytes.size > 10 && serializer.encodeToByteArray(message).size > 10) assertEquals(bytes, serializer.encodeToByteArray(message)) + } catch (e: SerializationException) { + handleSerializationException(e, bytes) + } catch (e: IllegalArgumentException) { + handleIllegalArgumentException(e, bytes) + } catch (_: IndexOutOfBoundsException) {} catch(_: MissingFieldException) { + } catch (e: AssertionError) { + throw e + } + } + + ListInt::class.java -> { + val bytes = data.remainingAsByteArray() + if (bytes.isEmpty()) return + var message: ProtobufMessage? = null + try { + message = serializer.decodeFromByteArray>(bytes) + if (bytes.size > 10 && serializer.encodeToByteArray(message).size > 10) assertEquals(bytes, serializer.encodeToByteArray(message)) + } catch (e: SerializationException) { + handleSerializationException(e, bytes) + } catch (e: IllegalArgumentException) { + handleIllegalArgumentException(e, bytes) + } catch (_: IndexOutOfBoundsException) {} catch(_: MissingFieldException) { + } catch (e: AssertionError) { + throw e + } + } + + MapStringInt::class.java -> { + val bytes = data.remainingAsByteArray() + if (bytes.isEmpty()) return + var message: ProtobufMessage? = null + try { + message = serializer.decodeFromByteArray>(bytes) + if (bytes.size > 10 && serializer.encodeToByteArray(message).size > 10) assertEquals(bytes, serializer.encodeToByteArray(message)) + } catch (e: SerializationException) { + handleSerializationException(e, bytes) + } catch (e: IllegalArgumentException) { + handleIllegalArgumentException(e, bytes) + } catch (_: IndexOutOfBoundsException) {} catch(_: MissingFieldException) { + } catch (e: AssertionError) { + throw e + } + } + + ProtobufMessageInt::class.java -> { + val bytes = data.remainingAsByteArray() + if (bytes.isEmpty()) return + var message: ProtobufMessage? = null + try { + message = serializer.decodeFromByteArray>(bytes) + if (bytes.size > 10 && serializer.encodeToByteArray(message).size > 10) assertEquals(bytes, serializer.encodeToByteArray(message)) + } catch (e: SerializationException) { + handleSerializationException(e, bytes) + } catch (e: IllegalArgumentException) { + handleIllegalArgumentException(e, bytes) + } catch (_: IndexOutOfBoundsException) {} catch(_: MissingFieldException) { + } catch (e: AssertionError) { + throw e + } + } + } + } +} \ No newline at end of file From f194c507a4edea05c3057139be15b648c146f3f8 Mon Sep 17 00:00:00 2001 From: FerrumBrain Date: Wed, 29 Jan 2025 10:38:49 +0100 Subject: [PATCH 02/20] Added modes, config parameters, changed ProtobufTests --- .../main/kotlin/kotlinx/fuzz/KFuzzConfig.kt | 23 ++++ .../kotlinx/fuzz/jazzer/JazzerEngine.kt | 52 ++++++-- kotlinx.fuzz.test/build.gradle.kts | 3 + .../kotlin/kotlinx/fuzz/test/ProtobufTests.kt | 123 ++++++++++-------- 4 files changed, 139 insertions(+), 62 deletions(-) diff --git a/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt b/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt index 69b89a1c..41324d49 100644 --- a/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt +++ b/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt @@ -1,5 +1,8 @@ package kotlinx.fuzz +import java.nio.file.Path +import java.nio.file.Paths +import kotlin.io.path.absolutePathString import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty import kotlin.reflect.KProperty1 @@ -19,6 +22,8 @@ import kotlin.time.Duration.Companion.seconds * (custom and built-in). * Default: empty list * @param maxSingleTargetFuzzTime - max time to fuzz a single target in seconds + * @param runMode - Regression or fuzzing. Default: regression_fuzzing + * @param reproducerPath - Path to store reproducers. Default: . */ interface KFuzzConfig { val fuzzEngine: String @@ -27,6 +32,8 @@ interface KFuzzConfig { val instrument: List val customHookExcludes: List val maxSingleTargetFuzzTime: Duration + val runMode: RunMode + val reproducerPath: Path fun toPropertiesMap(): Map @@ -72,6 +79,18 @@ class KFuzzConfigImpl private constructor() : KFuzzConfig { toString = { it.inWholeSeconds.toString() }, fromString = { it.toInt().seconds }, ) + override var runMode: RunMode by KFuzzConfigProperty( + "kotlinx.fuzz.runMode", + defaultValue = RunMode.REGRESSION, + toString = { it.toString() }, + fromString = { RunMode.valueOf(it.uppercase()) }, + ) + override var reproducerPath: Path by KFuzzConfigProperty( + "kotlinx.fuzz.reproducerPath", + defaultValue = Paths.get("."), + toString = { it.absolutePathString() }, + fromString = { Paths.get(it) }, + ) override fun toPropertiesMap(): Map = configProperties() .associate { it.systemProperty to it.stringValue } @@ -99,6 +118,10 @@ class KFuzzConfigImpl private constructor() : KFuzzConfig { } } +enum class RunMode { + FUZZING, REGRESSION, REGRESSION_FUZZING +} + /** * A delegate property class that manages a configuration option for KFuzz. * diff --git a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerEngine.kt b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerEngine.kt index 0fd4e05b..4e70fc6a 100644 --- a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerEngine.kt +++ b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerEngine.kt @@ -8,12 +8,14 @@ import com.code_intelligence.jazzer.driver.Opt import com.code_intelligence.jazzer.utils.Log import java.lang.invoke.MethodHandles import java.lang.reflect.Method +import java.nio.file.Path +import java.nio.file.Paths import java.util.concurrent.atomic.AtomicReference +import kotlin.io.path.* import kotlin.reflect.jvm.javaMethod import kotlinx.fuzz.KFuzzConfig import kotlinx.fuzz.KFuzzEngine -import java.nio.file.Paths -import kotlin.io.path.* +import kotlinx.fuzz.RunMode @Suppress("unused") class JazzerEngine(private val config: KFuzzConfig) : KFuzzEngine { @@ -26,6 +28,7 @@ class JazzerEngine(private val config: KFuzzConfig) : KFuzzEngine { Opt.instrumentationIncludes.setIfDefault(config.instrument) Opt.customHookIncludes.setIfDefault(config.instrument) Opt.customHookExcludes.setIfDefault(config.customHookExcludes) + Opt.reproducerPath.setIfDefault(config.reproducerPath.absolutePathString()) AgentInstaller.install(Opt.hooks.get()) @@ -35,21 +38,50 @@ class JazzerEngine(private val config: KFuzzConfig) : KFuzzEngine { ) } - @OptIn(ExperimentalPathApi::class) - override fun runTarget(instance: Any, method: Method): Throwable? { + private fun configure(reproducerPath: Path): Pair, Path> { val libFuzzerArgs = mutableListOf("fake_argv0") val corpusDir = createTempDirectory("jazzer-corpus") - val reproducerPath = Paths.get(Opt.reproducerPath.get(), method.declaringClass.name, method.name).toAbsolutePath() + libFuzzerArgs += corpusDir.toString() + libFuzzerArgs += "-rss_limit_mb=${jazzerConfig.libFuzzerRssLimit}" + libFuzzerArgs += "-artifact_prefix=${reproducerPath.toAbsolutePath()}/" + + Opt.keepGoing.setIfDefault( + when (config.runMode) { + RunMode.REGRESSION -> { + libFuzzerArgs += "${reproducerPath.toAbsolutePath()}" + libFuzzerArgs += "-runs=${reproducerPath.listDirectoryEntries("crash-*").size}" + + reproducerPath.listDirectoryEntries("crash-*").size.toLong() + } + + RunMode.REGRESSION_FUZZING -> { + libFuzzerArgs += "${reproducerPath.toAbsolutePath()}" + libFuzzerArgs += "-max_total_time=${config.maxSingleTargetFuzzTime.inWholeSeconds}" + + reproducerPath.listDirectoryEntries("crash-*").size + config.keepGoing.toLong() + } + + RunMode.FUZZING -> { + libFuzzerArgs += "-max_total_time=${config.maxSingleTargetFuzzTime.inWholeSeconds}" + + config.keepGoing.toLong() + } + }, + ) + + return libFuzzerArgs to reproducerPath + } + + @OptIn(ExperimentalPathApi::class) + override fun runTarget(instance: Any, method: Method): Throwable? { + val reproducerPath = + Paths.get(Opt.reproducerPath.get(), method.declaringClass.simpleName, method.name).absolute() if (!reproducerPath.exists()) { reproducerPath.createDirectories() } - libFuzzerArgs += corpusDir.toString() - libFuzzerArgs += "-max_total_time=${config.maxSingleTargetFuzzTime.inWholeSeconds}" - libFuzzerArgs += "-rss_limit_mb=${jazzerConfig.libFuzzerRssLimit}" - libFuzzerArgs += "-artifact_prefix=${reproducerPath.toAbsolutePath()}/" - libFuzzerArgs += "${reproducerPath.toAbsolutePath()}" + val (libFuzzerArgs, corpusDir) = configure(reproducerPath) val atomicFinding = AtomicReference() FuzzTargetRunner.registerFatalFindingHandlerForJUnit { finding -> diff --git a/kotlinx.fuzz.test/build.gradle.kts b/kotlinx.fuzz.test/build.gradle.kts index f1013515..1b67033b 100644 --- a/kotlinx.fuzz.test/build.gradle.kts +++ b/kotlinx.fuzz.test/build.gradle.kts @@ -1,3 +1,4 @@ +import kotlinx.fuzz.RunMode import kotlinx.fuzz.gradle.fuzzConfig import kotlin.time.Duration.Companion.seconds @@ -18,6 +19,8 @@ dependencies { } fuzzConfig { + keepGoing = 10 + runMode = RunMode.REGRESSION instrument = listOf("kotlinx.fuzz.test.**") maxSingleTargetFuzzTime = 3600.seconds } diff --git a/kotlinx.fuzz.test/src/test/kotlin/kotlinx/fuzz/test/ProtobufTests.kt b/kotlinx.fuzz.test/src/test/kotlin/kotlinx/fuzz/test/ProtobufTests.kt index acb07e14..75173692 100644 --- a/kotlinx.fuzz.test/src/test/kotlin/kotlinx/fuzz/test/ProtobufTests.kt +++ b/kotlinx.fuzz.test/src/test/kotlin/kotlinx/fuzz/test/ProtobufTests.kt @@ -1,4 +1,5 @@ @file:Suppress("UNCHECKED_CAST", "VARIABLE_WITH_REDUNDANT_INITIALIZER") + package kotlinx.fuzz.test import kotlinx.fuzz.* @@ -20,7 +21,7 @@ object ProtobufTests { TENTH(size = 10, value = "tenth"), ELEVENTH(size = 11, value = "eleventh"), } - + private fun checkCauses(e: Throwable, pred: (String?) -> Boolean): Boolean { if (pred(e.message)) return true @@ -28,16 +29,17 @@ object ProtobufTests { return checkCauses(e.cause!!, pred) } - private fun handleIllegalArgumentException(e: IllegalArgumentException, bytes: ByteArray) { + private fun handleIllegalArgumentException(e: IllegalArgumentException) { if (e.message != null && (e.message == "Cannot read polymorphic value before its type token" || e.message!!.startsWith("Polymorphic value has not been read for class")) || e.message!!.matches(Regex("startIndex: .+ > endIndex: .+")) || - (e.message == "Polymorphic value has not been read for class null")) return + (e.message == "Polymorphic value has not been read for class null") + ) return throw e } - private fun handleSerializationException(e: SerializationException, bytes: ByteArray) { + private fun handleSerializationException(e: SerializationException) { if (e.message == null) { throw e } @@ -55,14 +57,20 @@ object ProtobufTests { e.message == "Element 'value' is missing" || e.message == "Element 'key' is missing" || e.message == "Field 'value' is required for type with serial name 'org.plan.research.ProtobufMessageInt', but it was missing" || - checkCauses(e) {s -> s != null && s.matches(Regex(".+ is not among valid .+ enum proto numbers")) } + checkCauses(e) { s -> s != null && s.matches(Regex(".+ is not among valid .+ enum proto numbers")) } ) return - if (e.message!!.matches(Regex("""Serializer for subclass .+ is not found in the polymorphic scope of .+""", RegexOption.DOT_MATCHES_ALL))) return + if (e.message!!.matches( + Regex( + """Serializer for subclass .+ is not found in the polymorphic scope of .+""", + RegexOption.DOT_MATCHES_ALL + ) + ) + ) return throw e } - + private fun KFuzzer.generateProtobufMessage( clazz: Class, maxStrLength: Int, @@ -94,7 +102,7 @@ object ProtobufTests { return res } - + @Serializable data class ListInt(val value: List) @@ -336,12 +344,13 @@ object ProtobufTests { var message: ProtobufMessage? = null try { message = serializer.decodeFromByteArray>(bytes) - if (bytes.size > 10 && serializer.encodeToByteArray(message).size > 10) assertEquals(bytes, serializer.encodeToByteArray(message)) + assertEquals(bytes, serializer.encodeToByteArray(message)) } catch (e: SerializationException) { - handleSerializationException(e, bytes) + handleSerializationException(e) } catch (e: IllegalArgumentException) { - handleIllegalArgumentException(e, bytes) - } catch (_: IndexOutOfBoundsException) {} catch(_: MissingFieldException) { + handleIllegalArgumentException(e) + } catch (_: IndexOutOfBoundsException) { + } catch (_: MissingFieldException) { } catch (e: AssertionError) { throw e } @@ -353,12 +362,13 @@ object ProtobufTests { var message: ProtobufMessage? = null try { message = serializer.decodeFromByteArray>(bytes) - if (bytes.size > 10 && serializer.encodeToByteArray(message).size > 10) assertEquals(bytes, serializer.encodeToByteArray(message)) + assertEquals(bytes, serializer.encodeToByteArray(message)) } catch (e: SerializationException) { - handleSerializationException(e, bytes) + handleSerializationException(e) } catch (e: IllegalArgumentException) { - handleIllegalArgumentException(e, bytes) - } catch (_: IndexOutOfBoundsException) {} catch(_: MissingFieldException) { + handleIllegalArgumentException(e) + } catch (_: IndexOutOfBoundsException) { + } catch (_: MissingFieldException) { } catch (e: AssertionError) { throw e } @@ -370,12 +380,13 @@ object ProtobufTests { var message: ProtobufMessage? = null try { message = serializer.decodeFromByteArray>(bytes) - if (bytes.size > 10 && serializer.encodeToByteArray(message).size > 10) assertEquals(bytes, serializer.encodeToByteArray(message)) + assertEquals(bytes, serializer.encodeToByteArray(message)) } catch (e: SerializationException) { - handleSerializationException(e, bytes) + handleSerializationException(e) } catch (e: IllegalArgumentException) { - handleIllegalArgumentException(e, bytes) - } catch (_: IndexOutOfBoundsException) {} catch(_: MissingFieldException) { + handleIllegalArgumentException(e) + } catch (_: IndexOutOfBoundsException) { + } catch (_: MissingFieldException) { } catch (e: AssertionError) { throw e } @@ -387,12 +398,13 @@ object ProtobufTests { var message: ProtobufMessage? = null try { message = serializer.decodeFromByteArray>(bytes) - if (bytes.size > 10 && serializer.encodeToByteArray(message).size > 10) assertEquals(bytes, serializer.encodeToByteArray(message)) + assertEquals(bytes, serializer.encodeToByteArray(message)) } catch (e: SerializationException) { - handleSerializationException(e, bytes) + handleSerializationException(e) } catch (e: IllegalArgumentException) { - handleIllegalArgumentException(e, bytes) - } catch (_: IndexOutOfBoundsException) {} catch(_: MissingFieldException) { + handleIllegalArgumentException(e) + } catch (_: IndexOutOfBoundsException) { + } catch (_: MissingFieldException) { } catch (e: AssertionError) { throw e } @@ -404,12 +416,13 @@ object ProtobufTests { var message: ProtobufMessage? = null try { message = serializer.decodeFromByteArray>(bytes) - if (bytes.size > 10 && serializer.encodeToByteArray(message).size > 10) assertEquals(bytes, serializer.encodeToByteArray(message)) + assertEquals(bytes, serializer.encodeToByteArray(message)) } catch (e: SerializationException) { - handleSerializationException(e, bytes) + handleSerializationException(e) } catch (e: IllegalArgumentException) { - handleIllegalArgumentException(e, bytes) - } catch (_: IndexOutOfBoundsException) {} catch(_: MissingFieldException) { + handleIllegalArgumentException(e) + } catch (_: IndexOutOfBoundsException) { + } catch (_: MissingFieldException) { } catch (e: AssertionError) { throw e } @@ -421,12 +434,13 @@ object ProtobufTests { var message: ProtobufMessage? = null try { message = serializer.decodeFromByteArray>(bytes) - if (bytes.size > 10 && serializer.encodeToByteArray(message).size > 10) assertEquals(bytes, serializer.encodeToByteArray(message)) + assertEquals(bytes, serializer.encodeToByteArray(message)) } catch (e: SerializationException) { - handleSerializationException(e, bytes) + handleSerializationException(e) } catch (e: IllegalArgumentException) { - handleIllegalArgumentException(e, bytes) - } catch (_: IndexOutOfBoundsException) {} catch(_: MissingFieldException) { + handleIllegalArgumentException(e) + } catch (_: IndexOutOfBoundsException) { + } catch (_: MissingFieldException) { } catch (e: AssertionError) { throw e } @@ -438,12 +452,13 @@ object ProtobufTests { var message: ProtobufMessage? = null try { message = serializer.decodeFromByteArray>(bytes) - if (bytes.size > 10 && serializer.encodeToByteArray(message).size > 10) assertEquals(bytes, serializer.encodeToByteArray(message)) + assertEquals(bytes, serializer.encodeToByteArray(message)) } catch (e: SerializationException) { - handleSerializationException(e, bytes) + handleSerializationException(e) } catch (e: IllegalArgumentException) { - handleIllegalArgumentException(e, bytes) - } catch (_: IndexOutOfBoundsException) {} catch(_: MissingFieldException) { + handleIllegalArgumentException(e) + } catch (_: IndexOutOfBoundsException) { + } catch (_: MissingFieldException) { } catch (e: AssertionError) { throw e } @@ -455,12 +470,13 @@ object ProtobufTests { var message: ProtobufMessage? = null try { message = serializer.decodeFromByteArray>(bytes) - if (bytes.size > 10 && serializer.encodeToByteArray(message).size > 10) assertEquals(bytes, serializer.encodeToByteArray(message)) + assertEquals(bytes, serializer.encodeToByteArray(message)) } catch (e: SerializationException) { - handleSerializationException(e, bytes) + handleSerializationException(e) } catch (e: IllegalArgumentException) { - handleIllegalArgumentException(e, bytes) - } catch (_: IndexOutOfBoundsException) {} catch(_: MissingFieldException) { + handleIllegalArgumentException(e) + } catch (_: IndexOutOfBoundsException) { + } catch (_: MissingFieldException) { } catch (e: AssertionError) { throw e } @@ -472,12 +488,13 @@ object ProtobufTests { var message: ProtobufMessage? = null try { message = serializer.decodeFromByteArray>(bytes) - if (bytes.size > 10 && serializer.encodeToByteArray(message).size > 10) assertEquals(bytes, serializer.encodeToByteArray(message)) + assertEquals(bytes, serializer.encodeToByteArray(message)) } catch (e: SerializationException) { - handleSerializationException(e, bytes) + handleSerializationException(e) } catch (e: IllegalArgumentException) { - handleIllegalArgumentException(e, bytes) - } catch (_: IndexOutOfBoundsException) {} catch(_: MissingFieldException) { + handleIllegalArgumentException(e) + } catch (_: IndexOutOfBoundsException) { + } catch (_: MissingFieldException) { } catch (e: AssertionError) { throw e } @@ -489,12 +506,13 @@ object ProtobufTests { var message: ProtobufMessage? = null try { message = serializer.decodeFromByteArray>(bytes) - if (bytes.size > 10 && serializer.encodeToByteArray(message).size > 10) assertEquals(bytes, serializer.encodeToByteArray(message)) + assertEquals(bytes, serializer.encodeToByteArray(message)) } catch (e: SerializationException) { - handleSerializationException(e, bytes) + handleSerializationException(e) } catch (e: IllegalArgumentException) { - handleIllegalArgumentException(e, bytes) - } catch (_: IndexOutOfBoundsException) {} catch(_: MissingFieldException) { + handleIllegalArgumentException(e) + } catch (_: IndexOutOfBoundsException) { + } catch (_: MissingFieldException) { } catch (e: AssertionError) { throw e } @@ -506,12 +524,13 @@ object ProtobufTests { var message: ProtobufMessage? = null try { message = serializer.decodeFromByteArray>(bytes) - if (bytes.size > 10 && serializer.encodeToByteArray(message).size > 10) assertEquals(bytes, serializer.encodeToByteArray(message)) + assertEquals(bytes, serializer.encodeToByteArray(message)) } catch (e: SerializationException) { - handleSerializationException(e, bytes) + handleSerializationException(e) } catch (e: IllegalArgumentException) { - handleIllegalArgumentException(e, bytes) - } catch (_: IndexOutOfBoundsException) {} catch(_: MissingFieldException) { + handleIllegalArgumentException(e) + } catch (_: IndexOutOfBoundsException) { + } catch (_: MissingFieldException) { } catch (e: AssertionError) { throw e } From 5aa7804e3d8df60970a1c9cff6f273c8b65bf4a6 Mon Sep 17 00:00:00 2001 From: FerrumBrain Date: Wed, 29 Jan 2025 11:05:25 +0100 Subject: [PATCH 03/20] Fixed default value and fixed tests --- .../main/kotlin/kotlinx/fuzz/KFuzzConfig.kt | 2 +- .../EngineTest.kt | 20 +++++++++++++++---- .../kotlinx/fuzz/jazzer/JazzerEngine.kt | 2 +- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt b/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt index 41324d49..77f30712 100644 --- a/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt +++ b/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt @@ -81,7 +81,7 @@ class KFuzzConfigImpl private constructor() : KFuzzConfig { ) override var runMode: RunMode by KFuzzConfigProperty( "kotlinx.fuzz.runMode", - defaultValue = RunMode.REGRESSION, + defaultValue = RunMode.REGRESSION_FUZZING, toString = { it.toString() }, fromString = { RunMode.valueOf(it.uppercase()) }, ) diff --git a/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx.fuzz.gradle.junit.test/EngineTest.kt b/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx.fuzz.gradle.junit.test/EngineTest.kt index 0c734419..64b7e361 100644 --- a/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx.fuzz.gradle.junit.test/EngineTest.kt +++ b/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx.fuzz.gradle.junit.test/EngineTest.kt @@ -11,14 +11,16 @@ import org.junit.platform.engine.discovery.DiscoverySelectors.selectClass import org.junit.platform.testkit.engine.EngineTestKit object EngineTest { - object SimpleFuzzTest { + object SimpleFailureFuzzTest { @KFuzzTest fun `failure test`(data: KFuzzer) { if (data.boolean()) { error("Expected failure") } } + } + object SimpleSuccessFuzzTest { @KFuzzTest fun `success test`(@Suppress("UNUSED_PARAMETER") data: KFuzzer) { } @@ -33,13 +35,23 @@ object EngineTest { } @Test - fun `one pass one fail`() { + fun `one pass`() { + EngineTestKit + .engine(KotlinxFuzzJunitEngine()) + .selectors(selectClass(SimpleSuccessFuzzTest::class.java)) + .execute() + .testEvents() + .assertStatistics { it.started(1).succeeded(1).failed(0) } + } + + @Test + fun `one fail`() { EngineTestKit .engine(KotlinxFuzzJunitEngine()) - .selectors(selectClass(SimpleFuzzTest::class.java)) + .selectors(selectClass(SimpleFailureFuzzTest::class.java)) .execute() .testEvents() - .assertStatistics { it.started(2).succeeded(1).failed(1) } + .assertStatistics { it.started(1).succeeded(0).failed(1) } } } diff --git a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerEngine.kt b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerEngine.kt index 4e70fc6a..f7de5d6f 100644 --- a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerEngine.kt +++ b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerEngine.kt @@ -70,7 +70,7 @@ class JazzerEngine(private val config: KFuzzConfig) : KFuzzEngine { }, ) - return libFuzzerArgs to reproducerPath + return libFuzzerArgs to corpusDir } @OptIn(ExperimentalPathApi::class) From 62af120fe7f7f71341d30cf0b36eec979626e881 Mon Sep 17 00:00:00 2001 From: FerrumBrain Date: Wed, 29 Jan 2025 11:15:41 +0100 Subject: [PATCH 04/20] Removed one test --- .../kotlinx.fuzz.gradle.junit.test/EngineTest.kt | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx.fuzz.gradle.junit.test/EngineTest.kt b/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx.fuzz.gradle.junit.test/EngineTest.kt index 64b7e361..eb4fbf8e 100644 --- a/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx.fuzz.gradle.junit.test/EngineTest.kt +++ b/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx.fuzz.gradle.junit.test/EngineTest.kt @@ -3,6 +3,7 @@ package kotlinx.fuzz.gradle.junit.test import kotlin.time.Duration.Companion.seconds import kotlinx.fuzz.KFuzzTest import kotlinx.fuzz.KFuzzer +import kotlinx.fuzz.RunMode import kotlinx.fuzz.gradle.KFuzzConfigBuilder import kotlinx.fuzz.gradle.junit.KotlinxFuzzJunitEngine import org.junit.jupiter.api.BeforeEach @@ -29,21 +30,12 @@ object EngineTest { @BeforeEach fun setup() { writeToSystemProperties { + runMode = RunMode.FUZZING maxSingleTargetFuzzTime = 10.seconds instrument = listOf("kotlinx.fuzz.test.**") } } - @Test - fun `one pass`() { - EngineTestKit - .engine(KotlinxFuzzJunitEngine()) - .selectors(selectClass(SimpleSuccessFuzzTest::class.java)) - .execute() - .testEvents() - .assertStatistics { it.started(1).succeeded(1).failed(0) } - } - @Test fun `one fail`() { EngineTestKit From 1c8c39113667265511c7c547d8d7e29a66df007b Mon Sep 17 00:00:00 2001 From: FerrumBrain Date: Wed, 29 Jan 2025 18:56:45 +0100 Subject: [PATCH 05/20] merged and supports several processes but modes don't work --- .../main/kotlin/kotlinx/fuzz/KFuzzConfig.kt | 4 +- .../kotlin/kotlinx/fuzz/gradle/KFuzzPlugin.kt | 1 + .../EngineTest.kt | 10 ++-- .../kotlinx/fuzz/jazzer/JazzerLauncher.kt | 49 ++++++++++++++++--- .../kotlin/kotlinx/fuzz/test/ProtobufTests.kt | 22 ++++----- 5 files changed, 60 insertions(+), 26 deletions(-) diff --git a/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt b/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt index f1e1818a..bc4c4bcb 100644 --- a/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt +++ b/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt @@ -1,6 +1,7 @@ package kotlinx.fuzz import java.nio.file.Path +import java.nio.file.Paths import kotlin.io.path.* import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty @@ -25,7 +26,7 @@ import kotlin.time.Duration.Companion.seconds * Default: empty list * @param maxSingleTargetFuzzTime - max time to fuzz a single target in seconds * @param runMode - Regression or fuzzing. Default: regression_fuzzing - * @param reproducerPath - Path to store reproducers. Default: . + * @param reproducerPath - Path to store reproducers. Default: `$workDir/reproducers` */ interface KFuzzConfig { val fuzzEngine: String @@ -102,7 +103,6 @@ class KFuzzConfigImpl private constructor() : KFuzzConfig { ) override var reproducerPath: Path by KFuzzConfigProperty( "kotlinx.fuzz.reproducerPath", - defaultValue = Path("."), toString = { it.absolutePathString() }, fromString = { Path(it).absolute() }, ) diff --git a/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/KFuzzPlugin.kt b/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/KFuzzPlugin.kt index e021a717..8192a565 100644 --- a/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/KFuzzPlugin.kt +++ b/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/KFuzzPlugin.kt @@ -59,6 +59,7 @@ fun Project.fuzzConfig(block: KFuzzConfigBuilder.() -> Unit) { val defaultWorkDir = buildDir.dir("fuzz").asFile.toPath() val config = KFuzzConfigBuilder.build { workDir = defaultWorkDir + reproducerPath = defaultWorkDir.resolve("reproducers") block() } diff --git a/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx.fuzz.gradle.junit.test/EngineTest.kt b/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx.fuzz.gradle.junit.test/EngineTest.kt index e8d71c41..9c7dabe4 100644 --- a/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx.fuzz.gradle.junit.test/EngineTest.kt +++ b/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx.fuzz.gradle.junit.test/EngineTest.kt @@ -12,16 +12,14 @@ import org.junit.platform.engine.discovery.DiscoverySelectors.selectClass import org.junit.platform.testkit.engine.EngineTestKit object EngineTest { - object SimpleFailureFuzzTest { + object SimpleFuzzTest { @KFuzzTest fun `failure test`(data: KFuzzer) { if (data.boolean()) { error("Expected failure") } } - } - object SimpleSuccessFuzzTest { @KFuzzTest fun `success test`(@Suppress("UNUSED_PARAMETER", "unused") data: KFuzzer) { } @@ -38,13 +36,13 @@ object EngineTest { } @Test - fun `one fail`() { + fun `one pass one fail`() { EngineTestKit .engine(KotlinxFuzzJunitEngine()) - .selectors(selectClass(SimpleFailureFuzzTest::class.java)) + .selectors(selectClass(SimpleFuzzTest::class.java)) .execute() .testEvents() - .assertStatistics { it.started(1).succeeded(0).failed(1) } + .assertStatistics { it.started(2).succeeded(1).failed(1) } } } diff --git a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt index 02fb1069..6af4c612 100644 --- a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt +++ b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt @@ -11,15 +11,13 @@ import java.lang.invoke.MethodHandles import java.lang.reflect.Method import java.nio.file.Path import java.util.concurrent.atomic.AtomicReference -import kotlin.io.path.ExperimentalPathApi -import kotlin.io.path.absolute -import kotlin.io.path.createDirectories -import kotlin.io.path.outputStream import kotlin.reflect.full.memberFunctions import kotlin.reflect.full.primaryConstructor import kotlin.reflect.jvm.javaMethod import kotlin.system.exitProcess import kotlinx.fuzz.KFuzzConfig +import kotlinx.fuzz.RunMode +import kotlin.io.path.* object JazzerLauncher { private val config = KFuzzConfig.fromSystemProperties() @@ -51,8 +49,13 @@ object JazzerLauncher { exitProcess(0) } - @OptIn(ExperimentalPathApi::class) - fun runTarget(instance: Any, method: Method): Throwable? { + private fun configure(method: Method): List { + val reproducerPath = + Path(Opt.reproducerPath.get(), method.declaringClass.simpleName, method.name).absolute() + if (!reproducerPath.exists()) { + reproducerPath.createDirectories() + } + val libFuzzerArgs = mutableListOf("fake_argv0") val currentCorpus = config.corpusDir.resolve(method.fullName) currentCorpus.createDirectories() @@ -68,8 +71,39 @@ object JazzerLauncher { } libFuzzerArgs += currentCorpus.toString() - libFuzzerArgs += "-max_total_time=${config.maxSingleTargetFuzzTime.inWholeSeconds}" libFuzzerArgs += "-rss_limit_mb=${jazzerConfig.libFuzzerRssLimit}" + libFuzzerArgs += "-artifact_prefix=${reproducerPath.toAbsolutePath()}/" + + Opt.keepGoing.setIfDefault( + when (config.runMode) { + RunMode.REGRESSION -> { + libFuzzerArgs += "${reproducerPath.toAbsolutePath()}" + libFuzzerArgs += "-runs=${reproducerPath.listDirectoryEntries("crash-*").size}" + + reproducerPath.listDirectoryEntries("crash-*").size.toLong() + } + + RunMode.REGRESSION_FUZZING -> { + libFuzzerArgs += "${reproducerPath.toAbsolutePath()}" + libFuzzerArgs += "-max_total_time=${config.maxSingleTargetFuzzTime.inWholeSeconds}" + + reproducerPath.listDirectoryEntries("crash-*").size + config.keepGoing.toLong() + } + + RunMode.FUZZING -> { + libFuzzerArgs += "-max_total_time=${config.maxSingleTargetFuzzTime.inWholeSeconds}" + + config.keepGoing.toLong() + } + }, + ) + + return libFuzzerArgs + } + + @OptIn(ExperimentalPathApi::class) + fun runTarget(instance: Any, method: Method): Throwable? { + val libFuzzerArgs = configure(method) val atomicFinding = AtomicReference() FuzzTargetRunner.registerFatalFindingHandlerForJUnit { finding -> @@ -89,6 +123,7 @@ object JazzerLauncher { Opt.instrumentationIncludes.setIfDefault(config.instrument) Opt.customHookIncludes.setIfDefault(config.instrument) Opt.customHookExcludes.setIfDefault(config.customHookExcludes) + Opt.reproducerPath.setIfDefault(config.reproducerPath.absolutePathString()) AgentInstaller.install(Opt.hooks.get()) diff --git a/kotlinx.fuzz.test/src/test/kotlin/kotlinx/fuzz/test/ProtobufTests.kt b/kotlinx.fuzz.test/src/test/kotlin/kotlinx/fuzz/test/ProtobufTests.kt index 75173692..b6bcaeb3 100644 --- a/kotlinx.fuzz.test/src/test/kotlin/kotlinx/fuzz/test/ProtobufTests.kt +++ b/kotlinx.fuzz.test/src/test/kotlin/kotlinx/fuzz/test/ProtobufTests.kt @@ -344,7 +344,7 @@ object ProtobufTests { var message: ProtobufMessage? = null try { message = serializer.decodeFromByteArray>(bytes) - assertEquals(bytes, serializer.encodeToByteArray(message)) + if (bytes.size > 100 && serializer.encodeToByteArray(message).size > 100) assertEquals(bytes, serializer.encodeToByteArray(message)) } catch (e: SerializationException) { handleSerializationException(e) } catch (e: IllegalArgumentException) { @@ -362,7 +362,7 @@ object ProtobufTests { var message: ProtobufMessage? = null try { message = serializer.decodeFromByteArray>(bytes) - assertEquals(bytes, serializer.encodeToByteArray(message)) + if (bytes.size > 100 && serializer.encodeToByteArray(message).size > 100) assertEquals(bytes, serializer.encodeToByteArray(message)) } catch (e: SerializationException) { handleSerializationException(e) } catch (e: IllegalArgumentException) { @@ -380,7 +380,7 @@ object ProtobufTests { var message: ProtobufMessage? = null try { message = serializer.decodeFromByteArray>(bytes) - assertEquals(bytes, serializer.encodeToByteArray(message)) + if (bytes.size > 100 && serializer.encodeToByteArray(message).size > 100) assertEquals(bytes, serializer.encodeToByteArray(message)) } catch (e: SerializationException) { handleSerializationException(e) } catch (e: IllegalArgumentException) { @@ -398,7 +398,7 @@ object ProtobufTests { var message: ProtobufMessage? = null try { message = serializer.decodeFromByteArray>(bytes) - assertEquals(bytes, serializer.encodeToByteArray(message)) + if (bytes.size > 100 && serializer.encodeToByteArray(message).size > 100) assertEquals(bytes, serializer.encodeToByteArray(message)) } catch (e: SerializationException) { handleSerializationException(e) } catch (e: IllegalArgumentException) { @@ -416,7 +416,7 @@ object ProtobufTests { var message: ProtobufMessage? = null try { message = serializer.decodeFromByteArray>(bytes) - assertEquals(bytes, serializer.encodeToByteArray(message)) + if (bytes.size > 100 && serializer.encodeToByteArray(message).size > 100) assertEquals(bytes, serializer.encodeToByteArray(message)) } catch (e: SerializationException) { handleSerializationException(e) } catch (e: IllegalArgumentException) { @@ -434,7 +434,7 @@ object ProtobufTests { var message: ProtobufMessage? = null try { message = serializer.decodeFromByteArray>(bytes) - assertEquals(bytes, serializer.encodeToByteArray(message)) + if (bytes.size > 100 && serializer.encodeToByteArray(message).size > 100) assertEquals(bytes, serializer.encodeToByteArray(message)) } catch (e: SerializationException) { handleSerializationException(e) } catch (e: IllegalArgumentException) { @@ -452,7 +452,7 @@ object ProtobufTests { var message: ProtobufMessage? = null try { message = serializer.decodeFromByteArray>(bytes) - assertEquals(bytes, serializer.encodeToByteArray(message)) + if (bytes.size > 100 && serializer.encodeToByteArray(message).size > 100) assertEquals(bytes, serializer.encodeToByteArray(message)) } catch (e: SerializationException) { handleSerializationException(e) } catch (e: IllegalArgumentException) { @@ -470,7 +470,7 @@ object ProtobufTests { var message: ProtobufMessage? = null try { message = serializer.decodeFromByteArray>(bytes) - assertEquals(bytes, serializer.encodeToByteArray(message)) + if (bytes.size > 100 && serializer.encodeToByteArray(message).size > 100) assertEquals(bytes, serializer.encodeToByteArray(message)) } catch (e: SerializationException) { handleSerializationException(e) } catch (e: IllegalArgumentException) { @@ -488,7 +488,7 @@ object ProtobufTests { var message: ProtobufMessage? = null try { message = serializer.decodeFromByteArray>(bytes) - assertEquals(bytes, serializer.encodeToByteArray(message)) + if (bytes.size > 100 && serializer.encodeToByteArray(message).size > 100) assertEquals(bytes, serializer.encodeToByteArray(message)) } catch (e: SerializationException) { handleSerializationException(e) } catch (e: IllegalArgumentException) { @@ -506,7 +506,7 @@ object ProtobufTests { var message: ProtobufMessage? = null try { message = serializer.decodeFromByteArray>(bytes) - assertEquals(bytes, serializer.encodeToByteArray(message)) + if (bytes.size > 100 && serializer.encodeToByteArray(message).size > 100) assertEquals(bytes, serializer.encodeToByteArray(message)) } catch (e: SerializationException) { handleSerializationException(e) } catch (e: IllegalArgumentException) { @@ -524,7 +524,7 @@ object ProtobufTests { var message: ProtobufMessage? = null try { message = serializer.decodeFromByteArray>(bytes) - assertEquals(bytes, serializer.encodeToByteArray(message)) + if (bytes.size > 100 && serializer.encodeToByteArray(message).size > 100) assertEquals(bytes, serializer.encodeToByteArray(message)) } catch (e: SerializationException) { handleSerializationException(e) } catch (e: IllegalArgumentException) { From 342cda394961929fed1a31d62fb8b27ceb2720b4 Mon Sep 17 00:00:00 2001 From: FerrumBrain Date: Mon, 3 Feb 2025 22:29:31 +0100 Subject: [PATCH 06/20] resolved conversations and replaced with runOne --- gradle/libs.versions.toml | 8 ++--- .../main/kotlin/kotlinx/fuzz/KFuzzConfig.kt | 6 ++-- .../test/kotlin/org/plan/research/value.kt | 1 - .../EngineTest.kt | 1 + .../fuzz/gradle/test/FuzzConfigBuilderTest.kt | 3 ++ .../kotlinx/fuzz/jazzer/JazzerLauncher.kt | 30 ++++++++++++------- 6 files changed, 31 insertions(+), 18 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c562d89d..c37799f5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] kotlin = "2.0.21" diktat = "2.0.0" -jazzer = "0.22.1" +jazzer = "0.0.1" junit-platform = "1.11.4" junit-jupiter = "5.11.4" @@ -24,9 +24,9 @@ gradle-publish = "1.2.1" kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } diktat-gradle-plugin = { module = "com.saveourtool.diktat:diktat-gradle-plugin", version.ref = "diktat" } -jazzer = { module = "com.code-intelligence:jazzer", version.ref = "jazzer" } -jazzer-junit = { module = "com.code-intelligence:jazzer-junit", version.ref = "jazzer" } -jazzer-api = { module = "com.code-intelligence:jazzer-api", version.ref = "jazzer" } +jazzer = { module = "org.jetbrains:jazzer", version.ref = "jazzer" } +jazzer-junit = { module = "org.jetbrains:jazzer-junit", version.ref = "jazzer" } +jazzer-api = { module = "org.jetbrains:jazzer-api", version.ref = "jazzer" } reflections = { module = "org.reflections:reflections", version.ref = "reflections" } diff --git a/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt b/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt index bc4c4bcb..742cda7b 100644 --- a/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt +++ b/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt @@ -31,7 +31,7 @@ import kotlin.time.Duration.Companion.seconds interface KFuzzConfig { val fuzzEngine: String val hooks: Boolean - val keepGoing: Int + val keepGoing: Long val instrument: List val customHookExcludes: List val maxSingleTargetFuzzTime: Duration @@ -60,12 +60,12 @@ class KFuzzConfigImpl private constructor() : KFuzzConfig { toString = { it.toString() }, fromString = { it.toBooleanStrict() }, ) - override var keepGoing: Int by KFuzzConfigProperty( + override var keepGoing: Long by KFuzzConfigProperty( "kotlinx.fuzz.keepGoing", defaultValue = 1, validate = { require(it > 0) { "'keepGoing' must be positive" } }, toString = { it.toString() }, - fromString = { it.toInt() }, + fromString = { it.toLong() }, ) override var instrument: List by KFuzzConfigProperty( "kotlinx.fuzz.instrument", diff --git a/kotlinx.fuzz.examples/kotlinx.serialization/src/test/kotlin/org/plan/research/value.kt b/kotlinx.fuzz.examples/kotlinx.serialization/src/test/kotlin/org/plan/research/value.kt index 91533ee5..6999fb4c 100644 --- a/kotlinx.fuzz.examples/kotlinx.serialization/src/test/kotlin/org/plan/research/value.kt +++ b/kotlinx.fuzz.examples/kotlinx.serialization/src/test/kotlin/org/plan/research/value.kt @@ -1,7 +1,6 @@ package org.plan.research import com.code_intelligence.jazzer.api.FuzzedDataProvider -import com.code_intelligence.jazzer.third_party.kotlin.reflect.jvm.internal.impl.resolve.constants.ArrayValue import kotlinx.serialization.EncodeDefault import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerialName diff --git a/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx.fuzz.gradle.junit.test/EngineTest.kt b/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx.fuzz.gradle.junit.test/EngineTest.kt index 9c7dabe4..83edc281 100644 --- a/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx.fuzz.gradle.junit.test/EngineTest.kt +++ b/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx.fuzz.gradle.junit.test/EngineTest.kt @@ -32,6 +32,7 @@ object EngineTest { maxSingleTargetFuzzTime = 10.seconds instrument = listOf("kotlinx.fuzz.test.**") workDir = kotlin.io.path.createTempDirectory("fuzz-test") + reproducerPath = workDir.resolve("reproducers") } } diff --git a/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx/fuzz/gradle/test/FuzzConfigBuilderTest.kt b/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx/fuzz/gradle/test/FuzzConfigBuilderTest.kt index 4b37fe35..d49cb7ba 100644 --- a/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx/fuzz/gradle/test/FuzzConfigBuilderTest.kt +++ b/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx/fuzz/gradle/test/FuzzConfigBuilderTest.kt @@ -21,6 +21,7 @@ object FuzzConfigBuilderTest { maxSingleTargetFuzzTime = 0.seconds instrument = emptyList() workDir = Path("test") + reproducerPath = Path("test") } } } @@ -32,6 +33,7 @@ object FuzzConfigBuilderTest { instrument = listOf("1", "2") maxSingleTargetFuzzTime = 30.seconds workDir = Path("test") + reproducerPath = Path("test") } } } @@ -47,6 +49,7 @@ object FuzzConfigBuilderTest { customHookExcludes = listOf("exclude") maxSingleTargetFuzzTime = 1000.seconds workDir = Path("test") + reproducerPath = Path("test") } } } diff --git a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt index 6af4c612..4bd6a858 100644 --- a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt +++ b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt @@ -76,24 +76,18 @@ object JazzerLauncher { Opt.keepGoing.setIfDefault( when (config.runMode) { - RunMode.REGRESSION -> { - libFuzzerArgs += "${reproducerPath.toAbsolutePath()}" - libFuzzerArgs += "-runs=${reproducerPath.listDirectoryEntries("crash-*").size}" - - reproducerPath.listDirectoryEntries("crash-*").size.toLong() - } + RunMode.REGRESSION -> reproducerPath.listDirectoryEntries("crash-*").size.toLong() RunMode.REGRESSION_FUZZING -> { - libFuzzerArgs += "${reproducerPath.toAbsolutePath()}" libFuzzerArgs += "-max_total_time=${config.maxSingleTargetFuzzTime.inWholeSeconds}" - reproducerPath.listDirectoryEntries("crash-*").size + config.keepGoing.toLong() + reproducerPath.listDirectoryEntries("crash-*").size + config.keepGoing } RunMode.FUZZING -> { libFuzzerArgs += "-max_total_time=${config.maxSingleTargetFuzzTime.inWholeSeconds}" - config.keepGoing.toLong() + config.keepGoing } }, ) @@ -111,7 +105,23 @@ object JazzerLauncher { } JazzerTarget.reset(MethodHandles.lookup().unreflect(method), instance) - FuzzTargetRunner.startLibFuzzer(libFuzzerArgs) + + when (config.runMode) { + RunMode.REGRESSION -> { + Path(Opt.reproducerPath.get()).listDirectoryEntries("crash-*").forEach { + FuzzTargetRunner.runOne(it.readBytes()) + } + } + RunMode.REGRESSION_FUZZING -> { + Path(Opt.reproducerPath.get()).listDirectoryEntries("crash-*").forEach { + FuzzTargetRunner.runOne(it.readBytes()) + } + FuzzTargetRunner.startLibFuzzer(libFuzzerArgs) + } + RunMode.FUZZING -> { + FuzzTargetRunner.startLibFuzzer(libFuzzerArgs) + } + } return atomicFinding.get() } From 2d166a08da294639f69b945ee9238e8c578caa41 Mon Sep 17 00:00:00 2001 From: FerrumBrain Date: Mon, 3 Feb 2025 22:29:48 +0100 Subject: [PATCH 07/20] resolved conversations --- kotlinx.fuzz.test/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlinx.fuzz.test/build.gradle.kts b/kotlinx.fuzz.test/build.gradle.kts index 1b67033b..111deef5 100644 --- a/kotlinx.fuzz.test/build.gradle.kts +++ b/kotlinx.fuzz.test/build.gradle.kts @@ -22,7 +22,7 @@ fuzzConfig { keepGoing = 10 runMode = RunMode.REGRESSION instrument = listOf("kotlinx.fuzz.test.**") - maxSingleTargetFuzzTime = 3600.seconds + maxSingleTargetFuzzTime = 10.seconds } kotlin { From b6ae77964d41f4beff08741ac5aeeb19d4f870a5 Mon Sep 17 00:00:00 2001 From: FerrumBrain Date: Mon, 3 Feb 2025 22:43:09 +0100 Subject: [PATCH 08/20] final adjustments --- kotlinx.fuzz.jazzer/build.gradle.kts | 4 ++++ .../kotlinx/fuzz/jazzer/JazzerLauncher.kt | 20 +++++++++---------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/kotlinx.fuzz.jazzer/build.gradle.kts b/kotlinx.fuzz.jazzer/build.gradle.kts index 01abe9bf..5c4d043a 100644 --- a/kotlinx.fuzz.jazzer/build.gradle.kts +++ b/kotlinx.fuzz.jazzer/build.gradle.kts @@ -4,6 +4,10 @@ plugins { id("kotlinx.fuzz.src-module") } +repositories { + mavenLocal() +} + dependencies { implementation(project(":kotlinx.fuzz.engine")) implementation(project(":kotlinx.fuzz.api")) diff --git a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt index c3e76819..88def3a6 100644 --- a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt +++ b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt @@ -51,13 +51,7 @@ object JazzerLauncher { exitProcess(0) } - private fun configure(method: Method): List { - val reproducerPath = - Path(Opt.reproducerPath.get(), method.declaringClass.simpleName, method.name).absolute() - if (!reproducerPath.exists()) { - reproducerPath.createDirectories() - } - + private fun configure(reproducerPath: Path, method: Method): List { val libFuzzerArgs = mutableListOf("fake_argv0") val currentCorpus = config.corpusDir.resolve(method.fullName) currentCorpus.createDirectories() @@ -99,7 +93,13 @@ object JazzerLauncher { @OptIn(ExperimentalPathApi::class) fun runTarget(instance: Any, method: Method): Throwable? { - val libFuzzerArgs = configure(method) + val reproducerPath = + Path(Opt.reproducerPath.get(), method.declaringClass.simpleName, method.name).absolute() + if (!reproducerPath.exists()) { + reproducerPath.createDirectories() + } + + val libFuzzerArgs = configure(reproducerPath, method) val atomicFinding = AtomicReference() FuzzTargetRunner.registerFatalFindingHandlerForJUnit { finding -> @@ -110,12 +110,12 @@ object JazzerLauncher { when (config.runMode) { RunMode.REGRESSION -> { - Path(Opt.reproducerPath.get()).listDirectoryEntries("crash-*").forEach { + reproducerPath.listDirectoryEntries("crash-*").forEach { FuzzTargetRunner.runOne(it.readBytes()) } } RunMode.REGRESSION_FUZZING -> { - Path(Opt.reproducerPath.get()).listDirectoryEntries("crash-*").forEach { + reproducerPath.forEach { FuzzTargetRunner.runOne(it.readBytes()) } FuzzTargetRunner.startLibFuzzer(libFuzzerArgs) From 1e081f300038628626f4eb17fa14c8d2bf3a5be8 Mon Sep 17 00:00:00 2001 From: FerrumBrain Date: Mon, 3 Feb 2025 22:44:45 +0100 Subject: [PATCH 09/20] added maven repo --- kotlinx.fuzz.jazzer/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/kotlinx.fuzz.jazzer/build.gradle.kts b/kotlinx.fuzz.jazzer/build.gradle.kts index 5c4d043a..55d0fd9b 100644 --- a/kotlinx.fuzz.jazzer/build.gradle.kts +++ b/kotlinx.fuzz.jazzer/build.gradle.kts @@ -6,6 +6,7 @@ plugins { repositories { mavenLocal() + maven(url = "https://plan-maven.apal-research.com") } dependencies { From 0e9eb1909b89c29f7b5c1ae9f8dba2eaacd03861 Mon Sep 17 00:00:00 2001 From: FerrumBrain Date: Mon, 3 Feb 2025 22:48:22 +0100 Subject: [PATCH 10/20] diktat Fix --- .../src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt | 1 - .../kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt | 12 ++++-------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt b/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt index 742cda7b..b2d04578 100644 --- a/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt +++ b/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt @@ -1,7 +1,6 @@ package kotlinx.fuzz import java.nio.file.Path -import java.nio.file.Paths import kotlin.io.path.* import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty diff --git a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt index 88def3a6..098fc746 100644 --- a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt +++ b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt @@ -11,6 +11,7 @@ import java.lang.invoke.MethodHandles import java.lang.reflect.Method import java.nio.file.Path import java.util.concurrent.atomic.AtomicReference +import kotlin.io.path.* import kotlin.reflect.full.memberFunctions import kotlin.reflect.full.primaryConstructor import kotlin.reflect.jvm.javaMethod @@ -18,7 +19,6 @@ import kotlin.system.exitProcess import kotlinx.fuzz.KFuzzConfig import kotlinx.fuzz.KLoggerFactory import kotlinx.fuzz.RunMode -import kotlin.io.path.* object JazzerLauncher { private val log = KLoggerFactory.getLogger(JazzerLauncher::class.java) @@ -109,10 +109,8 @@ object JazzerLauncher { JazzerTarget.reset(MethodHandles.lookup().unreflect(method), instance) when (config.runMode) { - RunMode.REGRESSION -> { - reproducerPath.listDirectoryEntries("crash-*").forEach { - FuzzTargetRunner.runOne(it.readBytes()) - } + RunMode.REGRESSION -> reproducerPath.listDirectoryEntries("crash-*").forEach { + FuzzTargetRunner.runOne(it.readBytes()) } RunMode.REGRESSION_FUZZING -> { reproducerPath.forEach { @@ -120,9 +118,7 @@ object JazzerLauncher { } FuzzTargetRunner.startLibFuzzer(libFuzzerArgs) } - RunMode.FUZZING -> { - FuzzTargetRunner.startLibFuzzer(libFuzzerArgs) - } + RunMode.FUZZING -> FuzzTargetRunner.startLibFuzzer(libFuzzerArgs) } return atomicFinding.get() From bdca46bea8926564aeab6617b955d5553194b691 Mon Sep 17 00:00:00 2001 From: FerrumBrain Date: Mon, 3 Feb 2025 23:31:01 +0100 Subject: [PATCH 11/20] fixed maven repos --- buildSrc/src/main/kotlin/kotlinx.fuzz.example-module.gradle.kts | 1 + buildSrc/src/main/kotlin/kotlinx.fuzz.src-module.gradle.kts | 1 + kotlinx.fuzz.jazzer/build.gradle.kts | 1 - kotlinx.fuzz.test/build.gradle.kts | 1 + 4 files changed, 3 insertions(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/kotlinx.fuzz.example-module.gradle.kts b/buildSrc/src/main/kotlin/kotlinx.fuzz.example-module.gradle.kts index 28592325..1cc9795b 100644 --- a/buildSrc/src/main/kotlin/kotlinx.fuzz.example-module.gradle.kts +++ b/buildSrc/src/main/kotlin/kotlinx.fuzz.example-module.gradle.kts @@ -11,6 +11,7 @@ version = VERSION repositories { mavenCentral() + maven(url = "https://plan-maven.apal-research.com") } dependencies { diff --git a/buildSrc/src/main/kotlin/kotlinx.fuzz.src-module.gradle.kts b/buildSrc/src/main/kotlin/kotlinx.fuzz.src-module.gradle.kts index ca834dca..a0d58114 100644 --- a/buildSrc/src/main/kotlin/kotlinx.fuzz.src-module.gradle.kts +++ b/buildSrc/src/main/kotlin/kotlinx.fuzz.src-module.gradle.kts @@ -10,6 +10,7 @@ version = VERSION repositories { mavenCentral() + maven(url = "https://plan-maven.apal-research.com") } dependencies { diff --git a/kotlinx.fuzz.jazzer/build.gradle.kts b/kotlinx.fuzz.jazzer/build.gradle.kts index 55d0fd9b..5c4d043a 100644 --- a/kotlinx.fuzz.jazzer/build.gradle.kts +++ b/kotlinx.fuzz.jazzer/build.gradle.kts @@ -6,7 +6,6 @@ plugins { repositories { mavenLocal() - maven(url = "https://plan-maven.apal-research.com") } dependencies { diff --git a/kotlinx.fuzz.test/build.gradle.kts b/kotlinx.fuzz.test/build.gradle.kts index 4e5276ce..405fa62b 100644 --- a/kotlinx.fuzz.test/build.gradle.kts +++ b/kotlinx.fuzz.test/build.gradle.kts @@ -10,6 +10,7 @@ plugins { repositories { mavenCentral() + maven(url = "https://plan-maven.apal-research.com") } dependencies { From 3ab0de972a902c6c33971b49df2f21ae2b84ea36 Mon Sep 17 00:00:00 2001 From: FerrumBrain Date: Tue, 4 Feb 2025 13:40:06 +0100 Subject: [PATCH 12/20] set of run modes --- .../main/kotlin/kotlinx/fuzz/KFuzzConfig.kt | 17 ++++---- .../EngineTest.kt | 2 +- .../kotlinx/fuzz/jazzer/JazzerLauncher.kt | 39 +++++++------------ kotlinx.fuzz.test/build.gradle.kts | 2 +- 4 files changed, 24 insertions(+), 36 deletions(-) diff --git a/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt b/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt index b2d04578..97c147c8 100644 --- a/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt +++ b/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt @@ -24,7 +24,7 @@ import kotlin.time.Duration.Companion.seconds * (custom and built-in). * Default: empty list * @param maxSingleTargetFuzzTime - max time to fuzz a single target in seconds - * @param runMode - Regression or fuzzing. Default: regression_fuzzing + * @param runModes - Set of modes to be run: each element can be regression or fuzzing. Default: regression, fuzzing * @param reproducerPath - Path to store reproducers. Default: `$workDir/reproducers` */ interface KFuzzConfig { @@ -36,7 +36,7 @@ interface KFuzzConfig { val maxSingleTargetFuzzTime: Duration val workDir: Path val dumpCoverage: Boolean - val runMode: RunMode + val runModes: Set val reproducerPath: Path fun toPropertiesMap(): Map @@ -94,11 +94,12 @@ class KFuzzConfigImpl private constructor() : KFuzzConfig { toString = { it.toString() }, fromString = { it.toBooleanStrict() }, ) - override var runMode: RunMode by KFuzzConfigProperty( - "kotlinx.fuzz.runMode", - defaultValue = RunMode.REGRESSION_FUZZING, - toString = { it.toString() }, - fromString = { RunMode.valueOf(it.uppercase()) }, + override var runModes: Set by KFuzzConfigProperty( + "kotlinx.fuzz.runModes", + defaultValue = setOf(RunMode.REGRESSION, RunMode.FUZZING), + validate = { require(it.isNotEmpty()) { "runModes should not be empty" } }, + toString = { it.joinToString(", ") }, + fromString = { it.split(",").map { RunMode.valueOf(it.trim().uppercase()) }.toSet() }, ) override var reproducerPath: Path by KFuzzConfigProperty( "kotlinx.fuzz.reproducerPath", @@ -133,7 +134,7 @@ class KFuzzConfigImpl private constructor() : KFuzzConfig { } enum class RunMode { - FUZZING, REGRESSION, REGRESSION_FUZZING + FUZZING, REGRESSION } /** diff --git a/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx.fuzz.gradle.junit.test/EngineTest.kt b/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx.fuzz.gradle.junit.test/EngineTest.kt index 83edc281..4ef7c6a6 100644 --- a/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx.fuzz.gradle.junit.test/EngineTest.kt +++ b/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx.fuzz.gradle.junit.test/EngineTest.kt @@ -28,7 +28,7 @@ object EngineTest { @BeforeEach fun setup() { writeToSystemProperties { - runMode = RunMode.FUZZING + runModes = setOf(RunMode.FUZZING) maxSingleTargetFuzzTime = 10.seconds instrument = listOf("kotlinx.fuzz.test.**") workDir = kotlin.io.path.createTempDirectory("fuzz-test") diff --git a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt index 098fc746..5544db5e 100644 --- a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt +++ b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt @@ -70,23 +70,14 @@ object JazzerLauncher { libFuzzerArgs += "-rss_limit_mb=${jazzerConfig.libFuzzerRssLimit}" libFuzzerArgs += "-artifact_prefix=${reproducerPath.toAbsolutePath()}/" - Opt.keepGoing.setIfDefault( - when (config.runMode) { - RunMode.REGRESSION -> reproducerPath.listDirectoryEntries("crash-*").size.toLong() - - RunMode.REGRESSION_FUZZING -> { - libFuzzerArgs += "-max_total_time=${config.maxSingleTargetFuzzTime.inWholeSeconds}" - - reproducerPath.listDirectoryEntries("crash-*").size + config.keepGoing - } + var keepGoing = + if (config.runModes.contains(RunMode.REGRESSION)) reproducerPath.listDirectoryEntries("crash-*").size.toLong() else 0 + if (config.runModes.contains(RunMode.FUZZING)) { + libFuzzerArgs += "-max_total_time=${config.maxSingleTargetFuzzTime.inWholeSeconds}" + keepGoing += config.keepGoing + } - RunMode.FUZZING -> { - libFuzzerArgs += "-max_total_time=${config.maxSingleTargetFuzzTime.inWholeSeconds}" - - config.keepGoing - } - }, - ) + Opt.keepGoing.setIfDefault(keepGoing) return libFuzzerArgs } @@ -102,25 +93,21 @@ object JazzerLauncher { val libFuzzerArgs = configure(reproducerPath, method) val atomicFinding = AtomicReference() - FuzzTargetRunner.registerFatalFindingHandlerForJUnit { finding -> + FuzzTargetRunner.registerFatalFindingHandlerForJUnit { _, finding -> atomicFinding.set(finding) } JazzerTarget.reset(MethodHandles.lookup().unreflect(method), instance) - when (config.runMode) { - RunMode.REGRESSION -> reproducerPath.listDirectoryEntries("crash-*").forEach { + if (config.runModes.contains(RunMode.REGRESSION)) { + reproducerPath.listDirectoryEntries("crash-*").forEach { FuzzTargetRunner.runOne(it.readBytes()) } - RunMode.REGRESSION_FUZZING -> { - reproducerPath.forEach { - FuzzTargetRunner.runOne(it.readBytes()) - } - FuzzTargetRunner.startLibFuzzer(libFuzzerArgs) - } - RunMode.FUZZING -> FuzzTargetRunner.startLibFuzzer(libFuzzerArgs) } + if (config.runModes.contains(RunMode.FUZZING)) + FuzzTargetRunner.startLibFuzzer(libFuzzerArgs) + return atomicFinding.get() } diff --git a/kotlinx.fuzz.test/build.gradle.kts b/kotlinx.fuzz.test/build.gradle.kts index 405fa62b..cb59180f 100644 --- a/kotlinx.fuzz.test/build.gradle.kts +++ b/kotlinx.fuzz.test/build.gradle.kts @@ -21,7 +21,7 @@ dependencies { fuzzConfig { keepGoing = 10 - runMode = RunMode.REGRESSION + runModes = setOf(RunMode.REGRESSION) instrument = listOf("kotlinx.fuzz.test.**") maxSingleTargetFuzzTime = 10.seconds } From 2d93a0941b6cbf2c9e42e07b6cbc931cf5e7a5e0 Mon Sep 17 00:00:00 2001 From: FerrumBrain Date: Tue, 4 Feb 2025 14:06:01 +0100 Subject: [PATCH 13/20] fixed jazzer version --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c37799f5..aa3faf25 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] kotlin = "2.0.21" diktat = "2.0.0" -jazzer = "0.0.1" +jazzer = "0.0.2" junit-platform = "1.11.4" junit-jupiter = "5.11.4" From a2a9faffe9d79df05b27ac8438ac396875c5f07a Mon Sep 17 00:00:00 2001 From: FerrumBrain Date: Tue, 4 Feb 2025 14:08:07 +0100 Subject: [PATCH 14/20] diktat fix --- .../src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt index 5544db5e..7c15762f 100644 --- a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt +++ b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt @@ -105,8 +105,9 @@ object JazzerLauncher { } } - if (config.runModes.contains(RunMode.FUZZING)) + if (config.runModes.contains(RunMode.FUZZING)) { FuzzTargetRunner.startLibFuzzer(libFuzzerArgs) + } return atomicFinding.get() } From 8580fe5fe25a75f211230c6e8dcd2b83423195dc Mon Sep 17 00:00:00 2001 From: FerrumBrain Date: Tue, 4 Feb 2025 16:00:05 +0100 Subject: [PATCH 15/20] resolved conversations --- kotlinx.fuzz.jazzer/build.gradle.kts | 4 ---- .../main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt | 9 +++++++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/kotlinx.fuzz.jazzer/build.gradle.kts b/kotlinx.fuzz.jazzer/build.gradle.kts index 5c4d043a..01abe9bf 100644 --- a/kotlinx.fuzz.jazzer/build.gradle.kts +++ b/kotlinx.fuzz.jazzer/build.gradle.kts @@ -4,10 +4,6 @@ plugins { id("kotlinx.fuzz.src-module") } -repositories { - mavenLocal() -} - dependencies { implementation(project(":kotlinx.fuzz.engine")) implementation(project(":kotlinx.fuzz.api")) diff --git a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt index 7c15762f..b975cfc9 100644 --- a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt +++ b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt @@ -51,6 +51,9 @@ object JazzerLauncher { exitProcess(0) } + private fun countCrashes(reproducerPath: Path) = + reproducerPath.listDirectoryEntries("{crash-*,timeout-*,slow-imput-*}").size.toLong() + private fun configure(reproducerPath: Path, method: Method): List { val libFuzzerArgs = mutableListOf("fake_argv0") val currentCorpus = config.corpusDir.resolve(method.fullName) @@ -70,8 +73,10 @@ object JazzerLauncher { libFuzzerArgs += "-rss_limit_mb=${jazzerConfig.libFuzzerRssLimit}" libFuzzerArgs += "-artifact_prefix=${reproducerPath.toAbsolutePath()}/" - var keepGoing = - if (config.runModes.contains(RunMode.REGRESSION)) reproducerPath.listDirectoryEntries("crash-*").size.toLong() else 0 + var keepGoing = when(RunMode.REGRESSION) { + in config.runModes -> countCrashes(reproducerPath) + else -> 0 + } if (config.runModes.contains(RunMode.FUZZING)) { libFuzzerArgs += "-max_total_time=${config.maxSingleTargetFuzzTime.inWholeSeconds}" keepGoing += config.keepGoing From a4287273a857630cb68ddf0c5ec15d4f9663740b Mon Sep 17 00:00:00 2001 From: FerrumBrain Date: Tue, 4 Feb 2025 16:07:03 +0100 Subject: [PATCH 16/20] diktat fix --- .../src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt index b975cfc9..3325e412 100644 --- a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt +++ b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt @@ -73,7 +73,7 @@ object JazzerLauncher { libFuzzerArgs += "-rss_limit_mb=${jazzerConfig.libFuzzerRssLimit}" libFuzzerArgs += "-artifact_prefix=${reproducerPath.toAbsolutePath()}/" - var keepGoing = when(RunMode.REGRESSION) { + var keepGoing = when (RunMode.REGRESSION) { in config.runModes -> countCrashes(reproducerPath) else -> 0 } From 26d9d3fa3ddaf19a2ee2c7f495c22c763ac8465c Mon Sep 17 00:00:00 2001 From: FerrumBrain Date: Tue, 4 Feb 2025 17:45:34 +0100 Subject: [PATCH 17/20] resolved conversations --- .../src/main/kotlin/kotlinx/fuzz/jazzer/JazzerEngine.kt | 2 ++ .../src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt | 7 ++----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerEngine.kt b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerEngine.kt index e8b26949..6fe343d7 100644 --- a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerEngine.kt +++ b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerEngine.kt @@ -77,6 +77,8 @@ class JazzerEngine(private val config: KFuzzConfig) : KFuzzEngine { internal fun KFuzzConfig.exceptionPath(method: Method): Path = exceptionsDir.resolve("${method.fullName}.exception") +internal fun Path.listCrashes() = listDirectoryEntries("{crash-*,timeout-*,slow-input-*}") + /** * Reads a Throwable from the specified [path]. */ diff --git a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt index 3325e412..9b7aba02 100644 --- a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt +++ b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt @@ -51,9 +51,6 @@ object JazzerLauncher { exitProcess(0) } - private fun countCrashes(reproducerPath: Path) = - reproducerPath.listDirectoryEntries("{crash-*,timeout-*,slow-imput-*}").size.toLong() - private fun configure(reproducerPath: Path, method: Method): List { val libFuzzerArgs = mutableListOf("fake_argv0") val currentCorpus = config.corpusDir.resolve(method.fullName) @@ -74,7 +71,7 @@ object JazzerLauncher { libFuzzerArgs += "-artifact_prefix=${reproducerPath.toAbsolutePath()}/" var keepGoing = when (RunMode.REGRESSION) { - in config.runModes -> countCrashes(reproducerPath) + in config.runModes -> reproducerPath.listCrashes().size.toLong() else -> 0 } if (config.runModes.contains(RunMode.FUZZING)) { @@ -105,7 +102,7 @@ object JazzerLauncher { JazzerTarget.reset(MethodHandles.lookup().unreflect(method), instance) if (config.runModes.contains(RunMode.REGRESSION)) { - reproducerPath.listDirectoryEntries("crash-*").forEach { + reproducerPath.listCrashes().forEach { FuzzTargetRunner.runOne(it.readBytes()) } } From 385bb0c5b481fcc86f7cda706db3b77f979a006b Mon Sep 17 00:00:00 2001 From: FerrumBrain Date: Tue, 4 Feb 2025 23:20:01 +0100 Subject: [PATCH 18/20] fixes --- kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt | 2 +- .../src/main/kotlin/kotlinx/fuzz/jazzer/JazzerEngine.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt b/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt index 97c147c8..c8765e4e 100644 --- a/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt +++ b/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt @@ -98,7 +98,7 @@ class KFuzzConfigImpl private constructor() : KFuzzConfig { "kotlinx.fuzz.runModes", defaultValue = setOf(RunMode.REGRESSION, RunMode.FUZZING), validate = { require(it.isNotEmpty()) { "runModes should not be empty" } }, - toString = { it.joinToString(", ") }, + toString = { it.joinToString(",") }, fromString = { it.split(",").map { RunMode.valueOf(it.trim().uppercase()) }.toSet() }, ) override var reproducerPath: Path by KFuzzConfigProperty( diff --git a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerEngine.kt b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerEngine.kt index 6fe343d7..7605bf4a 100644 --- a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerEngine.kt +++ b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerEngine.kt @@ -77,7 +77,7 @@ class JazzerEngine(private val config: KFuzzConfig) : KFuzzEngine { internal fun KFuzzConfig.exceptionPath(method: Method): Path = exceptionsDir.resolve("${method.fullName}.exception") -internal fun Path.listCrashes() = listDirectoryEntries("{crash-*,timeout-*,slow-input-*}") +internal fun Path.listCrashes() = listDirectoryEntries("{crash-*,timeout-*,slow-unit-*}") /** * Reads a Throwable from the specified [path]. From f1b9688dde3c1bb957e8d8cf0790c2b169dc71f4 Mon Sep 17 00:00:00 2001 From: FerrumBrain Date: Wed, 5 Feb 2025 15:05:34 +0100 Subject: [PATCH 19/20] diktat fix --- .../src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt index 9a3657d7..8a9d9e53 100644 --- a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt +++ b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt @@ -17,10 +17,10 @@ import kotlin.reflect.full.primaryConstructor import kotlin.reflect.jvm.javaMethod import kotlin.system.exitProcess import kotlinx.fuzz.KFuzzConfig +import kotlinx.fuzz.RunMode import kotlinx.fuzz.log.LoggerFacade import kotlinx.fuzz.log.debug import kotlinx.fuzz.log.error -import kotlinx.fuzz.RunMode object JazzerLauncher { private val log = LoggerFacade.getLogger() From 0ff6c380647b8c2da8f2a56f05564c0c6500d4e7 Mon Sep 17 00:00:00 2001 From: FerrumBrain Date: Wed, 5 Feb 2025 15:36:40 +0100 Subject: [PATCH 20/20] resolved conversations --- .../kotlin/kotlinx.fuzz.example-module.gradle.kts | 1 - gradle/libs.versions.toml | 12 ++++++++---- kotlinx.fuzz.jazzer/build.gradle.kts | 2 +- .../main/kotlin/kotlinx/fuzz/jazzer/JazzerEngine.kt | 2 +- .../kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt | 11 +++++++++-- kotlinx.fuzz.test/build.gradle.kts | 2 +- 6 files changed, 20 insertions(+), 10 deletions(-) diff --git a/buildSrc/src/main/kotlin/kotlinx.fuzz.example-module.gradle.kts b/buildSrc/src/main/kotlin/kotlinx.fuzz.example-module.gradle.kts index 1cc9795b..28592325 100644 --- a/buildSrc/src/main/kotlin/kotlinx.fuzz.example-module.gradle.kts +++ b/buildSrc/src/main/kotlin/kotlinx.fuzz.example-module.gradle.kts @@ -11,7 +11,6 @@ version = VERSION repositories { mavenCentral() - maven(url = "https://plan-maven.apal-research.com") } dependencies { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 67a8b659..80c52c1c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,8 @@ [versions] kotlin = "2.0.21" diktat = "2.0.0" -jazzer = "0.0.2" +jazzer = "0.22.1" +plan-jazzer = "0.0.2" junit-platform = "1.11.4" junit-jupiter = "5.11.4" @@ -27,9 +28,12 @@ slf4j = "2.0.16" kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } diktat-gradle-plugin = { module = "com.saveourtool.diktat:diktat-gradle-plugin", version.ref = "diktat" } -jazzer = { module = "org.jetbrains:jazzer", version.ref = "jazzer" } -jazzer-junit = { module = "org.jetbrains:jazzer-junit", version.ref = "jazzer" } -jazzer-api = { module = "org.jetbrains:jazzer-api", version.ref = "jazzer" } +jazzer = { module = "com.code-intelligence:jazzer", version.ref = "jazzer" } +jazzer-junit = { module = "com.code-intelligence:jazzer-junit", version.ref = "jazzer" } +jazzer-api = { module = "com.code-intelligence:jazzer-api", version.ref = "jazzer" } +plan-jazzer = { module = "org.jetbrains:jazzer", version.ref = "plan-jazzer" } +plan-jazzer-junit = { module = "org.jetbrains:jazzer-junit", version.ref = "plan-jazzer" } +plan-jazzer-api = { module = "org.jetbrains:jazzer-api", version.ref = "plan-jazzer" } reflections = { module = "org.reflections:reflections", version.ref = "reflections" } diff --git a/kotlinx.fuzz.jazzer/build.gradle.kts b/kotlinx.fuzz.jazzer/build.gradle.kts index e838c22e..4a7a7940 100644 --- a/kotlinx.fuzz.jazzer/build.gradle.kts +++ b/kotlinx.fuzz.jazzer/build.gradle.kts @@ -8,7 +8,7 @@ dependencies { implementation(project(":kotlinx.fuzz.engine")) implementation(project(":kotlinx.fuzz.api")) implementation(kotlin("reflect")) - implementation(libs.jazzer) + implementation(libs.plan.jazzer) implementation(libs.slf4j.api) } diff --git a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerEngine.kt b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerEngine.kt index c1e17030..c21c5170 100644 --- a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerEngine.kt +++ b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerEngine.kt @@ -117,7 +117,7 @@ class JazzerEngine(private val config: KFuzzConfig) : KFuzzEngine { internal fun KFuzzConfig.exceptionPath(method: Method): Path = exceptionsDir.resolve("${method.fullName}.exception") -internal fun Path.listCrashes() = listDirectoryEntries("{crash-*,timeout-*,slow-unit-*}") +internal fun Path.listCrashes(): List = listDirectoryEntries("{crash-*,timeout-*,slow-unit-*}") /** * Reads a Throwable from the specified [path]. diff --git a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt index 8a9d9e53..96df01db 100644 --- a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt +++ b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt @@ -21,6 +21,7 @@ import kotlinx.fuzz.RunMode import kotlinx.fuzz.log.LoggerFacade import kotlinx.fuzz.log.debug import kotlinx.fuzz.log.error +import kotlinx.fuzz.log.warn object JazzerLauncher { private val log = LoggerFacade.getLogger() @@ -70,10 +71,16 @@ object JazzerLauncher { libFuzzerArgs += currentCorpus.toString() libFuzzerArgs += "-rss_limit_mb=${jazzerConfig.libFuzzerRssLimit}" - libFuzzerArgs += "-artifact_prefix=${reproducerPath.toAbsolutePath()}/" + libFuzzerArgs += "-artifact_prefix=${reproducerPath.absolute()}/" var keepGoing = when (RunMode.REGRESSION) { - in config.runModes -> reproducerPath.listCrashes().size.toLong() + in config.runModes -> { + val crashCount = reproducerPath.listCrashes().size + if (crashCount == 0) { + log.warn { "No crashes found for regression mode at ${reproducerPath.absolute()}" } + } + crashCount.toLong() + } else -> 0 } if (config.runModes.contains(RunMode.FUZZING)) { diff --git a/kotlinx.fuzz.test/build.gradle.kts b/kotlinx.fuzz.test/build.gradle.kts index 53de4849..685f45f8 100644 --- a/kotlinx.fuzz.test/build.gradle.kts +++ b/kotlinx.fuzz.test/build.gradle.kts @@ -22,7 +22,7 @@ dependencies { fuzzConfig { keepGoing = 10 - runModes = setOf(RunMode.REGRESSION) + runModes = setOf(RunMode.FUZZING) instrument = listOf("kotlinx.fuzz.test.**") maxSingleTargetFuzzTime = 10.seconds jacocoReports = setOf(HTML, CSV, XML)