From b715a6b63b31a6544870e62974fce9ac31444e4f Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Thu, 26 May 2022 09:06:13 +0100 Subject: [PATCH 001/246] Swift: Add test containing local declarations. --- .../controlflow/graph/Cfg.expected | 96 +++++++++++++++++++ .../library-tests/controlflow/graph/cfg.swift | 37 +++++++ 2 files changed, 133 insertions(+) diff --git a/swift/ql/test/library-tests/controlflow/graph/Cfg.expected b/swift/ql/test/library-tests/controlflow/graph/Cfg.expected index 181ae94bf32..09a945976a0 100644 --- a/swift/ql/test/library-tests/controlflow/graph/Cfg.expected +++ b/swift/ql/test/library-tests/controlflow/graph/Cfg.expected @@ -4785,3 +4785,99 @@ cfg.swift: # 405| DeclRefExpr #-----| -> TupleExpr + +# 410| TBD (YieldStmt) +#-----| -> exit AccessorDecl (normal) + +# 410| enter AccessorDecl + +# 410| enter AccessorDecl + +# 410| enter AccessorDecl +#-----| -> TBD (YieldStmt) + +# 410| exit AccessorDecl + +# 410| exit AccessorDecl + +# 410| exit AccessorDecl + +# 410| exit AccessorDecl (normal) +#-----| -> exit AccessorDecl + +# 410| exit AccessorDecl (normal) +#-----| -> exit AccessorDecl + +# 410| exit AccessorDecl (normal) +#-----| -> exit AccessorDecl + +# 411| enter ConstructorDecl +#-----| -> DeclRefExpr + +# 411| exit ConstructorDecl + +# 411| exit ConstructorDecl (normal) +#-----| -> exit ConstructorDecl + +# 412| DeclRefExpr +#-----| -> MemberRefExpr + +# 412| MemberRefExpr +#-----| -> IntegerLiteralExpr + +# 412| AssignExpr +#-----| -> ReturnStmt + +# 412| IntegerLiteralExpr +#-----| -> AssignExpr + +# 413| ReturnStmt +#-----| return -> exit ConstructorDecl (normal) + +# 417| TBD (YieldStmt) +#-----| -> exit AccessorDecl (normal) + +# 417| enter AccessorDecl + +# 417| enter AccessorDecl + +# 417| enter AccessorDecl +#-----| -> TBD (YieldStmt) + +# 417| exit AccessorDecl + +# 417| exit AccessorDecl + +# 417| exit AccessorDecl + +# 417| exit AccessorDecl (normal) +#-----| -> exit AccessorDecl + +# 417| exit AccessorDecl (normal) +#-----| -> exit AccessorDecl + +# 417| exit AccessorDecl (normal) +#-----| -> exit AccessorDecl + +# 418| enter ConstructorDecl +#-----| -> DeclRefExpr + +# 418| exit ConstructorDecl + +# 418| exit ConstructorDecl (normal) +#-----| -> exit ConstructorDecl + +# 419| DeclRefExpr +#-----| -> MemberRefExpr + +# 419| MemberRefExpr +#-----| -> IntegerLiteralExpr + +# 419| AssignExpr +#-----| -> ReturnStmt + +# 419| IntegerLiteralExpr +#-----| -> AssignExpr + +# 420| ReturnStmt +#-----| return -> exit ConstructorDecl (normal) diff --git a/swift/ql/test/library-tests/controlflow/graph/cfg.swift b/swift/ql/test/library-tests/controlflow/graph/cfg.swift index ae07cd05b71..9af52fce6c1 100644 --- a/swift/ql/test/library-tests/controlflow/graph/cfg.swift +++ b/swift/ql/test/library-tests/controlflow/graph/cfg.swift @@ -403,4 +403,41 @@ class Structors { func dictionaryLiteral(x: Int, y: Int) -> [String: Int] { return ["x": x, "y": y] +} + +func localDeclarations() -> Int { + class MyLocalClass { + var x: Int + init() { + x = 10 + } + } + + struct MyLocalStruct { + var x: Int + init() { + x = 10 + } + } + + enum MyLocalEnum { + case A + case B + } + + var myLocalVar : Int; + + // Error: declaration is only valid at file scope + // extension Int { + // func myExtensionMethod() -> Int { + // return self + // } + // } + + // protocol 'MyProtocol' cannot be nested inside another declaration + // protocol MyProtocol { + // func myMethod() + // } + + return 0 } \ No newline at end of file From df2c1972e9f120c984dfa7c4fe13f630c99940b0 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Thu, 26 May 2022 09:09:17 +0100 Subject: [PATCH 002/246] Swift: Add CFG trees for local declarations and accept test changes. --- .../internal/ControlFlowGraphImpl.qll | 8 + .../controlflow/graph/Cfg.expected | 139 ++++++++++++++++++ 2 files changed, 147 insertions(+) diff --git a/swift/ql/lib/codeql/swift/controlflow/internal/ControlFlowGraphImpl.qll b/swift/ql/lib/codeql/swift/controlflow/internal/ControlFlowGraphImpl.qll index cc89ab2f836..036e235c3d1 100644 --- a/swift/ql/lib/codeql/swift/controlflow/internal/ControlFlowGraphImpl.qll +++ b/swift/ql/lib/codeql/swift/controlflow/internal/ControlFlowGraphImpl.qll @@ -884,6 +884,14 @@ module Decls { ) } } + + private class AbstractFunctionDeclTree extends AstLeafTree { + override AbstractFunctionDecl ast; + } + + private class TypeDeclTree extends AstLeafTree { + override TypeDecl ast; + } } module Exprs { diff --git a/swift/ql/test/library-tests/controlflow/graph/Cfg.expected b/swift/ql/test/library-tests/controlflow/graph/Cfg.expected index 09a945976a0..9de7bce1b0b 100644 --- a/swift/ql/test/library-tests/controlflow/graph/Cfg.expected +++ b/swift/ql/test/library-tests/controlflow/graph/Cfg.expected @@ -357,6 +357,17 @@ cfg.swift: # 46| ClosureExpr #-----| -> ReturnStmt +# 51| enter ConcreteFuncDecl +#-----| -> ConcreteFuncDecl + +# 51| exit ConcreteFuncDecl + +# 51| exit ConcreteFuncDecl (normal) +#-----| -> exit ConcreteFuncDecl + +# 52| ConcreteFuncDecl +#-----| -> DeclRefExpr + # 52| enter ConcreteFuncDecl #-----| -> DeclRefExpr @@ -387,6 +398,12 @@ cfg.swift: # 53| DeclRefExpr #-----| -> BinaryExpr +# 55| ReturnStmt +#-----| return -> exit ConcreteFuncDecl (normal) + +# 55| DeclRefExpr +#-----| -> ReturnStmt + # 58| enter ConcreteFuncDecl #-----| -> ClosureExpr @@ -611,10 +628,16 @@ cfg.swift: # 81| enter ConcreteFuncDecl #-----| -> NamedPattern +# 81| exit ConcreteFuncDecl + +# 81| exit ConcreteFuncDecl (normal) +#-----| -> exit ConcreteFuncDecl + # 82| PatternBindingDecl #-----| -> ConcreteVarDecl # 82| ConcreteVarDecl +#-----| -> ConcreteFuncDecl # 82| NamedPattern #-----| -> IntegerLiteralExpr @@ -622,6 +645,9 @@ cfg.swift: # 82| IntegerLiteralExpr #-----| -> PatternBindingDecl +# 84| ConcreteFuncDecl +#-----| -> ConcreteFuncDecl + # 84| enter ConcreteFuncDecl #-----| -> DeclRefExpr @@ -658,6 +684,9 @@ cfg.swift: # 85| IntegerLiteralExpr #-----| -> BinaryExpr +# 88| ConcreteFuncDecl +#-----| -> DeclRefExpr + # 88| enter ConcreteFuncDecl #-----| -> DeclRefExpr @@ -675,6 +704,81 @@ cfg.swift: # 89| NilLiteralExpr #-----| -> AssignExpr +# 92| DeclRefExpr +#-----| -> DeclRefExpr + +# 92| CallExpr +#-----| exception -> exit ConcreteFuncDecl (normal) +#-----| -> NamedPattern + +# 92| InOutExpr +#-----| -> CallExpr + +# 92| DeclRefExpr +#-----| -> InOutExpr + +# 93| PatternBindingDecl +#-----| -> ConcreteVarDecl + +# 93| ConcreteVarDecl +#-----| -> DeclRefExpr + +# 93| NamedPattern +#-----| -> TypedPattern + +# 93| TypedPattern +#-----| -> IntegerLiteralExpr + +# 93| InjectIntoOptionalExpr +#-----| -> PatternBindingDecl + +# 93| IntegerLiteralExpr +#-----| -> InjectIntoOptionalExpr + +# 94| DeclRefExpr +#-----| -> DeclRefExpr + +# 94| CallExpr +#-----| exception -> exit ConcreteFuncDecl (normal) +#-----| -> DeclRefExpr + +# 94| InOutExpr +#-----| -> CallExpr + +# 94| DeclRefExpr +#-----| -> InOutExpr + +# 95| ReturnStmt +#-----| return -> exit ConcreteFuncDecl (normal) + +# 95| DeclRefExpr +#-----| -> LoadExpr + +# 95| LoadExpr +#-----| -> DeclRefExpr + +# 95| BinaryExpr +#-----| -> ReturnStmt + +# 95| DeclRefExpr +#-----| -> TypeExpr + +# 95| DotSyntaxCallExpr +#-----| exception -> exit ConcreteFuncDecl (normal) +#-----| -> DeclRefExpr + +# 95| TypeExpr +#-----| -> DotSyntaxCallExpr + +# 95| DeclRefExpr +#-----| -> LoadExpr + +# 95| LoadExpr +#-----| -> ForceValueExpr + +# 95| ForceValueExpr +#-----| -> BinaryExpr + # 99| enter AccessorDecl # 99| exit AccessorDecl @@ -4786,6 +4890,17 @@ cfg.swift: # 405| DeclRefExpr #-----| -> TupleExpr +# 408| enter ConcreteFuncDecl +#-----| -> ClassDecl + +# 408| exit ConcreteFuncDecl + +# 408| exit ConcreteFuncDecl (normal) +#-----| -> exit ConcreteFuncDecl + +# 409| ClassDecl +#-----| -> StructDecl + # 410| TBD (YieldStmt) #-----| -> exit AccessorDecl (normal) @@ -4834,6 +4949,9 @@ cfg.swift: # 413| ReturnStmt #-----| return -> exit ConstructorDecl (normal) +# 416| StructDecl +#-----| -> EnumDecl + # 417| TBD (YieldStmt) #-----| -> exit AccessorDecl (normal) @@ -4881,3 +4999,24 @@ cfg.swift: # 420| ReturnStmt #-----| return -> exit ConstructorDecl (normal) + +# 423| EnumDecl +#-----| -> NamedPattern + +# 428| PatternBindingDecl +#-----| -> ConcreteVarDecl + +# 428| ConcreteVarDecl +#-----| -> IntegerLiteralExpr + +# 428| NamedPattern +#-----| -> TypedPattern + +# 428| TypedPattern +#-----| -> PatternBindingDecl + +# 442| ReturnStmt +#-----| return -> exit ConcreteFuncDecl (normal) + +# 442| IntegerLiteralExpr +#-----| -> ReturnStmt From 7b5d9ec7df7129aeae2d0bc596b09baa5d157f72 Mon Sep 17 00:00:00 2001 From: Rasmus Lerchedahl Petersen Date: Tue, 14 Jun 2022 14:59:59 +0200 Subject: [PATCH 003/246] python: Straight port of tarslip --- .../python/security/dataflow/TarSlip.qll | 29 +++ .../dataflow/TarSlipCustomizations.qll | 145 +++++++++++++++ python/ql/src/Security/CWE-022/TarSlip.ql | 170 +----------------- .../Security/CWE-022-TarSlip/options | 1 - 4 files changed, 179 insertions(+), 166 deletions(-) create mode 100644 python/ql/lib/semmle/python/security/dataflow/TarSlip.qll create mode 100644 python/ql/lib/semmle/python/security/dataflow/TarSlipCustomizations.qll delete mode 100644 python/ql/test/query-tests/Security/CWE-022-TarSlip/options diff --git a/python/ql/lib/semmle/python/security/dataflow/TarSlip.qll b/python/ql/lib/semmle/python/security/dataflow/TarSlip.qll new file mode 100644 index 00000000000..90c79da1f04 --- /dev/null +++ b/python/ql/lib/semmle/python/security/dataflow/TarSlip.qll @@ -0,0 +1,29 @@ +/** + * Provides a taint-tracking configuration for detecting "command injection" vulnerabilities. + * + * Note, for performance reasons: only import this file if + * `TarSlip::Configuration` is needed, otherwise + * `TarSlipCustomizations` should be imported instead. + */ + +private import python +import semmle.python.dataflow.new.DataFlow +import semmle.python.dataflow.new.TaintTracking +import TarSlipCustomizations::TarSlip + +/** + * A taint-tracking configuration for detecting "command injection" vulnerabilities. + */ +class Configuration extends TaintTracking::Configuration { + Configuration() { this = "TarSlip" } + + override predicate isSource(DataFlow::Node source) { source instanceof Source } + + override predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer } + + override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { + guard instanceof SanitizerGuard + } +} diff --git a/python/ql/lib/semmle/python/security/dataflow/TarSlipCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/TarSlipCustomizations.qll new file mode 100644 index 00000000000..7393b129f37 --- /dev/null +++ b/python/ql/lib/semmle/python/security/dataflow/TarSlipCustomizations.qll @@ -0,0 +1,145 @@ +/** + * Provides default sources, sinks and sanitizers for detecting + * "tar slip" + * vulnerabilities, as well as extension points for adding your own. + */ + +private import python +private import semmle.python.dataflow.new.DataFlow +private import semmle.python.Concepts +private import semmle.python.dataflow.new.BarrierGuards +private import semmle.python.ApiGraphs + +/** + * Provides default sources, sinks and sanitizers for detecting + * "tar slip" + * vulnerabilities, as well as extension points for adding your own. + */ +module TarSlip { + /** + * A data flow source for "tar slip" vulnerabilities. + */ + abstract class Source extends DataFlow::Node { } + + /** + * A data flow sink for "tar slip" vulnerabilities. + */ + abstract class Sink extends DataFlow::Node { } + + /** + * A sanitizer for "tar slip" vulnerabilities. + */ + abstract class Sanitizer extends DataFlow::Node { } + + /** + * A sanitizer guard for "tar slip" vulnerabilities. + */ + abstract class SanitizerGuard extends DataFlow::BarrierGuard { } + + /** + * A source of exception info, considered as a flow source. + */ + class TarfileOpen extends Source { + TarfileOpen() { + this = API::moduleImport("tarfile").getMember("open").getACall() and + // If argument refers to a string object, then it's a hardcoded path and + // this tarfile is safe. + not this.(DataFlow::CallCfgNode).getArg(0).getALocalSource().asExpr() instanceof StrConst and + /* Ignore opens within the tarfile module itself */ + not this.getLocation().getFile().getBaseName() = "tarfile.py" + } + } + + /** + * For efficiency we don't want to track the flow of taint + * around the tarfile module. + */ + class ExcludeTarFilePy extends Sanitizer { + ExcludeTarFilePy() { this.getLocation().getFile().getBaseName() = "tarfile.py" } + } + + /** + * For a call to `file.extractall` without arguments, `file` is considered a sink. + */ + class ExtractAllSink extends Sink { + ExtractAllSink() { + exists(DataFlow::CallCfgNode call | + call = + API::moduleImport("tarfile") + .getMember("open") + .getReturn() + .getMember("extractall") + .getACall() and + not exists(call.getArg(_)) and + not exists(call.getArgByName(_)) and + this = call.(DataFlow::MethodCallNode).getObject() + ) + } + } + + /** + * An argument to `extract` is considered a sink. + */ + class ExtractSink extends Sink { + ExtractSink() { + exists(DataFlow::CallCfgNode call | + call = + API::moduleImport("tarfile").getMember("open").getReturn().getMember("extract").getACall() and + this = call.getArg(0) + ) + } + } + + /* Members argument to extract method */ + class ExtractMembersSink extends Sink { + ExtractMembersSink() { + exists(DataFlow::CallCfgNode call | + call = + API::moduleImport("tarfile") + .getMember("open") + .getReturn() + .getMember("extractall") + .getACall() and + this in [call.getArg(0), call.getArgByName("members")] + ) + } + } + + class TarFileInfoSanitizer extends SanitizerGuard { + ControlFlowNode tarInfo; + + TarFileInfoSanitizer() { + exists(CallNode call, AttrNode attr | + this = call and + // We must test the name of the tar info object. + attr = call.getAnArg() and + attr.getName() = "name" and + attr.getObject() = tarInfo + | + // Assume that any test with "path" in it is a sanitizer + call.getAChild*().(AttrNode).getName().matches("%path") + or + call.getAChild*().(NameNode).getId().matches("%path") + ) + } + + override predicate checks(ControlFlowNode checked, boolean branch) { + checked = tarInfo and + branch in [true, false] + } + + DataFlow::ExprNode shouldGuard() { + tarInfo.dominates(result.asCfgNode()) and + // exists(EssaDefinition def | + // def.getAUse() = tarInfo and + // def.getAUse() = result.asCfgNode() + // ) and + exists(SsaSourceVariable v | + v.getAUse() = tarInfo and + v.getAUse() = result.asCfgNode() + ) + } + } + + DataFlow::ExprNode getAGuardedNode(TarFileInfoSanitizer tfis) { result = tfis.getAGuardedNode() } +} diff --git a/python/ql/src/Security/CWE-022/TarSlip.ql b/python/ql/src/Security/CWE-022/TarSlip.ql index 76d799a0aca..1528be7377d 100644 --- a/python/ql/src/Security/CWE-022/TarSlip.ql +++ b/python/ql/src/Security/CWE-022/TarSlip.ql @@ -13,170 +13,10 @@ */ import python -import semmle.python.security.Paths -import semmle.python.dataflow.TaintTracking -import semmle.python.security.strings.Basic +import semmle.python.security.dataflow.TarSlip +import DataFlow::PathGraph -/** A TaintKind to represent open tarfile objects. That is, the result of calling `tarfile.open(...)` */ -class OpenTarFile extends TaintKind { - OpenTarFile() { this = "tarfile.open" } - - override TaintKind getTaintOfMethodResult(string name) { - name = "getmember" and result instanceof TarFileInfo - or - name = "getmembers" and result.(SequenceKind).getItem() instanceof TarFileInfo - } - - override ClassValue getType() { result = Value::named("tarfile.TarFile") } - - override TaintKind getTaintForIteration() { result instanceof TarFileInfo } -} - -/** The source of open tarfile objects. That is, any call to `tarfile.open(...)` */ -class TarfileOpen extends TaintSource { - TarfileOpen() { - Value::named("tarfile.open").getACall() = this and - /* - * If argument refers to a string object, then it's a hardcoded path and - * this tarfile is safe. - */ - - not this.(CallNode).getAnArg().pointsTo(any(StringValue str)) and - /* Ignore opens within the tarfile module itself */ - not this.(ControlFlowNode).getLocation().getFile().getBaseName() = "tarfile.py" - } - - override predicate isSourceOf(TaintKind kind) { kind instanceof OpenTarFile } -} - -class TarFileInfo extends TaintKind { - TarFileInfo() { this = "tarfile.entry" } - - override TaintKind getTaintOfMethodResult(string name) { name = "next" and result = this } - - override TaintKind getTaintOfAttribute(string name) { - name = "name" and result instanceof TarFileInfo - } -} - -/* - * For efficiency we don't want to track the flow of taint - * around the tarfile module. - */ - -class ExcludeTarFilePy extends Sanitizer { - ExcludeTarFilePy() { this = "Tar sanitizer" } - - override predicate sanitizingNode(TaintKind taint, ControlFlowNode node) { - node.getLocation().getFile().getBaseName() = "tarfile.py" and - ( - taint instanceof OpenTarFile - or - taint instanceof TarFileInfo - or - taint.(SequenceKind).getItem() instanceof TarFileInfo - ) - } -} - -/* Any call to an extractall method */ -class ExtractAllSink extends TaintSink { - ExtractAllSink() { - exists(CallNode call | - this = call.getFunction().(AttrNode).getObject("extractall") and - not exists(call.getAnArg()) - ) - } - - override predicate sinks(TaintKind kind) { kind instanceof OpenTarFile } -} - -/* Argument to extract method */ -class ExtractSink extends TaintSink { - CallNode call; - - ExtractSink() { - call.getFunction().(AttrNode).getName() = "extract" and - this = call.getArg(0) - } - - override predicate sinks(TaintKind kind) { kind instanceof TarFileInfo } -} - -/* Members argument to extract method */ -class ExtractMembersSink extends TaintSink { - CallNode call; - - ExtractMembersSink() { - call.getFunction().(AttrNode).getName() = "extractall" and - (this = call.getArg(0) or this = call.getArgByName("members")) - } - - override predicate sinks(TaintKind kind) { - kind.(SequenceKind).getItem() instanceof TarFileInfo - or - kind instanceof OpenTarFile - } -} - -class TarFileInfoSanitizer extends Sanitizer { - TarFileInfoSanitizer() { this = "TarInfo sanitizer" } - - /* The test `if :` clears taint on its `false` edge. */ - override predicate sanitizingEdge(TaintKind taint, PyEdgeRefinement test) { - taint instanceof TarFileInfo and - clears_taint_on_false_edge(test.getTest(), test.getSense()) - } - - private predicate clears_taint_on_false_edge(ControlFlowNode test, boolean sense) { - path_sanitizing_test(test) and - sense = false - or - // handle `not` (also nested) - test.(UnaryExprNode).getNode().getOp() instanceof Not and - clears_taint_on_false_edge(test.(UnaryExprNode).getOperand(), sense.booleanNot()) - } -} - -private predicate path_sanitizing_test(ControlFlowNode test) { - /* Assume that any test with "path" in it is a sanitizer */ - test.getAChild+().(AttrNode).getName().matches("%path") - or - test.getAChild+().(NameNode).getId().matches("%path") -} - -class TarSlipConfiguration extends TaintTracking::Configuration { - TarSlipConfiguration() { this = "TarSlip configuration" } - - override predicate isSource(TaintTracking::Source source) { source instanceof TarfileOpen } - - override predicate isSink(TaintTracking::Sink sink) { - sink instanceof ExtractSink or - sink instanceof ExtractAllSink or - sink instanceof ExtractMembersSink - } - - override predicate isSanitizer(Sanitizer sanitizer) { - sanitizer instanceof TarFileInfoSanitizer - or - sanitizer instanceof ExcludeTarFilePy - } - - override predicate isBarrier(DataFlow::Node node) { - // Avoid flow into the tarfile module - exists(ParameterDefinition def | - node.asVariable().getDefinition() = def - or - node.asCfgNode() = def.getDefiningNode() - | - def.getScope() = Value::named("tarfile.open").(CallableValue).getScope() - or - def.isSelf() and def.getScope().getEnclosingModule().getName() = "tarfile" - ) - } -} - -from TarSlipConfiguration config, TaintedPathSource src, TaintedPathSink sink -where config.hasFlowPath(src, sink) -select sink.getSink(), src, sink, "Extraction of tarfile from $@", src.getSource(), +from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink +where config.hasFlowPath(source, sink) +select sink.getNode(), source, sink, "Extraction of tarfile from $@", source.getNode(), "a potentially untrusted source" diff --git a/python/ql/test/query-tests/Security/CWE-022-TarSlip/options b/python/ql/test/query-tests/Security/CWE-022-TarSlip/options deleted file mode 100644 index 492768b3481..00000000000 --- a/python/ql/test/query-tests/Security/CWE-022-TarSlip/options +++ /dev/null @@ -1 +0,0 @@ -semmle-extractor-options: -p ../lib/ --max-import-depth=3 From 1959f491658d8ae892034d623ef1983042c35d10 Mon Sep 17 00:00:00 2001 From: Joe Farebrother Date: Tue, 29 Mar 2022 10:44:11 +0100 Subject: [PATCH 004/246] Add Improper Intent Verification query --- .../ImproperIntentVerificationQuery.qll | 154 ++++++++++++++++++ .../CWE/CWE-925/ImproperIntentVerification.ql | 18 ++ 2 files changed, 172 insertions(+) create mode 100644 java/ql/lib/semmle/code/java/security/ImproperIntentVerificationQuery.qll create mode 100644 java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql diff --git a/java/ql/lib/semmle/code/java/security/ImproperIntentVerificationQuery.qll b/java/ql/lib/semmle/code/java/security/ImproperIntentVerificationQuery.qll new file mode 100644 index 00000000000..f300697a6ed --- /dev/null +++ b/java/ql/lib/semmle/code/java/security/ImproperIntentVerificationQuery.qll @@ -0,0 +1,154 @@ +/** Definitions for the improper intent verification query. */ + +import java +import semmle.code.java.dataflow.DataFlow + +/** An `onRecieve` method of a `BroadcastReciever` */ +private class OnReceiveMethod extends Method { + OnReceiveMethod() { + this.getASourceOverriddenMethod*() + .hasQualifiedName("android.content", "BroadcastReciever", "onReceeve") + } + + /** Gets the paramter of this method that holds the received `Intent`. */ + Parameter getIntentParameter() { result = this.getParameter(1) } +} + +/** A configuration to detect whether the `action` of an `Intent` is checked. */ +private class VerifiedIntentConfig extends DataFlow::Configuration { + VerifiedIntentConfig() { this = "VerifiedIntentConfig" } + + override predicate isSource(DataFlow::Node src) { + src.asParameter() = any(OnReceiveMethod orm).getIntentParameter() + } + + override predicate isSink(DataFlow::Node sink) { + exists(MethodAccess ma | + ma.getMethod().hasQualifiedName("android.content", "Intent", "getAction") and + sink.asExpr() = ma.getQualifier() + ) + } +} + +/** An `onRecieve` method that doesn't verify the action of the intent it recieves. */ +class UnverifiedOnReceiveMethod extends OnReceiveMethod { + UnverifiedOnReceiveMethod() { + not any(VerifiedIntentConfig c).hasFlow(DataFlow::parameterNode(this.getIntentParameter()), _) + } +} + +/** Gets the name of an intent action that can only be sent by the system. */ +string getASystemActionName() { + result = + [ + "AIRPLANE_MODE", "AIRPLANE_MODE_CHANGED", "APPLICATION_LOCALE_CHANGED", + "APPLICATION_RESTRICTIONS_CHANGED", "BATTERY_CHANGED", "BATTERY_LOW", "BATTERY_OKAY", + "BOOT_COMPLETED", "CONFIGURATION_CHANGED", "DEVICE_STORAGE_LOW", "DEVICE_STORAGE_OK", + "DREAMING_STARTED", "DREAMING_STOPPED", "EXTERNAL_APPLICATIONS_AVAILABLE", + "EXTERNAL_APPLICATIONS_UNAVAILABLE", "LOCALE_CHANGED", "LOCKED_BOOT_COMPLETED", + "MY_PACKAGE_REPLACED", "MY_PACKAGE_SUSPENDED", "MY_PACKAGE_UNSUSPENDED", "NEW_OUTGOING_CALL", + "PACKAGES_SUSPENDED", "PACKAGES_UNSUSPENDED", "PACKAGE_ADDED", "PACKAGE_CHANGED", + "PACKAGE_DATA_CLEARED", "PACKAGE_FIRST_LAUNCH", "PACKAGE_FULLY_REMOVED", "PACKAGE_INSTALL", + "PACKAGE_NEEDS_VERIFICATION", "PACKAGE_REMOVED", "PACKAGE_REPLACED", "PACKAGE_RESTARTED", + "PACKAGE_VERIFIED", "POWER_CONNECTED", "POWER_DISCONNECTED", "REBOOT", "SCREEN_OFF", + "SCREEN_ON", "SHUTDOWN", "TIMEZONE_CHANGED", "TIME_TICK", "UID_REMOVED", "USER_PRESENT" + ] +} + +/** An expression or XML attribute that contains the name of a system intent action. */ +class SystemActionName extends Top { + string name; + + SystemActionName() { + name = getASystemActionName() and + ( + this.(StringLiteral).getValue() = "android.intent.action." + name + or + this.(FieldRead).getField().hasQualifiedName("android.content", "Intent", "ACTION_" + name) + or + this.(XMLAttribute).getValue() = "android.intent.action." + name + ) + } + + /** Gets the name of the system intent that this expression or attriute represents. */ + string getName() { result = name } +} + +/** A call to `Context.registerReciever` */ +private class RegisterReceiverCall extends MethodAccess { + RegisterReceiverCall() { + this.getMethod() + .getASourceOverriddenMethod*() + .hasQualifiedName("android.content", "Context", "registerReceiver") + } + + /** Gets the `BroadcastReceiver` argument to this call. */ + Expr getReceiverArgument() { result = this.getArgument(0) } + + /** Gets the `IntentFilter` argument to this call. */ + Expr getFilterArgument() { result = this.getArgument(1) } +} + +/** A configuration to detect uses of `registerReciever` with system intent actions. */ +private class RegisterSystemActionConfig extends DataFlow::Configuration { + RegisterSystemActionConfig() { this = "RegisterSystemActionConfig" } + + override predicate isSource(DataFlow::Node node) { node.asExpr() instanceof SystemActionName } + + override predicate isSink(DataFlow::Node node) { + exists(RegisterReceiverCall ma | node.asExpr() = ma.getFilterArgument()) + } + + override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { + exists(ConstructorCall cc | + cc.getConstructedType().hasQualifiedName("android.content", "IntentFilter") and + node1.asExpr() = cc.getArgument(0) and + node2.asExpr() = cc + ) + or + exists(MethodAccess ma | + ma.getMethod().hasQualifiedName("android.content", "IntentFilter", "create") and + node1.asExpr() = ma.getArgument(0) and + node2.asExpr() = ma + ) + or + exists(MethodAccess ma | + ma.getMethod().hasQualifiedName("android.content", "IntentFilter", "addAction") and + node1.asExpr() = ma.getArgument(0) and + node2.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr() = ma.getQualifier() + ) + } +} + +/** Holds if `rrc` registers a reciever `orm` to recieve the system action `sa` that doesn't verifiy intents it recieves. */ +predicate registeredUnverifiedSystemReciever( + RegisterReceiverCall rrc, UnverifiedOnReceiveMethod orm, SystemActionName sa +) { + exists(RegisterSystemActionConfig conf, ConstructorCall cc | + conf.hasFlow(DataFlow::exprNode(sa), DataFlow::exprNode(rrc.getFilterArgument())) and + cc.getConstructedType() = orm.getDeclaringType() and + DataFlow::localExprFlow(cc, rrc.getReceiverArgument()) + ) +} + +/** Holds if the XML element `rec` declares a reciever `orm` to recieve the system action named `sa` that doesn't verifiy intents it recieves. */ +predicate xmlUnverifiedSystemReciever( + XMLElement rec, UnverifiedOnReceiveMethod orm, SystemActionName sa +) { + exists(XMLElement filter, XMLElement action, Class ormty | + rec.hasName("receiver") and + filter.hasName("intent-filter") and + action.hasName("action") and + filter = rec.getAChild() and + action = rec.getAChild() and + ormty = orm.getDeclaringType() and + rec.getAttribute("android:name").getValue() = ["." + ormty.getName(), ormty.getQualifiedName()] and + action.getAttribute("android:name") = sa + ) +} + +/** Holds if `reg` registers (either explicitly or through XML) a reciever `orm` to recieve the system action named `sa` that doesn't verify intents it recieves. */ +predicate unverifiedSystemReciever(Top reg, Method orm, SystemActionName sa) { + registeredUnverifiedSystemReciever(reg, orm, sa) or + xmlUnverifiedSystemReciever(reg, orm, sa) +} diff --git a/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql b/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql new file mode 100644 index 00000000000..249da869250 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql @@ -0,0 +1,18 @@ +/** + * @name Improper Verification of Intent by Broadcast Reciever + * @description The Android application uses a Broadcast Receiver that receives an Intent but does not properly verify that the Intent came from an authorized source. + * @kind problem + * @problem.severity warning + * @precision high + * @id java/improper-intent-verification + * @tags security + * external/cwe/cwe-925 + */ + +import java +import semmle.code.java.security.ImproperIntentVerificationQuery + +from Top reg, Method orm, SystemActionName sa +where unverifiedSystemReciever(reg, orm, sa) +select orm, "This reciever doesn't verify intents it recieves, and is registered $@ to recieve $@.", + reg, "here", sa, "the system action " + sa.getName() From 87f26bf0337f5549c2486e3c64f20a0da03bec68 Mon Sep 17 00:00:00 2001 From: Joe Farebrother Date: Tue, 5 Apr 2022 12:32:31 +0100 Subject: [PATCH 005/246] Fix typos --- .../ImproperIntentVerificationQuery.qll | 20 +++++++++---------- .../CWE/CWE-925/ImproperIntentVerification.ql | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/java/ql/lib/semmle/code/java/security/ImproperIntentVerificationQuery.qll b/java/ql/lib/semmle/code/java/security/ImproperIntentVerificationQuery.qll index f300697a6ed..e8ed1f17b42 100644 --- a/java/ql/lib/semmle/code/java/security/ImproperIntentVerificationQuery.qll +++ b/java/ql/lib/semmle/code/java/security/ImproperIntentVerificationQuery.qll @@ -3,11 +3,11 @@ import java import semmle.code.java.dataflow.DataFlow -/** An `onRecieve` method of a `BroadcastReciever` */ +/** An `onReceive` method of a `BroadcastReceiver` */ private class OnReceiveMethod extends Method { OnReceiveMethod() { this.getASourceOverriddenMethod*() - .hasQualifiedName("android.content", "BroadcastReciever", "onReceeve") + .hasQualifiedName("android.content", "BroadcastReceiver", "onReceive") } /** Gets the paramter of this method that holds the received `Intent`. */ @@ -30,7 +30,7 @@ private class VerifiedIntentConfig extends DataFlow::Configuration { } } -/** An `onRecieve` method that doesn't verify the action of the intent it recieves. */ +/** An `onReceive` method that doesn't verify the action of the intent it recieves. */ class UnverifiedOnReceiveMethod extends OnReceiveMethod { UnverifiedOnReceiveMethod() { not any(VerifiedIntentConfig c).hasFlow(DataFlow::parameterNode(this.getIntentParameter()), _) @@ -74,7 +74,7 @@ class SystemActionName extends Top { string getName() { result = name } } -/** A call to `Context.registerReciever` */ +/** A call to `Context.registerReceiver` */ private class RegisterReceiverCall extends MethodAccess { RegisterReceiverCall() { this.getMethod() @@ -89,7 +89,7 @@ private class RegisterReceiverCall extends MethodAccess { Expr getFilterArgument() { result = this.getArgument(1) } } -/** A configuration to detect uses of `registerReciever` with system intent actions. */ +/** A configuration to detect uses of `registerReceiver` with system intent actions. */ private class RegisterSystemActionConfig extends DataFlow::Configuration { RegisterSystemActionConfig() { this = "RegisterSystemActionConfig" } @@ -121,7 +121,7 @@ private class RegisterSystemActionConfig extends DataFlow::Configuration { } /** Holds if `rrc` registers a reciever `orm` to recieve the system action `sa` that doesn't verifiy intents it recieves. */ -predicate registeredUnverifiedSystemReciever( +predicate registeredUnverifiedSystemReceiver( RegisterReceiverCall rrc, UnverifiedOnReceiveMethod orm, SystemActionName sa ) { exists(RegisterSystemActionConfig conf, ConstructorCall cc | @@ -132,7 +132,7 @@ predicate registeredUnverifiedSystemReciever( } /** Holds if the XML element `rec` declares a reciever `orm` to recieve the system action named `sa` that doesn't verifiy intents it recieves. */ -predicate xmlUnverifiedSystemReciever( +predicate xmlUnverifiedSystemReceiver( XMLElement rec, UnverifiedOnReceiveMethod orm, SystemActionName sa ) { exists(XMLElement filter, XMLElement action, Class ormty | @@ -148,7 +148,7 @@ predicate xmlUnverifiedSystemReciever( } /** Holds if `reg` registers (either explicitly or through XML) a reciever `orm` to recieve the system action named `sa` that doesn't verify intents it recieves. */ -predicate unverifiedSystemReciever(Top reg, Method orm, SystemActionName sa) { - registeredUnverifiedSystemReciever(reg, orm, sa) or - xmlUnverifiedSystemReciever(reg, orm, sa) +predicate unverifiedSystemReceiver(Top reg, Method orm, SystemActionName sa) { + registeredUnverifiedSystemReceiver(reg, orm, sa) or + xmlUnverifiedSystemReceiver(reg, orm, sa) } diff --git a/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql b/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql index 249da869250..867f2733954 100644 --- a/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql +++ b/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql @@ -13,6 +13,6 @@ import java import semmle.code.java.security.ImproperIntentVerificationQuery from Top reg, Method orm, SystemActionName sa -where unverifiedSystemReciever(reg, orm, sa) +where unverifiedSystemReceiver(reg, orm, sa) select orm, "This reciever doesn't verify intents it recieves, and is registered $@ to recieve $@.", reg, "here", sa, "the system action " + sa.getName() From 4aed1a1e235c737a09ed87becef3ba9222412af3 Mon Sep 17 00:00:00 2001 From: Joe Farebrother Date: Fri, 22 Apr 2022 16:41:01 +0100 Subject: [PATCH 006/246] Add test cases; fix handling of recievers declared through xml --- .../ImproperIntentVerificationQuery.qll | 11 +++++-- .../security/CWE-925/AndroidManifest.xml | 9 ++++++ .../security/CWE-925/BootReceiverXml.java | 13 ++++++++ .../ImproperIntentVerification.expected | 0 .../CWE-925/ImproperIntentVerification.ql | 18 +++++++++++ .../ImproperIntentVerificationTest.java | 31 +++++++++++++++++++ .../test/query-tests/security/CWE-925/options | 1 + 7 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 java/ql/test/query-tests/security/CWE-925/AndroidManifest.xml create mode 100644 java/ql/test/query-tests/security/CWE-925/BootReceiverXml.java create mode 100644 java/ql/test/query-tests/security/CWE-925/ImproperIntentVerification.expected create mode 100644 java/ql/test/query-tests/security/CWE-925/ImproperIntentVerification.ql create mode 100644 java/ql/test/query-tests/security/CWE-925/ImproperIntentVerificationTest.java create mode 100644 java/ql/test/query-tests/security/CWE-925/options diff --git a/java/ql/lib/semmle/code/java/security/ImproperIntentVerificationQuery.qll b/java/ql/lib/semmle/code/java/security/ImproperIntentVerificationQuery.qll index e8ed1f17b42..9004414664a 100644 --- a/java/ql/lib/semmle/code/java/security/ImproperIntentVerificationQuery.qll +++ b/java/ql/lib/semmle/code/java/security/ImproperIntentVerificationQuery.qll @@ -72,6 +72,11 @@ class SystemActionName extends Top { /** Gets the name of the system intent that this expression or attriute represents. */ string getName() { result = name } + + override string toString() { + result = + [this.(StringLiteral).toString(), this.(FieldRead).toString(), this.(XMLAttribute).toString()] + } } /** A call to `Context.registerReceiver` */ @@ -140,10 +145,10 @@ predicate xmlUnverifiedSystemReceiver( filter.hasName("intent-filter") and action.hasName("action") and filter = rec.getAChild() and - action = rec.getAChild() and + action = filter.getAChild() and ormty = orm.getDeclaringType() and - rec.getAttribute("android:name").getValue() = ["." + ormty.getName(), ormty.getQualifiedName()] and - action.getAttribute("android:name") = sa + rec.getAttribute("name").getValue() = ["." + ormty.getName(), ormty.getQualifiedName()] and + action.getAttribute("name") = sa ) } diff --git a/java/ql/test/query-tests/security/CWE-925/AndroidManifest.xml b/java/ql/test/query-tests/security/CWE-925/AndroidManifest.xml new file mode 100644 index 00000000000..f9e11a1ee81 --- /dev/null +++ b/java/ql/test/query-tests/security/CWE-925/AndroidManifest.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/java/ql/test/query-tests/security/CWE-925/BootReceiverXml.java b/java/ql/test/query-tests/security/CWE-925/BootReceiverXml.java new file mode 100644 index 00000000000..3a9f8498396 --- /dev/null +++ b/java/ql/test/query-tests/security/CWE-925/BootReceiverXml.java @@ -0,0 +1,13 @@ +package test; +import android.content.Intent; +import android.content.Context; +import android.content.BroadcastReceiver; + +class BootReceiverXml extends BroadcastReceiver { + void doStuff(Intent intent) {} + + @Override + public void onReceive(Context ctx, Intent intent) { // $hasResult + doStuff(intent); + } +} \ No newline at end of file diff --git a/java/ql/test/query-tests/security/CWE-925/ImproperIntentVerification.expected b/java/ql/test/query-tests/security/CWE-925/ImproperIntentVerification.expected new file mode 100644 index 00000000000..e69de29bb2d diff --git a/java/ql/test/query-tests/security/CWE-925/ImproperIntentVerification.ql b/java/ql/test/query-tests/security/CWE-925/ImproperIntentVerification.ql new file mode 100644 index 00000000000..30ced62b2ed --- /dev/null +++ b/java/ql/test/query-tests/security/CWE-925/ImproperIntentVerification.ql @@ -0,0 +1,18 @@ +import java +import semmle.code.java.security.ImproperIntentVerificationQuery +import TestUtilities.InlineExpectationsTest + +class HasFlowTest extends InlineExpectationsTest { + HasFlowTest() { this = "HasFlowTest" } + + override string getARelevantTag() { result = "hasResult" } + + override predicate hasActualResult(Location location, string element, string tag, string value) { + tag = "hasResult" and + exists(Method orm | unverifiedSystemReceiver(_, orm, _) | + orm.getLocation() = location and + element = orm.toString() and + value = "" + ) + } +} diff --git a/java/ql/test/query-tests/security/CWE-925/ImproperIntentVerificationTest.java b/java/ql/test/query-tests/security/CWE-925/ImproperIntentVerificationTest.java new file mode 100644 index 00000000000..736410eb9f0 --- /dev/null +++ b/java/ql/test/query-tests/security/CWE-925/ImproperIntentVerificationTest.java @@ -0,0 +1,31 @@ +package test; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.Context; +import android.content.BroadcastReceiver; + +class ImproperIntentVerificationTest { + static void doStuff(Intent intent) {} + + class ShutdownBroadcastReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context ctx, Intent intent) { // $hasResult + doStuff(intent); + } + } + + class ShutdownBroadcastReceiverSafe extends BroadcastReceiver { + @Override + public void onReceive(Context ctx, Intent intent) { + if (!intent.getAction().equals(Intent.ACTION_SHUTDOWN)) { + return; + } + doStuff(intent); + } + } + + void test(Context c) { + c.registerReceiver(new ShutdownBroadcastReceiver(), new IntentFilter(Intent.ACTION_SHUTDOWN)); + c.registerReceiver(new ShutdownBroadcastReceiverSafe(), new IntentFilter(Intent.ACTION_SHUTDOWN)); + } +} \ No newline at end of file diff --git a/java/ql/test/query-tests/security/CWE-925/options b/java/ql/test/query-tests/security/CWE-925/options new file mode 100644 index 00000000000..5a47a1d8fd3 --- /dev/null +++ b/java/ql/test/query-tests/security/CWE-925/options @@ -0,0 +1 @@ +// semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/google-android-9.0.0 \ No newline at end of file From 8e2e8cc77feba695d92465f64e1efcee2c94b698 Mon Sep 17 00:00:00 2001 From: Joe Farebrother Date: Wed, 27 Apr 2022 16:04:06 +0100 Subject: [PATCH 007/246] Add qhelp --- java/ql/src/Security/CWE/CWE-925/Bad.java | 13 +++++++ java/ql/src/Security/CWE/CWE-925/Good.java | 16 ++++++++ .../CWE-925/ImproperIntentVerification.qhelp | 39 +++++++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 java/ql/src/Security/CWE/CWE-925/Bad.java create mode 100644 java/ql/src/Security/CWE/CWE-925/Good.java create mode 100644 java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.qhelp diff --git a/java/ql/src/Security/CWE/CWE-925/Bad.java b/java/ql/src/Security/CWE/CWE-925/Bad.java new file mode 100644 index 00000000000..a2703d83cf4 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-925/Bad.java @@ -0,0 +1,13 @@ +... +IntentFilter filter = new IntentFilter(Intent.ACTION_SHUTDOWN); +BroadcastReceiver sReceiver = new ShutDownReceiver(); +context.registerReceiver(sReceiver, filter); +... + +public class ShutdownReceiver extends BroadcastReceiver { + @Override + public void onReceive(final Context context, final Intent intent) { + mainActivity.saveLocalData(); + mainActivity.stopActivity(); + } +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-925/Good.java b/java/ql/src/Security/CWE/CWE-925/Good.java new file mode 100644 index 00000000000..3830b8c4bc3 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-925/Good.java @@ -0,0 +1,16 @@ +... +IntentFilter filter = new IntentFilter(Intent.ACTION_SHUTDOWN); +BroadcastReceiver sReceiver = new ShutDownReceiver(); +context.registerReceiver(sReceiver, filter); +... + +public class ShutdownReceiver extends BroadcastReceiver { + @Override + public void onReceive(final Context context, final Intent intent) { + if (!intent.getAction().equals(Intent.ACTION_SHUTDOWN)) { + return; + } + mainActivity.saveLocalData(); + mainActivity.stopActivity(); + } +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.qhelp b/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.qhelp new file mode 100644 index 00000000000..d4218c228fb --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.qhelp @@ -0,0 +1,39 @@ + + + + + +

+When an android application uses a BroadcastReciever to receive Intents, +it is also able to receive explicit Intents that are sent drctly to it, egardless of its filter. + +Certain intent actions are only able to be sent by the operating system, not third-party applications. +However, a BroadcastReceiver that is registered to recieve system intents is still able to recieve +other intents from a third-party application, so it should check that the intent received has the expected action. +Otherwise, a third-party application could impersonate the system this way and cause unintended behaviour, such as a denial of service. +

+
+ + +

In the following code, the ShutdownReceiver initiates a shutdown procedure upon receiving an Intent, + without checking that the received action is indeed ACTION_SHUTDOWN. This allows third-party applications to + send explicit intents to this receiver to cause a denial of service.

+ +
+ + +

+In the onReceive method of a BroadcastReciever, the action of the received Intent should be checked. The following code demonstrates this. +

+ +
+ + + + + + + +
From 2fc142f41f38ece9f04109b530e309bc869b4ff0 Mon Sep 17 00:00:00 2001 From: Joe Farebrother Date: Wed, 27 Apr 2022 16:31:02 +0100 Subject: [PATCH 008/246] Add security severity --- java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql | 1 + 1 file changed, 1 insertion(+) diff --git a/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql b/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql index 867f2733954..3790ce3bb4d 100644 --- a/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql +++ b/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql @@ -3,6 +3,7 @@ * @description The Android application uses a Broadcast Receiver that receives an Intent but does not properly verify that the Intent came from an authorized source. * @kind problem * @problem.severity warning + * @security-severity 8.2 * @precision high * @id java/improper-intent-verification * @tags security From d88d216388c3d7c50ca15d75a3372c5e77bcff38 Mon Sep 17 00:00:00 2001 From: Joe Farebrother Date: Wed, 27 Apr 2022 16:37:58 +0100 Subject: [PATCH 009/246] Add change note --- .../src/Security/CWE/CWE-925/ImproperIntentVerification.ql | 2 +- java/ql/src/change-notes/2022-04-27-intent-verification.md | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 java/ql/src/change-notes/2022-04-27-intent-verification.md diff --git a/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql b/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql index 3790ce3bb4d..222e8ada5be 100644 --- a/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql +++ b/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql @@ -1,5 +1,5 @@ /** - * @name Improper Verification of Intent by Broadcast Reciever + * @name Improper Verification of Intent by Broadcast Receiver * @description The Android application uses a Broadcast Receiver that receives an Intent but does not properly verify that the Intent came from an authorized source. * @kind problem * @problem.severity warning diff --git a/java/ql/src/change-notes/2022-04-27-intent-verification.md b/java/ql/src/change-notes/2022-04-27-intent-verification.md new file mode 100644 index 00000000000..e0ed5f5ef27 --- /dev/null +++ b/java/ql/src/change-notes/2022-04-27-intent-verification.md @@ -0,0 +1,6 @@ +--- +category: newQuery +--- +* A new query "Improper Verification of Intent by Broadcast Receiver" (`java/improper-intent-verification`) has been added. +This query finds instances of Android `BroadcastReceiver`s that don't verify the action string of received Intents when registered +to receive system intents. \ No newline at end of file From 9d048e78af6cafc735cf3a6ef6c5a4b87a1d1237 Mon Sep 17 00:00:00 2001 From: Joe Farebrother Date: Thu, 28 Apr 2022 12:45:20 +0100 Subject: [PATCH 010/246] Apply suggestions from code review - fix typos/style, make things private Co-authored-by: Tony Torralba --- .../security/ImproperIntentVerificationQuery.qll | 16 ++++++++-------- java/ql/src/Security/CWE/CWE-925/Bad.java | 4 ++-- java/ql/src/Security/CWE/CWE-925/Good.java | 4 ++-- .../CWE/CWE-925/ImproperIntentVerification.qhelp | 10 +++++----- .../2022-04-27-intent-verification.md | 2 +- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/java/ql/lib/semmle/code/java/security/ImproperIntentVerificationQuery.qll b/java/ql/lib/semmle/code/java/security/ImproperIntentVerificationQuery.qll index 9004414664a..82885a65807 100644 --- a/java/ql/lib/semmle/code/java/security/ImproperIntentVerificationQuery.qll +++ b/java/ql/lib/semmle/code/java/security/ImproperIntentVerificationQuery.qll @@ -10,7 +10,7 @@ private class OnReceiveMethod extends Method { .hasQualifiedName("android.content", "BroadcastReceiver", "onReceive") } - /** Gets the paramter of this method that holds the received `Intent`. */ + /** Gets the parameter of this method that holds the received `Intent`. */ Parameter getIntentParameter() { result = this.getParameter(1) } } @@ -30,7 +30,7 @@ private class VerifiedIntentConfig extends DataFlow::Configuration { } } -/** An `onReceive` method that doesn't verify the action of the intent it recieves. */ +/** An `onReceive` method that doesn't verify the action of the intent it receives. */ class UnverifiedOnReceiveMethod extends OnReceiveMethod { UnverifiedOnReceiveMethod() { not any(VerifiedIntentConfig c).hasFlow(DataFlow::parameterNode(this.getIntentParameter()), _) @@ -70,7 +70,7 @@ class SystemActionName extends Top { ) } - /** Gets the name of the system intent that this expression or attriute represents. */ + /** Gets the name of the system intent that this expression or attribute represents. */ string getName() { result = name } override string toString() { @@ -125,8 +125,8 @@ private class RegisterSystemActionConfig extends DataFlow::Configuration { } } -/** Holds if `rrc` registers a reciever `orm` to recieve the system action `sa` that doesn't verifiy intents it recieves. */ -predicate registeredUnverifiedSystemReceiver( +/** Holds if `rrc` registers a receiver `orm` to receive the system action `sa` that doesn't verify the intents it receives. */ +private predicate registeredUnverifiedSystemReceiver( RegisterReceiverCall rrc, UnverifiedOnReceiveMethod orm, SystemActionName sa ) { exists(RegisterSystemActionConfig conf, ConstructorCall cc | @@ -136,8 +136,8 @@ predicate registeredUnverifiedSystemReceiver( ) } -/** Holds if the XML element `rec` declares a reciever `orm` to recieve the system action named `sa` that doesn't verifiy intents it recieves. */ -predicate xmlUnverifiedSystemReceiver( +/** Holds if the XML element `rec` declares a receiver `orm` to receive the system action named `sa` that doesn't verify intents it receives. */ +private predicate xmlUnverifiedSystemReceiver( XMLElement rec, UnverifiedOnReceiveMethod orm, SystemActionName sa ) { exists(XMLElement filter, XMLElement action, Class ormty | @@ -152,7 +152,7 @@ predicate xmlUnverifiedSystemReceiver( ) } -/** Holds if `reg` registers (either explicitly or through XML) a reciever `orm` to recieve the system action named `sa` that doesn't verify intents it recieves. */ +/** Holds if `reg` registers (either explicitly or through XML) a receiver `orm` to receive the system action named `sa` that doesn't verify the intents it receives. */ predicate unverifiedSystemReceiver(Top reg, Method orm, SystemActionName sa) { registeredUnverifiedSystemReceiver(reg, orm, sa) or xmlUnverifiedSystemReceiver(reg, orm, sa) diff --git a/java/ql/src/Security/CWE/CWE-925/Bad.java b/java/ql/src/Security/CWE/CWE-925/Bad.java index a2703d83cf4..52a5d0c29f8 100644 --- a/java/ql/src/Security/CWE/CWE-925/Bad.java +++ b/java/ql/src/Security/CWE/CWE-925/Bad.java @@ -1,8 +1,8 @@ -... +// ... IntentFilter filter = new IntentFilter(Intent.ACTION_SHUTDOWN); BroadcastReceiver sReceiver = new ShutDownReceiver(); context.registerReceiver(sReceiver, filter); -... +// ... public class ShutdownReceiver extends BroadcastReceiver { @Override diff --git a/java/ql/src/Security/CWE/CWE-925/Good.java b/java/ql/src/Security/CWE/CWE-925/Good.java index 3830b8c4bc3..6f3a718487a 100644 --- a/java/ql/src/Security/CWE/CWE-925/Good.java +++ b/java/ql/src/Security/CWE/CWE-925/Good.java @@ -1,8 +1,8 @@ -... +// ... IntentFilter filter = new IntentFilter(Intent.ACTION_SHUTDOWN); BroadcastReceiver sReceiver = new ShutDownReceiver(); context.registerReceiver(sReceiver, filter); -... +// ... public class ShutdownReceiver extends BroadcastReceiver { @Override diff --git a/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.qhelp b/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.qhelp index d4218c228fb..2fd06e9817b 100644 --- a/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.qhelp +++ b/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.qhelp @@ -6,18 +6,18 @@

-When an android application uses a BroadcastReciever to receive Intents, -it is also able to receive explicit Intents that are sent drctly to it, egardless of its filter. +When an android application uses a BroadcastReciever to receive intents, +it is also able to receive explicit intents that are sent directly to it, regardless of its filter. Certain intent actions are only able to be sent by the operating system, not third-party applications. -However, a BroadcastReceiver that is registered to recieve system intents is still able to recieve +However, a BroadcastReceiver that is registered to receive system intents is still able to receive other intents from a third-party application, so it should check that the intent received has the expected action. -Otherwise, a third-party application could impersonate the system this way and cause unintended behaviour, such as a denial of service. +Otherwise, a third-party application could impersonate the system this way and cause unintended behavior, such as a denial of service.

-

In the following code, the ShutdownReceiver initiates a shutdown procedure upon receiving an Intent, +

In the following code, the ShutdownReceiver initiates a shutdown procedure upon receiving an intent, without checking that the received action is indeed ACTION_SHUTDOWN. This allows third-party applications to send explicit intents to this receiver to cause a denial of service.

diff --git a/java/ql/src/change-notes/2022-04-27-intent-verification.md b/java/ql/src/change-notes/2022-04-27-intent-verification.md index e0ed5f5ef27..143b0bcd39b 100644 --- a/java/ql/src/change-notes/2022-04-27-intent-verification.md +++ b/java/ql/src/change-notes/2022-04-27-intent-verification.md @@ -2,5 +2,5 @@ category: newQuery --- * A new query "Improper Verification of Intent by Broadcast Receiver" (`java/improper-intent-verification`) has been added. -This query finds instances of Android `BroadcastReceiver`s that don't verify the action string of received Intents when registered +This query finds instances of Android `BroadcastReceiver`s that don't verify the action string of received intents when registered to receive system intents. \ No newline at end of file From 320c671b73dcb6a30428e6292a925fa586464da8 Mon Sep 17 00:00:00 2001 From: Joe Farebrother Date: Thu, 28 Apr 2022 14:14:02 +0100 Subject: [PATCH 011/246] Adress reveiw comments - make use of existing ql libraries --- .../ImproperIntentVerificationQuery.qll | 31 +++++++------------ .../CWE/CWE-925/ImproperIntentVerification.ql | 2 +- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/java/ql/lib/semmle/code/java/security/ImproperIntentVerificationQuery.qll b/java/ql/lib/semmle/code/java/security/ImproperIntentVerificationQuery.qll index 82885a65807..f161c67cfbb 100644 --- a/java/ql/lib/semmle/code/java/security/ImproperIntentVerificationQuery.qll +++ b/java/ql/lib/semmle/code/java/security/ImproperIntentVerificationQuery.qll @@ -2,13 +2,12 @@ import java import semmle.code.java.dataflow.DataFlow +import semmle.code.xml.AndroidManifest +import semmle.code.java.frameworks.android.Intent /** An `onReceive` method of a `BroadcastReceiver` */ private class OnReceiveMethod extends Method { - OnReceiveMethod() { - this.getASourceOverriddenMethod*() - .hasQualifiedName("android.content", "BroadcastReceiver", "onReceive") - } + OnReceiveMethod() { this.getASourceOverriddenMethod*() instanceof AndroidReceiveIntentMethod } /** Gets the parameter of this method that holds the received `Intent`. */ Parameter getIntentParameter() { result = this.getParameter(1) } @@ -31,7 +30,7 @@ private class VerifiedIntentConfig extends DataFlow::Configuration { } /** An `onReceive` method that doesn't verify the action of the intent it receives. */ -class UnverifiedOnReceiveMethod extends OnReceiveMethod { +private class UnverifiedOnReceiveMethod extends OnReceiveMethod { UnverifiedOnReceiveMethod() { not any(VerifiedIntentConfig c).hasFlow(DataFlow::parameterNode(this.getIntentParameter()), _) } @@ -62,21 +61,18 @@ class SystemActionName extends Top { SystemActionName() { name = getASystemActionName() and ( - this.(StringLiteral).getValue() = "android.intent.action." + name + this.(CompileTimeConstantExpr).getStringValue() = "android.intent.action." + name or this.(FieldRead).getField().hasQualifiedName("android.content", "Intent", "ACTION_" + name) or - this.(XMLAttribute).getValue() = "android.intent.action." + name + this.(AndroidActionXmlElement).getActionName() = "android.intent.action." + name ) } /** Gets the name of the system intent that this expression or attribute represents. */ string getName() { result = name } - override string toString() { - result = - [this.(StringLiteral).toString(), this.(FieldRead).toString(), this.(XMLAttribute).toString()] - } + override string toString() { result = [this.(Expr).toString(), this.(XMLAttribute).toString()] } } /** A call to `Context.registerReceiver` */ @@ -138,17 +134,12 @@ private predicate registeredUnverifiedSystemReceiver( /** Holds if the XML element `rec` declares a receiver `orm` to receive the system action named `sa` that doesn't verify intents it receives. */ private predicate xmlUnverifiedSystemReceiver( - XMLElement rec, UnverifiedOnReceiveMethod orm, SystemActionName sa + AndroidReceiverXmlElement rec, UnverifiedOnReceiveMethod orm, SystemActionName sa ) { - exists(XMLElement filter, XMLElement action, Class ormty | - rec.hasName("receiver") and - filter.hasName("intent-filter") and - action.hasName("action") and - filter = rec.getAChild() and - action = filter.getAChild() and + exists(Class ormty | ormty = orm.getDeclaringType() and - rec.getAttribute("name").getValue() = ["." + ormty.getName(), ormty.getQualifiedName()] and - action.getAttribute("name") = sa + rec.getComponentName() = ["." + ormty.getName(), ormty.getQualifiedName()] and + rec.getAnIntentFilterElement().getAnActionElement() = sa ) } diff --git a/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql b/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql index 222e8ada5be..87fcf3c659b 100644 --- a/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql +++ b/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql @@ -15,5 +15,5 @@ import semmle.code.java.security.ImproperIntentVerificationQuery from Top reg, Method orm, SystemActionName sa where unverifiedSystemReceiver(reg, orm, sa) -select orm, "This reciever doesn't verify intents it recieves, and is registered $@ to recieve $@.", +select orm, "This reciever doesn't verify intents it receives, and is registered $@ to receive $@.", reg, "here", sa, "the system action " + sa.getName() From c71586e1f8baa3e439fdf20acc763da701e9c7cc Mon Sep 17 00:00:00 2001 From: Joe Farebrother Date: Tue, 31 May 2022 15:26:48 +0100 Subject: [PATCH 012/246] Remove checks for dynamically registered recievers --- .../ImproperIntentVerificationQuery.qll | 79 +------------------ .../Security/CWE/CWE-925/AndroidManifest.xml | 9 +++ java/ql/src/Security/CWE/CWE-925/Bad.java | 6 -- java/ql/src/Security/CWE/CWE-925/Good.java | 6 -- .../CWE-925/ImproperIntentVerification.qhelp | 1 + .../CWE/CWE-925/ImproperIntentVerification.ql | 2 +- .../ImproperIntentVerificationTest.java | 31 -------- 7 files changed, 15 insertions(+), 119 deletions(-) create mode 100644 java/ql/src/Security/CWE/CWE-925/AndroidManifest.xml delete mode 100644 java/ql/test/query-tests/security/CWE-925/ImproperIntentVerificationTest.java diff --git a/java/ql/lib/semmle/code/java/security/ImproperIntentVerificationQuery.qll b/java/ql/lib/semmle/code/java/security/ImproperIntentVerificationQuery.qll index f161c67cfbb..00a6dae69e9 100644 --- a/java/ql/lib/semmle/code/java/security/ImproperIntentVerificationQuery.qll +++ b/java/ql/lib/semmle/code/java/security/ImproperIntentVerificationQuery.qll @@ -55,85 +55,20 @@ string getASystemActionName() { } /** An expression or XML attribute that contains the name of a system intent action. */ -class SystemActionName extends Top { +class SystemActionName extends AndroidActionXmlElement { string name; SystemActionName() { name = getASystemActionName() and - ( - this.(CompileTimeConstantExpr).getStringValue() = "android.intent.action." + name - or - this.(FieldRead).getField().hasQualifiedName("android.content", "Intent", "ACTION_" + name) - or - this.(AndroidActionXmlElement).getActionName() = "android.intent.action." + name - ) + this.getActionName() = "android.intent.action." + name } /** Gets the name of the system intent that this expression or attribute represents. */ - string getName() { result = name } - - override string toString() { result = [this.(Expr).toString(), this.(XMLAttribute).toString()] } -} - -/** A call to `Context.registerReceiver` */ -private class RegisterReceiverCall extends MethodAccess { - RegisterReceiverCall() { - this.getMethod() - .getASourceOverriddenMethod*() - .hasQualifiedName("android.content", "Context", "registerReceiver") - } - - /** Gets the `BroadcastReceiver` argument to this call. */ - Expr getReceiverArgument() { result = this.getArgument(0) } - - /** Gets the `IntentFilter` argument to this call. */ - Expr getFilterArgument() { result = this.getArgument(1) } -} - -/** A configuration to detect uses of `registerReceiver` with system intent actions. */ -private class RegisterSystemActionConfig extends DataFlow::Configuration { - RegisterSystemActionConfig() { this = "RegisterSystemActionConfig" } - - override predicate isSource(DataFlow::Node node) { node.asExpr() instanceof SystemActionName } - - override predicate isSink(DataFlow::Node node) { - exists(RegisterReceiverCall ma | node.asExpr() = ma.getFilterArgument()) - } - - override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { - exists(ConstructorCall cc | - cc.getConstructedType().hasQualifiedName("android.content", "IntentFilter") and - node1.asExpr() = cc.getArgument(0) and - node2.asExpr() = cc - ) - or - exists(MethodAccess ma | - ma.getMethod().hasQualifiedName("android.content", "IntentFilter", "create") and - node1.asExpr() = ma.getArgument(0) and - node2.asExpr() = ma - ) - or - exists(MethodAccess ma | - ma.getMethod().hasQualifiedName("android.content", "IntentFilter", "addAction") and - node1.asExpr() = ma.getArgument(0) and - node2.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr() = ma.getQualifier() - ) - } -} - -/** Holds if `rrc` registers a receiver `orm` to receive the system action `sa` that doesn't verify the intents it receives. */ -private predicate registeredUnverifiedSystemReceiver( - RegisterReceiverCall rrc, UnverifiedOnReceiveMethod orm, SystemActionName sa -) { - exists(RegisterSystemActionConfig conf, ConstructorCall cc | - conf.hasFlow(DataFlow::exprNode(sa), DataFlow::exprNode(rrc.getFilterArgument())) and - cc.getConstructedType() = orm.getDeclaringType() and - DataFlow::localExprFlow(cc, rrc.getReceiverArgument()) - ) + string getSystemActionName() { result = name } } /** Holds if the XML element `rec` declares a receiver `orm` to receive the system action named `sa` that doesn't verify intents it receives. */ -private predicate xmlUnverifiedSystemReceiver( +predicate unverifiedSystemReceiver( AndroidReceiverXmlElement rec, UnverifiedOnReceiveMethod orm, SystemActionName sa ) { exists(Class ormty | @@ -142,9 +77,3 @@ private predicate xmlUnverifiedSystemReceiver( rec.getAnIntentFilterElement().getAnActionElement() = sa ) } - -/** Holds if `reg` registers (either explicitly or through XML) a receiver `orm` to receive the system action named `sa` that doesn't verify the intents it receives. */ -predicate unverifiedSystemReceiver(Top reg, Method orm, SystemActionName sa) { - registeredUnverifiedSystemReceiver(reg, orm, sa) or - xmlUnverifiedSystemReceiver(reg, orm, sa) -} diff --git a/java/ql/src/Security/CWE/CWE-925/AndroidManifest.xml b/java/ql/src/Security/CWE/CWE-925/AndroidManifest.xml new file mode 100644 index 00000000000..f9e11a1ee81 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-925/AndroidManifest.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-925/Bad.java b/java/ql/src/Security/CWE/CWE-925/Bad.java index 52a5d0c29f8..376805f824e 100644 --- a/java/ql/src/Security/CWE/CWE-925/Bad.java +++ b/java/ql/src/Security/CWE/CWE-925/Bad.java @@ -1,9 +1,3 @@ -// ... -IntentFilter filter = new IntentFilter(Intent.ACTION_SHUTDOWN); -BroadcastReceiver sReceiver = new ShutDownReceiver(); -context.registerReceiver(sReceiver, filter); -// ... - public class ShutdownReceiver extends BroadcastReceiver { @Override public void onReceive(final Context context, final Intent intent) { diff --git a/java/ql/src/Security/CWE/CWE-925/Good.java b/java/ql/src/Security/CWE/CWE-925/Good.java index 6f3a718487a..b6ad1c43193 100644 --- a/java/ql/src/Security/CWE/CWE-925/Good.java +++ b/java/ql/src/Security/CWE/CWE-925/Good.java @@ -1,9 +1,3 @@ -// ... -IntentFilter filter = new IntentFilter(Intent.ACTION_SHUTDOWN); -BroadcastReceiver sReceiver = new ShutDownReceiver(); -context.registerReceiver(sReceiver, filter); -// ... - public class ShutdownReceiver extends BroadcastReceiver { @Override public void onReceive(final Context context, final Intent intent) { diff --git a/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.qhelp b/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.qhelp index 2fd06e9817b..edc9b6269f9 100644 --- a/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.qhelp +++ b/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.qhelp @@ -21,6 +21,7 @@ Otherwise, a third-party application could impersonate the system this way and c without checking that the received action is indeed ACTION_SHUTDOWN. This allows third-party applications to send explicit intents to this receiver to cause a denial of service.

+
diff --git a/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql b/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql index 87fcf3c659b..fb49d00cafa 100644 --- a/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql +++ b/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql @@ -13,7 +13,7 @@ import java import semmle.code.java.security.ImproperIntentVerificationQuery -from Top reg, Method orm, SystemActionName sa +from AndroidReceiverXmlElement reg, Method orm, SystemActionName sa where unverifiedSystemReceiver(reg, orm, sa) select orm, "This reciever doesn't verify intents it receives, and is registered $@ to receive $@.", reg, "here", sa, "the system action " + sa.getName() diff --git a/java/ql/test/query-tests/security/CWE-925/ImproperIntentVerificationTest.java b/java/ql/test/query-tests/security/CWE-925/ImproperIntentVerificationTest.java deleted file mode 100644 index 736410eb9f0..00000000000 --- a/java/ql/test/query-tests/security/CWE-925/ImproperIntentVerificationTest.java +++ /dev/null @@ -1,31 +0,0 @@ -package test; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.Context; -import android.content.BroadcastReceiver; - -class ImproperIntentVerificationTest { - static void doStuff(Intent intent) {} - - class ShutdownBroadcastReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context ctx, Intent intent) { // $hasResult - doStuff(intent); - } - } - - class ShutdownBroadcastReceiverSafe extends BroadcastReceiver { - @Override - public void onReceive(Context ctx, Intent intent) { - if (!intent.getAction().equals(Intent.ACTION_SHUTDOWN)) { - return; - } - doStuff(intent); - } - } - - void test(Context c) { - c.registerReceiver(new ShutdownBroadcastReceiver(), new IntentFilter(Intent.ACTION_SHUTDOWN)); - c.registerReceiver(new ShutdownBroadcastReceiverSafe(), new IntentFilter(Intent.ACTION_SHUTDOWN)); - } -} \ No newline at end of file From a6736a99e4059df26f0be42fd5b700009c4058f8 Mon Sep 17 00:00:00 2001 From: Joe Farebrother Date: Tue, 14 Jun 2022 14:55:37 +0100 Subject: [PATCH 013/246] Apply doc review suggestions - fix typos and capitilisation; reword description. --- .../Security/CWE/CWE-925/ImproperIntentVerification.qhelp | 6 +++--- .../src/Security/CWE/CWE-925/ImproperIntentVerification.ql | 4 ++-- java/ql/src/change-notes/2022-04-27-intent-verification.md | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.qhelp b/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.qhelp index edc9b6269f9..e489e411379 100644 --- a/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.qhelp +++ b/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.qhelp @@ -6,13 +6,13 @@

-When an android application uses a BroadcastReciever to receive intents, +When an Android application uses a BroadcastReceiver to receive intents, it is also able to receive explicit intents that are sent directly to it, regardless of its filter. Certain intent actions are only able to be sent by the operating system, not third-party applications. However, a BroadcastReceiver that is registered to receive system intents is still able to receive -other intents from a third-party application, so it should check that the intent received has the expected action. -Otherwise, a third-party application could impersonate the system this way and cause unintended behavior, such as a denial of service. +intents from a third-party application, so it should check that the intent received has the expected action. +Otherwise, a third-party application could impersonate the system this way to cause unintended behavior, such as a denial of service.

diff --git a/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql b/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql index fb49d00cafa..1314f91a2bd 100644 --- a/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql +++ b/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql @@ -1,6 +1,6 @@ /** - * @name Improper Verification of Intent by Broadcast Receiver - * @description The Android application uses a Broadcast Receiver that receives an Intent but does not properly verify that the Intent came from an authorized source. + * @name Improper verification of intent by broadcast receiver + * @description A broadcast reciever that does not verify intents it recieves may be susceptible to unintended behaviour by third party applications sending it explicit intents. * @kind problem * @problem.severity warning * @security-severity 8.2 diff --git a/java/ql/src/change-notes/2022-04-27-intent-verification.md b/java/ql/src/change-notes/2022-04-27-intent-verification.md index 143b0bcd39b..e5e0e287753 100644 --- a/java/ql/src/change-notes/2022-04-27-intent-verification.md +++ b/java/ql/src/change-notes/2022-04-27-intent-verification.md @@ -1,6 +1,6 @@ --- category: newQuery --- -* A new query "Improper Verification of Intent by Broadcast Receiver" (`java/improper-intent-verification`) has been added. +* A new query "Improper verification of intent by broadcast receiver" (`java/improper-intent-verification`) has been added. This query finds instances of Android `BroadcastReceiver`s that don't verify the action string of received intents when registered to receive system intents. \ No newline at end of file From f46dd8cc85d5202527b3135b4437cbb7446f7319 Mon Sep 17 00:00:00 2001 From: Joe Farebrother Date: Tue, 14 Jun 2022 15:34:08 +0100 Subject: [PATCH 014/246] Fix misspellings --- java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql b/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql index 1314f91a2bd..51c54e288ac 100644 --- a/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql +++ b/java/ql/src/Security/CWE/CWE-925/ImproperIntentVerification.ql @@ -1,6 +1,6 @@ /** * @name Improper verification of intent by broadcast receiver - * @description A broadcast reciever that does not verify intents it recieves may be susceptible to unintended behaviour by third party applications sending it explicit intents. + * @description A broadcast receiver that does not verify intents it receives may be susceptible to unintended behavior by third party applications sending it explicit intents. * @kind problem * @problem.severity warning * @security-severity 8.2 From f4ce382b7da04c17a3215d95b8a2cc6d3403c42e Mon Sep 17 00:00:00 2001 From: Rasmus Lerchedahl Petersen Date: Wed, 15 Jun 2022 12:40:14 +0200 Subject: [PATCH 015/246] python: update test expectations --- .../Security/CWE-022-TarSlip/TarSlip.expected | 61 +++++++++++-------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/python/ql/test/query-tests/Security/CWE-022-TarSlip/TarSlip.expected b/python/ql/test/query-tests/Security/CWE-022-TarSlip/TarSlip.expected index edcdaf88e48..2ddfe7143d0 100644 --- a/python/ql/test/query-tests/Security/CWE-022-TarSlip/TarSlip.expected +++ b/python/ql/test/query-tests/Security/CWE-022-TarSlip/TarSlip.expected @@ -1,29 +1,36 @@ edges -| tarslip.py:12:7:12:39 | tarfile.open | tarslip.py:13:1:13:3 | tarfile.open | -| tarslip.py:12:7:12:39 | tarfile.open | tarslip.py:13:1:13:3 | tarfile.open | -| tarslip.py:16:7:16:39 | tarfile.open | tarslip.py:17:14:17:16 | tarfile.open | -| tarslip.py:16:7:16:39 | tarfile.open | tarslip.py:17:14:17:16 | tarfile.open | -| tarslip.py:17:1:17:17 | tarfile.entry | tarslip.py:18:17:18:21 | tarfile.entry | -| tarslip.py:17:1:17:17 | tarfile.entry | tarslip.py:18:17:18:21 | tarfile.entry | -| tarslip.py:17:14:17:16 | tarfile.open | tarslip.py:17:1:17:17 | tarfile.entry | -| tarslip.py:17:14:17:16 | tarfile.open | tarslip.py:17:1:17:17 | tarfile.entry | -| tarslip.py:33:7:33:39 | tarfile.open | tarslip.py:34:14:34:16 | tarfile.open | -| tarslip.py:33:7:33:39 | tarfile.open | tarslip.py:34:14:34:16 | tarfile.open | -| tarslip.py:34:1:34:17 | tarfile.entry | tarslip.py:37:17:37:21 | tarfile.entry | -| tarslip.py:34:1:34:17 | tarfile.entry | tarslip.py:37:17:37:21 | tarfile.entry | -| tarslip.py:34:14:34:16 | tarfile.open | tarslip.py:34:1:34:17 | tarfile.entry | -| tarslip.py:34:14:34:16 | tarfile.open | tarslip.py:34:1:34:17 | tarfile.entry | -| tarslip.py:40:7:40:39 | tarfile.open | tarslip.py:41:24:41:26 | tarfile.open | -| tarslip.py:40:7:40:39 | tarfile.open | tarslip.py:41:24:41:26 | tarfile.open | -| tarslip.py:56:7:56:39 | tarfile.open | tarslip.py:57:14:57:16 | tarfile.open | -| tarslip.py:56:7:56:39 | tarfile.open | tarslip.py:57:14:57:16 | tarfile.open | -| tarslip.py:57:1:57:17 | tarfile.entry | tarslip.py:59:21:59:25 | tarfile.entry | -| tarslip.py:57:1:57:17 | tarfile.entry | tarslip.py:59:21:59:25 | tarfile.entry | -| tarslip.py:57:14:57:16 | tarfile.open | tarslip.py:57:1:57:17 | tarfile.entry | -| tarslip.py:57:14:57:16 | tarfile.open | tarslip.py:57:1:57:17 | tarfile.entry | +| tarslip.py:12:7:12:39 | ControlFlowNode for Attribute() | tarslip.py:13:1:13:3 | ControlFlowNode for tar | +| tarslip.py:16:7:16:39 | ControlFlowNode for Attribute() | tarslip.py:17:5:17:9 | GSSA Variable entry | +| tarslip.py:17:5:17:9 | GSSA Variable entry | tarslip.py:18:17:18:21 | ControlFlowNode for entry | +| tarslip.py:33:7:33:39 | ControlFlowNode for Attribute() | tarslip.py:34:5:34:9 | GSSA Variable entry | +| tarslip.py:34:5:34:9 | GSSA Variable entry | tarslip.py:37:17:37:21 | ControlFlowNode for entry | +| tarslip.py:40:7:40:39 | ControlFlowNode for Attribute() | tarslip.py:41:24:41:26 | ControlFlowNode for tar | +| tarslip.py:56:7:56:39 | ControlFlowNode for Attribute() | tarslip.py:57:5:57:9 | GSSA Variable entry | +| tarslip.py:57:5:57:9 | GSSA Variable entry | tarslip.py:59:21:59:25 | ControlFlowNode for entry | +| tarslip.py:79:7:79:39 | ControlFlowNode for Attribute() | tarslip.py:80:5:80:9 | GSSA Variable entry | +| tarslip.py:80:5:80:9 | GSSA Variable entry | tarslip.py:82:21:82:25 | ControlFlowNode for entry | +nodes +| tarslip.py:12:7:12:39 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| tarslip.py:13:1:13:3 | ControlFlowNode for tar | semmle.label | ControlFlowNode for tar | +| tarslip.py:16:7:16:39 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| tarslip.py:17:5:17:9 | GSSA Variable entry | semmle.label | GSSA Variable entry | +| tarslip.py:18:17:18:21 | ControlFlowNode for entry | semmle.label | ControlFlowNode for entry | +| tarslip.py:33:7:33:39 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| tarslip.py:34:5:34:9 | GSSA Variable entry | semmle.label | GSSA Variable entry | +| tarslip.py:37:17:37:21 | ControlFlowNode for entry | semmle.label | ControlFlowNode for entry | +| tarslip.py:40:7:40:39 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| tarslip.py:41:24:41:26 | ControlFlowNode for tar | semmle.label | ControlFlowNode for tar | +| tarslip.py:56:7:56:39 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| tarslip.py:57:5:57:9 | GSSA Variable entry | semmle.label | GSSA Variable entry | +| tarslip.py:59:21:59:25 | ControlFlowNode for entry | semmle.label | ControlFlowNode for entry | +| tarslip.py:79:7:79:39 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| tarslip.py:80:5:80:9 | GSSA Variable entry | semmle.label | GSSA Variable entry | +| tarslip.py:82:21:82:25 | ControlFlowNode for entry | semmle.label | ControlFlowNode for entry | +subpaths #select -| tarslip.py:13:1:13:3 | tar | tarslip.py:12:7:12:39 | tarfile.open | tarslip.py:13:1:13:3 | tarfile.open | Extraction of tarfile from $@ | tarslip.py:12:7:12:39 | Attribute() | a potentially untrusted source | -| tarslip.py:18:17:18:21 | entry | tarslip.py:16:7:16:39 | tarfile.open | tarslip.py:18:17:18:21 | tarfile.entry | Extraction of tarfile from $@ | tarslip.py:16:7:16:39 | Attribute() | a potentially untrusted source | -| tarslip.py:37:17:37:21 | entry | tarslip.py:33:7:33:39 | tarfile.open | tarslip.py:37:17:37:21 | tarfile.entry | Extraction of tarfile from $@ | tarslip.py:33:7:33:39 | Attribute() | a potentially untrusted source | -| tarslip.py:41:24:41:26 | tar | tarslip.py:40:7:40:39 | tarfile.open | tarslip.py:41:24:41:26 | tarfile.open | Extraction of tarfile from $@ | tarslip.py:40:7:40:39 | Attribute() | a potentially untrusted source | -| tarslip.py:59:21:59:25 | entry | tarslip.py:56:7:56:39 | tarfile.open | tarslip.py:59:21:59:25 | tarfile.entry | Extraction of tarfile from $@ | tarslip.py:56:7:56:39 | Attribute() | a potentially untrusted source | +| tarslip.py:13:1:13:3 | ControlFlowNode for tar | tarslip.py:12:7:12:39 | ControlFlowNode for Attribute() | tarslip.py:13:1:13:3 | ControlFlowNode for tar | Extraction of tarfile from $@ | tarslip.py:12:7:12:39 | ControlFlowNode for Attribute() | a potentially untrusted source | +| tarslip.py:18:17:18:21 | ControlFlowNode for entry | tarslip.py:16:7:16:39 | ControlFlowNode for Attribute() | tarslip.py:18:17:18:21 | ControlFlowNode for entry | Extraction of tarfile from $@ | tarslip.py:16:7:16:39 | ControlFlowNode for Attribute() | a potentially untrusted source | +| tarslip.py:37:17:37:21 | ControlFlowNode for entry | tarslip.py:33:7:33:39 | ControlFlowNode for Attribute() | tarslip.py:37:17:37:21 | ControlFlowNode for entry | Extraction of tarfile from $@ | tarslip.py:33:7:33:39 | ControlFlowNode for Attribute() | a potentially untrusted source | +| tarslip.py:41:24:41:26 | ControlFlowNode for tar | tarslip.py:40:7:40:39 | ControlFlowNode for Attribute() | tarslip.py:41:24:41:26 | ControlFlowNode for tar | Extraction of tarfile from $@ | tarslip.py:40:7:40:39 | ControlFlowNode for Attribute() | a potentially untrusted source | +| tarslip.py:59:21:59:25 | ControlFlowNode for entry | tarslip.py:56:7:56:39 | ControlFlowNode for Attribute() | tarslip.py:59:21:59:25 | ControlFlowNode for entry | Extraction of tarfile from $@ | tarslip.py:56:7:56:39 | ControlFlowNode for Attribute() | a potentially untrusted source | +| tarslip.py:82:21:82:25 | ControlFlowNode for entry | tarslip.py:79:7:79:39 | ControlFlowNode for Attribute() | tarslip.py:82:21:82:25 | ControlFlowNode for entry | Extraction of tarfile from $@ | tarslip.py:79:7:79:39 | ControlFlowNode for Attribute() | a potentially untrusted source | From 40b61fa85fef5e758cba013c68b756177b0af98c Mon Sep 17 00:00:00 2001 From: Rasmus Lerchedahl Petersen Date: Wed, 15 Jun 2022 14:07:35 +0200 Subject: [PATCH 016/246] python: fix qldocs and clean-up dead code --- .../dataflow/TarSlipCustomizations.qll | 24 ++++++------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/python/ql/lib/semmle/python/security/dataflow/TarSlipCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/TarSlipCustomizations.qll index 7393b129f37..21d7cc03e74 100644 --- a/python/ql/lib/semmle/python/security/dataflow/TarSlipCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/TarSlipCustomizations.qll @@ -90,7 +90,7 @@ module TarSlip { } } - /* Members argument to extract method */ + /** The `members` argument `extractall` is considered a sink. */ class ExtractMembersSink extends Sink { ExtractMembersSink() { exists(DataFlow::CallCfgNode call | @@ -105,6 +105,10 @@ module TarSlip { } } + /** + * For a "check-like function name" (matching `"%path"`), `checkPath`, + * and a call `checkPath(info.name)`, the variable `info` is considered checked. + */ class TarFileInfoSanitizer extends SanitizerGuard { ControlFlowNode tarInfo; @@ -117,9 +121,9 @@ module TarSlip { attr.getObject() = tarInfo | // Assume that any test with "path" in it is a sanitizer - call.getAChild*().(AttrNode).getName().matches("%path") + call.getAChild*().(AttrNode).getName().toLowerCase().matches("%path") or - call.getAChild*().(NameNode).getId().matches("%path") + call.getAChild*().(NameNode).getId().toLowerCase().matches("%path") ) } @@ -127,19 +131,5 @@ module TarSlip { checked = tarInfo and branch in [true, false] } - - DataFlow::ExprNode shouldGuard() { - tarInfo.dominates(result.asCfgNode()) and - // exists(EssaDefinition def | - // def.getAUse() = tarInfo and - // def.getAUse() = result.asCfgNode() - // ) and - exists(SsaSourceVariable v | - v.getAUse() = tarInfo and - v.getAUse() = result.asCfgNode() - ) - } } - - DataFlow::ExprNode getAGuardedNode(TarFileInfoSanitizer tfis) { result = tfis.getAGuardedNode() } } From 0608d4d2f991180cff54881d08296938eee9f907 Mon Sep 17 00:00:00 2001 From: Rasmus Lerchedahl Petersen Date: Wed, 15 Jun 2022 14:18:29 +0200 Subject: [PATCH 017/246] python: fix alerts Also, remove the `toLowerCase` again, as I do not know what effect it will have. --- .../security/dataflow/TarSlipCustomizations.qll | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/python/ql/lib/semmle/python/security/dataflow/TarSlipCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/TarSlipCustomizations.qll index 21d7cc03e74..d767f90c5c6 100644 --- a/python/ql/lib/semmle/python/security/dataflow/TarSlipCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/TarSlipCustomizations.qll @@ -51,6 +51,8 @@ module TarSlip { } /** + * A sanitizer based on file name. This beacuse we extract the standard library. + * * For efficiency we don't want to track the flow of taint * around the tarfile module. */ @@ -59,6 +61,8 @@ module TarSlip { } /** + * A sink capturing method calls to `extractall`. + * * For a call to `file.extractall` without arguments, `file` is considered a sink. */ class ExtractAllSink extends Sink { @@ -106,7 +110,9 @@ module TarSlip { } /** - * For a "check-like function name" (matching `"%path"`), `checkPath`, + * A sanitizer guard heuristic. + * + * For a "check-like function-name" (matching `"%path"`), `checkPath`, * and a call `checkPath(info.name)`, the variable `info` is considered checked. */ class TarFileInfoSanitizer extends SanitizerGuard { @@ -121,9 +127,9 @@ module TarSlip { attr.getObject() = tarInfo | // Assume that any test with "path" in it is a sanitizer - call.getAChild*().(AttrNode).getName().toLowerCase().matches("%path") + call.getAChild*().(AttrNode).getName().matches("%path") or - call.getAChild*().(NameNode).getId().toLowerCase().matches("%path") + call.getAChild*().(NameNode).getId().matches("%path") ) } From 4ecd595b73259545a47427c61f18670fd34f86e7 Mon Sep 17 00:00:00 2001 From: Harry Maclean Date: Thu, 16 Jun 2022 13:38:50 +1200 Subject: [PATCH 018/246] Remove duplicate import --- ruby/ql/lib/codeql/ruby/frameworks/ActiveSupport.qll | 1 - 1 file changed, 1 deletion(-) diff --git a/ruby/ql/lib/codeql/ruby/frameworks/ActiveSupport.qll b/ruby/ql/lib/codeql/ruby/frameworks/ActiveSupport.qll index 09aa423dfa5..11b2019e9cf 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/ActiveSupport.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/ActiveSupport.qll @@ -7,7 +7,6 @@ private import ruby private import codeql.ruby.Concepts private import codeql.ruby.DataFlow private import codeql.ruby.dataflow.FlowSummary -private import codeql.ruby.Concepts private import codeql.ruby.ApiGraphs private import codeql.ruby.frameworks.stdlib.Logger::Logger as StdlibLogger From 0ce14fc4e5c5e0c5ce74d4e5dc28e9c42f7cff6a Mon Sep 17 00:00:00 2001 From: Harry Maclean Date: Thu, 16 Jun 2022 13:40:02 +1200 Subject: [PATCH 019/246] Ruby: Recognise ActionCable logger class --- ruby/ql/lib/codeql/ruby/Frameworks.qll | 1 + .../codeql/ruby/frameworks/ActionCable.qll | 29 +++++++++++++++++++ .../action_cable/ActionCable.expected | 1 + .../frameworks/action_cable/ActionCable.ql | 4 +++ .../frameworks/action_cable/action_cable.rb | 1 + 5 files changed, 36 insertions(+) create mode 100644 ruby/ql/lib/codeql/ruby/frameworks/ActionCable.qll create mode 100644 ruby/ql/test/library-tests/frameworks/action_cable/ActionCable.expected create mode 100644 ruby/ql/test/library-tests/frameworks/action_cable/ActionCable.ql create mode 100644 ruby/ql/test/library-tests/frameworks/action_cable/action_cable.rb diff --git a/ruby/ql/lib/codeql/ruby/Frameworks.qll b/ruby/ql/lib/codeql/ruby/Frameworks.qll index 7301b973f78..4f46b3a2b13 100644 --- a/ruby/ql/lib/codeql/ruby/Frameworks.qll +++ b/ruby/ql/lib/codeql/ruby/Frameworks.qll @@ -3,6 +3,7 @@ */ private import codeql.ruby.frameworks.Core +private import codeql.ruby.frameworks.ActionCable private import codeql.ruby.frameworks.ActionController private import codeql.ruby.frameworks.ActiveRecord private import codeql.ruby.frameworks.ActiveStorage diff --git a/ruby/ql/lib/codeql/ruby/frameworks/ActionCable.qll b/ruby/ql/lib/codeql/ruby/frameworks/ActionCable.qll new file mode 100644 index 00000000000..06327c4d9fa --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/frameworks/ActionCable.qll @@ -0,0 +1,29 @@ +/** + * Modeling for `ActionCable`, which is a websocket gem that ships with Rails. + * https://rubygems.org/gems/actioncable + */ + +private import ruby +private import codeql.ruby.Concepts +private import codeql.ruby.ApiGraphs +private import codeql.ruby.frameworks.stdlib.Logger::Logger as StdlibLogger + +/** + * Modeling for `ActionCable`. + */ +module ActionCable { + /** + * `ActionCable::Connection::TaggedLoggerProxy` + */ + module Logger { + private class ActionCableLoggerInstantiation extends StdlibLogger::LoggerInstantiation { + ActionCableLoggerInstantiation() { + this = + API::getTopLevelMember("ActionCable") + .getMember("Connection") + .getMember("TaggedLoggerProxy") + .getAnInstantiation() + } + } + } +} diff --git a/ruby/ql/test/library-tests/frameworks/action_cable/ActionCable.expected b/ruby/ql/test/library-tests/frameworks/action_cable/ActionCable.expected new file mode 100644 index 00000000000..94e553900b1 --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/action_cable/ActionCable.expected @@ -0,0 +1 @@ +| action_cable.rb:1:1:1:54 | call to new | diff --git a/ruby/ql/test/library-tests/frameworks/action_cable/ActionCable.ql b/ruby/ql/test/library-tests/frameworks/action_cable/ActionCable.ql new file mode 100644 index 00000000000..fd2359607f7 --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/action_cable/ActionCable.ql @@ -0,0 +1,4 @@ +import codeql.ruby.frameworks.ActionCable +import codeql.ruby.frameworks.stdlib.Logger + +query predicate loggerInstantiations(Logger::LoggerInstantiation l) { any() } diff --git a/ruby/ql/test/library-tests/frameworks/action_cable/action_cable.rb b/ruby/ql/test/library-tests/frameworks/action_cable/action_cable.rb new file mode 100644 index 00000000000..3988ec4c875 --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/action_cable/action_cable.rb @@ -0,0 +1 @@ +ActionCable::Connection::TaggedLoggerProxy.new(logger) From a298f5eb5e9023ba6cf933c2758954172f70b708 Mon Sep 17 00:00:00 2001 From: Harry Maclean Date: Thu, 16 Jun 2022 13:46:16 +1200 Subject: [PATCH 020/246] Ruby: Recognise File.atomic_write as a file writer This method is an ActiveSupport extension, but there's no harm in recognising it universally as any identically-named method is likely to also be a file writer. --- .../lib/codeql/ruby/frameworks/core/internal/IOOrFile.qll | 2 +- ruby/ql/test/library-tests/frameworks/files/Files.expected | 6 ++++++ ruby/ql/test/library-tests/frameworks/files/Files.rb | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/ruby/ql/lib/codeql/ruby/frameworks/core/internal/IOOrFile.qll b/ruby/ql/lib/codeql/ruby/frameworks/core/internal/IOOrFile.qll index 9e25592de1c..af7058ee7ab 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/core/internal/IOOrFile.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/core/internal/IOOrFile.qll @@ -137,7 +137,7 @@ class IOOrFileWriteMethodCall extends IOOrFileMethodCall { receiverKind = "class" and api = ["IO", "File"] and this = API::getTopLevelMember(api).getAMethodCall(methodName) and - methodName = ["binwrite", "write"] and + methodName = ["binwrite", "write", "atomic_write"] and dataNode = this.getArgument(1) or // e.g. `{IO,File}.new("foo.txt", "a+).puts("hello")` diff --git a/ruby/ql/test/library-tests/frameworks/files/Files.expected b/ruby/ql/test/library-tests/frameworks/files/Files.expected index a05b2da0269..bebb93ef371 100644 --- a/ruby/ql/test/library-tests/frameworks/files/Files.expected +++ b/ruby/ql/test/library-tests/frameworks/files/Files.expected @@ -76,6 +76,8 @@ fileSystemAccesses | Files.rb:41:1:41:26 | call to open | | Files.rb:41:1:41:43 | call to write | | Files.rb:48:1:48:40 | call to printf | +| Files.rb:49:1:49:30 | call to write | +| Files.rb:50:1:50:37 | call to atomic_write | fileNameSources | Files.rb:10:6:10:18 | call to path | | Files.rb:11:6:11:21 | call to to_path | @@ -86,7 +88,11 @@ fileWriters | Files.rb:40:1:40:22 | call to puts | | Files.rb:41:1:41:43 | call to write | | Files.rb:48:1:48:40 | call to printf | +| Files.rb:49:1:49:30 | call to write | +| Files.rb:50:1:50:37 | call to atomic_write | fileSystemWriteAccesses | Files.rb:40:1:40:22 | call to puts | | Files.rb:41:1:41:43 | call to write | | Files.rb:48:1:48:40 | call to printf | +| Files.rb:49:1:49:30 | call to write | +| Files.rb:50:1:50:37 | call to atomic_write | diff --git a/ruby/ql/test/library-tests/frameworks/files/Files.rb b/ruby/ql/test/library-tests/frameworks/files/Files.rb index f8e37661cf2..754a01a543d 100644 --- a/ruby/ql/test/library-tests/frameworks/files/Files.rb +++ b/ruby/ql/test/library-tests/frameworks/files/Files.rb @@ -46,3 +46,5 @@ str_1 = "hello" int_1 = 123 # File/IO write io_file.printf("%s: %d\n", str_1, int_1) +File.write("foo.txt", "hello") +File.atomic_write("foo.txt", "hello") From 7dfab371f6f5bc0274c437caab16ea9d018b200b Mon Sep 17 00:00:00 2001 From: Harry Maclean Date: Thu, 16 Jun 2022 15:33:01 +1200 Subject: [PATCH 021/246] Ruby: Model redirect_back and redirect_back_or_to These are ActionController methods that redirect to the HTTP Referer, falling back to the given location if there is no Referer. --- .../ruby/frameworks/ActionController.qll | 27 ++++++++++++++----- .../security/UrlRedirectCustomizations.qll | 4 ++- .../frameworks/ActionController.expected | 14 ++++++---- .../frameworks/ActionView.expected | 2 +- .../app/controllers/foo/bars_controller.rb | 8 ++++++ .../security/cwe-601/UrlRedirect.expected | 22 +++++++++++---- .../security/cwe-601/UrlRedirect.rb | 27 ++++++++++++++++++- 7 files changed, 84 insertions(+), 20 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/frameworks/ActionController.qll b/ruby/ql/lib/codeql/ruby/frameworks/ActionController.qll index f034e229e32..c0b4a836eda 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/ActionController.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/ActionController.qll @@ -180,18 +180,31 @@ private class ActionControllerHtmlEscapeCall extends HtmlEscapeCall { * specific URL/path or to a different action in this controller. */ class RedirectToCall extends ActionControllerContextCall { - RedirectToCall() { this.getMethodName() = "redirect_to" } + RedirectToCall() { + this.getMethodName() = ["redirect_to", "redirect_back", "redirect_back_or_to"] + } /** Gets the `Expr` representing the URL to redirect to, if any */ - Expr getRedirectUrl() { result = this.getArgument(0) } + Expr getRedirectUrl() { + this.getMethodName() = "redirect_back" and result = this.getKeywordArgument("fallback_location") + or + this.getMethodName() = ["redirect_to", "redirect_back_or_to"] and result = this.getArgument(0) + } /** Gets the `ActionControllerActionMethod` to redirect to, if any */ ActionControllerActionMethod getRedirectActionMethod() { - exists(string methodName | - this.getKeywordArgument("action").getConstantValue().isStringlikeValue(methodName) and - methodName = result.getName() and - result.getEnclosingModule() = this.getControllerClass() - ) + this.getKeywordArgument("action").getConstantValue().isStringlikeValue(result.getName()) and + result.getEnclosingModule() = this.getControllerClass() + } + + /** + * Holds if this method call allows a redirect to an external host. + */ + predicate allowsExternalRedirect() { + // Unless the option allow_other_host is explicitly set to false, assume that external redirects are allowed. + // TODO: Take into account `config.action_controller.raise_on_open_redirects`. + // TODO: Take into account that this option defaults to false in Rails 7. + not this.getKeywordArgument("allow_other_host").getConstantValue().isBoolean(false) } } diff --git a/ruby/ql/lib/codeql/ruby/security/UrlRedirectCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/UrlRedirectCustomizations.qll index 0de946022e6..b42040512ba 100644 --- a/ruby/ql/lib/codeql/ruby/security/UrlRedirectCustomizations.qll +++ b/ruby/ql/lib/codeql/ruby/security/UrlRedirectCustomizations.qll @@ -69,7 +69,9 @@ module UrlRedirect { // We exclude any handlers with names containing create/update/destroy, as these are not likely to handle GET requests. not exists(method.(ActionControllerActionMethod).getARoute()) and not method.getName().regexpMatch(".*(create|update|destroy).*") - ) + ) and + // If this redirect is an ActionController method call, it is only vulnerable if it allows external redirects. + forall(RedirectToCall c | c = e.asExpr().getExpr() | c.allowsExternalRedirect()) ) } } diff --git a/ruby/ql/test/library-tests/frameworks/ActionController.expected b/ruby/ql/test/library-tests/frameworks/ActionController.expected index b9ffd30438a..d306f09b64b 100644 --- a/ruby/ql/test/library-tests/frameworks/ActionController.expected +++ b/ruby/ql/test/library-tests/frameworks/ActionController.expected @@ -3,7 +3,7 @@ actionControllerControllerClasses | ActiveRecord.rb:41:1:64:3 | BarController | | ActiveRecord.rb:66:1:70:3 | BazController | | app/controllers/comments_controller.rb:1:1:7:3 | CommentsController | -| app/controllers/foo/bars_controller.rb:3:1:31:3 | BarsController | +| app/controllers/foo/bars_controller.rb:3:1:39:3 | BarsController | | app/controllers/photos_controller.rb:1:1:4:3 | PhotosController | | app/controllers/posts_controller.rb:1:1:10:3 | PostsController | | app/controllers/users/notifications_controller.rb:2:3:5:5 | NotificationsController | @@ -17,6 +17,8 @@ actionControllerActionMethods | app/controllers/foo/bars_controller.rb:5:3:7:5 | index | | app/controllers/foo/bars_controller.rb:9:3:18:5 | show_debug | | app/controllers/foo/bars_controller.rb:20:3:24:5 | show | +| app/controllers/foo/bars_controller.rb:26:3:28:5 | go_back | +| app/controllers/foo/bars_controller.rb:30:3:32:5 | go_back_2 | | app/controllers/photos_controller.rb:2:3:3:5 | show | | app/controllers/posts_controller.rb:2:3:3:5 | index | | app/controllers/posts_controller.rb:5:3:6:5 | show | @@ -66,10 +68,12 @@ cookiesSources | app/controllers/foo/bars_controller.rb:10:27:10:33 | call to cookies | redirectToCalls | app/controllers/foo/bars_controller.rb:17:5:17:30 | call to redirect_to | +| app/controllers/foo/bars_controller.rb:27:5:27:39 | call to redirect_back_or_to | +| app/controllers/foo/bars_controller.rb:31:5:31:56 | call to redirect_back | actionControllerHelperMethods getAssociatedControllerClasses -| app/controllers/foo/bars_controller.rb:3:1:31:3 | BarsController | app/views/foo/bars/_widget.html.erb:0:0:0:0 | app/views/foo/bars/_widget.html.erb | -| app/controllers/foo/bars_controller.rb:3:1:31:3 | BarsController | app/views/foo/bars/show.html.erb:0:0:0:0 | app/views/foo/bars/show.html.erb | +| app/controllers/foo/bars_controller.rb:3:1:39:3 | BarsController | app/views/foo/bars/_widget.html.erb:0:0:0:0 | app/views/foo/bars/_widget.html.erb | +| app/controllers/foo/bars_controller.rb:3:1:39:3 | BarsController | app/views/foo/bars/show.html.erb:0:0:0:0 | app/views/foo/bars/show.html.erb | controllerTemplateFiles -| app/controllers/foo/bars_controller.rb:3:1:31:3 | BarsController | app/views/foo/bars/_widget.html.erb:0:0:0:0 | app/views/foo/bars/_widget.html.erb | -| app/controllers/foo/bars_controller.rb:3:1:31:3 | BarsController | app/views/foo/bars/show.html.erb:0:0:0:0 | app/views/foo/bars/show.html.erb | +| app/controllers/foo/bars_controller.rb:3:1:39:3 | BarsController | app/views/foo/bars/_widget.html.erb:0:0:0:0 | app/views/foo/bars/_widget.html.erb | +| app/controllers/foo/bars_controller.rb:3:1:39:3 | BarsController | app/views/foo/bars/show.html.erb:0:0:0:0 | app/views/foo/bars/show.html.erb | diff --git a/ruby/ql/test/library-tests/frameworks/ActionView.expected b/ruby/ql/test/library-tests/frameworks/ActionView.expected index 5e80abbae53..5baeda49354 100644 --- a/ruby/ql/test/library-tests/frameworks/ActionView.expected +++ b/ruby/ql/test/library-tests/frameworks/ActionView.expected @@ -14,7 +14,7 @@ rawCalls renderCalls | app/controllers/foo/bars_controller.rb:6:5:6:37 | call to render | | app/controllers/foo/bars_controller.rb:23:5:23:76 | call to render | -| app/controllers/foo/bars_controller.rb:29:5:29:17 | call to render | +| app/controllers/foo/bars_controller.rb:37:5:37:17 | call to render | | app/views/foo/bars/show.html.erb:31:5:31:89 | call to render | renderToCalls | app/controllers/foo/bars_controller.rb:15:16:15:97 | call to render_to_string | diff --git a/ruby/ql/test/library-tests/frameworks/app/controllers/foo/bars_controller.rb b/ruby/ql/test/library-tests/frameworks/app/controllers/foo/bars_controller.rb index 5651648056f..cea4d47a76c 100644 --- a/ruby/ql/test/library-tests/frameworks/app/controllers/foo/bars_controller.rb +++ b/ruby/ql/test/library-tests/frameworks/app/controllers/foo/bars_controller.rb @@ -23,6 +23,14 @@ class BarsController < ApplicationController render "foo/bars/show", locals: { display_text: dt, safe_text: "hello" } end + def go_back + redirect_back_or_to action: "index" + end + + def go_back_2 + redirect_back fallback_location: { action: "index" } + end + private def unreachable_action diff --git a/ruby/ql/test/query-tests/security/cwe-601/UrlRedirect.expected b/ruby/ql/test/query-tests/security/cwe-601/UrlRedirect.expected index 23ab017b05b..1fbacf68ad1 100644 --- a/ruby/ql/test/query-tests/security/cwe-601/UrlRedirect.expected +++ b/ruby/ql/test/query-tests/security/cwe-601/UrlRedirect.expected @@ -3,11 +3,14 @@ edges | UrlRedirect.rb:14:17:14:22 | call to params : | UrlRedirect.rb:14:17:14:43 | call to fetch | | UrlRedirect.rb:19:17:19:22 | call to params : | UrlRedirect.rb:19:17:19:37 | call to to_unsafe_hash | | UrlRedirect.rb:24:31:24:36 | call to params : | UrlRedirect.rb:24:17:24:37 | call to filter_params | -| UrlRedirect.rb:24:31:24:36 | call to params : | UrlRedirect.rb:63:21:63:32 | input_params : | +| UrlRedirect.rb:24:31:24:36 | call to params : | UrlRedirect.rb:88:21:88:32 | input_params : | | UrlRedirect.rb:34:20:34:25 | call to params : | UrlRedirect.rb:34:20:34:31 | ...[...] : | | UrlRedirect.rb:34:20:34:31 | ...[...] : | UrlRedirect.rb:34:17:34:37 | "#{...}/foo" | | UrlRedirect.rb:58:17:58:22 | call to params : | UrlRedirect.rb:58:17:58:28 | ...[...] | -| UrlRedirect.rb:63:21:63:32 | input_params : | UrlRedirect.rb:64:5:64:29 | call to permit : | +| UrlRedirect.rb:63:38:63:43 | call to params : | UrlRedirect.rb:63:38:63:49 | ...[...] | +| UrlRedirect.rb:68:38:68:43 | call to params : | UrlRedirect.rb:68:38:68:49 | ...[...] | +| UrlRedirect.rb:73:25:73:30 | call to params : | UrlRedirect.rb:73:25:73:36 | ...[...] | +| UrlRedirect.rb:88:21:88:32 | input_params : | UrlRedirect.rb:89:5:89:29 | call to permit : | nodes | UrlRedirect.rb:4:17:4:22 | call to params | semmle.label | call to params | | UrlRedirect.rb:9:17:9:22 | call to params : | semmle.label | call to params : | @@ -23,10 +26,16 @@ nodes | UrlRedirect.rb:34:20:34:31 | ...[...] : | semmle.label | ...[...] : | | UrlRedirect.rb:58:17:58:22 | call to params : | semmle.label | call to params : | | UrlRedirect.rb:58:17:58:28 | ...[...] | semmle.label | ...[...] | -| UrlRedirect.rb:63:21:63:32 | input_params : | semmle.label | input_params : | -| UrlRedirect.rb:64:5:64:29 | call to permit : | semmle.label | call to permit : | +| UrlRedirect.rb:63:38:63:43 | call to params : | semmle.label | call to params : | +| UrlRedirect.rb:63:38:63:49 | ...[...] | semmle.label | ...[...] | +| UrlRedirect.rb:68:38:68:43 | call to params : | semmle.label | call to params : | +| UrlRedirect.rb:68:38:68:49 | ...[...] | semmle.label | ...[...] | +| UrlRedirect.rb:73:25:73:30 | call to params : | semmle.label | call to params : | +| UrlRedirect.rb:73:25:73:36 | ...[...] | semmle.label | ...[...] | +| UrlRedirect.rb:88:21:88:32 | input_params : | semmle.label | input_params : | +| UrlRedirect.rb:89:5:89:29 | call to permit : | semmle.label | call to permit : | subpaths -| UrlRedirect.rb:24:31:24:36 | call to params : | UrlRedirect.rb:63:21:63:32 | input_params : | UrlRedirect.rb:64:5:64:29 | call to permit : | UrlRedirect.rb:24:17:24:37 | call to filter_params | +| UrlRedirect.rb:24:31:24:36 | call to params : | UrlRedirect.rb:88:21:88:32 | input_params : | UrlRedirect.rb:89:5:89:29 | call to permit : | UrlRedirect.rb:24:17:24:37 | call to filter_params | #select | UrlRedirect.rb:4:17:4:22 | call to params | UrlRedirect.rb:4:17:4:22 | call to params | UrlRedirect.rb:4:17:4:22 | call to params | Untrusted URL redirection due to $@. | UrlRedirect.rb:4:17:4:22 | call to params | a user-provided value | | UrlRedirect.rb:9:17:9:28 | ...[...] | UrlRedirect.rb:9:17:9:22 | call to params : | UrlRedirect.rb:9:17:9:28 | ...[...] | Untrusted URL redirection due to $@. | UrlRedirect.rb:9:17:9:22 | call to params | a user-provided value | @@ -35,3 +44,6 @@ subpaths | UrlRedirect.rb:24:17:24:37 | call to filter_params | UrlRedirect.rb:24:31:24:36 | call to params : | UrlRedirect.rb:24:17:24:37 | call to filter_params | Untrusted URL redirection due to $@. | UrlRedirect.rb:24:31:24:36 | call to params | a user-provided value | | UrlRedirect.rb:34:17:34:37 | "#{...}/foo" | UrlRedirect.rb:34:20:34:25 | call to params : | UrlRedirect.rb:34:17:34:37 | "#{...}/foo" | Untrusted URL redirection due to $@. | UrlRedirect.rb:34:20:34:25 | call to params | a user-provided value | | UrlRedirect.rb:58:17:58:28 | ...[...] | UrlRedirect.rb:58:17:58:22 | call to params : | UrlRedirect.rb:58:17:58:28 | ...[...] | Untrusted URL redirection due to $@. | UrlRedirect.rb:58:17:58:22 | call to params | a user-provided value | +| UrlRedirect.rb:63:38:63:49 | ...[...] | UrlRedirect.rb:63:38:63:43 | call to params : | UrlRedirect.rb:63:38:63:49 | ...[...] | Untrusted URL redirection due to $@. | UrlRedirect.rb:63:38:63:43 | call to params | a user-provided value | +| UrlRedirect.rb:68:38:68:49 | ...[...] | UrlRedirect.rb:68:38:68:43 | call to params : | UrlRedirect.rb:68:38:68:49 | ...[...] | Untrusted URL redirection due to $@. | UrlRedirect.rb:68:38:68:43 | call to params | a user-provided value | +| UrlRedirect.rb:73:25:73:36 | ...[...] | UrlRedirect.rb:73:25:73:30 | call to params : | UrlRedirect.rb:73:25:73:36 | ...[...] | Untrusted URL redirection due to $@. | UrlRedirect.rb:73:25:73:30 | call to params | a user-provided value | diff --git a/ruby/ql/test/query-tests/security/cwe-601/UrlRedirect.rb b/ruby/ql/test/query-tests/security/cwe-601/UrlRedirect.rb index 9a8f63be011..fa941bc37a0 100644 --- a/ruby/ql/test/query-tests/security/cwe-601/UrlRedirect.rb +++ b/ruby/ql/test/query-tests/security/cwe-601/UrlRedirect.rb @@ -53,11 +53,36 @@ class UsersController < ActionController::Base # BAD # The same as `create1` but this is reachable via a GET request, as configured - # by the routes at the top of this file. + # by the routes at the bottom of this file. def route9 redirect_to params[:key] end + # BAD + def route10 + redirect_back fallback_location: params[:key] + end + + # BAD + def route11 + redirect_back fallback_location: params[:key], allow_other_host: true + end + + # BAD + def route12 + redirect_back_or_to params[:key] + end + + # GOOD + def route13 + redirect_back fallback_location: params[:key], allow_other_host: false + end + + # GOOD + def route14 + redirect_back_or_to params[:key], allow_other_host: false + end + private def filter_params(input_params) From 20ff4c4299a4e322f729de22036f337bbf98eb60 Mon Sep 17 00:00:00 2001 From: Harry Maclean Date: Fri, 17 Jun 2022 10:19:53 +1200 Subject: [PATCH 022/246] Ruby: Model ActiveRecord::Relation#touch_all --- .../codeql/ruby/frameworks/ActiveRecord.qll | 24 ++++++++++++++++++- .../concepts/PersistentWriteAccess.expected | 1 + .../app/controllers/users_controller.rb | 4 ++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll b/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll index 0846e30d047..1097a75919a 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll @@ -359,7 +359,7 @@ private module Persistence { } /** - * Holds if `call` has a keyword argument of with value `value`. + * Holds if `call` has a keyword argument with value `value`. */ private predicate keywordArgumentWithValue(DataFlow::CallNode call, DataFlow::ExprNode value) { exists(ExprNodes::PairCfgNode pair | pair = call.getArgument(_).asExpr() | @@ -412,6 +412,28 @@ private module Persistence { } } + /** + * A call to `ActiveRecord::Relation#touch_all`, which updates the `updated_at` + * attribute on all records in the relation, setting it to the current time or + * the time specified. If passed additional attribute names, they will also be + * updated with the time. + * Examples: + * ```rb + * Person.all.touch_all + * Person.where(name: "David").touch_all + * Person.all.touch_all(:created_at) + * Person.all.touch_all(time: Time.new(2020, 5, 16, 0, 0, 0)) + * ``` + */ + private class TouchAllCall extends DataFlow::CallNode, PersistentWriteAccess::Range { + TouchAllCall() { + exists(this.asExpr().getExpr().(ActiveRecordModelClassMethodCall).getReceiverClass()) and + this.getMethodName() = "touch_all" + } + + override DataFlow::Node getValue() { result = this.getKeywordArgument("time") } + } + /** A call to e.g. `User.insert_all([{name: "foo"}, {name: "bar"}])` */ private class InsertAllLikeCall extends DataFlow::CallNode, PersistentWriteAccess::Range { private ExprNodes::ArrayLiteralCfgNode arr; diff --git a/ruby/ql/test/library-tests/concepts/PersistentWriteAccess.expected b/ruby/ql/test/library-tests/concepts/PersistentWriteAccess.expected index f4ecda5888d..f71e5f89116 100644 --- a/ruby/ql/test/library-tests/concepts/PersistentWriteAccess.expected +++ b/ruby/ql/test/library-tests/concepts/PersistentWriteAccess.expected @@ -14,6 +14,7 @@ | app/controllers/users_controller.rb:20:7:20:57 | call to update_attributes | app/controllers/users_controller.rb:20:49:20:55 | call to get_uid | | app/controllers/users_controller.rb:23:7:23:42 | call to update_attribute | app/controllers/users_controller.rb:23:37:23:41 | "U13" | | app/controllers/users_controller.rb:26:19:26:23 | ... = ... | app/controllers/users_controller.rb:26:19:26:23 | "U14" | +| app/controllers/users_controller.rb:31:7:31:32 | call to touch_all | app/controllers/users_controller.rb:31:28:31:31 | call to time | | app/models/user.rb:4:5:4:28 | call to update | app/models/user.rb:4:23:4:27 | "U15" | | app/models/user.rb:5:5:5:23 | call to update | app/models/user.rb:5:18:5:22 | "U16" | | app/models/user.rb:6:5:6:56 | call to update_attributes | app/models/user.rb:6:35:6:39 | "U17" | diff --git a/ruby/ql/test/library-tests/concepts/app/controllers/users_controller.rb b/ruby/ql/test/library-tests/concepts/app/controllers/users_controller.rb index b6f23d5ff43..83ee6088527 100644 --- a/ruby/ql/test/library-tests/concepts/app/controllers/users_controller.rb +++ b/ruby/ql/test/library-tests/concepts/app/controllers/users_controller.rb @@ -25,6 +25,10 @@ module Users # AssignAttributeCall user.name = "U14" user.save + + # TouchAllCall + User.touch_all + User.touch_all(time: time) end def get_uid From e1dcc207b402a989d193badb3122a524e2f3ed82 Mon Sep 17 00:00:00 2001 From: Harry Maclean Date: Fri, 17 Jun 2022 15:24:26 +1200 Subject: [PATCH 023/246] Ruby: Model methods in Rails::Generators::Actions These methods are sinks for command injection. --- ruby/ql/lib/codeql/ruby/Frameworks.qll | 1 + .../lib/codeql/ruby/frameworks/Railties.qll | 62 +++++++++++++++++++ .../frameworks/railties/Railties.expected | 5 ++ .../frameworks/railties/Railties.ql | 5 ++ .../frameworks/railties/Railties.rb | 14 +++++ 5 files changed, 87 insertions(+) create mode 100644 ruby/ql/lib/codeql/ruby/frameworks/Railties.qll create mode 100644 ruby/ql/test/library-tests/frameworks/railties/Railties.expected create mode 100644 ruby/ql/test/library-tests/frameworks/railties/Railties.ql create mode 100644 ruby/ql/test/library-tests/frameworks/railties/Railties.rb diff --git a/ruby/ql/lib/codeql/ruby/Frameworks.qll b/ruby/ql/lib/codeql/ruby/Frameworks.qll index 4f46b3a2b13..87b8a3f61a2 100644 --- a/ruby/ql/lib/codeql/ruby/Frameworks.qll +++ b/ruby/ql/lib/codeql/ruby/Frameworks.qll @@ -12,6 +12,7 @@ private import codeql.ruby.frameworks.ActiveSupport private import codeql.ruby.frameworks.Archive private import codeql.ruby.frameworks.GraphQL private import codeql.ruby.frameworks.Rails +private import codeql.ruby.frameworks.Railties private import codeql.ruby.frameworks.Stdlib private import codeql.ruby.frameworks.Files private import codeql.ruby.frameworks.HttpClients diff --git a/ruby/ql/lib/codeql/ruby/frameworks/Railties.qll b/ruby/ql/lib/codeql/ruby/frameworks/Railties.qll new file mode 100644 index 00000000000..2c6d45df773 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/frameworks/Railties.qll @@ -0,0 +1,62 @@ +/** + * Modeling for `railties`, which is a gem containing various internals and utilities for the Rails framework. + * https://rubygems.org/gems/railties + */ + +private import ruby +private import codeql.ruby.Concepts +private import codeql.ruby.ApiGraphs +private import codeql.ruby.DataFlow +private import codeql.ruby.ast.internal.Module + +/** + * Modeling for `railties`. + */ +module Railties { + /** + * A class which `include`s `Rails::Generators::Actions`. + */ + private class GeneratorsActionsContext extends ClassDeclaration { + GeneratorsActionsContext() { + exists(IncludeOrPrependCall i | + i.getEnclosingModule() = this and + i.getArgument(0) = + API::getTopLevelMember("Rails") + .getMember("Generators") + .getMember("Actions") + .getAUse() + .asExpr() + .getExpr() + ) + } + } + + /** + * A call to `Rails::Generators::Actions#execute_command`. + * This method concatenates its first and second arguments and executes the result as a shell command. + */ + private class ExecuteCommandCall extends SystemCommandExecution::Range, DataFlow::CallNode { + ExecuteCommandCall() { + this.asExpr().getExpr().getEnclosingModule() instanceof GeneratorsActionsContext and + this.getMethodName() = "execute_command" + } + + override DataFlow::Node getAnArgument() { result = this.getArgument([0, 1]) } + + override predicate isShellInterpreted(DataFlow::Node arg) { any() } + } + + /** + * A call to a method in `Rails::Generators::Actions` which delegates to `execute_command`. + */ + private class ExecuteCommandWrapperCall extends SystemCommandExecution::Range, DataFlow::CallNode { + ExecuteCommandWrapperCall() { + this.asExpr().getExpr().getEnclosingModule() instanceof GeneratorsActionsContext and + this.getMethodName() = ["rake", "rails_command", "git"] + } + + override DataFlow::Node getAnArgument() { result = this.getArgument(0) } + + override predicate isShellInterpreted(DataFlow::Node arg) { any() } + } +} diff --git a/ruby/ql/test/library-tests/frameworks/railties/Railties.expected b/ruby/ql/test/library-tests/frameworks/railties/Railties.expected new file mode 100644 index 00000000000..c012090bbe1 --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/railties/Railties.expected @@ -0,0 +1,5 @@ +| Railties.rb:5:5:5:34 | call to execute_command | +| Railties.rb:6:5:6:37 | call to execute_command | +| Railties.rb:8:5:8:16 | call to rake | +| Railties.rb:10:5:10:27 | call to rails_command | +| Railties.rb:12:5:12:17 | call to git | diff --git a/ruby/ql/test/library-tests/frameworks/railties/Railties.ql b/ruby/ql/test/library-tests/frameworks/railties/Railties.ql new file mode 100644 index 00000000000..9a9731befb4 --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/railties/Railties.ql @@ -0,0 +1,5 @@ +private import ruby +private import codeql.ruby.Concepts +private import codeql.ruby.frameworks.Railties + +query predicate systemCommandExecutions(SystemCommandExecution e) { any() } diff --git a/ruby/ql/test/library-tests/frameworks/railties/Railties.rb b/ruby/ql/test/library-tests/frameworks/railties/Railties.rb new file mode 100644 index 00000000000..59f68b91ae8 --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/railties/Railties.rb @@ -0,0 +1,14 @@ +class Foo + include Rails::Generators::Actions + + def foo + execute_command(:rake, "test") + execute_command(:rails, "server") + + rake("test") + + rails_command("server") + + git("status") + end +end From 94145e9e74bf91641d1dfeca221bb9201ad4d29f Mon Sep 17 00:00:00 2001 From: yoff Date: Mon, 20 Jun 2022 10:14:52 +0200 Subject: [PATCH 024/246] Update python/ql/lib/semmle/python/security/dataflow/TarSlipCustomizations.qll --- .../semmle/python/security/dataflow/TarSlipCustomizations.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ql/lib/semmle/python/security/dataflow/TarSlipCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/TarSlipCustomizations.qll index d767f90c5c6..8795bd31c27 100644 --- a/python/ql/lib/semmle/python/security/dataflow/TarSlipCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/TarSlipCustomizations.qll @@ -51,7 +51,7 @@ module TarSlip { } /** - * A sanitizer based on file name. This beacuse we extract the standard library. + * A sanitizer based on file name. This because we extract the standard library. * * For efficiency we don't want to track the flow of taint * around the tarfile module. From 42dc6814f0a4476b21334023b8cced3c4fc00fc0 Mon Sep 17 00:00:00 2001 From: Alex Denisov Date: Mon, 20 Jun 2022 11:29:41 +0200 Subject: [PATCH 025/246] Swift: extract all output-producing source files, not only primary files --- swift/extractor/SwiftExtractor.cpp | 29 +++++++++++++++---- .../frontend-invocations/A.swift | 0 .../frontend-invocations/B.swift | 0 .../frontend-invocations/C.swift | 0 .../frontend-invocations/D.swift | 0 .../frontend-invocations/E.swift | 0 .../frontend-invocations/Esup.swift | 0 .../frontend-invocations/Files.expected | 6 ++++ .../frontend-invocations/Files.ql | 4 +++ .../frontend-invocations/Makefile | 10 +++++++ .../frontend-invocations/test.py | 5 ++++ .../hello-world/test.expected | 1 + 12 files changed, 49 insertions(+), 6 deletions(-) create mode 100644 swift/integration-tests/frontend-invocations/A.swift create mode 100644 swift/integration-tests/frontend-invocations/B.swift create mode 100644 swift/integration-tests/frontend-invocations/C.swift create mode 100644 swift/integration-tests/frontend-invocations/D.swift create mode 100644 swift/integration-tests/frontend-invocations/E.swift create mode 100644 swift/integration-tests/frontend-invocations/Esup.swift create mode 100644 swift/integration-tests/frontend-invocations/Files.expected create mode 100644 swift/integration-tests/frontend-invocations/Files.ql create mode 100644 swift/integration-tests/frontend-invocations/Makefile create mode 100644 swift/integration-tests/frontend-invocations/test.py diff --git a/swift/extractor/SwiftExtractor.cpp b/swift/extractor/SwiftExtractor.cpp index 49366de53bc..c2bb7c6307e 100644 --- a/swift/extractor/SwiftExtractor.cpp +++ b/swift/extractor/SwiftExtractor.cpp @@ -6,8 +6,10 @@ #include #include #include +#include #include +#include #include #include #include @@ -124,6 +126,17 @@ static void extractDeclarations(const SwiftExtractorConfiguration& config, void codeql::extractSwiftFiles(const SwiftExtractorConfiguration& config, swift::CompilerInstance& compiler) { + // The frontend can be called in many different ways. + // At each invocation we only extract system and builtin modules and any input source files that + // have an output associated with them. + std::unordered_set sourceFiles; + auto inputFiles = compiler.getInvocation().getFrontendOptions().InputsAndOutputs.getAllInputs(); + for (auto& input : inputFiles) { + if (input.getType() == swift::file_types::TY_Swift && !input.outputFilename().empty()) { + sourceFiles.insert(input.getFileName()); + } + } + for (auto& [_, module] : compiler.getASTContext().getLoadedModules()) { // We only extract system and builtin modules here as the other "user" modules can be built // during the build process and then re-used at a later stage. In this case, we extract the @@ -135,12 +148,16 @@ void codeql::extractSwiftFiles(const SwiftExtractorConfiguration& config, // TODO: pass ModuleDecl directly when we have module extraction in place? extractDeclarations(config, decls, compiler, *module); } else { - // The extraction will only work if one (or more) `-primary-file` CLI option is provided, - // which is what always happens in case of `swift build` and `xcodebuild` - for (auto primaryFile : module->getPrimarySourceFiles()) { - archiveFile(config, *primaryFile); - extractDeclarations(config, primaryFile->getTopLevelDecls(), compiler, *module, - primaryFile); + for (auto file : module->getFiles()) { + if (!llvm::isa(file)) { + continue; + } + auto sourceFile = llvm::cast(file); + if (sourceFiles.count(sourceFile->getFilename().str()) == 0) { + continue; + } + archiveFile(config, *sourceFile); + extractDeclarations(config, sourceFile->getTopLevelDecls(), compiler, *module, sourceFile); } } } diff --git a/swift/integration-tests/frontend-invocations/A.swift b/swift/integration-tests/frontend-invocations/A.swift new file mode 100644 index 00000000000..e69de29bb2d diff --git a/swift/integration-tests/frontend-invocations/B.swift b/swift/integration-tests/frontend-invocations/B.swift new file mode 100644 index 00000000000..e69de29bb2d diff --git a/swift/integration-tests/frontend-invocations/C.swift b/swift/integration-tests/frontend-invocations/C.swift new file mode 100644 index 00000000000..e69de29bb2d diff --git a/swift/integration-tests/frontend-invocations/D.swift b/swift/integration-tests/frontend-invocations/D.swift new file mode 100644 index 00000000000..e69de29bb2d diff --git a/swift/integration-tests/frontend-invocations/E.swift b/swift/integration-tests/frontend-invocations/E.swift new file mode 100644 index 00000000000..e69de29bb2d diff --git a/swift/integration-tests/frontend-invocations/Esup.swift b/swift/integration-tests/frontend-invocations/Esup.swift new file mode 100644 index 00000000000..e69de29bb2d diff --git a/swift/integration-tests/frontend-invocations/Files.expected b/swift/integration-tests/frontend-invocations/Files.expected new file mode 100644 index 00000000000..319c1010ab4 --- /dev/null +++ b/swift/integration-tests/frontend-invocations/Files.expected @@ -0,0 +1,6 @@ +| A.swift:0:0:0:0 | A.swift | +| B.swift:0:0:0:0 | B.swift | +| C.swift:0:0:0:0 | C.swift | +| D.swift:0:0:0:0 | D.swift | +| E.swift:0:0:0:0 | E.swift | +| file://:0:0:0:0 | | diff --git a/swift/integration-tests/frontend-invocations/Files.ql b/swift/integration-tests/frontend-invocations/Files.ql new file mode 100644 index 00000000000..9782ea4ce0b --- /dev/null +++ b/swift/integration-tests/frontend-invocations/Files.ql @@ -0,0 +1,4 @@ +import swift + +from File f +select f diff --git a/swift/integration-tests/frontend-invocations/Makefile b/swift/integration-tests/frontend-invocations/Makefile new file mode 100644 index 00000000000..a05c5f1e9f8 --- /dev/null +++ b/swift/integration-tests/frontend-invocations/Makefile @@ -0,0 +1,10 @@ +# TODO: Add linux +SDK=$(shell xcrun -show-sdk-path) +FRONTEND=$(shell xcrun -find swift-frontend) + +all: + $(FRONTEND) -frontend -c A.swift -sdk $(SDK) + $(FRONTEND) -frontend -c B.swift -o B.o -sdk $(SDK) + $(FRONTEND) -frontend -c -primary-file C.swift -sdk $(SDK) + $(FRONTEND) -frontend -c -primary-file D.swift -o D.o -sdk $(SDK) + $(FRONTEND) -frontend -c -primary-file E.swift Esup.swift -o E.o -sdk $(SDK) diff --git a/swift/integration-tests/frontend-invocations/test.py b/swift/integration-tests/frontend-invocations/test.py new file mode 100644 index 00000000000..2c956137cb5 --- /dev/null +++ b/swift/integration-tests/frontend-invocations/test.py @@ -0,0 +1,5 @@ +from create_database_utils import * + +run_codeql_database_create([ + 'make', +], lang='swift') diff --git a/swift/integration-tests/hello-world/test.expected b/swift/integration-tests/hello-world/test.expected index 29cb05cfa35..8a3be429106 100644 --- a/swift/integration-tests/hello-world/test.expected +++ b/swift/integration-tests/hello-world/test.expected @@ -1,2 +1,3 @@ +| Package.swift:0:0:0:0 | Package.swift | | Sources/hello-world/hello_world.swift:0:0:0:0 | Sources/hello-world/hello_world.swift | | file://:0:0:0:0 | | From 8f259d4bb6a774a088b7468493cfafd6660c41dc Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 30 May 2022 14:10:34 +0200 Subject: [PATCH 026/246] Python: port API graph doc comment --- python/ql/lib/semmle/python/ApiGraphs.qll | 77 ++++++++++++++++++++++- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/python/ql/lib/semmle/python/ApiGraphs.qll b/python/ql/lib/semmle/python/ApiGraphs.qll index fcb89e5f866..16fa18adea0 100644 --- a/python/ql/lib/semmle/python/ApiGraphs.qll +++ b/python/ql/lib/semmle/python/ApiGraphs.qll @@ -12,12 +12,83 @@ import semmle.python.dataflow.new.DataFlow private import semmle.python.internal.CachedStages /** - * Provides classes and predicates for working with APIs used in a database. + * Provides classes and predicates for working with the API boundary between the current + * codebase and external libraries. + * + * See `API::Node` for more in-depth documentation. */ module API { /** - * An abstract representation of a definition or use of an API component such as a function - * exported by a Python package, or its result. + * A node in the API graph, representing a value that has crossed the boundary between this + * codebase and an external library (or in general, any external codebase). + * + * ### Basic usage + * + * API graphs are typically used to identify "API calls", that is, calls to an external function + * whose implementation is not necessarily part of the current codebase. + * + * The most basic use of API graphs is typically as follows: + * 1. Start with `API::moduleImport` for the relevant library. + * 2. Follow up with a chain of accessors such as `getMember` describing how to get to the relevant API function. + * 3. Map the resulting API graph nodes to data-flow nodes, using `asSource` or `asSink`. + * + * For example, a simplified way to get arguments to `json.dumps` would be + * ```ql + * API::moduleImport("json").getMember("dumps").getParameter(0).asSink() + * ``` + * + * The most commonly used accessors are `getMember`, `getParameter`, and `getReturn`. + * + * ### API graph nodes + * + * There are two kinds of nodes in the API graphs, distinguished by who is "holding" the value: + * - **Use-nodes** represent values held by the current codebase, which came from an external library. + * (The current codebase is "using" a value that came from the library). + * - **Def-nodes** represent values held by the external library, which came from this codebase. + * (The current codebase "defines" the value seen by the library). + * + * API graph nodes are associated with data-flow nodes in the current codebase. + * (Since external libraries are not part of the database, there is no way to associate with concrete + * data-flow nodes from the external library). + * - **Use-nodes** are associated with data-flow nodes where a value enters the current codebase, + * such as the return value of a call to an external function. + * - **Def-nodes** are associated with data-flow nodes where a value leaves the current codebase, + * such as an argument passed in a call to an external function. + * + * + * ### Access paths and edge labels + * + * Nodes in the API graph are associated with a set of access paths, describing a series of operations + * that may be performed to obtain that value. + * + * For example, the access path `API::moduleImport("json").getMember("dumps")` represents the action of + * importing `json` and then accessing the member `dumps` on the resulting object. + * + * Each edge in the graph is labelled by such an "operation". For an edge `A->B`, the type of the `A` node + * determines who is performing the operation, and the type of the `B` node determines who ends up holding + * the result: + * - An edge starting from a use-node describes what the current codebase is doing to a value that + * came from a library. + * - An edge starting from a def-node describes what the external library might do to a value that + * came from the current codebase. + * - An edge ending in a use-node means the result ends up in the current codebase (at its associated data-flow node). + * - An edge ending in a def-node means the result ends up in external code (its associated data-flow node is + * the place where it was "last seen" in the current codebase before flowing out) + * + * Because the implementation of the external library is not visible, it is not known exactly what operations + * it will perform on values that flow there. Instead, the edges starting from a def-node are operations that would + * lead to an observable effect within the current codebase; without knowing for certain if the library will actually perform + * those operations. (When constructing these edges, we assume the library is somewhat well-behaved). + * + * For example, given this snippet: + * ```python + * import foo + * foo.bar(lambda x: doSomething(x)) + * ``` + * A callback is passed to the external function `foo.bar`. We can't know if `foo.bar` will actually invoke this callback. + * But _if_ the library should decide to invoke the callback, then a value will flow into the current codebase via the `x` parameter. + * For that reason, an edge is generated representing the argument-passing operation that might be performed by `foo.bar`. + * This edge is going from the def-node associated with the callback to the use-node associated with the parameter `x`. */ class Node extends Impl::TApiNode { /** From 60fde3c031e4b3c2f1c92bd91c23a459daa1bab4 Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 13 Jun 2022 09:56:10 +0200 Subject: [PATCH 027/246] Python: Rename getARhs -> asSink --- python/ql/lib/semmle/python/ApiGraphs.qll | 10 +++++----- python/ql/lib/semmle/python/frameworks/Aiomysql.qll | 4 ++-- python/ql/lib/semmle/python/frameworks/Aiopg.qll | 4 ++-- python/ql/lib/semmle/python/frameworks/Asyncpg.qll | 4 ++-- python/ql/lib/semmle/python/frameworks/Requests.qll | 2 +- python/ql/lib/semmle/python/frameworks/Stdlib.qll | 12 ++++++------ .../semmle/python/frameworks/data/ModelsAsData.qll | 2 +- .../dataflow/PathInjectionCustomizations.qll | 2 +- .../security/dataflow/SqlInjectionCustomizations.qll | 2 +- python/ql/test/TestUtilities/VerifyApiGraphs.qll | 2 +- python/ql/test/experimental/meta/MaDTest.qll | 2 +- python/ql/test/library-tests/frameworks/data/test.ql | 4 ++-- 12 files changed, 25 insertions(+), 25 deletions(-) diff --git a/python/ql/lib/semmle/python/ApiGraphs.qll b/python/ql/lib/semmle/python/ApiGraphs.qll index 16fa18adea0..9be1419f230 100644 --- a/python/ql/lib/semmle/python/ApiGraphs.qll +++ b/python/ql/lib/semmle/python/ApiGraphs.qll @@ -128,13 +128,13 @@ module API { * ``` * `x` is the right-hand side of a definition of the first parameter of `bar` from the `mypkg.foo` module. */ - DataFlow::Node getARhs() { Impl::rhs(this, result) } + DataFlow::Node asSink() { Impl::rhs(this, result) } /** * Gets a data-flow node that may interprocedurally flow to the right-hand side of a definition * of the API component represented by this node. */ - DataFlow::Node getAValueReachingRhs() { result = Impl::trackDefNode(this.getARhs()) } + DataFlow::Node getAValueReachingRhs() { result = Impl::trackDefNode(this.asSink()) } /** * Gets an immediate use of the API component represented by this node. @@ -390,14 +390,14 @@ module API { * Gets an API node where a RHS of the node is the `i`th argument to this call. */ pragma[noinline] - private Node getAParameterCandidate(int i) { result.getARhs() = this.getArg(i) } + private Node getAParameterCandidate(int i) { result.asSink() = this.getArg(i) } /** Gets the API node for a parameter of this invocation. */ Node getAParameter() { result = this.getParameter(_) } /** Gets the object that this method-call is being called on, if this is a method-call */ Node getSelfParameter() { - result.getARhs() = this.(DataFlow::MethodCallNode).getObject() and + result.asSink() = this.(DataFlow::MethodCallNode).getObject() and result = callee.getSelfParameter() } @@ -417,7 +417,7 @@ module API { pragma[noinline] private Node getAKeywordParameterCandidate(string name) { - result.getARhs() = this.getArgByName(name) + result.asSink() = this.getArgByName(name) } /** Gets the API node for the return value of this call. */ diff --git a/python/ql/lib/semmle/python/frameworks/Aiomysql.qll b/python/ql/lib/semmle/python/frameworks/Aiomysql.qll index fdcf21afc34..aa676e8fe82 100644 --- a/python/ql/lib/semmle/python/frameworks/Aiomysql.qll +++ b/python/ql/lib/semmle/python/frameworks/Aiomysql.qll @@ -53,7 +53,7 @@ private module Aiomysql { class CursorExecuteCall extends SqlConstruction::Range, API::CallNode { CursorExecuteCall() { this = cursor().getMember("execute").getACall() } - override DataFlow::Node getSql() { result = this.getParameter(0, "operation").getARhs() } + override DataFlow::Node getSql() { result = this.getParameter(0, "operation").asSink() } } /** @@ -94,7 +94,7 @@ private module Aiomysql { class SAConnectionExecuteCall extends SqlConstruction::Range, API::CallNode { SAConnectionExecuteCall() { this = saConnection().getMember("execute").getACall() } - override DataFlow::Node getSql() { result = this.getParameter(0, "query").getARhs() } + override DataFlow::Node getSql() { result = this.getParameter(0, "query").asSink() } } /** diff --git a/python/ql/lib/semmle/python/frameworks/Aiopg.qll b/python/ql/lib/semmle/python/frameworks/Aiopg.qll index afc553fe04b..27c754fb344 100644 --- a/python/ql/lib/semmle/python/frameworks/Aiopg.qll +++ b/python/ql/lib/semmle/python/frameworks/Aiopg.qll @@ -53,7 +53,7 @@ private module Aiopg { class CursorExecuteCall extends SqlConstruction::Range, API::CallNode { CursorExecuteCall() { this = cursor().getMember("execute").getACall() } - override DataFlow::Node getSql() { result = this.getParameter(0, "operation").getARhs() } + override DataFlow::Node getSql() { result = this.getParameter(0, "operation").asSink() } } /** @@ -90,7 +90,7 @@ private module Aiopg { class SAConnectionExecuteCall extends SqlConstruction::Range, API::CallNode { SAConnectionExecuteCall() { this = saConnection().getMember("execute").getACall() } - override DataFlow::Node getSql() { result = this.getParameter(0, "query").getARhs() } + override DataFlow::Node getSql() { result = this.getParameter(0, "query").asSink() } } /** diff --git a/python/ql/lib/semmle/python/frameworks/Asyncpg.qll b/python/ql/lib/semmle/python/frameworks/Asyncpg.qll index 81da12a015c..26361366022 100644 --- a/python/ql/lib/semmle/python/frameworks/Asyncpg.qll +++ b/python/ql/lib/semmle/python/frameworks/Asyncpg.qll @@ -61,7 +61,7 @@ private module Asyncpg { this = ModelOutput::getATypeNode("asyncpg", "Connection").getMember("cursor").getACall() } - override DataFlow::Node getSql() { result = this.getParameter(0, "query").getARhs() } + override DataFlow::Node getSql() { result = this.getParameter(0, "query").asSink() } } /** The creation of a `Cursor` executes the associated query. */ @@ -78,7 +78,7 @@ private module Asyncpg { prepareCall = ModelOutput::getATypeNode("asyncpg", "Connection").getMember("prepare").getACall() | - sql = prepareCall.getParameter(0, "query").getARhs() and + sql = prepareCall.getParameter(0, "query").asSink() and this = prepareCall .getReturn() diff --git a/python/ql/lib/semmle/python/frameworks/Requests.qll b/python/ql/lib/semmle/python/frameworks/Requests.qll index 6c9028d6135..05e08b45166 100644 --- a/python/ql/lib/semmle/python/frameworks/Requests.qll +++ b/python/ql/lib/semmle/python/frameworks/Requests.qll @@ -61,7 +61,7 @@ private module Requests { override predicate disablesCertificateValidation( DataFlow::Node disablingNode, DataFlow::Node argumentOrigin ) { - disablingNode = this.getKeywordParameter("verify").getARhs() and + disablingNode = this.getKeywordParameter("verify").asSink() and argumentOrigin = this.getKeywordParameter("verify").getAValueReachingRhs() and // requests treats `None` as the default and all other "falsey" values as `False`. argumentOrigin.asExpr().(ImmutableLiteral).booleanValue() = false and diff --git a/python/ql/lib/semmle/python/frameworks/Stdlib.qll b/python/ql/lib/semmle/python/frameworks/Stdlib.qll index a273511979c..bb1b6073d16 100644 --- a/python/ql/lib/semmle/python/frameworks/Stdlib.qll +++ b/python/ql/lib/semmle/python/frameworks/Stdlib.qll @@ -2553,7 +2553,7 @@ private module StdlibPrivate { override DataFlow::Node getAPathArgument() { result = super.getAPathArgument() or - result = this.getParameter(0, "target").getARhs() + result = this.getParameter(0, "target").asSink() } } @@ -2570,7 +2570,7 @@ private module StdlibPrivate { override DataFlow::Node getAPathArgument() { result = super.getAPathArgument() or - result = this.getParameter(0, "target").getARhs() + result = this.getParameter(0, "target").asSink() } } @@ -2585,7 +2585,7 @@ private module StdlibPrivate { override DataFlow::Node getAPathArgument() { result = super.getAPathArgument() or - result = this.getParameter(0, "other_path").getARhs() + result = this.getParameter(0, "other_path").asSink() } } @@ -2670,7 +2670,7 @@ private module StdlibPrivate { override Cryptography::CryptographicAlgorithm getAlgorithm() { result.matchesName(hashName) } - override DataFlow::Node getAnInput() { result = this.getParameter(1, "data").getARhs() } + override DataFlow::Node getAnInput() { result = this.getParameter(1, "data").asSink() } override Cryptography::BlockMode getBlockMode() { none() } } @@ -3433,7 +3433,7 @@ private module StdlibPrivate { private DataFlow::Node saxParserWithFeatureExternalGesTurnedOn(DataFlow::TypeTracker t) { t.start() and exists(SaxParserSetFeatureCall call | - call.getFeatureArg().getARhs() = + call.getFeatureArg().asSink() = API::moduleImport("xml") .getMember("sax") .getMember("handler") @@ -3449,7 +3449,7 @@ private module StdlibPrivate { // take account of that we can set the feature to False, which makes the parser safe again not exists(SaxParserSetFeatureCall call | call.getObject() = result and - call.getFeatureArg().getARhs() = + call.getFeatureArg().asSink() = API::moduleImport("xml") .getMember("sax") .getMember("handler") diff --git a/python/ql/lib/semmle/python/frameworks/data/ModelsAsData.qll b/python/ql/lib/semmle/python/frameworks/data/ModelsAsData.qll index 2af91a69432..e80e9f7ad0b 100644 --- a/python/ql/lib/semmle/python/frameworks/data/ModelsAsData.qll +++ b/python/ql/lib/semmle/python/frameworks/data/ModelsAsData.qll @@ -34,7 +34,7 @@ private class RemoteFlowSourceFromCsv extends RemoteFlowSource { private predicate summaryStepNodes(DataFlow::Node pred, DataFlow::Node succ, string kind) { exists(API::Node predNode, API::Node succNode | Specific::summaryStep(predNode, succNode, kind) and - pred = predNode.getARhs() and + pred = predNode.asSink() and succ = succNode.getAnImmediateUse() ) } diff --git a/python/ql/lib/semmle/python/security/dataflow/PathInjectionCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/PathInjectionCustomizations.qll index 5a033664823..fe7053ef4d2 100644 --- a/python/ql/lib/semmle/python/security/dataflow/PathInjectionCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/PathInjectionCustomizations.qll @@ -62,7 +62,7 @@ module PathInjection { private import semmle.python.frameworks.data.ModelsAsData private class DataAsFileSink extends Sink { - DataAsFileSink() { this = ModelOutput::getASinkNode("path-injection").getARhs() } + DataAsFileSink() { this = ModelOutput::getASinkNode("path-injection").asSink() } } /** diff --git a/python/ql/lib/semmle/python/security/dataflow/SqlInjectionCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/SqlInjectionCustomizations.qll index cf21a5c0e94..ae011c00f11 100644 --- a/python/ql/lib/semmle/python/security/dataflow/SqlInjectionCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/SqlInjectionCustomizations.qll @@ -65,6 +65,6 @@ module SqlInjection { /** A sink for sql-injection from model data. */ private class DataAsSqlSink extends Sink { - DataAsSqlSink() { this = ModelOutput::getASinkNode("sql-injection").getARhs() } + DataAsSqlSink() { this = ModelOutput::getASinkNode("sql-injection").asSink() } } } diff --git a/python/ql/test/TestUtilities/VerifyApiGraphs.qll b/python/ql/test/TestUtilities/VerifyApiGraphs.qll index 22d7ae365d4..03af20dbabe 100644 --- a/python/ql/test/TestUtilities/VerifyApiGraphs.qll +++ b/python/ql/test/TestUtilities/VerifyApiGraphs.qll @@ -21,7 +21,7 @@ import semmle.python.ApiGraphs private DataFlow::Node getNode(API::Node nd, string kind) { kind = "def" and - result = nd.getARhs() + result = nd.asSink() or kind = "use" and result = nd.getAUse() diff --git a/python/ql/test/experimental/meta/MaDTest.qll b/python/ql/test/experimental/meta/MaDTest.qll index 3d75ba0d2e7..92b8afef422 100644 --- a/python/ql/test/experimental/meta/MaDTest.qll +++ b/python/ql/test/experimental/meta/MaDTest.qll @@ -19,7 +19,7 @@ class MadSinkTest extends InlineExpectationsTest { override predicate hasActualResult(Location location, string element, string tag, string value) { exists(location.getFile().getRelativePath()) and exists(DataFlow::Node sink, string kind | - sink = ModelOutput::getASinkNode(kind).getARhs() and + sink = ModelOutput::getASinkNode(kind).asSink() and location = sink.getLocation() and element = sink.toString() and value = prettyNodeForInlineTest(sink) and diff --git a/python/ql/test/library-tests/frameworks/data/test.ql b/python/ql/test/library-tests/frameworks/data/test.ql index 86f960b1adf..c1bc85eaf49 100644 --- a/python/ql/test/library-tests/frameworks/data/test.ql +++ b/python/ql/test/library-tests/frameworks/data/test.ql @@ -91,7 +91,7 @@ class BasicTaintTracking extends TaintTracking::Configuration { } override predicate isSink(DataFlow::Node sink) { - sink = ModelOutput::getASinkNode("test-sink").getARhs() + sink = ModelOutput::getASinkNode("test-sink").asSink() } } @@ -100,7 +100,7 @@ query predicate taintFlow(DataFlow::Node source, DataFlow::Node sink) { } query predicate isSink(DataFlow::Node node, string kind) { - node = ModelOutput::getASinkNode(kind).getARhs() + node = ModelOutput::getASinkNode(kind).asSink() } query predicate isSource(DataFlow::Node node, string kind) { From 181a53bd03837c620feb63aa68145fa0e58bbc9f Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 13 Jun 2022 09:59:24 +0200 Subject: [PATCH 028/246] Python: Rename getAnImmediateUse -> asSource --- python/ql/lib/semmle/python/ApiGraphs.qll | 8 ++++---- python/ql/lib/semmle/python/filters/Tests.qll | 2 +- python/ql/lib/semmle/python/frameworks/Aiohttp.qll | 4 +--- .../ql/lib/semmle/python/frameworks/Aiomysql.qll | 4 ++-- python/ql/lib/semmle/python/frameworks/Aiopg.qll | 4 ++-- python/ql/lib/semmle/python/frameworks/Asyncpg.qll | 4 ++-- .../lib/semmle/python/frameworks/Cryptography.qll | 4 +--- python/ql/lib/semmle/python/frameworks/Django.qll | 14 ++++++-------- python/ql/lib/semmle/python/frameworks/FastApi.qll | 2 +- python/ql/lib/semmle/python/frameworks/Flask.qll | 14 ++++++-------- .../semmle/python/frameworks/FlaskSqlAlchemy.qll | 4 ++-- .../lib/semmle/python/frameworks/RestFramework.qll | 2 +- python/ql/lib/semmle/python/frameworks/Stdlib.qll | 12 ++++++------ python/ql/lib/semmle/python/frameworks/Tornado.qll | 2 +- python/ql/lib/semmle/python/frameworks/Twisted.qll | 2 +- .../semmle/python/frameworks/data/ModelsAsData.qll | 4 ++-- .../python/frameworks/internal/SubclassFinder.qll | 2 +- python/ql/lib/semmle/python/regex.qll | 2 +- .../dataflow/typetracking/moduleattr.ql | 2 +- .../experimental/dataflow/typetracking/tracked.ql | 6 +++--- python/ql/test/experimental/meta/MaDTest.qll | 2 +- .../ql/test/library-tests/frameworks/data/test.ql | 4 ++-- 22 files changed, 48 insertions(+), 56 deletions(-) diff --git a/python/ql/lib/semmle/python/ApiGraphs.qll b/python/ql/lib/semmle/python/ApiGraphs.qll index 9be1419f230..61a42f812a7 100644 --- a/python/ql/lib/semmle/python/ApiGraphs.qll +++ b/python/ql/lib/semmle/python/ApiGraphs.qll @@ -147,12 +147,12 @@ module API { * to the `escape` member of `re`, neither `x` nor any node that `x` flows to is a reference to * this API component. */ - DataFlow::LocalSourceNode getAnImmediateUse() { Impl::use(this, result) } + DataFlow::LocalSourceNode asSource() { Impl::use(this, result) } /** * Gets a call to the function represented by this API component. */ - CallNode getACall() { result = this.getReturn().getAnImmediateUse() } + CallNode getACall() { result = this.getReturn().asSource() } /** * Gets a node representing member `m` of this API component. @@ -377,7 +377,7 @@ module API { class CallNode extends DataFlow::CallCfgNode { API::Node callee; - CallNode() { this = callee.getReturn().getAnImmediateUse() } + CallNode() { this = callee.getReturn().asSource() } /** Gets the API node for the `i`th parameter of this invocation. */ pragma[nomagic] @@ -423,7 +423,7 @@ module API { /** Gets the API node for the return value of this call. */ Node getReturn() { result = callee.getReturn() and - result.getAnImmediateUse() = this + result.asSource() = this } /** diff --git a/python/ql/lib/semmle/python/filters/Tests.qll b/python/ql/lib/semmle/python/filters/Tests.qll index 44ea2edebe4..20f6713a837 100644 --- a/python/ql/lib/semmle/python/filters/Tests.qll +++ b/python/ql/lib/semmle/python/filters/Tests.qll @@ -9,7 +9,7 @@ class UnitTestClass extends TestScope, Class { testCaseString.matches("%TestCase") and testCaseClass = any(API::Node mod).getMember(testCaseString) | - this.getParent() = testCaseClass.getASubclass*().getAnImmediateUse().asExpr() + this.getParent() = testCaseClass.getASubclass*().asSource().asExpr() ) } } diff --git a/python/ql/lib/semmle/python/frameworks/Aiohttp.qll b/python/ql/lib/semmle/python/frameworks/Aiohttp.qll index 359c8c14159..733845de30b 100644 --- a/python/ql/lib/semmle/python/frameworks/Aiohttp.qll +++ b/python/ql/lib/semmle/python/frameworks/Aiohttp.qll @@ -243,9 +243,7 @@ module AiohttpWebModel { /** A class that has a super-type which is an aiohttp.web View class. */ class AiohttpViewClassFromSuperClass extends AiohttpViewClass { - AiohttpViewClassFromSuperClass() { - this.getParent() = View::subclassRef().getAnImmediateUse().asExpr() - } + AiohttpViewClassFromSuperClass() { this.getParent() = View::subclassRef().asSource().asExpr() } } /** A class that is used in a route-setup, therefore being considered an aiohttp.web View class. */ diff --git a/python/ql/lib/semmle/python/frameworks/Aiomysql.qll b/python/ql/lib/semmle/python/frameworks/Aiomysql.qll index aa676e8fe82..3d43c13b91a 100644 --- a/python/ql/lib/semmle/python/frameworks/Aiomysql.qll +++ b/python/ql/lib/semmle/python/frameworks/Aiomysql.qll @@ -63,7 +63,7 @@ private module Aiomysql { class AwaitedCursorExecuteCall extends SqlExecution::Range { CursorExecuteCall executeCall; - AwaitedCursorExecuteCall() { this = executeCall.getReturn().getAwaited().getAnImmediateUse() } + AwaitedCursorExecuteCall() { this = executeCall.getReturn().getAwaited().asSource() } override DataFlow::Node getSql() { result = executeCall.getSql() } } @@ -104,7 +104,7 @@ private module Aiomysql { class AwaitedSAConnectionExecuteCall extends SqlExecution::Range { SAConnectionExecuteCall execute; - AwaitedSAConnectionExecuteCall() { this = execute.getReturn().getAwaited().getAnImmediateUse() } + AwaitedSAConnectionExecuteCall() { this = execute.getReturn().getAwaited().asSource() } override DataFlow::Node getSql() { result = execute.getSql() } } diff --git a/python/ql/lib/semmle/python/frameworks/Aiopg.qll b/python/ql/lib/semmle/python/frameworks/Aiopg.qll index 27c754fb344..053d59df51a 100644 --- a/python/ql/lib/semmle/python/frameworks/Aiopg.qll +++ b/python/ql/lib/semmle/python/frameworks/Aiopg.qll @@ -63,7 +63,7 @@ private module Aiopg { class AwaitedCursorExecuteCall extends SqlExecution::Range { CursorExecuteCall execute; - AwaitedCursorExecuteCall() { this = execute.getReturn().getAwaited().getAnImmediateUse() } + AwaitedCursorExecuteCall() { this = execute.getReturn().getAwaited().asSource() } override DataFlow::Node getSql() { result = execute.getSql() } } @@ -100,7 +100,7 @@ private module Aiopg { class AwaitedSAConnectionExecuteCall extends SqlExecution::Range { SAConnectionExecuteCall excute; - AwaitedSAConnectionExecuteCall() { this = excute.getReturn().getAwaited().getAnImmediateUse() } + AwaitedSAConnectionExecuteCall() { this = excute.getReturn().getAwaited().asSource() } override DataFlow::Node getSql() { result = excute.getSql() } } diff --git a/python/ql/lib/semmle/python/frameworks/Asyncpg.qll b/python/ql/lib/semmle/python/frameworks/Asyncpg.qll index 26361366022..ca28dca550f 100644 --- a/python/ql/lib/semmle/python/frameworks/Asyncpg.qll +++ b/python/ql/lib/semmle/python/frameworks/Asyncpg.qll @@ -71,7 +71,7 @@ private module Asyncpg { CursorCreation() { exists(CursorConstruction c | sql = c.getSql() and - this = c.getReturn().getAwaited().getAnImmediateUse() + this = c.getReturn().getAwaited().asSource() ) or exists(API::CallNode prepareCall | @@ -86,7 +86,7 @@ private module Asyncpg { .getMember("cursor") .getReturn() .getAwaited() - .getAnImmediateUse() + .asSource() ) } diff --git a/python/ql/lib/semmle/python/frameworks/Cryptography.qll b/python/ql/lib/semmle/python/frameworks/Cryptography.qll index 954def9e7da..1520f17e883 100644 --- a/python/ql/lib/semmle/python/frameworks/Cryptography.qll +++ b/python/ql/lib/semmle/python/frameworks/Cryptography.qll @@ -144,9 +144,7 @@ private module CryptographyModel { DataFlow::Node getCurveArg() { result in [this.getArg(0), this.getArgByName("curve")] } override int getKeySizeWithOrigin(DataFlow::Node origin) { - exists(API::Node n | - n = Ecc::predefinedCurveClass(result) and origin = n.getAnImmediateUse() - | + exists(API::Node n | n = Ecc::predefinedCurveClass(result) and origin = n.asSource() | this.getCurveArg() = n.getAUse() or this.getCurveArg() = n.getReturn().getAUse() diff --git a/python/ql/lib/semmle/python/frameworks/Django.qll b/python/ql/lib/semmle/python/frameworks/Django.qll index a22957d1fe0..a955de7a01a 100644 --- a/python/ql/lib/semmle/python/frameworks/Django.qll +++ b/python/ql/lib/semmle/python/frameworks/Django.qll @@ -562,7 +562,7 @@ module PrivateDjango { /** A `django.db.connection` is a PEP249 compliant DB connection. */ class DjangoDbConnection extends PEP249::Connection::InstanceSource { - DjangoDbConnection() { this = connection().getAnImmediateUse() } + DjangoDbConnection() { this = connection().asSource() } } // ------------------------------------------------------------------------- @@ -869,7 +869,7 @@ module PrivateDjango { /** Gets the (AST) class of the Django model class `modelClass`. */ Class getModelClassClass(API::Node modelClass) { - result.getParent() = modelClass.getAnImmediateUse().asExpr() and + result.getParent() = modelClass.asSource().asExpr() and modelClass = Model::subclassRef() } @@ -2202,9 +2202,7 @@ module PrivateDjango { * thereby handling user input. */ class DjangoFormClass extends Class, SelfRefMixin { - DjangoFormClass() { - this.getParent() = Django::Forms::Form::subclassRef().getAnImmediateUse().asExpr() - } + DjangoFormClass() { this.getParent() = Django::Forms::Form::subclassRef().asSource().asExpr() } } /** @@ -2237,7 +2235,7 @@ module PrivateDjango { */ class DjangoFormFieldClass extends Class { DjangoFormFieldClass() { - this.getParent() = Django::Forms::Field::subclassRef().getAnImmediateUse().asExpr() + this.getParent() = Django::Forms::Field::subclassRef().asSource().asExpr() } } @@ -2340,7 +2338,7 @@ module PrivateDjango { */ class DjangoViewClassFromSuperClass extends DjangoViewClass { DjangoViewClassFromSuperClass() { - this.getParent() = Django::Views::View::subclassRef().getAnImmediateUse().asExpr() + this.getParent() = Django::Views::View::subclassRef().asSource().asExpr() } } @@ -2743,7 +2741,7 @@ module PrivateDjango { .getMember("utils") .getMember("log") .getMember("request_logger") - .getAnImmediateUse() + .asSource() } } diff --git a/python/ql/lib/semmle/python/frameworks/FastApi.qll b/python/ql/lib/semmle/python/frameworks/FastApi.qll index 1c48562eb70..3a683108f4c 100644 --- a/python/ql/lib/semmle/python/frameworks/FastApi.qll +++ b/python/ql/lib/semmle/python/frameworks/FastApi.qll @@ -166,7 +166,7 @@ private module FastApi { exists(Class cls, API::Node base | base = getModeledResponseClass(_).getASubclass*() and cls.getABase() = base.getAUse().asExpr() and - responseClass.getAnImmediateUse().asExpr() = cls.getParent() + responseClass.asSource().asExpr() = cls.getParent() | exists(Assign assign | assign = cls.getAStmt() | assign.getATarget().(Name).getId() = "media_type" and diff --git a/python/ql/lib/semmle/python/frameworks/Flask.qll b/python/ql/lib/semmle/python/frameworks/Flask.qll index 02331ed316e..8df9295c285 100644 --- a/python/ql/lib/semmle/python/frameworks/Flask.qll +++ b/python/ql/lib/semmle/python/frameworks/Flask.qll @@ -195,7 +195,7 @@ module Flask { FlaskViewClass() { api_node = Views::View::subclassRef() and - this.getParent() = api_node.getAnImmediateUse().asExpr() + this.getParent() = api_node.asSource().asExpr() } /** Gets a function that could handle incoming requests, if any. */ @@ -220,7 +220,7 @@ module Flask { class FlaskMethodViewClass extends FlaskViewClass { FlaskMethodViewClass() { api_node = Views::MethodView::subclassRef() and - this.getParent() = api_node.getAnImmediateUse().asExpr() + this.getParent() = api_node.asSource().asExpr() } override Function getARequestHandler() { @@ -404,7 +404,7 @@ module Flask { private class RequestAttrMultiDict extends Werkzeug::MultiDict::InstanceSource { RequestAttrMultiDict() { - this = request().getMember(["args", "values", "form", "files"]).getAnImmediateUse() + this = request().getMember(["args", "values", "form", "files"]).asSource() } } @@ -427,14 +427,12 @@ module Flask { /** An `Headers` instance that originates from a flask request. */ private class FlaskRequestHeadersInstances extends Werkzeug::Headers::InstanceSource { - FlaskRequestHeadersInstances() { this = request().getMember("headers").getAnImmediateUse() } + FlaskRequestHeadersInstances() { this = request().getMember("headers").asSource() } } /** An `Authorization` instance that originates from a flask request. */ private class FlaskRequestAuthorizationInstances extends Werkzeug::Authorization::InstanceSource { - FlaskRequestAuthorizationInstances() { - this = request().getMember("authorization").getAnImmediateUse() - } + FlaskRequestAuthorizationInstances() { this = request().getMember("authorization").asSource() } } // --------------------------------------------------------------------------- @@ -574,6 +572,6 @@ module Flask { * - https://flask.palletsprojects.com/en/2.0.x/logging/ */ private class FlaskLogger extends Stdlib::Logger::InstanceSource { - FlaskLogger() { this = FlaskApp::instance().getMember("logger").getAnImmediateUse() } + FlaskLogger() { this = FlaskApp::instance().getMember("logger").asSource() } } } diff --git a/python/ql/lib/semmle/python/frameworks/FlaskSqlAlchemy.qll b/python/ql/lib/semmle/python/frameworks/FlaskSqlAlchemy.qll index 6269baee9d5..535e32426b2 100644 --- a/python/ql/lib/semmle/python/frameworks/FlaskSqlAlchemy.qll +++ b/python/ql/lib/semmle/python/frameworks/FlaskSqlAlchemy.qll @@ -35,7 +35,7 @@ private module FlaskSqlAlchemy { /** Access on a DB resulting in an Engine */ private class DbEngine extends SqlAlchemy::Engine::InstanceSource { DbEngine() { - this = dbInstance().getMember("engine").getAnImmediateUse() + this = dbInstance().getMember("engine").asSource() or this = dbInstance().getMember("get_engine").getACall() } @@ -44,7 +44,7 @@ private module FlaskSqlAlchemy { /** Access on a DB resulting in a Session */ private class DbSession extends SqlAlchemy::Session::InstanceSource { DbSession() { - this = dbInstance().getMember("session").getAnImmediateUse() + this = dbInstance().getMember("session").asSource() or this = dbInstance().getMember("create_session").getReturn().getACall() or diff --git a/python/ql/lib/semmle/python/frameworks/RestFramework.qll b/python/ql/lib/semmle/python/frameworks/RestFramework.qll index 61f031576de..c8f73f909c2 100644 --- a/python/ql/lib/semmle/python/frameworks/RestFramework.qll +++ b/python/ql/lib/semmle/python/frameworks/RestFramework.qll @@ -115,7 +115,7 @@ private module RestFramework { */ class RestFrameworkApiViewClass extends PrivateDjango::DjangoViewClassFromSuperClass { RestFrameworkApiViewClass() { - this.getParent() = any(ModeledApiViewClasses c).getASubclass*().getAnImmediateUse().asExpr() + this.getParent() = any(ModeledApiViewClasses c).getASubclass*().asSource().asExpr() } override Function getARequestHandler() { diff --git a/python/ql/lib/semmle/python/frameworks/Stdlib.qll b/python/ql/lib/semmle/python/frameworks/Stdlib.qll index bb1b6073d16..b0629d94b2c 100644 --- a/python/ql/lib/semmle/python/frameworks/Stdlib.qll +++ b/python/ql/lib/semmle/python/frameworks/Stdlib.qll @@ -274,7 +274,7 @@ module Stdlib { ClassInstantiation() { this = subclassRef().getACall() or - this = API::moduleImport("logging").getMember("root").getAnImmediateUse() + this = API::moduleImport("logging").getMember("root").asSource() or this = API::moduleImport("logging").getMember("getLogger").getACall() } @@ -1767,11 +1767,11 @@ private module StdlibPrivate { or nodeFrom.asCfgNode() = nodeTo.asCfgNode().(CallNode).getFunction() and ( - nodeFrom = getvalueRef().getAUse() and nodeTo = getvalueResult().getAnImmediateUse() + nodeFrom = getvalueRef().getAUse() and nodeTo = getvalueResult().asSource() or - nodeFrom = getfirstRef().getAUse() and nodeTo = getfirstResult().getAnImmediateUse() + nodeFrom = getfirstRef().getAUse() and nodeTo = getfirstResult().asSource() or - nodeFrom = getlistRef().getAUse() and nodeTo = getlistResult().getAnImmediateUse() + nodeFrom = getlistRef().getAUse() and nodeTo = getlistResult().asSource() ) or // Indexing @@ -1939,7 +1939,7 @@ private module StdlibPrivate { /** A HttpRequestHandler class definition (most likely in project code). */ class HttpRequestHandlerClassDef extends Class { - HttpRequestHandlerClassDef() { this.getParent() = subclassRef().getAnImmediateUse().asExpr() } + HttpRequestHandlerClassDef() { this.getParent() = subclassRef().asSource().asExpr() } } /** DEPRECATED: Alias for HttpRequestHandlerClassDef */ @@ -2037,7 +2037,7 @@ private module StdlibPrivate { .getMember("simple_server") .getMember("WSGIServer") .getASubclass*() - .getAnImmediateUse() + .asSource() .asExpr() } } diff --git a/python/ql/lib/semmle/python/frameworks/Tornado.qll b/python/ql/lib/semmle/python/frameworks/Tornado.qll index 9c604afc1ec..d66da17aa5d 100644 --- a/python/ql/lib/semmle/python/frameworks/Tornado.qll +++ b/python/ql/lib/semmle/python/frameworks/Tornado.qll @@ -92,7 +92,7 @@ private module Tornado { /** A RequestHandler class (most likely in project code). */ class RequestHandlerClass extends Class { - RequestHandlerClass() { this.getParent() = subclassRef().getAnImmediateUse().asExpr() } + RequestHandlerClass() { this.getParent() = subclassRef().asSource().asExpr() } /** Gets a function that could handle incoming requests, if any. */ Function getARequestHandler() { diff --git a/python/ql/lib/semmle/python/frameworks/Twisted.qll b/python/ql/lib/semmle/python/frameworks/Twisted.qll index 513f5c942d0..c316321555e 100644 --- a/python/ql/lib/semmle/python/frameworks/Twisted.qll +++ b/python/ql/lib/semmle/python/frameworks/Twisted.qll @@ -33,7 +33,7 @@ private module Twisted { .getMember("resource") .getMember("Resource") .getASubclass*() - .getAnImmediateUse() + .asSource() .asExpr() } diff --git a/python/ql/lib/semmle/python/frameworks/data/ModelsAsData.qll b/python/ql/lib/semmle/python/frameworks/data/ModelsAsData.qll index e80e9f7ad0b..f8d7ae75ad0 100644 --- a/python/ql/lib/semmle/python/frameworks/data/ModelsAsData.qll +++ b/python/ql/lib/semmle/python/frameworks/data/ModelsAsData.qll @@ -23,7 +23,7 @@ private import semmle.python.dataflow.new.TaintTracking * A remote flow source originating from a CSV source row. */ private class RemoteFlowSourceFromCsv extends RemoteFlowSource { - RemoteFlowSourceFromCsv() { this = ModelOutput::getASourceNode("remote").getAnImmediateUse() } + RemoteFlowSourceFromCsv() { this = ModelOutput::getASourceNode("remote").asSource() } override string getSourceType() { result = "Remote flow (from model)" } } @@ -35,7 +35,7 @@ private predicate summaryStepNodes(DataFlow::Node pred, DataFlow::Node succ, str exists(API::Node predNode, API::Node succNode | Specific::summaryStep(predNode, succNode, kind) and pred = predNode.asSink() and - succ = succNode.getAnImmediateUse() + succ = succNode.asSource() ) } diff --git a/python/ql/lib/semmle/python/frameworks/internal/SubclassFinder.qll b/python/ql/lib/semmle/python/frameworks/internal/SubclassFinder.qll index 8bef349f417..100d8165b51 100644 --- a/python/ql/lib/semmle/python/frameworks/internal/SubclassFinder.qll +++ b/python/ql/lib/semmle/python/frameworks/internal/SubclassFinder.qll @@ -204,7 +204,7 @@ private module NotExposed { FindSubclassesSpec spec, string newSubclassQualified, ClassExpr classExpr, Module mod, Location loc ) { - classExpr = newOrExistingModeling(spec).getASubclass*().getAnImmediateUse().asExpr() and + classExpr = newOrExistingModeling(spec).getASubclass*().asSource().asExpr() and classExpr.getScope() = mod and newSubclassQualified = mod.getName() + "." + classExpr.getName() and loc = classExpr.getLocation() and diff --git a/python/ql/lib/semmle/python/regex.qll b/python/ql/lib/semmle/python/regex.qll index b72adba1716..5431d5df320 100644 --- a/python/ql/lib/semmle/python/regex.qll +++ b/python/ql/lib/semmle/python/regex.qll @@ -75,7 +75,7 @@ private string canonical_name(API::Node flag) { */ private DataFlow::TypeTrackingNode re_flag_tracker(string flag_name, DataFlow::TypeTracker t) { t.start() and - exists(API::Node flag | flag_name = canonical_name(flag) and result = flag.getAnImmediateUse()) + exists(API::Node flag | flag_name = canonical_name(flag) and result = flag.asSource()) or exists(BinaryExprNode binop, DataFlow::Node operand | operand.getALocalSource() = re_flag_tracker(flag_name, t.continue()) and diff --git a/python/ql/test/experimental/dataflow/typetracking/moduleattr.ql b/python/ql/test/experimental/dataflow/typetracking/moduleattr.ql index 91375904043..74f5f259319 100644 --- a/python/ql/test/experimental/dataflow/typetracking/moduleattr.ql +++ b/python/ql/test/experimental/dataflow/typetracking/moduleattr.ql @@ -5,7 +5,7 @@ import semmle.python.ApiGraphs private DataFlow::TypeTrackingNode module_tracker(TypeTracker t) { t.start() and - result = API::moduleImport("module").getAnImmediateUse() + result = API::moduleImport("module").asSource() or exists(TypeTracker t2 | result = module_tracker(t2).track(t2, t)) } diff --git a/python/ql/test/experimental/dataflow/typetracking/tracked.ql b/python/ql/test/experimental/dataflow/typetracking/tracked.ql index 142e5b11639..c35775d0046 100644 --- a/python/ql/test/experimental/dataflow/typetracking/tracked.ql +++ b/python/ql/test/experimental/dataflow/typetracking/tracked.ql @@ -120,7 +120,7 @@ class TrackedSelfTest extends InlineExpectationsTest { /** Gets a reference to `foo` (fictive module). */ private DataFlow::TypeTrackingNode foo(DataFlow::TypeTracker t) { t.start() and - result = API::moduleImport("foo").getAnImmediateUse() + result = API::moduleImport("foo").asSource() or exists(DataFlow::TypeTracker t2 | result = foo(t2).track(t2, t)) } @@ -131,7 +131,7 @@ DataFlow::Node foo() { foo(DataFlow::TypeTracker::end()).flowsTo(result) } /** Gets a reference to `foo.bar` (fictive module). */ private DataFlow::TypeTrackingNode foo_bar(DataFlow::TypeTracker t) { t.start() and - result = API::moduleImport("foo").getMember("bar").getAnImmediateUse() + result = API::moduleImport("foo").getMember("bar").asSource() or t.startInAttr("bar") and result = foo() @@ -145,7 +145,7 @@ DataFlow::Node foo_bar() { foo_bar(DataFlow::TypeTracker::end()).flowsTo(result) /** Gets a reference to `foo.bar.baz` (fictive attribute on `foo.bar` module). */ private DataFlow::TypeTrackingNode foo_bar_baz(DataFlow::TypeTracker t) { t.start() and - result = API::moduleImport("foo").getMember("bar").getMember("baz").getAnImmediateUse() + result = API::moduleImport("foo").getMember("bar").getMember("baz").asSource() or t.startInAttr("baz") and result = foo_bar() diff --git a/python/ql/test/experimental/meta/MaDTest.qll b/python/ql/test/experimental/meta/MaDTest.qll index 92b8afef422..a4b5877f5ea 100644 --- a/python/ql/test/experimental/meta/MaDTest.qll +++ b/python/ql/test/experimental/meta/MaDTest.qll @@ -38,7 +38,7 @@ class MadSourceTest extends InlineExpectationsTest { override predicate hasActualResult(Location location, string element, string tag, string value) { exists(location.getFile().getRelativePath()) and exists(DataFlow::Node source, string kind | - source = ModelOutput::getASourceNode(kind).getAnImmediateUse() and + source = ModelOutput::getASourceNode(kind).asSource() and location = source.getLocation() and element = source.toString() and value = prettyNodeForInlineTest(source) and diff --git a/python/ql/test/library-tests/frameworks/data/test.ql b/python/ql/test/library-tests/frameworks/data/test.ql index c1bc85eaf49..cdd1782052a 100644 --- a/python/ql/test/library-tests/frameworks/data/test.ql +++ b/python/ql/test/library-tests/frameworks/data/test.ql @@ -87,7 +87,7 @@ class BasicTaintTracking extends TaintTracking::Configuration { BasicTaintTracking() { this = "BasicTaintTracking" } override predicate isSource(DataFlow::Node source) { - source = ModelOutput::getASourceNode("test-source").getAnImmediateUse() + source = ModelOutput::getASourceNode("test-source").asSource() } override predicate isSink(DataFlow::Node sink) { @@ -104,7 +104,7 @@ query predicate isSink(DataFlow::Node node, string kind) { } query predicate isSource(DataFlow::Node node, string kind) { - node = ModelOutput::getASourceNode(kind).getAnImmediateUse() + node = ModelOutput::getASourceNode(kind).asSource() } class SyntaxErrorTest extends ModelInput::SinkModelCsv { From b096f9ec72064fd68a2f850b4d67737d5bc69145 Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 13 Jun 2022 10:02:14 +0200 Subject: [PATCH 029/246] Python: Rename getAUse -> getAValueReachableFromSource --- python/ql/lib/semmle/python/ApiGraphs.qll | 2 +- .../lib/semmle/python/frameworks/Aiohttp.qll | 3 +- .../semmle/python/frameworks/Cryptodome.qll | 2 +- .../semmle/python/frameworks/Cryptography.qll | 12 +++---- .../lib/semmle/python/frameworks/Django.qll | 2 +- .../lib/semmle/python/frameworks/Fabric.qll | 2 +- .../lib/semmle/python/frameworks/FastApi.qll | 16 ++++++---- .../ql/lib/semmle/python/frameworks/Flask.qll | 11 ++++--- .../lib/semmle/python/frameworks/Invoke.qll | 3 +- .../semmle/python/frameworks/RuamelYaml.qll | 2 +- .../lib/semmle/python/frameworks/Stdlib.qll | 32 +++++++++++-------- .../lib/semmle/python/frameworks/Werkzeug.qll | 6 ++-- .../ql/lib/semmle/python/frameworks/Yaml.qll | 2 +- .../python/frameworks/internal/PEP249Impl.qll | 4 ++- .../frameworks/internal/SubclassFinder.qll | 4 +-- .../semmle/python/internal/CachedStages.qll | 2 +- .../CWE-295/MissingHostKeyValidation.ql | 4 +-- python/ql/src/Security/CWE-327/PyOpenSSL.qll | 10 +++--- python/ql/src/Security/CWE-327/Ssl.qll | 25 ++++++++++++--- .../semmle/python/frameworks/Django.qll | 6 ++-- .../semmle/python/frameworks/Flask.qll | 6 +++- .../semmle/python/frameworks/LDAP.qll | 4 ++- .../semmle/python/frameworks/NoSQL.qll | 4 +-- .../ql/test/TestUtilities/VerifyApiGraphs.qll | 2 +- .../test/library-tests/ApiGraphs/py2/use.ql | 2 +- 25 files changed, 103 insertions(+), 65 deletions(-) diff --git a/python/ql/lib/semmle/python/ApiGraphs.qll b/python/ql/lib/semmle/python/ApiGraphs.qll index 61a42f812a7..538d4fe8818 100644 --- a/python/ql/lib/semmle/python/ApiGraphs.qll +++ b/python/ql/lib/semmle/python/ApiGraphs.qll @@ -106,7 +106,7 @@ module API { * ``` * both `obj.foo` and `x` are uses of the `foo` member from `obj`. */ - DataFlow::Node getAUse() { + DataFlow::Node getAValueReachableFromSource() { exists(DataFlow::LocalSourceNode src | Impl::use(this, src) | Impl::trackUseNode(src).flowsTo(result) ) diff --git a/python/ql/lib/semmle/python/frameworks/Aiohttp.qll b/python/ql/lib/semmle/python/frameworks/Aiohttp.qll index 733845de30b..b78feb217c6 100644 --- a/python/ql/lib/semmle/python/frameworks/Aiohttp.qll +++ b/python/ql/lib/semmle/python/frameworks/Aiohttp.qll @@ -626,7 +626,8 @@ module AiohttpWebModel { // and just go with the LHS this.asCfgNode() = subscript | - subscript.getObject() = aiohttpResponseInstance().getMember("cookies").getAUse().asCfgNode() and + subscript.getObject() = + aiohttpResponseInstance().getMember("cookies").getAValueReachableFromSource().asCfgNode() and value.asCfgNode() = subscript.(DefinitionNode).getValue() and index.asCfgNode() = subscript.getIndex() ) diff --git a/python/ql/lib/semmle/python/frameworks/Cryptodome.qll b/python/ql/lib/semmle/python/frameworks/Cryptodome.qll index 8d1e47d0ff3..0c3e8968c23 100644 --- a/python/ql/lib/semmle/python/frameworks/Cryptodome.qll +++ b/python/ql/lib/semmle/python/frameworks/Cryptodome.qll @@ -164,7 +164,7 @@ private module CryptodomeModel { .getMember("Cipher") .getMember(cipherName) .getMember(modeName) - .getAUse() + .getAValueReachableFromSource() | result = modeName.splitAt("_", 1) ) diff --git a/python/ql/lib/semmle/python/frameworks/Cryptography.qll b/python/ql/lib/semmle/python/frameworks/Cryptography.qll index 1520f17e883..021da3ef0a0 100644 --- a/python/ql/lib/semmle/python/frameworks/Cryptography.qll +++ b/python/ql/lib/semmle/python/frameworks/Cryptography.qll @@ -145,9 +145,9 @@ private module CryptographyModel { override int getKeySizeWithOrigin(DataFlow::Node origin) { exists(API::Node n | n = Ecc::predefinedCurveClass(result) and origin = n.asSource() | - this.getCurveArg() = n.getAUse() + this.getCurveArg() = n.getAValueReachableFromSource() or - this.getCurveArg() = n.getReturn().getAUse() + this.getCurveArg() = n.getReturn().getAValueReachableFromSource() ) } @@ -189,12 +189,12 @@ private module CryptographyModel { .getMember("ciphers") .getMember("Cipher") .getACall() and - algorithmClassRef(algorithmName).getReturn().getAUse() in [ + algorithmClassRef(algorithmName).getReturn().getAValueReachableFromSource() in [ call.getArg(0), call.getArgByName("algorithm") ] and exists(DataFlow::Node modeArg | modeArg in [call.getArg(1), call.getArgByName("mode")] | - if modeArg = modeClassRef(_).getReturn().getAUse() - then modeArg = modeClassRef(modeName).getReturn().getAUse() + if modeArg = modeClassRef(_).getReturn().getAValueReachableFromSource() + then modeArg = modeClassRef(modeName).getReturn().getAValueReachableFromSource() else modeName = "" ) ) @@ -252,7 +252,7 @@ private module CryptographyModel { .getMember("hashes") .getMember("Hash") .getACall() and - algorithmClassRef(algorithmName).getReturn().getAUse() in [ + algorithmClassRef(algorithmName).getReturn().getAValueReachableFromSource() in [ call.getArg(0), call.getArgByName("algorithm") ] ) diff --git a/python/ql/lib/semmle/python/frameworks/Django.qll b/python/ql/lib/semmle/python/frameworks/Django.qll index a955de7a01a..a430513cb62 100644 --- a/python/ql/lib/semmle/python/frameworks/Django.qll +++ b/python/ql/lib/semmle/python/frameworks/Django.qll @@ -2799,7 +2799,7 @@ module PrivateDjango { .getMember("decorators") .getMember("csrf") .getMember(decoratorName) - .getAUse() and + .getAValueReachableFromSource() and this.asExpr() = function.getADecorator() } diff --git a/python/ql/lib/semmle/python/frameworks/Fabric.qll b/python/ql/lib/semmle/python/frameworks/Fabric.qll index bb1500965f4..5fd9d2afe18 100644 --- a/python/ql/lib/semmle/python/frameworks/Fabric.qll +++ b/python/ql/lib/semmle/python/frameworks/Fabric.qll @@ -179,7 +179,7 @@ private module FabricV2 { DataFlow::ParameterNode { FabricTaskFirstParamConnectionInstance() { exists(Function func | - func.getADecorator() = Fabric::Tasks::task().getAUse().asExpr() and + func.getADecorator() = Fabric::Tasks::task().getAValueReachableFromSource().asExpr() and this.getParameter() = func.getArg(0) ) } diff --git a/python/ql/lib/semmle/python/frameworks/FastApi.qll b/python/ql/lib/semmle/python/frameworks/FastApi.qll index 3a683108f4c..f042dafea59 100644 --- a/python/ql/lib/semmle/python/frameworks/FastApi.qll +++ b/python/ql/lib/semmle/python/frameworks/FastApi.qll @@ -90,7 +90,8 @@ private module FastApi { private class PydanticModelRequestHandlerParam extends Pydantic::BaseModel::InstanceSource, DataFlow::ParameterNode { PydanticModelRequestHandlerParam() { - this.getParameter().getAnnotation() = Pydantic::BaseModel::subclassRef().getAUse().asExpr() and + this.getParameter().getAnnotation() = + Pydantic::BaseModel::subclassRef().getAValueReachableFromSource().asExpr() and any(FastApiRouteSetup rs).getARequestHandler().getArgByName(_) = this.getParameter() } } @@ -104,7 +105,8 @@ private module FastApi { private class WebSocketRequestHandlerParam extends Starlette::WebSocket::InstanceSource, DataFlow::ParameterNode { WebSocketRequestHandlerParam() { - this.getParameter().getAnnotation() = Starlette::WebSocket::classRef().getAUse().asExpr() and + this.getParameter().getAnnotation() = + Starlette::WebSocket::classRef().getAValueReachableFromSource().asExpr() and any(FastApiRouteSetup rs).getARequestHandler().getArgByName(_) = this.getParameter() } } @@ -165,7 +167,7 @@ private module FastApi { // user-defined subclasses exists(Class cls, API::Node base | base = getModeledResponseClass(_).getASubclass*() and - cls.getABase() = base.getAUse().asExpr() and + cls.getABase() = base.getAValueReachableFromSource().asExpr() and responseClass.asSource().asExpr() = cls.getParent() | exists(Assign assign | assign = cls.getAStmt() | @@ -257,7 +259,7 @@ private module FastApi { override string getMimetypeDefault() { exists(API::Node responseClass | - responseClass.getAUse() = routeSetup.getResponseClassArg() and + responseClass.getAValueReachableFromSource() = routeSetup.getResponseClassArg() and result = getDefaultMimeType(responseClass) ) or @@ -274,7 +276,7 @@ private module FastApi { FileSystemAccess::Range { FastApiRequestHandlerFileResponseReturn() { exists(API::Node responseClass | - responseClass.getAUse() = routeSetup.getResponseClassArg() and + responseClass.getAValueReachableFromSource() = routeSetup.getResponseClassArg() and responseClass = getModeledResponseClass("FileResponse").getASubclass*() ) } @@ -292,7 +294,7 @@ private module FastApi { HTTP::Server::HttpRedirectResponse::Range { FastApiRequestHandlerRedirectReturn() { exists(API::Node responseClass | - responseClass.getAUse() = routeSetup.getResponseClassArg() and + responseClass.getAValueReachableFromSource() = routeSetup.getResponseClassArg() and responseClass = getModeledResponseClass("RedirectResponse").getASubclass*() ) } @@ -311,7 +313,7 @@ private module FastApi { class RequestHandlerParam extends InstanceSource, DataFlow::ParameterNode { RequestHandlerParam() { this.getParameter().getAnnotation() = - getModeledResponseClass(_).getASubclass*().getAUse().asExpr() and + getModeledResponseClass(_).getASubclass*().getAValueReachableFromSource().asExpr() and any(FastApiRouteSetup rs).getARequestHandler().getArgByName(_) = this.getParameter() } } diff --git a/python/ql/lib/semmle/python/frameworks/Flask.qll b/python/ql/lib/semmle/python/frameworks/Flask.qll index 8df9295c285..8ee93fcec52 100644 --- a/python/ql/lib/semmle/python/frameworks/Flask.qll +++ b/python/ql/lib/semmle/python/frameworks/Flask.qll @@ -305,7 +305,7 @@ module Flask { ) or exists(FlaskViewClass vc | - this.getViewArg() = vc.asViewResult().getAUse() and + this.getViewArg() = vc.asViewResult().getAValueReachableFromSource() and result = vc.getARequestHandler() ) } @@ -339,7 +339,7 @@ module Flask { */ private class FlaskRequestSource extends RemoteFlowSource::Range { FlaskRequestSource() { - this = request().getAUse() and + this = request().getAValueReachableFromSource() and not any(Import imp).contains(this.asExpr()) and not exists(ControlFlowNode def | this.asVar().getSourceVariable().hasDefiningNode(def) | any(Import imp).contains(def.getNode()) @@ -357,7 +357,7 @@ module Flask { private class InstanceTaintSteps extends InstanceTaintStepsHelper { InstanceTaintSteps() { this = "flask.Request" } - override DataFlow::Node getInstance() { result = request().getAUse() } + override DataFlow::Node getInstance() { result = request().getAValueReachableFromSource() } override string getAttributeName() { result in [ @@ -415,12 +415,13 @@ module Flask { // be able to do something more structured for providing modeling of the members // of a container-object. exists(API::Node files | files = request().getMember("files") | - this.asCfgNode().(SubscriptNode).getObject() = files.getAUse().asCfgNode() + this.asCfgNode().(SubscriptNode).getObject() = + files.getAValueReachableFromSource().asCfgNode() or this = files.getMember("get").getACall() or this.asCfgNode().(SubscriptNode).getObject() = - files.getMember("getlist").getReturn().getAUse().asCfgNode() + files.getMember("getlist").getReturn().getAValueReachableFromSource().asCfgNode() ) } } diff --git a/python/ql/lib/semmle/python/frameworks/Invoke.qll b/python/ql/lib/semmle/python/frameworks/Invoke.qll index 435536dda9f..b1ceb078907 100644 --- a/python/ql/lib/semmle/python/frameworks/Invoke.qll +++ b/python/ql/lib/semmle/python/frameworks/Invoke.qll @@ -39,7 +39,8 @@ private module Invoke { result = InvokeModule::Context::ContextClass::classRef().getACall() or exists(Function func | - func.getADecorator() = invoke().getMember("task").getAUse().asExpr() and + func.getADecorator() = + invoke().getMember("task").getAValueReachableFromSource().asExpr() and result.(DataFlow::ParameterNode).getParameter() = func.getArg(0) ) ) diff --git a/python/ql/lib/semmle/python/frameworks/RuamelYaml.qll b/python/ql/lib/semmle/python/frameworks/RuamelYaml.qll index 2d553c409b0..d9ba39814eb 100644 --- a/python/ql/lib/semmle/python/frameworks/RuamelYaml.qll +++ b/python/ql/lib/semmle/python/frameworks/RuamelYaml.qll @@ -44,7 +44,7 @@ private module RuamelYaml { API::moduleImport("ruamel") .getMember("yaml") .getMember(["SafeLoader", "BaseLoader", "CSafeLoader", "CBaseLoader"]) - .getAUse() + .getAValueReachableFromSource() ) } diff --git a/python/ql/lib/semmle/python/frameworks/Stdlib.qll b/python/ql/lib/semmle/python/frameworks/Stdlib.qll index b0629d94b2c..74f80b1d478 100644 --- a/python/ql/lib/semmle/python/frameworks/Stdlib.qll +++ b/python/ql/lib/semmle/python/frameworks/Stdlib.qll @@ -1727,15 +1727,16 @@ private module StdlibPrivate { private DataFlow::TypeTrackingNode fieldList(DataFlow::TypeTracker t) { t.start() and // TODO: Should have better handling of subscripting - result.asCfgNode().(SubscriptNode).getObject() = instance().getAUse().asCfgNode() + result.asCfgNode().(SubscriptNode).getObject() = + instance().getAValueReachableFromSource().asCfgNode() or exists(DataFlow::TypeTracker t2 | result = fieldList(t2).track(t2, t)) } /** Gets a reference to a list of fields. */ DataFlow::Node fieldList() { - result = getlistResult().getAUse() or - result = getvalueResult().getAUse() or + result = getlistResult().getAValueReachableFromSource() or + result = getvalueResult().getAValueReachableFromSource() or fieldList(DataFlow::TypeTracker::end()).flowsTo(result) } @@ -1744,16 +1745,16 @@ private module StdlibPrivate { t.start() and // TODO: Should have better handling of subscripting result.asCfgNode().(SubscriptNode).getObject() = - [instance().getAUse(), fieldList()].asCfgNode() + [instance().getAValueReachableFromSource(), fieldList()].asCfgNode() or exists(DataFlow::TypeTracker t2 | result = field(t2).track(t2, t)) } /** Gets a reference to a field. */ DataFlow::Node field() { - result = getfirstResult().getAUse() + result = getfirstResult().getAValueReachableFromSource() or - result = getvalueResult().getAUse() + result = getvalueResult().getAValueReachableFromSource() or field(DataFlow::TypeTracker::end()).flowsTo(result) } @@ -1762,20 +1763,23 @@ private module StdlibPrivate { override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { // Methods nodeFrom = nodeTo.(DataFlow::AttrRead).getObject() and - nodeFrom = instance().getAUse() and - nodeTo = [getvalueRef(), getfirstRef(), getlistRef()].getAUse() + nodeFrom = instance().getAValueReachableFromSource() and + nodeTo = [getvalueRef(), getfirstRef(), getlistRef()].getAValueReachableFromSource() or nodeFrom.asCfgNode() = nodeTo.asCfgNode().(CallNode).getFunction() and ( - nodeFrom = getvalueRef().getAUse() and nodeTo = getvalueResult().asSource() + nodeFrom = getvalueRef().getAValueReachableFromSource() and + nodeTo = getvalueResult().asSource() or - nodeFrom = getfirstRef().getAUse() and nodeTo = getfirstResult().asSource() + nodeFrom = getfirstRef().getAValueReachableFromSource() and + nodeTo = getfirstResult().asSource() or - nodeFrom = getlistRef().getAUse() and nodeTo = getlistResult().asSource() + nodeFrom = getlistRef().getAValueReachableFromSource() and + nodeTo = getlistResult().asSource() ) or // Indexing - nodeFrom in [instance().getAUse(), fieldList()] and + nodeFrom in [instance().getAValueReachableFromSource(), fieldList()] and nodeTo.asCfgNode().(SubscriptNode).getObject() = nodeFrom.asCfgNode() or // Attributes on Field @@ -3438,7 +3442,7 @@ private module StdlibPrivate { .getMember("sax") .getMember("handler") .getMember("feature_external_ges") - .getAUse() and + .getAValueReachableFromSource() and call.getStateArg().getAValueReachingRhs().asExpr().(BooleanLiteral).booleanValue() = true and result = call.getObject() ) @@ -3454,7 +3458,7 @@ private module StdlibPrivate { .getMember("sax") .getMember("handler") .getMember("feature_external_ges") - .getAUse() and + .getAValueReachableFromSource() and call.getStateArg().getAValueReachingRhs().asExpr().(BooleanLiteral).booleanValue() = false ) } diff --git a/python/ql/lib/semmle/python/frameworks/Werkzeug.qll b/python/ql/lib/semmle/python/frameworks/Werkzeug.qll index e9e3f257871..5e318f59e73 100644 --- a/python/ql/lib/semmle/python/frameworks/Werkzeug.qll +++ b/python/ql/lib/semmle/python/frameworks/Werkzeug.qll @@ -285,7 +285,7 @@ private module WerkzeugOld { * See https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.Headers.getlist */ deprecated DataFlow::Node getlist() { - result = any(InstanceSourceApiNode a).getMember("getlist").getAUse() + result = any(InstanceSourceApiNode a).getMember("getlist").getAValueReachableFromSource() } private class MultiDictAdditionalTaintStep extends TaintTracking::AdditionalTaintStep { @@ -331,7 +331,9 @@ private module WerkzeugOld { abstract deprecated class InstanceSourceApiNode extends API::Node { } /** Gets a reference to an instance of `werkzeug.datastructures.FileStorage`. */ - deprecated DataFlow::Node instance() { result = any(InstanceSourceApiNode a).getAUse() } + deprecated DataFlow::Node instance() { + result = any(InstanceSourceApiNode a).getAValueReachableFromSource() + } private class FileStorageAdditionalTaintStep extends TaintTracking::AdditionalTaintStep { override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { diff --git a/python/ql/lib/semmle/python/frameworks/Yaml.qll b/python/ql/lib/semmle/python/frameworks/Yaml.qll index 07a98ec2ba3..670fad75e6e 100644 --- a/python/ql/lib/semmle/python/frameworks/Yaml.qll +++ b/python/ql/lib/semmle/python/frameworks/Yaml.qll @@ -64,7 +64,7 @@ private module Yaml { loader_arg = API::moduleImport("yaml") .getMember(["SafeLoader", "BaseLoader", "CSafeLoader", "CBaseLoader"]) - .getAUse() + .getAValueReachableFromSource() ) } diff --git a/python/ql/lib/semmle/python/frameworks/internal/PEP249Impl.qll b/python/ql/lib/semmle/python/frameworks/internal/PEP249Impl.qll index 57e7131c2b7..5feaa7f61da 100644 --- a/python/ql/lib/semmle/python/frameworks/internal/PEP249Impl.qll +++ b/python/ql/lib/semmle/python/frameworks/internal/PEP249Impl.qll @@ -33,7 +33,9 @@ module PEP249 { } /** Gets a reference to the `connect` function of a module that implements PEP 249. */ - DataFlow::Node connect() { result = any(PEP249ModuleApiNode a).getMember("connect").getAUse() } + DataFlow::Node connect() { + result = any(PEP249ModuleApiNode a).getMember("connect").getAValueReachableFromSource() + } /** * Provides models for database connections (following PEP 249). diff --git a/python/ql/lib/semmle/python/frameworks/internal/SubclassFinder.qll b/python/ql/lib/semmle/python/frameworks/internal/SubclassFinder.qll index 100d8165b51..47521ac0b31 100644 --- a/python/ql/lib/semmle/python/frameworks/internal/SubclassFinder.qll +++ b/python/ql/lib/semmle/python/frameworks/internal/SubclassFinder.qll @@ -152,7 +152,7 @@ private module NotExposed { FindSubclassesSpec spec, string newAliasFullyQualified, ImportMember importMember, Module mod, Location loc ) { - importMember = newOrExistingModeling(spec).getAUse().asExpr() and + importMember = newOrExistingModeling(spec).getAValueReachableFromSource().asExpr() and importMember.getScope() = mod and loc = importMember.getLocation() and ( @@ -182,7 +182,7 @@ private module NotExposed { // WHAT A HACK :D :D relevantClass.getPath() = relevantClass.getAPredecessor().getPath() + ".getMember(\"" + relevantName + "\")" and - relevantClass.getAPredecessor().getAUse().asExpr() = importStar.getModule() and + relevantClass.getAPredecessor().getAValueReachableFromSource().asExpr() = importStar.getModule() and ( mod.isPackageInit() and newAliasFullyQualified = mod.getPackageName() + "." + relevantName diff --git a/python/ql/lib/semmle/python/internal/CachedStages.qll b/python/ql/lib/semmle/python/internal/CachedStages.qll index ad4fe98ccee..290a90f5a73 100644 --- a/python/ql/lib/semmle/python/internal/CachedStages.qll +++ b/python/ql/lib/semmle/python/internal/CachedStages.qll @@ -121,7 +121,7 @@ module Stages { or exists(any(NewDataFlow::TypeTracker t).append(_)) or - exists(any(API::Node n).getAMember().getAUse()) + exists(any(API::Node n).getAMember().getAValueReachableFromSource()) } } diff --git a/python/ql/src/Security/CWE-295/MissingHostKeyValidation.ql b/python/ql/src/Security/CWE-295/MissingHostKeyValidation.ql index 89548d714ce..713354c84a0 100644 --- a/python/ql/src/Security/CWE-295/MissingHostKeyValidation.ql +++ b/python/ql/src/Security/CWE-295/MissingHostKeyValidation.ql @@ -29,7 +29,7 @@ where call = paramikoSSHClientInstance().getMember("set_missing_host_key_policy").getACall() and arg in [call.getArg(0), call.getArgByName("policy")] and ( - arg = unsafe_paramiko_policy(name).getAUse() or - arg = unsafe_paramiko_policy(name).getReturn().getAUse() + arg = unsafe_paramiko_policy(name).getAValueReachableFromSource() or + arg = unsafe_paramiko_policy(name).getReturn().getAValueReachableFromSource() ) select call, "Setting missing host key policy to " + name + " may be unsafe." diff --git a/python/ql/src/Security/CWE-327/PyOpenSSL.qll b/python/ql/src/Security/CWE-327/PyOpenSSL.qll index a0008cdc7c1..7f7b9184570 100644 --- a/python/ql/src/Security/CWE-327/PyOpenSSL.qll +++ b/python/ql/src/Security/CWE-327/PyOpenSSL.qll @@ -17,7 +17,8 @@ class PyOpenSSLContextCreation extends ContextCreation, DataFlow::CallCfgNode { protocolArg in [this.getArg(0), this.getArgByName("method")] | protocolArg in [ - pyo.specific_version(result).getAUse(), pyo.unspecific_version(result).getAUse() + pyo.specific_version(result).getAValueReachableFromSource(), + pyo.unspecific_version(result).getAValueReachableFromSource() ] ) } @@ -43,9 +44,10 @@ class SetOptionsCall extends ProtocolRestriction, DataFlow::CallCfgNode { } override ProtocolVersion getRestriction() { - API::moduleImport("OpenSSL").getMember("SSL").getMember("OP_NO_" + result).getAUse() in [ - this.getArg(0), this.getArgByName("options") - ] + API::moduleImport("OpenSSL") + .getMember("SSL") + .getMember("OP_NO_" + result) + .getAValueReachableFromSource() in [this.getArg(0), this.getArgByName("options")] } } diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll index 80ef32c8de6..d1122f82ed9 100644 --- a/python/ql/src/Security/CWE-327/Ssl.qll +++ b/python/ql/src/Security/CWE-327/Ssl.qll @@ -15,7 +15,10 @@ class SSLContextCreation extends ContextCreation, DataFlow::CallCfgNode { protocolArg in [this.getArg(0), this.getArgByName("protocol")] | protocolArg = - [ssl.specific_version(result).getAUse(), ssl.unspecific_version(result).getAUse()] + [ + ssl.specific_version(result).getAValueReachableFromSource(), + ssl.unspecific_version(result).getAValueReachableFromSource() + ] ) or not exists(this.getArg(_)) and @@ -54,7 +57,11 @@ class OptionsAugOr extends ProtocolRestriction, DataFlow::CfgNode { aa.getTarget() = attr.getNode() and attr.getName() = "options" and attr.getObject() = node and - flag = API::moduleImport("ssl").getMember("OP_NO_" + restriction).getAUse().asExpr() and + flag = + API::moduleImport("ssl") + .getMember("OP_NO_" + restriction) + .getAValueReachableFromSource() + .asExpr() and ( aa.getValue() = flag or @@ -79,7 +86,11 @@ class OptionsAugAndNot extends ProtocolUnrestriction, DataFlow::CfgNode { attr.getObject() = node and notFlag.getOp() instanceof Invert and notFlag.getOperand() = flag and - flag = API::moduleImport("ssl").getMember("OP_NO_" + restriction).getAUse().asExpr() and + flag = + API::moduleImport("ssl") + .getMember("OP_NO_" + restriction) + .getAValueReachableFromSource() + .asExpr() and ( aa.getValue() = notFlag or @@ -134,7 +145,10 @@ class ContextSetVersion extends ProtocolRestriction, ProtocolUnrestriction, Data this = aw.getObject() and aw.getAttributeName() = "minimum_version" and aw.getValue() = - API::moduleImport("ssl").getMember("TLSVersion").getMember(restriction).getAUse() + API::moduleImport("ssl") + .getMember("TLSVersion") + .getMember(restriction) + .getAValueReachableFromSource() ) } @@ -188,7 +202,8 @@ class Ssl extends TlsLibrary { override DataFlow::CallCfgNode insecure_connection_creation(ProtocolVersion version) { result = API::moduleImport("ssl").getMember("wrap_socket").getACall() and - this.specific_version(version).getAUse() = result.getArgByName("ssl_version") and + this.specific_version(version).getAValueReachableFromSource() = + result.getArgByName("ssl_version") and version.isInsecure() } diff --git a/python/ql/src/experimental/semmle/python/frameworks/Django.qll b/python/ql/src/experimental/semmle/python/frameworks/Django.qll index 6853f9c3f6a..f1895af0ea9 100644 --- a/python/ql/src/experimental/semmle/python/frameworks/Django.qll +++ b/python/ql/src/experimental/semmle/python/frameworks/Django.qll @@ -86,11 +86,13 @@ private module ExperimentalPrivateDjango { t.start() and ( exists(SubscriptNode subscript | - subscript.getObject() = baseClassRef().getReturn().getAUse().asCfgNode() and + subscript.getObject() = + baseClassRef().getReturn().getAValueReachableFromSource().asCfgNode() and result.asCfgNode() = subscript ) or - result.(DataFlow::AttrRead).getObject() = baseClassRef().getReturn().getAUse() + result.(DataFlow::AttrRead).getObject() = + baseClassRef().getReturn().getAValueReachableFromSource() ) or exists(DataFlow::TypeTracker t2 | result = headerInstance(t2).track(t2, t)) diff --git a/python/ql/src/experimental/semmle/python/frameworks/Flask.qll b/python/ql/src/experimental/semmle/python/frameworks/Flask.qll index 5444a692b26..3252acf24fd 100644 --- a/python/ql/src/experimental/semmle/python/frameworks/Flask.qll +++ b/python/ql/src/experimental/semmle/python/frameworks/Flask.qll @@ -29,7 +29,11 @@ module ExperimentalFlask { /** Gets a reference to a header instance. */ private DataFlow::LocalSourceNode headerInstance() { - result = [Flask::Response::classRef(), flaskMakeResponse()].getReturn().getAMember().getAUse() + result = + [Flask::Response::classRef(), flaskMakeResponse()] + .getReturn() + .getAMember() + .getAValueReachableFromSource() } /** Gets a reference to a header instance call/subscript */ diff --git a/python/ql/src/experimental/semmle/python/frameworks/LDAP.qll b/python/ql/src/experimental/semmle/python/frameworks/LDAP.qll index 6085dddf5f8..871b47b48c9 100644 --- a/python/ql/src/experimental/semmle/python/frameworks/LDAP.qll +++ b/python/ql/src/experimental/semmle/python/frameworks/LDAP.qll @@ -90,7 +90,9 @@ private module LDAP { /**List of SSL-demanding options */ private class LDAPSSLOptions extends DataFlow::Node { - LDAPSSLOptions() { this = ldap().getMember("OPT_X_TLS_" + ["DEMAND", "HARD"]).getAUse() } + LDAPSSLOptions() { + this = ldap().getMember("OPT_X_TLS_" + ["DEMAND", "HARD"]).getAValueReachableFromSource() + } } /** diff --git a/python/ql/src/experimental/semmle/python/frameworks/NoSQL.qll b/python/ql/src/experimental/semmle/python/frameworks/NoSQL.qll index c5efb3dd833..2d7c93c3029 100644 --- a/python/ql/src/experimental/semmle/python/frameworks/NoSQL.qll +++ b/python/ql/src/experimental/semmle/python/frameworks/NoSQL.qll @@ -50,11 +50,11 @@ private module NoSql { t.start() and ( exists(SubscriptNode subscript | - subscript.getObject() = mongoClientInstance().getAUse().asCfgNode() and + subscript.getObject() = mongoClientInstance().getAValueReachableFromSource().asCfgNode() and result.asCfgNode() = subscript ) or - result.(DataFlow::AttrRead).getObject() = mongoClientInstance().getAUse() + result.(DataFlow::AttrRead).getObject() = mongoClientInstance().getAValueReachableFromSource() or result = mongoEngine().getMember(["get_db", "connect"]).getACall() or diff --git a/python/ql/test/TestUtilities/VerifyApiGraphs.qll b/python/ql/test/TestUtilities/VerifyApiGraphs.qll index 03af20dbabe..f2d43a08867 100644 --- a/python/ql/test/TestUtilities/VerifyApiGraphs.qll +++ b/python/ql/test/TestUtilities/VerifyApiGraphs.qll @@ -24,7 +24,7 @@ private DataFlow::Node getNode(API::Node nd, string kind) { result = nd.asSink() or kind = "use" and - result = nd.getAUse() + result = nd.getAValueReachableFromSource() } private string getLocStr(Location loc) { diff --git a/python/ql/test/library-tests/ApiGraphs/py2/use.ql b/python/ql/test/library-tests/ApiGraphs/py2/use.ql index f02bb048c58..6b18b2c1dd7 100644 --- a/python/ql/test/library-tests/ApiGraphs/py2/use.ql +++ b/python/ql/test/library-tests/ApiGraphs/py2/use.ql @@ -9,7 +9,7 @@ class ApiUseTest extends InlineExpectationsTest { override string getARelevantTag() { result = "use" } private predicate relevant_node(API::Node a, DataFlow::Node n, Location l) { - n = a.getAUse() and l = n.getLocation() + n = a.getAValueReachableFromSource() and l = n.getLocation() } override predicate hasActualResult(Location location, string element, string tag, string value) { From 3a669a8d213e2e341d07b8b43cf30ef1a6aa6144 Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 13 Jun 2022 10:03:32 +0200 Subject: [PATCH 030/246] Python: getAValueReachingRhs -> getAValueReachingSink --- python/ql/lib/semmle/python/ApiGraphs.qll | 2 +- .../ql/lib/semmle/python/frameworks/Aiohttp.qll | 4 ++-- .../ql/lib/semmle/python/frameworks/Httpx.qll | 8 ++++---- python/ql/lib/semmle/python/frameworks/Lxml.qll | 17 +++++++++-------- .../lib/semmle/python/frameworks/Requests.qll | 2 +- .../ql/lib/semmle/python/frameworks/Stdlib.qll | 6 +++--- .../ql/lib/semmle/python/frameworks/Urllib3.qll | 9 +++++---- .../lib/semmle/python/frameworks/Xmltodict.qll | 2 +- .../Security/CWE-079/Jinja2WithoutEscaping.ql | 2 +- .../ql/src/Security/CWE-285/PamAuthorization.ql | 4 ++-- .../src/Security/CWE-732/WeakFilePermissions.ql | 4 ++-- 11 files changed, 31 insertions(+), 29 deletions(-) diff --git a/python/ql/lib/semmle/python/ApiGraphs.qll b/python/ql/lib/semmle/python/ApiGraphs.qll index 538d4fe8818..72f49c859ef 100644 --- a/python/ql/lib/semmle/python/ApiGraphs.qll +++ b/python/ql/lib/semmle/python/ApiGraphs.qll @@ -134,7 +134,7 @@ module API { * Gets a data-flow node that may interprocedurally flow to the right-hand side of a definition * of the API component represented by this node. */ - DataFlow::Node getAValueReachingRhs() { result = Impl::trackDefNode(this.asSink()) } + DataFlow::Node getAValueReachingSink() { result = Impl::trackDefNode(this.asSink()) } /** * Gets an immediate use of the API component represented by this node. diff --git a/python/ql/lib/semmle/python/frameworks/Aiohttp.qll b/python/ql/lib/semmle/python/frameworks/Aiohttp.qll index b78feb217c6..5f687a01b49 100644 --- a/python/ql/lib/semmle/python/frameworks/Aiohttp.qll +++ b/python/ql/lib/semmle/python/frameworks/Aiohttp.qll @@ -685,8 +685,8 @@ private module AiohttpClientModel { DataFlow::Node disablingNode, DataFlow::Node argumentOrigin ) { exists(API::Node param | param = this.getKeywordParameter(["ssl", "verify_ssl"]) | - disablingNode = param.getARhs() and - argumentOrigin = param.getAValueReachingRhs() and + disablingNode = param.asSink() and + argumentOrigin = param.getAValueReachingSink() and // aiohttp.client treats `None` as the default and all other "falsey" values as `False`. argumentOrigin.asExpr().(ImmutableLiteral).booleanValue() = false and not argumentOrigin.asExpr() instanceof None diff --git a/python/ql/lib/semmle/python/frameworks/Httpx.qll b/python/ql/lib/semmle/python/frameworks/Httpx.qll index 67dbbffbd9f..b746de6ee5e 100644 --- a/python/ql/lib/semmle/python/frameworks/Httpx.qll +++ b/python/ql/lib/semmle/python/frameworks/Httpx.qll @@ -44,8 +44,8 @@ private module HttpxModel { override predicate disablesCertificateValidation( DataFlow::Node disablingNode, DataFlow::Node argumentOrigin ) { - disablingNode = this.getKeywordParameter("verify").getARhs() and - argumentOrigin = this.getKeywordParameter("verify").getAValueReachingRhs() and + disablingNode = this.getKeywordParameter("verify").asSink() and + argumentOrigin = this.getKeywordParameter("verify").getAValueReachingSink() and // unlike `requests`, httpx treats `None` as turning off verify (and not as the default) argumentOrigin.asExpr().(ImmutableLiteral).booleanValue() = false // TODO: Handling of insecure SSLContext passed to verify argument @@ -89,8 +89,8 @@ private module HttpxModel { constructor = classRef().getACall() and this = constructor.getReturn().getMember(methodName).getACall() | - disablingNode = constructor.getKeywordParameter("verify").getARhs() and - argumentOrigin = constructor.getKeywordParameter("verify").getAValueReachingRhs() and + disablingNode = constructor.getKeywordParameter("verify").asSink() and + argumentOrigin = constructor.getKeywordParameter("verify").getAValueReachingSink() and // unlike `requests`, httpx treats `None` as turning off verify (and not as the default) argumentOrigin.asExpr().(ImmutableLiteral).booleanValue() = false // TODO: Handling of insecure SSLContext passed to verify argument diff --git a/python/ql/lib/semmle/python/frameworks/Lxml.qll b/python/ql/lib/semmle/python/frameworks/Lxml.qll index 70e46a6d3b0..63c71a67110 100644 --- a/python/ql/lib/semmle/python/frameworks/Lxml.qll +++ b/python/ql/lib/semmle/python/frameworks/Lxml.qll @@ -141,17 +141,18 @@ private module Lxml { // resolve_entities has default True not exists(this.getArgByName("resolve_entities")) or - this.getKeywordParameter("resolve_entities").getAValueReachingRhs().asExpr() = any(True t) + this.getKeywordParameter("resolve_entities").getAValueReachingSink().asExpr() = + any(True t) ) or kind.isXmlBomb() and - this.getKeywordParameter("huge_tree").getAValueReachingRhs().asExpr() = any(True t) and - not this.getKeywordParameter("resolve_entities").getAValueReachingRhs().asExpr() = + this.getKeywordParameter("huge_tree").getAValueReachingSink().asExpr() = any(True t) and + not this.getKeywordParameter("resolve_entities").getAValueReachingSink().asExpr() = any(False t) or kind.isDtdRetrieval() and - this.getKeywordParameter("load_dtd").getAValueReachingRhs().asExpr() = any(True t) and - this.getKeywordParameter("no_network").getAValueReachingRhs().asExpr() = any(False t) + this.getKeywordParameter("load_dtd").getAValueReachingSink().asExpr() = any(True t) and + this.getKeywordParameter("no_network").getAValueReachingSink().asExpr() = any(False t) } } @@ -318,11 +319,11 @@ private module Lxml { kind.isXxe() or kind.isXmlBomb() and - this.getKeywordParameter("huge_tree").getAValueReachingRhs().asExpr() = any(True t) + this.getKeywordParameter("huge_tree").getAValueReachingSink().asExpr() = any(True t) or kind.isDtdRetrieval() and - this.getKeywordParameter("load_dtd").getAValueReachingRhs().asExpr() = any(True t) and - this.getKeywordParameter("no_network").getAValueReachingRhs().asExpr() = any(False t) + this.getKeywordParameter("load_dtd").getAValueReachingSink().asExpr() = any(True t) and + this.getKeywordParameter("no_network").getAValueReachingSink().asExpr() = any(False t) } override predicate mayExecuteInput() { none() } diff --git a/python/ql/lib/semmle/python/frameworks/Requests.qll b/python/ql/lib/semmle/python/frameworks/Requests.qll index 05e08b45166..0c944d889d2 100644 --- a/python/ql/lib/semmle/python/frameworks/Requests.qll +++ b/python/ql/lib/semmle/python/frameworks/Requests.qll @@ -62,7 +62,7 @@ private module Requests { DataFlow::Node disablingNode, DataFlow::Node argumentOrigin ) { disablingNode = this.getKeywordParameter("verify").asSink() and - argumentOrigin = this.getKeywordParameter("verify").getAValueReachingRhs() and + argumentOrigin = this.getKeywordParameter("verify").getAValueReachingSink() and // requests treats `None` as the default and all other "falsey" values as `False`. argumentOrigin.asExpr().(ImmutableLiteral).booleanValue() = false and not argumentOrigin.asExpr() instanceof None diff --git a/python/ql/lib/semmle/python/frameworks/Stdlib.qll b/python/ql/lib/semmle/python/frameworks/Stdlib.qll index 74f80b1d478..df9301a46c3 100644 --- a/python/ql/lib/semmle/python/frameworks/Stdlib.qll +++ b/python/ql/lib/semmle/python/frameworks/Stdlib.qll @@ -2657,7 +2657,7 @@ private module StdlibPrivate { /** Gets a call to `hashlib.new` with `algorithmName` as the first argument. */ private API::CallNode hashlibNewCall(string algorithmName) { algorithmName = - result.getParameter(0, "name").getAValueReachingRhs().asExpr().(StrConst).getText() and + result.getParameter(0, "name").getAValueReachingSink().asExpr().(StrConst).getText() and result = API::moduleImport("hashlib").getMember("new").getACall() } @@ -3443,7 +3443,7 @@ private module StdlibPrivate { .getMember("handler") .getMember("feature_external_ges") .getAValueReachableFromSource() and - call.getStateArg().getAValueReachingRhs().asExpr().(BooleanLiteral).booleanValue() = true and + call.getStateArg().getAValueReachingSink().asExpr().(BooleanLiteral).booleanValue() = true and result = call.getObject() ) or @@ -3459,7 +3459,7 @@ private module StdlibPrivate { .getMember("handler") .getMember("feature_external_ges") .getAValueReachableFromSource() and - call.getStateArg().getAValueReachingRhs().asExpr().(BooleanLiteral).booleanValue() = false + call.getStateArg().getAValueReachingSink().asExpr().(BooleanLiteral).booleanValue() = false ) } diff --git a/python/ql/lib/semmle/python/frameworks/Urllib3.qll b/python/ql/lib/semmle/python/frameworks/Urllib3.qll index 568591d64f9..418a135fbfc 100644 --- a/python/ql/lib/semmle/python/frameworks/Urllib3.qll +++ b/python/ql/lib/semmle/python/frameworks/Urllib3.qll @@ -71,14 +71,15 @@ private module Urllib3 { | // cert_reqs // see https://urllib3.readthedocs.io/en/stable/user-guide.html?highlight=cert_reqs#certificate-verification - disablingNode = constructor.getKeywordParameter("cert_reqs").getARhs() and - argumentOrigin = constructor.getKeywordParameter("cert_reqs").getAValueReachingRhs() and + disablingNode = constructor.getKeywordParameter("cert_reqs").asSink() and + argumentOrigin = constructor.getKeywordParameter("cert_reqs").getAValueReachingSink() and argumentOrigin.asExpr().(StrConst).getText() = "CERT_NONE" or // assert_hostname // see https://urllib3.readthedocs.io/en/stable/reference/urllib3.connectionpool.html?highlight=assert_hostname#urllib3.HTTPSConnectionPool - disablingNode = constructor.getKeywordParameter("assert_hostname").getARhs() and - argumentOrigin = constructor.getKeywordParameter("assert_hostname").getAValueReachingRhs() and + disablingNode = constructor.getKeywordParameter("assert_hostname").asSink() and + argumentOrigin = + constructor.getKeywordParameter("assert_hostname").getAValueReachingSink() and argumentOrigin.asExpr().(BooleanLiteral).booleanValue() = false ) } diff --git a/python/ql/lib/semmle/python/frameworks/Xmltodict.qll b/python/ql/lib/semmle/python/frameworks/Xmltodict.qll index f63fec7afe4..495725e9a76 100644 --- a/python/ql/lib/semmle/python/frameworks/Xmltodict.qll +++ b/python/ql/lib/semmle/python/frameworks/Xmltodict.qll @@ -29,7 +29,7 @@ private module Xmltodict { override predicate vulnerableTo(XML::XmlParsingVulnerabilityKind kind) { kind.isXmlBomb() and - this.getKeywordParameter("disable_entities").getAValueReachingRhs().asExpr() = any(False f) + this.getKeywordParameter("disable_entities").getAValueReachingSink().asExpr() = any(False f) } override predicate mayExecuteInput() { none() } diff --git a/python/ql/src/Security/CWE-079/Jinja2WithoutEscaping.ql b/python/ql/src/Security/CWE-079/Jinja2WithoutEscaping.ql index 820937f8649..97bbb72edec 100644 --- a/python/ql/src/Security/CWE-079/Jinja2WithoutEscaping.ql +++ b/python/ql/src/Security/CWE-079/Jinja2WithoutEscaping.ql @@ -42,7 +42,7 @@ where not exists(call.getArgByName("autoescape")) or call.getKeywordParameter("autoescape") - .getAValueReachingRhs() + .getAValueReachingSink() .asExpr() .(ImmutableLiteral) .booleanValue() = false diff --git a/python/ql/src/Security/CWE-285/PamAuthorization.ql b/python/ql/src/Security/CWE-285/PamAuthorization.ql index 233e42e6239..affb59ff7db 100644 --- a/python/ql/src/Security/CWE-285/PamAuthorization.ql +++ b/python/ql/src/Security/CWE-285/PamAuthorization.ql @@ -18,9 +18,9 @@ import semmle.python.dataflow.new.TaintTracking API::Node libPam() { exists(API::CallNode findLibCall, API::CallNode cdllCall | findLibCall = API::moduleImport("ctypes").getMember("util").getMember("find_library").getACall() and - findLibCall.getParameter(0).getAValueReachingRhs().asExpr().(StrConst).getText() = "pam" and + findLibCall.getParameter(0).getAValueReachingSink().asExpr().(StrConst).getText() = "pam" and cdllCall = API::moduleImport("ctypes").getMember("CDLL").getACall() and - cdllCall.getParameter(0).getAValueReachingRhs() = findLibCall + cdllCall.getParameter(0).getAValueReachingSink() = findLibCall | result = cdllCall.getReturn() ) diff --git a/python/ql/src/Security/CWE-732/WeakFilePermissions.ql b/python/ql/src/Security/CWE-732/WeakFilePermissions.ql index 393198cf72e..90921b99fb9 100644 --- a/python/ql/src/Security/CWE-732/WeakFilePermissions.ql +++ b/python/ql/src/Security/CWE-732/WeakFilePermissions.ql @@ -36,13 +36,13 @@ string permissive_permission(int p) { predicate chmod_call(API::CallNode call, string name, int mode) { call = API::moduleImport("os").getMember("chmod").getACall() and - mode = call.getParameter(1, "mode").getAValueReachingRhs().asExpr().(IntegerLiteral).getValue() and + mode = call.getParameter(1, "mode").getAValueReachingSink().asExpr().(IntegerLiteral).getValue() and name = "chmod" } predicate open_call(API::CallNode call, string name, int mode) { call = API::moduleImport("os").getMember("open").getACall() and - mode = call.getParameter(2, "mode").getAValueReachingRhs().asExpr().(IntegerLiteral).getValue() and + mode = call.getParameter(2, "mode").getAValueReachingSink().asExpr().(IntegerLiteral).getValue() and name = "open" } From fecbfa6ca37e70a308f5fdd8e8d07b1361798ebd Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 30 May 2022 14:15:48 +0200 Subject: [PATCH 031/246] Python: add deprecation --- python/ql/lib/semmle/python/ApiGraphs.qll | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/python/ql/lib/semmle/python/ApiGraphs.qll b/python/ql/lib/semmle/python/ApiGraphs.qll index 72f49c859ef..7d59b14696f 100644 --- a/python/ql/lib/semmle/python/ApiGraphs.qll +++ b/python/ql/lib/semmle/python/ApiGraphs.qll @@ -149,6 +149,18 @@ module API { */ DataFlow::LocalSourceNode asSource() { Impl::use(this, result) } + /** DEPRECATED. This predicate has been renamed to `getAValueReachableFromSource()`. */ + deprecated DataFlow::Node getAUse() { result = this.getAValueReachableFromSource() } + + /** DEPRECATED. This predicate has been renamed to `asSource()`. */ + deprecated DataFlow::LocalSourceNode getAnImmediateUse() { result = this.asSource() } + + /** DEPRECATED. This predicate has been renamed to `asSink()`. */ + deprecated DataFlow::Node getARhs() { result = this.asSink() } + + /** DEPRECATED. This predicate has been renamed to `getAValueReachingSink()`. */ + deprecated DataFlow::Node getAValueReachingRhs() { result = this.getAValueReachingSink() } + /** * Gets a call to the function represented by this API component. */ From 092a6a01ac71d57baa80d0639907ccad8113eacb Mon Sep 17 00:00:00 2001 From: Asger F Date: Tue, 14 Jun 2022 10:44:30 +0200 Subject: [PATCH 032/246] Python: Update member documentation --- python/ql/lib/semmle/python/ApiGraphs.qll | 60 +++++++++++------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/python/ql/lib/semmle/python/ApiGraphs.qll b/python/ql/lib/semmle/python/ApiGraphs.qll index 7d59b14696f..65fffc9aee1 100644 --- a/python/ql/lib/semmle/python/ApiGraphs.qll +++ b/python/ql/lib/semmle/python/ApiGraphs.qll @@ -92,19 +92,10 @@ module API { */ class Node extends Impl::TApiNode { /** - * Gets a data-flow node corresponding to a use of the API component represented by this node. + * Gets a data-flow node where this value may flow after entering the current codebase. * - * For example, `import re; re.escape` is a use of the `escape` function from the - * `re` module, and `import re; re.escape("hello")` is a use of the return of that function. - * - * This includes indirect uses found via data flow, meaning that in - * ```python - * def f(x): - * pass - * - * f(obj.foo) - * ``` - * both `obj.foo` and `x` are uses of the `foo` member from `obj`. + * This is similar to `asSource()` but additionally includes nodes that are transitively reachable by data flow. + * See `asSource()` for examples. */ DataFlow::Node getAValueReachableFromSource() { exists(DataFlow::LocalSourceNode src | Impl::use(this, src) | @@ -113,39 +104,48 @@ module API { } /** - * Gets a data-flow node corresponding to the right-hand side of a definition of the API - * component represented by this node. + * Gets a data-flow node where this value leaves the current codebase and flows into an + * external library (or in general, any external codebase). * - * For example, in the property write `foo.bar = x`, variable `x` is the the right-hand side - * of a write to the `bar` property of `foo`. + * Concretely, this is either an argument passed to a call to external code, + * or the right-hand side of a property write on an object flowing into such a call. * - * Note that for parameters, it is the arguments flowing into that parameter that count as - * right-hand sides of the definition, not the declaration of the parameter itself. - * Consequently, in : + * For example: * ```python - * from mypkg import foo; + * import foo + * + * # 'x' is matched by API::moduleImport("foo").getMember("bar").getParameter(0).asSink() * foo.bar(x) + * + * # 'x' is matched by API::moduleImport("foo").getMember("bar").getParameter(0).getMember("prop").asSink() + * obj.prop = x + * foo.bar(obj); * ``` - * `x` is the right-hand side of a definition of the first parameter of `bar` from the `mypkg.foo` module. */ DataFlow::Node asSink() { Impl::rhs(this, result) } /** - * Gets a data-flow node that may interprocedurally flow to the right-hand side of a definition - * of the API component represented by this node. + * Gets a data-flow node that transitively flows to an external library (or in general, any external codebase). + * + * This is similar to `asSink()` but additionally includes nodes that transitively reach a sink by data flow. + * See `asSink()` for examples. */ DataFlow::Node getAValueReachingSink() { result = Impl::trackDefNode(this.asSink()) } /** - * Gets an immediate use of the API component represented by this node. + * Gets a data-flow node where this value enters the current codebase. * - * For example, `import re; re.escape` is a an immediate use of the `escape` member - * from the `re` module. + * For example: + * ```python + * # API::moduleImport("re").asSource() + * import re * - * Unlike `getAUse()`, this predicate only gets the immediate references, not the indirect uses - * found via data flow. This means that in `x = re.escape` only `re.escape` is a reference - * to the `escape` member of `re`, neither `x` nor any node that `x` flows to is a reference to - * this API component. + * # API::moduleImport("re").getMember("escape").asSource() + * re.escape + * + * # API::moduleImport("re").getMember("escape").getReturn().asSource() + * re.escape() + * ``` */ DataFlow::LocalSourceNode asSource() { Impl::use(this, result) } From f2403e2610c3f853644ea5e6debdc4f5ebb3fe01 Mon Sep 17 00:00:00 2001 From: Asger F Date: Wed, 18 May 2022 15:19:13 +0200 Subject: [PATCH 033/246] Ruby: port API graph doc comment --- ruby/ql/lib/codeql/ruby/ApiGraphs.qll | 72 ++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll index 02d9bc18132..04e5aa1e66b 100644 --- a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll +++ b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll @@ -19,8 +19,76 @@ private import codeql.ruby.dataflow.internal.DataFlowDispatch as DataFlowDispatc */ module API { /** - * An abstract representation of a definition or use of an API component such as a Ruby module, - * or the result of a method call. + * A node in the API graph, representing a value that has crossed the boundary between this + * codebase and an external library (or in general, any external codebase). + * + * ### Basic usage + * + * API graphs are typically used to identify "API calls", that is, calls to an external function + * whose implementation is not necessarily part of the current codebase. + * + * The most basic use of API graphs is typically as follows: + * 1. Start with `API::getTopLevelMember` for the relevant library. + * 2. Follow up with a chain of accessors such as `getMethod` describing how to get to the relevant API function. + * 3. Map the resulting API graph nodes to data-flow nodes, using `asSource` or `asSink`. + * + * For example, a simplified way to get arguments to `Foo.bar` would be + * ```codeql + * API::getTopLevelMember("Foo").getMethod("bar").getParameter(0).asSink() + * ``` + * + * The most commonly used accessors are `getMember`, `getMethod`, `getParameter`, and `getReturn`. + * + * ### API graph nodes + * + * There are two kinds of nodes in the API graphs, distinguished by who is "holding" the value: + * - **Use-nodes** represent values held by the current codebase, which came from an external library. + * (The current codebase is "using" a value that came from the library). + * - **Def-nodes** represent values held by the external library, which came from this codebase. + * (The current codebase "defines" the value seen by the library). + * + * API graph nodes are associated with data-flow nodes in the current codebase. + * (Since external libraries are not part of the database, there is no way to associate with concrete + * data-flow nodes from the external library). + * - **Use-nodes** are associated with data-flow nodes where a value enters the current codebase, + * such as the return value of a call to an external function. + * - **Def-nodes** are associated with data-flow nodes where a value leaves the current codebase, + * such as an argument passed in a call to an external function. + * + * + * ### Access paths and edge labels + * + * Nodes in the API graph are associated with a set of access paths, describing a series of operations + * that may be performed to obtain that value. + * + * For example, the access path `API::getTopLevelMember("Foo").getMethod("bar")` represents the action of + * reading the top-level constant `Foo` and then accessing the method `bar` on the resulting object. + * It would be associated with a call such as `Foo.bar()`. + * + * Each edge in the graph is labelled by such an "operation". For an edge `A->B`, the type of the `A` node + * determines who is performing the operation, and the type of the `B` node determines who ends up holding + * the result: + * - An edge starting from a use-node describes what the current codebase is doing to a value that + * came from a library. + * - An edge starting from a def-node describes what the external library might do to a value that + * came from the current codebase. + * - An edge ending in a use-node means the result ends up in the current codebase (at its associated data-flow node). + * - An edge ending in a def-node means the result ends up in external code (its associated data-flow node is + * the place where it was "last seen" in the current codebase before flowing out) + * + * Because the implementation of the external library is not visible, it is not known exactly what operations + * it will perform on values that flow there. Instead, the edges starting from a def-node are operations that would + * lead to an observable effect within the current codebase; without knowing for certain if the library will actually perform + * those operations. (When constructing these edges, we assume the library is somewhat well-behaved). + * + * For example, given this snippet: + * ```ruby + * Foo.bar(->(x) { doSomething(x) }) + * ``` + * A callback is passed to the external function `Foo.bar`. We can't know if `Foo.bar` will actually invoke this callback. + * But _if_ the library should decide to invoke the callback, then a value will flow into the current codebase via the `x` parameter. + * For that reason, an edge is generated representing the argument-passing operation that might be performed by `Foo.bar`. + * This edge is going from the def-node associated with the callback to the use-node associated with the parameter `x` of the lambda. */ class Node extends Impl::TApiNode { /** From 573c5c5efebd2b97f7ad5a2bedc83fd30a06e1ad Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 30 May 2022 13:25:50 +0200 Subject: [PATCH 034/246] Ruby: Rename getAnImmediateUse -> asSource --- ruby/ql/lib/codeql/ruby/ApiGraphs.qll | 8 +++----- ruby/ql/lib/codeql/ruby/frameworks/Rails.qll | 6 +----- ruby/ql/lib/codeql/ruby/frameworks/data/ModelsAsData.qll | 2 +- ruby/ql/lib/codeql/ruby/frameworks/http_clients/Excon.qll | 4 ++-- .../lib/codeql/ruby/frameworks/http_clients/Faraday.qll | 4 ++-- .../codeql/ruby/frameworks/http_clients/HttpClient.qll | 5 ++--- .../lib/codeql/ruby/frameworks/http_clients/Httparty.qll | 2 +- .../lib/codeql/ruby/frameworks/http_clients/NetHttp.qll | 4 ++-- .../lib/codeql/ruby/frameworks/http_clients/OpenURI.qll | 2 +- .../codeql/ruby/frameworks/http_clients/RestClient.qll | 2 +- .../lib/codeql/ruby/frameworks/http_clients/Typhoeus.qll | 2 +- 11 files changed, 17 insertions(+), 24 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll index 04e5aa1e66b..9c34a82aaab 100644 --- a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll +++ b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll @@ -111,7 +111,7 @@ module API { * Unlike `getAUse()`, this predicate only gets the immediate references, not the indirect uses * found via data flow. */ - DataFlow::LocalSourceNode getAnImmediateUse() { Impl::use(this, result) } + DataFlow::LocalSourceNode asSource() { Impl::use(this, result) } /** * Gets a data-flow node corresponding the value flowing into this API component. @@ -126,9 +126,7 @@ module API { /** * Gets a call to a method on the receiver represented by this API component. */ - DataFlow::CallNode getAMethodCall(string method) { - result = this.getReturn(method).getAnImmediateUse() - } + DataFlow::CallNode getAMethodCall(string method) { result = this.getReturn(method).asSource() } /** * Gets a node representing member `m` of this API component. @@ -203,7 +201,7 @@ module API { /** * Gets a `new` call to the function represented by this API component. */ - DataFlow::ExprNode getAnInstantiation() { result = this.getInstance().getAnImmediateUse() } + DataFlow::ExprNode getAnInstantiation() { result = this.getInstance().asSource() } /** * Gets a node representing a (direct or indirect) subclass of the class represented by this node. diff --git a/ruby/ql/lib/codeql/ruby/frameworks/Rails.qll b/ruby/ql/lib/codeql/ruby/frameworks/Rails.qll index 54c27fc994d..9b45d220486 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/Rails.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/Rails.qll @@ -63,11 +63,7 @@ private module Config { ) or // `Rails.application.config` - this = - API::getTopLevelMember("Rails") - .getReturn("application") - .getReturn("config") - .getAnImmediateUse() + this = API::getTopLevelMember("Rails").getReturn("application").getReturn("config").asSource() or // `Rails.application.configure { ... config ... }` // `Rails::Application.configure { ... config ... }` diff --git a/ruby/ql/lib/codeql/ruby/frameworks/data/ModelsAsData.qll b/ruby/ql/lib/codeql/ruby/frameworks/data/ModelsAsData.qll index a3d92b486c3..615568fec6a 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/data/ModelsAsData.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/data/ModelsAsData.qll @@ -25,7 +25,7 @@ private import codeql.ruby.dataflow.RemoteFlowSources * A remote flow source originating from a CSV source row. */ private class RemoteFlowSourceFromCsv extends RemoteFlowSource::Range { - RemoteFlowSourceFromCsv() { this = ModelOutput::getASourceNode("remote").getAnImmediateUse() } + RemoteFlowSourceFromCsv() { this = ModelOutput::getASourceNode("remote").asSource() } override string getSourceType() { result = "Remote flow (from model)" } } diff --git a/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Excon.qll b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Excon.qll index 83d62d196ee..ddd66ecf6a0 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Excon.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Excon.qll @@ -30,8 +30,8 @@ class ExconHttpRequest extends HTTP::Client::Request::Range { DataFlow::Node connectionUse; ExconHttpRequest() { - requestUse = requestNode.getAnImmediateUse() and - connectionUse = connectionNode.getAnImmediateUse() and + requestUse = requestNode.asSource() and + connectionUse = connectionNode.asSource() and connectionNode = [ // one-off requests diff --git a/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Faraday.qll b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Faraday.qll index f74c9059e70..b9367ee4765 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Faraday.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Faraday.qll @@ -38,8 +38,8 @@ class FaradayHttpRequest extends HTTP::Client::Request::Range { ] and requestNode = connectionNode.getReturn(["get", "head", "delete", "post", "put", "patch", "trace"]) and - requestUse = requestNode.getAnImmediateUse() and - connectionUse = connectionNode.getAnImmediateUse() and + requestUse = requestNode.asSource() and + connectionUse = connectionNode.asSource() and this = requestUse.asExpr().getExpr() } diff --git a/ruby/ql/lib/codeql/ruby/frameworks/http_clients/HttpClient.qll b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/HttpClient.qll index 5a3ef8c355c..f2f2b7b0440 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/http_clients/HttpClient.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/HttpClient.qll @@ -29,7 +29,7 @@ class HttpClientRequest extends HTTP::Client::Request::Range { API::getTopLevelMember("HTTPClient").getInstance() ] and requestNode = connectionNode.getReturn(method) and - requestUse = requestNode.getAnImmediateUse() and + requestUse = requestNode.asSource() and method in [ "get", "head", "delete", "options", "post", "put", "trace", "get_content", "post_content" ] and @@ -52,8 +52,7 @@ class HttpClientRequest extends HTTP::Client::Request::Range { // Look for calls to set // `c.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE` // on an HTTPClient connection object `c`. - disablingNode = - connectionNode.getReturn("ssl_config").getReturn("verify_mode=").getAnImmediateUse() and + disablingNode = connectionNode.getReturn("ssl_config").getReturn("verify_mode=").asSource() and disablingNode.(DataFlow::CallNode).getArgument(0) = API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").getAUse() } diff --git a/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Httparty.qll b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Httparty.qll index 29197a71e29..f92010c03cf 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Httparty.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Httparty.qll @@ -28,7 +28,7 @@ class HttpartyRequest extends HTTP::Client::Request::Range { DataFlow::CallNode requestUse; HttpartyRequest() { - requestUse = requestNode.getAnImmediateUse() and + requestUse = requestNode.asSource() and requestNode = API::getTopLevelMember("HTTParty") .getReturn(["get", "head", "delete", "options", "post", "put", "patch"]) and diff --git a/ruby/ql/lib/codeql/ruby/frameworks/http_clients/NetHttp.qll b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/NetHttp.qll index 5f0e6903094..c62107f525f 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/http_clients/NetHttp.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/NetHttp.qll @@ -25,7 +25,7 @@ class NetHttpRequest extends HTTP::Client::Request::Range { NetHttpRequest() { exists(string method | - request = requestNode.getAnImmediateUse() and + request = requestNode.asSource() and this = request.asExpr().getExpr() | // Net::HTTP.get(...) @@ -59,7 +59,7 @@ class NetHttpRequest extends HTTP::Client::Request::Range { new = API::getTopLevelMember("Net").getMember("HTTP").getInstance() and requestNode = new.getReturn(_) | - result = new.getAnImmediateUse().(DataFlow::CallNode).getArgument(0) + result = new.asSource().(DataFlow::CallNode).getArgument(0) ) } diff --git a/ruby/ql/lib/codeql/ruby/frameworks/http_clients/OpenURI.qll b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/OpenURI.qll index c76310bf150..aa93083f6a4 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/http_clients/OpenURI.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/OpenURI.qll @@ -28,7 +28,7 @@ class OpenUriRequest extends HTTP::Client::Request::Range { [API::getTopLevelMember("URI"), API::getTopLevelMember("URI").getReturn("parse")] .getReturn("open"), API::getTopLevelMember("OpenURI").getReturn("open_uri") ] and - requestUse = requestNode.getAnImmediateUse() and + requestUse = requestNode.asSource() and this = requestUse.asExpr().getExpr() } diff --git a/ruby/ql/lib/codeql/ruby/frameworks/http_clients/RestClient.qll b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/RestClient.qll index 19bd89d8b23..9e55aee25b5 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/http_clients/RestClient.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/RestClient.qll @@ -22,7 +22,7 @@ class RestClientHttpRequest extends HTTP::Client::Request::Range { API::Node connectionNode; RestClientHttpRequest() { - requestUse = requestNode.getAnImmediateUse() and + requestUse = requestNode.asSource() and this = requestUse.asExpr().getExpr() and ( connectionNode = diff --git a/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Typhoeus.qll b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Typhoeus.qll index d3de66a6758..2102e255ce9 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Typhoeus.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Typhoeus.qll @@ -19,7 +19,7 @@ class TyphoeusHttpRequest extends HTTP::Client::Request::Range { API::Node requestNode; TyphoeusHttpRequest() { - requestUse = requestNode.getAnImmediateUse() and + requestUse = requestNode.asSource() and requestNode = API::getTopLevelMember("Typhoeus") .getReturn(["get", "head", "delete", "options", "post", "put", "patch"]) and From 2f8086bb57d00398ec5125e1f84e095e1b74c0b7 Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 30 May 2022 13:27:20 +0200 Subject: [PATCH 035/246] Ruby: Rename getAUse -> getAValueReachableFromSource --- ruby/ql/lib/codeql/ruby/ApiGraphs.qll | 4 ++-- .../ruby/frameworks/ActionController.qll | 2 +- .../codeql/ruby/frameworks/ActiveRecord.qll | 2 +- .../ql/lib/codeql/ruby/frameworks/GraphQL.qll | 21 ++++++++++++++++--- .../lib/codeql/ruby/frameworks/XmlParsing.qll | 2 +- .../lib/codeql/ruby/frameworks/core/Hash.qll | 6 ++++-- .../ruby/frameworks/http_clients/Excon.qll | 6 ++++-- .../ruby/frameworks/http_clients/Faraday.qll | 9 ++++++-- .../frameworks/http_clients/HttpClient.qll | 5 ++++- .../ruby/frameworks/http_clients/NetHttp.qll | 5 ++++- .../ruby/frameworks/http_clients/OpenURI.qll | 6 +++++- .../frameworks/http_clients/RestClient.qll | 9 ++++++-- .../library-tests/dataflow/api-graphs/use.ql | 2 +- 13 files changed, 59 insertions(+), 20 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll index 9c34a82aaab..c55795946a9 100644 --- a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll +++ b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll @@ -99,7 +99,7 @@ module API { * * This includes indirect uses found via data flow. */ - DataFlow::Node getAUse() { + DataFlow::Node getAValueReachableFromSource() { exists(DataFlow::LocalSourceNode src | Impl::use(this, src) | Impl::trackUseNode(src).flowsTo(result) ) @@ -108,7 +108,7 @@ module API { /** * Gets an immediate use of the API component represented by this node. * - * Unlike `getAUse()`, this predicate only gets the immediate references, not the indirect uses + * Unlike `getAValueReachableFromSource()`, this predicate only gets the immediate references, not the indirect uses * found via data flow. */ DataFlow::LocalSourceNode asSource() { Impl::use(this, result) } diff --git a/ruby/ql/lib/codeql/ruby/frameworks/ActionController.qll b/ruby/ql/lib/codeql/ruby/frameworks/ActionController.qll index f034e229e32..36cfcb230f3 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/ActionController.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/ActionController.qll @@ -33,7 +33,7 @@ class ActionControllerControllerClass extends ClassDeclaration { // In Rails applications `ApplicationController` typically extends `ActionController::Base`, but we // treat it separately in case the `ApplicationController` definition is not in the database. API::getTopLevelMember("ApplicationController") - ].getASubclass().getAUse().asExpr().getExpr() + ].getASubclass().getAValueReachableFromSource().asExpr().getExpr() } /** diff --git a/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll b/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll index 0846e30d047..a394a828ac7 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll @@ -54,7 +54,7 @@ class ActiveRecordModelClass extends ClassDeclaration { // In Rails applications `ApplicationRecord` typically extends `ActiveRecord::Base`, but we // treat it separately in case the `ApplicationRecord` definition is not in the database. API::getTopLevelMember("ApplicationRecord") - ].getASubclass().getAUse().asExpr().getExpr() + ].getASubclass().getAValueReachableFromSource().asExpr().getExpr() } // Gets the class declaration for this class and all of its super classes diff --git a/ruby/ql/lib/codeql/ruby/frameworks/GraphQL.qll b/ruby/ql/lib/codeql/ruby/frameworks/GraphQL.qll index a7ac5cab6cd..3dacd89b25e 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/GraphQL.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/GraphQL.qll @@ -41,7 +41,12 @@ private API::Node graphQlSchema() { result = API::getTopLevelMember("GraphQL").g private class GraphqlRelayClassicMutationClass extends ClassDeclaration { GraphqlRelayClassicMutationClass() { this.getSuperclassExpr() = - graphQlSchema().getMember("RelayClassicMutation").getASubclass*().getAUse().asExpr().getExpr() + graphQlSchema() + .getMember("RelayClassicMutation") + .getASubclass*() + .getAValueReachableFromSource() + .asExpr() + .getExpr() } } @@ -71,7 +76,12 @@ private class GraphqlRelayClassicMutationClass extends ClassDeclaration { private class GraphqlSchemaResolverClass extends ClassDeclaration { GraphqlSchemaResolverClass() { this.getSuperclassExpr() = - graphQlSchema().getMember("Resolver").getASubclass().getAUse().asExpr().getExpr() + graphQlSchema() + .getMember("Resolver") + .getASubclass() + .getAValueReachableFromSource() + .asExpr() + .getExpr() } } @@ -92,7 +102,12 @@ private class GraphqlSchemaResolverClass extends ClassDeclaration { class GraphqlSchemaObjectClass extends ClassDeclaration { GraphqlSchemaObjectClass() { this.getSuperclassExpr() = - graphQlSchema().getMember("Object").getASubclass().getAUse().asExpr().getExpr() + graphQlSchema() + .getMember("Object") + .getASubclass() + .getAValueReachableFromSource() + .asExpr() + .getExpr() } /** Gets a `GraphqlFieldDefinitionMethodCall` called in this class. */ diff --git a/ruby/ql/lib/codeql/ruby/frameworks/XmlParsing.qll b/ruby/ql/lib/codeql/ruby/frameworks/XmlParsing.qll index cd03951304b..73cefe8d255 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/XmlParsing.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/XmlParsing.qll @@ -143,7 +143,7 @@ private DataFlow::LocalSourceNode trackFeature(Feature f, boolean enable, TypeTr or // Use of a constant f enable = true and - result = parseOptionsModule().getMember(f.getConstantName()).getAUse() + result = parseOptionsModule().getMember(f.getConstantName()).getAValueReachableFromSource() or // Treat `&`, `&=`, `|` and `|=` operators as if they preserve the on/off states // of their operands. This is an overapproximation but likely to work well in practice diff --git a/ruby/ql/lib/codeql/ruby/frameworks/core/Hash.qll b/ruby/ql/lib/codeql/ruby/frameworks/core/Hash.qll index dd628d4929d..87733c0af9b 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/core/Hash.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/core/Hash.qll @@ -99,7 +99,8 @@ module Hash { HashNewSummary() { this = "Hash[]" } final override ElementReference getACall() { - result.getReceiver() = API::getTopLevelMember("Hash").getAUse().asExpr().getExpr() and + result.getReceiver() = + API::getTopLevelMember("Hash").getAValueReachableFromSource().asExpr().getExpr() and result.getNumberOfArguments() = 1 } @@ -138,7 +139,8 @@ module Hash { } final override ElementReference getACall() { - result.getReceiver() = API::getTopLevelMember("Hash").getAUse().asExpr().getExpr() and + result.getReceiver() = + API::getTopLevelMember("Hash").getAValueReachableFromSource().asExpr().getExpr() and key = result.getArgument(i - 1).getConstantValue() and exists(result.getArgument(i)) } diff --git a/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Excon.qll b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Excon.qll index ddd66ecf6a0..fdfd1f92096 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Excon.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Excon.qll @@ -66,7 +66,8 @@ class ExconHttpRequest extends HTTP::Client::Request::Range { override predicate disablesCertificateValidation(DataFlow::Node disablingNode) { // Check for `ssl_verify_peer: false` in the options hash. exists(DataFlow::Node arg, int i | - i > 0 and arg = connectionNode.getAUse().(DataFlow::CallNode).getArgument(i) + i > 0 and + arg = connectionNode.getAValueReachableFromSource().(DataFlow::CallNode).getArgument(i) | argSetsVerifyPeer(arg, false, disablingNode) ) @@ -79,7 +80,8 @@ class ExconHttpRequest extends HTTP::Client::Request::Range { disableCall.asExpr().getASuccessor+() = requestUse.asExpr() and disablingNode = disableCall and not exists(DataFlow::Node arg, int i | - i > 0 and arg = connectionNode.getAUse().(DataFlow::CallNode).getArgument(i) + i > 0 and + arg = connectionNode.getAValueReachableFromSource().(DataFlow::CallNode).getArgument(i) | argSetsVerifyPeer(arg, true, _) ) diff --git a/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Faraday.qll b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Faraday.qll index b9367ee4765..59d2e33dad5 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Faraday.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Faraday.qll @@ -58,7 +58,8 @@ class FaradayHttpRequest extends HTTP::Client::Request::Range { // or // `{ ssl: { verify_mode: OpenSSL::SSL::VERIFY_NONE } }` exists(DataFlow::Node arg, int i | - i > 0 and arg = connectionNode.getAUse().(DataFlow::CallNode).getArgument(i) + i > 0 and + arg = connectionNode.getAValueReachableFromSource().(DataFlow::CallNode).getArgument(i) | // Either passed as an individual key:value argument, e.g.: // Faraday.new(..., ssl: {...}) @@ -132,7 +133,11 @@ private predicate isVerifyModeNonePair(CfgNodes::ExprNodes::PairCfgNode p) { key.asExpr() = p.getKey() and value.asExpr() = p.getValue() and isSymbolLiteral(key, "verify_mode") and - value = API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").getAUse() + value = + API::getTopLevelMember("OpenSSL") + .getMember("SSL") + .getMember("VERIFY_NONE") + .getAValueReachableFromSource() ) } diff --git a/ruby/ql/lib/codeql/ruby/frameworks/http_clients/HttpClient.qll b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/HttpClient.qll index f2f2b7b0440..2f63fd96005 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/http_clients/HttpClient.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/HttpClient.qll @@ -54,7 +54,10 @@ class HttpClientRequest extends HTTP::Client::Request::Range { // on an HTTPClient connection object `c`. disablingNode = connectionNode.getReturn("ssl_config").getReturn("verify_mode=").asSource() and disablingNode.(DataFlow::CallNode).getArgument(0) = - API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").getAUse() + API::getTopLevelMember("OpenSSL") + .getMember("SSL") + .getMember("VERIFY_NONE") + .getAValueReachableFromSource() } override string getFramework() { result = "HTTPClient" } diff --git a/ruby/ql/lib/codeql/ruby/frameworks/http_clients/NetHttp.qll b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/NetHttp.qll index c62107f525f..3396ede11da 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/http_clients/NetHttp.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/NetHttp.qll @@ -73,7 +73,10 @@ class NetHttpRequest extends HTTP::Client::Request::Range { // foo.request(...) exists(DataFlow::CallNode setter | disablingNode = - API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").getAUse() and + API::getTopLevelMember("OpenSSL") + .getMember("SSL") + .getMember("VERIFY_NONE") + .getAValueReachableFromSource() and setter.asExpr().getExpr().(SetterMethodCall).getMethodName() = "verify_mode=" and disablingNode = setter.getArgument(0) and localFlow(setter.getReceiver(), request.getReceiver()) diff --git a/ruby/ql/lib/codeql/ruby/frameworks/http_clients/OpenURI.qll b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/OpenURI.qll index aa93083f6a4..565ac2e85ca 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/http_clients/OpenURI.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/OpenURI.qll @@ -110,7 +110,11 @@ private predicate isSslVerifyModeNonePair(CfgNodes::ExprNodes::PairCfgNode p) { key.asExpr() = p.getKey() and value.asExpr() = p.getValue() and isSslVerifyModeLiteral(key) and - value = API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").getAUse() + value = + API::getTopLevelMember("OpenSSL") + .getMember("SSL") + .getMember("VERIFY_NONE") + .getAValueReachableFromSource() ) } diff --git a/ruby/ql/lib/codeql/ruby/frameworks/http_clients/RestClient.qll b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/RestClient.qll index 9e55aee25b5..41e4c1fbcd2 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/http_clients/RestClient.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/RestClient.qll @@ -52,7 +52,8 @@ class RestClientHttpRequest extends HTTP::Client::Request::Range { // `RestClient::Resource::new` takes an options hash argument, and we're // looking for `{ verify_ssl: OpenSSL::SSL::VERIFY_NONE }`. exists(DataFlow::Node arg, int i | - i > 0 and arg = connectionNode.getAUse().(DataFlow::CallNode).getArgument(i) + i > 0 and + arg = connectionNode.getAValueReachableFromSource().(DataFlow::CallNode).getArgument(i) | // Either passed as an individual key:value argument, e.g.: // RestClient::Resource.new(..., verify_ssl: OpenSSL::SSL::VERIFY_NONE) @@ -79,7 +80,11 @@ private predicate isVerifySslNonePair(CfgNodes::ExprNodes::PairCfgNode p) { key.asExpr() = p.getKey() and value.asExpr() = p.getValue() and isSslVerifyModeLiteral(key) and - value = API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").getAUse() + value = + API::getTopLevelMember("OpenSSL") + .getMember("SSL") + .getMember("VERIFY_NONE") + .getAValueReachableFromSource() ) } diff --git a/ruby/ql/test/library-tests/dataflow/api-graphs/use.ql b/ruby/ql/test/library-tests/dataflow/api-graphs/use.ql index 63728b0a14b..4c96b0cd789 100644 --- a/ruby/ql/test/library-tests/dataflow/api-graphs/use.ql +++ b/ruby/ql/test/library-tests/dataflow/api-graphs/use.ql @@ -26,7 +26,7 @@ class ApiUseTest extends InlineExpectationsTest { l = n.getLocation() and ( tag = "use" and - n = a.getAUse() + n = a.getAValueReachableFromSource() or tag = "def" and n = a.getARhs() From 7c877c7861f122ca24fc78995cba1b5b135f3913 Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 30 May 2022 13:27:50 +0200 Subject: [PATCH 036/246] Ruby: Rename getARhs -> asSink --- ruby/ql/lib/codeql/ruby/ApiGraphs.qll | 4 ++-- ruby/ql/test/library-tests/dataflow/api-graphs/use.ql | 2 +- ruby/ql/test/library-tests/dataflow/summaries/Summaries.ql | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll index c55795946a9..b129142ab8f 100644 --- a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll +++ b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll @@ -116,12 +116,12 @@ module API { /** * Gets a data-flow node corresponding the value flowing into this API component. */ - DataFlow::Node getARhs() { Impl::def(this, result) } + DataFlow::Node asSink() { Impl::def(this, result) } /** * Gets a data-flow node that may interprocedurally flow to the value escaping into this API component. */ - DataFlow::Node getAValueReachingRhs() { result = Impl::trackDefNode(this.getARhs()) } + DataFlow::Node getAValueReachingRhs() { result = Impl::trackDefNode(this.asSink()) } /** * Gets a call to a method on the receiver represented by this API component. diff --git a/ruby/ql/test/library-tests/dataflow/api-graphs/use.ql b/ruby/ql/test/library-tests/dataflow/api-graphs/use.ql index 4c96b0cd789..c47939a8405 100644 --- a/ruby/ql/test/library-tests/dataflow/api-graphs/use.ql +++ b/ruby/ql/test/library-tests/dataflow/api-graphs/use.ql @@ -29,7 +29,7 @@ class ApiUseTest extends InlineExpectationsTest { n = a.getAValueReachableFromSource() or tag = "def" and - n = a.getARhs() + n = a.asSink() or tag = "call" and n = a.(API::MethodAccessNode).getCallNode() diff --git a/ruby/ql/test/library-tests/dataflow/summaries/Summaries.ql b/ruby/ql/test/library-tests/dataflow/summaries/Summaries.ql index a1ee8a05332..c81af2546e7 100644 --- a/ruby/ql/test/library-tests/dataflow/summaries/Summaries.ql +++ b/ruby/ql/test/library-tests/dataflow/summaries/Summaries.ql @@ -132,7 +132,7 @@ class CustomValueSink extends DefaultValueFlowConf { override predicate isSink(DataFlow::Node sink) { super.isSink(sink) or - sink = ModelOutput::getASinkNode("test-sink").getARhs() + sink = ModelOutput::getASinkNode("test-sink").asSink() } } @@ -140,7 +140,7 @@ class CustomTaintSink extends DefaultTaintFlowConf { override predicate isSink(DataFlow::Node sink) { super.isSink(sink) or - sink = ModelOutput::getASinkNode("test-sink").getARhs() + sink = ModelOutput::getASinkNode("test-sink").asSink() } } From 9838e2e10164dd571c13d8b592ea30560918cbcf Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 30 May 2022 13:28:18 +0200 Subject: [PATCH 037/246] Ruby: Rename getAValueReachingRhs -> getAValueReachingSink --- ruby/ql/lib/codeql/ruby/ApiGraphs.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll index b129142ab8f..eaf6ecb009b 100644 --- a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll +++ b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll @@ -121,7 +121,7 @@ module API { /** * Gets a data-flow node that may interprocedurally flow to the value escaping into this API component. */ - DataFlow::Node getAValueReachingRhs() { result = Impl::trackDefNode(this.asSink()) } + DataFlow::Node getAValueReachingSink() { result = Impl::trackDefNode(this.asSink()) } /** * Gets a call to a method on the receiver represented by this API component. From d15b90e21acdfebb75074756e278c4d5d38f90a1 Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 30 May 2022 13:31:31 +0200 Subject: [PATCH 038/246] Ruby: Add deprecation --- ruby/ql/lib/codeql/ruby/ApiGraphs.qll | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll index eaf6ecb009b..1759f42ad94 100644 --- a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll +++ b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll @@ -123,6 +123,18 @@ module API { */ DataFlow::Node getAValueReachingSink() { result = Impl::trackDefNode(this.asSink()) } + /** DEPRECATED. This predicate has been renamed to `getAValueReachableFromSource()`. */ + deprecated DataFlow::Node getAUse() { result = this.getAValueReachableFromSource() } + + /** DEPRECATED. This predicate has been renamed to `asSource()`. */ + deprecated DataFlow::LocalSourceNode getAnImmediateUse() { result = this.asSource() } + + /** DEPRECATED. This predicate has been renamed to `asSink()`. */ + deprecated DataFlow::Node getARhs() { result = this.asSink() } + + /** DEPRECATED. This predicate has been renamed to `getAValueReachingSink()`. */ + deprecated DataFlow::Node getAValueReachingRhs() { result = this.getAValueReachingSink() } + /** * Gets a call to a method on the receiver represented by this API component. */ From a1af9c3d7d23c82dfb5e2bfe6c0bae341aaca53a Mon Sep 17 00:00:00 2001 From: Asger F Date: Tue, 14 Jun 2022 11:02:17 +0200 Subject: [PATCH 039/246] Ruby: update predicate docs --- ruby/ql/lib/codeql/ruby/ApiGraphs.qll | 48 +++++++++++++++++++++------ 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll index 1759f42ad94..3be0e036b74 100644 --- a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll +++ b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll @@ -92,12 +92,10 @@ module API { */ class Node extends Impl::TApiNode { /** - * Gets a data-flow node corresponding to a use of the API component represented by this node. + * Gets a data-flow node where this value may flow after entering the current codebase. * - * For example, `Kernel.format "%s world!", "Hello"` is a use of the return of the `format` function of - * the `Kernel` module. - * - * This includes indirect uses found via data flow. + * This is similar to `asSource()` but additionally includes nodes that are transitively reachable by data flow. + * See `asSource()` for examples. */ DataFlow::Node getAValueReachableFromSource() { exists(DataFlow::LocalSourceNode src | Impl::use(this, src) | @@ -106,20 +104,50 @@ module API { } /** - * Gets an immediate use of the API component represented by this node. + * Gets a data-flow node where this value enters the current codebase. * - * Unlike `getAValueReachableFromSource()`, this predicate only gets the immediate references, not the indirect uses - * found via data flow. + * For example: + * ```ruby + * # API::getTopLevelMember("Foo").asSource() + * Foo + * + * # API::getTopLevelMember("Foo").getMethod("bar").getReturn().asSource() + * Foo.bar + * + * # 'x' is found by: + * # API::getTopLevelMember("Foo").getMethod("bar").getBlock().getParameter(0).asSource() + * Foo.bar do |x| + * end + * ``` */ DataFlow::LocalSourceNode asSource() { Impl::use(this, result) } /** - * Gets a data-flow node corresponding the value flowing into this API component. + * Gets a data-flow node where this value leaves the current codebase and flows into an + * external library (or in general, any external codebase). + * + * Concretely, this corresponds to an argument passed to a call to external code. + * + * For example: + * ```ruby + * # 'x' is found by: + * # API::getTopLevelMember("Foo").getMethod("bar").getParameter(0).asSink() + * Foo.bar(x) + * + * Foo.bar(-> { + * # 'x' is found by: + * # API::getTopLevelMember("Foo").getMethod("bar").getParameter(0).getReturn().asSink() + * x + * }) + * ``` */ DataFlow::Node asSink() { Impl::def(this, result) } /** - * Gets a data-flow node that may interprocedurally flow to the value escaping into this API component. + * Get a data-flow node that transitively flows to an external library (or in general, any external codebase). + * + * This is similar to `asSink()` but additionally includes nodes that transitively reach a sink by data flow. + * See `asSink()` for examples. */ DataFlow::Node getAValueReachingSink() { result = Impl::trackDefNode(this.asSink()) } From a2e2dcdfd5d743edd3eafd1bc34989f3332dcdb2 Mon Sep 17 00:00:00 2001 From: Brandon Stewart <20469703+boveus@users.noreply.github.com> Date: Tue, 21 Jun 2022 14:44:52 -0400 Subject: [PATCH 040/246] Make ActiveRecordInstanceMethodCall Public --- ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll b/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll index 0846e30d047..5809b35baf4 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll @@ -331,7 +331,7 @@ class ActiveRecordInstance extends DataFlow::Node { } // A call whose receiver may be an active record model object -private class ActiveRecordInstanceMethodCall extends DataFlow::CallNode { +class ActiveRecordInstanceMethodCall extends DataFlow::CallNode { private ActiveRecordInstance instance; ActiveRecordInstanceMethodCall() { this.getReceiver() = instance } From abdcfd55c3297384d9b56823738653231dbc28e4 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Wed, 22 Jun 2022 09:59:17 +0200 Subject: [PATCH 041/246] Python: `uncertainty` is treated as a certificate :O --- python/ql/test/experimental/dataflow/sensitive-data/test.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/python/ql/test/experimental/dataflow/sensitive-data/test.py b/python/ql/test/experimental/dataflow/sensitive-data/test.py index 626bc771118..ee93af4f627 100644 --- a/python/ql/test/experimental/dataflow/sensitive-data/test.py +++ b/python/ql/test/experimental/dataflow/sensitive-data/test.py @@ -56,6 +56,11 @@ getattr(foo, x) # $ SensitiveDataSource=password def my_func(password): # $ SensitiveDataSource=password print(password) # $ SensitiveUse=password +# FP where the `cert` in `uncertainty` makes us treat it like a certificate +# https://github.com/github/codeql/issues/9632 +def my_other_func(uncertainty): # $ SPURIOUS: SensitiveDataSource=certificate + print(uncertainty) # $ SPURIOUS: SensitiveUse=certificate + password = some_function() # $ SensitiveDataSource=password print(password) # $ SensitiveUse=password From 5dc2bb717abc7ed9740311d40bc18b6e0222f876 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Wed, 22 Jun 2022 11:02:00 +0200 Subject: [PATCH 042/246] Python: ignore common words (certain/concert) as sensitive source --- .../python/security/internal/SensitiveDataHeuristics.qll | 6 +++++- python/ql/test/experimental/dataflow/sensitive-data/test.py | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/python/ql/lib/semmle/python/security/internal/SensitiveDataHeuristics.qll b/python/ql/lib/semmle/python/security/internal/SensitiveDataHeuristics.qll index 96abac10da2..7a319f79303 100644 --- a/python/ql/lib/semmle/python/security/internal/SensitiveDataHeuristics.qll +++ b/python/ql/lib/semmle/python/security/internal/SensitiveDataHeuristics.qll @@ -96,10 +96,14 @@ module HeuristicNames { * Gets a regular expression that identifies strings that may indicate the presence of data * that is hashed or encrypted, and hence rendered non-sensitive, or contains special characters * suggesting nouns within the string do not represent the meaning of the whole string (e.g. a URL or a SQL query). + * + * We also filter out common words like `certain` and `concert`, since otherwise these could + * be matched by the certificate regular expressions. Same for `accountable` (account), or + * `secretarial` (secret). */ string notSensitiveRegexp() { result = - "(?is).*([^\\w$.-]|redact|censor|obfuscate|hash|md5|sha|random|((? Date: Wed, 22 Jun 2022 10:18:14 +0200 Subject: [PATCH 043/246] Python: `_` in var name not handled by sensitive-data-sources --- python/ql/test/experimental/dataflow/sensitive-data/test.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/python/ql/test/experimental/dataflow/sensitive-data/test.py b/python/ql/test/experimental/dataflow/sensitive-data/test.py index 857f5fd85a5..b150f2f1a73 100644 --- a/python/ql/test/experimental/dataflow/sensitive-data/test.py +++ b/python/ql/test/experimental/dataflow/sensitive-data/test.py @@ -37,6 +37,10 @@ f = not_found.get_passwd # $ SensitiveDataSource=password x = f() print(x) # $ SensitiveUse=password +# some prefixes makes us ignore it as a source +not_found.isSecret +not_found.is_secret # $ SPURIOUS: SensitiveDataSource=secret + def my_func(non_sensitive_name): x = non_sensitive_name() print(x) # $ SensitiveUse=password From 4be375521f791d6c5060944a3fa969fdef81e77c Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Wed, 22 Jun 2022 10:18:59 +0200 Subject: [PATCH 044/246] Python: Handle `_` in sensitive-data-sources --- .../semmle/python/security/internal/SensitiveDataHeuristics.qll | 2 +- python/ql/test/experimental/dataflow/sensitive-data/test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/ql/lib/semmle/python/security/internal/SensitiveDataHeuristics.qll b/python/ql/lib/semmle/python/security/internal/SensitiveDataHeuristics.qll index 7a319f79303..43f2c2436a1 100644 --- a/python/ql/lib/semmle/python/security/internal/SensitiveDataHeuristics.qll +++ b/python/ql/lib/semmle/python/security/internal/SensitiveDataHeuristics.qll @@ -50,7 +50,7 @@ module HeuristicNames { * Gets a regular expression that identifies strings that may indicate the presence of secret * or trusted data. */ - string maybeSecret() { result = "(?is).*((? Date: Wed, 22 Jun 2022 10:20:05 +0200 Subject: [PATCH 045/246] SensitiveDataHeuristics: sync --- .../security/internal/SensitiveDataHeuristics.qll | 8 ++++++-- .../ruby/security/internal/SensitiveDataHeuristics.qll | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/security/internal/SensitiveDataHeuristics.qll b/javascript/ql/lib/semmle/javascript/security/internal/SensitiveDataHeuristics.qll index 96abac10da2..43f2c2436a1 100644 --- a/javascript/ql/lib/semmle/javascript/security/internal/SensitiveDataHeuristics.qll +++ b/javascript/ql/lib/semmle/javascript/security/internal/SensitiveDataHeuristics.qll @@ -50,7 +50,7 @@ module HeuristicNames { * Gets a regular expression that identifies strings that may indicate the presence of secret * or trusted data. */ - string maybeSecret() { result = "(?is).*((? Date: Wed, 22 Jun 2022 11:14:05 +0200 Subject: [PATCH 046/246] Python/JS/Ruby: Add change-note --- .../ql/lib/change-notes/2022-06-22-sensitive-common-words.md | 4 ++++ .../ql/lib/change-notes/2022-06-22-sensitive-common-words.md | 4 ++++ ruby/ql/lib/change-notes/2022-06-22-sensitive-common-words.md | 4 ++++ 3 files changed, 12 insertions(+) create mode 100644 javascript/ql/lib/change-notes/2022-06-22-sensitive-common-words.md create mode 100644 python/ql/lib/change-notes/2022-06-22-sensitive-common-words.md create mode 100644 ruby/ql/lib/change-notes/2022-06-22-sensitive-common-words.md diff --git a/javascript/ql/lib/change-notes/2022-06-22-sensitive-common-words.md b/javascript/ql/lib/change-notes/2022-06-22-sensitive-common-words.md new file mode 100644 index 00000000000..9102d58abdb --- /dev/null +++ b/javascript/ql/lib/change-notes/2022-06-22-sensitive-common-words.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Improved modeling of sensitive data sources, so common words like `certain` and `secretary` are no longer considered a certificate and a secret (respectively). diff --git a/python/ql/lib/change-notes/2022-06-22-sensitive-common-words.md b/python/ql/lib/change-notes/2022-06-22-sensitive-common-words.md new file mode 100644 index 00000000000..9102d58abdb --- /dev/null +++ b/python/ql/lib/change-notes/2022-06-22-sensitive-common-words.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Improved modeling of sensitive data sources, so common words like `certain` and `secretary` are no longer considered a certificate and a secret (respectively). diff --git a/ruby/ql/lib/change-notes/2022-06-22-sensitive-common-words.md b/ruby/ql/lib/change-notes/2022-06-22-sensitive-common-words.md new file mode 100644 index 00000000000..9102d58abdb --- /dev/null +++ b/ruby/ql/lib/change-notes/2022-06-22-sensitive-common-words.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Improved modeling of sensitive data sources, so common words like `certain` and `secretary` are no longer considered a certificate and a secret (respectively). From 42929a70e870bd098d8da1bb6560e7858e2a99d8 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Wed, 22 Jun 2022 17:14:18 +0000 Subject: [PATCH 047/246] Swift: implement LambdaCall in dataflow library --- .../dataflow/internal/DataFlowPrivate.qll | 20 +++- .../dataflow/dataflow/DataFlow.expected | 99 +++++++++++++++++++ .../dataflow/dataflow/LocalFlow.expected | 28 ++++++ .../dataflow/dataflow/test.swift | 53 ++++++++++ 4 files changed, 196 insertions(+), 4 deletions(-) diff --git a/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowPrivate.qll b/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowPrivate.qll index 292193eda28..1e3eb87944e 100644 --- a/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowPrivate.qll +++ b/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowPrivate.qll @@ -157,7 +157,7 @@ private module ParameterNodes { override string toStringImpl() { result = param.toString() } override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { - exists(FuncDecl f, int index | + exists(Callable f, int index | c = TDataFlowFunc(f) and f.getParam(index) = param and pos = TPositionalParameter(index) @@ -375,13 +375,25 @@ class Unit extends TUnit { */ predicate isUnreachableInCall(Node n, DataFlowCall call) { none() } -newtype LambdaCallKind = TODO_TLambdaCallKind() +newtype LambdaCallKind = TLambdaCallKind() /** Holds if `creation` is an expression that creates a lambda of kind `kind` for `c`. */ -predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c) { none() } +predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c) { + kind = TLambdaCallKind() and + ( + // Closures + c.getUnderlyingCallable() = creation.asExpr() + or + // Reference to a function declaration + creation.asExpr().(DeclRefExpr).getDecl() = c.getUnderlyingCallable() + ) +} /** Holds if `call` is a lambda call of kind `kind` where `receiver` is the lambda expression. */ -predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) { none() } +predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) { + kind = TLambdaCallKind() and + receiver.asExpr() = call.asCall().getExpr().(ApplyExpr).getFunction() +} /** Extra data-flow steps needed for lambda flow analysis. */ predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preservesValue) { none() } diff --git a/swift/ql/test/library-tests/dataflow/dataflow/DataFlow.expected b/swift/ql/test/library-tests/dataflow/dataflow/DataFlow.expected index 0566e7e6eb7..1bcceb82f22 100644 --- a/swift/ql/test/library-tests/dataflow/dataflow/DataFlow.expected +++ b/swift/ql/test/library-tests/dataflow/dataflow/DataFlow.expected @@ -29,6 +29,42 @@ edges | test.swift:89:15:89:22 | call to source() : | test.swift:84:1:91:1 | arg[return] : | | test.swift:97:9:97:41 | arg : | test.swift:98:19:98:19 | x | | test.swift:104:9:104:54 | arg : | test.swift:105:19:105:19 | x | +| test.swift:109:9:109:14 | WriteDef : | test.swift:110:12:110:12 | arg : | +| test.swift:109:9:109:14 | arg : | test.swift:110:12:110:12 | arg : | +| test.swift:113:14:113:19 | WriteDef : | test.swift:114:19:114:19 | arg : | +| test.swift:113:14:113:19 | WriteDef : | test.swift:114:19:114:19 | arg : | +| test.swift:113:14:113:19 | arg : | test.swift:114:19:114:19 | arg : | +| test.swift:113:14:113:19 | arg : | test.swift:114:19:114:19 | arg : | +| test.swift:114:19:114:19 | arg : | test.swift:109:9:109:14 | WriteDef : | +| test.swift:114:19:114:19 | arg : | test.swift:109:9:109:14 | arg : | +| test.swift:114:19:114:19 | arg : | test.swift:114:12:114:22 | call to ... : | +| test.swift:114:19:114:19 | arg : | test.swift:114:12:114:22 | call to ... : | +| test.swift:114:19:114:19 | arg : | test.swift:123:10:123:13 | WriteDef : | +| test.swift:114:19:114:19 | arg : | test.swift:123:10:123:13 | i : | +| test.swift:118:18:118:25 | call to source() : | test.swift:119:31:119:31 | x : | +| test.swift:119:18:119:44 | call to forward(arg:lambda:) : | test.swift:120:15:120:15 | y | +| test.swift:119:31:119:31 | x : | test.swift:113:14:113:19 | WriteDef : | +| test.swift:119:31:119:31 | x : | test.swift:113:14:113:19 | arg : | +| test.swift:119:31:119:31 | x : | test.swift:119:18:119:44 | call to forward(arg:lambda:) : | +| test.swift:122:18:125:6 | call to forward(arg:lambda:) : | test.swift:126:15:126:15 | z | +| test.swift:122:31:122:38 | call to source() : | test.swift:113:14:113:19 | WriteDef : | +| test.swift:122:31:122:38 | call to source() : | test.swift:113:14:113:19 | arg : | +| test.swift:122:31:122:38 | call to source() : | test.swift:122:18:125:6 | call to forward(arg:lambda:) : | +| test.swift:123:10:123:13 | WriteDef : | test.swift:124:16:124:16 | i : | +| test.swift:123:10:123:13 | i : | test.swift:124:16:124:16 | i : | +| test.swift:142:10:142:13 | WriteDef : | test.swift:143:16:143:16 | i : | +| test.swift:142:10:142:13 | i : | test.swift:143:16:143:16 | i : | +| test.swift:145:23:145:30 | call to source() : | test.swift:142:10:142:13 | WriteDef : | +| test.swift:145:23:145:30 | call to source() : | test.swift:142:10:142:13 | i : | +| test.swift:145:23:145:30 | call to source() : | test.swift:145:15:145:31 | call to ... | +| test.swift:149:16:149:23 | call to source() : | test.swift:151:15:151:28 | call to ... | +| test.swift:149:16:149:23 | call to source() : | test.swift:159:16:159:29 | call to ... : | +| test.swift:154:10:154:13 | WriteDef : | test.swift:155:19:155:19 | i | +| test.swift:154:10:154:13 | i : | test.swift:155:19:155:19 | i | +| test.swift:157:16:157:23 | call to source() : | test.swift:154:10:154:13 | WriteDef : | +| test.swift:157:16:157:23 | call to source() : | test.swift:154:10:154:13 | i : | +| test.swift:159:16:159:29 | call to ... : | test.swift:154:10:154:13 | WriteDef : | +| test.swift:159:16:159:29 | call to ... : | test.swift:154:10:154:13 | i : | nodes | test.swift:6:19:6:26 | call to source() : | semmle.label | call to source() : | | test.swift:7:15:7:15 | t1 | semmle.label | t1 | @@ -72,9 +108,65 @@ nodes | test.swift:98:19:98:19 | x | semmle.label | x | | test.swift:104:9:104:54 | arg : | semmle.label | arg : | | test.swift:105:19:105:19 | x | semmle.label | x | +| test.swift:109:9:109:14 | WriteDef : | semmle.label | WriteDef : | +| test.swift:109:9:109:14 | WriteDef : | semmle.label | arg : | +| test.swift:109:9:109:14 | arg : | semmle.label | WriteDef : | +| test.swift:109:9:109:14 | arg : | semmle.label | arg : | +| test.swift:110:12:110:12 | arg : | semmle.label | arg : | +| test.swift:113:14:113:19 | WriteDef : | semmle.label | WriteDef : | +| test.swift:113:14:113:19 | WriteDef : | semmle.label | WriteDef : | +| test.swift:113:14:113:19 | WriteDef : | semmle.label | arg : | +| test.swift:113:14:113:19 | WriteDef : | semmle.label | arg : | +| test.swift:113:14:113:19 | arg : | semmle.label | WriteDef : | +| test.swift:113:14:113:19 | arg : | semmle.label | WriteDef : | +| test.swift:113:14:113:19 | arg : | semmle.label | arg : | +| test.swift:113:14:113:19 | arg : | semmle.label | arg : | +| test.swift:114:12:114:22 | call to ... : | semmle.label | call to ... : | +| test.swift:114:12:114:22 | call to ... : | semmle.label | call to ... : | +| test.swift:114:19:114:19 | arg : | semmle.label | arg : | +| test.swift:114:19:114:19 | arg : | semmle.label | arg : | +| test.swift:118:18:118:25 | call to source() : | semmle.label | call to source() : | +| test.swift:119:18:119:44 | call to forward(arg:lambda:) : | semmle.label | call to forward(arg:lambda:) : | +| test.swift:119:31:119:31 | x : | semmle.label | x : | +| test.swift:120:15:120:15 | y | semmle.label | y | +| test.swift:122:18:125:6 | call to forward(arg:lambda:) : | semmle.label | call to forward(arg:lambda:) : | +| test.swift:122:31:122:38 | call to source() : | semmle.label | call to source() : | +| test.swift:123:10:123:13 | WriteDef : | semmle.label | WriteDef : | +| test.swift:123:10:123:13 | WriteDef : | semmle.label | i : | +| test.swift:123:10:123:13 | i : | semmle.label | WriteDef : | +| test.swift:123:10:123:13 | i : | semmle.label | i : | +| test.swift:124:16:124:16 | i : | semmle.label | i : | +| test.swift:126:15:126:15 | z | semmle.label | z | +| test.swift:138:19:138:26 | call to source() | semmle.label | call to source() | +| test.swift:142:10:142:13 | WriteDef : | semmle.label | WriteDef : | +| test.swift:142:10:142:13 | WriteDef : | semmle.label | i : | +| test.swift:142:10:142:13 | i : | semmle.label | WriteDef : | +| test.swift:142:10:142:13 | i : | semmle.label | i : | +| test.swift:143:16:143:16 | i : | semmle.label | i : | +| test.swift:145:15:145:31 | call to ... | semmle.label | call to ... | +| test.swift:145:23:145:30 | call to source() : | semmle.label | call to source() : | +| test.swift:149:16:149:23 | call to source() : | semmle.label | call to source() : | +| test.swift:151:15:151:28 | call to ... | semmle.label | call to ... | +| test.swift:154:10:154:13 | WriteDef : | semmle.label | WriteDef : | +| test.swift:154:10:154:13 | WriteDef : | semmle.label | i : | +| test.swift:154:10:154:13 | i : | semmle.label | WriteDef : | +| test.swift:154:10:154:13 | i : | semmle.label | i : | +| test.swift:155:19:155:19 | i | semmle.label | i | +| test.swift:157:16:157:23 | call to source() : | semmle.label | call to source() : | +| test.swift:159:16:159:29 | call to ... : | semmle.label | call to ... : | subpaths | test.swift:75:21:75:22 | &... : | test.swift:65:16:65:28 | WriteDef : | test.swift:65:1:70:1 | arg2[return] : | test.swift:75:5:75:33 | arg2 : | | test.swift:75:21:75:22 | &... : | test.swift:65:16:65:28 | arg1 : | test.swift:65:1:70:1 | arg2[return] : | test.swift:75:5:75:33 | arg2 : | +| test.swift:114:19:114:19 | arg : | test.swift:109:9:109:14 | WriteDef : | test.swift:110:12:110:12 | arg : | test.swift:114:12:114:22 | call to ... : | +| test.swift:114:19:114:19 | arg : | test.swift:109:9:109:14 | arg : | test.swift:110:12:110:12 | arg : | test.swift:114:12:114:22 | call to ... : | +| test.swift:114:19:114:19 | arg : | test.swift:123:10:123:13 | WriteDef : | test.swift:124:16:124:16 | i : | test.swift:114:12:114:22 | call to ... : | +| test.swift:114:19:114:19 | arg : | test.swift:123:10:123:13 | i : | test.swift:124:16:124:16 | i : | test.swift:114:12:114:22 | call to ... : | +| test.swift:119:31:119:31 | x : | test.swift:113:14:113:19 | WriteDef : | test.swift:114:12:114:22 | call to ... : | test.swift:119:18:119:44 | call to forward(arg:lambda:) : | +| test.swift:119:31:119:31 | x : | test.swift:113:14:113:19 | arg : | test.swift:114:12:114:22 | call to ... : | test.swift:119:18:119:44 | call to forward(arg:lambda:) : | +| test.swift:122:31:122:38 | call to source() : | test.swift:113:14:113:19 | WriteDef : | test.swift:114:12:114:22 | call to ... : | test.swift:122:18:125:6 | call to forward(arg:lambda:) : | +| test.swift:122:31:122:38 | call to source() : | test.swift:113:14:113:19 | arg : | test.swift:114:12:114:22 | call to ... : | test.swift:122:18:125:6 | call to forward(arg:lambda:) : | +| test.swift:145:23:145:30 | call to source() : | test.swift:142:10:142:13 | WriteDef : | test.swift:143:16:143:16 | i : | test.swift:145:15:145:31 | call to ... | +| test.swift:145:23:145:30 | call to source() : | test.swift:142:10:142:13 | i : | test.swift:143:16:143:16 | i : | test.swift:145:15:145:31 | call to ... | #select | test.swift:7:15:7:15 | t1 | test.swift:6:19:6:26 | call to source() : | test.swift:7:15:7:15 | t1 | result | | test.swift:9:15:9:15 | t1 | test.swift:6:19:6:26 | call to source() : | test.swift:9:15:9:15 | t1 | result | @@ -88,3 +180,10 @@ subpaths | test.swift:98:19:98:19 | x | test.swift:81:11:81:18 | call to source() : | test.swift:98:19:98:19 | x | result | | test.swift:105:19:105:19 | x | test.swift:86:15:86:22 | call to source() : | test.swift:105:19:105:19 | x | result | | test.swift:105:19:105:19 | x | test.swift:89:15:89:22 | call to source() : | test.swift:105:19:105:19 | x | result | +| test.swift:120:15:120:15 | y | test.swift:118:18:118:25 | call to source() : | test.swift:120:15:120:15 | y | result | +| test.swift:126:15:126:15 | z | test.swift:122:31:122:38 | call to source() : | test.swift:126:15:126:15 | z | result | +| test.swift:138:19:138:26 | call to source() | test.swift:138:19:138:26 | call to source() | test.swift:138:19:138:26 | call to source() | result | +| test.swift:145:15:145:31 | call to ... | test.swift:145:23:145:30 | call to source() : | test.swift:145:15:145:31 | call to ... | result | +| test.swift:151:15:151:28 | call to ... | test.swift:149:16:149:23 | call to source() : | test.swift:151:15:151:28 | call to ... | result | +| test.swift:155:19:155:19 | i | test.swift:149:16:149:23 | call to source() : | test.swift:155:19:155:19 | i | result | +| test.swift:155:19:155:19 | i | test.swift:157:16:157:23 | call to source() : | test.swift:155:19:155:19 | i | result | diff --git a/swift/ql/test/library-tests/dataflow/dataflow/LocalFlow.expected b/swift/ql/test/library-tests/dataflow/dataflow/LocalFlow.expected index e84385199d0..54423a6d8d6 100644 --- a/swift/ql/test/library-tests/dataflow/dataflow/LocalFlow.expected +++ b/swift/ql/test/library-tests/dataflow/dataflow/LocalFlow.expected @@ -74,3 +74,31 @@ | test.swift:104:9:104:54 | WriteDef | test.swift:105:19:105:19 | x | | test.swift:104:9:104:54 | arg | test.swift:104:9:104:54 | WriteDef | | test.swift:104:41:104:41 | x | test.swift:104:40:104:41 | &... | +| test.swift:109:9:109:14 | WriteDef | test.swift:110:12:110:12 | arg | +| test.swift:109:9:109:14 | arg | test.swift:110:12:110:12 | arg | +| test.swift:113:14:113:19 | WriteDef | test.swift:114:19:114:19 | arg | +| test.swift:113:14:113:19 | arg | test.swift:114:19:114:19 | arg | +| test.swift:113:24:113:41 | WriteDef | test.swift:114:12:114:12 | lambda | +| test.swift:113:24:113:41 | lambda | test.swift:114:12:114:12 | lambda | +| test.swift:118:9:118:12 | WriteDef | test.swift:119:31:119:31 | x | +| test.swift:118:18:118:25 | call to source() | test.swift:118:9:118:12 | WriteDef | +| test.swift:119:9:119:12 | WriteDef | test.swift:120:15:120:15 | y | +| test.swift:119:18:119:44 | call to forward(arg:lambda:) | test.swift:119:9:119:12 | WriteDef | +| test.swift:122:9:122:12 | WriteDef | test.swift:126:15:126:15 | z | +| test.swift:122:18:125:6 | call to forward(arg:lambda:) | test.swift:122:9:122:12 | WriteDef | +| test.swift:123:10:123:13 | WriteDef | test.swift:124:16:124:16 | i | +| test.swift:123:10:123:13 | i | test.swift:124:16:124:16 | i | +| test.swift:128:9:128:16 | WriteDef | test.swift:132:15:132:15 | clean | +| test.swift:128:22:131:6 | call to forward(arg:lambda:) | test.swift:128:9:128:16 | WriteDef | +| test.swift:141:9:141:9 | WriteDef | test.swift:145:15:145:15 | lambda2 | +| test.swift:141:19:144:5 | { ... } | test.swift:141:9:141:9 | WriteDef | +| test.swift:142:10:142:13 | WriteDef | test.swift:143:16:143:16 | i | +| test.swift:142:10:142:13 | i | test.swift:143:16:143:16 | i | +| test.swift:147:9:147:9 | WriteDef | test.swift:151:15:151:15 | lambdaSource | +| test.swift:147:24:150:5 | { ... } | test.swift:147:9:147:9 | WriteDef | +| test.swift:151:15:151:15 | lambdaSource | test.swift:159:16:159:16 | lambdaSource | +| test.swift:153:9:153:9 | WriteDef | test.swift:157:5:157:5 | lambdaSink | +| test.swift:153:22:156:5 | { ... } | test.swift:153:9:153:9 | WriteDef | +| test.swift:154:10:154:13 | WriteDef | test.swift:155:19:155:19 | i | +| test.swift:154:10:154:13 | i | test.swift:155:19:155:19 | i | +| test.swift:157:5:157:5 | lambdaSink | test.swift:159:5:159:5 | lambdaSink | diff --git a/swift/ql/test/library-tests/dataflow/dataflow/test.swift b/swift/ql/test/library-tests/dataflow/dataflow/test.swift index b8ff57a631f..61400c2aadc 100644 --- a/swift/ql/test/library-tests/dataflow/dataflow/test.swift +++ b/swift/ql/test/library-tests/dataflow/dataflow/test.swift @@ -105,3 +105,56 @@ func inoutUser2(bool: Bool) { sink(arg: x) // tainted by two sources } } + +func id(arg: Int) -> Int { + return arg +} + +func forward(arg: Int, lambda: (Int) -> Int) -> Int { + return lambda(arg) +} + +func forwarder() { + var x: Int = source() + var y: Int = forward(arg: x, lambda: id) + sink(arg: y) + + var z: Int = forward(arg: source(), lambda: { + (i: Int) -> Int in + return i + }) + sink(arg: z) + + var clean: Int = forward(arg: source(), lambda: { + (i: Int) -> Int in + return 0 + }) + sink(arg: clean) +} + +func lambdaFlows() { + var lambda1 = { + () -> Void in + sink(arg: source()) + } + + var lambda2 = { + (i: Int) -> Int in + return i + } + sink(arg: lambda2(source())) + + var lambdaSource = { + () -> Int in + return source() + } + sink(arg: lambdaSource()) + + var lambdaSink = { + (i: Int) -> Void in + sink(arg: i) + } + lambdaSink(source()) + + lambdaSink(lambdaSource()) +} \ No newline at end of file From 28d801fde33870a5426c1c8feabdeeb63c934433 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Mon, 20 Jun 2022 16:53:55 +0100 Subject: [PATCH 048/246] Swift: CWE-135 query sources and sinks. --- .../CWE-135/StringLengthConflation.ql | 74 ++++++++++++++++++- .../CWE-135/StringLengthConflation.expected | 46 +++++++++++- 2 files changed, 118 insertions(+), 2 deletions(-) diff --git a/swift/ql/src/queries/Security/CWE-135/StringLengthConflation.ql b/swift/ql/src/queries/Security/CWE-135/StringLengthConflation.ql index a2d06a31331..dd4b35c4ca3 100644 --- a/swift/ql/src/queries/Security/CWE-135/StringLengthConflation.ql +++ b/swift/ql/src/queries/Security/CWE-135/StringLengthConflation.ql @@ -12,4 +12,76 @@ import swift -select "TODO" +predicate isSource(Expr e) { + // result of a call to to `String.count` + exists(MemberRefExpr member | + member.getBaseExpr().getType().toString() = "String" and // TODO: use of toString + member.getMember().toString() = "count" and // TODO: use of toString + e = member + ) + // TODO: other sources such as NSString.length, with different set of sinks +} + +predicate isSink(Expr e) { + // arguments to method calls... + exists( + string className, string methodName, string argName, ClassDecl c, AbstractFunctionDecl f, + CallExpr call, int arg + | + ( + // `NSRange.init` + className = "NSRange" and + methodName = "init" and + argName = ["location", "length"] + or + // `NSString.character` + className = ["NSString", "NSMutableString"] and + methodName = "character" and + argName = "at" + or + // `NSString.character` + className = ["NSString", "NSMutableString"] and + methodName = "substring" and + argName = ["from", "to"] + or + // `NSMutableString.insert` + className = "NSMutableString" and + methodName = "insert" and + argName = "at" + ) and + c.toString() = className and // TODO: use of toString + c.getAMember() = f and // TODO: will this even work if its defined in a parent class? + call.getFunction().(ApplyExpr).getFunction().(DeclRefExpr).getDecl() = f and + call.getFunction().(ApplyExpr).getFunction().toString() = methodName and // TODO: use of toString + call.getFunction() + .(ApplyExpr) + .getFunction() + .(DeclRefExpr) + .getDecl() + .(AbstractFunctionDecl) + .getParam(arg) + .getName() = argName and + call.getArgument(arg).getExpr() = e + ) + or + // arguments to function calls... + exists(string funcName, string argName, CallExpr call, int arg | + // `NSMakeRange` + funcName = "NSMakeRange" and + argName = ["loc", "len"] and + call.getStaticTarget().getName() = funcName and + call.getStaticTarget().getParam(arg).getName() = argName and + call.getArgument(arg).getExpr() = e + ) +} + +string describe(Element e) { + isSource(e) and result = "isSource" + or + isSink(e) and result = "isSink" + or + isSource(e) and isSink(e) and result = "***RESULT***" +} + +from Locatable e +select e.getLocation(), e, strictconcat(describe(e), ", ") diff --git a/swift/ql/test/query-tests/Security/CWE-135/StringLengthConflation.expected b/swift/ql/test/query-tests/Security/CWE-135/StringLengthConflation.expected index 583b1a97a98..7c1ab32c6e9 100644 --- a/swift/ql/test/query-tests/Security/CWE-135/StringLengthConflation.expected +++ b/swift/ql/test/query-tests/Security/CWE-135/StringLengthConflation.expected @@ -1 +1,45 @@ -| TODO | +| StringLengthConflation.swift:10:37:10:44 | Location | StringLengthConflation.swift:10:37:10:44 | .count | isSource | +| StringLengthConflation.swift:21:37:21:44 | Location | StringLengthConflation.swift:21:37:21:44 | .count | isSource | +| StringLengthConflation.swift:38:80:38:80 | Location | StringLengthConflation.swift:38:80:38:80 | loc | isSink | +| StringLengthConflation.swift:38:93:38:93 | Location | StringLengthConflation.swift:38:93:38:93 | len | isSink | +| StringLengthConflation.swift:47:20:47:22 | Location | StringLengthConflation.swift:47:20:47:22 | .count | isSource | +| StringLengthConflation.swift:52:43:52:45 | Location | StringLengthConflation.swift:52:43:52:45 | .count | isSource | +| StringLengthConflation.swift:59:47:59:49 | Location | StringLengthConflation.swift:59:47:59:49 | .count | isSource | +| StringLengthConflation.swift:64:33:64:35 | Location | StringLengthConflation.swift:64:33:64:35 | .count | isSource | +| StringLengthConflation.swift:71:30:71:30 | Location | StringLengthConflation.swift:71:30:71:30 | 0 | isSink | +| StringLengthConflation.swift:71:33:71:36 | Location | StringLengthConflation.swift:71:33:71:36 | .length | isSink | +| StringLengthConflation.swift:72:30:72:30 | Location | StringLengthConflation.swift:72:30:72:30 | 0 | isSink | +| StringLengthConflation.swift:72:33:72:35 | Location | StringLengthConflation.swift:72:33:72:35 | .count | ***RESULT***, isSink, isSource | +| StringLengthConflation.swift:73:30:73:30 | Location | StringLengthConflation.swift:73:30:73:30 | 0 | isSink | +| StringLengthConflation.swift:73:33:73:46 | Location | StringLengthConflation.swift:73:33:73:46 | .count | isSink | +| StringLengthConflation.swift:74:30:74:30 | Location | StringLengthConflation.swift:74:30:74:30 | 0 | isSink | +| StringLengthConflation.swift:74:33:74:78 | Location | StringLengthConflation.swift:74:33:74:78 | call to ... | isSink | +| StringLengthConflation.swift:77:36:77:36 | Location | StringLengthConflation.swift:77:36:77:36 | 0 | isSink | +| StringLengthConflation.swift:77:47:77:50 | Location | StringLengthConflation.swift:77:47:77:50 | .length | isSink | +| StringLengthConflation.swift:78:36:78:36 | Location | StringLengthConflation.swift:78:36:78:36 | 0 | isSink | +| StringLengthConflation.swift:78:47:78:49 | Location | StringLengthConflation.swift:78:47:78:49 | .count | ***RESULT***, isSink, isSource | +| StringLengthConflation.swift:83:28:83:30 | Location | StringLengthConflation.swift:83:28:83:30 | .count | isSource | +| StringLengthConflation.swift:87:27:87:29 | Location | StringLengthConflation.swift:87:27:87:29 | .count | isSource | +| StringLengthConflation.swift:91:25:91:27 | Location | StringLengthConflation.swift:91:25:91:27 | .count | isSource | +| StringLengthConflation.swift:95:25:95:27 | Location | StringLengthConflation.swift:95:25:95:27 | .count | isSource | +| StringLengthConflation.swift:99:34:99:46 | Location | StringLengthConflation.swift:99:34:99:46 | ... call to - ... | isSink | +| StringLengthConflation.swift:100:36:100:49 | Location | StringLengthConflation.swift:100:36:100:49 | ... call to - ... | isSink | +| StringLengthConflation.swift:101:34:101:36 | Location | StringLengthConflation.swift:101:34:101:36 | .count | isSource | +| StringLengthConflation.swift:101:34:101:44 | Location | StringLengthConflation.swift:101:34:101:44 | ... call to - ... | isSink | +| StringLengthConflation.swift:102:36:102:38 | Location | StringLengthConflation.swift:102:36:102:38 | .count | isSource | +| StringLengthConflation.swift:102:36:102:46 | Location | StringLengthConflation.swift:102:36:102:46 | ... call to - ... | isSink | +| StringLengthConflation.swift:105:36:105:48 | Location | StringLengthConflation.swift:105:36:105:48 | ... call to - ... | isSink | +| StringLengthConflation.swift:106:38:106:51 | Location | StringLengthConflation.swift:106:38:106:51 | ... call to - ... | isSink | +| StringLengthConflation.swift:107:36:107:38 | Location | StringLengthConflation.swift:107:36:107:38 | .count | isSource | +| StringLengthConflation.swift:107:36:107:46 | Location | StringLengthConflation.swift:107:36:107:46 | ... call to - ... | isSink | +| StringLengthConflation.swift:108:38:108:40 | Location | StringLengthConflation.swift:108:38:108:40 | .count | isSource | +| StringLengthConflation.swift:108:38:108:48 | Location | StringLengthConflation.swift:108:38:108:48 | ... call to - ... | isSink | +| StringLengthConflation.swift:111:34:111:46 | Location | StringLengthConflation.swift:111:34:111:46 | ... call to - ... | isSink | +| StringLengthConflation.swift:112:36:112:49 | Location | StringLengthConflation.swift:112:36:112:49 | ... call to - ... | isSink | +| StringLengthConflation.swift:113:34:113:36 | Location | StringLengthConflation.swift:113:34:113:36 | .count | isSource | +| StringLengthConflation.swift:113:34:113:44 | Location | StringLengthConflation.swift:113:34:113:44 | ... call to - ... | isSink | +| StringLengthConflation.swift:114:36:114:38 | Location | StringLengthConflation.swift:114:36:114:38 | .count | isSource | +| StringLengthConflation.swift:114:36:114:46 | Location | StringLengthConflation.swift:114:36:114:46 | ... call to - ... | isSink | +| StringLengthConflation.swift:118:28:118:41 | Location | StringLengthConflation.swift:118:28:118:41 | ... call to - ... | isSink | +| StringLengthConflation.swift:120:28:120:30 | Location | StringLengthConflation.swift:120:28:120:30 | .count | isSource | +| StringLengthConflation.swift:120:28:120:38 | Location | StringLengthConflation.swift:120:28:120:38 | ... call to - ... | isSink | From da7f49155d201672d46b3366160c7fd0d1b5d7bf Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Wed, 22 Jun 2022 15:55:50 +0100 Subject: [PATCH 049/246] Swift: Use dataflow. --- .../CWE-135/StringLengthConflation.ql | 27 ++++++---- .../CWE-135/StringLengthConflation.expected | 51 +++---------------- 2 files changed, 23 insertions(+), 55 deletions(-) diff --git a/swift/ql/src/queries/Security/CWE-135/StringLengthConflation.ql b/swift/ql/src/queries/Security/CWE-135/StringLengthConflation.ql index dd4b35c4ca3..e18bc2b7930 100644 --- a/swift/ql/src/queries/Security/CWE-135/StringLengthConflation.ql +++ b/swift/ql/src/queries/Security/CWE-135/StringLengthConflation.ql @@ -11,8 +11,10 @@ */ import swift +import codeql.swift.dataflow.DataFlow +import DataFlow::PathGraph -predicate isSource(Expr e) { +predicate isSource0(Expr e) { // result of a call to to `String.count` exists(MemberRefExpr member | member.getBaseExpr().getType().toString() = "String" and // TODO: use of toString @@ -22,7 +24,7 @@ predicate isSource(Expr e) { // TODO: other sources such as NSString.length, with different set of sinks } -predicate isSink(Expr e) { +predicate isSink0(Expr e) { // arguments to method calls... exists( string className, string methodName, string argName, ClassDecl c, AbstractFunctionDecl f, @@ -75,13 +77,18 @@ predicate isSink(Expr e) { ) } -string describe(Element e) { - isSource(e) and result = "isSource" - or - isSink(e) and result = "isSink" - or - isSource(e) and isSink(e) and result = "***RESULT***" +class StringLengthConflationConfiguration extends DataFlow::Configuration { + StringLengthConflationConfiguration() { this = "StringLengthConflationConfiguration" } + + override predicate isSource(DataFlow::Node node, string flowstate) { + isSource0(node.asExpr()) and flowstate = "String" + } + + override predicate isSink(DataFlow::Node node, string flowstate) { + isSink0(node.asExpr()) and flowstate = "String" + } } -from Locatable e -select e.getLocation(), e, strictconcat(describe(e), ", ") +from StringLengthConflationConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink +where config.hasFlowPath(source, sink) +select sink, source, sink, "RESULT" diff --git a/swift/ql/test/query-tests/Security/CWE-135/StringLengthConflation.expected b/swift/ql/test/query-tests/Security/CWE-135/StringLengthConflation.expected index 7c1ab32c6e9..1237d812a43 100644 --- a/swift/ql/test/query-tests/Security/CWE-135/StringLengthConflation.expected +++ b/swift/ql/test/query-tests/Security/CWE-135/StringLengthConflation.expected @@ -1,45 +1,6 @@ -| StringLengthConflation.swift:10:37:10:44 | Location | StringLengthConflation.swift:10:37:10:44 | .count | isSource | -| StringLengthConflation.swift:21:37:21:44 | Location | StringLengthConflation.swift:21:37:21:44 | .count | isSource | -| StringLengthConflation.swift:38:80:38:80 | Location | StringLengthConflation.swift:38:80:38:80 | loc | isSink | -| StringLengthConflation.swift:38:93:38:93 | Location | StringLengthConflation.swift:38:93:38:93 | len | isSink | -| StringLengthConflation.swift:47:20:47:22 | Location | StringLengthConflation.swift:47:20:47:22 | .count | isSource | -| StringLengthConflation.swift:52:43:52:45 | Location | StringLengthConflation.swift:52:43:52:45 | .count | isSource | -| StringLengthConflation.swift:59:47:59:49 | Location | StringLengthConflation.swift:59:47:59:49 | .count | isSource | -| StringLengthConflation.swift:64:33:64:35 | Location | StringLengthConflation.swift:64:33:64:35 | .count | isSource | -| StringLengthConflation.swift:71:30:71:30 | Location | StringLengthConflation.swift:71:30:71:30 | 0 | isSink | -| StringLengthConflation.swift:71:33:71:36 | Location | StringLengthConflation.swift:71:33:71:36 | .length | isSink | -| StringLengthConflation.swift:72:30:72:30 | Location | StringLengthConflation.swift:72:30:72:30 | 0 | isSink | -| StringLengthConflation.swift:72:33:72:35 | Location | StringLengthConflation.swift:72:33:72:35 | .count | ***RESULT***, isSink, isSource | -| StringLengthConflation.swift:73:30:73:30 | Location | StringLengthConflation.swift:73:30:73:30 | 0 | isSink | -| StringLengthConflation.swift:73:33:73:46 | Location | StringLengthConflation.swift:73:33:73:46 | .count | isSink | -| StringLengthConflation.swift:74:30:74:30 | Location | StringLengthConflation.swift:74:30:74:30 | 0 | isSink | -| StringLengthConflation.swift:74:33:74:78 | Location | StringLengthConflation.swift:74:33:74:78 | call to ... | isSink | -| StringLengthConflation.swift:77:36:77:36 | Location | StringLengthConflation.swift:77:36:77:36 | 0 | isSink | -| StringLengthConflation.swift:77:47:77:50 | Location | StringLengthConflation.swift:77:47:77:50 | .length | isSink | -| StringLengthConflation.swift:78:36:78:36 | Location | StringLengthConflation.swift:78:36:78:36 | 0 | isSink | -| StringLengthConflation.swift:78:47:78:49 | Location | StringLengthConflation.swift:78:47:78:49 | .count | ***RESULT***, isSink, isSource | -| StringLengthConflation.swift:83:28:83:30 | Location | StringLengthConflation.swift:83:28:83:30 | .count | isSource | -| StringLengthConflation.swift:87:27:87:29 | Location | StringLengthConflation.swift:87:27:87:29 | .count | isSource | -| StringLengthConflation.swift:91:25:91:27 | Location | StringLengthConflation.swift:91:25:91:27 | .count | isSource | -| StringLengthConflation.swift:95:25:95:27 | Location | StringLengthConflation.swift:95:25:95:27 | .count | isSource | -| StringLengthConflation.swift:99:34:99:46 | Location | StringLengthConflation.swift:99:34:99:46 | ... call to - ... | isSink | -| StringLengthConflation.swift:100:36:100:49 | Location | StringLengthConflation.swift:100:36:100:49 | ... call to - ... | isSink | -| StringLengthConflation.swift:101:34:101:36 | Location | StringLengthConflation.swift:101:34:101:36 | .count | isSource | -| StringLengthConflation.swift:101:34:101:44 | Location | StringLengthConflation.swift:101:34:101:44 | ... call to - ... | isSink | -| StringLengthConflation.swift:102:36:102:38 | Location | StringLengthConflation.swift:102:36:102:38 | .count | isSource | -| StringLengthConflation.swift:102:36:102:46 | Location | StringLengthConflation.swift:102:36:102:46 | ... call to - ... | isSink | -| StringLengthConflation.swift:105:36:105:48 | Location | StringLengthConflation.swift:105:36:105:48 | ... call to - ... | isSink | -| StringLengthConflation.swift:106:38:106:51 | Location | StringLengthConflation.swift:106:38:106:51 | ... call to - ... | isSink | -| StringLengthConflation.swift:107:36:107:38 | Location | StringLengthConflation.swift:107:36:107:38 | .count | isSource | -| StringLengthConflation.swift:107:36:107:46 | Location | StringLengthConflation.swift:107:36:107:46 | ... call to - ... | isSink | -| StringLengthConflation.swift:108:38:108:40 | Location | StringLengthConflation.swift:108:38:108:40 | .count | isSource | -| StringLengthConflation.swift:108:38:108:48 | Location | StringLengthConflation.swift:108:38:108:48 | ... call to - ... | isSink | -| StringLengthConflation.swift:111:34:111:46 | Location | StringLengthConflation.swift:111:34:111:46 | ... call to - ... | isSink | -| StringLengthConflation.swift:112:36:112:49 | Location | StringLengthConflation.swift:112:36:112:49 | ... call to - ... | isSink | -| StringLengthConflation.swift:113:34:113:36 | Location | StringLengthConflation.swift:113:34:113:36 | .count | isSource | -| StringLengthConflation.swift:113:34:113:44 | Location | StringLengthConflation.swift:113:34:113:44 | ... call to - ... | isSink | -| StringLengthConflation.swift:114:36:114:38 | Location | StringLengthConflation.swift:114:36:114:38 | .count | isSource | -| StringLengthConflation.swift:114:36:114:46 | Location | StringLengthConflation.swift:114:36:114:46 | ... call to - ... | isSink | -| StringLengthConflation.swift:118:28:118:41 | Location | StringLengthConflation.swift:118:28:118:41 | ... call to - ... | isSink | -| StringLengthConflation.swift:120:28:120:30 | Location | StringLengthConflation.swift:120:28:120:30 | .count | isSource | -| StringLengthConflation.swift:120:28:120:38 | Location | StringLengthConflation.swift:120:28:120:38 | ... call to - ... | isSink | +edges +nodes +| StringLengthConflation.swift:78:47:78:49 | .count | semmle.label | .count | +subpaths +#select +| StringLengthConflation.swift:78:47:78:49 | .count | StringLengthConflation.swift:78:47:78:49 | .count | StringLengthConflation.swift:78:47:78:49 | .count | RESULT | From 19026e9ed57ebd221d1877b7741d662ebc7ae788 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Wed, 22 Jun 2022 19:10:05 +0100 Subject: [PATCH 050/246] Swift: Work around toString change. --- swift/ql/src/queries/Security/CWE-135/StringLengthConflation.ql | 2 +- .../Security/CWE-135/StringLengthConflation.expected | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/swift/ql/src/queries/Security/CWE-135/StringLengthConflation.ql b/swift/ql/src/queries/Security/CWE-135/StringLengthConflation.ql index e18bc2b7930..f89eec97d09 100644 --- a/swift/ql/src/queries/Security/CWE-135/StringLengthConflation.ql +++ b/swift/ql/src/queries/Security/CWE-135/StringLengthConflation.ql @@ -71,7 +71,7 @@ predicate isSink0(Expr e) { // `NSMakeRange` funcName = "NSMakeRange" and argName = ["loc", "len"] and - call.getStaticTarget().getName() = funcName and + call.getStaticTarget().getName().matches(funcName + "%") and call.getStaticTarget().getParam(arg).getName() = argName and call.getArgument(arg).getExpr() = e ) diff --git a/swift/ql/test/query-tests/Security/CWE-135/StringLengthConflation.expected b/swift/ql/test/query-tests/Security/CWE-135/StringLengthConflation.expected index 1237d812a43..7b416fcc95e 100644 --- a/swift/ql/test/query-tests/Security/CWE-135/StringLengthConflation.expected +++ b/swift/ql/test/query-tests/Security/CWE-135/StringLengthConflation.expected @@ -1,6 +1,8 @@ edges nodes +| StringLengthConflation.swift:72:33:72:35 | .count | semmle.label | .count | | StringLengthConflation.swift:78:47:78:49 | .count | semmle.label | .count | subpaths #select +| StringLengthConflation.swift:72:33:72:35 | .count | StringLengthConflation.swift:72:33:72:35 | .count | StringLengthConflation.swift:72:33:72:35 | .count | RESULT | | StringLengthConflation.swift:78:47:78:49 | .count | StringLengthConflation.swift:78:47:78:49 | .count | StringLengthConflation.swift:78:47:78:49 | .count | RESULT | From 07b89b89d7b37880b6026c4df5da46e66b25afe3 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Wed, 22 Jun 2022 19:35:37 +0100 Subject: [PATCH 051/246] Swift: Clean up a bit. --- .../CWE-135/StringLengthConflation.ql | 124 +++++++++--------- 1 file changed, 59 insertions(+), 65 deletions(-) diff --git a/swift/ql/src/queries/Security/CWE-135/StringLengthConflation.ql b/swift/ql/src/queries/Security/CWE-135/StringLengthConflation.ql index f89eec97d09..d85259eead2 100644 --- a/swift/ql/src/queries/Security/CWE-135/StringLengthConflation.ql +++ b/swift/ql/src/queries/Security/CWE-135/StringLengthConflation.ql @@ -14,78 +14,72 @@ import swift import codeql.swift.dataflow.DataFlow import DataFlow::PathGraph -predicate isSource0(Expr e) { - // result of a call to to `String.count` - exists(MemberRefExpr member | - member.getBaseExpr().getType().toString() = "String" and // TODO: use of toString - member.getMember().toString() = "count" and // TODO: use of toString - e = member - ) - // TODO: other sources such as NSString.length, with different set of sinks -} - -predicate isSink0(Expr e) { - // arguments to method calls... - exists( - string className, string methodName, string argName, ClassDecl c, AbstractFunctionDecl f, - CallExpr call, int arg - | - ( - // `NSRange.init` - className = "NSRange" and - methodName = "init" and - argName = ["location", "length"] - or - // `NSString.character` - className = ["NSString", "NSMutableString"] and - methodName = "character" and - argName = "at" - or - // `NSString.character` - className = ["NSString", "NSMutableString"] and - methodName = "substring" and - argName = ["from", "to"] - or - // `NSMutableString.insert` - className = "NSMutableString" and - methodName = "insert" and - argName = "at" - ) and - c.toString() = className and // TODO: use of toString - c.getAMember() = f and // TODO: will this even work if its defined in a parent class? - call.getFunction().(ApplyExpr).getFunction().(DeclRefExpr).getDecl() = f and - call.getFunction().(ApplyExpr).getFunction().toString() = methodName and // TODO: use of toString - call.getFunction() - .(ApplyExpr) - .getFunction() - .(DeclRefExpr) - .getDecl() - .(AbstractFunctionDecl) - .getParam(arg) - .getName() = argName and - call.getArgument(arg).getExpr() = e - ) - or - // arguments to function calls... - exists(string funcName, string argName, CallExpr call, int arg | - // `NSMakeRange` - funcName = "NSMakeRange" and - argName = ["loc", "len"] and - call.getStaticTarget().getName().matches(funcName + "%") and - call.getStaticTarget().getParam(arg).getName() = argName and - call.getArgument(arg).getExpr() = e - ) -} - class StringLengthConflationConfiguration extends DataFlow::Configuration { StringLengthConflationConfiguration() { this = "StringLengthConflationConfiguration" } override predicate isSource(DataFlow::Node node, string flowstate) { - isSource0(node.asExpr()) and flowstate = "String" + // result of a call to to `String.count` + exists(MemberRefExpr member | + member.getBaseExpr().getType().toString() = "String" and // TODO: use of toString + member.getMember().toString() = "count" and // TODO: use of toString + node.asExpr() = member and + flowstate = "String" + ) } override predicate isSink(DataFlow::Node node, string flowstate) { - isSink0(node.asExpr()) and flowstate = "String" + // arguments to method calls... + exists( + string className, string methodName, string argName, ClassDecl c, AbstractFunctionDecl f, + CallExpr call, int arg + | + ( + // `NSRange.init` + className = "NSRange" and + methodName = "init" and + argName = ["location", "length"] + or + // `NSString.character` + className = ["NSString", "NSMutableString"] and + methodName = "character" and + argName = "at" + or + // `NSString.character` + className = ["NSString", "NSMutableString"] and + methodName = "substring" and + argName = ["from", "to"] + or + // `NSMutableString.insert` + className = "NSMutableString" and + methodName = "insert" and + argName = "at" + ) and + c.toString() = className and // TODO: use of toString + c.getAMember() = f and // TODO: will this even work if its defined in a parent class? + call.getFunction().(ApplyExpr).getFunction().(DeclRefExpr).getDecl() = f and + call.getFunction().(ApplyExpr).getFunction().toString() = methodName and // TODO: use of toString + call.getFunction() + .(ApplyExpr) + .getFunction() + .(DeclRefExpr) + .getDecl() + .(AbstractFunctionDecl) + .getParam(arg) + .getName() = argName and + call.getArgument(arg).getExpr() = node.asExpr() and + flowstate = "String" // `String` length flowing into `NSString` + ) + or + // arguments to function calls... + exists(string funcName, string argName, CallExpr call, int arg | + // `NSMakeRange` + funcName = "NSMakeRange" and + argName = ["loc", "len"] and + call.getStaticTarget().getName().matches(funcName + "%") and + call.getStaticTarget().getParam(arg).getName() = argName and + call.getArgument(arg).getExpr() = node.asExpr() and + flowstate = "String" // `String` length flowing into `NSString` + ) } } From decb1364714abea072ff2837dcdfe3f7db70620f Mon Sep 17 00:00:00 2001 From: AlexDenisov Date: Thu, 23 Jun 2022 07:23:17 +0200 Subject: [PATCH 052/246] Update swift/extractor/SwiftExtractor.cpp Co-authored-by: Paolo Tranquilli --- swift/extractor/SwiftExtractor.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/swift/extractor/SwiftExtractor.cpp b/swift/extractor/SwiftExtractor.cpp index c2bb7c6307e..ef05648c6f3 100644 --- a/swift/extractor/SwiftExtractor.cpp +++ b/swift/extractor/SwiftExtractor.cpp @@ -149,11 +149,8 @@ void codeql::extractSwiftFiles(const SwiftExtractorConfiguration& config, extractDeclarations(config, decls, compiler, *module); } else { for (auto file : module->getFiles()) { - if (!llvm::isa(file)) { - continue; - } - auto sourceFile = llvm::cast(file); - if (sourceFiles.count(sourceFile->getFilename().str()) == 0) { + auto sourceFile = llvm::dyn_cast(file); + if (!sourceFile || sourceFiles.count(sourceFile->getFilename().str()) == 0) { continue; } archiveFile(config, *sourceFile); From dabc956dbf88a1bb64ad91bc7fb29c6336aad34e Mon Sep 17 00:00:00 2001 From: Tamas Vajk Date: Tue, 3 May 2022 10:48:51 +0200 Subject: [PATCH 053/246] Unify loop break/continue statement handling between java and kotlin --- .../src/main/kotlin/KotlinFileExtractor.kt | 81 +++++++++++-------- java/ql/lib/config/semmlecode.dbscheme | 11 --- java/ql/lib/semmle/code/java/Statement.qll | 24 ------ .../kotlin/library-tests/stmts/loops.expected | 3 +- .../test/kotlin/library-tests/stmts/loops.ql | 4 +- .../kotlin/library-tests/stmts/stmts.expected | 1 + 6 files changed, 49 insertions(+), 75 deletions(-) diff --git a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt index 743c04bd33f..680a0d3970d 100644 --- a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt +++ b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt @@ -2153,8 +2153,6 @@ open class KotlinFileExtractor( } } - private val loopIdMap: MutableMap> = mutableMapOf() - // todo: calculating the enclosing ref type could be done through this, instead of walking up the declaration parent chain private val declarationStack: Stack = Stack() @@ -2402,32 +2400,10 @@ open class KotlinFileExtractor( } } is IrWhileLoop -> { - val stmtParent = parent.stmt(e, callable) - val id = tw.getFreshIdLabel() - loopIdMap[e] = id - val locId = tw.getLocation(e) - tw.writeStmts_whilestmt(id, stmtParent.parent, stmtParent.idx, callable) - tw.writeHasLocation(id, locId) - extractExpressionExpr(e.condition, callable, id, 0, id) - val body = e.body - if(body != null) { - extractExpressionStmt(body, callable, id, 1) - } - loopIdMap.remove(e) + extractLoop(e, parent, callable) } is IrDoWhileLoop -> { - val stmtParent = parent.stmt(e, callable) - val id = tw.getFreshIdLabel() - loopIdMap[e] = id - val locId = tw.getLocation(e) - tw.writeStmts_dostmt(id, stmtParent.parent, stmtParent.idx, callable) - tw.writeHasLocation(id, locId) - extractExpressionExpr(e.condition, callable, id, 0, id) - val body = e.body - if(body != null) { - extractExpressionStmt(body, callable, id, 1) - } - loopIdMap.remove(e) + extractLoop(e, parent, callable) } is IrInstanceInitializerCall -> { val stmtParent = parent.stmt(e, callable) @@ -2928,6 +2904,49 @@ open class KotlinFileExtractor( } } + private fun extractLoop( + loop: IrLoop, + stmtExprParent: StmtExprParent, + callable: Label + ) { + val stmtParent = stmtExprParent.stmt(loop, callable) + val locId = tw.getLocation(loop) + + val idx: Int + val parent: Label + + val label = loop.label + if (label != null) { + val labeledStmt = tw.getFreshIdLabel() + tw.writeStmts_labeledstmt(labeledStmt, stmtParent.parent, stmtParent.idx, callable) + tw.writeHasLocation(labeledStmt, locId) + + tw.writeNamestrings(label, "", labeledStmt) + idx = 0 + parent = labeledStmt + } else { + idx = stmtParent.idx + parent = stmtParent.parent + } + + val id = if (loop is IrWhileLoop) { + val id = tw.getFreshIdLabel() + tw.writeStmts_whilestmt(id, parent, idx, callable) + id + } else { + val id = tw.getFreshIdLabel() + tw.writeStmts_dostmt(id, parent, idx, callable) + id + } + + tw.writeHasLocation(id, locId) + extractExpressionExpr(loop.condition, callable, id, 0, id) + val body = loop.body + if (body != null) { + extractExpressionStmt(body, callable, id, 1) + } + } + private fun IrValueParameter.isExtensionReceiver(): Boolean { val parentFun = parent as? IrFunction ?: return false return parentFun.extensionReceiverParameter == this @@ -4201,7 +4220,7 @@ open class KotlinFileExtractor( private fun extractBreakContinue( e: IrBreakContinue, - id: Label + id: Label ) { with("break/continue", e) { val locId = tw.getLocation(e) @@ -4210,14 +4229,6 @@ open class KotlinFileExtractor( if (label != null) { tw.writeNamestrings(label, "", id) } - - val loopId = loopIdMap[e.loop] - if (loopId == null) { - logger.errorElement("Missing break/continue target", e) - return - } - - tw.writeKtBreakContinueTargets(id, loopId) } } diff --git a/java/ql/lib/config/semmlecode.dbscheme b/java/ql/lib/config/semmlecode.dbscheme index b9225587bc0..57c55f404a5 100755 --- a/java/ql/lib/config/semmlecode.dbscheme +++ b/java/ql/lib/config/semmlecode.dbscheme @@ -1165,17 +1165,6 @@ ktCommentOwners( int owner: @top ref ) -@breakcontinuestmt = @breakstmt - | @continuestmt; - -@ktloopstmt = @whilestmt - | @dostmt; - -ktBreakContinueTargets( - unique int id: @breakcontinuestmt ref, - int target: @ktloopstmt ref -) - ktExtensionFunctions( unique int id: @method ref, int typeid: @type ref, diff --git a/java/ql/lib/semmle/code/java/Statement.qll b/java/ql/lib/semmle/code/java/Statement.qll index 963f006aaf5..2c8cff3c217 100755 --- a/java/ql/lib/semmle/code/java/Statement.qll +++ b/java/ql/lib/semmle/code/java/Statement.qll @@ -888,27 +888,3 @@ class SuperConstructorInvocationStmt extends Stmt, ConstructorCall, @superconstr override string getAPrimaryQlClass() { result = "SuperConstructorInvocationStmt" } } - -/** A Kotlin loop statement. */ -class KtLoopStmt extends Stmt, @ktloopstmt { - KtLoopStmt() { - this instanceof WhileStmt or - this instanceof DoStmt - } -} - -/** A Kotlin `break` or `continue` statement. */ -abstract class KtBreakContinueStmt extends Stmt, @breakcontinuestmt { - KtLoopStmt loop; - - KtBreakContinueStmt() { ktBreakContinueTargets(this, loop) } - - /** Gets the target loop statement of this `break`. */ - KtLoopStmt getLoopStmt() { result = loop } -} - -/** A Kotlin `break` statement. */ -class KtBreakStmt extends BreakStmt, KtBreakContinueStmt { } - -/** A Kotlin `continue` statement. */ -class KtContinueStmt extends ContinueStmt, KtBreakContinueStmt { } diff --git a/java/ql/test/kotlin/library-tests/stmts/loops.expected b/java/ql/test/kotlin/library-tests/stmts/loops.expected index 57c59b35d3a..83fd4aa5072 100644 --- a/java/ql/test/kotlin/library-tests/stmts/loops.expected +++ b/java/ql/test/kotlin/library-tests/stmts/loops.expected @@ -1,7 +1,6 @@ breakLabel | stmts.kt:25:24:25:33 | break | loop | continueLabel -breakTarget +jumpTarget | stmts.kt:25:24:25:33 | break | stmts.kt:23:11:27:5 | while (...) | -continueTarget | stmts.kt:29:9:29:16 | continue | stmts.kt:28:5:29:16 | while (...) | diff --git a/java/ql/test/kotlin/library-tests/stmts/loops.ql b/java/ql/test/kotlin/library-tests/stmts/loops.ql index 87b88738125..6887d3409ba 100644 --- a/java/ql/test/kotlin/library-tests/stmts/loops.ql +++ b/java/ql/test/kotlin/library-tests/stmts/loops.ql @@ -4,6 +4,4 @@ query predicate breakLabel(BreakStmt s, string label) { s.getLabel() = label } query predicate continueLabel(ContinueStmt s, string label) { s.getLabel() = label } -query predicate breakTarget(KtBreakStmt s, KtLoopStmt l) { s.getLoopStmt() = l } - -query predicate continueTarget(KtContinueStmt s, KtLoopStmt l) { s.getLoopStmt() = l } +query predicate jumpTarget(JumpStmt s, StmtParent p) { s.getTarget() = p } diff --git a/java/ql/test/kotlin/library-tests/stmts/stmts.expected b/java/ql/test/kotlin/library-tests/stmts/stmts.expected index e73b8e82407..4c8ed3dcaf3 100644 --- a/java/ql/test/kotlin/library-tests/stmts/stmts.expected +++ b/java/ql/test/kotlin/library-tests/stmts/stmts.expected @@ -80,6 +80,7 @@ enclosing | stmts.kt:18:52:18:52 | ; | ExprStmt | | stmts.kt:19:5:19:16 | return ... | ReturnStmt | | stmts.kt:22:27:30:1 | { ... } | BlockStmt | +| stmts.kt:23:11:27:5 |