Skip to content

Commit

Permalink
Adding DynamicLoading concept (#2055)
Browse files Browse the repository at this point in the history
  • Loading branch information
oxisto authored Feb 19, 2025
1 parent 7ca07fa commit adaa2fb
Show file tree
Hide file tree
Showing 10 changed files with 469 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ class MultiValueEvaluator : ValueEvaluator() {
is VariableDeclaration -> return handleHasInitializer(node, depth)
// For a literal, we can just take its value, and we are finished
is Literal<*> -> return node.value
is Reference -> return handlePrevDFG(node, depth)
is UnaryOperator -> return handleUnaryOp(node, depth)
is AssignExpression -> return handleAssignExpression(node, depth)
is BinaryOperator -> return handleBinaryOperator(node, depth)
Expand All @@ -79,6 +78,7 @@ class MultiValueEvaluator : ValueEvaluator() {
// While we are not handling different paths of variables with If statements, we can
// easily be partly path-sensitive in a conditional expression
is ConditionalExpression -> return handleConditionalExpression(node, depth)
else -> return handlePrevDFG(node, depth)
}

// At this point, we cannot evaluate, and we are calling our [cannotEvaluate] hook, maybe
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,18 @@ open class ValueEvaluator(

/** Tries to evaluate this node. Anything can happen. */
protected open fun evaluateInternal(node: Node?, depth: Int): Any? {
if (node == null) {
return null
}

// Add the expression to the current path
node?.let { this.path += it }
node.let { this.path += it }

when (node) {
is NewArrayExpression -> return handleHasInitializer(node, depth)
is VariableDeclaration -> return handleHasInitializer(node, depth)
// For a literal, we can just take its value, and we are finished
is Literal<*> -> return node.value
is Reference -> return handlePrevDFG(node, depth)
is UnaryOperator -> return handleUnaryOp(node, depth)
is BinaryOperator -> return handleBinaryOperator(node, depth)
// Casts are just a wrapper in this case, we are interested in the inner expression
Expand All @@ -102,6 +105,7 @@ open class ValueEvaluator(
// easily be partly path-sensitive in a conditional expression
is ConditionalExpression -> return handleConditionalExpression(node, depth)
is AssignExpression -> return handleAssignExpression(node, depth)
else -> return handlePrevDFG(node, depth)
}

// At this point, we cannot evaluate, and we are calling our [cannotEvaluate] hook, maybe
Expand Down
8 changes: 5 additions & 3 deletions cpg-concepts/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,16 @@ publishing {
}

dependencies {
// to evaluate some test cases
testImplementation(projects.cpgAnalysis)
implementation(projects.cpgAnalysis)

// We depend on the Python frontend for the integration tests, but the frontend is only available if enabled.
// We depend on the Python and C/C++ frontend for the integration tests, but the frontend is only available if enabled.
// If it's not available, the integration tests fail (which is ok). But if we would directly reference the
// project here, the build system would fail any task since it will not find a non-enabled project.
findProject(":cpg-language-python")?.also {
integrationTestImplementation(it)
}
findProject(":cpg-language-cxx")?.also {
integrationTestImplementation(it)
}
integrationTestImplementation(projects.cpgAnalysis)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Copyright (c) 2025, 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.cpg.concepts

import de.fraunhofer.aisec.cpg.analysis.MultiValueEvaluator
import de.fraunhofer.aisec.cpg.frontends.cxx.CLanguage
import de.fraunhofer.aisec.cpg.graph.*
import de.fraunhofer.aisec.cpg.graph.concepts.memory.LoadLibrary
import de.fraunhofer.aisec.cpg.graph.concepts.memory.LoadSymbol
import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration
import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression
import de.fraunhofer.aisec.cpg.passes.concepts.memory.CXXDynamicLoadingPass
import de.fraunhofer.aisec.cpg.test.analyze
import de.fraunhofer.aisec.cpg.test.assertInvokes
import java.io.File
import kotlin.test.Test
import kotlin.test.assertContains
import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlin.test.assertNotNull

class DynamicLoadingTest {
@Test
fun testCXX() {
val topLevel = File("src/integrationTest/resources/c")
val result =
analyze(listOf(), topLevel.toPath(), true) {
it.registerLanguage<CLanguage>()
it.registerPass<CXXDynamicLoadingPass>()
it.softwareComponents(
mutableMapOf(
"main" to listOf(topLevel.resolve("main")),
"libexample" to listOf(topLevel.resolve("libexample")),
)
)
}
assertNotNull(result)

val libExample = result.components["libexample"]
assertNotNull(libExample)

val myFunc = result.functions["myfunc"]
assertNotNull(myFunc)

val lib = result.variables["lib"]
assertNotNull(lib)

val path =
lib.followPrevDFG { it is CallExpression && it.overlays.any { it is LoadLibrary } }
assertNotNull(path)

val loadLibrary =
path.lastOrNull()?.operationNodes?.filterIsInstance<LoadLibrary>()?.singleOrNull()
assertNotNull(loadLibrary)
assertEquals(
libExample,
loadLibrary.what,
"\"what\" of the LoadLibrary should be the libexample component",
)

val bCall = result.calls["b"]
assertNotNull(bCall)
assertInvokes(bCall, myFunc, "The call to b should invoke myFunc")

val dlSym = result.calls["dlsym"]
assertNotNull(dlSym)

val loadSymbol =
dlSym.operationNodes.filterIsInstance<LoadSymbol<FunctionDeclaration>>().singleOrNull()
assertNotNull(loadSymbol)
assertEquals(myFunc, loadSymbol.what, "\"what\" of the LoadSymbol should be myFunc")

val c = result.refs["c"]
assertNotNull(c)

// The multi-evaluator contains too many values for now, since we just stupidly take all DFG
// edges into the function declaration, we need to instead look at the calling context,
// similar to what we do with the dataflow queries.
var values = c.evaluate(MultiValueEvaluator())
assertIs<Set<*>>(values)
assertContains(values, 2)

val a = result.refs["a"]
assertNotNull(a)

values = a.evaluate(MultiValueEvaluator())
assertIs<Set<*>>(values)
assertContains(values, 3)
}
}
5 changes: 5 additions & 0 deletions cpg-concepts/src/integrationTest/resources/c/libexample/lib.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
int myvar = 2;

int myfunc(int i) {
return i + 1;
}
28 changes: 28 additions & 0 deletions cpg-concepts/src/integrationTest/resources/c/main/load.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// To reproduce this example, run the following commands from the parent directory:
// clang -shared libexample/lib.c -o libexample.so
// clang main/load.c -o load
// ./load
// Expected output: a = 3

#include <dlfcn.h>
#include <stdio.h>

int main() {
void* lib = dlopen("libexample.so", RTLD_LAZY);

int (*b)(int);
int *c;

// does not work yet because of wrong DFG edges
//*(void **) (&b) = dlsym(lib, "myfunc");
// but the following works and is also a valid syntax
b = dlsym(lib, "myfunc");
c = dlsym(lib, "myvar"); // c = 2

int a = b(*c);

// a = 3
printf("a = %d\n", a);

dlclose(lib);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright (c) 2025, 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.cpg.graph.concepts.memory

import de.fraunhofer.aisec.cpg.graph.Component
import de.fraunhofer.aisec.cpg.graph.Node
import de.fraunhofer.aisec.cpg.graph.concepts.Concept
import de.fraunhofer.aisec.cpg.graph.declarations.Declaration
import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration
import de.fraunhofer.aisec.cpg.graph.scopes.Symbol

/**
* Represents an entity that loads a piece of code dynamically during runtime. Examples include a
* class loader in Java, loading shared library code in C++. Interpreters, such as Python can also
* load code dynamically during runtime.
*/
class DynamicLoading(underlyingNode: Node) :
Concept<DynamicLoadingOperation<*>>(underlyingNode = underlyingNode), IsMemory

/** Represents an operation used by the [DynamicLoading] concept. */
abstract class DynamicLoadingOperation<T : Node>(
underlyingNode: Node,
concept: Concept<DynamicLoadingOperation<T>>,
/** Represents the entity that we load during runtime. */
var what: T?,
) : MemoryOperation(underlyingNode = underlyingNode, concept = concept), IsMemory

/**
* Represents an operation that loads a shared library during runtime. A common example would be a
* call to `dlopen` in C/C++.
*
* The [underlyingNode] is most likely a function call and [what] can point to a [Component]
* representing the library.
*/
class LoadLibrary(
underlyingNode: Node,
concept: Concept<DynamicLoadingOperation<Component>>,
/** Represents the source code of library that we load in our graph. */
what: Component?,
) :
DynamicLoadingOperation<Component>(
underlyingNode = underlyingNode,
concept = concept,
what = what,
) {

/** Looks up symbol candidates for [symbol] in the [LoadLibrary.what]. */
fun findSymbol(symbol: Symbol?): List<Declaration> {
if (symbol == null) {
return listOf()
}

return this.what?.translationUnits?.flatMap { it.scope?.lookupSymbol(symbol) ?: listOf() }
?: listOf()
}
}

/**
* Represents an operation that loads a symbol during runtime. A common example would be a call to
* `dlsym` in C/C++.
*
* The [underlyingNode] is most likely a function call and [what] can point to a [Declaration]
* representing the symbol (e.g., a [FunctionDeclaration]) that we load.
*
* If we are loading a symbol from an external library, [loader] can point to the [LoadLibrary]
* operation that loaded the library.
*/
class LoadSymbol<T : Declaration>(
underlyingNode: Node,
concept: Concept<DynamicLoadingOperation<T>>,
/** Represents the symbol's [Declaration] that we load in our graph. */
what: T?,

/**
* If we are loading a symbol from an external library, this points to the [LoadLibrary]
* operation that loaded the library.
*/
var loader: LoadLibrary?,
) : DynamicLoadingOperation<T>(underlyingNode = underlyingNode, concept = concept, what = what)
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class Memory(underlyingNode: Node, mode: MemoryManagementMode) :
interface IsMemory

/** A common abstract class for memory operations. */
abstract class MemoryOperation(underlyingNode: Node, concept: Concept<MemoryOperation>) :
abstract class MemoryOperation(underlyingNode: Node, concept: Concept<out MemoryOperation>) :
Operation(underlyingNode = underlyingNode, concept = concept), IsMemory

/**
Expand Down
Loading

0 comments on commit adaa2fb

Please sign in to comment.