<lambda>null1 // Copyright 2021 Code Intelligence GmbH
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 @file:JvmName("ExceptionUtils")
16
17 package com.code_intelligence.jazzer.utils
18
19 import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow
20 import java.lang.management.ManagementFactory
21 import java.nio.ByteBuffer
22 import java.security.MessageDigest
23
24 private fun hash(throwable: Throwable, passToRootCause: Boolean): ByteArray =
25 MessageDigest.getInstance("SHA-256").run {
26 // It suffices to hash the stack trace of the deepest cause as the higher-level causes only
27 // contain part of the stack trace (plus possibly a different exception type).
28 var rootCause = throwable
29 if (passToRootCause) {
30 while (true) {
31 rootCause = rootCause.cause ?: break
32 }
33 }
34 update(rootCause.javaClass.name.toByteArray())
35 for (element in rootCause.stackTrace) {
36 update(element.toString().toByteArray())
37 }
38 if (throwable.suppressed.isNotEmpty()) {
39 update("suppressed".toByteArray())
40 for (suppressed in throwable.suppressed) {
41 update(hash(suppressed, passToRootCause))
42 }
43 }
44 digest()
45 }
46
47 /**
48 * Computes a hash of the stack trace of [throwable] without messages.
49 *
50 * The hash can be used to deduplicate stack traces obtained on crashes. By not including the
51 * messages, this hash should not depend on the precise crashing input.
52 */
computeDedupTokennull53 fun computeDedupToken(throwable: Throwable): Long {
54 var passToRootCause = true
55 if (throwable is FuzzerSecurityIssueLow && throwable.cause is StackOverflowError) {
56 // Special handling for StackOverflowErrors as processed by preprocessThrowable:
57 // Only consider the repeated part of the stack trace and ignore the original stack trace in
58 // the cause.
59 passToRootCause = false
60 }
61 return ByteBuffer.wrap(hash(throwable, passToRootCause)).long
62 }
63
64 /**
65 * Annotates [throwable] with a severity and additional information if it represents a bug type
66 * that has security content.
67 */
preprocessThrowablenull68 fun preprocessThrowable(throwable: Throwable): Throwable = when (throwable) {
69 is StackOverflowError -> {
70 // StackOverflowErrors are hard to deduplicate as the top-most stack frames vary wildly,
71 // whereas the information that is most useful for deduplication detection is hidden in the
72 // rest of the (truncated) stack frame.
73 // We heuristically clean up the stack trace by taking the elements from the bottom and
74 // stopping at the first repetition of a frame. The original error is returned as the cause
75 // unchanged.
76 val observedFrames = mutableSetOf<StackTraceElement>()
77 val bottomFramesWithoutRepetition = throwable.stackTrace.takeLastWhile { frame ->
78 (frame !in observedFrames).also { observedFrames.add(frame) }
79 }
80 FuzzerSecurityIssueLow("Stack overflow (use '${getReproducingXssArg()}' to reproduce)", throwable).apply {
81 stackTrace = bottomFramesWithoutRepetition.toTypedArray()
82 }
83 }
84 is OutOfMemoryError -> stripOwnStackTrace(
85 FuzzerSecurityIssueLow(
86 "Out of memory (use '${getReproducingXmxArg()}' to reproduce)", throwable
87 )
88 )
89 is VirtualMachineError -> stripOwnStackTrace(FuzzerSecurityIssueLow(throwable))
90 else -> throwable
91 }
92
93 /**
94 * Strips the stack trace of [throwable] (e.g. because it was created in a utility method), but not
95 * the stack traces of its causes.
96 */
<lambda>null97 private fun stripOwnStackTrace(throwable: Throwable) = throwable.apply {
98 stackTrace = emptyArray()
99 }
100
101 /**
102 * Returns a valid `-Xmx` JVM argument that sets the stack size to a value with which [StackOverflowError] findings can
103 * be reproduced, assuming the environment is sufficiently similar (e.g. OS and JVM version).
104 */
getReproducingXmxArgnull105 private fun getReproducingXmxArg(): String? {
106 val maxHeapSizeInMegaBytes = (getNumericFinalFlagValue("MaxHeapSize") ?: return null) shr 20
107 val conservativeMaxHeapSizeInMegaBytes = (maxHeapSizeInMegaBytes * 0.9).toInt()
108 return "-Xmx${conservativeMaxHeapSizeInMegaBytes}m"
109 }
110
111 /**
112 * Returns a valid `-Xss` JVM argument that sets the stack size to a value with which [StackOverflowError] findings can
113 * be reproduced, assuming the environment is sufficiently similar (e.g. OS and JVM version).
114 */
getReproducingXssArgnull115 private fun getReproducingXssArg(): String? {
116 val threadStackSizeInKiloBytes = getNumericFinalFlagValue("ThreadStackSize") ?: return null
117 val conservativeThreadStackSizeInKiloBytes = (threadStackSizeInKiloBytes * 0.9).toInt()
118 return "-Xss${conservativeThreadStackSizeInKiloBytes}k"
119 }
120
getNumericFinalFlagValuenull121 private fun getNumericFinalFlagValue(arg: String): Long? {
122 val argPattern = "$arg\\D*(\\d*)".toRegex()
123 return argPattern.find(javaFullFinalFlags ?: return null)?.groupValues?.get(1)?.toLongOrNull()
124 }
125
<lambda>null126 private val javaFullFinalFlags by lazy {
127 readJavaFullFinalFlags()
128 }
129
readJavaFullFinalFlagsnull130 private fun readJavaFullFinalFlags(): String? {
131 val javaHome = System.getProperty("java.home") ?: return null
132 val javaBinary = "$javaHome/bin/java"
133 val currentJvmArgs = ManagementFactory.getRuntimeMXBean().inputArguments
134 val javaPrintFlagsProcess = ProcessBuilder(
135 listOf(javaBinary) + currentJvmArgs + listOf(
136 "-XX:+PrintFlagsFinal",
137 "-version"
138 )
139 ).start()
140 return javaPrintFlagsProcess.inputStream.bufferedReader().useLines { lineSequence ->
141 lineSequence
142 .filter { it.contains("ThreadStackSize") || it.contains("MaxHeapSize") }
143 .joinToString("\n")
144 }
145 }
146
dumpAllStackTracesnull147 fun dumpAllStackTraces() {
148 System.err.println("\nStack traces of all JVM threads:\n")
149 for ((thread, stack) in Thread.getAllStackTraces()) {
150 System.err.println(thread)
151 // Remove traces of this method and the methods it calls.
152 stack.asList()
153 .asReversed()
154 .takeWhile {
155 !(
156 it.className == "com.code_intelligence.jazzer.runtime.ExceptionUtils" &&
157 it.methodName == "dumpAllStackTraces"
158 )
159 }
160 .asReversed()
161 .forEach { frame ->
162 System.err.println("\tat $frame")
163 }
164 System.err.println()
165 }
166 System.err.println("Garbage collector stats:")
167 System.err.println(
168 ManagementFactory.getGarbageCollectorMXBeans().joinToString("\n", "\n", "\n") {
169 "${it.name}: ${it.collectionCount} collections took ${it.collectionTime}ms"
170 }
171 )
172 }
173