diff options
author | Fabian Meumertzheim <meumertzheim@code-intelligence.com> | 2021-07-19 11:05:43 +0200 |
---|---|---|
committer | Fabian Meumertzheim <fabian@meumertzhe.im> | 2021-07-23 13:28:35 +0200 |
commit | ae8beedfa4db4fd2400d61389e8c4c16954beadc (patch) | |
tree | 882dc878e9e313aa62545c019860d4c3d0fb0fd2 /agent | |
parent | 6279f164d3f4ca9b74852a38037288fff4af8cfd (diff) | |
download | jazzer-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.kt | 3 | ||||
-rw-r--r-- | agent/src/main/java/com/code_intelligence/jazzer/runtime/ExceptionUtils.kt | 55 |
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") + } +} |