Files
codeql/csharp/ql/src/Language Abuse/ForeachCapture.ql

118 lines
3.7 KiB
Plaintext

/**
* @name Capturing a foreach variable
* @description Code that captures a 'foreach' variable and uses it outside the loop behaves differently in C# version 4 and C# version 5
* @kind problem
* @problem.severity recommendation
* @precision medium
* @id cs/captured-foreach-variable
* @tags portability
* maintainability
* language-features
* external/cwe/cwe-758
*/
import csharp
import semmle.code.csharp.dataflow.LibraryTypeDataFlow
import semmle.code.csharp.frameworks.system.Collections
import semmle.code.csharp.frameworks.system.collections.Generic
predicate lambdaCaptures(AnonymousFunctionExpr lambda, Variable v) {
exists(VariableAccess va | va.getEnclosingCallable() = lambda | va.getTarget() = v)
}
predicate lambdaCapturesLoopVariable(AnonymousFunctionExpr lambda, ForeachStmt loop, Variable v) {
lambdaCaptures(lambda, v) and
inForeachStmtBody(loop, lambda) and
loop.getVariable() = v
}
predicate inForeachStmtBody(ForeachStmt loop, Element e) {
e = loop.getBody()
or
exists(Element mid |
inForeachStmtBody(loop, mid) and
e = mid.getAChild()
)
}
class LambdaDataFlowConfiguration extends DataFlow::Configuration {
LambdaDataFlowConfiguration() { this = "LambdaDataFlowConfiguration" }
override predicate isSource(DataFlow::Node source) {
lambdaCapturesLoopVariable(source.asExpr(), _, _)
}
override predicate isSink(DataFlow::Node sink) { exists(getAssignmentTarget(sink.asExpr())) }
predicate capturesLoopVarAndIsStoredIn(
AnonymousFunctionExpr lambda, Variable loopVar, Element storage
) {
exists(DataFlow::Node sink | this.hasFlow(DataFlow::exprNode(lambda), sink) |
storage = getAssignmentTarget(sink.asExpr())
) and
exists(ForeachStmt loop | lambdaCapturesLoopVariable(lambda, loop, loopVar) |
not declaredInsideLoop(loop, storage)
)
}
}
Element getAssignmentTarget(Expr e) {
exists(Assignment a | a.getRValue() = e |
result = a.getLValue().(PropertyAccess).getTarget() or
result = a.getLValue().(FieldAccess).getTarget() or
result = a.getLValue().(LocalVariableAccess).getTarget() or
result = a.getLValue().(EventAccess).getTarget()
)
or
exists(AddEventExpr aee |
e = aee.getRValue() and
result = aee.getLValue().getTarget()
)
or
result = getCollectionAssignmentTarget(e)
}
Element getCollectionAssignmentTarget(Expr e) {
// Store into collection via method
exists(
MethodCall mc, Method m, LibraryTypeDataFlow ltdf, CallableFlowSource source,
CallableFlowSink sink
|
m = mc.getTarget().getUnboundDeclaration() and
ltdf.callableFlow(source, AccessPath::empty(), sink, AccessPath::element(), m, _) and
e = source.getSource(mc) and
result.(Variable).getAnAccess() = sink.getSink(mc)
)
or
// Array initializer
e = result.(ArrayCreation).getInitializer().getAnElement()
or
// Collection initializer
e =
result
.(ObjectCreation)
.getInitializer()
.(CollectionInitializer)
.getElementInitializer(_)
.getAnArgument()
or
// Store values using indexer
exists(IndexerAccess ia, AssignExpr ae |
ia.getQualifier() = result.(Variable).getAnAccess() and
ia = ae.getLValue() and
e = ae.getRValue()
)
}
// Variable v is declared inside the loop body
predicate declaredInsideLoop(ForeachStmt loop, LocalVariable v) {
exists(LocalVariableDeclStmt decl | decl.getVariableDeclExpr(_).getVariable() = v |
inForeachStmtBody(loop, decl)
)
}
from LambdaDataFlowConfiguration c, AnonymousFunctionExpr lambda, Variable loopVar, Element storage
where c.capturesLoopVarAndIsStoredIn(lambda, loopVar, storage)
select lambda, "Function which may be stored in $@ captures variable $@", storage,
storage.toString(), loopVar, loopVar.getName()