Skip to content

Commit 1772af5

Browse files
authored
Taint configuration feature support (#195)
* Add taint configuration * Use kotlinx serialization * Store rules in a trie * Small refactorings * First tests * Add some tests and fix some bugs * Add more tests * Add config into release script
1 parent ed17f6b commit 1772af5

File tree

17 files changed

+2167
-1
lines changed

17 files changed

+2167
-1
lines changed

.github/workflows/release.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ jobs:
4343
files: |
4444
jacodb-api/build/libs/jacodb-api-${{inputs.semVer}}.jar
4545
jacodb-approximations/build/libs/jacodb-approximations-${{inputs.semVer}}.jar
46+
jacodb-taint-configuration/build/libs/jacodb-taint-configuration-${{inputs.semVer}}.jar
4647
jacodb-analysis/build/libs/jacodb-analysis-${{inputs.semVer}}.jar
4748
jacodb-core/build/libs/jacodb-core-${{inputs.semVer}}.jar
4849
jacodb-cli/build/libs/jacodb-cli-${{inputs.semVer}}.jar

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ if (!repoUrl.isNullOrEmpty()) {
160160
project(":jacodb-core"),
161161
project(":jacodb-analysis"),
162162
project(":jacodb-approximations"),
163+
project(":jacodb-taint-configuration"),
163164
)
164165
) {
165166
tasks {

jacodb-api/src/main/kotlin/org/jacodb/api/ext/JcClasses.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ fun JcClassOrInterface.toType(): JcClassType {
5050
return classpath.typeOf(this) as JcClassType
5151
}
5252

53-
val JcClassOrInterface.packageName get() = name.substringBeforeLast(".")
53+
val JcClassOrInterface.packageName get() = name.substringBeforeLast(".", missingDelimiterValue = "")
5454

5555
const val JAVA_OBJECT = "java.lang.Object"
5656

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
plugins {
2+
id("java")
3+
kotlin("plugin.serialization")
4+
}
5+
6+
repositories {
7+
mavenCentral()
8+
}
9+
10+
dependencies {
11+
implementation(project(":jacodb-api"))
12+
implementation(project(":jacodb-core"))
13+
implementation(testFixtures(project(":jacodb-core")))
14+
implementation(Libs.kotlinx_serialization_json)
15+
16+
testImplementation(group = "io.github.microutils", name = "kotlin-logging", version = "1.8.3")
17+
}
18+
19+
tasks.test {
20+
useJUnitPlatform()
21+
}
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/*
2+
* Copyright 2022 UnitTestBot contributors (utbot.org)
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.jacodb.configuration
18+
19+
import org.jacodb.api.JcClassOrInterface
20+
import org.jacodb.api.ext.packageName
21+
22+
class ConfigurationTrie(
23+
configuration: List<SerializedTaintConfigurationItem>,
24+
private val nameMatcher: (NameMatcher, String) -> Boolean
25+
) {
26+
private val unprocessedRules: MutableList<SerializedTaintConfigurationItem> = configuration.toMutableList()
27+
private val rootNode: RootNode = RootNode()
28+
29+
private fun initializeIfRequired() {
30+
if (unprocessedRules.isEmpty()) return
31+
32+
while (unprocessedRules.isNotEmpty()) {
33+
var configurationRule = unprocessedRules.removeLast()
34+
val classMatcher = configurationRule.methodInfo.cls
35+
36+
val alternativeClassMatchers = classMatcher.extractAlternatives()
37+
if (alternativeClassMatchers.size != 1) {
38+
alternativeClassMatchers.forEach {
39+
val updatedMethodInfo = configurationRule.methodInfo.copy(cls = it)
40+
unprocessedRules += configurationRule.updateMethodInfo(updatedMethodInfo)
41+
}
42+
43+
continue
44+
}
45+
46+
val simplifiedClassMatcher = alternativeClassMatchers.single()
47+
val updatedMethodInfo = configurationRule.methodInfo.copy(cls = simplifiedClassMatcher)
48+
configurationRule = configurationRule.updateMethodInfo(updatedMethodInfo)
49+
50+
var currentNode: Node = rootNode
51+
52+
val (simplifiedPkgMatcher, simplifiedClassNameMatcher) = simplifiedClassMatcher
53+
54+
var matchedPackageNameParts = emptyList<String>()
55+
var unmatchedPackageNamePart: String? = null
56+
57+
when (simplifiedPkgMatcher) {
58+
AnyNameMatcher -> {
59+
currentNode.unmatchedRules += configurationRule
60+
continue
61+
}
62+
63+
is NameExactMatcher -> matchedPackageNameParts = simplifiedPkgMatcher.name.split(DOT_DELIMITER)
64+
is NamePatternMatcher -> {
65+
val (matchedParts, unmatchedParts) = simplifiedPkgMatcher.splitRegex()
66+
matchedPackageNameParts = matchedParts
67+
unmatchedPackageNamePart = unmatchedParts
68+
}
69+
}
70+
71+
for (part in matchedPackageNameParts) {
72+
currentNode = currentNode.children[part] ?: NodeImpl(part).also { currentNode.children += part to it }
73+
}
74+
75+
if (unmatchedPackageNamePart != null && unmatchedPackageNamePart != ALL_MATCH) {
76+
currentNode.unmatchedRules += configurationRule
77+
continue
78+
}
79+
80+
when (simplifiedClassNameMatcher) {
81+
AnyNameMatcher -> currentNode.rules += configurationRule
82+
83+
is NameExactMatcher -> if (unmatchedPackageNamePart == null) {
84+
val name = simplifiedClassNameMatcher.name
85+
currentNode = currentNode.children[name] ?: Leaf(name).also { currentNode.children += name to it }
86+
currentNode.rules += configurationRule
87+
} else {
88+
// case for patterns like ".*\.Request"
89+
currentNode.unmatchedRules += configurationRule
90+
}
91+
92+
is NamePatternMatcher -> {
93+
val classPattern = simplifiedClassNameMatcher.pattern
94+
95+
if (classPattern == ALL_MATCH) {
96+
currentNode.rules += configurationRule
97+
continue
98+
}
99+
100+
currentNode.unmatchedRules += configurationRule
101+
}
102+
}
103+
}
104+
}
105+
106+
fun getRulesForClass(clazz: JcClassOrInterface): List<SerializedTaintConfigurationItem> {
107+
initializeIfRequired()
108+
109+
val results = mutableListOf<SerializedTaintConfigurationItem>()
110+
111+
val className = clazz.simpleName
112+
val packageName = clazz.packageName
113+
val nameParts = clazz.name.split(DOT_DELIMITER)
114+
115+
var currentNode: Node = rootNode
116+
117+
for (i in 0..nameParts.size) {
118+
results += currentNode.unmatchedRules.filter {
119+
val classMatcher = it.methodInfo.cls
120+
nameMatcher(classMatcher.pkg, packageName) && nameMatcher(classMatcher.classNameMatcher, className)
121+
}
122+
123+
results += currentNode.rules
124+
125+
// We must process rules containing in the leaf, therefore, we have to spin one more iteration
126+
currentNode = nameParts.getOrNull(i)?.let { currentNode.children[it] } ?: break
127+
}
128+
129+
return results
130+
}
131+
132+
private sealed class Node {
133+
abstract val value: String
134+
abstract val children: MutableMap<String, Node>
135+
abstract val rules: MutableList<SerializedTaintConfigurationItem>
136+
abstract val unmatchedRules: MutableList<SerializedTaintConfigurationItem>
137+
}
138+
139+
private class RootNode : Node() {
140+
override val children: MutableMap<String, Node> = mutableMapOf()
141+
override val value: String
142+
get() = error("Must not be called for the root")
143+
override val rules: MutableList<SerializedTaintConfigurationItem> = mutableListOf()
144+
override val unmatchedRules: MutableList<SerializedTaintConfigurationItem> = mutableListOf()
145+
}
146+
147+
private data class NodeImpl(
148+
override val value: String,
149+
) : Node() {
150+
override val children: MutableMap<String, Node> = mutableMapOf()
151+
override val rules: MutableList<SerializedTaintConfigurationItem> = mutableListOf()
152+
override val unmatchedRules: MutableList<SerializedTaintConfigurationItem> = mutableListOf()
153+
}
154+
155+
private data class Leaf(
156+
override val value: String
157+
) : Node() {
158+
override val children: MutableMap<String, Node>
159+
get() = error("Leaf nodes do not have children")
160+
override val unmatchedRules: MutableList<SerializedTaintConfigurationItem>
161+
get() = mutableListOf()
162+
163+
override val rules: MutableList<SerializedTaintConfigurationItem> = mutableListOf()
164+
}
165+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2022 UnitTestBot contributors (utbot.org)
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.jacodb.configuration
18+
19+
import kotlinx.serialization.SerialName
20+
import kotlinx.serialization.Serializable
21+
22+
interface PositionResolver<R> {
23+
fun resolve(position: Position): R
24+
}
25+
26+
@Serializable
27+
sealed interface Position
28+
29+
@Serializable
30+
@SerialName("Argument")
31+
data class Argument(val number: Int) : Position
32+
33+
@Serializable
34+
@SerialName("AnyArgument")
35+
object AnyArgument : Position
36+
37+
@Serializable
38+
@SerialName("This")
39+
object ThisArgument : Position
40+
41+
@Serializable
42+
@SerialName("Result")
43+
object Result : Position
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright 2022 UnitTestBot contributors (utbot.org)
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.jacodb.configuration
18+
19+
import kotlinx.serialization.SerialName
20+
import kotlinx.serialization.Serializable
21+
22+
23+
@Serializable
24+
sealed interface SerializedTaintConfigurationItem {
25+
val methodInfo: FunctionMatcher
26+
27+
fun updateMethodInfo(updatedMethodInfo: FunctionMatcher): SerializedTaintConfigurationItem =
28+
when (this) {
29+
is SerializedTaintCleaner -> copy(methodInfo = updatedMethodInfo)
30+
is SerializedTaintEntryPointSource -> copy(methodInfo = updatedMethodInfo)
31+
is SerializedTaintMethodSink -> copy(methodInfo = updatedMethodInfo)
32+
is SerializedTaintMethodSource -> copy(methodInfo = updatedMethodInfo)
33+
is SerializedTaintPassThrough -> copy(methodInfo = updatedMethodInfo)
34+
}
35+
}
36+
37+
@Serializable
38+
@SerialName("EntryPointSource")
39+
data class SerializedTaintEntryPointSource(
40+
override val methodInfo: FunctionMatcher,
41+
val condition: Condition,
42+
val actionsAfter: List<Action>,
43+
) : SerializedTaintConfigurationItem
44+
45+
@Serializable
46+
@SerialName("MethodSource")
47+
data class SerializedTaintMethodSource(
48+
override val methodInfo: FunctionMatcher,
49+
val condition: Condition,
50+
val actionsAfter: List<Action>,
51+
) : SerializedTaintConfigurationItem
52+
53+
@Serializable
54+
@SerialName("MethodSink")
55+
data class SerializedTaintMethodSink(
56+
val ruleNote: String,
57+
val cwe: List<Int>,
58+
override val methodInfo: FunctionMatcher,
59+
val condition: Condition
60+
) : SerializedTaintConfigurationItem
61+
62+
@Serializable
63+
@SerialName("PassThrough")
64+
data class SerializedTaintPassThrough(
65+
override val methodInfo: FunctionMatcher,
66+
val condition: Condition,
67+
val actionsAfter: List<Action>,
68+
) : SerializedTaintConfigurationItem
69+
70+
@Serializable
71+
@SerialName("Cleaner")
72+
data class SerializedTaintCleaner(
73+
override val methodInfo: FunctionMatcher,
74+
val condition: Condition,
75+
val actionsAfter: List<Action>,
76+
) : SerializedTaintConfigurationItem
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2022 UnitTestBot contributors (utbot.org)
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.jacodb.configuration
18+
19+
import kotlinx.serialization.SerialName
20+
import kotlinx.serialization.Serializable
21+
22+
interface TaintActionVisitor<R> {
23+
fun visit(action: CopyAllMarks): R
24+
fun visit(action: CopyMark): R
25+
fun visit(action: AssignMark): R
26+
fun visit(action: RemoveAllMarks): R
27+
fun visit(action: RemoveMark): R
28+
}
29+
30+
interface Action {
31+
fun <R> accept(visitor: TaintActionVisitor<R>): R
32+
}
33+
34+
// TODO add marks for aliases (if you pass an object and return it from the function)
35+
@Serializable
36+
@SerialName("CopyAllMarks")
37+
data class CopyAllMarks(val from: Position, val to: Position) : Action {
38+
override fun <R> accept(visitor: TaintActionVisitor<R>): R = visitor.visit(this)
39+
}
40+
41+
@Serializable
42+
@SerialName("AssignMark")
43+
data class AssignMark(val position: Position, val mark: TaintMark) : Action {
44+
override fun <R> accept(visitor: TaintActionVisitor<R>): R = visitor.visit(this)
45+
}
46+
47+
@Serializable
48+
@SerialName("RemoveAllMarks")
49+
data class RemoveAllMarks(val position: Position) : Action {
50+
override fun <R> accept(visitor: TaintActionVisitor<R>): R = visitor.visit(this)
51+
}
52+
53+
@Serializable
54+
@SerialName("RemoveMark")
55+
data class RemoveMark(val position: Position, val mark: TaintMark) : Action {
56+
override fun <R> accept(visitor: TaintActionVisitor<R>): R = visitor.visit(this)
57+
}
58+
59+
@Serializable
60+
@SerialName("CopyMark")
61+
data class CopyMark(val from: Position, val to: Position, val mark: TaintMark) : Action {
62+
override fun <R> accept(visitor: TaintActionVisitor<R>): R = visitor.visit(this)
63+
}

0 commit comments

Comments
 (0)