From 31acd51ac4d114d01c9a4ac6cac516be3c59c2e6 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 17 Jun 2024 11:52:55 +0200 Subject: [PATCH 01/17] introduce the Length Type to check the length of an argument --- .../cpg/coko/dsl/ImplementationDsl.kt | 61 +++++++++++++++---- .../coko/core/dsl/Types.kt | 3 + 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt index 9de960be9..149690afb 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt @@ -24,10 +24,13 @@ import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.* import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.* import de.fraunhofer.aisec.cpg.TranslationResult import de.fraunhofer.aisec.cpg.graph.* -import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.query.dataFlow import de.fraunhofer.aisec.cpg.query.executionPath +import de.fraunhofer.aisec.cpg.query.max +import de.fraunhofer.aisec.cpg.query.min +import de.fraunhofer.aisec.cpg.query.sizeof // // all functions/properties defined here must use CokoBackend @@ -160,21 +163,53 @@ context(CallExpression) * - If this is a [Node], we use the DFG of the CPG. */ infix fun Any.cpgFlowsTo(that: Collection): Boolean = - if (this is Wildcard) { - true - } else { - when (this) { - is String -> that.any { - val regex = Regex(this) - regex.matches((it as? Expression)?.evaluate()?.toString().orEmpty()) || regex.matches(it.code.orEmpty()) + when (this) { + is Wildcard -> true + is String -> that.any { + val regex = Regex(this) + regex.matches((it as? Expression)?.evaluate()?.toString().orEmpty()) || regex.matches(it.code.orEmpty()) + } + // Separate cases for IntRange and LongRange result in a huge performance boost for large ranges + is LongRange, is IntRange -> checkRange(that) + is Iterable<*> -> this.any { it?.cpgFlowsTo(that) ?: false } + is Array<*> -> this.any { it?.cpgFlowsTo(that) ?: false } + is Node -> that.any { dataFlow(this, it).value } + is ParameterGroup -> this.parameters.all { it?.cpgFlowsTo(that) ?: false } + is Length -> checkLength(that) + else -> this in that.map { (it as Expression).evaluate() } + } + +private fun Any.checkRange(that: Collection): Boolean { + when (this) { + // I would love to combine the following two cases, but any implementation loses the benefit of + // quickly reading the last value of the range, therefore making the whole distinction useless. + is IntRange -> { + return that.all { + val minValue = min(it).value.toInt() + val maxValue = max(it).value.toInt() + minValue > this.first && maxValue < this.last + } + } + is LongRange -> { + return that.all { + val minValue = min(it).value.toInt() + val maxValue = max(it).value.toInt() + minValue > this.first && maxValue < this.last } - is Iterable<*> -> this.any { it?.cpgFlowsTo(that) ?: false } - is Array<*> -> this.any { it?.cpgFlowsTo(that) ?: false } - is Node -> that.any { dataFlow(this, it).value } - is ParameterGroup -> this.parameters.all { it?.cpgFlowsTo(that) ?: false } - else -> this in that.map { (it as Expression).evaluate() } } + else -> throw IllegalArgumentException("Unexpected type") } +} + +private fun Length.checkLength(that: Collection): Boolean { + return that.all { + val size = sizeof(it).value + if (size == -1) { + // TODO: handle case where size could not be determined -> OPEN Finding + } + size in this.value + } +} context(CokoBackend) // TODO: better description diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Types.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Types.kt index c0363f54e..28c097db4 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Types.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Types.kt @@ -29,4 +29,7 @@ data class Type(val fqn: String) data class ParamWithType(val param: Any, val type: Type) +/** Marks that we want to check the length of the argument, not the contents */ +data class Length(val value: IntRange) + infix fun Any.withType(fqn: String) = ParamWithType(this, Type(fqn)) From d81132e9d6e0e40f56c4416b2ff46d55e4fcba14 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 24 Jun 2024 10:37:29 +0200 Subject: [PATCH 02/17] Add Length related tests --- .../{ => coko/dsl}/ImplementationDslTest.kt | 85 ++++++++++++++++--- .../evaluators}/FollowsEvaluationTest.kt | 4 +- .../evaluators}/NeverEvaluationTest.kt | 4 +- .../evaluators}/OnlyEvaluationTest.kt | 4 +- .../evaluators}/OrderEvaluationTest.kt | 3 +- .../ordering}/NfaDfaConstructionTest.kt | 5 +- .../ImplementationDslTest/LengthJavaFile.java | 42 +++++++++ 7 files changed, 126 insertions(+), 21 deletions(-) rename codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/{ => coko/dsl}/ImplementationDslTest.kt (50%) rename codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/{ => coko/evaluators}/FollowsEvaluationTest.kt (96%) rename codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/{ => coko/evaluators}/NeverEvaluationTest.kt (95%) rename codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/{ => coko/evaluators}/OnlyEvaluationTest.kt (93%) rename codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/{ => coko/evaluators}/OrderEvaluationTest.kt (98%) rename codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/{ => coko/ordering}/NfaDfaConstructionTest.kt (99%) create mode 100644 codyze-backends/cpg/src/test/resources/ImplementationDslTest/LengthJavaFile.java diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/ImplementationDslTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt similarity index 50% rename from codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/ImplementationDslTest.kt rename to codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt index 0dece26f6..a16b6d5a9 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/ImplementationDslTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt @@ -13,14 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.aisec.codyze.backends.cpg +package de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend -import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.cpgGetAllNodes -import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.cpgGetNodes -import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Wildcard -import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.op -import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.signature +import de.fraunhofer.aisec.codyze.backends.cpg.createCpgConfiguration +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.* import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test import kotlin.io.path.toPath @@ -36,7 +33,7 @@ class ImplementationDslTest { signature(2) } } - with(backend) { + with(simpleBackend) { val allNodes = op.cpgGetAllNodes() assertEquals( 5, @@ -53,7 +50,7 @@ class ImplementationDslTest { signature(1..10) } } - with(backend) { + with(simpleBackend) { val nodes = op.cpgGetNodes() assertEquals( 2, @@ -73,7 +70,7 @@ class ImplementationDslTest { } } } - with(backend) { + with(simpleBackend) { val nodes = op.cpgGetNodes() assertEquals( 3, @@ -83,19 +80,79 @@ class ImplementationDslTest { } } + @Test + fun `test Array Length`() { + val opX: MutableList = mutableListOf() + for (i in 0..3) { + opX += op { + "Foo.fun" { + signature( + Length(i..i) + ) + } + } + } + + val results = arrayOf(1, 0, 1, 2) + for (i in 0..3) { + with(lengthBackend) { + val nodes = opX[i].cpgGetNodes() + assertEquals( + results[i], + nodes.size, + "cpgGetNodes returned ${nodes.size} node(s) instead of ${results[i]} nodes for the Op: ${opX[i]}." + ) + } + } + } + + @Test + fun `test List Length`() { + val opX: MutableList = mutableListOf() + for (i in 0..3) { + opX += op { + "Foo.bar" { + signature( + Length(i..i) + ) + } + } + } + + val results = arrayOf(1, 0, 1, 2) + for (i in 0..3) { + with(lengthBackend) { + val nodes = opX[i].cpgGetNodes() + assertEquals( + results[i], + nodes.size, + "cpgGetNodes returned ${nodes.size} node(s) instead of ${results[i]} nodes for the Op: ${opX[i]}." + ) + } + } + } + companion object { - lateinit var backend: CokoCpgBackend + lateinit var simpleBackend: CokoCpgBackend + lateinit var lengthBackend: CokoCpgBackend @BeforeAll @JvmStatic fun startup() { val classLoader = ImplementationDslTest::class.java.classLoader - val testFileResource = classLoader.getResource("ImplementationDslTest/SimpleJavaFile.java") - assertNotNull(testFileResource) - val testFile = testFileResource.toURI().toPath() - backend = CokoCpgBackend(config = createCpgConfiguration(testFile)) + val simpleFileResource = classLoader.getResource("ImplementationDslTest/SimpleJavaFile.java") + val lengthFileResource = classLoader.getResource("ImplementationDslTest/LengthJavaFile.java") + + assertNotNull(simpleFileResource) + assertNotNull(lengthFileResource) + + val simpleFile = simpleFileResource.toURI().toPath() + val lengthFile = lengthFileResource.toURI().toPath() + + simpleBackend = CokoCpgBackend(config = createCpgConfiguration(simpleFile)) + lengthBackend = CokoCpgBackend(config = createCpgConfiguration(lengthFile)) } } } diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/FollowsEvaluationTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/FollowsEvaluationTest.kt similarity index 96% rename from codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/FollowsEvaluationTest.kt rename to codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/FollowsEvaluationTest.kt index 231f0b736..a0f43063f 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/FollowsEvaluationTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/FollowsEvaluationTest.kt @@ -13,10 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.aisec.codyze.backends.cpg +package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend import de.fraunhofer.aisec.codyze.backends.cpg.coko.CpgFinding +import de.fraunhofer.aisec.codyze.backends.cpg.createCpgConfiguration +import de.fraunhofer.aisec.codyze.backends.cpg.dummyRule import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Finding import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.definition diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/NeverEvaluationTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluationTest.kt similarity index 95% rename from codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/NeverEvaluationTest.kt rename to codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluationTest.kt index 615c531df..5068a2333 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/NeverEvaluationTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluationTest.kt @@ -13,9 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.aisec.codyze.backends.cpg +package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend +import de.fraunhofer.aisec.codyze.backends.cpg.createCpgConfiguration +import de.fraunhofer.aisec.codyze.backends.cpg.dummyRule import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Finding import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.definition diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/OnlyEvaluationTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluationTest.kt similarity index 93% rename from codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/OnlyEvaluationTest.kt rename to codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluationTest.kt index 5b99e2a31..7d4d3e394 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/OnlyEvaluationTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluationTest.kt @@ -13,9 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.aisec.codyze.backends.cpg +package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend +import de.fraunhofer.aisec.codyze.backends.cpg.createCpgConfiguration +import de.fraunhofer.aisec.codyze.backends.cpg.dummyRule import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Finding import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.definition diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/OrderEvaluationTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OrderEvaluationTest.kt similarity index 98% rename from codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/OrderEvaluationTest.kt rename to codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OrderEvaluationTest.kt index e7f8fa616..741230829 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/OrderEvaluationTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OrderEvaluationTest.kt @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.aisec.codyze.backends.cpg +package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend import de.fraunhofer.aisec.codyze.backends.cpg.coko.CpgFinding +import de.fraunhofer.aisec.codyze.backends.cpg.createCpgConfiguration import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.CokoBackend import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Evaluator diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/NfaDfaConstructionTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/ordering/NfaDfaConstructionTest.kt similarity index 99% rename from codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/NfaDfaConstructionTest.kt rename to codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/ordering/NfaDfaConstructionTest.kt index fabb881a0..b8baf492f 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/NfaDfaConstructionTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/ordering/NfaDfaConstructionTest.kt @@ -13,10 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.aisec.codyze.backends.cpg +package de.fraunhofer.aisec.codyze.backends.cpg.coko.ordering import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend -import de.fraunhofer.aisec.codyze.backends.cpg.coko.ordering.toNfa import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.* import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.ordering.* import de.fraunhofer.aisec.cpg.analysis.fsm.DFA @@ -48,7 +47,7 @@ class NfaDfaConstructionTest { } private val baseName = - "de.fraunhofer.aisec.codyze.backends.cpg.NfaDfaConstructionTest\$TestClass" + "de.fraunhofer.aisec.codyze.backends.cpg.coko.ordering.NfaDfaConstructionTest\$TestClass" private fun orderExpressionToNfa(block: Order.() -> Unit): NFA { val order = Order().apply(block) diff --git a/codyze-backends/cpg/src/test/resources/ImplementationDslTest/LengthJavaFile.java b/codyze-backends/cpg/src/test/resources/ImplementationDslTest/LengthJavaFile.java new file mode 100644 index 000000000..53b1ef7f4 --- /dev/null +++ b/codyze-backends/cpg/src/test/resources/ImplementationDslTest/LengthJavaFile.java @@ -0,0 +1,42 @@ +import java.util.List; +import java.lang.String; + +public class Length { + + public void one(int[] e) { + Foo f = new Foo(); + + int[] a = {1, 2, 3}; + int[] b = { }; + String[] c = {"a", "b", "c"}; + String[] d = {"ab", "c"}; + + f.fun(a); + f.fun(b); + f.fun(c); + f.fun(d); + f.fun(e); + } + + public void two(List e) { + Foo f = new Foo(); + + List a = List.of(1, 2, 3); + List b = List.of(); + List c = List.of("a", "b", "c"); + List c = List.of("ab", "c"); + + f.bar(a); + f.bar(b); + f.bar(c); + f.bar(d); + f.bar(e); + } +} + + +public class Foo { + public void fun(int[] a) {} + + public void bar(List l) {} +} From bb82ebdfbf87035d46646e6a73fb435a74fa7104 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 24 Jun 2024 12:52:16 +0200 Subject: [PATCH 03/17] rewrite cpgGetNodes to return the associated Result with the Node --- .../cpg/coko/dsl/ImplementationDsl.kt | 122 ++++++++++++------ 1 file changed, 86 insertions(+), 36 deletions(-) diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt index 149690afb..0f1186973 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt @@ -18,6 +18,7 @@ package de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl import de.fraunhofer.aisec.codyze.backends.cpg.coko.Nodes +import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result.* import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.CokoBackend import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.CokoMarker import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.* @@ -39,6 +40,40 @@ import de.fraunhofer.aisec.cpg.query.sizeof val CokoBackend.cpg: TranslationResult get() = this.backendData as TranslationResult +enum class Result { + VALID, + INVALID, + OPEN; + + companion object { + fun convert(from: Any?): Result { + return when(from) { + is Result -> from + is Boolean -> if (from) VALID else INVALID + else -> OPEN + } + } + } +} + +inline fun Iterable.allResult(predicate: (T) -> Result?): Result { + for (element in this) { + if (predicate(element) == OPEN) return OPEN + else if (predicate(element) == INVALID) return INVALID + } + return VALID +} + +inline fun Iterable.anyResult(predicate: (T) -> Result?): Result { + var openFlag = false + for (element in this) { + if (predicate(element) == VALID) return VALID + else if (predicate(element) == OPEN) openFlag = true + } + return if (openFlag) OPEN else INVALID +} + + /** Get all [Nodes] that are associated with this [Op]. */ context(CokoBackend) fun Op.cpgGetAllNodes(): Collection = @@ -63,26 +98,35 @@ fun Op.cpgGetAllNodes(): Collection = * [Definition]s. */ context(CokoBackend) -fun Op.cpgGetNodes(): Collection = +fun Op.cpgGetNodes(): Collection> = when (this@Op) { - is FunctionOp -> - this@Op.definitions - .flatMap { def -> - this@CokoBackend.cpgCallFqn(def.fqn) { - def.signatures.any { sig -> - cpgSignature(*sig.parameters.toTypedArray()) && - sig.unorderedParameters.all { it?.cpgFlowsTo(arguments) ?: false } - } + is FunctionOp -> { + val results = mutableListOf() + val fqn = this@Op.definitions.flatMap { + def -> + this@CokoBackend.cpgCallFqn(def.fqn) { + def.signatures.any { + sig -> + val result = sig.unorderedParameters.allResult { it?.cpgFlowsTo(arguments) } + results.add(result) + cpgSignature(*sig.parameters.toTypedArray()) && result != INVALID } } - is ConstructorOp -> - this@Op.signatures - .flatMap { sig -> + } + fqn.zip(results) + } + is ConstructorOp -> { + val results = mutableListOf() + val fqn = this@Op.signatures.flatMap { + sig -> this@CokoBackend.cpgConstructor(this@Op.classFqn) { - cpgSignature(*sig.parameters.toTypedArray()) && - sig.unorderedParameters.all { it?.cpgFlowsTo(arguments) ?: false } + val result = sig.unorderedParameters.allResult { it?.cpgFlowsTo(arguments) } + results.add(result) + cpgSignature(*sig.parameters.toTypedArray()) && result != INVALID } - } + } + fqn.zip(results) + } is GroupingOp -> this@Op.ops.flatMap { it.cpgGetNodes() } is ConditionalOp -> { val resultNodes = resultOp.cpgGetNodes() @@ -90,7 +134,7 @@ fun Op.cpgGetNodes(): Collection = resultNodes.filter { resultNode -> conditionNodes.any { conditionNode -> // TODO: Is it correct to use the EOG relationship here? - val result = executionPath(conditionNode, resultNode) + val result = executionPath(conditionNode.first, resultNode.first) result.value } } @@ -151,7 +195,7 @@ context(CallExpression) * - If this is a Collection, we check if at least one of the elements flows to [that] * - If this is a [Node], we use the DFG of the CPG. */ -infix fun Any.cpgFlowsTo(that: Node): Boolean = +infix fun Any.cpgFlowsTo(that: Node): Result = this.cpgFlowsTo(listOf(that)) // it should only be available in the context of a CallExpression @@ -162,22 +206,27 @@ context(CallExpression) * - If this is a Collection, we check if at least one of the elements flows to [that] * - If this is a [Node], we use the DFG of the CPG. */ -infix fun Any.cpgFlowsTo(that: Collection): Boolean = - when (this) { - is Wildcard -> true - is String -> that.any { - val regex = Regex(this) - regex.matches((it as? Expression)?.evaluate()?.toString().orEmpty()) || regex.matches(it.code.orEmpty()) +infix fun Any.cpgFlowsTo(that: Collection): Result = + Result.convert( + when (this) { + is Wildcard -> true + is String -> that.any { + val regex = Regex(this) + regex.matches((it as? Expression)?.evaluate()?.toString().orEmpty()) || regex.matches(it.code.orEmpty()) + } + // Separate cases for IntRange and LongRange result in a huge performance boost for large ranges + is LongRange, is IntRange -> checkRange(that) + is Iterable<*> -> this.any { it?.cpgFlowsTo(that) == VALID } + is Array<*> -> this.any { it?.cpgFlowsTo(that) == VALID } + is Node -> that.any { dataFlow(this, it).value } + is ParameterGroup -> this.parameters.all { + val flow = it?.cpgFlowsTo(that) + if (flow == OPEN) return OPEN else it?.cpgFlowsTo(that) == VALID + } + is Length -> checkLength(that) + else -> this in that.map { (it as Expression).evaluate() } } - // Separate cases for IntRange and LongRange result in a huge performance boost for large ranges - is LongRange, is IntRange -> checkRange(that) - is Iterable<*> -> this.any { it?.cpgFlowsTo(that) ?: false } - is Array<*> -> this.any { it?.cpgFlowsTo(that) ?: false } - is Node -> that.any { dataFlow(this, it).value } - is ParameterGroup -> this.parameters.all { it?.cpgFlowsTo(that) ?: false } - is Length -> checkLength(that) - else -> this in that.map { (it as Expression).evaluate() } - } + ) private fun Any.checkRange(that: Collection): Boolean { when (this) { @@ -201,14 +250,15 @@ private fun Any.checkRange(that: Collection): Boolean { } } -private fun Length.checkLength(that: Collection): Boolean { - return that.all { +private fun Length.checkLength(that: Collection): Result { + return Result.convert(that.all { val size = sizeof(it).value if (size == -1) { - // TODO: handle case where size could not be determined -> OPEN Finding + // Handle case where size could not be determined -> OPEN Finding + return Result.OPEN } size in this.value - } + }) } context(CokoBackend) From 46b86ea2bf67a7e580229b18bd77b4a346310e29 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Wed, 26 Jun 2024 10:18:29 +0200 Subject: [PATCH 04/17] move result to own file and rewrite cpgSignature to use results --- .../cpg/coko/dsl/ImplementationDsl.kt | 80 ++++++------------- .../codyze/backends/cpg/coko/dsl/Result.kt | 67 ++++++++++++++++ 2 files changed, 91 insertions(+), 56 deletions(-) create mode 100644 codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt index 0f1186973..7426c938f 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt @@ -18,7 +18,6 @@ package de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl import de.fraunhofer.aisec.codyze.backends.cpg.coko.Nodes -import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result.* import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.CokoBackend import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.CokoMarker import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.* @@ -32,6 +31,7 @@ import de.fraunhofer.aisec.cpg.query.executionPath import de.fraunhofer.aisec.cpg.query.max import de.fraunhofer.aisec.cpg.query.min import de.fraunhofer.aisec.cpg.query.sizeof +import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result.* // // all functions/properties defined here must use CokoBackend @@ -40,39 +40,6 @@ import de.fraunhofer.aisec.cpg.query.sizeof val CokoBackend.cpg: TranslationResult get() = this.backendData as TranslationResult -enum class Result { - VALID, - INVALID, - OPEN; - - companion object { - fun convert(from: Any?): Result { - return when(from) { - is Result -> from - is Boolean -> if (from) VALID else INVALID - else -> OPEN - } - } - } -} - -inline fun Iterable.allResult(predicate: (T) -> Result?): Result { - for (element in this) { - if (predicate(element) == OPEN) return OPEN - else if (predicate(element) == INVALID) return INVALID - } - return VALID -} - -inline fun Iterable.anyResult(predicate: (T) -> Result?): Result { - var openFlag = false - for (element in this) { - if (predicate(element) == VALID) return VALID - else if (predicate(element) == OPEN) openFlag = true - } - return if (openFlag) OPEN else INVALID -} - /** Get all [Nodes] that are associated with this [Op]. */ context(CokoBackend) @@ -107,9 +74,12 @@ fun Op.cpgGetNodes(): Collection> = this@CokoBackend.cpgCallFqn(def.fqn) { def.signatures.any { sig -> - val result = sig.unorderedParameters.allResult { it?.cpgFlowsTo(arguments) } - results.add(result) - cpgSignature(*sig.parameters.toTypedArray()) && result != INVALID + // We consider a result, when both the signature and the flow are not invalid + // However, if at least one of them is OPEN, we propagate this information to the caller + val signature = cpgSignature(*sig.parameters.toTypedArray()) + val flow = sig.unorderedParameters.allResult { it?.cpgFlowsTo(arguments) } + results.add(signature.and(flow)) + signature != INVALID && flow != INVALID } } } @@ -120,9 +90,10 @@ fun Op.cpgGetNodes(): Collection> = val fqn = this@Op.signatures.flatMap { sig -> this@CokoBackend.cpgConstructor(this@Op.classFqn) { - val result = sig.unorderedParameters.allResult { it?.cpgFlowsTo(arguments) } - results.add(result) - cpgSignature(*sig.parameters.toTypedArray()) && result != INVALID + val signature = cpgSignature(*sig.parameters.toTypedArray()) + val flow = sig.unorderedParameters.allResult { it?.cpgFlowsTo(arguments) } + results.add(signature.and(flow)) + signature != INVALID && flow != INVALID } } fqn.zip(results) @@ -216,13 +187,10 @@ infix fun Any.cpgFlowsTo(that: Collection): Result = } // Separate cases for IntRange and LongRange result in a huge performance boost for large ranges is LongRange, is IntRange -> checkRange(that) - is Iterable<*> -> this.any { it?.cpgFlowsTo(that) == VALID } - is Array<*> -> this.any { it?.cpgFlowsTo(that) == VALID } + is Iterable<*> -> this.anyResult { it?.cpgFlowsTo(that) } + is Array<*> -> this.anyResult { it?.cpgFlowsTo(that) } is Node -> that.any { dataFlow(this, it).value } - is ParameterGroup -> this.parameters.all { - val flow = it?.cpgFlowsTo(that) - if (flow == OPEN) return OPEN else it?.cpgFlowsTo(that) == VALID - } + is ParameterGroup -> this.parameters.allResult { it?.cpgFlowsTo(that) } is Length -> checkLength(that) else -> this in that.map { (it as Expression).evaluate() } } @@ -280,29 +248,29 @@ context(CokoBackend) * are not important to the analysis */ @Suppress("UnsafeCallOnNullableType") -fun CallExpression.cpgSignature(vararg parameters: Any?, hasVarargs: Boolean = false): Boolean { +fun CallExpression.cpgSignature(vararg parameters: Any?, hasVarargs: Boolean = false): Result { // checks if amount of parameters is the same as amount of arguments of this CallExpression - return cpgCheckArgsSize(parameters, hasVarargs) && + return cpgCheckArgsSize(parameters, hasVarargs).and( // checks if the CallExpression matches with the parameters - parameters.withIndex().all { (i: Int, parameter: Any?) -> + parameters.withIndex().allResult { (i: Int, parameter: Any?) -> when (parameter) { // if any parameter is null, signature returns false - null -> false + null -> INVALID is ParamWithType -> // if `parameter` is a `ParamWithType` object we want to check the type and // if there is dataflow - cpgCheckType(parameter.type, i) && - parameter.param cpgFlowsTo arguments[i] + cpgCheckType(parameter.type, i).and(parameter.param cpgFlowsTo arguments[i]) // checks if the type of the argument is the same - is Type -> cpgCheckType(parameter, i) + is Type -> Result.convert(cpgCheckType(parameter, i)) // check if any of the Nodes of the Op flow to the argument - is Op -> parameter.cpgGetNodes() cpgFlowsTo arguments[i] + is Op -> Result.convert(parameter.cpgGetNodes() cpgFlowsTo arguments[i]) // check if any of the Nodes of the DataItem flow to the argument - is DataItem<*> -> parameter.cpgGetNodes() cpgFlowsTo arguments[i] + is DataItem<*> -> Result.convert(parameter.cpgGetNodes() cpgFlowsTo arguments[i]) // checks if there is dataflow from the parameter to the argument in the same position - else -> parameter cpgFlowsTo arguments[i] + else -> Result.convert(parameter cpgFlowsTo arguments[i]) } } + ) } /** Checks the [type] against the type of the argument at [index] for the Call Expression */ diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt new file mode 100644 index 000000000..75d8dd260 --- /dev/null +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt @@ -0,0 +1,67 @@ +package de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl + +import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result.* + +enum class Result { + VALID, + INVALID, + OPEN; + + companion object { + fun convert(from: Any?): Result { + return when(from) { + is Result -> from + is Boolean -> if (from) VALID else INVALID + else -> OPEN + } + } + } +} + +inline fun Iterable.allResult(predicate: (T) -> Result?): Result { + for (element in this) { + if (predicate(element) == OPEN) return OPEN + else if (predicate(element) == INVALID) return INVALID + } + return VALID +} + +inline fun Iterable.anyResult(predicate: (T) -> Result?): Result { + var openFlag = false + for (element in this) { + if (predicate(element) == VALID) return VALID + else if (predicate(element) == OPEN) openFlag = true + } + return if (openFlag) OPEN else INVALID +} + +inline fun Array.allResult(predicate: (T) -> Result?): Result { + for (element in this) { + if (predicate(element) == OPEN) return OPEN + else if (predicate(element) == INVALID) return INVALID + } + return VALID +} + +inline fun Array.anyResult(predicate: (T) -> Result?): Result { + var openFlag = false + for (element in this) { + if (predicate(element) == VALID) return VALID + else if (predicate(element) == OPEN) openFlag = true + } + return if (openFlag) OPEN else INVALID +} + +fun Result.and(other: Result): Result { + return if (this == OPEN || other == OPEN) OPEN + else if (this == INVALID || other == INVALID) INVALID + else VALID +} + +fun Boolean.and(other: Result): Result { + return Companion.convert(this).and(other) +} + +fun Result.and(other: Boolean): Result { + return this.and(Companion.convert(other)) +} \ No newline at end of file From 1cb9af503ccf6238225eb5f8af48e725bf33d7d3 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 1 Jul 2024 11:48:46 +0200 Subject: [PATCH 05/17] have cpgGetNodes return a Map and adapt the Evaluators --- .../cpg/coko/dsl/ImplementationDsl.kt | 10 ++++----- .../coko/evaluators/CpgWheneverEvaluator.kt | 2 +- .../cpg/coko/evaluators/FollowsEvaluator.kt | 4 ++-- .../cpg/coko/evaluators/NeverEvaluator.kt | 14 ++++++++++-- .../cpg/coko/evaluators/OnlyEvaluator.kt | 22 ++++++++++++++----- .../cpg/coko/evaluators/OrderEvaluator.kt | 2 +- 6 files changed, 38 insertions(+), 16 deletions(-) diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt index 7426c938f..9e45a6358 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt @@ -65,7 +65,7 @@ fun Op.cpgGetAllNodes(): Collection = * [Definition]s. */ context(CokoBackend) -fun Op.cpgGetNodes(): Collection> = +fun Op.cpgGetNodes(): Map = when (this@Op) { is FunctionOp -> { val results = mutableListOf() @@ -83,7 +83,7 @@ fun Op.cpgGetNodes(): Collection> = } } } - fqn.zip(results) + fqn.zip(results).toMap() } is ConstructorOp -> { val results = mutableListOf() @@ -96,16 +96,16 @@ fun Op.cpgGetNodes(): Collection> = signature != INVALID && flow != INVALID } } - fqn.zip(results) + fqn.zip(results).toMap() } - is GroupingOp -> this@Op.ops.flatMap { it.cpgGetNodes() } + is GroupingOp -> this@Op.ops.flatMap { it.cpgGetNodes().entries }.associate { it.toPair() } is ConditionalOp -> { val resultNodes = resultOp.cpgGetNodes() val conditionNodes = conditionOp.cpgGetNodes() resultNodes.filter { resultNode -> conditionNodes.any { conditionNode -> // TODO: Is it correct to use the EOG relationship here? - val result = executionPath(conditionNode.first, resultNode.first) + val result = executionPath(conditionNode.key, resultNode.key) result.value } } diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/CpgWheneverEvaluator.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/CpgWheneverEvaluator.kt index c6595faa0..088e5eb1e 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/CpgWheneverEvaluator.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/CpgWheneverEvaluator.kt @@ -331,7 +331,7 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem callConditionComponent: CallConditionComponent, premiseNode: Node? = null ): EvaluationResult { - val callNodes = callConditionComponent.op.cpgGetNodes().filterWithDistanceToPremise(premiseNode) + val callNodes = callConditionComponent.op.cpgGetNodes().keys.filterWithDistanceToPremise(premiseNode) return EvaluationResult(callNodes, emptyList(), Problems()) } diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/FollowsEvaluator.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/FollowsEvaluator.kt index 898ef1e45..659c49725 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/FollowsEvaluator.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/FollowsEvaluator.kt @@ -38,10 +38,10 @@ class FollowsEvaluator(val ifOp: Op, val thenOp: Op) : Evaluator { override fun evaluate(context: EvaluationContext): List { val (unreachableThisNodes, thisNodes) = - with(this@CokoCpgBackend) { ifOp.cpgGetNodes().toSet() } + with(this@CokoCpgBackend) { ifOp.cpgGetNodes().keys } .partition { it.isUnreachable() } - val thatNodes = with(this@CokoCpgBackend) { thenOp.cpgGetNodes().toSet() } + val thatNodes = with(this@CokoCpgBackend) { thenOp.cpgGetNodes().keys } val findings = mutableListOf() diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluator.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluator.kt index 5b0cf63da..7bed28b7c 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluator.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluator.kt @@ -17,6 +17,7 @@ package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend import de.fraunhofer.aisec.codyze.backends.cpg.coko.CpgFinding +import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.cpgGetNodes import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Evaluator @@ -51,11 +52,20 @@ class NeverEvaluator(val forbiddenOps: List) : Evaluator { if (nodes.isNotEmpty()) { // This means there are calls to the forbidden op, so Fail findings are added for (node in nodes) { + if (node.value == Result.OPEN) { + findings.add( + CpgFinding( + message = "Not enough information to evaluate \"${node.key.code}\"", + kind = Finding.Kind.Open, + node = node.key + ) + ) + } findings.add( CpgFinding( - message = "Violation against rule: \"${node.code}\". $failMessage", + message = "Violation against rule: \"${node.key.code}\". $failMessage", kind = Finding.Kind.Fail, - node = node + node = node.key ) ) } diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluator.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluator.kt index bec29b8ec..780ded2c6 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluator.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluator.kt @@ -17,6 +17,7 @@ package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend import de.fraunhofer.aisec.codyze.backends.cpg.coko.CpgFinding +import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.cpgGetAllNodes import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.cpgGetNodes import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext @@ -39,9 +40,11 @@ class OnlyEvaluator(val ops: List) : Evaluator { private val defaultPassMessage = "Call is in compliance with rule" override fun evaluate(context: EvaluationContext): List { - val correctNodes = - with(this@CokoCpgBackend) { ops.flatMap { it.cpgGetNodes() } } - .toSet() + + val correctAndOpen = with(this@CokoCpgBackend) { + ops.flatMap { it.cpgGetNodes().entries }.associate { it.toPair() } + } + val correctAndOpenNodes = correctAndOpen.keys.toSet() val distinctOps = ops.toSet() val allNodes = @@ -50,7 +53,7 @@ class OnlyEvaluator(val ops: List) : Evaluator { // `correctNodes` is a subset of `allNodes` // we want to find nodes in `allNodes` that are not contained in `correctNodes` since they are violations - val violatingNodes = allNodes.minus(correctNodes) + val violatingNodes = allNodes.minus(correctAndOpenNodes) val ruleAnnotation = context.rule.findAnnotation() val failMessage = ruleAnnotation?.failMessage?.takeIf { it.isNotEmpty() } ?: defaultFailMessage @@ -67,7 +70,16 @@ class OnlyEvaluator(val ops: List) : Evaluator { ) } - for (node in correctNodes) { + for (node in correctAndOpenNodes) { + if (correctAndOpen[node] == Result.OPEN) { + findings.add( + CpgFinding( + message = "Not enough information to evaluate \"${node.code}\"", + kind = Finding.Kind.Open, + node = node + ) + ) + } findings.add( CpgFinding( message = "Complies with rule: \"${node.code}\". $passMessage", diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OrderEvaluator.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OrderEvaluator.kt index ef4132ce0..8de639bb5 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OrderEvaluator.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OrderEvaluator.kt @@ -153,7 +153,7 @@ class OrderEvaluator(val baseNodes: Collection, val order: Order) : Evalua // the nodes from +testObj.start(123) <- userDefined Ops are used // only allow the start nodes that take '123' as argument for ((_, op) in order.userDefinedOps.entries) { - val nodes = op.cpgGetNodes() + val nodes = op.cpgGetNodes().keys registerOpAndNodes(op, nodes, hashToMethod, nodesToOp) } From aee5df37cda3d6e12ee80f0ebfdfc91f4f00d978 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 1 Jul 2024 12:09:56 +0200 Subject: [PATCH 06/17] changes around new cpgGetNodes --- .../backends/cpg/coko/CokoCpgBackend.kt | 2 +- .../backends/cpg/coko/dsl/DataItemCpgDsl.kt | 4 ++-- .../cpg/coko/dsl/ImplementationDsl.kt | 2 +- .../codyze/backends/cpg/SignatureTest.kt | 20 +++++++++---------- .../cpg/coko/dsl/ImplementationDslTest.kt | 20 +++++++++++++++---- 5 files changed, 30 insertions(+), 18 deletions(-) diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/CokoCpgBackend.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/CokoCpgBackend.kt index a9533e0a8..34721ad3a 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/CokoCpgBackend.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/CokoCpgBackend.kt @@ -77,7 +77,7 @@ class CokoCpgBackend(config: BackendConfiguration) : */ override fun order(baseNodes: Op, block: Order.() -> Unit): OrderEvaluator = OrderEvaluator( - baseNodes = baseNodes.cpgGetNodes(), + baseNodes = baseNodes.cpgGetNodes().keys, order = Order().apply(block) ) diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/DataItemCpgDsl.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/DataItemCpgDsl.kt index 287ca5986..cc1f4d497 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/DataItemCpgDsl.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/DataItemCpgDsl.kt @@ -42,9 +42,9 @@ fun DataItem<*>.cpgGetAllNodes(): Nodes = context(CokoBackend) fun DataItem<*>.cpgGetNodes(): Nodes { return when (this@DataItem) { - is ReturnValueItem -> op.cpgGetNodes().flatMap { it.getVariableInNextDFGOrThis() } + is ReturnValueItem -> op.cpgGetNodes().flatMap { it.key.getVariableInNextDFGOrThis() } is Value -> this@DataItem.getNodes() - is ArgumentItem -> op.cpgGetNodes().map { it.arguments[index] } // TODO: Do we count starting at 0 or 1? + is ArgumentItem -> op.cpgGetNodes().map { it.key.arguments[index] } // TODO: Do we count starting at 0 or 1? } } diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt index 9e45a6358..4521779f5 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt @@ -223,7 +223,7 @@ private fun Length.checkLength(that: Collection): Result { val size = sizeof(it).value if (size == -1) { // Handle case where size could not be determined -> OPEN Finding - return Result.OPEN + return OPEN } size in this.value }) diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt index ce1508e7e..2859e6143 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt @@ -43,14 +43,14 @@ class SignatureTest { fun `test signature with wrong number of params`() { every { node.arguments } returns listOf() - assertFalse { with(backend) { with(node) { cpgSignature("test") } } } + assertFalse { with(backend) { with(node) { cpgSignature("test") == Result.VALID } } } } @Test fun `test signature with null`() { every { node.arguments } returns listOf(mockk()) - assertFalse { with(backend) { with(node) { cpgSignature(null) } } } + assertFalse { with(backend) { with(node) { cpgSignature(null) } == Result.VALID } } } @Test @@ -58,7 +58,7 @@ class SignatureTest { every { node.arguments } returns listOf(stringArgument) every { stringArgument.type.typeName } returns "kotlin.String" - assertTrue { with(backend) { with(node) { cpgSignature(Type("kotlin.String")) } } } + assertTrue { with(backend) { with(node) { cpgSignature(Type("kotlin.String")) } == Result.VALID } } } @Test @@ -66,7 +66,7 @@ class SignatureTest { every { node.arguments } returns listOf(stringArgument) every { stringArgument.type.typeName } returns "kotlin.String" - assertFalse { with(backend) { with(node) { cpgSignature(Type("kotlin.Int")) } } } + assertFalse { with(backend) { with(node) { cpgSignature(Type("kotlin.Int")) } } == Result.VALID } } @Test @@ -75,7 +75,7 @@ class SignatureTest { every { stringArgument.type.typeName } returns "kotlin.String" every { stringArgument.value } returns "test" - assertTrue { with(backend) { with(node) { cpgSignature("test" withType "kotlin.String") } } } + assertTrue { with(backend) { with(node) { cpgSignature("test" withType "kotlin.String") } } == Result.VALID } } @Test @@ -84,7 +84,7 @@ class SignatureTest { every { stringArgument.type.typeName } returns "kotlin.String" every { stringArgument.value } returns "test" - assertFalse { with(backend) { with(node) { cpgSignature("test" withType "kotlin.Int") } } } + assertFalse { with(backend) { with(node) { cpgSignature("test" withType "kotlin.Int") } } == Result.VALID } } @Test @@ -93,7 +93,7 @@ class SignatureTest { every { stringArgument.type.typeName } returns "kotlin.String" every { stringArgument.value } returns "test" - assertFalse { with(backend) { with(node) { cpgSignature(1 withType "kotlin.String") } } } + assertFalse { with(backend) { with(node) { cpgSignature(1 withType "kotlin.String") } } == Result.VALID } } @Test @@ -104,7 +104,7 @@ class SignatureTest { every { pairArgument.value } returns (1 to "one") mockkStatic("de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.ImplementationDslKt") - every { with(node) { param.cpgFlowsTo(pairArgument) } } returns true + every { with(node) { param.cpgFlowsTo(pairArgument) } } returns Result.VALID // tests that with normal pair only flowsTo is called with(backend) { with(node) { cpgSignature(param) } } @@ -118,7 +118,7 @@ class SignatureTest { every { stringArgument.value } returns "test" mockkStatic("de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.ImplementationDslKt") - every { with(node) { param.cpgFlowsTo(stringArgument) } } returns true + every { with(node) { param.cpgFlowsTo(stringArgument) } } returns Result.VALID // assert that signature checks the dataflow from the parameter to the argument with(backend) { with(node) { cpgSignature(param) } } @@ -134,7 +134,7 @@ class SignatureTest { val a = mockk>() args.add(a) every { a.value } returns "test" - every { with(node) { p.cpgFlowsTo(a) } } returns true + every { with(node) { p.cpgFlowsTo(a) } } returns Result.VALID } every { node.arguments } returns args diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt index a16b6d5a9..94c047989 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt @@ -97,10 +97,16 @@ class ImplementationDslTest { for (i in 0..3) { with(lengthBackend) { val nodes = opX[i].cpgGetNodes() + val validNodes = nodes.filter { it.value == Result.VALID } assertEquals( results[i], - nodes.size, - "cpgGetNodes returned ${nodes.size} node(s) instead of ${results[i]} nodes for the Op: ${opX[i]}." + validNodes.size, + "cpgGetNodes returned ${validNodes.size} node(s) instead of ${results[i]} nodes for the Op: ${opX[i]}." + ) + assertEquals( + 1, + nodes.filter { it.value == Result.OPEN }.size, + "cpgGetNodes did not return exactly one OPEN result as expected." ) } } @@ -123,10 +129,16 @@ class ImplementationDslTest { for (i in 0..3) { with(lengthBackend) { val nodes = opX[i].cpgGetNodes() + val validNodes = nodes.filter { it.value == Result.VALID } assertEquals( results[i], - nodes.size, - "cpgGetNodes returned ${nodes.size} node(s) instead of ${results[i]} nodes for the Op: ${opX[i]}." + validNodes.size, + "cpgGetNodes returned ${validNodes.size} node(s) instead of ${results[i]} nodes for the Op: ${opX[i]}." + ) + assertEquals( + 1, + nodes.filter { it.value == Result.OPEN }.size, + "cpgGetNodes did not return exactly one OPEN result as expected." ) } } From e2371a4fefaa10bb5de58d0f6731c7d1099a0063 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 1 Jul 2024 12:20:03 +0200 Subject: [PATCH 07/17] fixes to the signatureTest --- .../aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt | 6 +++--- .../fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt index 4521779f5..b166dd156 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt @@ -263,11 +263,11 @@ fun CallExpression.cpgSignature(vararg parameters: Any?, hasVarargs: Boolean = f // checks if the type of the argument is the same is Type -> Result.convert(cpgCheckType(parameter, i)) // check if any of the Nodes of the Op flow to the argument - is Op -> Result.convert(parameter.cpgGetNodes() cpgFlowsTo arguments[i]) + is Op -> parameter.cpgGetNodes() cpgFlowsTo arguments[i] // check if any of the Nodes of the DataItem flow to the argument - is DataItem<*> -> Result.convert(parameter.cpgGetNodes() cpgFlowsTo arguments[i]) + is DataItem<*> -> parameter.cpgGetNodes() cpgFlowsTo arguments[i] // checks if there is dataflow from the parameter to the argument in the same position - else -> Result.convert(parameter cpgFlowsTo arguments[i]) + else -> parameter cpgFlowsTo arguments[i] } } ) diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt index 2859e6143..de1362529 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt @@ -43,14 +43,14 @@ class SignatureTest { fun `test signature with wrong number of params`() { every { node.arguments } returns listOf() - assertFalse { with(backend) { with(node) { cpgSignature("test") == Result.VALID } } } + assertFalse { with(backend) { with(node) { cpgSignature("test") } } == Result.VALID } } @Test fun `test signature with null`() { every { node.arguments } returns listOf(mockk()) - assertFalse { with(backend) { with(node) { cpgSignature(null) } == Result.VALID } } + assertFalse { with(backend) { with(node) { cpgSignature(null) } } == Result.VALID } } @Test @@ -58,7 +58,7 @@ class SignatureTest { every { node.arguments } returns listOf(stringArgument) every { stringArgument.type.typeName } returns "kotlin.String" - assertTrue { with(backend) { with(node) { cpgSignature(Type("kotlin.String")) } == Result.VALID } } + assertTrue { with(backend) { with(node) { cpgSignature(Type("kotlin.String")) } } == Result.VALID } } @Test From 945768eb1f086ade5d541589be1fa23514a6faf6 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 1 Jul 2024 12:28:25 +0200 Subject: [PATCH 08/17] remove custom and implementation between Boolean and Result because of missing lazy evaluation --- .../codyze/backends/cpg/coko/dsl/ImplementationDsl.kt | 9 +++++---- .../aisec/codyze/backends/cpg/coko/dsl/Result.kt | 9 +-------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt index b166dd156..7b52a2f26 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt @@ -250,16 +250,16 @@ context(CokoBackend) @Suppress("UnsafeCallOnNullableType") fun CallExpression.cpgSignature(vararg parameters: Any?, hasVarargs: Boolean = false): Result { // checks if amount of parameters is the same as amount of arguments of this CallExpression - return cpgCheckArgsSize(parameters, hasVarargs).and( + if(cpgCheckArgsSize(parameters, hasVarargs)) { // checks if the CallExpression matches with the parameters - parameters.withIndex().allResult { (i: Int, parameter: Any?) -> + return parameters.withIndex().allResult { (i: Int, parameter: Any?) -> when (parameter) { // if any parameter is null, signature returns false null -> INVALID is ParamWithType -> // if `parameter` is a `ParamWithType` object we want to check the type and // if there is dataflow - cpgCheckType(parameter.type, i).and(parameter.param cpgFlowsTo arguments[i]) + if (cpgCheckType(parameter.type, i)) parameter.param cpgFlowsTo arguments[i] else INVALID // checks if the type of the argument is the same is Type -> Result.convert(cpgCheckType(parameter, i)) // check if any of the Nodes of the Op flow to the argument @@ -270,7 +270,8 @@ fun CallExpression.cpgSignature(vararg parameters: Any?, hasVarargs: Boolean = f else -> parameter cpgFlowsTo arguments[i] } } - ) + } + return INVALID } /** Checks the [type] against the type of the argument at [index] for the Call Expression */ diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt index 75d8dd260..0c3ef463d 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt @@ -52,16 +52,9 @@ inline fun Array.anyResult(predicate: (T) -> Result?): Result { return if (openFlag) OPEN else INVALID } +/** precedence order for ternary and: OPEN > INVALID > VALID */ fun Result.and(other: Result): Result { return if (this == OPEN || other == OPEN) OPEN else if (this == INVALID || other == INVALID) INVALID else VALID } - -fun Boolean.and(other: Result): Result { - return Companion.convert(this).and(other) -} - -fun Result.and(other: Boolean): Result { - return this.and(Companion.convert(other)) -} \ No newline at end of file From 300154292b6855d60d197e40d368156453bdd65b Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 1 Jul 2024 12:35:33 +0200 Subject: [PATCH 09/17] add code documentation --- .../codyze/backends/cpg/coko/dsl/Result.kt | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt index 0c3ef463d..11d98eb66 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt @@ -2,6 +2,11 @@ package de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result.* +/** + * A data class that serves as a ternary value for the analysis result. + * + * OPEN is used where we cannot deduce either VALID or INVALID results because of lack of information. + */ enum class Result { VALID, INVALID, @@ -18,14 +23,17 @@ enum class Result { } } +/** returns VALID if all Results are VALID, otherwise returns OPEN if any result is OPEN, otherwise returns INVALID */ inline fun Iterable.allResult(predicate: (T) -> Result?): Result { + var invalidFlag = false for (element in this) { if (predicate(element) == OPEN) return OPEN - else if (predicate(element) == INVALID) return INVALID + else if (predicate(element) == INVALID)invalidFlag = true } - return VALID + return if (invalidFlag) INVALID else VALID } +/** returns VALID if any Result is VALID, otherwise returns OPEN if any result is OPEN, otherwise returns INVALID */ inline fun Iterable.anyResult(predicate: (T) -> Result?): Result { var openFlag = false for (element in this) { @@ -35,14 +43,17 @@ inline fun Iterable.anyResult(predicate: (T) -> Result?): Result { return if (openFlag) OPEN else INVALID } +/** returns VALID if all Results are VALID, otherwise returns OPEN if any result is OPEN, otherwise returns INVALID */ inline fun Array.allResult(predicate: (T) -> Result?): Result { + var invalidFlag = false for (element in this) { if (predicate(element) == OPEN) return OPEN - else if (predicate(element) == INVALID) return INVALID + else if (predicate(element) == INVALID)invalidFlag = true } - return VALID + return if (invalidFlag) INVALID else VALID } +/** returns VALID if any Result is VALID, otherwise returns OPEN if any result is OPEN, otherwise returns INVALID */ inline fun Array.anyResult(predicate: (T) -> Result?): Result { var openFlag = false for (element in this) { From f9f02e9aeb48977ec94fa2d0859f191d843ea5c7 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Wed, 3 Jul 2024 10:55:10 +0200 Subject: [PATCH 10/17] fix code style issues --- .../cpg/coko/dsl/ImplementationDsl.kt | 62 +++++++++---------- .../codyze/backends/cpg/coko/dsl/Result.kt | 53 ++++++++++++---- .../cpg/coko/evaluators/NeverEvaluator.kt | 30 ++++----- .../cpg/coko/evaluators/OnlyEvaluator.kt | 1 - .../cpg/coko/dsl/ImplementationDslTest.kt | 6 +- 5 files changed, 92 insertions(+), 60 deletions(-) diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt index 7b52a2f26..bdd4c2cf6 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt @@ -18,20 +18,22 @@ package de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl import de.fraunhofer.aisec.codyze.backends.cpg.coko.Nodes +import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result.* import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.CokoBackend import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.CokoMarker import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.* -import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.* +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.DataItem +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.Definition +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.ParameterGroup +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.Signature import de.fraunhofer.aisec.cpg.TranslationResult import de.fraunhofer.aisec.cpg.graph.* -import de.fraunhofer.aisec.cpg.graph.declarations.* -import de.fraunhofer.aisec.cpg.graph.statements.expressions.* -import de.fraunhofer.aisec.cpg.query.dataFlow -import de.fraunhofer.aisec.cpg.query.executionPath -import de.fraunhofer.aisec.cpg.query.max -import de.fraunhofer.aisec.cpg.query.min -import de.fraunhofer.aisec.cpg.query.sizeof -import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result.* +import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.ConstructExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression +import de.fraunhofer.aisec.cpg.query.* // // all functions/properties defined here must use CokoBackend @@ -40,7 +42,6 @@ import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result.* val CokoBackend.cpg: TranslationResult get() = this.backendData as TranslationResult - /** Get all [Nodes] that are associated with this [Op]. */ context(CokoBackend) fun Op.cpgGetAllNodes(): Collection = @@ -69,11 +70,9 @@ fun Op.cpgGetNodes(): Map = when (this@Op) { is FunctionOp -> { val results = mutableListOf() - val fqn = this@Op.definitions.flatMap { - def -> - this@CokoBackend.cpgCallFqn(def.fqn) { - def.signatures.any { - sig -> + val fqn = this@Op.definitions.flatMap { def -> + this@CokoBackend.cpgCallFqn(def.fqn) { + def.signatures.any { sig -> // We consider a result, when both the signature and the flow are not invalid // However, if at least one of them is OPEN, we propagate this information to the caller val signature = cpgSignature(*sig.parameters.toTypedArray()) @@ -87,14 +86,13 @@ fun Op.cpgGetNodes(): Map = } is ConstructorOp -> { val results = mutableListOf() - val fqn = this@Op.signatures.flatMap { - sig -> - this@CokoBackend.cpgConstructor(this@Op.classFqn) { - val signature = cpgSignature(*sig.parameters.toTypedArray()) - val flow = sig.unorderedParameters.allResult { it?.cpgFlowsTo(arguments) } - results.add(signature.and(flow)) - signature != INVALID && flow != INVALID - } + val fqn = this@Op.signatures.flatMap { sig -> + this@CokoBackend.cpgConstructor(this@Op.classFqn) { + val signature = cpgSignature(*sig.parameters.toTypedArray()) + val flow = sig.unorderedParameters.allResult { it?.cpgFlowsTo(arguments) } + results.add(signature.and(flow)) + signature != INVALID && flow != INVALID + } } fqn.zip(results).toMap() } @@ -219,14 +217,16 @@ private fun Any.checkRange(that: Collection): Boolean { } private fun Length.checkLength(that: Collection): Result { - return Result.convert(that.all { - val size = sizeof(it).value - if (size == -1) { - // Handle case where size could not be determined -> OPEN Finding - return OPEN + return Result.convert( + that.all { + val size = sizeof(it).value + if (size == -1) { + // Handle case where size could not be determined -> OPEN Finding + return OPEN + } + size in this.value } - size in this.value - }) + ) } context(CokoBackend) @@ -250,7 +250,7 @@ context(CokoBackend) @Suppress("UnsafeCallOnNullableType") fun CallExpression.cpgSignature(vararg parameters: Any?, hasVarargs: Boolean = false): Result { // checks if amount of parameters is the same as amount of arguments of this CallExpression - if(cpgCheckArgsSize(parameters, hasVarargs)) { + if (cpgCheckArgsSize(parameters, hasVarargs)) { // checks if the CallExpression matches with the parameters return parameters.withIndex().allResult { (i: Int, parameter: Any?) -> when (parameter) { diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt index 11d98eb66..a07dfebd8 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024, Fraunhofer AISEC. All rights reserved. + * + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result.* @@ -14,7 +29,7 @@ enum class Result { companion object { fun convert(from: Any?): Result { - return when(from) { + return when (from) { is Result -> from is Boolean -> if (from) VALID else INVALID else -> OPEN @@ -27,8 +42,11 @@ enum class Result { inline fun Iterable.allResult(predicate: (T) -> Result?): Result { var invalidFlag = false for (element in this) { - if (predicate(element) == OPEN) return OPEN - else if (predicate(element) == INVALID)invalidFlag = true + if (predicate(element) == OPEN) { + return OPEN + } else if (predicate(element) == INVALID) { + invalidFlag = true + } } return if (invalidFlag) INVALID else VALID } @@ -37,8 +55,11 @@ inline fun Iterable.allResult(predicate: (T) -> Result?): Result { inline fun Iterable.anyResult(predicate: (T) -> Result?): Result { var openFlag = false for (element in this) { - if (predicate(element) == VALID) return VALID - else if (predicate(element) == OPEN) openFlag = true + if (predicate(element) == VALID) { + return VALID + } else if (predicate(element) == OPEN) { + openFlag = true + } } return if (openFlag) OPEN else INVALID } @@ -47,8 +68,9 @@ inline fun Iterable.anyResult(predicate: (T) -> Result?): Result { inline fun Array.allResult(predicate: (T) -> Result?): Result { var invalidFlag = false for (element in this) { - if (predicate(element) == OPEN) return OPEN - else if (predicate(element) == INVALID)invalidFlag = true + if (predicate(element) == OPEN) { + return OPEN + } else if (predicate(element) == INVALID)invalidFlag = true } return if (invalidFlag) INVALID else VALID } @@ -57,15 +79,22 @@ inline fun Array.allResult(predicate: (T) -> Result?): Result { inline fun Array.anyResult(predicate: (T) -> Result?): Result { var openFlag = false for (element in this) { - if (predicate(element) == VALID) return VALID - else if (predicate(element) == OPEN) openFlag = true + if (predicate(element) == VALID) { + return VALID + } else if (predicate(element) == OPEN) { + openFlag = true + } } return if (openFlag) OPEN else INVALID } /** precedence order for ternary and: OPEN > INVALID > VALID */ fun Result.and(other: Result): Result { - return if (this == OPEN || other == OPEN) OPEN - else if (this == INVALID || other == INVALID) INVALID - else VALID + return if (this == OPEN || other == OPEN) { + OPEN + } else if (this == INVALID || other == INVALID) { + INVALID + } else { + VALID + } } diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluator.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluator.kt index 7bed28b7c..36b92a5d1 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluator.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluator.kt @@ -49,26 +49,28 @@ class NeverEvaluator(val forbiddenOps: List) : Evaluator { for (op in forbiddenOps) { val nodes = op.cpgGetNodes() - if (nodes.isNotEmpty()) { - // This means there are calls to the forbidden op, so Fail findings are added - for (node in nodes) { - if (node.value == Result.OPEN) { - findings.add( - CpgFinding( - message = "Not enough information to evaluate \"${node.key.code}\"", - kind = Finding.Kind.Open, - node = node.key - ) - ) - } + if (nodes.isEmpty()) { + continue + } + + // This means there are calls to the forbidden op, so Fail findings are added + for (node in nodes) { + if (node.value == Result.OPEN) { findings.add( CpgFinding( - message = "Violation against rule: \"${node.key.code}\". $failMessage", - kind = Finding.Kind.Fail, + message = "Not enough information to evaluate \"${node.key.code}\"", + kind = Finding.Kind.Open, node = node.key ) ) } + findings.add( + CpgFinding( + message = "Violation against rule: \"${node.key.code}\". $failMessage", + kind = Finding.Kind.Fail, + node = node.key + ) + ) } } diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluator.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluator.kt index 780ded2c6..9865ec563 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluator.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluator.kt @@ -40,7 +40,6 @@ class OnlyEvaluator(val ops: List) : Evaluator { private val defaultPassMessage = "Call is in compliance with rule" override fun evaluate(context: EvaluationContext): List { - val correctAndOpen = with(this@CokoCpgBackend) { ops.flatMap { it.cpgGetNodes().entries }.associate { it.toPair() } } diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt index 94c047989..d4739e82b 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt @@ -101,7 +101,8 @@ class ImplementationDslTest { assertEquals( results[i], validNodes.size, - "cpgGetNodes returned ${validNodes.size} node(s) instead of ${results[i]} nodes for the Op: ${opX[i]}." + "cpgGetNodes returned ${validNodes.size} node(s) instead of ${results[i]} nodes " + + "for the Op: ${opX[i]}." ) assertEquals( 1, @@ -133,7 +134,8 @@ class ImplementationDslTest { assertEquals( results[i], validNodes.size, - "cpgGetNodes returned ${validNodes.size} node(s) instead of ${results[i]} nodes for the Op: ${opX[i]}." + "cpgGetNodes returned ${validNodes.size} node(s) instead of ${results[i]} nodes " + + "for the Op: ${opX[i]}." ) assertEquals( 1, From 9c03a5bb37dcc26253088e9e32d8ede6ce8b863d Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Wed, 3 Jul 2024 11:09:27 +0200 Subject: [PATCH 11/17] only add the result when the CallExpression is not invalid --- .../backends/cpg/coko/dsl/ImplementationDsl.kt | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt index bdd4c2cf6..64346e6b1 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt @@ -73,12 +73,15 @@ fun Op.cpgGetNodes(): Map = val fqn = this@Op.definitions.flatMap { def -> this@CokoBackend.cpgCallFqn(def.fqn) { def.signatures.any { sig -> - // We consider a result, when both the signature and the flow are not invalid - // However, if at least one of them is OPEN, we propagate this information to the caller + // We consider a result when both the signature and the flow are not invalid + // However, if at least one of them is OPEN we propagate this information to the caller val signature = cpgSignature(*sig.parameters.toTypedArray()) val flow = sig.unorderedParameters.allResult { it?.cpgFlowsTo(arguments) } - results.add(signature.and(flow)) - signature != INVALID && flow != INVALID + if (signature != INVALID && flow != INVALID) { + results.add(signature.and(flow)) + } else { + false + } } } } @@ -90,8 +93,11 @@ fun Op.cpgGetNodes(): Map = this@CokoBackend.cpgConstructor(this@Op.classFqn) { val signature = cpgSignature(*sig.parameters.toTypedArray()) val flow = sig.unorderedParameters.allResult { it?.cpgFlowsTo(arguments) } - results.add(signature.and(flow)) - signature != INVALID && flow != INVALID + if (signature != INVALID && flow != INVALID) { + results.add(signature.and(flow)) + } else { + false + } } } fqn.zip(results).toMap() From 5985bf991e3d57eb498308eb42879f5121c4b30f Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 8 Jul 2024 10:36:35 +0200 Subject: [PATCH 12/17] disable tests that rely on missing CPG features --- .../codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt index d4739e82b..10871daa1 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt @@ -19,6 +19,7 @@ import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend import de.fraunhofer.aisec.codyze.backends.cpg.createCpgConfiguration import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.* import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import kotlin.io.path.toPath import kotlin.test.assertEquals @@ -80,6 +81,7 @@ class ImplementationDslTest { } } + @Disabled("sizeof in CPG currently does not support InitListExpression as initializer") @Test fun `test Array Length`() { val opX: MutableList = mutableListOf() @@ -113,6 +115,7 @@ class ImplementationDslTest { } } + @Disabled("sizeof in CPG currently does not support MemberCallExpression as Initializer") @Test fun `test List Length`() { val opX: MutableList = mutableListOf() From 38049862afb66a519573fe8dae8e50762f7ee9fd Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Wed, 10 Jul 2024 10:56:21 +0200 Subject: [PATCH 13/17] remove all not-length-type related changes --- .../backends/cpg/coko/CokoCpgBackend.kt | 2 +- .../backends/cpg/coko/dsl/DataItemCpgDsl.kt | 4 +- .../cpg/coko/dsl/ImplementationDsl.kt | 92 ++++++---------- .../codyze/backends/cpg/coko/dsl/Result.kt | 100 ------------------ .../coko/evaluators/CpgWheneverEvaluator.kt | 2 +- .../cpg/coko/evaluators/FollowsEvaluator.kt | 4 +- .../cpg/coko/evaluators/NeverEvaluator.kt | 24 ++--- .../cpg/coko/evaluators/OnlyEvaluator.kt | 21 +--- .../cpg/coko/evaluators/OrderEvaluator.kt | 2 +- .../codyze/backends/cpg/SignatureTest.kt | 20 ++-- .../coko/evaluators/FollowsEvaluationTest.kt | 4 +- .../coko/evaluators/NeverEvaluationTest.kt | 4 +- .../cpg/coko/evaluators/OnlyEvaluationTest.kt | 4 +- .../coko/evaluators/OrderEvaluationTest.kt | 3 +- .../coko/ordering/NfaDfaConstructionTest.kt | 3 +- 15 files changed, 69 insertions(+), 220 deletions(-) delete mode 100644 codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/CokoCpgBackend.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/CokoCpgBackend.kt index 34721ad3a..a9533e0a8 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/CokoCpgBackend.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/CokoCpgBackend.kt @@ -77,7 +77,7 @@ class CokoCpgBackend(config: BackendConfiguration) : */ override fun order(baseNodes: Op, block: Order.() -> Unit): OrderEvaluator = OrderEvaluator( - baseNodes = baseNodes.cpgGetNodes().keys, + baseNodes = baseNodes.cpgGetNodes(), order = Order().apply(block) ) diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/DataItemCpgDsl.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/DataItemCpgDsl.kt index cc1f4d497..287ca5986 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/DataItemCpgDsl.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/DataItemCpgDsl.kt @@ -42,9 +42,9 @@ fun DataItem<*>.cpgGetAllNodes(): Nodes = context(CokoBackend) fun DataItem<*>.cpgGetNodes(): Nodes { return when (this@DataItem) { - is ReturnValueItem -> op.cpgGetNodes().flatMap { it.key.getVariableInNextDFGOrThis() } + is ReturnValueItem -> op.cpgGetNodes().flatMap { it.getVariableInNextDFGOrThis() } is Value -> this@DataItem.getNodes() - is ArgumentItem -> op.cpgGetNodes().map { it.key.arguments[index] } // TODO: Do we count starting at 0 or 1? + is ArgumentItem -> op.cpgGetNodes().map { it.arguments[index] } // TODO: Do we count starting at 0 or 1? } } diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt index 64346e6b1..f0c6dd6ef 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt @@ -18,22 +18,16 @@ package de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl import de.fraunhofer.aisec.codyze.backends.cpg.coko.Nodes -import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result.* import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.CokoBackend import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.CokoMarker import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.* -import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.DataItem -import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.Definition -import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.ParameterGroup -import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.Signature +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.* import de.fraunhofer.aisec.cpg.TranslationResult import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration -import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.ConstructExpression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression -import de.fraunhofer.aisec.cpg.query.* +import de.fraunhofer.aisec.cpg.graph.statements.expressions.* +import de.fraunhofer.aisec.cpg.query.dataFlow +import de.fraunhofer.aisec.cpg.query.executionPath // // all functions/properties defined here must use CokoBackend @@ -66,50 +60,34 @@ fun Op.cpgGetAllNodes(): Collection = * [Definition]s. */ context(CokoBackend) -fun Op.cpgGetNodes(): Map = +fun Op.cpgGetNodes(): Collection = when (this@Op) { - is FunctionOp -> { - val results = mutableListOf() - val fqn = this@Op.definitions.flatMap { def -> - this@CokoBackend.cpgCallFqn(def.fqn) { - def.signatures.any { sig -> - // We consider a result when both the signature and the flow are not invalid - // However, if at least one of them is OPEN we propagate this information to the caller - val signature = cpgSignature(*sig.parameters.toTypedArray()) - val flow = sig.unorderedParameters.allResult { it?.cpgFlowsTo(arguments) } - if (signature != INVALID && flow != INVALID) { - results.add(signature.and(flow)) - } else { - false + is FunctionOp -> + this@Op.definitions + .flatMap { def -> + this@CokoBackend.cpgCallFqn(def.fqn) { + def.signatures.any { sig -> + cpgSignature(*sig.parameters.toTypedArray()) && + sig.unorderedParameters.all { it?.cpgFlowsTo(arguments) ?: false } } } } - } - fqn.zip(results).toMap() - } - is ConstructorOp -> { - val results = mutableListOf() - val fqn = this@Op.signatures.flatMap { sig -> - this@CokoBackend.cpgConstructor(this@Op.classFqn) { - val signature = cpgSignature(*sig.parameters.toTypedArray()) - val flow = sig.unorderedParameters.allResult { it?.cpgFlowsTo(arguments) } - if (signature != INVALID && flow != INVALID) { - results.add(signature.and(flow)) - } else { - false + is ConstructorOp -> + this@Op.signatures + .flatMap { sig -> + this@CokoBackend.cpgConstructor(this@Op.classFqn) { + cpgSignature(*sig.parameters.toTypedArray()) && + sig.unorderedParameters.all { it?.cpgFlowsTo(arguments) ?: false } } } - } - fqn.zip(results).toMap() - } - is GroupingOp -> this@Op.ops.flatMap { it.cpgGetNodes().entries }.associate { it.toPair() } + is GroupingOp -> this@Op.ops.flatMap { it.cpgGetNodes() } is ConditionalOp -> { val resultNodes = resultOp.cpgGetNodes() val conditionNodes = conditionOp.cpgGetNodes() resultNodes.filter { resultNode -> conditionNodes.any { conditionNode -> // TODO: Is it correct to use the EOG relationship here? - val result = executionPath(conditionNode.key, resultNode.key) + val result = executionPath(conditionNode, resultNode) result.value } } @@ -170,7 +148,7 @@ context(CallExpression) * - If this is a Collection, we check if at least one of the elements flows to [that] * - If this is a [Node], we use the DFG of the CPG. */ -infix fun Any.cpgFlowsTo(that: Node): Result = +infix fun Any.cpgFlowsTo(that: Node): Boolean = this.cpgFlowsTo(listOf(that)) // it should only be available in the context of a CallExpression @@ -181,20 +159,21 @@ context(CallExpression) * - If this is a Collection, we check if at least one of the elements flows to [that] * - If this is a [Node], we use the DFG of the CPG. */ -infix fun Any.cpgFlowsTo(that: Collection): Result = - Result.convert( +infix fun Any.cpgFlowsTo(that: Collection): Boolean = + if (this is Wildcard) { + true + } else { when (this) { - is Wildcard -> true is String -> that.any { val regex = Regex(this) regex.matches((it as? Expression)?.evaluate()?.toString().orEmpty()) || regex.matches(it.code.orEmpty()) } // Separate cases for IntRange and LongRange result in a huge performance boost for large ranges is LongRange, is IntRange -> checkRange(that) - is Iterable<*> -> this.anyResult { it?.cpgFlowsTo(that) } - is Array<*> -> this.anyResult { it?.cpgFlowsTo(that) } + is Iterable<*> -> this.any { it?.cpgFlowsTo(that) ?: false } + is Array<*> -> this.any { it?.cpgFlowsTo(that) ?: false } is Node -> that.any { dataFlow(this, it).value } - is ParameterGroup -> this.parameters.allResult { it?.cpgFlowsTo(that) } + is ParameterGroup -> this.parameters.all { it?.cpgFlowsTo(that) ?: false } is Length -> checkLength(that) else -> this in that.map { (it as Expression).evaluate() } } @@ -254,20 +233,21 @@ context(CokoBackend) * are not important to the analysis */ @Suppress("UnsafeCallOnNullableType") -fun CallExpression.cpgSignature(vararg parameters: Any?, hasVarargs: Boolean = false): Result { +fun CallExpression.cpgSignature(vararg parameters: Any?, hasVarargs: Boolean = false): Boolean { // checks if amount of parameters is the same as amount of arguments of this CallExpression - if (cpgCheckArgsSize(parameters, hasVarargs)) { + return cpgCheckArgsSize(parameters, hasVarargs) && // checks if the CallExpression matches with the parameters - return parameters.withIndex().allResult { (i: Int, parameter: Any?) -> + parameters.withIndex().all { (i: Int, parameter: Any?) -> when (parameter) { // if any parameter is null, signature returns false - null -> INVALID + null -> false is ParamWithType -> // if `parameter` is a `ParamWithType` object we want to check the type and // if there is dataflow - if (cpgCheckType(parameter.type, i)) parameter.param cpgFlowsTo arguments[i] else INVALID + cpgCheckType(parameter.type, i) && + parameter.param cpgFlowsTo arguments[i] // checks if the type of the argument is the same - is Type -> Result.convert(cpgCheckType(parameter, i)) + is Type -> cpgCheckType(parameter, i) // check if any of the Nodes of the Op flow to the argument is Op -> parameter.cpgGetNodes() cpgFlowsTo arguments[i] // check if any of the Nodes of the DataItem flow to the argument @@ -276,8 +256,6 @@ fun CallExpression.cpgSignature(vararg parameters: Any?, hasVarargs: Boolean = f else -> parameter cpgFlowsTo arguments[i] } } - } - return INVALID } /** Checks the [type] against the type of the argument at [index] for the Call Expression */ diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt deleted file mode 100644 index a07dfebd8..000000000 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (c) 2024, Fraunhofer AISEC. All rights reserved. - * - * 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 - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * 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 de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl - -import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result.* - -/** - * A data class that serves as a ternary value for the analysis result. - * - * OPEN is used where we cannot deduce either VALID or INVALID results because of lack of information. - */ -enum class Result { - VALID, - INVALID, - OPEN; - - companion object { - fun convert(from: Any?): Result { - return when (from) { - is Result -> from - is Boolean -> if (from) VALID else INVALID - else -> OPEN - } - } - } -} - -/** returns VALID if all Results are VALID, otherwise returns OPEN if any result is OPEN, otherwise returns INVALID */ -inline fun Iterable.allResult(predicate: (T) -> Result?): Result { - var invalidFlag = false - for (element in this) { - if (predicate(element) == OPEN) { - return OPEN - } else if (predicate(element) == INVALID) { - invalidFlag = true - } - } - return if (invalidFlag) INVALID else VALID -} - -/** returns VALID if any Result is VALID, otherwise returns OPEN if any result is OPEN, otherwise returns INVALID */ -inline fun Iterable.anyResult(predicate: (T) -> Result?): Result { - var openFlag = false - for (element in this) { - if (predicate(element) == VALID) { - return VALID - } else if (predicate(element) == OPEN) { - openFlag = true - } - } - return if (openFlag) OPEN else INVALID -} - -/** returns VALID if all Results are VALID, otherwise returns OPEN if any result is OPEN, otherwise returns INVALID */ -inline fun Array.allResult(predicate: (T) -> Result?): Result { - var invalidFlag = false - for (element in this) { - if (predicate(element) == OPEN) { - return OPEN - } else if (predicate(element) == INVALID)invalidFlag = true - } - return if (invalidFlag) INVALID else VALID -} - -/** returns VALID if any Result is VALID, otherwise returns OPEN if any result is OPEN, otherwise returns INVALID */ -inline fun Array.anyResult(predicate: (T) -> Result?): Result { - var openFlag = false - for (element in this) { - if (predicate(element) == VALID) { - return VALID - } else if (predicate(element) == OPEN) { - openFlag = true - } - } - return if (openFlag) OPEN else INVALID -} - -/** precedence order for ternary and: OPEN > INVALID > VALID */ -fun Result.and(other: Result): Result { - return if (this == OPEN || other == OPEN) { - OPEN - } else if (this == INVALID || other == INVALID) { - INVALID - } else { - VALID - } -} diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/CpgWheneverEvaluator.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/CpgWheneverEvaluator.kt index 088e5eb1e..c6595faa0 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/CpgWheneverEvaluator.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/CpgWheneverEvaluator.kt @@ -331,7 +331,7 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem callConditionComponent: CallConditionComponent, premiseNode: Node? = null ): EvaluationResult { - val callNodes = callConditionComponent.op.cpgGetNodes().keys.filterWithDistanceToPremise(premiseNode) + val callNodes = callConditionComponent.op.cpgGetNodes().filterWithDistanceToPremise(premiseNode) return EvaluationResult(callNodes, emptyList(), Problems()) } diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/FollowsEvaluator.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/FollowsEvaluator.kt index 659c49725..898ef1e45 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/FollowsEvaluator.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/FollowsEvaluator.kt @@ -38,10 +38,10 @@ class FollowsEvaluator(val ifOp: Op, val thenOp: Op) : Evaluator { override fun evaluate(context: EvaluationContext): List { val (unreachableThisNodes, thisNodes) = - with(this@CokoCpgBackend) { ifOp.cpgGetNodes().keys } + with(this@CokoCpgBackend) { ifOp.cpgGetNodes().toSet() } .partition { it.isUnreachable() } - val thatNodes = with(this@CokoCpgBackend) { thenOp.cpgGetNodes().keys } + val thatNodes = with(this@CokoCpgBackend) { thenOp.cpgGetNodes().toSet() } val findings = mutableListOf() diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluator.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluator.kt index 36b92a5d1..5b0cf63da 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluator.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluator.kt @@ -17,7 +17,6 @@ package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend import de.fraunhofer.aisec.codyze.backends.cpg.coko.CpgFinding -import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.cpgGetNodes import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Evaluator @@ -49,28 +48,17 @@ class NeverEvaluator(val forbiddenOps: List) : Evaluator { for (op in forbiddenOps) { val nodes = op.cpgGetNodes() - if (nodes.isEmpty()) { - continue - } - - // This means there are calls to the forbidden op, so Fail findings are added - for (node in nodes) { - if (node.value == Result.OPEN) { + if (nodes.isNotEmpty()) { + // This means there are calls to the forbidden op, so Fail findings are added + for (node in nodes) { findings.add( CpgFinding( - message = "Not enough information to evaluate \"${node.key.code}\"", - kind = Finding.Kind.Open, - node = node.key + message = "Violation against rule: \"${node.code}\". $failMessage", + kind = Finding.Kind.Fail, + node = node ) ) } - findings.add( - CpgFinding( - message = "Violation against rule: \"${node.key.code}\". $failMessage", - kind = Finding.Kind.Fail, - node = node.key - ) - ) } } diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluator.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluator.kt index 9865ec563..bec29b8ec 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluator.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluator.kt @@ -17,7 +17,6 @@ package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend import de.fraunhofer.aisec.codyze.backends.cpg.coko.CpgFinding -import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.cpgGetAllNodes import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.cpgGetNodes import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext @@ -40,10 +39,9 @@ class OnlyEvaluator(val ops: List) : Evaluator { private val defaultPassMessage = "Call is in compliance with rule" override fun evaluate(context: EvaluationContext): List { - val correctAndOpen = with(this@CokoCpgBackend) { - ops.flatMap { it.cpgGetNodes().entries }.associate { it.toPair() } - } - val correctAndOpenNodes = correctAndOpen.keys.toSet() + val correctNodes = + with(this@CokoCpgBackend) { ops.flatMap { it.cpgGetNodes() } } + .toSet() val distinctOps = ops.toSet() val allNodes = @@ -52,7 +50,7 @@ class OnlyEvaluator(val ops: List) : Evaluator { // `correctNodes` is a subset of `allNodes` // we want to find nodes in `allNodes` that are not contained in `correctNodes` since they are violations - val violatingNodes = allNodes.minus(correctAndOpenNodes) + val violatingNodes = allNodes.minus(correctNodes) val ruleAnnotation = context.rule.findAnnotation() val failMessage = ruleAnnotation?.failMessage?.takeIf { it.isNotEmpty() } ?: defaultFailMessage @@ -69,16 +67,7 @@ class OnlyEvaluator(val ops: List) : Evaluator { ) } - for (node in correctAndOpenNodes) { - if (correctAndOpen[node] == Result.OPEN) { - findings.add( - CpgFinding( - message = "Not enough information to evaluate \"${node.code}\"", - kind = Finding.Kind.Open, - node = node - ) - ) - } + for (node in correctNodes) { findings.add( CpgFinding( message = "Complies with rule: \"${node.code}\". $passMessage", diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OrderEvaluator.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OrderEvaluator.kt index 8de639bb5..ef4132ce0 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OrderEvaluator.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OrderEvaluator.kt @@ -153,7 +153,7 @@ class OrderEvaluator(val baseNodes: Collection, val order: Order) : Evalua // the nodes from +testObj.start(123) <- userDefined Ops are used // only allow the start nodes that take '123' as argument for ((_, op) in order.userDefinedOps.entries) { - val nodes = op.cpgGetNodes().keys + val nodes = op.cpgGetNodes() registerOpAndNodes(op, nodes, hashToMethod, nodesToOp) } diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt index de1362529..ce1508e7e 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt @@ -43,14 +43,14 @@ class SignatureTest { fun `test signature with wrong number of params`() { every { node.arguments } returns listOf() - assertFalse { with(backend) { with(node) { cpgSignature("test") } } == Result.VALID } + assertFalse { with(backend) { with(node) { cpgSignature("test") } } } } @Test fun `test signature with null`() { every { node.arguments } returns listOf(mockk()) - assertFalse { with(backend) { with(node) { cpgSignature(null) } } == Result.VALID } + assertFalse { with(backend) { with(node) { cpgSignature(null) } } } } @Test @@ -58,7 +58,7 @@ class SignatureTest { every { node.arguments } returns listOf(stringArgument) every { stringArgument.type.typeName } returns "kotlin.String" - assertTrue { with(backend) { with(node) { cpgSignature(Type("kotlin.String")) } } == Result.VALID } + assertTrue { with(backend) { with(node) { cpgSignature(Type("kotlin.String")) } } } } @Test @@ -66,7 +66,7 @@ class SignatureTest { every { node.arguments } returns listOf(stringArgument) every { stringArgument.type.typeName } returns "kotlin.String" - assertFalse { with(backend) { with(node) { cpgSignature(Type("kotlin.Int")) } } == Result.VALID } + assertFalse { with(backend) { with(node) { cpgSignature(Type("kotlin.Int")) } } } } @Test @@ -75,7 +75,7 @@ class SignatureTest { every { stringArgument.type.typeName } returns "kotlin.String" every { stringArgument.value } returns "test" - assertTrue { with(backend) { with(node) { cpgSignature("test" withType "kotlin.String") } } == Result.VALID } + assertTrue { with(backend) { with(node) { cpgSignature("test" withType "kotlin.String") } } } } @Test @@ -84,7 +84,7 @@ class SignatureTest { every { stringArgument.type.typeName } returns "kotlin.String" every { stringArgument.value } returns "test" - assertFalse { with(backend) { with(node) { cpgSignature("test" withType "kotlin.Int") } } == Result.VALID } + assertFalse { with(backend) { with(node) { cpgSignature("test" withType "kotlin.Int") } } } } @Test @@ -93,7 +93,7 @@ class SignatureTest { every { stringArgument.type.typeName } returns "kotlin.String" every { stringArgument.value } returns "test" - assertFalse { with(backend) { with(node) { cpgSignature(1 withType "kotlin.String") } } == Result.VALID } + assertFalse { with(backend) { with(node) { cpgSignature(1 withType "kotlin.String") } } } } @Test @@ -104,7 +104,7 @@ class SignatureTest { every { pairArgument.value } returns (1 to "one") mockkStatic("de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.ImplementationDslKt") - every { with(node) { param.cpgFlowsTo(pairArgument) } } returns Result.VALID + every { with(node) { param.cpgFlowsTo(pairArgument) } } returns true // tests that with normal pair only flowsTo is called with(backend) { with(node) { cpgSignature(param) } } @@ -118,7 +118,7 @@ class SignatureTest { every { stringArgument.value } returns "test" mockkStatic("de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.ImplementationDslKt") - every { with(node) { param.cpgFlowsTo(stringArgument) } } returns Result.VALID + every { with(node) { param.cpgFlowsTo(stringArgument) } } returns true // assert that signature checks the dataflow from the parameter to the argument with(backend) { with(node) { cpgSignature(param) } } @@ -134,7 +134,7 @@ class SignatureTest { val a = mockk>() args.add(a) every { a.value } returns "test" - every { with(node) { p.cpgFlowsTo(a) } } returns Result.VALID + every { with(node) { p.cpgFlowsTo(a) } } returns true } every { node.arguments } returns args diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/FollowsEvaluationTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/FollowsEvaluationTest.kt index a0f43063f..231f0b736 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/FollowsEvaluationTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/FollowsEvaluationTest.kt @@ -13,12 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators +package de.fraunhofer.aisec.codyze.backends.cpg import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend import de.fraunhofer.aisec.codyze.backends.cpg.coko.CpgFinding -import de.fraunhofer.aisec.codyze.backends.cpg.createCpgConfiguration -import de.fraunhofer.aisec.codyze.backends.cpg.dummyRule import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Finding import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.definition diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluationTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluationTest.kt index 5068a2333..615c531df 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluationTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluationTest.kt @@ -13,11 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators +package de.fraunhofer.aisec.codyze.backends.cpg import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend -import de.fraunhofer.aisec.codyze.backends.cpg.createCpgConfiguration -import de.fraunhofer.aisec.codyze.backends.cpg.dummyRule import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Finding import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.definition diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluationTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluationTest.kt index 7d4d3e394..5b99e2a31 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluationTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluationTest.kt @@ -13,11 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators +package de.fraunhofer.aisec.codyze.backends.cpg import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend -import de.fraunhofer.aisec.codyze.backends.cpg.createCpgConfiguration -import de.fraunhofer.aisec.codyze.backends.cpg.dummyRule import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Finding import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.definition diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OrderEvaluationTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OrderEvaluationTest.kt index 741230829..e7f8fa616 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OrderEvaluationTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OrderEvaluationTest.kt @@ -13,11 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators +package de.fraunhofer.aisec.codyze.backends.cpg import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend import de.fraunhofer.aisec.codyze.backends.cpg.coko.CpgFinding -import de.fraunhofer.aisec.codyze.backends.cpg.createCpgConfiguration import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.CokoBackend import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Evaluator diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/ordering/NfaDfaConstructionTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/ordering/NfaDfaConstructionTest.kt index b8baf492f..630d48441 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/ordering/NfaDfaConstructionTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/ordering/NfaDfaConstructionTest.kt @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.aisec.codyze.backends.cpg.coko.ordering +package de.fraunhofer.aisec.codyze.backends.cpg import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend +import de.fraunhofer.aisec.codyze.backends.cpg.coko.ordering.toNfa import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.* import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.ordering.* import de.fraunhofer.aisec.cpg.analysis.fsm.DFA From 1d8267ba00879488ef6bc686b4a59ce41e341a77 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Wed, 10 Jul 2024 11:12:07 +0200 Subject: [PATCH 14/17] cleanup --- .../cpg/coko/dsl/ImplementationDsl.kt | 22 ++++++++----------- .../cpg/coko/dsl/ImplementationDslTest.kt | 22 +++++-------------- .../coko/evaluators/FollowsEvaluationTest.kt | 4 +++- .../coko/evaluators/NeverEvaluationTest.kt | 4 +++- .../cpg/coko/evaluators/OnlyEvaluationTest.kt | 4 +++- .../coko/evaluators/OrderEvaluationTest.kt | 3 ++- .../coko/ordering/NfaDfaConstructionTest.kt | 3 +-- 7 files changed, 27 insertions(+), 35 deletions(-) diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt index f0c6dd6ef..1a9880ebf 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt @@ -26,8 +26,7 @@ import de.fraunhofer.aisec.cpg.TranslationResult import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.* -import de.fraunhofer.aisec.cpg.query.dataFlow -import de.fraunhofer.aisec.cpg.query.executionPath +import de.fraunhofer.aisec.cpg.query.* // // all functions/properties defined here must use CokoBackend @@ -177,7 +176,7 @@ infix fun Any.cpgFlowsTo(that: Collection): Boolean = is Length -> checkLength(that) else -> this in that.map { (it as Expression).evaluate() } } - ) + } private fun Any.checkRange(that: Collection): Boolean { when (this) { @@ -201,17 +200,14 @@ private fun Any.checkRange(that: Collection): Boolean { } } -private fun Length.checkLength(that: Collection): Result { - return Result.convert( - that.all { - val size = sizeof(it).value - if (size == -1) { - // Handle case where size could not be determined -> OPEN Finding - return OPEN - } - size in this.value +private fun Length.checkLength(that: Collection): Boolean { + return that.all { + val size = sizeof(it).value + if (size == -1) { + // TODO: Handle case where size could not be determined -> OPEN Finding } - ) + size in this.value + } } context(CokoBackend) diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt index 10871daa1..e719b9950 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt @@ -99,18 +99,13 @@ class ImplementationDslTest { for (i in 0..3) { with(lengthBackend) { val nodes = opX[i].cpgGetNodes() - val validNodes = nodes.filter { it.value == Result.VALID } + // TODO: filter out false positives assertEquals( results[i], - validNodes.size, - "cpgGetNodes returned ${validNodes.size} node(s) instead of ${results[i]} nodes " + + nodes.size, + "cpgGetNodes returned ${nodes.size} node(s) instead of ${results[i]} nodes " + "for the Op: ${opX[i]}." ) - assertEquals( - 1, - nodes.filter { it.value == Result.OPEN }.size, - "cpgGetNodes did not return exactly one OPEN result as expected." - ) } } } @@ -133,18 +128,13 @@ class ImplementationDslTest { for (i in 0..3) { with(lengthBackend) { val nodes = opX[i].cpgGetNodes() - val validNodes = nodes.filter { it.value == Result.VALID } + // TODO: filter out false positives assertEquals( results[i], - validNodes.size, - "cpgGetNodes returned ${validNodes.size} node(s) instead of ${results[i]} nodes " + + nodes.size, + "cpgGetNodes returned ${nodes.size} node(s) instead of ${results[i]} nodes " + "for the Op: ${opX[i]}." ) - assertEquals( - 1, - nodes.filter { it.value == Result.OPEN }.size, - "cpgGetNodes did not return exactly one OPEN result as expected." - ) } } } diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/FollowsEvaluationTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/FollowsEvaluationTest.kt index 231f0b736..a0f43063f 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/FollowsEvaluationTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/FollowsEvaluationTest.kt @@ -13,10 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.aisec.codyze.backends.cpg +package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend import de.fraunhofer.aisec.codyze.backends.cpg.coko.CpgFinding +import de.fraunhofer.aisec.codyze.backends.cpg.createCpgConfiguration +import de.fraunhofer.aisec.codyze.backends.cpg.dummyRule import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Finding import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.definition diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluationTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluationTest.kt index 615c531df..5068a2333 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluationTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluationTest.kt @@ -13,9 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.aisec.codyze.backends.cpg +package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend +import de.fraunhofer.aisec.codyze.backends.cpg.createCpgConfiguration +import de.fraunhofer.aisec.codyze.backends.cpg.dummyRule import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Finding import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.definition diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluationTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluationTest.kt index 5b99e2a31..7d4d3e394 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluationTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluationTest.kt @@ -13,9 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.aisec.codyze.backends.cpg +package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend +import de.fraunhofer.aisec.codyze.backends.cpg.createCpgConfiguration +import de.fraunhofer.aisec.codyze.backends.cpg.dummyRule import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Finding import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.definition diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OrderEvaluationTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OrderEvaluationTest.kt index e7f8fa616..741230829 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OrderEvaluationTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OrderEvaluationTest.kt @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.aisec.codyze.backends.cpg +package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend import de.fraunhofer.aisec.codyze.backends.cpg.coko.CpgFinding +import de.fraunhofer.aisec.codyze.backends.cpg.createCpgConfiguration import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.CokoBackend import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Evaluator diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/ordering/NfaDfaConstructionTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/ordering/NfaDfaConstructionTest.kt index 630d48441..b8baf492f 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/ordering/NfaDfaConstructionTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/ordering/NfaDfaConstructionTest.kt @@ -13,10 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.aisec.codyze.backends.cpg +package de.fraunhofer.aisec.codyze.backends.cpg.coko.ordering import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend -import de.fraunhofer.aisec.codyze.backends.cpg.coko.ordering.toNfa import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.* import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.ordering.* import de.fraunhofer.aisec.cpg.analysis.fsm.DFA From 42a8c448f14cb50daa406c42c1646acc7aaf486e Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Wed, 24 Jul 2024 09:34:48 +0200 Subject: [PATCH 15/17] add signature test for the Length --- .../aisec/codyze/backends/cpg/SignatureTest.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt index ce1508e7e..6e16704e3 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt @@ -17,6 +17,7 @@ package de.fraunhofer.aisec.codyze.backends.cpg import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.* import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.CokoBackend +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Length import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Type import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.withType import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression @@ -144,4 +145,13 @@ class SignatureTest { for (i in args.indices) verify { with(node) { params[i].cpgFlowsTo(args[i]) } } } // TODO hasVarargs + + @Test + fun `test signature with String length`() { + every { node.arguments } returns listOf(stringArgument) + every { stringArgument.type.typeName } returns "kotlin.String" + every { stringArgument.value } returns "test" + + assertTrue { with(backend) { with(node) { cpgSignature(Length(4..4)) } } } + } } From 5cb49cb83040c187c12f1cd6b7910f4484038263 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Wed, 24 Jul 2024 09:51:23 +0200 Subject: [PATCH 16/17] differentiate between good and bad length parameter --- .../aisec/codyze/backends/cpg/SignatureTest.kt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt index 6e16704e3..0fc2a2a2f 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt @@ -147,11 +147,20 @@ class SignatureTest { // TODO hasVarargs @Test - fun `test signature with String length`() { + fun `test signature with good String length`() { every { node.arguments } returns listOf(stringArgument) every { stringArgument.type.typeName } returns "kotlin.String" every { stringArgument.value } returns "test" assertTrue { with(backend) { with(node) { cpgSignature(Length(4..4)) } } } } + + @Test + fun `test signature with bad String length`() { + every { node.arguments } returns listOf(stringArgument) + every { stringArgument.type.typeName } returns "kotlin.String" + every { stringArgument.value } returns "string" + + assertFalse { with(backend) { with(node) { cpgSignature(Length(-1..4)) } } } + } } From a60f69b2e9312edd9f0950c23b07bb0af0babad3 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Wed, 24 Jul 2024 09:56:28 +0200 Subject: [PATCH 17/17] add test for Long Range instead of only testing Int Range --- .../cpg/coko/dsl/ImplementationDslTest.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt index e719b9950..215c63ec6 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt @@ -61,6 +61,23 @@ class ImplementationDslTest { } } + @Test + fun `test cpgGetNodes with Long Range`() { + val op = op { + "Foo.fun" { + signature(1..10L) + } + } + with(simpleBackend) { + val nodes = op.cpgGetNodes() + assertEquals( + 2, + nodes.size, + "cpgGetNodes returned ${nodes.size} node(s) instead of 2 nodes for the Op: $op." + ) + } + } + @Test fun `test cpgGetNodes with unordered parameters`() { val op = op {