aboutsummaryrefslogtreecommitdiff
path: root/agent
diff options
context:
space:
mode:
authorFabian Meumertzheim <meumertzheim@code-intelligence.com>2021-07-19 11:05:43 +0200
committerFabian Meumertzheim <fabian@meumertzhe.im>2021-07-23 13:28:35 +0200
commitae8beedfa4db4fd2400d61389e8c4c16954beadc (patch)
tree882dc878e9e313aa62545c019860d4c3d0fb0fd2 /agent
parent6279f164d3f4ca9b74852a38037288fff4af8cfd (diff)
downloadjazzer-api-ae8beedfa4db4fd2400d61389e8c4c16954beadc.tar.gz
Print JVM args that reproduce OOMs & stack overflows
This should help with issues such as https://github.com/jhy/jsoup/issues/1577#issuecomment-877898842
Diffstat (limited to 'agent')
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt3
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/runtime/ExceptionUtils.kt55
2 files changed, 56 insertions, 2 deletions
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt
index 64a5ca56..c6d14569 100644
--- a/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt
+++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt
@@ -46,6 +46,9 @@ private val BASE_EXCLUDED_CLASS_NAME_GLOBS = listOf(
"jdk.**",
"kotlin.**",
"sun.**",
+ "javax.management.DynamicMBean", // used by ManagementFactory.getRuntimeMXBean()
+ "javax.management.NotificationEmitter", // used by ManagementFactory.getRuntimeMXBean()
+ "javax.management.NotificationBroadcaster", // used by ManagementFactory.getRuntimeMXBean()
)
class SimpleGlobMatcher(val glob: String) {
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/ExceptionUtils.kt b/agent/src/main/java/com/code_intelligence/jazzer/runtime/ExceptionUtils.kt
index 94a45dcc..e7fba337 100644
--- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/ExceptionUtils.kt
+++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/ExceptionUtils.kt
@@ -17,6 +17,7 @@
package com.code_intelligence.jazzer.runtime
import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow
+import java.lang.management.ManagementFactory
import java.nio.ByteBuffer
import java.security.MessageDigest
@@ -76,11 +77,15 @@ fun preprocessThrowable(throwable: Throwable): Throwable = when (throwable) {
val bottomFramesWithoutRepetition = throwable.stackTrace.takeLastWhile { frame ->
(frame !in observedFrames).also { observedFrames.add(frame) }
}
- FuzzerSecurityIssueLow("Stack overflow (truncated to likely cause)", throwable).apply {
+ FuzzerSecurityIssueLow("Stack overflow (use '${getReproducingXssArg()}' to reproduce)", throwable).apply {
stackTrace = bottomFramesWithoutRepetition.toTypedArray()
}
}
- // Includes OutOfMemoryError
+ is OutOfMemoryError -> stripOwnStackTrace(
+ FuzzerSecurityIssueLow(
+ "Out of memory (use '${getReproducingXmxArg()}' to reproduce)", throwable
+ )
+ )
is VirtualMachineError -> stripOwnStackTrace(FuzzerSecurityIssueLow(throwable))
else -> throwable
}
@@ -92,3 +97,49 @@ fun preprocessThrowable(throwable: Throwable): Throwable = when (throwable) {
private fun stripOwnStackTrace(throwable: Throwable) = throwable.apply {
stackTrace = emptyArray()
}
+
+/**
+ * Returns a valid `-Xmx` JVM argument that sets the stack size to a value with which [StackOverflowError] findings can
+ * be reproduced, assuming the environment is sufficiently similar (e.g. OS and JVM version).
+ */
+private fun getReproducingXmxArg(): String? {
+ val maxHeapSizeInMegaBytes = (getNumericFinalFlagValue("MaxHeapSize") ?: return null) shr 20
+ val conservativeMaxHeapSizeInMegaBytes = (maxHeapSizeInMegaBytes * 0.9).toInt()
+ return "-Xmx${conservativeMaxHeapSizeInMegaBytes}m"
+}
+
+/**
+ * Returns a valid `-Xss` JVM argument that sets the stack size to a value with which [StackOverflowError] findings can
+ * be reproduced, assuming the environment is sufficiently similar (e.g. OS and JVM version).
+ */
+private fun getReproducingXssArg(): String? {
+ val threadStackSizeInKiloBytes = getNumericFinalFlagValue("ThreadStackSize") ?: return null
+ val conservativeThreadStackSizeInKiloBytes = (threadStackSizeInKiloBytes * 0.9).toInt()
+ return "-Xss${conservativeThreadStackSizeInKiloBytes}k"
+}
+
+private fun getNumericFinalFlagValue(arg: String): Long? {
+ val argPattern = "$arg\\D*(\\d*)".toRegex()
+ return argPattern.find(javaFullFinalFlags ?: return null)?.groupValues?.get(1)?.toLongOrNull()
+}
+
+private val javaFullFinalFlags by lazy {
+ readJavaFullFinalFlags()
+}
+
+private fun readJavaFullFinalFlags(): String? {
+ val javaHome = System.getProperty("java.home") ?: return null
+ val javaBinary = "$javaHome/bin/java"
+ val currentJvmArgs = ManagementFactory.getRuntimeMXBean().inputArguments
+ val javaPrintFlagsProcess = ProcessBuilder(
+ listOf(javaBinary) + currentJvmArgs + listOf(
+ "-XX:+PrintFlagsFinal",
+ "-version"
+ )
+ ).start()
+ return javaPrintFlagsProcess.inputStream.bufferedReader().useLines { lineSequence ->
+ lineSequence
+ .filter { it.contains("ThreadStackSize") || it.contains("MaxHeapSize") }
+ .joinToString("\n")
+ }
+}