mirror of
https://github.com/github/codeql.git
synced 2026-04-25 00:35:20 +02:00
python: add tests
- add `getACallSimple` to `SummarizedCallable` (by adding it to `LibraryCallable`)
This commit is contained in:
@@ -250,6 +250,9 @@ abstract class LibraryCallable extends string {
|
||||
/** Gets a call to this library callable. */
|
||||
abstract CallCfgNode getACall();
|
||||
|
||||
/** Same as `getACall` but without referring to the call graph or API graph. */
|
||||
CallCfgNode getACallSimple() { none() }
|
||||
|
||||
/** Gets a data-flow node, where this library callable is used as a call-back. */
|
||||
abstract ArgumentNode getACallback();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.FlowSummary
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
/**
|
||||
* This module ensures that the `callStep` predicate in
|
||||
* our type tracker implelemtation does not refer to the
|
||||
* `getACall` predicate on `SummarizedCallable`.
|
||||
*/
|
||||
module RecursionGuard {
|
||||
private import semmle.python.dataflow.new.internal.TypeTrackerSpecific as TT
|
||||
|
||||
private class RecursionGuard extends SummarizedCallable {
|
||||
RecursionGuard() { this = "RecursionGuard" }
|
||||
|
||||
override DataFlow::CallCfgNode getACall() {
|
||||
result.getFunction().asCfgNode().(NameNode).getId() = this and
|
||||
(TT::callStep(_, _) implies any())
|
||||
}
|
||||
|
||||
override DataFlow::CallCfgNode getACallSimple() { none() }
|
||||
|
||||
override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this }
|
||||
}
|
||||
|
||||
predicate test(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
TT::levelStepNoCall(nodeFrom, nodeTo)
|
||||
}
|
||||
}
|
||||
|
||||
private class SummarizedCallableIdentity extends SummarizedCallable {
|
||||
SummarizedCallableIdentity() { this = "identity" }
|
||||
|
||||
override DataFlow::CallCfgNode getACall() { none() }
|
||||
|
||||
override DataFlow::CallCfgNode getACallSimple() {
|
||||
result.getFunction().asCfgNode().(NameNode).getId() = this
|
||||
}
|
||||
|
||||
override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[0]" and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
// For lambda flow to work, implement lambdaCall and lambdaCreation
|
||||
private class SummarizedCallableApplyLambda extends SummarizedCallable {
|
||||
SummarizedCallableApplyLambda() { this = "apply_lambda" }
|
||||
|
||||
override DataFlow::CallCfgNode getACall() { none() }
|
||||
|
||||
override DataFlow::CallCfgNode getACallSimple() {
|
||||
result.getFunction().asCfgNode().(NameNode).getId() = this
|
||||
}
|
||||
|
||||
override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[1]" and
|
||||
output = "Argument[0].Parameter[0]" and
|
||||
preservesValue = true
|
||||
or
|
||||
input = "Argument[0].ReturnValue" and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class SummarizedCallableReversed extends SummarizedCallable {
|
||||
SummarizedCallableReversed() { this = "reversed" }
|
||||
|
||||
override DataFlow::CallCfgNode getACall() { none() }
|
||||
|
||||
override DataFlow::CallCfgNode getACallSimple() {
|
||||
result.getFunction().asCfgNode().(NameNode).getId() = this
|
||||
}
|
||||
|
||||
override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[0].ListElement" and
|
||||
output = "ReturnValue.ListElement" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class SummarizedCallableMap extends SummarizedCallable {
|
||||
SummarizedCallableMap() { this = "list_map" }
|
||||
|
||||
override DataFlow::CallCfgNode getACall() { none() }
|
||||
|
||||
override DataFlow::CallCfgNode getACallSimple() {
|
||||
result.getFunction().asCfgNode().(NameNode).getId() = this
|
||||
}
|
||||
|
||||
override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[1].ListElement" and
|
||||
output = "Argument[0].Parameter[0]" and
|
||||
preservesValue = true
|
||||
or
|
||||
input = "Argument[0].ReturnValue" and
|
||||
output = "ReturnValue.ListElement" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class SummarizedCallableAppend extends SummarizedCallable {
|
||||
SummarizedCallableAppend() { this = "append_to_list" }
|
||||
|
||||
override DataFlow::CallCfgNode getACall() { none() }
|
||||
|
||||
override DataFlow::CallCfgNode getACallSimple() {
|
||||
result.getFunction().asCfgNode().(NameNode).getId() = this
|
||||
}
|
||||
|
||||
override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[0]" and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = false
|
||||
or
|
||||
input = "Argument[1]" and
|
||||
output = "ReturnValue.ListElement" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class SummarizedCallableJsonLoads extends SummarizedCallable {
|
||||
SummarizedCallableJsonLoads() { this = "json.loads" }
|
||||
|
||||
override DataFlow::CallCfgNode getACall() {
|
||||
result = API::moduleImport("json").getMember("loads").getACall()
|
||||
}
|
||||
|
||||
override DataFlow::CallCfgNode getACallSimple() { none() }
|
||||
|
||||
override DataFlow::ArgumentNode getACallback() {
|
||||
result = API::moduleImport("json").getMember("loads").getAValueReachableFromSource()
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[0]" and
|
||||
output = "ReturnValue.ListElement" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
// read and store
|
||||
private class SummarizedCallableReadSecret extends SummarizedCallable {
|
||||
SummarizedCallableReadSecret() { this = "read_secret" }
|
||||
|
||||
override DataFlow::CallCfgNode getACall() { none() }
|
||||
|
||||
override DataFlow::CallCfgNode getACallSimple() {
|
||||
result.getFunction().asCfgNode().(NameNode).getId() = this
|
||||
}
|
||||
|
||||
override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[0].Attribute[secret]" and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class SummarizedCallableSetSecret extends SummarizedCallable {
|
||||
SummarizedCallableSetSecret() { this = "set_secret" }
|
||||
|
||||
override DataFlow::CallCfgNode getACall() { none() }
|
||||
|
||||
override DataFlow::CallCfgNode getACallSimple() {
|
||||
result.getFunction().asCfgNode().(NameNode).getId() = this
|
||||
}
|
||||
|
||||
override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[1]" and
|
||||
output = "Argument[0].Attribute[secret]" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Simple summary
|
||||
tainted = identity(tracked) # $ tracked
|
||||
tainted # $ MISSING: tracked
|
||||
|
||||
# Lambda summary
|
||||
# I think the missing result is expected because type tracking
|
||||
# is not allowed to flow back out of a call.
|
||||
tainted_lambda = apply_lambda(lambda x: x, tracked) # $ tracked
|
||||
tainted_lambda # $ MISSING: tracked
|
||||
|
||||
# A lambda that directly introduces taint
|
||||
bad_lambda = apply_lambda(lambda x: tracked, 1) # $ tracked
|
||||
bad_lambda # $ MISSING: tracked
|
||||
|
||||
# A lambda that breaks the flow
|
||||
untainted_lambda = apply_lambda(lambda x: 1, tracked) # $ tracked
|
||||
untainted_lambda
|
||||
|
||||
# Collection summaries
|
||||
tainted_list = reversed([tracked]) # $ tracked
|
||||
tl = tainted_list[0]
|
||||
tl # $ MISSING: tracked
|
||||
|
||||
# Complex summaries
|
||||
def add_colon(x):
|
||||
return x + ":"
|
||||
|
||||
tainted_mapped = list_map(add_colon, [tracked]) # $ tracked
|
||||
tm = tainted_mapped[0]
|
||||
tm # $ MISSING: tracked
|
||||
|
||||
def explicit_identity(x):
|
||||
return x
|
||||
|
||||
tainted_mapped_explicit = list_map(explicit_identity, [tracked]) # $ tracked
|
||||
tainted_mapped_explicit[0] # $ MISSING: tracked
|
||||
|
||||
tainted_mapped_summary = list_map(identity, [tracked]) # $ tracked
|
||||
tms = tainted_mapped_summary[0]
|
||||
tms # $ MISSING: tracked
|
||||
|
||||
another_tainted_list = append_to_list([], tracked) # $ tracked
|
||||
atl = another_tainted_list[0]
|
||||
atl # $ MISSING: tracked
|
||||
|
||||
from json import loads as json_loads
|
||||
tainted_resultlist = json_loads(tracked) # $ tracked
|
||||
tr = tainted_resultlist[0]
|
||||
tr # $ MISSING: tracked
|
||||
|
||||
x.secret = tracked # $ tracked=secret tracked
|
||||
r = read_secret(x) # $ tracked=secret MISSING: tracked
|
||||
r # $ MISSING: tracked
|
||||
|
||||
y # $ MISSING: tracked=secret
|
||||
set_secret(y, tracked) # $ tracked MISSING: tracked=secret
|
||||
y.secret # $ MISSING: tracked tracked=secret
|
||||
@@ -0,0 +1,36 @@
|
||||
import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.dataflow.new.TypeTracker
|
||||
import TestUtilities.InlineExpectationsTest
|
||||
import semmle.python.ApiGraphs
|
||||
import TestSummaries
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// tracked
|
||||
// -----------------------------------------------------------------------------
|
||||
private DataFlow::TypeTrackingNode tracked(TypeTracker t) {
|
||||
t.start() and
|
||||
result.asCfgNode() = any(NameNode n | n.getId() = "tracked")
|
||||
or
|
||||
exists(TypeTracker t2 | result = tracked(t2).track(t2, t))
|
||||
}
|
||||
|
||||
class TrackedTest extends InlineExpectationsTest {
|
||||
TrackedTest() { this = "TrackedTest" }
|
||||
|
||||
override string getARelevantTag() { result = "tracked" }
|
||||
|
||||
override predicate hasActualResult(Location location, string element, string tag, string value) {
|
||||
exists(DataFlow::Node e, TypeTracker t |
|
||||
exists(e.getLocation().getFile().getRelativePath()) and
|
||||
e.getLocation().getStartLine() > 0 and
|
||||
tracked(t).flowsTo(e) and
|
||||
// Module variables have no sensible location, and hence can't be annotated.
|
||||
not e instanceof DataFlow::ModuleVariableNode and
|
||||
tag = "tracked" and
|
||||
location = e.getLocation() and
|
||||
value = t.getAttr() and
|
||||
element = e.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user