-
Notifications
You must be signed in to change notification settings - Fork 68
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding
DynamicLoading
concept (#2055)
- Loading branch information
Showing
10 changed files
with
469 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
113 changes: 113 additions & 0 deletions
113
...oncepts/src/integrationTest/kotlin/de/fraunhofer/aisec/cpg/concepts/DynamicLoadingTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,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
5
cpg-concepts/src/integrationTest/resources/c/libexample/lib.c
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
int myvar = 2; | ||
|
||
int myfunc(int i) { | ||
return i + 1; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,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); | ||
} |
102 changes: 102 additions & 0 deletions
102
cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/memory/DynamicLoading.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.