Skip to content

Commit

Permalink
Merge branch 'main' into rh/open-result
Browse files Browse the repository at this point in the history
  • Loading branch information
fwendland committed Sep 17, 2024
2 parents 7768611 + 5587864 commit 55c99b6
Show file tree
Hide file tree
Showing 36 changed files with 1,080 additions and 178 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
# store version in GitHub environment file
echo "version=$VERSION" >> $GITHUB_ENV
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
uses: gradle/actions/setup-gradle@v4
- name: Build ${{ env.version }}
run: ./gradlew :codyze-cli:build -x check --parallel -Pversion=${{ env.version }}
- name: Push Release Docker Image
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
distribution: "temurin"
java-version: 17
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
uses: gradle/actions/setup-gradle@v4
- name: Generate coverage report
run: ./gradlew testCodeCoverageReport --continue
- name: Archive test reports
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/detekt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
java-version: 17

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
uses: gradle/actions/setup-gradle@v4
- name: Run analysis
run: ./gradlew detektMain detektTest --continue

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
distribution: "temurin"
java-version: 17
- name: 'Setup Gradle'
uses: gradle/actions/setup-gradle@v3
uses: gradle/actions/setup-gradle@v4
- name: 'Build API pages'
run: |
./gradlew dokkaHtmlMultiModule \
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/upgrade.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
distribution: "temurin"
java-version: ${{ matrix.java-lts }}
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
uses: gradle/actions/setup-gradle@v4
- name: Build and Test
id: build-and-test
run: ./gradlew build --parallel
Expand All @@ -65,7 +65,7 @@ jobs:
merge-multiple: true
- name: Process failures
id: process-failures
run: test -f all-failures/failure && echo 'hasFails=true' >> "$GITHUB_OUTPUT"
run: (test -f all-failures/failure && echo 'hasFails=true' || echo 'hasFails=false') >> "$GITHUB_OUTPUT"
- if: ${{ steps.process-failures.outputs.hasFails == 'true' }}
uses: actions/github-script@v7
with:
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM eclipse-temurin:17.0.11_9-jre
FROM eclipse-temurin:17.0.12_7-jre

LABEL org.opencontainers.image.authors="Fraunhofer AISEC <[email protected]>"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import io.github.detekt.sarif4k.Artifact
import io.github.detekt.sarif4k.ArtifactLocation
import io.github.detekt.sarif4k.ToolComponent
import kotlin.io.path.absolutePathString
import kotlin.reflect.KFunction

typealias Nodes = Collection<Node>

Expand Down Expand Up @@ -61,6 +62,9 @@ class CokoCpgBackend(config: BackendConfiguration) :
/** For each of the nodes in [this], there is a path to at least one of the nodes in [that]. */
override infix fun Op.followedBy(that: Op): FollowsEvaluator = FollowsEvaluator(ifOp = this, thenOp = that)

/** For each of the nodes in [that], there is a path from at least one of the nodes in [this]. */
override infix fun Op.precedes(that: Op): PrecedesEvaluator = PrecedesEvaluator(prevOp = this, thisOp = that)

/*
* Ensures the order of nodes as specified in the user configured [Order] object.
* The order evaluation starts at the given [baseNodes].
Expand All @@ -81,11 +85,21 @@ class CokoCpgBackend(config: BackendConfiguration) :
order = Order().apply(block)
)

/** Verifies that the argument at [argPos] of [targetOp] stems from a call to [originOp] */
override fun argumentOrigin(targetOp: KFunction<Op>, argPos: Int, originOp: KFunction<Op>): ArgumentEvaluator =
ArgumentEvaluator(
targetCall = targetOp.getOp(),
argPos = argPos,
originCall = originOp.getOp()
)

/**
* Ensures that all calls to the [ops] have arguments that fit the parameters specified in [ops]
*/
override fun only(vararg ops: Op): OnlyEvaluator = OnlyEvaluator(ops.toList())
override fun never(vararg ops: Op): NeverEvaluator = NeverEvaluator(ops.toList())
override fun only(vararg ops: Op): OnlyNeverEvaluator =
OnlyNeverEvaluator(ops.toList(), OnlyNeverEvaluator.Functionality.ONLY)
override fun never(vararg ops: Op): OnlyNeverEvaluator =
OnlyNeverEvaluator(ops.toList(), OnlyNeverEvaluator.Functionality.NEVER)
override fun whenever(
premise: Condition.() -> ConditionComponent,
assertionBlock: WheneverEvaluator.() -> Unit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,3 +276,27 @@ private fun CallExpression.cpgCheckArgsSize(parameters: Array<*>, hasVarargs: Bo
} else {
parameters.size == arguments.size
}

/**
* To generate more interesting Findings
* we want to find key points where the forbidden operation influences other code.
* For this we traverse the DFG for a fixed amount of steps and search for all usages of declared values.
*/
fun Node.findUsages(depth: Int = 5): Collection<Node> {
val currentNodes: MutableSet<Node> = mutableSetOf(this)
val usages = mutableSetOf<Node>()
for (i in 0..depth) {
// The set will be empty if we found a usage or no further DFG for all branches
if (currentNodes.isEmpty()) {
break
}
for (current in currentNodes) {
currentNodes.remove(current)
when (current) {
is ValueDeclaration -> usages += current.usages
else -> currentNodes += current.nextDFG
}
}
}
return usages
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* 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.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.cpgGetAllNodes
import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext
import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Evaluator
import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Finding
import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Op
import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration
import de.fraunhofer.aisec.cpg.graph.followPrevEOGEdgesUntilHit
import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression
import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression
import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal
import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference

context(CokoCpgBackend)
class ArgumentEvaluator(val targetCall: Op, val argPos: Int, val originCall: Op) : Evaluator {
override fun evaluate(context: EvaluationContext): List<CpgFinding> {
// Get all good calls and the associated variables
val originCalls = originCall.cpgGetAllNodes()
val variables = originCalls.mapNotNull {
it.tryGetVariableDeclaration()
}
val findings = mutableListOf<CpgFinding>()
// Get all target calls using the variable and check whether it is in a good state
val targetCalls = targetCall.cpgGetAllNodes()
for (call in targetCalls) {
val arg: VariableDeclaration? =
(call.arguments.getOrNull(argPos) as? Reference)?.refersTo as? VariableDeclaration
if (arg in variables && arg?.allowsInvalidPaths(originCalls.toList(), call) == false) {
findings.add(
CpgFinding(
message = "Complies with rule: " +
"arg $argPos of \"${call.code}\" stems from a call to \"$originCall\"",
kind = Finding.Kind.Pass,
node = call,
relatedNodes = listOfNotNull(originCalls.firstOrNull { it.tryGetVariableDeclaration() == arg })
)
)
} else {
findings.add(
CpgFinding(
message = "Violation against rule: " +
"arg $argPos of \"${call.code}\" does not stem from a call to \"$originCall\"",
kind = Finding.Kind.Fail,
node = call,
relatedNodes = listOf()
)
)
}
}

return findings
}

/**
* Tries to resolve which variable is modified by a CallExpression
* @return The VariableExpression modified by the CallExpression or null
*/
private fun CallExpression.tryGetVariableDeclaration(): VariableDeclaration? {
return when (val nextDFG = this.nextDFG.firstOrNull()) {
is VariableDeclaration -> nextDFG
is Reference -> nextDFG.refersTo as? VariableDeclaration
else -> null
}
}

/**
* This method tries to get all possible CallExpressions that try to override the variable value
* @return The CallExpressions modifying the variable
*/
private fun VariableDeclaration.getOverrides(): List<Expression> {
val assignments = this.typeObservers.mapNotNull { (it as? Reference)?.prevDFG?.firstOrNull() }
// Consider overwrites caused by CallExpressions and Literals
return assignments.mapNotNull {
when (it) {
is CallExpression -> it
is Literal<*> -> it
else -> null
}
}
}

/**
* This method checks whether there are any paths with forbidden values for the variable that end in the target call
* @param allowedCalls The calls that set the variable to an allowed value
* @param targetCall The target call using the variable as an argument
* @return whether there is at least one path that allows an invalid value for the variable to reach the target
*/
private fun VariableDeclaration.allowsInvalidPaths(
allowedCalls: List<CallExpression>,
targetCall: CallExpression
): Boolean {
// Get every MemberCall that tries to override our variable, ignoring allowed calls
val interferingDeclarations = this.getOverrides().toMutableList() - allowedCalls.toSet()
// Check whether there is a path from any invalid call to our target call that is not overridden at least once
val targetToNoise = targetCall.followPrevEOGEdgesUntilHit { interferingDeclarations.contains(it) }.fulfilled
.filterNot { badPath -> allowedCalls.any { goodCall -> goodCall in badPath } }
return targetToNoise.isNotEmpty()
}
}

This file was deleted.

Loading

0 comments on commit 55c99b6

Please sign in to comment.