• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download

<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