-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
1 parent
ed17f6b
commit 1772af5
Showing
17 changed files
with
2,167 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
plugins { | ||
id("java") | ||
kotlin("plugin.serialization") | ||
} | ||
|
||
repositories { | ||
mavenCentral() | ||
} | ||
|
||
dependencies { | ||
implementation(project(":jacodb-api")) | ||
implementation(project(":jacodb-core")) | ||
implementation(testFixtures(project(":jacodb-core"))) | ||
implementation(Libs.kotlinx_serialization_json) | ||
|
||
testImplementation(group = "io.github.microutils", name = "kotlin-logging", version = "1.8.3") | ||
} | ||
|
||
tasks.test { | ||
useJUnitPlatform() | ||
} |
165 changes: 165 additions & 0 deletions
165
jacodb-taint-configuration/src/main/kotlin/org/jacodb/configuration/ConfigurationTrie.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
/* | ||
* Copyright 2022 UnitTestBot contributors (utbot.org) | ||
* <p> | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* <p> | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* <p> | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package org.jacodb.configuration | ||
|
||
import org.jacodb.api.JcClassOrInterface | ||
import org.jacodb.api.ext.packageName | ||
|
||
class ConfigurationTrie( | ||
configuration: List<SerializedTaintConfigurationItem>, | ||
private val nameMatcher: (NameMatcher, String) -> Boolean | ||
) { | ||
private val unprocessedRules: MutableList<SerializedTaintConfigurationItem> = configuration.toMutableList() | ||
private val rootNode: RootNode = RootNode() | ||
|
||
private fun initializeIfRequired() { | ||
if (unprocessedRules.isEmpty()) return | ||
|
||
while (unprocessedRules.isNotEmpty()) { | ||
var configurationRule = unprocessedRules.removeLast() | ||
val classMatcher = configurationRule.methodInfo.cls | ||
|
||
val alternativeClassMatchers = classMatcher.extractAlternatives() | ||
if (alternativeClassMatchers.size != 1) { | ||
alternativeClassMatchers.forEach { | ||
val updatedMethodInfo = configurationRule.methodInfo.copy(cls = it) | ||
unprocessedRules += configurationRule.updateMethodInfo(updatedMethodInfo) | ||
} | ||
|
||
continue | ||
} | ||
|
||
val simplifiedClassMatcher = alternativeClassMatchers.single() | ||
val updatedMethodInfo = configurationRule.methodInfo.copy(cls = simplifiedClassMatcher) | ||
configurationRule = configurationRule.updateMethodInfo(updatedMethodInfo) | ||
|
||
var currentNode: Node = rootNode | ||
|
||
val (simplifiedPkgMatcher, simplifiedClassNameMatcher) = simplifiedClassMatcher | ||
|
||
var matchedPackageNameParts = emptyList<String>() | ||
var unmatchedPackageNamePart: String? = null | ||
|
||
when (simplifiedPkgMatcher) { | ||
AnyNameMatcher -> { | ||
currentNode.unmatchedRules += configurationRule | ||
continue | ||
} | ||
|
||
is NameExactMatcher -> matchedPackageNameParts = simplifiedPkgMatcher.name.split(DOT_DELIMITER) | ||
is NamePatternMatcher -> { | ||
val (matchedParts, unmatchedParts) = simplifiedPkgMatcher.splitRegex() | ||
matchedPackageNameParts = matchedParts | ||
unmatchedPackageNamePart = unmatchedParts | ||
} | ||
} | ||
|
||
for (part in matchedPackageNameParts) { | ||
currentNode = currentNode.children[part] ?: NodeImpl(part).also { currentNode.children += part to it } | ||
} | ||
|
||
if (unmatchedPackageNamePart != null && unmatchedPackageNamePart != ALL_MATCH) { | ||
currentNode.unmatchedRules += configurationRule | ||
continue | ||
} | ||
|
||
when (simplifiedClassNameMatcher) { | ||
AnyNameMatcher -> currentNode.rules += configurationRule | ||
|
||
is NameExactMatcher -> if (unmatchedPackageNamePart == null) { | ||
val name = simplifiedClassNameMatcher.name | ||
currentNode = currentNode.children[name] ?: Leaf(name).also { currentNode.children += name to it } | ||
currentNode.rules += configurationRule | ||
} else { | ||
// case for patterns like ".*\.Request" | ||
currentNode.unmatchedRules += configurationRule | ||
} | ||
|
||
is NamePatternMatcher -> { | ||
val classPattern = simplifiedClassNameMatcher.pattern | ||
|
||
if (classPattern == ALL_MATCH) { | ||
currentNode.rules += configurationRule | ||
continue | ||
} | ||
|
||
currentNode.unmatchedRules += configurationRule | ||
} | ||
} | ||
} | ||
} | ||
|
||
fun getRulesForClass(clazz: JcClassOrInterface): List<SerializedTaintConfigurationItem> { | ||
initializeIfRequired() | ||
|
||
val results = mutableListOf<SerializedTaintConfigurationItem>() | ||
|
||
val className = clazz.simpleName | ||
val packageName = clazz.packageName | ||
val nameParts = clazz.name.split(DOT_DELIMITER) | ||
|
||
var currentNode: Node = rootNode | ||
|
||
for (i in 0..nameParts.size) { | ||
results += currentNode.unmatchedRules.filter { | ||
val classMatcher = it.methodInfo.cls | ||
nameMatcher(classMatcher.pkg, packageName) && nameMatcher(classMatcher.classNameMatcher, className) | ||
} | ||
|
||
results += currentNode.rules | ||
|
||
// We must process rules containing in the leaf, therefore, we have to spin one more iteration | ||
currentNode = nameParts.getOrNull(i)?.let { currentNode.children[it] } ?: break | ||
} | ||
|
||
return results | ||
} | ||
|
||
private sealed class Node { | ||
abstract val value: String | ||
abstract val children: MutableMap<String, Node> | ||
abstract val rules: MutableList<SerializedTaintConfigurationItem> | ||
abstract val unmatchedRules: MutableList<SerializedTaintConfigurationItem> | ||
} | ||
|
||
private class RootNode : Node() { | ||
override val children: MutableMap<String, Node> = mutableMapOf() | ||
override val value: String | ||
get() = error("Must not be called for the root") | ||
override val rules: MutableList<SerializedTaintConfigurationItem> = mutableListOf() | ||
override val unmatchedRules: MutableList<SerializedTaintConfigurationItem> = mutableListOf() | ||
} | ||
|
||
private data class NodeImpl( | ||
override val value: String, | ||
) : Node() { | ||
override val children: MutableMap<String, Node> = mutableMapOf() | ||
override val rules: MutableList<SerializedTaintConfigurationItem> = mutableListOf() | ||
override val unmatchedRules: MutableList<SerializedTaintConfigurationItem> = mutableListOf() | ||
} | ||
|
||
private data class Leaf( | ||
override val value: String | ||
) : Node() { | ||
override val children: MutableMap<String, Node> | ||
get() = error("Leaf nodes do not have children") | ||
override val unmatchedRules: MutableList<SerializedTaintConfigurationItem> | ||
get() = mutableListOf() | ||
|
||
override val rules: MutableList<SerializedTaintConfigurationItem> = mutableListOf() | ||
} | ||
} |
43 changes: 43 additions & 0 deletions
43
jacodb-taint-configuration/src/main/kotlin/org/jacodb/configuration/Position.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/* | ||
* Copyright 2022 UnitTestBot contributors (utbot.org) | ||
* <p> | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* <p> | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* <p> | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package org.jacodb.configuration | ||
|
||
import kotlinx.serialization.SerialName | ||
import kotlinx.serialization.Serializable | ||
|
||
interface PositionResolver<R> { | ||
fun resolve(position: Position): R | ||
} | ||
|
||
@Serializable | ||
sealed interface Position | ||
|
||
@Serializable | ||
@SerialName("Argument") | ||
data class Argument(val number: Int) : Position | ||
|
||
@Serializable | ||
@SerialName("AnyArgument") | ||
object AnyArgument : Position | ||
|
||
@Serializable | ||
@SerialName("This") | ||
object ThisArgument : Position | ||
|
||
@Serializable | ||
@SerialName("Result") | ||
object Result : Position |
76 changes: 76 additions & 0 deletions
76
...onfiguration/src/main/kotlin/org/jacodb/configuration/SerializedTaintConfigurationItem.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
/* | ||
* Copyright 2022 UnitTestBot contributors (utbot.org) | ||
* <p> | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* <p> | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* <p> | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package org.jacodb.configuration | ||
|
||
import kotlinx.serialization.SerialName | ||
import kotlinx.serialization.Serializable | ||
|
||
|
||
@Serializable | ||
sealed interface SerializedTaintConfigurationItem { | ||
val methodInfo: FunctionMatcher | ||
|
||
fun updateMethodInfo(updatedMethodInfo: FunctionMatcher): SerializedTaintConfigurationItem = | ||
when (this) { | ||
is SerializedTaintCleaner -> copy(methodInfo = updatedMethodInfo) | ||
is SerializedTaintEntryPointSource -> copy(methodInfo = updatedMethodInfo) | ||
is SerializedTaintMethodSink -> copy(methodInfo = updatedMethodInfo) | ||
is SerializedTaintMethodSource -> copy(methodInfo = updatedMethodInfo) | ||
is SerializedTaintPassThrough -> copy(methodInfo = updatedMethodInfo) | ||
} | ||
} | ||
|
||
@Serializable | ||
@SerialName("EntryPointSource") | ||
data class SerializedTaintEntryPointSource( | ||
override val methodInfo: FunctionMatcher, | ||
val condition: Condition, | ||
val actionsAfter: List<Action>, | ||
) : SerializedTaintConfigurationItem | ||
|
||
@Serializable | ||
@SerialName("MethodSource") | ||
data class SerializedTaintMethodSource( | ||
override val methodInfo: FunctionMatcher, | ||
val condition: Condition, | ||
val actionsAfter: List<Action>, | ||
) : SerializedTaintConfigurationItem | ||
|
||
@Serializable | ||
@SerialName("MethodSink") | ||
data class SerializedTaintMethodSink( | ||
val ruleNote: String, | ||
val cwe: List<Int>, | ||
override val methodInfo: FunctionMatcher, | ||
val condition: Condition | ||
) : SerializedTaintConfigurationItem | ||
|
||
@Serializable | ||
@SerialName("PassThrough") | ||
data class SerializedTaintPassThrough( | ||
override val methodInfo: FunctionMatcher, | ||
val condition: Condition, | ||
val actionsAfter: List<Action>, | ||
) : SerializedTaintConfigurationItem | ||
|
||
@Serializable | ||
@SerialName("Cleaner") | ||
data class SerializedTaintCleaner( | ||
override val methodInfo: FunctionMatcher, | ||
val condition: Condition, | ||
val actionsAfter: List<Action>, | ||
) : SerializedTaintConfigurationItem |
63 changes: 63 additions & 0 deletions
63
jacodb-taint-configuration/src/main/kotlin/org/jacodb/configuration/TaintAction.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
/* | ||
* Copyright 2022 UnitTestBot contributors (utbot.org) | ||
* <p> | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* <p> | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* <p> | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package org.jacodb.configuration | ||
|
||
import kotlinx.serialization.SerialName | ||
import kotlinx.serialization.Serializable | ||
|
||
interface TaintActionVisitor<R> { | ||
fun visit(action: CopyAllMarks): R | ||
fun visit(action: CopyMark): R | ||
fun visit(action: AssignMark): R | ||
fun visit(action: RemoveAllMarks): R | ||
fun visit(action: RemoveMark): R | ||
} | ||
|
||
interface Action { | ||
fun <R> accept(visitor: TaintActionVisitor<R>): R | ||
} | ||
|
||
// TODO add marks for aliases (if you pass an object and return it from the function) | ||
@Serializable | ||
@SerialName("CopyAllMarks") | ||
data class CopyAllMarks(val from: Position, val to: Position) : Action { | ||
override fun <R> accept(visitor: TaintActionVisitor<R>): R = visitor.visit(this) | ||
} | ||
|
||
@Serializable | ||
@SerialName("AssignMark") | ||
data class AssignMark(val position: Position, val mark: TaintMark) : Action { | ||
override fun <R> accept(visitor: TaintActionVisitor<R>): R = visitor.visit(this) | ||
} | ||
|
||
@Serializable | ||
@SerialName("RemoveAllMarks") | ||
data class RemoveAllMarks(val position: Position) : Action { | ||
override fun <R> accept(visitor: TaintActionVisitor<R>): R = visitor.visit(this) | ||
} | ||
|
||
@Serializable | ||
@SerialName("RemoveMark") | ||
data class RemoveMark(val position: Position, val mark: TaintMark) : Action { | ||
override fun <R> accept(visitor: TaintActionVisitor<R>): R = visitor.visit(this) | ||
} | ||
|
||
@Serializable | ||
@SerialName("CopyMark") | ||
data class CopyMark(val from: Position, val to: Position, val mark: TaintMark) : Action { | ||
override fun <R> accept(visitor: TaintActionVisitor<R>): R = visitor.visit(this) | ||
} |
Oops, something went wrong.