Merge branch 'main' into incomplete-url-string-sanitization

This commit is contained in:
Arthur Baars
2022-03-24 11:27:30 +01:00
committed by GitHub
180 changed files with 9978 additions and 1026 deletions

View File

@@ -36,7 +36,7 @@ If you have an idea for a query that you would like to share with other CodeQL u
For details, see the [guide on query metadata](docs/query-metadata-style-guide.md).
Make sure the `select` statement is compatible with the query `@kind`. See [About CodeQL queries](https://help.semmle.com/QL/learn-ql/writing-queries/introduction-to-queries.html#select-clause) on help.semmle.com.
Make sure the `select` statement is compatible with the query `@kind`. See [About CodeQL queries](https://codeql.github.com/docs/writing-codeql-queries/about-codeql-queries/#select-clause) on codeql.github.com.
3. **Formatting**

View File

@@ -27,7 +27,8 @@
"python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl3.qll",
"python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl4.qll",
"ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl.qll",
"ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl2.qll"
"ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl2.qll",
"ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplForLibraries.qll"
],
"DataFlow Java/C++/C#/Python Common": [
"java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll",
@@ -54,7 +55,8 @@
"python/ql/lib/semmle/python/dataflow/new/internal/tainttracking2/TaintTrackingImpl.qll",
"python/ql/lib/semmle/python/dataflow/new/internal/tainttracking3/TaintTrackingImpl.qll",
"python/ql/lib/semmle/python/dataflow/new/internal/tainttracking4/TaintTrackingImpl.qll",
"ruby/ql/lib/codeql/ruby/dataflow/internal/tainttracking1/TaintTrackingImpl.qll"
"ruby/ql/lib/codeql/ruby/dataflow/internal/tainttracking1/TaintTrackingImpl.qll",
"ruby/ql/lib/codeql/ruby/dataflow/internal/tainttrackingforlibraries/TaintTrackingImpl.qll"
],
"DataFlow Java/C++/C#/Python Consistency checks": [
"java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplConsistency.qll",
@@ -480,11 +482,12 @@
"python/ql/lib/semmle/python/security/performance/ReDoSUtil.qll",
"ruby/ql/lib/codeql/ruby/security/performance/ReDoSUtil.qll"
],
"ReDoS Exponential Python/JS": [
"ReDoS Exponential Python/JS/Ruby": [
"javascript/ql/lib/semmle/javascript/security/performance/ExponentialBackTracking.qll",
"python/ql/lib/semmle/python/security/performance/ExponentialBackTracking.qll"
"python/ql/lib/semmle/python/security/performance/ExponentialBackTracking.qll",
"ruby/ql/lib/codeql/ruby/security/performance/ExponentialBackTracking.qll"
],
"ReDoS Polynomial Python/JS": [
"ReDoS Polynomial Python/JS/Ruby": [
"javascript/ql/lib/semmle/javascript/security/performance/SuperlinearBackTracking.qll",
"python/ql/lib/semmle/python/security/performance/SuperlinearBackTracking.qll",
"ruby/ql/lib/codeql/ruby/security/performance/SuperlinearBackTracking.qll"
@@ -520,12 +523,34 @@
"javascript/ql/src/Security/CWE-020/IncompleteUrlSubstringSanitization.qll",
"ruby/ql/src/queries/security/cwe-020/IncompleteUrlSubstringSanitization.qll"
],
"Concepts Python/Ruby/JS": [
"python/ql/lib/semmle/python/internal/ConceptsShared.qll",
"ruby/ql/lib/codeql/ruby/internal/ConceptsShared.qll",
"javascript/ql/lib/semmle/javascript/internal/ConceptsShared.qll"
],
"Hostname Regexp queries": [
"javascript/ql/src/Security/CWE-020/HostnameRegexpShared.qll",
"python/ql/src/Security/CWE-020/HostnameRegexpShared.qll",
"ruby/ql/src/queries/security/cwe-020/HostnameRegexpShared.qll"
],
"ApiGraphModels": [
"javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModels.qll",
"ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModels.qll"
],
"TaintedFormatStringQuery Ruby/JS": [
"javascript/ql/lib/semmle/javascript/security/dataflow/TaintedFormatStringQuery.qll",
"ruby/ql/lib/codeql/ruby/security/TaintedFormatStringQuery.qll"
],
"TaintedFormatStringCustomizations Ruby/JS": [
"javascript/ql/lib/semmle/javascript/security/dataflow/TaintedFormatStringCustomizations.qll",
"ruby/ql/lib/codeql/ruby/security/TaintedFormatStringCustomizations.qll"
],
"HttpToFileAccessQuery JS/Ruby": [
"javascript/ql/lib/semmle/javascript/security/dataflow/HttpToFileAccessQuery.qll",
"ruby/ql/lib/codeql/ruby/security/HttpToFileAccessQuery.qll"
],
"HttpToFileAccessCustomizations JS/Ruby": [
"javascript/ql/lib/semmle/javascript/security/dataflow/HttpToFileAccessCustomizations.qll",
"ruby/ql/lib/codeql/ruby/security/HttpToFileAccessCustomizations.qll"
]
}

View File

@@ -73,8 +73,24 @@ class Location extends @location {
/** Holds if `this` comes on a line strictly before `l`. */
pragma[inline]
predicate isBefore(Location l) {
this.getFile() = l.getFile() and this.getEndLine() < l.getStartLine()
predicate isBefore(Location l) { this.isBefore(l, false) }
/**
* Holds if `this` comes strictly before `l`. The boolean `sameLine` is
* true if `l` is on the same line as `this`, but starts at a later column.
* Otherwise, `sameLine` is false.
*/
pragma[inline]
predicate isBefore(Location l, boolean sameLine) {
this.getFile() = l.getFile() and
(
sameLine = false and
this.getEndLine() < l.getStartLine()
or
sameLine = true and
this.getEndLine() = l.getStartLine() and
this.getEndColumn() < l.getStartColumn()
)
}
/** Holds if location `l` is completely contained within this one. */

View File

@@ -94,6 +94,7 @@ class Type extends Locatable, @type {
* The result of this predicate will be the type itself, except in the case of a TypedefType or a Decltype,
* in which case the result will be type which results from (possibly recursively) resolving typedefs.
*/
pragma[nomagic]
Type getUnderlyingType() { result = this }
/**

View File

@@ -349,7 +349,7 @@ Instruction getInstructionBackEdgeSuccessor(Instruction instruction, EdgeKind ki
/** Holds if `goto` jumps strictly forward in the program text. */
private predicate isStrictlyForwardGoto(GotoStmt goto) {
goto.getLocation().isBefore(goto.getTarget().getLocation())
goto.getLocation().isBefore(goto.getTarget().getLocation(), _)
}
Locatable getInstructionAst(TStageInstruction instr) {

View File

@@ -92,6 +92,7 @@ abstract class FormattingFunction extends ArrayFunction, TaintFunction {
* snapshots there may be multiple results where we can't tell which is correct for a
* particular function.
*/
pragma[nomagic]
Type getWideCharType() {
result = getFormatCharType() and
result.getSize() > 1

View File

@@ -0,0 +1,21 @@
/**
* @name Extraction errors
* @description List all extraction errors for files in the source code directory.
* @kind diagnostic
* @id cpp/diagnostics/extraction-errors
*/
import cpp
import ExtractionErrors
// NOTE:
// This file looks like the other `diagnostics/extraction-errors` queries in other CodeQL supported
// languages. However, since this diagnostic query is located in the `Internal` subdirectory it will not
// appear in the Code Scanning suite. The related query `cpp/diagnostics/extraction-warnings` is,
// however, included as a public diagnostics query.
from ExtractionError error
where
error instanceof ExtractionUnknownError or
exists(error.getFile().getRelativePath())
select error, "Extraction failed in " + error.getFile() + " with error " + error.getErrorMessage(),
error.getSeverity()

View File

@@ -0,0 +1,137 @@
/**
* Provides a common hierarchy of all types of errors that can occur during extraction.
*/
import cpp
/*
* A note about how the C/C++ extractor emits diagnostics:
* When the extractor frontend encounters an error, it emits a diagnostic message,
* that includes a message, location and severity.
* However, that process is best-effort and may fail (e.g. due to lack of memory).
* Thus, if the extractor emitted at least one diagnostic of severity discretionary
* error (or higher), it *also* emits a simple "There was an error during this compilation"
* error diagnostic, without location information.
* In the common case, this means that a compilation during which one or more errors happened also gets
* the catch-all diagnostic.
* This diagnostic has the empty string as file path.
* We filter out these useless diagnostics if there is at least one error-level diagnostic
* for the affected compilation in the database.
* Otherwise, we show it to indicate that something went wrong and that we
* don't know what exactly happened.
*/
/**
* An error that, if present, leads to a file being marked as non-successfully extracted.
*/
class ReportableError extends Diagnostic {
ReportableError() {
(
this instanceof CompilerDiscretionaryError or
this instanceof CompilerError or
this instanceof CompilerCatastrophe
) and
// Filter for the catch-all diagnostic, see note above.
not this.getFile().getAbsolutePath() = ""
}
}
private newtype TExtractionError =
TReportableError(ReportableError err) or
TCompilationFailed(Compilation c, File f) {
f = c.getAFileCompiled() and not c.normalTermination()
} or
// Show the catch-all diagnostic (see note above) only if we haven't seen any other error-level diagnostic
// for that compilation
TUnknownError(CompilerError err) {
not exists(ReportableError e | e.getCompilation() = err.getCompilation())
}
/**
* Superclass for the extraction error hierarchy.
*/
class ExtractionError extends TExtractionError {
/** Gets the string representation of the error. */
string toString() { none() }
/** Gets the error message for this error. */
string getErrorMessage() { none() }
/** Gets the file this error occured in. */
File getFile() { none() }
/** Gets the location this error occured in. */
Location getLocation() { none() }
/** Gets the SARIF severity of this error. */
int getSeverity() {
// Unfortunately, we can't distinguish between errors and fatal errors in SARIF,
// so all errors have severity 2.
result = 2
}
}
/**
* An unrecoverable extraction error, where extraction was unable to finish.
* This can be caused by a multitude of reasons, for example:
* - hitting a frontend assertion
* - crashing due to dereferencing an invalid pointer
* - stack overflow
* - out of memory
*/
class ExtractionUnrecoverableError extends ExtractionError, TCompilationFailed {
Compilation c;
File f;
ExtractionUnrecoverableError() { this = TCompilationFailed(c, f) }
override string toString() {
result = "Unrecoverable extraction error while compiling " + f.toString()
}
override string getErrorMessage() { result = "unrecoverable compilation failure." }
override File getFile() { result = f }
override Location getLocation() { result = f.getLocation() }
}
/**
* A recoverable extraction error.
* These are compiler errors from the frontend.
* Upon encountering one of these, we still continue extraction, but the
* database will be incomplete for that file.
*/
class ExtractionRecoverableError extends ExtractionError, TReportableError {
ReportableError err;
ExtractionRecoverableError() { this = TReportableError(err) }
override string toString() { result = "Recoverable extraction error: " + err }
override string getErrorMessage() { result = err.getFullMessage() }
override File getFile() { result = err.getFile() }
override Location getLocation() { result = err.getLocation() }
}
/**
* An unknown error happened during extraction.
* These are only displayed if we know that we encountered an error during extraction,
* but, for some reason, failed to emit a proper diagnostic with location information
* and error message.
*/
class ExtractionUnknownError extends ExtractionError, TUnknownError {
CompilerError err;
ExtractionUnknownError() { this = TUnknownError(err) }
override string toString() { result = "Unknown extraction error: " + err }
override string getErrorMessage() { result = err.getFullMessage() }
override File getFile() { result = err.getFile() }
override Location getLocation() { result = err.getLocation() }
}

View File

@@ -19,9 +19,9 @@ import semmle.code.cpp.security.Security
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
import semmle.code.cpp.ir.IR
import semmle.code.cpp.ir.dataflow.TaintTracking
import semmle.code.cpp.ir.dataflow.TaintTracking2
import semmle.code.cpp.security.FlowSources
import semmle.code.cpp.models.implementations.Strcat
import DataFlow::PathGraph
Expr sinkAsArgumentIndirection(DataFlow::Node sink) {
result =
@@ -66,154 +66,70 @@ predicate interestingConcatenation(DataFlow::Node fst, DataFlow::Node snd) {
)
}
class TaintToConcatenationConfiguration extends TaintTracking::Configuration {
TaintToConcatenationConfiguration() { this = "TaintToConcatenationConfiguration" }
override predicate isSource(DataFlow::Node source) { source instanceof FlowSource }
override predicate isSink(DataFlow::Node sink) { interestingConcatenation(sink, _) }
override predicate isSanitizer(DataFlow::Node node) {
node.asInstruction().getResultType() instanceof IntegralType
or
node.asInstruction().getResultType() instanceof FloatingPointType
}
class ConcatState extends DataFlow::FlowState {
ConcatState() { this = "ConcatState" }
}
class ExecTaintConfiguration extends TaintTracking2::Configuration {
class ExecState extends DataFlow::FlowState {
DataFlow::Node fst;
DataFlow::Node snd;
ExecState() {
this =
"ExecState (" + fst.getLocation() + " | " + fst + ", " + snd.getLocation() + " | " + snd + ")" and
interestingConcatenation(fst, snd)
}
DataFlow::Node getFstNode() { result = fst }
DataFlow::Node getSndNode() { result = snd }
}
class ExecTaintConfiguration extends TaintTracking::Configuration {
ExecTaintConfiguration() { this = "ExecTaintConfiguration" }
override predicate isSource(DataFlow::Node source) {
exists(DataFlow::Node prevSink, TaintToConcatenationConfiguration conf |
conf.hasFlow(_, prevSink) and
interestingConcatenation(prevSink, source)
)
override predicate isSource(DataFlow::Node source, DataFlow::FlowState state) {
source instanceof FlowSource and
state instanceof ConcatState
}
override predicate isSink(DataFlow::Node sink) {
shellCommand(sinkAsArgumentIndirection(sink), _)
override predicate isSink(DataFlow::Node sink, DataFlow::FlowState state) {
shellCommand(sinkAsArgumentIndirection(sink), _) and
state instanceof ExecState
}
override predicate isSanitizerOut(DataFlow::Node node) {
isSink(node) // Prevent duplicates along a call chain, since `shellCommand` will include wrappers
}
}
module StitchedPathGraph {
// There's a different PathNode class for each DataFlowImplN.qll, so we can't simply combine the
// PathGraph predicates directly. Instead, we use a newtype so there's a single type that
// contains both sets of PathNodes.
newtype TMergedPathNode =
TPathNode1(DataFlow::PathNode node) or
TPathNode2(DataFlow2::PathNode node)
// this wraps the toString and location predicates so we can use the merged node type in a
// selection
class MergedPathNode extends TMergedPathNode {
string toString() {
exists(DataFlow::PathNode n |
this = TPathNode1(n) and
result = n.toString()
)
or
exists(DataFlow2::PathNode n |
this = TPathNode2(n) and
result = n.toString()
)
}
DataFlow::Node getNode() {
exists(DataFlow::PathNode n |
this = TPathNode1(n) and
result = n.getNode()
)
or
exists(DataFlow2::PathNode n |
this = TPathNode2(n) and
result = n.getNode()
)
}
DataFlow::PathNode getPathNode1() { this = TPathNode1(result) }
DataFlow2::PathNode getPathNode2() { this = TPathNode2(result) }
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
exists(DataFlow::PathNode n |
this = TPathNode1(n) and
n.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
)
or
exists(DataFlow2::PathNode n |
this = TPathNode2(n) and
n.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
)
}
}
query predicate edges(MergedPathNode a, MergedPathNode b) {
exists(DataFlow::PathNode an, DataFlow::PathNode bn |
a = TPathNode1(an) and
b = TPathNode1(bn) and
DataFlow::PathGraph::edges(an, bn)
)
or
exists(DataFlow2::PathNode an, DataFlow2::PathNode bn |
a = TPathNode2(an) and
b = TPathNode2(bn) and
DataFlow2::PathGraph::edges(an, bn)
)
or
// This is where paths from the two configurations are connected. `interestingConcatenation`
// is the only thing in this module that's actually specific to the query - everything else is
// just using types and predicates from the DataFlow library.
interestingConcatenation(a.getNode(), b.getNode()) and
a instanceof TPathNode1 and
b instanceof TPathNode2
}
query predicate nodes(MergedPathNode mpn, string key, string val) {
// here we just need the union of the underlying `nodes` predicates
exists(DataFlow::PathNode n |
mpn = TPathNode1(n) and
DataFlow::PathGraph::nodes(n, key, val)
)
or
exists(DataFlow2::PathNode n |
mpn = TPathNode2(n) and
DataFlow2::PathGraph::nodes(n, key, val)
)
}
query predicate subpaths(
MergedPathNode arg, MergedPathNode par, MergedPathNode ret, MergedPathNode out
override predicate isAdditionalTaintStep(
DataFlow::Node node1, DataFlow::FlowState state1, DataFlow::Node node2,
DataFlow::FlowState state2
) {
// just forward subpaths from the underlying libraries. This might be slightly awkward when
// the concatenation is deep in a call chain.
DataFlow::PathGraph::subpaths(arg.getPathNode1(), par.getPathNode1(), ret.getPathNode1(),
out.getPathNode1())
or
DataFlow2::PathGraph::subpaths(arg.getPathNode2(), par.getPathNode2(), ret.getPathNode2(),
out.getPathNode2())
state1 instanceof ConcatState and
state2.(ExecState).getFstNode() = node1 and
state2.(ExecState).getSndNode() = node2
}
override predicate isSanitizer(DataFlow::Node node, DataFlow::FlowState state) {
(
node.asInstruction().getResultType() instanceof IntegralType
or
node.asInstruction().getResultType() instanceof FloatingPointType
) and
state instanceof ConcatState
}
override predicate isSanitizerOut(DataFlow::Node node, DataFlow::FlowState state) {
isSink(node, state) // Prevent duplicates along a call chain, since `shellCommand` will include wrappers
}
}
import StitchedPathGraph
from
DataFlow::PathNode sourceNode, DataFlow::PathNode concatSink, DataFlow2::PathNode concatSource,
DataFlow2::PathNode sinkNode, string taintCause, string callChain,
TaintToConcatenationConfiguration conf1, ExecTaintConfiguration conf2
ExecTaintConfiguration conf, DataFlow::PathNode sourceNode, DataFlow::PathNode sinkNode,
string taintCause, string callChain, DataFlow::Node concatResult
where
conf.hasFlowPath(sourceNode, sinkNode) and
taintCause = sourceNode.getNode().(FlowSource).getSourceType() and
conf1.hasFlowPath(sourceNode, concatSink) and
interestingConcatenation(concatSink.getNode(), concatSource.getNode()) and // this loses call context
conf2.hasFlowPath(concatSource, sinkNode) and
shellCommand(sinkAsArgumentIndirection(sinkNode.getNode()), callChain)
select sinkAsArgumentIndirection(sinkNode.getNode()), TPathNode1(sourceNode).(MergedPathNode),
TPathNode2(sinkNode).(MergedPathNode),
shellCommand(sinkAsArgumentIndirection(sinkNode.getNode()), callChain) and
concatResult = sinkNode.getState().(ExecState).getSndNode()
select sinkAsArgumentIndirection(sinkNode.getNode()), sourceNode, sinkNode,
"This argument to an OS command is derived from $@, dangerously concatenated into $@, and then passed to "
+ callChain, sourceNode, "user input (" + taintCause + ")", concatSource,
concatSource.toString()
+ callChain, sourceNode, "user input (" + taintCause + ")", concatResult,
concatResult.toString()

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* The `cpp/command-line-injection` query now takes into account calling contexts across string concatenations. This removes false positives due to mismatched calling contexts before and after string concatenations.

View File

@@ -0,0 +1,46 @@
/**
* @name Linux kernel double-fetch vulnerability detection
* @description Double-fetch is a very common vulnerability pattern
* in linux kernel, attacker can exploit double-fetch
* issues to obatain root privilege.
* Double-fetch is caused by fetching data from user
* mode by calling copy_from_user twice, CVE-2016-6480
* is quite a good example for your information.
* @kind problem
* @id cpp/linux-kernel-double-fetch-vulnerability
* @problem.severity warning
* @security-severity 7.5
* @tags security
* external/cwe/cwe-362
*/
import cpp
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
class CopyFromUserFunctionCall extends FunctionCall {
CopyFromUserFunctionCall() {
this.getTarget().getName() = "copy_from_user" and
not this.getArgument(1) instanceof AddressOfExpr
}
//root cause of double-fetech issue is read from
//the same user mode memory twice, so it makes
//sense that only check user mode pointer
predicate readFromSameUserModePointer(CopyFromUserFunctionCall another) {
globalValueNumber(this.getArgument(1)) = globalValueNumber(another.getArgument(1))
}
}
from CopyFromUserFunctionCall p1, CopyFromUserFunctionCall p2
where
not p1 = p2 and
p1.readFromSameUserModePointer(p2) and
exists(IfStmt ifStmt |
p1.getBasicBlock().getAFalseSuccessor*() = ifStmt.getBasicBlock() and
ifStmt.getBasicBlock().getAFalseSuccessor*() = p2.getBasicBlock()
) and
not exists(AssignPointerAddExpr assignPtrAdd |
globalValueNumber(p1.getArgument(1)) = globalValueNumber(assignPtrAdd.getLValue()) and
p1.getBasicBlock().getAFalseSuccessor*() = assignPtrAdd.getBasicBlock()
)
select p2, "Double fetch vulnerability. First fetch was $@.", p1, p1.toString()

View File

@@ -13035,6 +13035,23 @@ ir.cpp:
# 1689| getEntryPoint(): [BlockStmt] { ... }
# 1689| getStmt(0): [EmptyStmt] ;
# 1689| getStmt(1): [ReturnStmt] return ...
# 1693| [TopLevelFunction] int goto_on_same_line()
# 1693| <params>:
# 1693| getEntryPoint(): [BlockStmt] { ... }
# 1694| getStmt(0): [DeclStmt] declaration
# 1694| getDeclarationEntry(0): [VariableDeclarationEntry] definition of x
# 1694| Type = [IntType] int
# 1694| getVariable().getInitializer(): [Initializer] initializer for x
# 1694| getExpr(): [Literal] 42
# 1694| Type = [IntType] int
# 1694| Value = [Literal] 42
# 1694| ValueCategory = prvalue
# 1695| getStmt(1): [GotoStmt] goto ...
# 1695| getStmt(2): [LabelStmt] label ...:
# 1696| getStmt(3): [ReturnStmt] return ...
# 1696| getExpr(): [VariableAccess] x
# 1696| Type = [IntType] int
# 1696| ValueCategory = prvalue(load)
perf-regression.cpp:
# 4| [CopyAssignmentOperator] Big& Big::operator=(Big const&)
# 4| <params>:

View File

@@ -1690,4 +1690,10 @@ void captured_lambda(int x, int &y, int &&z)
};
}
int goto_on_same_line() {
int x = 42;
goto next; next:
return x;
}
// semmle-extractor-options: -std=c++17 --clang

View File

@@ -7527,6 +7527,17 @@
| ir.cpp:1689:50:1689:50 | Load | m1689_6 |
| ir.cpp:1689:50:1689:50 | SideEffect | m1689_3 |
| ir.cpp:1689:50:1689:50 | SideEffect | m1689_8 |
| ir.cpp:1693:5:1693:21 | Address | &:r1693_5 |
| ir.cpp:1693:5:1693:21 | ChiPartial | partial:m1693_3 |
| ir.cpp:1693:5:1693:21 | ChiTotal | total:m1693_2 |
| ir.cpp:1693:5:1693:21 | Load | m1696_4 |
| ir.cpp:1693:5:1693:21 | SideEffect | m1693_3 |
| ir.cpp:1694:7:1694:7 | Address | &:r1694_1 |
| ir.cpp:1694:10:1694:12 | StoreValue | r1694_2 |
| ir.cpp:1696:3:1696:11 | Address | &:r1696_1 |
| ir.cpp:1696:10:1696:10 | Address | &:r1696_2 |
| ir.cpp:1696:10:1696:10 | Load | m1694_3 |
| ir.cpp:1696:10:1696:10 | StoreValue | r1696_3 |
| perf-regression.cpp:6:3:6:5 | Address | &:r6_5 |
| perf-regression.cpp:6:3:6:5 | Address | &:r6_5 |
| perf-regression.cpp:6:3:6:5 | Address | &:r6_7 |

View File

@@ -8842,6 +8842,25 @@ ir.cpp:
# 1689| v1689_12(void) = AliasedUse : ~m?
# 1689| v1689_13(void) = ExitFunction :
# 1693| int goto_on_same_line()
# 1693| Block 0
# 1693| v1693_1(void) = EnterFunction :
# 1693| mu1693_2(unknown) = AliasedDefinition :
# 1693| mu1693_3(unknown) = InitializeNonLocal :
# 1694| r1694_1(glval<int>) = VariableAddress[x] :
# 1694| r1694_2(int) = Constant[42] :
# 1694| mu1694_3(int) = Store[x] : &:r1694_1, r1694_2
# 1695| v1695_1(void) = NoOp :
# 1695| v1695_2(void) = NoOp :
# 1696| r1696_1(glval<int>) = VariableAddress[#return] :
# 1696| r1696_2(glval<int>) = VariableAddress[x] :
# 1696| r1696_3(int) = Load[x] : &:r1696_2, ~m?
# 1696| mu1696_4(int) = Store[#return] : &:r1696_1, r1696_3
# 1693| r1693_4(glval<int>) = VariableAddress[#return] :
# 1693| v1693_5(void) = ReturnValue : &:r1693_4, ~m?
# 1693| v1693_6(void) = AliasedUse : ~m?
# 1693| v1693_7(void) = ExitFunction :
perf-regression.cpp:
# 6| void Big::Big()
# 6| Block 0

View File

@@ -3,7 +3,6 @@ edges
| tests.cpp:33:34:33:39 | call to getenv | tests.cpp:38:39:38:49 | environment indirection |
| tests.cpp:38:25:38:36 | strncat output argument | tests.cpp:26:15:26:23 | ReturnValue |
| tests.cpp:38:39:38:49 | environment indirection | tests.cpp:38:25:38:36 | strncat output argument |
| tests.cpp:38:39:38:49 | environment indirection | tests.cpp:38:25:38:36 | strncat output argument |
| tests.cpp:51:12:51:20 | call to badSource | tests.cpp:53:16:53:19 | data indirection |
nodes
| tests.cpp:26:15:26:23 | ReturnValue | semmle.label | ReturnValue |

View File

@@ -2,39 +2,77 @@ edges
| test.cpp:16:20:16:23 | argv | test.cpp:22:45:22:52 | userName indirection |
| test.cpp:22:13:22:20 | sprintf output argument | test.cpp:23:12:23:19 | command1 indirection |
| test.cpp:22:45:22:52 | userName indirection | test.cpp:22:13:22:20 | sprintf output argument |
| test.cpp:22:45:22:52 | userName indirection | test.cpp:22:13:22:20 | sprintf output argument |
| test.cpp:47:21:47:26 | call to getenv | test.cpp:50:35:50:43 | envCflags indirection |
| test.cpp:50:11:50:17 | sprintf output argument | test.cpp:51:10:51:16 | command indirection |
| test.cpp:50:35:50:43 | envCflags indirection | test.cpp:50:11:50:17 | sprintf output argument |
| test.cpp:50:35:50:43 | envCflags indirection | test.cpp:50:11:50:17 | sprintf output argument |
| test.cpp:62:9:62:16 | fread output argument | test.cpp:64:20:64:27 | filename indirection |
| test.cpp:64:11:64:17 | strncat output argument | test.cpp:65:10:65:16 | command indirection |
| test.cpp:64:20:64:27 | filename indirection | test.cpp:64:11:64:17 | strncat output argument |
| test.cpp:64:20:64:27 | filename indirection | test.cpp:64:11:64:17 | strncat output argument |
| test.cpp:82:9:82:16 | fread output argument | test.cpp:84:20:84:27 | filename indirection |
| test.cpp:84:11:84:17 | strncat output argument | test.cpp:85:32:85:38 | command indirection |
| test.cpp:84:20:84:27 | filename indirection | test.cpp:84:11:84:17 | strncat output argument |
| test.cpp:84:20:84:27 | filename indirection | test.cpp:84:11:84:17 | strncat output argument |
| test.cpp:91:9:91:16 | fread output argument | test.cpp:93:17:93:24 | filename indirection |
| test.cpp:93:11:93:14 | strncat output argument | test.cpp:94:45:94:48 | path indirection |
| test.cpp:93:17:93:24 | filename indirection | test.cpp:93:11:93:14 | strncat output argument |
| test.cpp:93:17:93:24 | filename indirection | test.cpp:93:11:93:14 | strncat output argument |
| test.cpp:106:20:106:25 | call to getenv | test.cpp:107:33:107:36 | path indirection |
| test.cpp:107:31:107:31 | call to operator+ | test.cpp:108:18:108:22 | call to c_str indirection |
| test.cpp:107:33:107:36 | path indirection | test.cpp:107:31:107:31 | call to operator+ |
| test.cpp:107:33:107:36 | path indirection | test.cpp:107:31:107:31 | call to operator+ |
| test.cpp:113:20:113:25 | call to getenv | test.cpp:114:19:114:22 | path indirection |
| test.cpp:114:17:114:17 | Call | test.cpp:114:25:114:29 | call to c_str indirection |
| test.cpp:114:19:114:22 | path indirection | test.cpp:114:17:114:17 | Call |
| test.cpp:114:19:114:22 | path indirection | test.cpp:114:17:114:17 | Call |
| test.cpp:119:20:119:25 | call to getenv | test.cpp:120:19:120:22 | path indirection |
| test.cpp:120:17:120:17 | Call | test.cpp:120:10:120:30 | call to data indirection |
| test.cpp:120:19:120:22 | path indirection | test.cpp:120:17:120:17 | Call |
| test.cpp:120:19:120:22 | path indirection | test.cpp:120:17:120:17 | Call |
| test.cpp:140:9:140:11 | fread output argument | test.cpp:142:31:142:33 | str indirection |
| test.cpp:142:11:142:17 | sprintf output argument | test.cpp:143:10:143:16 | command indirection |
| test.cpp:142:31:142:33 | str indirection | test.cpp:142:11:142:17 | sprintf output argument |
| test.cpp:142:31:142:33 | str indirection | test.cpp:142:11:142:17 | sprintf output argument |
| test.cpp:174:9:174:16 | fread output argument | test.cpp:177:20:177:27 | filename indirection |
| test.cpp:174:9:174:16 | fread output argument | test.cpp:178:22:178:26 | flags indirection |
| test.cpp:174:9:174:16 | fread output argument | test.cpp:180:22:180:29 | filename indirection |
| test.cpp:177:13:177:17 | strncat output argument | test.cpp:183:32:183:38 | command indirection |
| test.cpp:177:20:177:27 | filename indirection | test.cpp:177:13:177:17 | strncat output argument |
| test.cpp:178:13:178:19 | strncat output argument | test.cpp:183:32:183:38 | command indirection |
| test.cpp:178:22:178:26 | flags indirection | test.cpp:178:13:178:19 | strncat output argument |
| test.cpp:180:13:180:19 | strncat output argument | test.cpp:183:32:183:38 | command indirection |
| test.cpp:180:22:180:29 | filename indirection | test.cpp:180:13:180:19 | strncat output argument |
| test.cpp:186:47:186:54 | *filename | test.cpp:187:18:187:25 | filename indirection |
| test.cpp:186:47:186:54 | *filename | test.cpp:188:20:188:24 | flags indirection |
| test.cpp:186:47:186:54 | filename | test.cpp:187:18:187:25 | filename indirection |
| test.cpp:186:47:186:54 | filename | test.cpp:188:20:188:24 | flags indirection |
| test.cpp:187:11:187:15 | strncat output argument | test.cpp:188:11:188:17 | command [post update] |
| test.cpp:187:11:187:15 | strncat output argument | test.cpp:188:11:188:17 | command [post update] |
| test.cpp:187:11:187:15 | strncat output argument | test.cpp:188:11:188:17 | command [post update] |
| test.cpp:187:11:187:15 | strncat output argument | test.cpp:188:11:188:17 | command [post update] |
| test.cpp:187:18:187:25 | filename indirection | test.cpp:187:11:187:15 | strncat output argument |
| test.cpp:187:18:187:25 | filename indirection | test.cpp:187:11:187:15 | strncat output argument |
| test.cpp:188:11:188:17 | command [post update] | test.cpp:188:11:188:17 | command [post update] |
| test.cpp:188:11:188:17 | command [post update] | test.cpp:188:11:188:17 | command [post update] |
| test.cpp:188:11:188:17 | command [post update] | test.cpp:188:11:188:17 | command [post update] |
| test.cpp:188:11:188:17 | command [post update] | test.cpp:188:11:188:17 | command [post update] |
| test.cpp:188:11:188:17 | strncat output argument | test.cpp:188:11:188:17 | command [post update] |
| test.cpp:188:11:188:17 | strncat output argument | test.cpp:188:11:188:17 | command [post update] |
| test.cpp:188:11:188:17 | strncat output argument | test.cpp:188:11:188:17 | command [post update] |
| test.cpp:188:11:188:17 | strncat output argument | test.cpp:188:11:188:17 | command [post update] |
| test.cpp:188:20:188:24 | flags indirection | test.cpp:188:11:188:17 | strncat output argument |
| test.cpp:188:20:188:24 | flags indirection | test.cpp:188:11:188:17 | strncat output argument |
| test.cpp:194:9:194:16 | fread output argument | test.cpp:196:26:196:33 | filename |
| test.cpp:194:9:194:16 | fread output argument | test.cpp:196:26:196:33 | filename indirection |
| test.cpp:196:10:196:16 | command [post update] | test.cpp:198:32:198:38 | command indirection |
| test.cpp:196:10:196:16 | command [post update] | test.cpp:198:32:198:38 | command indirection |
| test.cpp:196:26:196:33 | filename | test.cpp:186:47:186:54 | filename |
| test.cpp:196:26:196:33 | filename | test.cpp:196:10:196:16 | command [post update] |
| test.cpp:196:26:196:33 | filename | test.cpp:196:10:196:16 | command [post update] |
| test.cpp:196:26:196:33 | filename indirection | test.cpp:186:47:186:54 | *filename |
| test.cpp:196:26:196:33 | filename indirection | test.cpp:196:10:196:16 | command [post update] |
| test.cpp:196:26:196:33 | filename indirection | test.cpp:196:10:196:16 | command [post update] |
| test.cpp:218:9:218:16 | fread output argument | test.cpp:220:19:220:26 | filename indirection |
| test.cpp:218:9:218:16 | fread output argument | test.cpp:220:19:220:26 | filename indirection |
| test.cpp:220:10:220:16 | strncat output argument | test.cpp:222:32:222:38 | command indirection |
| test.cpp:220:10:220:16 | strncat output argument | test.cpp:222:32:222:38 | command indirection |
| test.cpp:220:19:220:26 | filename indirection | test.cpp:220:10:220:16 | strncat output argument |
| test.cpp:220:19:220:26 | filename indirection | test.cpp:220:10:220:16 | strncat output argument |
| test.cpp:220:19:220:26 | filename indirection | test.cpp:220:10:220:16 | strncat output argument |
| test.cpp:220:19:220:26 | filename indirection | test.cpp:220:10:220:16 | strncat output argument |
nodes
| test.cpp:16:20:16:23 | argv | semmle.label | argv |
| test.cpp:22:13:22:20 | sprintf output argument | semmle.label | sprintf output argument |
@@ -72,7 +110,56 @@ nodes
| test.cpp:142:11:142:17 | sprintf output argument | semmle.label | sprintf output argument |
| test.cpp:142:31:142:33 | str indirection | semmle.label | str indirection |
| test.cpp:143:10:143:16 | command indirection | semmle.label | command indirection |
| test.cpp:174:9:174:16 | fread output argument | semmle.label | fread output argument |
| test.cpp:177:13:177:17 | strncat output argument | semmle.label | strncat output argument |
| test.cpp:177:20:177:27 | filename indirection | semmle.label | filename indirection |
| test.cpp:178:13:178:19 | strncat output argument | semmle.label | strncat output argument |
| test.cpp:178:22:178:26 | flags indirection | semmle.label | flags indirection |
| test.cpp:180:13:180:19 | strncat output argument | semmle.label | strncat output argument |
| test.cpp:180:22:180:29 | filename indirection | semmle.label | filename indirection |
| test.cpp:183:32:183:38 | command indirection | semmle.label | command indirection |
| test.cpp:183:32:183:38 | command indirection | semmle.label | command indirection |
| test.cpp:183:32:183:38 | command indirection | semmle.label | command indirection |
| test.cpp:186:47:186:54 | *filename | semmle.label | *filename |
| test.cpp:186:47:186:54 | filename | semmle.label | filename |
| test.cpp:187:11:187:15 | strncat output argument | semmle.label | strncat output argument |
| test.cpp:187:11:187:15 | strncat output argument | semmle.label | strncat output argument |
| test.cpp:187:18:187:25 | filename indirection | semmle.label | filename indirection |
| test.cpp:187:18:187:25 | filename indirection | semmle.label | filename indirection |
| test.cpp:188:11:188:17 | command [post update] | semmle.label | command [post update] |
| test.cpp:188:11:188:17 | command [post update] | semmle.label | command [post update] |
| test.cpp:188:11:188:17 | command [post update] | semmle.label | command [post update] |
| test.cpp:188:11:188:17 | command [post update] | semmle.label | command [post update] |
| test.cpp:188:11:188:17 | command [post update] | semmle.label | command [post update] |
| test.cpp:188:11:188:17 | command [post update] | semmle.label | command [post update] |
| test.cpp:188:11:188:17 | command [post update] | semmle.label | command [post update] |
| test.cpp:188:11:188:17 | command [post update] | semmle.label | command [post update] |
| test.cpp:188:11:188:17 | strncat output argument | semmle.label | strncat output argument |
| test.cpp:188:11:188:17 | strncat output argument | semmle.label | strncat output argument |
| test.cpp:188:20:188:24 | flags indirection | semmle.label | flags indirection |
| test.cpp:188:20:188:24 | flags indirection | semmle.label | flags indirection |
| test.cpp:194:9:194:16 | fread output argument | semmle.label | fread output argument |
| test.cpp:196:10:196:16 | command [post update] | semmle.label | command [post update] |
| test.cpp:196:10:196:16 | command [post update] | semmle.label | command [post update] |
| test.cpp:196:26:196:33 | filename | semmle.label | filename |
| test.cpp:196:26:196:33 | filename indirection | semmle.label | filename indirection |
| test.cpp:198:32:198:38 | command indirection | semmle.label | command indirection |
| test.cpp:198:32:198:38 | command indirection | semmle.label | command indirection |
| test.cpp:218:9:218:16 | fread output argument | semmle.label | fread output argument |
| test.cpp:220:10:220:16 | strncat output argument | semmle.label | strncat output argument |
| test.cpp:220:10:220:16 | strncat output argument | semmle.label | strncat output argument |
| test.cpp:220:19:220:26 | filename indirection | semmle.label | filename indirection |
| test.cpp:220:19:220:26 | filename indirection | semmle.label | filename indirection |
| test.cpp:222:32:222:38 | command indirection | semmle.label | command indirection |
subpaths
| test.cpp:196:26:196:33 | filename | test.cpp:186:47:186:54 | filename | test.cpp:188:11:188:17 | command [post update] | test.cpp:196:10:196:16 | command [post update] |
| test.cpp:196:26:196:33 | filename | test.cpp:186:47:186:54 | filename | test.cpp:188:11:188:17 | command [post update] | test.cpp:196:10:196:16 | command [post update] |
| test.cpp:196:26:196:33 | filename | test.cpp:186:47:186:54 | filename | test.cpp:188:11:188:17 | command [post update] | test.cpp:196:10:196:16 | command [post update] |
| test.cpp:196:26:196:33 | filename | test.cpp:186:47:186:54 | filename | test.cpp:188:11:188:17 | command [post update] | test.cpp:196:10:196:16 | command [post update] |
| test.cpp:196:26:196:33 | filename indirection | test.cpp:186:47:186:54 | *filename | test.cpp:188:11:188:17 | command [post update] | test.cpp:196:10:196:16 | command [post update] |
| test.cpp:196:26:196:33 | filename indirection | test.cpp:186:47:186:54 | *filename | test.cpp:188:11:188:17 | command [post update] | test.cpp:196:10:196:16 | command [post update] |
| test.cpp:196:26:196:33 | filename indirection | test.cpp:186:47:186:54 | *filename | test.cpp:188:11:188:17 | command [post update] | test.cpp:196:10:196:16 | command [post update] |
| test.cpp:196:26:196:33 | filename indirection | test.cpp:186:47:186:54 | *filename | test.cpp:188:11:188:17 | command [post update] | test.cpp:196:10:196:16 | command [post update] |
#select
| test.cpp:23:12:23:19 | command1 | test.cpp:16:20:16:23 | argv | test.cpp:23:12:23:19 | command1 indirection | This argument to an OS command is derived from $@, dangerously concatenated into $@, and then passed to system(string) | test.cpp:16:20:16:23 | argv | user input (a command-line argument) | test.cpp:22:13:22:20 | sprintf output argument | sprintf output argument |
| test.cpp:51:10:51:16 | command | test.cpp:47:21:47:26 | call to getenv | test.cpp:51:10:51:16 | command indirection | This argument to an OS command is derived from $@, dangerously concatenated into $@, and then passed to system(string) | test.cpp:47:21:47:26 | call to getenv | user input (an environment variable) | test.cpp:50:11:50:17 | sprintf output argument | sprintf output argument |
@@ -83,3 +170,10 @@ subpaths
| test.cpp:114:25:114:29 | call to c_str | test.cpp:113:20:113:25 | call to getenv | test.cpp:114:25:114:29 | call to c_str indirection | This argument to an OS command is derived from $@, dangerously concatenated into $@, and then passed to system(string) | test.cpp:113:20:113:25 | call to getenv | user input (an environment variable) | test.cpp:114:17:114:17 | Call | Call |
| test.cpp:120:25:120:28 | call to data | test.cpp:119:20:119:25 | call to getenv | test.cpp:120:10:120:30 | call to data indirection | This argument to an OS command is derived from $@, dangerously concatenated into $@, and then passed to system(string) | test.cpp:119:20:119:25 | call to getenv | user input (an environment variable) | test.cpp:120:17:120:17 | Call | Call |
| test.cpp:143:10:143:16 | command | test.cpp:140:9:140:11 | fread output argument | test.cpp:143:10:143:16 | command indirection | This argument to an OS command is derived from $@, dangerously concatenated into $@, and then passed to system(string) | test.cpp:140:9:140:11 | fread output argument | user input (String read by fread) | test.cpp:142:11:142:17 | sprintf output argument | sprintf output argument |
| test.cpp:183:32:183:38 | command | test.cpp:174:9:174:16 | fread output argument | test.cpp:183:32:183:38 | command indirection | This argument to an OS command is derived from $@, dangerously concatenated into $@, and then passed to execl | test.cpp:174:9:174:16 | fread output argument | user input (String read by fread) | test.cpp:177:13:177:17 | strncat output argument | strncat output argument |
| test.cpp:183:32:183:38 | command | test.cpp:174:9:174:16 | fread output argument | test.cpp:183:32:183:38 | command indirection | This argument to an OS command is derived from $@, dangerously concatenated into $@, and then passed to execl | test.cpp:174:9:174:16 | fread output argument | user input (String read by fread) | test.cpp:178:13:178:19 | strncat output argument | strncat output argument |
| test.cpp:183:32:183:38 | command | test.cpp:174:9:174:16 | fread output argument | test.cpp:183:32:183:38 | command indirection | This argument to an OS command is derived from $@, dangerously concatenated into $@, and then passed to execl | test.cpp:174:9:174:16 | fread output argument | user input (String read by fread) | test.cpp:180:13:180:19 | strncat output argument | strncat output argument |
| test.cpp:198:32:198:38 | command | test.cpp:194:9:194:16 | fread output argument | test.cpp:198:32:198:38 | command indirection | This argument to an OS command is derived from $@, dangerously concatenated into $@, and then passed to execl | test.cpp:194:9:194:16 | fread output argument | user input (String read by fread) | test.cpp:187:11:187:15 | strncat output argument | strncat output argument |
| test.cpp:198:32:198:38 | command | test.cpp:194:9:194:16 | fread output argument | test.cpp:198:32:198:38 | command indirection | This argument to an OS command is derived from $@, dangerously concatenated into $@, and then passed to execl | test.cpp:194:9:194:16 | fread output argument | user input (String read by fread) | test.cpp:188:11:188:17 | strncat output argument | strncat output argument |
| test.cpp:222:32:222:38 | command | test.cpp:218:9:218:16 | fread output argument | test.cpp:222:32:222:38 | command indirection | This argument to an OS command is derived from $@, dangerously concatenated into $@, and then passed to execl | test.cpp:218:9:218:16 | fread output argument | user input (String read by fread) | test.cpp:220:10:220:16 | strncat output argument | strncat output argument |
| test.cpp:222:32:222:38 | command | test.cpp:218:9:218:16 | fread output argument | test.cpp:222:32:222:38 | command indirection | This argument to an OS command is derived from $@, dangerously concatenated into $@, and then passed to execl | test.cpp:218:9:218:16 | fread output argument | user input (String read by fread) | test.cpp:220:10:220:16 | strncat output argument | strncat output argument |

View File

@@ -168,7 +168,58 @@ void test15(FILE *f) {
system(command); // GOOD: the user string was converted to an integer and back
}
void test16(FILE *f, bool use_flags) {
// BAD: the user string is injected directly into a command
char command[1000] = "mv ", flags[1000] = "-R", filename[1000];
fread(filename, 1, 1000, f);
// TODO: test for call context sensitivity at concatenation site
if (use_flags) {
strncat(flags, filename, 1000);
strncat(command, flags, 1000);
} else {
strncat(command, filename, 1000);
}
execl("/bin/sh", "sh", "-c", command);
}
void concat(char *command, char *flags, char *filename) {
strncat(flags, filename, 1000);
strncat(command, flags, 1000);
}
void test17(FILE *f) {
// BAD: the user string is injected directly into a command
char command[1000] = "mv ", flags[1000] = "-R", filename[1000];
fread(filename, 1, 1000, f);
concat(command, flags, filename);
execl("/bin/sh", "sh", "-c", command);
}
void test18() {
// GOOD
char command[1000] = "ls ", flags[1000] = "-l", filename[1000] = ".";
concat(command, flags, filename);
execl("/bin/sh", "sh", "-c", command);
}
#define CONCAT(COMMAND, FILENAME) \
strncat(COMMAND, FILENAME, 1000); \
strncat(COMMAND, " ", 1000); \
strncat(COMMAND, FILENAME, 1000);
void test19(FILE *f) {
// BAD: the user string is injected directly into a command
char command[1000] = "mv ", filename[1000];
fread(filename, 1, 1000, f);
CONCAT(command, filename)
execl("/bin/sh", "sh", "-c", command);
}
// open question: do we want to report certain sources even when they're the start of the string?

View File

@@ -1,6 +1,7 @@
/** Provides classes for collections. */
import csharp
import semmle.code.csharp.frameworks.system.Collections
private string modifyMethodName() {
result =
@@ -66,6 +67,12 @@ class CollectionType extends RefType {
}
}
/** Holds if `t` is a collection type. */
predicate isCollectionType(ValueOrRefType t) {
t.getABaseType*() instanceof SystemCollectionsIEnumerableInterface and
not t instanceof StringType
}
/** An object creation that creates an empty collection. */
class EmptyCollectionCreation extends ObjectCreation {
EmptyCollectionCreation() {

View File

@@ -105,31 +105,12 @@ private module ConstantComparisonOperation {
}
}
private class StructuralComparisonConfig extends StructuralComparison::StructuralComparisonConfiguration {
StructuralComparisonConfig() { this = "CompareIdenticalValues" }
override predicate candidate(ControlFlowElement x, ControlFlowElement y) {
exists(ComparisonTest ct |
x = ct.getFirstArgument() and
y = ct.getSecondArgument()
)
}
ComparisonTest getComparisonTest() {
exists(Element x, Element y |
result.getFirstArgument() = x and
result.getSecondArgument() = y and
same(x, y)
)
}
}
/**
* Holds if comparison test `ct` compares two structurally identical
* expressions.
*/
predicate comparesIdenticalValues(ComparisonTest ct) {
ct = any(StructuralComparisonConfig c).getComparisonTest()
StructuralComparison::sameGvn(ct.getFirstArgument(), ct.getSecondArgument())
}
/**

View File

@@ -192,13 +192,18 @@ private import Cached
predicate toGvn = toGvnCached/1;
/**
* Holds if the control flow elements `x` and `y` are structurally equal.
*/
pragma[inline]
private predicate sameGvn(ControlFlowElement x, ControlFlowElement y) {
predicate sameGvn(ControlFlowElement x, ControlFlowElement y) {
pragma[only_bind_into](toGvn(pragma[only_bind_out](x))) =
pragma[only_bind_into](toGvn(pragma[only_bind_out](y)))
}
/**
* DEPRECATED: Use `sameGvn` instead.
*
* A configuration for performing structural comparisons of program elements
* (expressions and statements).
*
@@ -207,7 +212,7 @@ private predicate sameGvn(ControlFlowElement x, ControlFlowElement y) {
*
* Each use of the library is identified by a unique string value.
*/
abstract class StructuralComparisonConfiguration extends string {
abstract deprecated class StructuralComparisonConfiguration extends string {
bindingset[this]
StructuralComparisonConfiguration() { any() }
@@ -235,55 +240,3 @@ abstract class StructuralComparisonConfiguration extends string {
*/
predicate same(ControlFlowElement x, ControlFlowElement y) { candidate(x, y) and sameGvn(x, y) }
}
/**
* INTERNAL: Do not use.
*
* A verbatim copy of the class `StructuralComparisonConfiguration` for internal
* use.
*
* A copy is needed in order to use structural comparison within the standard
* library without running into caching issues.
*/
module Internal {
// Import all uses of the internal library to make sure caching works
private import semmle.code.csharp.controlflow.Guards as G
/**
* A configuration for performing structural comparisons of program elements
* (expressions and statements).
*
* The predicate `candidate()` must be overridden, in order to identify the
* elements for which to perform structural comparison.
*
* Each use of the library is identified by a unique string value.
*/
abstract class InternalStructuralComparisonConfiguration extends string {
bindingset[this]
InternalStructuralComparisonConfiguration() { any() }
/**
* Holds if elements `x` and `y` are candidates for testing structural
* equality.
*
* Subclasses are expected to override this predicate to identify the
* top-level elements which they want to compare. Care should be
* taken to avoid identifying too many pairs of elements, as in general
* there are very many structurally equal subtrees in a program, and
* in order to keep the computation feasible we must focus attention.
*
* Note that this relation is not expected to be symmetric -- it's
* fine to include a pair `(x, y)` but not `(y, x)`.
* In fact, not including the symmetrically implied fact will save
* half the computation time on the structural comparison.
*/
abstract predicate candidate(ControlFlowElement x, ControlFlowElement y);
/**
* Holds if elements `x` and `y` structurally equal. `x` and `y` must be
* flagged as candidates for structural equality, that is,
* `candidate(x, y)` must hold.
*/
predicate same(ControlFlowElement x, ControlFlowElement y) { candidate(x, y) and sameGvn(x, y) }
}
}

View File

@@ -8,7 +8,7 @@ private import dotnet
private import ControlFlow::SuccessorTypes
private import semmle.code.csharp.commons.Assertions
private import semmle.code.csharp.commons.ComparisonTest
private import semmle.code.csharp.commons.StructuralComparison::Internal
private import semmle.code.csharp.commons.StructuralComparison as SC
private import semmle.code.csharp.controlflow.BasicBlocks
private import semmle.code.csharp.controlflow.internal.Completion
private import semmle.code.csharp.frameworks.System
@@ -1798,32 +1798,30 @@ module Internal {
}
/**
* A helper class for calculating structurally equal access/call expressions.
* Holds if access/call expression `e` (targeting declaration `target`)
* is a sub expression of a guard that controls whether basic block
* `bb` is reached.
*/
private class ConditionOnExprComparisonConfig extends InternalStructuralComparisonConfiguration {
ConditionOnExprComparisonConfig() { this = "ConditionOnExprComparisonConfig" }
pragma[noinline]
private predicate candidateAux(AccessOrCallExpr e, Declaration target, BasicBlock bb) {
target = e.getTarget() and
guardControlsSub(_, bb, e)
}
override predicate candidate(ControlFlowElement x, ControlFlowElement y) {
exists(BasicBlock bb, Declaration d |
this.candidateAux(x, d, bb) and
y =
any(AccessOrCallExpr e |
e.getAControlFlowNode().getBasicBlock() = bb and
e.getTarget() = d
)
)
}
private predicate candidate(AccessOrCallExpr x, AccessOrCallExpr y) {
exists(BasicBlock bb, Declaration d |
candidateAux(x, d, bb) and
y =
any(AccessOrCallExpr e |
e.getAControlFlowNode().getBasicBlock() = bb and
e.getTarget() = d
)
)
}
/**
* Holds if access/call expression `e` (targeting declaration `target`)
* is a sub expression of a guard that controls whether basic block
* `bb` is reached.
*/
pragma[noinline]
private predicate candidateAux(AccessOrCallExpr e, Declaration target, BasicBlock bb) {
target = e.getTarget() and
guardControlsSub(_, bb, e)
}
private predicate same(AccessOrCallExpr x, AccessOrCallExpr y) {
candidate(x, y) and
SC::sameGvn(x, y)
}
cached
@@ -1849,7 +1847,7 @@ module Internal {
pragma[nomagic]
private predicate guardControlsSubSame(Guard g, BasicBlock bb, ControlGuardDescendant sub) {
guardControlsSub(g, bb, sub) and
any(ConditionOnExprComparisonConfig c).same(sub, _)
same(sub, _)
}
pragma[nomagic]
@@ -1862,7 +1860,7 @@ module Internal {
guardedBB = guardedCfn.getBasicBlock() and
guardControls(g, guardedBB, v) and
guardControlsSubSame(g, guardedBB, sub) and
any(ConditionOnExprComparisonConfig c).same(sub, guarded)
same(sub, guarded)
}
pragma[nomagic]

View File

@@ -2031,3 +2031,47 @@ abstract class SyntheticField extends string {
* Holds if the the content `c` is a container.
*/
predicate containerContent(DataFlow::Content c) { c instanceof DataFlow::ElementContent }
/**
* A module containing predicates related to generating models as data.
*/
module Csv {
private string parameterQualifiedTypeNamesToString(DataFlowCallable c) {
result =
concat(Parameter p, int i |
p = c.getParameter(i)
|
p.getType().getQualifiedName(), "," order by i
)
}
/** Holds if the summary should apply for all overrides of `c`. */
predicate isBaseCallableOrPrototype(DataFlowCallable c) {
c.getDeclaringType() instanceof Interface
or
exists(Modifiable m | m = [c.(Modifiable), c.(Accessor).getDeclaration()] |
m.isAbstract()
or
c.getDeclaringType().(Modifiable).isAbstract() and m.(Virtualizable).isVirtual()
)
}
/** Gets a string representing whether the summary should apply for all overrides of `c`. */
private string getCallableOverride(DataFlowCallable c) {
if isBaseCallableOrPrototype(c) then result = "true" else result = "false"
}
/** Computes the first 6 columns for CSV rows of `c`. */
string asPartialModel(DataFlowCallable c) {
exists(string namespace, string type |
c.getDeclaringType().hasQualifiedName(namespace, type) and
result =
namespace + ";" //
+ type + ";" //
+ getCallableOverride(c) + ";" //
+ c.getName() + ";" //
+ "(" + parameterQualifiedTypeNamesToString(c) + ")" //
+ /* ext + */ ";" //
)
}
}

View File

@@ -1056,7 +1056,7 @@ module Private {
|
c.relevantSummary(input, output, preservesValue) and
csv =
c.getCallableCsv() + ";;" + getComponentStackCsv(input) + ";" +
c.getCallableCsv() + ";" + getComponentStackCsv(input) + ";" +
getComponentStackCsv(output) + ";" + renderKind(preservesValue)
)
}

View File

@@ -14,24 +14,12 @@
import csharp
import semmle.code.csharp.commons.StructuralComparison
class DoubleCheckedLock extends StructuralComparisonConfiguration {
DoubleCheckedLock() { this = "double checked lock" }
override predicate candidate(ControlFlowElement x, ControlFlowElement y) {
exists(IfStmt unlockedIf, IfStmt lockedIf, LockStmt lock |
x = unlockedIf.getCondition() and
y = lockedIf.getCondition() and
lock = unlockedIf.getThen().stripSingletonBlocks() and
lockedIf.getParent*() = lock.getBlock()
)
}
}
predicate doubleCheckedLock(Field field, IfStmt ifs) {
exists(DoubleCheckedLock config, LockStmt lock, Expr eq1, Expr eq2 | ifs.getCondition() = eq1 |
lock = ifs.getThen().stripSingletonBlocks() and
config.same(eq1, eq2) and
field.getAnAccess() = eq1.getAChildExpr*()
predicate doubleCheckedLock(Field field, IfStmt unlockedIf) {
exists(LockStmt lock, IfStmt lockedIf |
lock = unlockedIf.getThen().stripSingletonBlocks() and
lockedIf.getParent*() = lock.getBlock() and
sameGvn(unlockedIf.getCondition(), lockedIf.getCondition()) and
field.getAnAccess() = unlockedIf.getCondition().getAChildExpr*()
)
}

View File

@@ -12,25 +12,8 @@
import csharp
import semmle.code.csharp.commons.StructuralComparison
class StructuralComparisonConfig extends StructuralComparisonConfiguration {
StructuralComparisonConfig() { this = "MissedTernaryOpportunity" }
override predicate candidate(ControlFlowElement x, ControlFlowElement y) {
exists(IfStmt is, AssignExpr ae1 |
ae1 = is.getThen().stripSingletonBlocks().(ExprStmt).getExpr()
|
x = ae1.getLValue() and
exists(AssignExpr ae2 | ae2 = is.getElse().stripSingletonBlocks().(ExprStmt).getExpr() |
y = ae2.getLValue()
)
)
}
IfStmt getIfStmt() {
exists(AssignExpr ae | ae = result.getThen().stripSingletonBlocks().(ExprStmt).getExpr() |
same(ae.getLValue(), _)
)
}
private Expr getAssignedExpr(Stmt stmt) {
result = stmt.stripSingletonBlocks().(ExprStmt).getExpr().(AssignExpr).getLValue()
}
from IfStmt is, string what
@@ -40,10 +23,8 @@ where
is.getElse().stripSingletonBlocks() instanceof ReturnStmt and
what = "return"
or
exists(StructuralComparisonConfig c |
is = c.getIfStmt() and
what = "write to the same variable"
)
sameGvn(getAssignedExpr(is.getThen()), getAssignedExpr(is.getElse())) and
what = "write to the same variable"
) and
not exists(IfStmt other | is = other.getElse())
select is,

View File

@@ -13,35 +13,26 @@
import csharp
import semmle.code.csharp.commons.StructuralComparison
class StructuralComparisonConfig extends StructuralComparisonConfiguration {
StructuralComparisonConfig() { this = "UselessIsBeforeAs" }
private predicate candidate(AsExpr ae, IsExpr ie) {
exists(IfStmt is, TypeAccessPatternExpr tape |
ie = is.getCondition().getAChild*() and
tape = ie.getPattern() and
ae.getTargetType() = tape.getTarget()
|
ae = is.getThen().getAChild*()
or
ae = is.getElse().getAChild*()
)
}
override predicate candidate(ControlFlowElement x, ControlFlowElement y) {
exists(IfStmt is, AsExpr ae, IsExpr ie, TypeAccessPatternExpr tape |
ie = is.getCondition().getAChild*() and
tape = ie.getPattern() and
ae.getTargetType() = tape.getTarget() and
x = ie.getExpr() and
y = ae.getExpr()
|
ae = is.getThen().getAChild*()
or
ae = is.getElse().getAChild*()
)
}
predicate uselessIsBeforeAs(AsExpr ae, IsExpr ie) {
exists(Expr x, Expr y |
same(x, y) and
ie.getExpr() = x and
ae.getExpr() = y
)
}
private predicate uselessIsBeforeAs(AsExpr ae, IsExpr ie) {
candidate(ae, ie) and
sameGvn(ie.getExpr(), ae.getExpr())
}
from AsExpr ae, IsExpr ie
where
exists(StructuralComparisonConfig c | c.uselessIsBeforeAs(ae, ie)) and
uselessIsBeforeAs(ae, ie) and
not exists(MethodCall mc | ae = mc.getAnArgument().getAChildExpr*())
select ae,
"This 'as' expression performs a type test - it should be directly compared against null, rendering the 'is' $@ potentially redundant.",

View File

@@ -14,24 +14,22 @@
import csharp
import semmle.code.csharp.commons.StructuralComparison
class StructuralComparisonConfig extends StructuralComparisonConfiguration {
StructuralComparisonConfig() { this = "UselessNullCoalescingExpression" }
override predicate candidate(ControlFlowElement x, ControlFlowElement y) {
exists(NullCoalescingExpr nce |
x.(Access) = nce.getLeftOperand() and
y.(Access) = nce.getRightOperand().getAChildExpr*()
)
}
NullCoalescingExpr getUselessNullCoalescingExpr() {
exists(AssignableAccess x |
result.getLeftOperand() = x and
forex(AssignableAccess y | same(x, y) | y instanceof AssignableRead and not y.isRefArgument())
)
}
pragma[noinline]
private predicate same(AssignableAccess x, AssignableAccess y) {
exists(NullCoalescingExpr nce |
x = nce.getLeftOperand() and
y = nce.getRightOperand().getAChildExpr*()
) and
sameGvn(x, y)
}
from StructuralComparisonConfig c, NullCoalescingExpr nce
where nce = c.getUselessNullCoalescingExpr()
private predicate uselessNullCoalescingExpr(NullCoalescingExpr nce) {
exists(AssignableAccess x |
nce.getLeftOperand() = x and
forex(AssignableAccess y | same(x, y) | y instanceof AssignableRead and not y.isRefArgument())
)
}
from NullCoalescingExpr nce
where uselessNullCoalescingExpr(nce)
select nce, "Both operands of this null-coalescing expression access the same variable or property."

View File

@@ -15,18 +15,6 @@ import csharp
import semmle.code.csharp.commons.ComparisonTest
import semmle.code.csharp.commons.StructuralComparison as SC
/** A structural comparison configuration for comparing the conditions of nested `for` loops. */
class NestedForConditions extends SC::StructuralComparisonConfiguration {
NestedForConditions() { this = "Compare nested for conditions" }
override predicate candidate(ControlFlowElement e1, ControlFlowElement e2) {
exists(NestedForLoopSameVariable nested |
e1 = nested.getInnerForStmt().getCondition() and
e2 = nested.getOuterForStmt().getCondition()
)
}
}
private predicate hasChild(Stmt outer, Element child) {
outer = child.getParent() and
(outer instanceof ForStmt or outer = any(ForStmt f).getBody())
@@ -61,9 +49,7 @@ class NestedForLoopSameVariable extends ForStmt {
}
private predicate haveSameCondition() {
exists(NestedForConditions config |
config.same(this.getInnerForStmt().getCondition(), this.getOuterForStmt().getCondition())
)
SC::sameGvn(this.getInnerForStmt().getCondition(), this.getOuterForStmt().getCondition())
}
private predicate haveSameUpdate() {

View File

@@ -13,37 +13,25 @@
import csharp
import semmle.code.csharp.commons.StructuralComparison
class StructuralComparisonConfig extends StructuralComparisonConfiguration {
StructuralComparisonConfig() { this = "SelfAssignment" }
override predicate candidate(ControlFlowElement x, ControlFlowElement y) {
exists(AssignExpr ae |
// Member initializers are never self-assignments, in particular
// not initializers such as `new C { F = F };`
not ae instanceof MemberInitializer and
// Enum field initializers are never self assignments. `enum E { A = 42 }`
not ae.getParent().(Field).getDeclaringType() instanceof Enum
|
ae.getLValue() = x and
ae.getRValue() = y
) and
forall(Expr e | e = x.(Expr).getAChildExpr*() |
// Non-trivial property accesses may have side-effects,
// so these are not considered
e instanceof PropertyAccess implies e instanceof TrivialPropertyAccess
)
}
AssignExpr getSelfAssignExpr() {
exists(Expr x, Expr y |
same(x, y) and
result.getLValue() = x and
result.getRValue() = y
)
}
private predicate candidate(AssignExpr ae) {
// Member initializers are never self-assignments, in particular
// not initializers such as `new C { F = F };`
not ae instanceof MemberInitializer and
// Enum field initializers are never self assignments. `enum E { A = 42 }`
not ae.getParent().(Field).getDeclaringType() instanceof Enum and
forall(Expr e | e = ae.getLValue().getAChildExpr*() |
// Non-trivial property accesses may have side-effects,
// so these are not considered
e instanceof PropertyAccess implies e instanceof TrivialPropertyAccess
)
}
Declaration getDeclaration(Expr e) {
private predicate selfAssignExpr(AssignExpr ae) {
candidate(ae) and
sameGvn(ae.getLValue(), ae.getRValue())
}
private Declaration getDeclaration(Expr e) {
result = e.(VariableAccess).getTarget()
or
result = e.(MemberAccess).getTarget()
@@ -51,6 +39,6 @@ Declaration getDeclaration(Expr e) {
result = getDeclaration(e.(ArrayAccess).getQualifier())
}
from StructuralComparisonConfig c, AssignExpr ae, Declaration target
where ae = c.getSelfAssignExpr() and target = getDeclaration(ae.getLValue())
from AssignExpr ae, Declaration target
where selfAssignExpr(ae) and target = getDeclaration(ae.getLValue())
select ae, "This assignment assigns $@ to itself.", target, target.getName()

View File

@@ -13,19 +13,14 @@ import csharp
import semmle.code.csharp.commons.StructuralComparison
import semmle.code.csharp.controlflow.Guards as G
class SameElement extends StructuralComparisonConfiguration {
SameElement() { this = "Same element" }
override predicate candidate(ControlFlowElement e1, ControlFlowElement e2) {
exists(MethodCall mc, IndexerRead access |
mc.getTarget().hasName("ContainsKey") and
access.getQualifier().(G::GuardedExpr).isGuardedBy(mc, mc.getQualifier(), _) and
e1 = mc.getArgument(0) and
e2 = access.getIndex(0)
)
}
pragma[noinline]
private predicate candidate(MethodCall mc, IndexerRead access) {
mc.getTarget().hasName("ContainsKey") and
access.getQualifier().(G::GuardedExpr).isGuardedBy(mc, mc.getQualifier(), _)
}
from SameElement element, MethodCall call, IndexerAccess index
where element.same(call.getArgument(0), index.getIndex(0))
from MethodCall call, IndexerRead index
where
candidate(call, index) and
sameGvn(call.getArgument(0), index.getIndex(0))
select call, "Inefficient use of 'ContainsKey' and $@.", index, "indexer"

View File

@@ -1,18 +1,19 @@
import csharp
import semmle.code.csharp.dataflow.internal.DataFlowPrivate
private import semmle.code.csharp.commons.Util
private import semmle.code.csharp.commons.Collections
private import semmle.code.csharp.dataflow.internal.DataFlowImplCommon
private import semmle.code.csharp.dataflow.internal.DataFlowDispatch
private predicate isRelevantForModels(Callable api) { not api instanceof MainMethod }
/**
* A class of Callables that are relevant for generating summary, source and sinks models for.
* A class of callables that are relevant generating summary, source and sinks models for.
*
* In the Standard library and 3rd party libraries it the Callables that can be called
* In the Standard library and 3rd party libraries it the callables that can be called
* from outside the library itself.
*/
class TargetApi extends Callable {
class TargetApi extends DataFlowCallable {
TargetApi() {
[this.(Modifiable), this.(Accessor).getDeclaration()].isEffectivelyPublic() and
this.fromSource() and
@@ -20,44 +21,7 @@ class TargetApi extends Callable {
}
}
private string parameterQualifiedTypeNamesToString(TargetApi api) {
result =
concat(Parameter p, int i |
p = api.getParameter(i)
|
p.getType().getQualifiedName(), "," order by i
)
}
/** Holds if the summary should apply for all overrides of this. */
private predicate isBaseCallableOrPrototype(TargetApi api) {
api.getDeclaringType() instanceof Interface
or
exists(Modifiable m | m = [api.(Modifiable), api.(Accessor).getDeclaration()] |
m.isAbstract()
or
api.getDeclaringType().(Modifiable).isAbstract() and m.(Virtualizable).isVirtual()
)
}
/** Gets a string representing whether the summary should apply for all overrides of this. */
private string getCallableOverride(TargetApi api) {
if isBaseCallableOrPrototype(api) then result = "true" else result = "false"
}
/** Computes the first 6 columns for CSV rows. */
string asPartialModel(TargetApi api) {
exists(string namespace, string type |
api.getDeclaringType().hasQualifiedName(namespace, type) and
result =
namespace + ";" //
+ type + ";" //
+ getCallableOverride(api) + ";" //
+ api.getName() + ";" //
+ "(" + parameterQualifiedTypeNamesToString(api) + ")" //
+ /* ext + */ ";" //
)
}
predicate asPartialModel = Csv::asPartialModel/1;
/**
* Holds for type `t` for fields that are relevant as an intermediate
@@ -65,14 +29,8 @@ string asPartialModel(TargetApi api) {
*/
predicate isRelevantType(Type t) { not t instanceof Enum }
private predicate isPrimitiveTypeUsedForBulkData(Type t) {
t.getName().regexpMatch("byte|char|Byte|Char")
}
private string parameterAccess(Parameter p) {
if
p.getType() instanceof ArrayType and
not isPrimitiveTypeUsedForBulkData(p.getType().(ArrayType).getElementType())
if isCollectionType(p.getType())
then result = "Argument[" + p.getPosition() + "].Element"
else result = "Argument[" + p.getPosition() + "]"
}

View File

@@ -1,4 +1,5 @@
import shared.FlowSummaries
private import semmle.code.csharp.dataflow.internal.DataFlowPrivate::Csv
private import semmle.code.csharp.dataflow.ExternalFlow
class IncludeFilteredSummarizedCallable extends IncludeSummarizedCallable {
@@ -14,7 +15,7 @@ class IncludeFilteredSummarizedCallable extends IncludeSummarizedCallable {
) {
this.propagatesFlow(input, output, preservesValue) and
not exists(IncludeSummarizedCallable rsc |
rsc.isBaseCallableOrPrototype() and
isBaseCallableOrPrototype(rsc) and
rsc.propagatesFlow(input, output, preservesValue) and
this.(UnboundCallable).overridesOrImplementsUnbound(rsc)
)

View File

@@ -1,28 +1,25 @@
import csharp
import semmle.code.csharp.commons.StructuralComparison
private class StructuralComparisonTest extends StructuralComparisonConfiguration {
StructuralComparisonTest() { this = "StructuralComparisonTest" }
/**
* All pairs of controls flow elements found in the source and within the same
* enclosing callable excluding all instances of `ThisAccess` to reduce the size
* of the output.
*/
override predicate candidate(ControlFlowElement e1, ControlFlowElement e2) {
e1.fromSource() and
e2.fromSource() and
e1 != e2 and
e1.getEnclosingCallable() = e2.getEnclosingCallable() and
not e1 instanceof ThisAccess
}
/**
* All pairs of controls flow elements found in the source and within the same
* enclosing callable excluding all instances of `ThisAccess` to reduce the size
* of the output.
*/
private predicate candidate(ControlFlowElement x, ControlFlowElement y) {
x.fromSource() and
y.fromSource() and
x != y and
x.getEnclosingCallable() = y.getEnclosingCallable() and
not x instanceof ThisAccess
}
query predicate same(ControlFlowElement e1, ControlFlowElement e2) {
exists(StructuralComparisonTest sct, Location l1, Location l2 |
sct.same(e1, e2) and
l1 = e1.getLocation() and
l2 = e2.getLocation() and
query predicate same(ControlFlowElement x, ControlFlowElement y) {
exists(Location l1, Location l2 |
candidate(x, y) and
sameGvn(x, y) and
l1 = x.getLocation() and
l2 = y.getLocation() and
(
l1.getStartLine() < l2.getStartLine()
or
@@ -31,4 +28,4 @@ query predicate same(ControlFlowElement e1, ControlFlowElement e2) {
)
}
query predicate gvn(ControlFlowElement e, Gvn gvn) { gvn = toGvn(e) and e.fromSource() }
query predicate gvn(ControlFlowElement x, Gvn gvn) { gvn = toGvn(x) and x.fromSource() }

View File

@@ -1,44 +1,12 @@
import semmle.code.csharp.dataflow.FlowSummary
import semmle.code.csharp.dataflow.internal.FlowSummaryImpl::Private::TestOutput
private import semmle.code.csharp.dataflow.internal.DataFlowPrivate
abstract class IncludeSummarizedCallable extends RelevantSummarizedCallable {
IncludeSummarizedCallable() {
[this.(Modifiable), this.(Accessor).getDeclaration()].isEffectivelyPublic()
}
/** Gets the qualified parameter types of this callable as a comma-separated string. */
private string parameterQualifiedTypeNamesToString() {
result =
concat(Parameter p, int i |
p = this.getParameter(i)
|
p.getType().getQualifiedName(), "," order by i
)
}
/** Holds if the summary should apply for all overrides of this. */
predicate isBaseCallableOrPrototype() {
this.getDeclaringType() instanceof Interface
or
exists(Modifiable m | m = [this.(Modifiable), this.(Accessor).getDeclaration()] |
m.isAbstract()
or
this.getDeclaringType().(Modifiable).isAbstract() and m.(Virtualizable).isVirtual()
)
}
/** Gets a string representing whether the summary should apply for all overrides of this. */
private string getCallableOverride() {
if this.isBaseCallableOrPrototype() then result = "true" else result = "false"
}
/** Gets a string representing the callable in semi-colon separated format for use in flow summaries. */
final override string getCallableCsv() {
exists(string namespace, string type |
this.getDeclaringType().hasQualifiedName(namespace, type) and
result =
namespace + ";" + type + ";" + this.getCallableOverride() + ";" + this.getName() + ";" + "("
+ this.parameterQualifiedTypeNamesToString() + ")"
)
}
final override string getCallableCsv() { result = Csv::asPartialModel(this) }
}

View File

@@ -1,6 +1,5 @@
| Summaries;BasicFlow;false;AssignFieldToArray;(System.Object[]);Argument[Qualifier];Argument[0].Element;taint |
| Summaries;BasicFlow;false;AssignToArray;(System.Int32,System.Int32[]);Argument[0];Argument[1].Element;taint |
| Summaries;BasicFlow;false;ReturnArrayElement;(System.Int32[]);Argument[0].Element;ReturnValue;taint |
| NoSummaries;PublicClassFlow;false;PublicReturn;(System.Int32);Argument[0];ReturnValue;taint |
| Summaries;BaseClassFlow;true;ReturnParam;(System.Int32);Argument[0];ReturnValue;taint |
| Summaries;BasicFlow;false;ReturnField;();Argument[Qualifier];ReturnValue;taint |
| Summaries;BasicFlow;false;ReturnParam0;(System.String,System.Object);Argument[0];ReturnValue;taint |
| Summaries;BasicFlow;false;ReturnParam1;(System.String,System.Object);Argument[1];ReturnValue;taint |
@@ -9,3 +8,23 @@
| Summaries;BasicFlow;false;ReturnSubstring;(System.String);Argument[0];ReturnValue;taint |
| Summaries;BasicFlow;false;ReturnThis;(System.Object);Argument[Qualifier];ReturnValue;value |
| Summaries;BasicFlow;false;SetField;(System.String);Argument[0];Argument[Qualifier];taint |
| Summaries;CollectionFlow;false;AddFieldToList;(System.Collections.Generic.List<System.String>);Argument[Qualifier];Argument[0].Element;taint |
| Summaries;CollectionFlow;false;AddToList;(System.Collections.Generic.List<System.Object>,System.Object);Argument[1];Argument[0].Element;taint |
| Summaries;CollectionFlow;false;AssignFieldToArray;(System.Object[]);Argument[Qualifier];Argument[0].Element;taint |
| Summaries;CollectionFlow;false;AssignToArray;(System.Int32,System.Int32[]);Argument[0];Argument[1].Element;taint |
| Summaries;CollectionFlow;false;ReturnArrayElement;(System.Int32[]);Argument[0].Element;ReturnValue;taint |
| Summaries;CollectionFlow;false;ReturnFieldInAList;();Argument[Qualifier];ReturnValue;taint |
| Summaries;CollectionFlow;false;ReturnListElement;(System.Collections.Generic.List<System.Object>);Argument[0].Element;ReturnValue;taint |
| Summaries;DerivedClass1Flow;false;ReturnParam1;(System.Int32,System.Int32);Argument[1];ReturnValue;taint |
| Summaries;DerivedClass2Flow;false;ReturnParam0;(System.Int32,System.Int32);Argument[0];ReturnValue;taint |
| Summaries;DerivedClass2Flow;false;ReturnParam;(System.Int32);Argument[0];ReturnValue;taint |
| Summaries;GenericFlow<>;false;AddFieldToGenericList;(System.Collections.Generic.List<T>);Argument[Qualifier];Argument[0].Element;taint |
| Summaries;GenericFlow<>;false;AddToGenericList<>;(System.Collections.Generic.List<S>,S);Argument[1];Argument[0].Element;taint |
| Summaries;GenericFlow<>;false;ReturnFieldInGenericList;();Argument[Qualifier];ReturnValue;taint |
| Summaries;GenericFlow<>;false;ReturnGenericElement<>;(System.Collections.Generic.List<S>);Argument[0].Element;ReturnValue;taint |
| Summaries;GenericFlow<>;false;ReturnGenericField;();Argument[Qualifier];ReturnValue;taint |
| Summaries;GenericFlow<>;false;ReturnGenericParam<>;(S);Argument[0];ReturnValue;taint |
| Summaries;GenericFlow<>;false;SetGenericField;(T);Argument[0];Argument[Qualifier];taint |
| Summaries;IEnumerableFlow;false;ReturnFieldInIEnumerable;();Argument[Qualifier];ReturnValue;taint |
| Summaries;IEnumerableFlow;false;ReturnIEnumerable;(System.Collections.Generic.IEnumerable<System.String>);Argument[0].Element;ReturnValue;taint |
| Summaries;IEnumerableFlow;false;ReturnIEnumerableElement;(System.Collections.Generic.IEnumerable<System.Object>);Argument[0].Element;ReturnValue;taint |

View File

@@ -0,0 +1,45 @@
using System;
namespace NoSummaries;
// Single class with a method that produces a flow summary.
// Just to prove that, if a method like this is correctly exposed, a flow summary will be captured.
public class PublicClassFlow
{
public int PublicReturn(int input)
{
return input;
}
}
public sealed class PublicClassNoFlow
{
private int PrivateReturn(int input)
{
return input;
}
internal int InternalReturn(int input)
{
return input;
}
private class PrivateClassNoFlow
{
public int ReturnParam(int input)
{
return input;
}
}
private class PrivateClassNestedPublicClassNoFlow
{
public class NestedPublicClassFlow
{
public int ReturnParam(int input)
{
return input;
}
}
}
}

View File

@@ -1,4 +1,6 @@
using System;
using System.Linq;
using System.Collections.Generic;
namespace Summaries;
@@ -31,6 +33,21 @@ public class BasicFlow
return s.Substring(0, 1);
}
public void SetField(string s)
{
tainted = s;
}
public string ReturnField()
{
return tainted;
}
}
public class CollectionFlow
{
private string tainted;
public int ReturnArrayElement(int[] input)
{
return input[0];
@@ -41,18 +58,117 @@ public class BasicFlow
target[0] = data;
}
public void SetField(string s)
{
tainted = s;
}
public string ReturnField()
{
return tainted;
}
public void AssignFieldToArray(object[] target)
{
target[0] = tainted;
}
public object ReturnListElement(List<object> input)
{
return input[0];
}
public void AddToList(List<object> input, object data)
{
input.Add(data);
}
public void AddFieldToList(List<string> input)
{
input.Add(tainted);
}
public List<string> ReturnFieldInAList()
{
return new List<string> { tainted };
}
}
public class IEnumerableFlow
{
private string tainted;
public IEnumerable<string> ReturnIEnumerable(IEnumerable<string> input)
{
return input;
}
public object ReturnIEnumerableElement(IEnumerable<object> input)
{
return input.First();
}
public IEnumerable<string> ReturnFieldInIEnumerable()
{
return new List<string> { tainted };
}
}
public class GenericFlow<T>
{
private T tainted;
public void SetGenericField(T t)
{
tainted = t;
}
public T ReturnGenericField()
{
return tainted;
}
public void AddFieldToGenericList(List<T> input)
{
input.Add(tainted);
}
public List<T> ReturnFieldInGenericList()
{
return new List<T> { tainted };
}
public S ReturnGenericParam<S>(S input)
{
return input;
}
public S ReturnGenericElement<S>(List<S> input)
{
return input[0];
}
public void AddToGenericList<S>(List<S> input, S data)
{
input.Add(data);
}
}
public abstract class BaseClassFlow
{
public virtual int ReturnParam(int input)
{
return input;
}
}
public class DerivedClass1Flow : BaseClassFlow
{
public int ReturnParam1(int input0, int input1)
{
return input1;
}
}
public class DerivedClass2Flow : BaseClassFlow
{
public override int ReturnParam(int input)
{
return input;
}
public int ReturnParam0(int input0, int input1)
{
return input0;
}
}

View File

@@ -0,0 +1 @@
semmle-extractor-options: /r:System.Linq.dll

View File

@@ -7,8 +7,8 @@ When analyzing a Go program, CodeQL does not examine the source code for
external packages. To track the flow of untrusted data through a library, you
can create a model of the library.
You can find existing models in the ``ql/src/semmle/go/frameworks/`` folder of the
`CodeQL for Go repository <https://github.com/github/codeql-go/tree/main/ql/src/semmle/go/frameworks>`__.
You can find existing models in the ``ql/lib/semmle/go/frameworks/`` folder of the
`CodeQL for Go repository <https://github.com/github/codeql-go/tree/main/ql/lib/semmle/go/frameworks>`__.
To add a new model, you should make a new file in that folder, named after the library.
Sources
@@ -102,8 +102,8 @@ Data-flow sinks are specified by queries rather than by library models.
However, you can use library models to indicate when functions belong to
special categories. Queries can then use these categories when specifying
sinks. Classes representing these special categories are contained in
``ql/src/semmle/go/Concepts.qll`` in the `CodeQL for Go repository
<https://github.com/github/codeql-go/blob/main/ql/src/semmle/go/Concepts.qll>`__.
``ql/lib/semmle/go/Concepts.qll`` in the `CodeQL for Go repository
<https://github.com/github/codeql-go/blob/main/ql/lib/semmle/go/Concepts.qll>`__.
``Concepts.qll`` includes classes for logger mechanisms,
HTTP response writers, HTTP redirects, and marshaling and unmarshaling
functions.

View File

@@ -1056,7 +1056,7 @@ module Private {
|
c.relevantSummary(input, output, preservesValue) and
csv =
c.getCallableCsv() + ";;" + getComponentStackCsv(input) + ";" +
c.getCallableCsv() + ";" + getComponentStackCsv(input) + ";" +
getComponentStackCsv(output) + ";" + renderKind(preservesValue)
)
}

View File

@@ -5,7 +5,7 @@ private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.dataflow.internal.ContainerFlow
private import semmle.code.java.dataflow.internal.DataFlowImplCommon
Method superImpl(Method m) {
private Method superImpl(Method m) {
result = m.getAnOverride() and
not exists(result.getAnOverride()) and
not m instanceof ToStringMethod

View File

@@ -1,5 +1,5 @@
name: codeql/javascript-experimental-atm-lib
version: 0.1.1
version: 0.2.1
extractor: javascript
library: true
groups:

View File

@@ -1,7 +1,7 @@
name: codeql/javascript-experimental-atm-model
version: 0.0.6
version: 0.1.1
groups:
- javascript
- experimental
mlModels:
- "resources/*.codeqlmodel"
- "resources/*.codeqlmodel"

View File

@@ -1,6 +1,6 @@
---
dependencies:
codeql/javascript-experimental-atm-model:
version: 0.0.6
version: 0.1.0
compiled: false
lockVersion: 1.0.0

View File

@@ -6,4 +6,4 @@ groups:
- experimental
dependencies:
codeql/javascript-experimental-atm-lib: "*"
codeql/javascript-experimental-atm-model: "0.0.6"
codeql/javascript-experimental-atm-model: "0.1.0"

View File

@@ -1,6 +1,6 @@
---
dependencies:
codeql/javascript-experimental-atm-model:
version: 0.0.6
version: 0.1.0
compiled: false
lockVersion: 1.0.0

View File

@@ -1,6 +1,6 @@
name: codeql/javascript-experimental-atm-queries
language: javascript
version: 0.1.1
version: 0.2.1
suites: codeql-suites
defaultSuiteFile: codeql-suites/javascript-atm-code-scanning.qls
groups:
@@ -8,4 +8,4 @@ groups:
- experimental
dependencies:
codeql/javascript-experimental-atm-lib: "*"
codeql/javascript-experimental-atm-model: "0.0.6"
codeql/javascript-experimental-atm-model: "0.1.0"

View File

@@ -1,6 +1,6 @@
---
dependencies:
codeql/javascript-experimental-atm-model:
version: 0.0.6
version: 0.1.0
compiled: false
lockVersion: 1.0.0

View File

@@ -0,0 +1,7 @@
---
category: fix
---
* The following predicates on `API::Node` have been changed so as not to include the receiver. The receiver should now only be accessed via `getReceiver()`.
- `getParameter(int i)` previously included the receiver when `i = -1`
- `getAParameter()` previously included the receiver
- `getLastParameter()` previously included the receiver for calls with no arguments

View File

@@ -187,8 +187,7 @@ module API {
}
/**
* Gets a node representing a parameter or the receiver of the function represented by this
* node.
* Gets a node representing a parameter of the function represented by this node.
*
* This predicate may result in a mix of parameters from different call sites in cases where
* there are multiple invocations of this API component.
@@ -198,8 +197,6 @@ module API {
Node getAParameter() {
Stages::ApiStage::ref() and
result = this.getParameter(_)
or
result = this.getReceiver()
}
/**
@@ -561,9 +558,10 @@ module API {
rhs = f.getExceptionalReturn()
)
or
exists(int i |
lbl = Label::parameter(i) and
argumentPassing(base, i, rhs)
exists(int i | argumentPassing(base, i, rhs) |
lbl = Label::parameter(i)
or
i = -1 and lbl = Label::receiver()
)
or
exists(DataFlow::SourceNode src, DataFlow::PropWrite pw |
@@ -1096,8 +1094,8 @@ module API {
*/
LabelParameter parameter(int i) { result.getIndex() = i }
/** Gets the `parameter` edge label for the receiver. */
LabelParameter receiver() { result = parameter(-1) }
/** Gets the edge label for the receiver. */
LabelReceiver receiver() { any() }
/** Gets the `return` edge label. */
LabelReturn return() { any() }
@@ -1132,12 +1130,13 @@ module API {
MkLabelUnknownMember() or
MkLabelParameter(int i) {
i =
[-1 .. max(int args |
[0 .. max(int args |
args = any(InvokeExpr invk).getNumArgument() or
args = any(Function f).getNumParameter()
)] or
i = [0 .. 10]
} or
MkLabelReceiver() or
MkLabelReturn() or
MkLabelPromised() or
MkLabelPromisedError() or
@@ -1225,6 +1224,11 @@ module API {
/** Gets the index of the parameter for this label. */
int getIndex() { result = i }
}
/** A label for the receiver of call, that is, the value passed as `this`. */
class LabelReceiver extends ApiLabel, MkLabelReceiver {
override string toString() { result = "receiver" }
}
}
}
}

View File

@@ -1365,27 +1365,31 @@ private predicate loadStep(
/**
* Holds if there is flow to `base.startProp`, and `base.startProp` flows to `nd.endProp` under `cfg/summary`.
*
* If `onlyRelevantInCall` is true, the `base` object will not be propagated out of return edges, because
* the flow that originally reached `base.startProp` used a call edge.
*/
pragma[nomagic]
private predicate reachableFromStoreBase(
string startProp, string endProp, DataFlow::Node base, DataFlow::Node nd,
DataFlow::Configuration cfg, PathSummary summary
DataFlow::Configuration cfg, PathSummary summary, boolean onlyRelevantInCall
) {
exists(PathSummary s1, PathSummary s2, DataFlow::Node rhs |
reachableFromSource(rhs, cfg, s1)
reachableFromSource(rhs, cfg, s1) and
onlyRelevantInCall = s1.hasCall()
or
reachableFromStoreBase(_, _, _, rhs, cfg, s1)
reachableFromStoreBase(_, _, _, rhs, cfg, s1, onlyRelevantInCall)
|
storeStep(rhs, nd, startProp, cfg, s2) and
endProp = startProp and
base = nd and
summary =
MkPathSummary(false, s1.hasCall().booleanOr(s2.hasCall()), DataFlow::FlowLabel::data(),
DataFlow::FlowLabel::data())
MkPathSummary(false, s2.hasCall(), DataFlow::FlowLabel::data(), DataFlow::FlowLabel::data())
)
or
exists(PathSummary newSummary, PathSummary oldSummary |
reachableFromStoreBaseStep(startProp, endProp, base, nd, cfg, oldSummary, newSummary) and
reachableFromStoreBaseStep(startProp, endProp, base, nd, cfg, oldSummary, newSummary,
onlyRelevantInCall) and
summary = oldSummary.appendValuePreserving(newSummary)
)
}
@@ -1399,14 +1403,16 @@ private predicate reachableFromStoreBase(
pragma[noinline]
private predicate reachableFromStoreBaseStep(
string startProp, string endProp, DataFlow::Node base, DataFlow::Node nd,
DataFlow::Configuration cfg, PathSummary oldSummary, PathSummary newSummary
DataFlow::Configuration cfg, PathSummary oldSummary, PathSummary newSummary,
boolean onlyRelevantInCall
) {
exists(DataFlow::Node mid |
reachableFromStoreBase(startProp, endProp, base, mid, cfg, oldSummary) and
flowStep(mid, cfg, nd, newSummary)
reachableFromStoreBase(startProp, endProp, base, mid, cfg, oldSummary, onlyRelevantInCall) and
flowStep(mid, cfg, nd, newSummary) and
onlyRelevantInCall.booleanAnd(newSummary.hasReturn()) = false
or
exists(string midProp |
reachableFromStoreBase(startProp, midProp, base, mid, cfg, oldSummary) and
reachableFromStoreBase(startProp, midProp, base, mid, cfg, oldSummary, onlyRelevantInCall) and
isAdditionalLoadStoreStep(mid, nd, midProp, endProp, cfg) and
newSummary = PathSummary::level()
)
@@ -1446,7 +1452,7 @@ private predicate storeToLoad(
PathSummary s1, PathSummary s2
|
storeStep(pred, storeBase, storeProp, cfg, s1) and
reachableFromStoreBase(storeProp, loadProp, storeBase, loadBase, cfg, s2) and
reachableFromStoreBase(storeProp, loadProp, storeBase, loadBase, cfg, s2, _) and
oldSummary = s1.appendValuePreserving(s2) and
loadStep(loadBase, succ, loadProp, cfg, newSummary)
)

View File

@@ -0,0 +1,6 @@
/**
* This file contains imports required for the JavaScript version of `ConceptsShared.qll`.
* Since they are language-specific, they can't be placed directly in that file, as it is shared between languages.
*/
import semmle.javascript.dataflow.DataFlow::DataFlow as DataFlow

View File

@@ -0,0 +1,13 @@
/**
* Provides Concepts which are shared across languages.
*
* Each language has a language specific `Concepts.qll` file that can import the
* shared concepts from this file. A language can either re-export the concept directly,
* or can add additional member-predicates that are needed for that language.
*
* Moving forward, `Concepts.qll` will be the staging ground for brand new concepts from
* each language, but we will maintain a discipline of moving those concepts to
* `ConceptsShared.qll` ASAP.
*/
private import ConceptsImports

View File

@@ -219,7 +219,6 @@ module ExternalApiUsedWithUntrustedData {
or
exists(string callbackName, int index |
node = getNamedParameter(base.getParameter(index).getMember(callbackName), paramName) and
index != -1 and // ignore receiver
result =
basename + ".[callback " + index + " '" + callbackName + "'].[param '" + paramName +
"']"

View File

@@ -4,10 +4,14 @@
* for adding your own.
*/
import javascript
import semmle.javascript.security.dataflow.RemoteFlowSources
/**
* Provides default sources, sinks and sanitizers for reasoning about
* writing user-controlled data to files, as well as extension points
* for adding your own.
*/
module HttpToFileAccess {
import HttpToFileAccessSpecific
/**
* A data flow source for writing user-controlled data to files.
*/
@@ -23,18 +27,6 @@ module HttpToFileAccess {
*/
abstract class Sanitizer extends DataFlow::Node { }
/**
* An access to a user-controlled HTTP request input, considered as a flow source for writing user-controlled data to files
*/
private class RequestInputAccessAsSource extends Source {
RequestInputAccessAsSource() { this instanceof HTTP::RequestInputAccess }
}
/** A response from a server, considered as a flow source for writing user-controlled data to files. */
private class ServerResponseAsSource extends Source {
ServerResponseAsSource() { this = any(ClientRequest r).getAResponseDataNode() }
}
/** A sink that represents file access method (write, append) argument */
class FileAccessAsSink extends Sink {
FileAccessAsSink() { exists(FileSystemWriteAccess src | this = src.getADataNode()) }

View File

@@ -6,8 +6,7 @@
* `HttpToFileAccessCustomizations` should be imported instead.
*/
import javascript
import HttpToFileAccessCustomizations::HttpToFileAccess
private import HttpToFileAccessCustomizations::HttpToFileAccess
/**
* A taint tracking configuration for writing user-controlled data to files.

View File

@@ -0,0 +1,19 @@
/**
* Provides imports and classes needed for `HttpToFileAccessQuery` and `HttpToFileAccessCustomizations`.
*/
import javascript
import semmle.javascript.security.dataflow.RemoteFlowSources
private import HttpToFileAccessCustomizations::HttpToFileAccess
/**
* An access to a user-controlled HTTP request input, considered as a flow source for writing user-controlled data to files
*/
private class RequestInputAccessAsSource extends Source {
RequestInputAccessAsSource() { this instanceof HTTP::RequestInputAccess }
}
/** A response from a server, considered as a flow source for writing user-controlled data to files. */
private class ServerResponseAsSource extends Source {
ServerResponseAsSource() { this = any(ClientRequest r).getAResponseDataNode() }
}

View File

@@ -3,10 +3,13 @@
* format injections, as well as extension points for adding your own.
*/
import javascript
import semmle.javascript.security.dataflow.DOM
/**
* Provides default sources, sinks and sanitizers for reasoning about
* format injections, as well as extension points for adding your own.
*/
module TaintedFormatString {
import TaintedFormatStringSpecific
/**
* A data flow source for format injections.
*/
@@ -23,9 +26,7 @@ module TaintedFormatString {
abstract class Sanitizer extends DataFlow::Node { }
/** A source of remote user input, considered as a flow source for format injection. */
class RemoteSource extends Source {
RemoteSource() { this instanceof RemoteFlowSource }
}
class RemoteSource extends Source instanceof RemoteFlowSource { }
/**
* A format argument to a printf-like function, considered as a flow sink for format injection.

View File

@@ -8,9 +8,7 @@
* `TaintedFormatStringCustomizations` should be imported instead.
*/
import javascript
import semmle.javascript.security.dataflow.DOM
import TaintedFormatStringCustomizations::TaintedFormatString
private import TaintedFormatStringCustomizations::TaintedFormatString
/**
* A taint-tracking configuration for format injections.

View File

@@ -0,0 +1,6 @@
/**
* Provides JS-specific imports needed for `TaintedFormatStringQuery` and `TaintedFormatStringCustomizations`.
*/
import javascript
import semmle.javascript.security.dataflow.DOM

View File

@@ -32,36 +32,58 @@ module XssThroughDom {
*/
string unsafeDomPropertyName() { result = ["innerText", "textContent", "value", "name", "src"] }
/**
* A source for text from the DOM from a JQuery method call.
*/
class JQueryTextSource extends Source, JQuery::MethodCall {
/** A read of a DOM property seen as a source for cross-site scripting vulnerabilities through the DOM. */
abstract class DomPropertySource extends Source {
/**
* Gets the name of the DOM property that the source originated from.
*/
abstract string getPropertyName();
}
/* Gets a jQuery method where the receiver looks like `$("<p>" + ... )`, which is benign for this query. */
private JQuery::MethodCall benignJQueryMethod() {
exists(DataFlow::Node prefix |
DomBasedXss::isPrefixOfJQueryHtmlString(result
.getReceiver()
.(DataFlow::CallNode)
.getAnArgument(), prefix)
|
prefix.getStringValue().regexpMatch("\\s*<.*")
)
}
/** A source for text from the DOM from a JQuery method call. */
class JQueryTextSource extends Source instanceof JQuery::MethodCall {
JQueryTextSource() {
(
this.getMethodName() = ["text", "val"] and this.getNumArgument() = 0
or
exists(string methodName, string value |
this.getMethodName() = methodName and
this.getNumArgument() = 1 and
forex(InferredType t | t = this.getArgument(0).analyze().getAType() | t = TTString()) and
this.getArgument(0).mayHaveStringValue(value)
|
methodName = "attr" and value = unsafeAttributeName()
or
methodName = "prop" and value = unsafeDomPropertyName()
)
) and
// looks like a $("<p>" + ... ) source, which is benign for this query.
not exists(DataFlow::Node prefix |
DomBasedXss::isPrefixOfJQueryHtmlString(this.getReceiver()
.(DataFlow::CallNode)
.getAnArgument(), prefix)
|
prefix.getStringValue().regexpMatch("\\s*<.*")
)
this.getMethodName() = ["text", "val"] and
this.getNumArgument() = 0 and
not this = benignJQueryMethod()
}
}
/**
* A source for text from a DOM property read by jQuery.
*/
class JQueryDOMPropertySource extends DomPropertySource instanceof JQuery::MethodCall {
string prop;
JQueryDOMPropertySource() {
exists(string methodName |
this.getMethodName() = methodName and
this.getNumArgument() = 1 and
forex(InferredType t | t = this.getArgument(0).analyze().getAType() | t = TTString()) and
this.getArgument(0).mayHaveStringValue(prop)
|
methodName = "attr" and prop = unsafeAttributeName()
or
methodName = "prop" and prop = unsafeDomPropertyName()
) and
not this = benignJQueryMethod()
}
override string getPropertyName() { result = prop }
}
/**
* A source for text from the DOM from a `d3` method call.
*/
@@ -88,19 +110,25 @@ module XssThroughDom {
/**
* A source for text from the DOM from a DOM property read or call to `getAttribute()`.
*/
class DomTextSource extends Source {
class DomTextSource extends DomPropertySource {
string prop;
DomTextSource() {
exists(DataFlow::PropRead read | read = this |
read.getBase().getALocalSource() = DOM::domValueRef() and
read.mayHavePropertyName(unsafeDomPropertyName())
prop = unsafeDomPropertyName() and
read.mayHavePropertyName(prop)
)
or
exists(DataFlow::MethodCallNode mcn | mcn = this |
mcn.getReceiver().getALocalSource() = DOM::domValueRef() and
mcn.getMethodName() = "getAttribute" and
mcn.getArgument(0).mayHaveStringValue(unsafeAttributeName())
prop = unsafeAttributeName() and
mcn.getArgument(0).mayHaveStringValue(prop)
)
}
override string getPropertyName() { result = prop }
}
/** DEPRECATED: Alias for DomTextSource */

View File

@@ -35,4 +35,13 @@ class Configuration extends TaintTracking::Configuration {
override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
DomBasedXss::isOptionallySanitizedEdge(pred, succ)
}
override predicate hasFlowPath(DataFlow::SourcePathNode src, DataFlow::SinkPathNode sink) {
super.hasFlowPath(src, sink) and
// filtering away readings of `src` that end in a URL sink.
not (
sink.getNode() instanceof DomBasedXss::WriteURLSink and
src.getNode().(DomPropertySource).getPropertyName() = "src"
)
}
}

View File

@@ -98,7 +98,8 @@ module HeuristicNames {
* suggesting nouns within the string do not represent the meaning of the whole string (e.g. a URL or a SQL query).
*/
string notSensitiveRegexp() {
result = "(?is).*([^\\w$.-]|redact|censor|obfuscate|hash|md5|sha|((?<!un)(en))?(crypt|code)).*"
result =
"(?is).*([^\\w$.-]|redact|censor|obfuscate|hash|md5|sha|random|((?<!un)(en))?(crypt|code)).*"
}
/**

View File

@@ -0,0 +1,6 @@
---
category: minorAnalysis
---
* Fixed an issue that would sometimes prevent the data-flow analysis from finding flow
paths through a function that stores its result on an object.
This may lead to more results for the security queries.

View File

@@ -1,7 +1,7 @@
import bar from 'foo';
let boundbar = bar.bind(
"receiver", // def (parameter -1 (member default (member exports (module foo))))
"receiver", // def (receiver (member default (member exports (module foo))))
"firstarg" // def (parameter 0 (member default (member exports (module foo))))
);
boundbar(
@@ -9,7 +9,7 @@ boundbar(
)
let boundbar2 = boundbar.bind(
"ignored", // !def (parameter -1 (member default (member exports (module foo))))
"ignored", // !def (receiver (member default (member exports (module foo))))
"othersecondarg" // def (parameter 1 (member default (member exports (module foo))))
)
boundbar2(

View File

@@ -2,7 +2,7 @@ const cp = require('child_process');
module.exports = function () {
return cp.spawn.bind(
cp, // def (parameter -1 (member spawn (member exports (module child_process))))
cp, // def (receiver (member spawn (member exports (module child_process))))
"cat" // def (parameter 0 (member spawn (member exports (module child_process))))
);
};

View File

@@ -172,6 +172,7 @@ typeInferenceMismatch
| string-replace.js:3:13:3:20 | source() | string-replace.js:21:6:21:41 | safe(). ... taint) |
| string-replace.js:3:13:3:20 | source() | string-replace.js:22:6:22:48 | safe(). ... taint) |
| string-replace.js:3:13:3:20 | source() | string-replace.js:24:6:24:45 | taint.r ... + '!') |
| summarize-store-load-in-call.js:9:15:9:22 | source() | summarize-store-load-in-call.js:9:10:9:23 | blah(source()) |
| thisAssignments.js:4:17:4:24 | source() | thisAssignments.js:5:10:5:18 | obj.field |
| thisAssignments.js:7:19:7:26 | source() | thisAssignments.js:8:10:8:20 | this.field2 |
| tst.js:2:13:2:20 | source() | tst.js:4:10:4:10 | x |

View File

@@ -0,0 +1,12 @@
import * as dummy from 'dummy';
function blah(obj) {
obj.prop = obj.prop + "x";
return obj.prop;
}
function test() {
sink(blah(source())); // NOT OK
blah(); // ensure more than one call site exists
}

View File

@@ -42,11 +42,11 @@ knexObject
| tst.js:17:1:17:23 | use (return (member avg (return (member exports (module knex))))) |
| tst.js:17:1:19:4 | use (return (member from (return (member avg (return (member exports (module knex))))))) |
| tst.js:17:1:19:24 | use (return (member as (return (member from (return (member avg (return (member exports (module knex))))))))) |
| tst.js:17:30:17:29 | use (parameter -1 (parameter 0 (member from (return (member avg (return (member exports (module knex)))))))) |
| tst.js:18:5:18:38 | use (return (member sum (parameter -1 (parameter 0 (member from (return (member avg (return (member exports (module knex)))))))))) |
| tst.js:18:5:18:49 | use (return (member from (return (member sum (parameter -1 (parameter 0 (member from (return (member avg (return (member exports (module knex)))))))))))) |
| tst.js:18:5:18:68 | use (return (member groupBy (return (member from (return (member sum (parameter -1 (parameter 0 (member from (return (member avg (return (member exports (module knex)))))))))))))) |
| tst.js:18:5:18:77 | use (return (member as (return (member groupBy (return (member from (return (member sum (parameter -1 (parameter 0 (member from (return (member avg (return (member exports (module knex)))))))))))))))) |
| tst.js:17:30:17:29 | use (receiver (parameter 0 (member from (return (member avg (return (member exports (module knex)))))))) |
| tst.js:18:5:18:38 | use (return (member sum (receiver (parameter 0 (member from (return (member avg (return (member exports (module knex)))))))))) |
| tst.js:18:5:18:49 | use (return (member from (return (member sum (receiver (parameter 0 (member from (return (member avg (return (member exports (module knex)))))))))))) |
| tst.js:18:5:18:68 | use (return (member groupBy (return (member from (return (member sum (receiver (parameter 0 (member from (return (member avg (return (member exports (module knex)))))))))))))) |
| tst.js:18:5:18:77 | use (return (member as (return (member groupBy (return (member from (return (member sum (receiver (parameter 0 (member from (return (member avg (return (member exports (module knex)))))))))))))))) |
| tst.js:21:1:21:38 | use (return (member column (return (member exports (module knex))))) |
| tst.js:21:1:21:47 | use (return (member select (return (member column (return (member exports (module knex))))))) |
| tst.js:21:1:21:61 | use (return (member from (return (member select (return (member column (return (member exports (module knex))))))))) |
@@ -70,14 +70,14 @@ knexObject
| tst.js:42:1:42:13 | use (return (return (member exports (module knex)))) |
| tst.js:42:1:45:3 | use (return (member where (return (return (member exports (module knex)))))) |
| tst.js:42:1:48:4 | use (return (member andWhere (return (member where (return (return (member exports (module knex)))))))) |
| tst.js:46:13:46:12 | use (parameter -1 (parameter 0 (member andWhere (return (member where (return (return (member exports (module knex))))))))) |
| tst.js:47:5:47:29 | use (return (member where (parameter -1 (parameter 0 (member andWhere (return (member where (return (return (member exports (module knex))))))))))) |
| tst.js:46:13:46:12 | use (receiver (parameter 0 (member andWhere (return (member where (return (return (member exports (module knex))))))))) |
| tst.js:47:5:47:29 | use (return (member where (receiver (parameter 0 (member andWhere (return (member where (return (return (member exports (module knex))))))))))) |
| tst.js:50:1:50:13 | use (return (return (member exports (module knex)))) |
| tst.js:50:1:52:2 | use (return (member where (return (return (member exports (module knex)))))) |
| tst.js:50:1:52:28 | use (return (member orWhere (return (member where (return (return (member exports (module knex)))))))) |
| tst.js:50:21:50:20 | use (parameter -1 (parameter 0 (member where (return (return (member exports (module knex))))))) |
| tst.js:51:3:51:21 | use (return (member where (parameter -1 (parameter 0 (member where (return (return (member exports (module knex))))))))) |
| tst.js:51:3:51:44 | use (return (member orWhere (return (member where (parameter -1 (parameter 0 (member where (return (return (member exports (module knex))))))))))) |
| tst.js:50:21:50:20 | use (receiver (parameter 0 (member where (return (return (member exports (module knex))))))) |
| tst.js:51:3:51:21 | use (return (member where (receiver (parameter 0 (member where (return (return (member exports (module knex))))))))) |
| tst.js:51:3:51:44 | use (return (member orWhere (return (member where (receiver (parameter 0 (member where (return (return (member exports (module knex))))))))))) |
| tst.js:54:1:54:13 | use (return (return (member exports (module knex)))) |
| tst.js:54:1:54:56 | use (return (member where (return (return (member exports (module knex)))))) |
| tst.js:56:1:56:13 | use (return (return (member exports (module knex)))) |
@@ -100,9 +100,9 @@ knexObject
| tst.js:70:1:70:13 | use (return (return (member exports (module knex)))) |
| tst.js:70:1:72:2 | use (return (member whereNot (return (return (member exports (module knex)))))) |
| tst.js:70:1:72:31 | use (return (member orWhereNot (return (member whereNot (return (return (member exports (module knex)))))))) |
| tst.js:70:24:70:23 | use (parameter -1 (parameter 0 (member whereNot (return (return (member exports (module knex))))))) |
| tst.js:71:3:71:21 | use (return (member where (parameter -1 (parameter 0 (member whereNot (return (return (member exports (module knex))))))))) |
| tst.js:71:3:71:47 | use (return (member orWhereNot (return (member where (parameter -1 (parameter 0 (member whereNot (return (return (member exports (module knex))))))))))) |
| tst.js:70:24:70:23 | use (receiver (parameter 0 (member whereNot (return (return (member exports (module knex))))))) |
| tst.js:71:3:71:21 | use (return (member where (receiver (parameter 0 (member whereNot (return (return (member exports (module knex))))))))) |
| tst.js:71:3:71:47 | use (return (member orWhereNot (return (member where (receiver (parameter 0 (member whereNot (return (return (member exports (module knex))))))))))) |
| tst.js:74:19:74:31 | use (return (return (member exports (module knex)))) |
| tst.js:74:19:75:30 | use (return (member whereNot (return (return (member exports (module knex)))))) |
| tst.js:74:19:76:31 | use (return (member andWhere (return (member whereNot (return (return (member exports (module knex)))))))) |
@@ -128,10 +128,10 @@ knexObject
| tst.js:97:1:97:40 | use (return (member whereNotNull (return (return (member exports (module knex)))))) |
| tst.js:99:1:99:13 | use (return (return (member exports (module knex)))) |
| tst.js:99:1:101:2 | use (return (member whereExists (return (return (member exports (module knex)))))) |
| tst.js:99:27:99:26 | use (parameter -1 (parameter 0 (member whereExists (return (return (member exports (module knex))))))) |
| tst.js:100:3:100:18 | use (return (member select (parameter -1 (parameter 0 (member whereExists (return (return (member exports (module knex))))))))) |
| tst.js:100:3:100:35 | use (return (member from (return (member select (parameter -1 (parameter 0 (member whereExists (return (return (member exports (module knex))))))))))) |
| tst.js:100:3:100:78 | use (return (member whereRaw (return (member from (return (member select (parameter -1 (parameter 0 (member whereExists (return (return (member exports (module knex))))))))))))) |
| tst.js:99:27:99:26 | use (receiver (parameter 0 (member whereExists (return (return (member exports (module knex))))))) |
| tst.js:100:3:100:18 | use (return (member select (receiver (parameter 0 (member whereExists (return (return (member exports (module knex))))))))) |
| tst.js:100:3:100:35 | use (return (member from (return (member select (receiver (parameter 0 (member whereExists (return (return (member exports (module knex))))))))))) |
| tst.js:100:3:100:78 | use (return (member whereRaw (return (member from (return (member select (receiver (parameter 0 (member whereExists (return (return (member exports (module knex))))))))))))) |
| tst.js:103:1:103:13 | use (return (return (member exports (module knex)))) |
| tst.js:103:1:103:103 | use (return (member whereExists (return (return (member exports (module knex)))))) |
| tst.js:103:27:103:42 | use (return (member select (return (member exports (module knex))))) |
@@ -139,10 +139,10 @@ knexObject
| tst.js:103:27:103:102 | use (return (member whereRaw (return (member from (return (member select (return (member exports (module knex))))))))) |
| tst.js:105:1:105:13 | use (return (return (member exports (module knex)))) |
| tst.js:105:1:107:2 | use (return (member whereNotExists (return (return (member exports (module knex)))))) |
| tst.js:105:30:105:29 | use (parameter -1 (parameter 0 (member whereNotExists (return (return (member exports (module knex))))))) |
| tst.js:106:3:106:18 | use (return (member select (parameter -1 (parameter 0 (member whereNotExists (return (return (member exports (module knex))))))))) |
| tst.js:106:3:106:35 | use (return (member from (return (member select (parameter -1 (parameter 0 (member whereNotExists (return (return (member exports (module knex))))))))))) |
| tst.js:106:3:106:78 | use (return (member whereRaw (return (member from (return (member select (parameter -1 (parameter 0 (member whereNotExists (return (return (member exports (module knex))))))))))))) |
| tst.js:105:30:105:29 | use (receiver (parameter 0 (member whereNotExists (return (return (member exports (module knex))))))) |
| tst.js:106:3:106:18 | use (return (member select (receiver (parameter 0 (member whereNotExists (return (return (member exports (module knex))))))))) |
| tst.js:106:3:106:35 | use (return (member from (return (member select (receiver (parameter 0 (member whereNotExists (return (return (member exports (module knex))))))))))) |
| tst.js:106:3:106:78 | use (return (member whereRaw (return (member from (return (member select (receiver (parameter 0 (member whereNotExists (return (return (member exports (module knex))))))))))))) |
| tst.js:109:1:109:13 | use (return (return (member exports (module knex)))) |
| tst.js:109:1:109:45 | use (return (member whereBetween (return (return (member exports (module knex)))))) |
| tst.js:111:1:111:13 | use (return (return (member exports (module knex)))) |

View File

@@ -122,6 +122,13 @@ nodes
| xss-through-dom.js:109:31:109:70 | "<a src ... oo</a>" |
| xss-through-dom.js:109:45:109:55 | this.el.src |
| xss-through-dom.js:109:45:109:55 | this.el.src |
| xss-through-dom.js:114:11:114:52 | src |
| xss-through-dom.js:114:17:114:52 | documen ... k").src |
| xss-through-dom.js:114:17:114:52 | documen ... k").src |
| xss-through-dom.js:115:16:115:18 | src |
| xss-through-dom.js:115:16:115:18 | src |
| xss-through-dom.js:117:26:117:28 | src |
| xss-through-dom.js:117:26:117:28 | src |
edges
| forms.js:8:23:8:28 | values | forms.js:9:31:9:36 | values |
| forms.js:8:23:8:28 | values | forms.js:9:31:9:36 | values |
@@ -194,6 +201,12 @@ edges
| xss-through-dom.js:109:45:109:55 | this.el.src | xss-through-dom.js:109:31:109:70 | "<a src ... oo</a>" |
| xss-through-dom.js:109:45:109:55 | this.el.src | xss-through-dom.js:109:31:109:70 | "<a src ... oo</a>" |
| xss-through-dom.js:109:45:109:55 | this.el.src | xss-through-dom.js:109:31:109:70 | "<a src ... oo</a>" |
| xss-through-dom.js:114:11:114:52 | src | xss-through-dom.js:115:16:115:18 | src |
| xss-through-dom.js:114:11:114:52 | src | xss-through-dom.js:115:16:115:18 | src |
| xss-through-dom.js:114:11:114:52 | src | xss-through-dom.js:117:26:117:28 | src |
| xss-through-dom.js:114:11:114:52 | src | xss-through-dom.js:117:26:117:28 | src |
| xss-through-dom.js:114:17:114:52 | documen ... k").src | xss-through-dom.js:114:11:114:52 | src |
| xss-through-dom.js:114:17:114:52 | documen ... k").src | xss-through-dom.js:114:11:114:52 | src |
#select
| forms.js:9:31:9:40 | values.foo | forms.js:8:23:8:28 | values | forms.js:9:31:9:40 | values.foo | $@ is reinterpreted as HTML without escaping meta-characters. | forms.js:8:23:8:28 | values | DOM text |
| forms.js:12:31:12:40 | values.bar | forms.js:11:24:11:29 | values | forms.js:12:31:12:40 | values.bar | $@ is reinterpreted as HTML without escaping meta-characters. | forms.js:11:24:11:29 | values | DOM text |
@@ -228,3 +241,4 @@ edges
| xss-through-dom.js:93:16:93:46 | $("#foo ... ].value | xss-through-dom.js:93:16:93:46 | $("#foo ... ].value | xss-through-dom.js:93:16:93:46 | $("#foo ... ].value | $@ is reinterpreted as HTML without escaping meta-characters. | xss-through-dom.js:93:16:93:46 | $("#foo ... ].value | DOM text |
| xss-through-dom.js:96:17:96:47 | $("#foo ... ].value | xss-through-dom.js:96:17:96:47 | $("#foo ... ].value | xss-through-dom.js:96:17:96:47 | $("#foo ... ].value | $@ is reinterpreted as HTML without escaping meta-characters. | xss-through-dom.js:96:17:96:47 | $("#foo ... ].value | DOM text |
| xss-through-dom.js:109:31:109:70 | "<a src ... oo</a>" | xss-through-dom.js:109:45:109:55 | this.el.src | xss-through-dom.js:109:31:109:70 | "<a src ... oo</a>" | $@ is reinterpreted as HTML without escaping meta-characters. | xss-through-dom.js:109:45:109:55 | this.el.src | DOM text |
| xss-through-dom.js:115:16:115:18 | src | xss-through-dom.js:114:17:114:52 | documen ... k").src | xss-through-dom.js:115:16:115:18 | src | $@ is reinterpreted as HTML without escaping meta-characters. | xss-through-dom.js:114:17:114:52 | documen ... k").src | DOM text |

View File

@@ -108,4 +108,11 @@ class Sub extends Super {
super();
$("#id").get(0).innerHTML = "<a src=\"" + this.el.src + "\">foo</a>"; // NOT OK. Attack: `<mytag id="id" src="x:&quot;&gt;&lt;img src=1 onerror=&quot;alert(1)&quot;&gt;" />`
}
}
}
(function () {
const src = document.getElementById("#link").src;
$("#id").html(src); // NOT OK.
$("#id").attr("src", src); // OK
})();

View File

@@ -27,3 +27,4 @@
query path:
- /^experimental\/.*/
- Metrics/Summaries/FrameworkCoverage.ql
- /Diagnostics/Internal/.*/

View File

@@ -0,0 +1,4 @@
---
category: majorAnalysis
---
* Added data-flow for Django ORM models that are saved in a database (no `models.ForeignKey` support).

View File

@@ -445,6 +445,8 @@ class RegExpAlt extends RegExpTerm, TRegExpAlt {
override string getPrimaryQLClass() { result = "RegExpAlt" }
}
class RegExpCharEscape = RegExpEscape;
/**
* An escaped regular expression term, that is, a regular expression
* term starting with a backslash, which is not a backreference.
@@ -751,6 +753,9 @@ class RegExpGroup extends RegExpTerm, TRegExpGroup {
*/
int getNumber() { result = re.getGroupNumber(start, end) }
/** Holds if this is a capture group. */
predicate isCapture() { exists(this.getNumber()) }
/** Holds if this is a named capture group. */
predicate isNamed() { exists(this.getName()) }

View File

@@ -0,0 +1,41 @@
/**
* Provides classes for working with regular expressions.
*/
private import semmle.python.RegexTreeView
private import semmle.python.regex
private import semmle.python.dataflow.new.DataFlow
/**
* Provides utility predicates related to regular expressions.
*/
module RegExpPatterns {
/**
* Gets a pattern that matches common top-level domain names in lower case.
*/
string getACommonTld() {
// according to ranking by http://google.com/search?q=site:.<<TLD>>
result = "(?:com|org|edu|gov|uk|net|io)(?![a-z0-9])"
}
}
/**
* A node whose value may flow to a position where it is interpreted
* as a part of a regular expression.
*/
class RegExpPatternSource extends DataFlow::CfgNode {
private Regex astNode;
RegExpPatternSource() { astNode = this.asExpr() }
/**
* Gets a node where the pattern of this node is parsed as a part of
* a regular expression.
*/
DataFlow::Node getAParse() { result = this }
/**
* Gets the root term of the regular expression parsed from this pattern.
*/
RegExpTerm getRegExpTerm() { result.getRegex() = astNode }
}

View File

@@ -3,6 +3,15 @@ private import DataFlowPublic
import semmle.python.SpecialMethods
private import semmle.python.essa.SsaCompute
private import semmle.python.dataflow.new.internal.ImportStar
// Since we allow extra data-flow steps from modeled frameworks, we import these
// up-front, to ensure these are included. This provides a more seamless experience from
// a user point of view, since they don't need to know they need to import a specific
// set of .qll files to get the same data-flow steps as they are used to seeing. This
// also ensures that we don't end up re-evaluating data-flow because it has different
// global steps in some configurations.
//
// This matches behavior in C#.
private import semmle.python.Frameworks
/** Gets the callable in which this node occurs. */
DataFlowCallable nodeGetEnclosingCallable(Node n) { result = n.getEnclosingCallable() }
@@ -943,6 +952,24 @@ string ppReprType(DataFlowType t) { none() }
* taken into account.
*/
predicate jumpStep(Node nodeFrom, Node nodeTo) {
jumpStepSharedWithTypeTracker(nodeFrom, nodeTo)
or
jumpStepNotSharedWithTypeTracker(nodeFrom, nodeTo)
}
/**
* Set of jumpSteps that are shared with type-tracker implementation.
*
* For ORM modeling we want to add jumpsteps to global dataflow, but since these are
* based on type-trackers, it's important that these new ORM jumsteps are not used in
* the type-trackers as well, as that would make evaluation of type-tracking recursive
* with the new jumpsteps.
*
* Holds if `pred` can flow to `succ`, by jumping from one callable to
* another. Additional steps specified by the configuration are *not*
* taken into account.
*/
predicate jumpStepSharedWithTypeTracker(Node nodeFrom, Node nodeTo) {
runtimeJumpStep(nodeFrom, nodeTo)
or
// Read of module attribute:
@@ -956,6 +983,22 @@ predicate jumpStep(Node nodeFrom, Node nodeTo) {
defaultValueFlowStep(nodeFrom, nodeTo)
}
/**
* Set of jumpSteps that are NOT shared with type-tracker implementation.
*
* For ORM modeling we want to add jumpsteps to global dataflow, but since these are
* based on type-trackers, it's important that these new ORM jumsteps are not used in
* the type-trackers as well, as that would make evaluation of type-tracking recursive
* with the new jumpsteps.
*
* Holds if `pred` can flow to `succ`, by jumping from one callable to
* another. Additional steps specified by the configuration are *not*
* taken into account.
*/
predicate jumpStepNotSharedWithTypeTracker(Node nodeFrom, Node nodeTo) {
any(Orm::AdditionalOrmSteps es).jumpStep(nodeFrom, nodeTo)
}
/**
* Holds if the module `m` defines a name `name` by assigning `defn` to it. This is an
* overapproximation, as `name` may not in fact be exported (e.g. by defining an `__all__` that does
@@ -999,6 +1042,51 @@ predicate storeStep(Node nodeFrom, Content c, Node nodeTo) {
kwOverflowStoreStep(nodeFrom, c, nodeTo)
or
matchStoreStep(nodeFrom, c, nodeTo)
or
any(Orm::AdditionalOrmSteps es).storeStep(nodeFrom, c, nodeTo)
}
/**
* INTERNAL: Do not use.
*
* Provides classes for modeling data-flow through ORM models saved in a DB.
*/
module Orm {
/**
* INTERNAL: Do not use.
*
* A unit class for adding additional data-flow steps for ORM models.
*/
class AdditionalOrmSteps extends Unit {
/**
* Holds if data can flow from `nodeFrom` to `nodeTo` via an assignment to
* content `c`.
*/
abstract predicate storeStep(Node nodeFrom, Content c, Node nodeTo);
/**
* Holds if `pred` can flow to `succ`, by jumping from one callable to
* another. Additional steps specified by the configuration are *not*
* taken into account.
*/
abstract predicate jumpStep(Node nodeFrom, Node nodeTo);
}
/** A synthetic node representing the data for an ORM model saved in a DB. */
class SyntheticOrmModelNode extends Node, TSyntheticOrmModelNode {
Class cls;
SyntheticOrmModelNode() { this = TSyntheticOrmModelNode(cls) }
override string toString() { result = "[orm-model] " + cls.toString() }
override Scope getScope() { result = cls.getEnclosingScope() }
override Location getLocation() { result = cls.getLocation() }
/** Gets the class that defines this ORM model. */
Class getClass() { result = cls }
}
}
/** Data flows from an element of a list to the list. */

View File

@@ -87,7 +87,20 @@ newtype TNode =
/**
* A synthetic node representing element content in a star pattern.
*/
TStarPatternElementNode(MatchStarPattern target)
TStarPatternElementNode(MatchStarPattern target) or
/**
* INTERNAL: Do not use.
*
* A synthetic node representing the data for an ORM model saved in a DB.
*/
// TODO: Limiting the classes here to the ones that are actually ORM models was
// non-trivial, since that logic is based on API::Node results, and trying to do this
// causes non-monotonic recursion, and makes the API graph evaluation recursive with
// data-flow, which might do bad things for performance.
//
// So for now we live with having these synthetic ORM nodes for _all_ classes, which
// is a bit wasteful, but we don't think it will hurt too much.
TSyntheticOrmModelNode(Class cls)
/** Helper for `Node::getEnclosingCallable`. */
private DataFlowCallable getCallableScope(Scope s) {

View File

@@ -12,7 +12,7 @@ class TypeTrackingNode = DataFlowPublic::TypeTrackingNode;
predicate simpleLocalFlowStep = DataFlowPrivate::simpleLocalFlowStep/2;
predicate jumpStep = DataFlowPrivate::jumpStep/2;
predicate jumpStep = DataFlowPrivate::jumpStepSharedWithTypeTracker/2;
/** Holds if there is a level step from `pred` to `succ`. */
predicate levelStep(Node pred, Node succ) { none() }

View File

@@ -574,7 +574,7 @@ module PrivateDjango {
* See https://docs.djangoproject.com/en/3.1/topics/db/models/.
*/
module Model {
/** Gets a reference to the `flask.views.View` class or any subclass. */
/** Gets a reference to the `django.db.models.Model` class or any subclass. */
API::Node subclassRef() {
result =
API::moduleImport("django")
@@ -582,37 +582,199 @@ module PrivateDjango {
.getMember("models")
.getMember("Model")
.getASubclass*()
or
result =
API::moduleImport("django")
.getMember("db")
.getMember("models")
.getMember("base")
.getMember("Model")
.getASubclass*()
or
result =
API::moduleImport("polymorphic")
.getMember("models")
.getMember("PolymorphicModel")
.getASubclass*()
}
/**
* A source of instances of `django.db.models.Model` class or any subclass, extend this class to model new instances.
*
* This can include instantiations of the class, return values from function
* calls, or a special parameter that will be set when functions are called by an external
* library.
*
* Use the predicate `Model::instance()` to get references to instances of `django.db.models.Model` class or any subclass.
*/
abstract class InstanceSource extends DataFlow::LocalSourceNode {
/** Gets the model class that this is an instance source of. */
abstract API::Node getModelClass();
/** Holds if this instance-source is fetching data from the DB. */
abstract predicate isDbFetch();
}
/** A direct instantiation of `django.db.models.Model` class or any subclass. */
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
API::Node modelClass;
ClassInstantiation() {
modelClass = subclassRef() and
this = modelClass.getACall()
}
override API::Node getModelClass() { result = modelClass }
override predicate isDbFetch() { none() }
}
/** A method call on a query-set or manager that returns an instance of a django model. */
private class QuerySetMethod extends InstanceSource, DataFlow::CallCfgNode {
API::Node modelClass;
string methodName;
QuerySetMethod() {
modelClass = subclassRef() and
methodName in ["get", "create", "earliest", "latest", "first", "last"] and
this = [manager(modelClass), querySet(modelClass)].getMember(methodName).getACall()
}
override API::Node getModelClass() { result = modelClass }
override predicate isDbFetch() { not methodName = "create" }
}
/**
* A method call on a query-set or manager that returns a collection
* containing instances of a django model.
*/
class QuerySetMethodInstanceCollection extends DataFlow::CallCfgNode {
API::Node modelClass;
string methodName;
QuerySetMethodInstanceCollection() {
modelClass = subclassRef() and
this = querySetReturningMethod(modelClass, methodName).getACall() and
not methodName in ["none", "datetimes", "dates", "values", "values_list"]
or
// TODO: When we have flow-summaries, we should be able to model `values` and `values_list`
// Potentially by doing `synthetic ===store of list element==> <Model>.objects`, and then
// `.all()` just keeps that content, and `.first()` will do a read step (of the list element).
//
// So for `Model.objects.filter().exclude().first()` we would have
// 1: <Synthetic node for Model> ===store of list element==> Model.objects
// 2: Model.objects ==> Model.objects.filter()
// 3: Model.objects.filter() ==> Model.objects.filter().exclude()
// 4: Model.objects.filter().exclude() ===read of list element==> Model.objects.filter().exclude().first()
//
// This also means that `.none()` could clear contents. Right now we
// think that the result of `Model.objects.none().all()` can contain
// Model objects, but it will be empty due to the `.none()` part. Not
// that this is important, since no-one would need to write
// `.none().all()` code anyway, but it would be cool to be able to model it properly :D
//
//
// The big benefit is for how we could then model `values`/`values_list`. For example,
// `Model.objects.value_list(name, description)` would result in (for the attribute description)
// 0: <Synthetic node for Model> -- [attr description]
// 1: ==> Model.objects [ListElement, attr description]
// 2: ==> .value_list(...) [ListElement, TupleIndex 1]
//
// but for now, we just model a store step directly from the synthetic
// node to the method call.
//
// extra method on query-set/manager that does _not_ return a query-set,
// but a collection of instances.
modelClass = subclassRef() and
methodName in ["iterator", "bulk_create"] and
this = [manager(modelClass), querySet(modelClass)].getMember(methodName).getACall()
}
/** Gets the model class that this is an instance source of. */
API::Node getModelClass() { result = modelClass }
/** Holds if this instance-source is fetching data from the DB. */
predicate isDbFetch() { not methodName = "bulk_create" }
}
/**
* A method call on a query-set or manager that returns a dictionary
* containing instances of a django models as the values.
*/
class QuerySetMethodInstanceDictValue extends DataFlow::CallCfgNode {
API::Node modelClass;
QuerySetMethodInstanceDictValue() {
modelClass = subclassRef() and
this = [manager(modelClass), querySet(modelClass)].getMember("in_bulk").getACall()
}
/** Gets the model class that this is an instance source of. */
API::Node getModelClass() { result = modelClass }
/** Holds if this instance-source is fetching data from the DB. */
predicate isDbFetch() { any() }
}
/**
* Gets a reference to an instance of `django.db.models.Model` class or any subclass,
* where `modelClass` specifies the class.
*/
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t, API::Node modelClass) {
t.start() and
modelClass = result.(InstanceSource).getModelClass()
or
exists(DataFlow::TypeTracker t2 | result = instance(t2, modelClass).track(t2, t))
}
/**
* Gets a reference to an instance of `django.db.models.Model` class or any subclass,
* where `modelClass` specifies the class.
*/
DataFlow::Node instance(API::Node modelClass) {
instance(DataFlow::TypeTracker::end(), modelClass).flowsTo(result)
}
}
/**
* Gets a reference to the Manager (django.db.models.Manager) for a django Model,
* accessed by `<ModelName>.objects`.
* Gets a reference to the Manager (django.db.models.Manager) for the django Model `modelClass`,
* accessed by `<modelClass>.objects`.
*/
API::Node manager() { result = Model::subclassRef().getMember("objects") }
API::Node manager(API::Node modelClass) {
modelClass = Model::subclassRef() and
result = modelClass.getMember("objects")
}
/**
* Gets a method with `name` that returns a QuerySet.
* This method can originate on a QuerySet or a Manager.
* `modelClass` specifies the django Model that this query-set originates from.
*
* See https://docs.djangoproject.com/en/3.1/ref/models/querysets/
*/
API::Node querySetReturningMethod(string name) {
API::Node querySetReturningMethod(API::Node modelClass, string name) {
name in [
"none", "all", "filter", "exclude", "complex_filter", "union", "intersection",
"difference", "select_for_update", "select_related", "prefetch_related", "order_by",
"distinct", "reverse", "defer", "only", "using", "annotate", "extra", "raw",
"datetimes", "dates", "values", "values_list", "alias"
] and
result = [manager(), querySet()].getMember(name)
result = [manager(modelClass), querySet(modelClass)].getMember(name)
or
name = "get_queryset" and
result = manager(modelClass).getMember(name)
}
/**
* Gets a reference to a QuerySet (django.db.models.query.QuerySet).
* `modelClass` specifies the django Model that this query-set originates from.
*
* See https://docs.djangoproject.com/en/3.1/ref/models/querysets/
*/
API::Node querySet() { result = querySetReturningMethod(_).getReturn() }
API::Node querySet(API::Node modelClass) {
result = querySetReturningMethod(modelClass, _).getReturn()
}
/** Gets a reference to the `django.db.models.expressions` module. */
API::Node expressions() { result = models().getMember("expressions") }
@@ -660,6 +822,166 @@ module PrivateDjango {
/** DEPRECATED: Alias for RawSql */
deprecated module RawSQL = RawSql;
}
/** This internal module provides data-flow modeling of Django ORM. */
private module OrmDataflow {
private import semmle.python.dataflow.new.internal.DataFlowPrivate::Orm
/** Gets the (AST) class of the Django model class `modelClass`. */
Class getModelClassClass(API::Node modelClass) {
result.getParent() = modelClass.getAUse().asExpr().(ClassExpr) and
modelClass = Model::subclassRef()
}
/** A synthetic node representing the data for an Django ORM model saved in a DB. */
class SyntheticDjangoOrmModelNode extends SyntheticOrmModelNode {
API::Node modelClass;
SyntheticDjangoOrmModelNode() { this.getClass() = getModelClassClass(modelClass) }
/** Gets the API node for this Django model class. */
API::Node getModelClass() { result = modelClass }
}
/**
* Gets a synthetic node where the data in the attribute `fieldName` can flow
* to, when a DB store is made on `subModel`, taking ORM inheritance into
* account.
*
* If `fieldName` is defined in class `base`, the results will include the
* synthetic node for `base` itself, the synthetic node for `subModel`, as
* well as all the classes in-between (in the class hierarchy).
*/
SyntheticDjangoOrmModelNode nodeToStoreIn(API::Node subModel, string fieldName) {
exists(Class base, API::Node baseModel, API::Node resultModel |
baseModel = Model::subclassRef() and
resultModel = Model::subclassRef() and
baseModel.getASubclass*() = subModel and
base = getModelClassClass(baseModel) and
exists(Variable v |
base.getBody().getAnItem().(AssignStmt).defines(v) and
v.getId() = fieldName
)
|
baseModel.getASubclass*() = resultModel and
resultModel.getASubclass*() = subModel and
result.getModelClass() = resultModel
)
}
/**
* Gets the synthetic node where data could be loaded from, when a fetch is
* made on `modelClass`.
*
* In vanilla Django inheritance, this is simply the model itself, but if a
* model is based on `polymorphic.models.PolymorphicModel`, a fetch of the
* base-class can also yield instances of its subclasses.
*/
SyntheticDjangoOrmModelNode nodeToLoadFrom(API::Node modelClass) {
result.getModelClass() = modelClass
or
exists(API::Node polymorphicModel |
polymorphicModel =
API::moduleImport("polymorphic").getMember("models").getMember("PolymorphicModel")
|
polymorphicModel.getASubclass+() = modelClass and
modelClass.getASubclass+() = result.getModelClass()
)
}
/** Additional data-flow steps for Django ORM models. */
class DjangOrmSteps extends AdditionalOrmSteps {
override predicate storeStep(
DataFlow::Node nodeFrom, DataFlow::Content c, DataFlow::Node nodeTo
) {
// attribute value from constructor call -> object created
exists(DataFlow::CallCfgNode call, string fieldName |
// Note: Currently only supports kwargs, which should by far be the most
// common way to do things. We _should_ investigate how often
// positional-args are used.
call = Model::subclassRef().getACall() and
nodeFrom = call.getArgByName(fieldName) and
c.(DataFlow::AttributeContent).getAttribute() = fieldName and
nodeTo = call
)
or
// attribute store in `<Model>.objects.create`, `get_or_create`, and `update_or_create`
// see https://docs.djangoproject.com/en/4.0/ref/models/querysets/#create
// see https://docs.djangoproject.com/en/4.0/ref/models/querysets/#get-or-create
// see https://docs.djangoproject.com/en/4.0/ref/models/querysets/#update-or-create
// TODO: This does currently not handle values passed in the `defaults` dictionary
exists(
DataFlow::CallCfgNode call, API::Node modelClass, string fieldName,
string methodName
|
modelClass = Model::subclassRef() and
methodName in ["create", "get_or_create", "update_or_create"] and
call = modelClass.getMember("objects").getMember(methodName).getACall() and
nodeFrom = call.getArgByName(fieldName) and
c.(DataFlow::AttributeContent).getAttribute() = fieldName and
(
// -> object created
(
methodName = "create" and nodeTo = call
or
// TODO: for these two methods, the result is a tuple `(<Model>, bool)`,
// which we need flow-summaries to model properly
methodName in ["get_or_create", "update_or_create"] and none()
)
or
// -> DB store on synthetic node
nodeTo = nodeToStoreIn(modelClass, fieldName)
)
)
or
// attribute store in `<Model>.objects.[<QuerySet>].update()` -> synthetic
// see https://docs.djangoproject.com/en/4.0/ref/models/querysets/#update
exists(DataFlow::CallCfgNode call, API::Node modelClass, string fieldName |
call = [manager(modelClass), querySet(modelClass)].getMember("update").getACall() and
nodeFrom = call.getArgByName(fieldName) and
c.(DataFlow::AttributeContent).getAttribute() = fieldName and
nodeTo = nodeToStoreIn(modelClass, fieldName)
)
or
// synthetic -> method-call that returns collection of ORM models (all/filter/...)
exists(API::Node modelClass |
nodeFrom = nodeToLoadFrom(modelClass) and
nodeTo.(Model::QuerySetMethodInstanceCollection).getModelClass() = modelClass and
nodeTo.(Model::QuerySetMethodInstanceCollection).isDbFetch() and
c instanceof DataFlow::ListElementContent
)
or
// synthetic -> method-call that returns dictionary with ORM models as values
exists(API::Node modelClass |
nodeFrom = nodeToLoadFrom(modelClass) and
nodeTo.(Model::QuerySetMethodInstanceDictValue).getModelClass() = modelClass and
nodeTo.(Model::QuerySetMethodInstanceDictValue).isDbFetch() and
c instanceof DataFlow::DictionaryElementAnyContent
)
}
override predicate jumpStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
// save -> synthetic
exists(API::Node modelClass, DataFlow::MethodCallNode saveCall |
// TODO: The `nodeTo` should be restricted more, such that flow to
// base-classes are only for the fields that are defined in the
// base-class... but only passing on flow for a specific attribute requires flow-summaries,
// so we can do
// `obj (in obj.save call) ==read of attr==> synthetic attr on base-class ==store of attr==> synthetic for base-class`
nodeTo = nodeToStoreIn(modelClass, _) and
saveCall.calls(Model::instance(modelClass), "save") and
nodeFrom = saveCall.getObject()
)
or
// synthetic -> method-call that returns single ORM model (get/first/...)
exists(API::Node modelClass |
nodeFrom = nodeToLoadFrom(modelClass) and
nodeTo.(Model::InstanceSource).getModelClass() = modelClass and
nodeTo.(Model::InstanceSource).isDbFetch()
)
}
}
}
}
}
@@ -674,7 +996,7 @@ module PrivateDjango {
DataFlow::Node sql;
ObjectsAnnotate() {
this = DjangoImpl::DB::Models::querySetReturningMethod("annotate").getACall() and
this = DjangoImpl::DB::Models::querySetReturningMethod(_, "annotate").getACall() and
DjangoImpl::DB::Models::Expressions::RawSql::instance(sql) in [
this.getArg(_), this.getArgByName(_)
]
@@ -692,7 +1014,7 @@ module PrivateDjango {
DataFlow::Node sql;
ObjectsAlias() {
this = DjangoImpl::DB::Models::querySetReturningMethod("alias").getACall() and
this = DjangoImpl::DB::Models::querySetReturningMethod(_, "alias").getACall() and
DjangoImpl::DB::Models::Expressions::RawSql::instance(sql) in [
this.getArg(_), this.getArgByName(_)
]
@@ -709,7 +1031,7 @@ module PrivateDjango {
* - https://docs.djangoproject.com/en/3.1/ref/models/querysets/#raw
*/
private class ObjectsRaw extends SqlExecution::Range, DataFlow::CallCfgNode {
ObjectsRaw() { this = DjangoImpl::DB::Models::querySetReturningMethod("raw").getACall() }
ObjectsRaw() { this = DjangoImpl::DB::Models::querySetReturningMethod(_, "raw").getACall() }
override DataFlow::Node getSql() { result = this.getArg(0) }
}
@@ -720,7 +1042,9 @@ module PrivateDjango {
* See https://docs.djangoproject.com/en/3.1/ref/models/querysets/#extra
*/
private class ObjectsExtra extends SqlExecution::Range, DataFlow::CallCfgNode {
ObjectsExtra() { this = DjangoImpl::DB::Models::querySetReturningMethod("extra").getACall() }
ObjectsExtra() {
this = DjangoImpl::DB::Models::querySetReturningMethod(_, "extra").getACall()
}
override DataFlow::Node getSql() {
result in [

View File

@@ -0,0 +1,6 @@
/**
* This file contains imports required for the Python version of `ConceptsShared.qll`.
* Since they are language-specific, they can't be placed directly in that file, as it is shared between languages.
*/
import semmle.python.dataflow.new.DataFlow

View File

@@ -0,0 +1,13 @@
/**
* Provides Concepts which are shared across languages.
*
* Each language has a language specific `Concepts.qll` file that can import the
* shared concepts from this file. A language can either re-export the concept directly,
* or can add additional member-predicates that are needed for that language.
*
* Moving forward, `Concepts.qll` will be the staging ground for brand new concepts from
* each language, but we will maintain a discipline of moving those concepts to
* `ConceptsShared.qll` ASAP.
*/
private import ConceptsImports

View File

@@ -98,7 +98,8 @@ module HeuristicNames {
* suggesting nouns within the string do not represent the meaning of the whole string (e.g. a URL or a SQL query).
*/
string notSensitiveRegexp() {
result = "(?is).*([^\\w$.-]|redact|censor|obfuscate|hash|md5|sha|((?<!un)(en))?(crypt|code)).*"
result =
"(?is).*([^\\w$.-]|redact|censor|obfuscate|hash|md5|sha|random|((?<!un)(en))?(crypt|code)).*"
}
/**

View File

@@ -0,0 +1,202 @@
/**
* Provides predicates for reasoning about regular expressions
* that match URLs and hostname patterns.
*/
private import HostnameRegexpSpecific
/**
* Holds if the given constant is unlikely to occur in the origin part of a URL.
*/
predicate isConstantInvalidInsideOrigin(RegExpConstant term) {
// Look for any of these cases:
// - A character that can't occur in the origin
// - Two dashes in a row
// - A colon that is not part of port or scheme separator
// - A slash that is not part of scheme separator
term.getValue().regexpMatch(".*(?:[^a-zA-Z0-9.:/-]|--|:[^0-9/]|(?<![/:]|^)/).*")
}
/** Holds if `term` is a dot constant of form `\.` or `[.]`. */
predicate isDotConstant(RegExpTerm term) {
term.(RegExpCharEscape).getValue() = "."
or
exists(RegExpCharacterClass cls |
term = cls and
not cls.isInverted() and
cls.getNumChild() = 1 and
cls.getAChild().(RegExpConstant).getValue() = "."
)
}
/** Holds if `term` is a wildcard `.` or an actual `.` character. */
predicate isDotLike(RegExpTerm term) {
term instanceof RegExpDot
or
isDotConstant(term)
}
/** Holds if `term` will only ever be matched against the beginning of the input. */
predicate matchesBeginningOfString(RegExpTerm term) {
term.isRootTerm()
or
exists(RegExpTerm parent | matchesBeginningOfString(parent) |
term = parent.(RegExpSequence).getChild(0)
or
parent.(RegExpSequence).getChild(0) instanceof RegExpCaret and
term = parent.(RegExpSequence).getChild(1)
or
term = parent.(RegExpAlt).getAChild()
or
term = parent.(RegExpGroup).getAChild()
)
}
/**
* Holds if the given sequence contains top-level domain preceded by a dot, such as `.com`,
* excluding cases where this is at the very beginning of the regexp.
*
* `i` is bound to the index of the last child in the top-level domain part.
*/
predicate hasTopLevelDomainEnding(RegExpSequence seq, int i) {
seq.getChild(i)
.(RegExpConstant)
.getValue()
.regexpMatch("(?i)" + RegExpPatterns::getACommonTld() + "(:\\d+)?([/?#].*)?") and
isDotLike(seq.getChild(i - 1)) and
not (i = 1 and matchesBeginningOfString(seq))
}
/**
* Holds if the given regular expression term contains top-level domain preceded by a dot,
* such as `.com`.
*/
predicate hasTopLevelDomainEnding(RegExpSequence seq) { hasTopLevelDomainEnding(seq, _) }
/**
* Holds if `term` will always match a hostname, that is, all disjunctions contain
* a hostname pattern that isn't inside a quantifier.
*/
predicate alwaysMatchesHostname(RegExpTerm term) {
hasTopLevelDomainEnding(term, _)
or
// `localhost` is considered a hostname pattern, but has no TLD
term.(RegExpConstant).getValue().regexpMatch("\\blocalhost\\b")
or
not term instanceof RegExpAlt and
not term instanceof RegExpQuantifier and
alwaysMatchesHostname(term.getAChild())
or
alwaysMatchesHostnameAlt(term)
}
/** Holds if every child of `alt` contains a hostname pattern. */
predicate alwaysMatchesHostnameAlt(RegExpAlt alt) {
alwaysMatchesHostnameAlt(alt, alt.getNumChild() - 1)
}
/**
* Holds if the first `i` children of `alt` contains a hostname pattern.
*
* This is used instead of `forall` to avoid materializing the set of alternatives
* that don't contains hostnames, which is much larger.
*/
predicate alwaysMatchesHostnameAlt(RegExpAlt alt, int i) {
alwaysMatchesHostname(alt.getChild(0)) and i = 0
or
alwaysMatchesHostnameAlt(alt, i - 1) and
alwaysMatchesHostname(alt.getChild(i))
}
/**
* Holds if `term` occurs inside a quantifier or alternative (and thus
* can not be expected to correspond to a unique match), or as part of
* a lookaround assertion (which are rarely used for capture groups).
*/
predicate isInsideChoiceOrSubPattern(RegExpTerm term) {
exists(RegExpParent parent | parent = term.getParent() |
parent instanceof RegExpAlt
or
parent instanceof RegExpQuantifier
or
parent instanceof RegExpSubPattern
or
isInsideChoiceOrSubPattern(parent)
)
}
/**
* Holds if `group` is likely to be used as a capture group.
*/
predicate isLikelyCaptureGroup(RegExpGroup group) {
group.isCapture() and
not isInsideChoiceOrSubPattern(group)
}
/**
* Holds if `seq` contains two consecutive dots `..` or escaped dots.
*
* At least one of these dots is not intended to be a subdomain separator,
* so we avoid flagging the pattern in this case.
*/
predicate hasConsecutiveDots(RegExpSequence seq) {
exists(int i |
isDotLike(seq.getChild(i)) and
isDotLike(seq.getChild(i + 1))
)
}
predicate isIncompleteHostNameRegExpPattern(RegExpTerm regexp, RegExpSequence seq, string msg) {
seq = regexp.getAChild*() and
exists(RegExpDot unescapedDot, int i, string hostname |
hasTopLevelDomainEnding(seq, i) and
not isConstantInvalidInsideOrigin(seq.getChild([0 .. i - 1]).getAChild*()) and
not isLikelyCaptureGroup(seq.getChild([i .. seq.getNumChild() - 1]).getAChild*()) and
unescapedDot = seq.getChild([0 .. i - 1]).getAChild*() and
unescapedDot != seq.getChild(i - 1) and // Should not be the '.' immediately before the TLD
not hasConsecutiveDots(unescapedDot.getParent()) and
hostname =
seq.getChild(i - 2).getRawValue() + seq.getChild(i - 1).getRawValue() +
seq.getChild(i).getRawValue()
|
if unescapedDot.getParent() instanceof RegExpQuantifier
then
// `.*\.example.com` can match `evil.com/?x=.example.com`
//
// This problem only occurs when the pattern is applied against a full URL, not just a hostname/origin.
// We therefore check if the pattern includes a suffix after the TLD, such as `.*\.example.com/`.
// Note that a post-anchored pattern (`.*\.example.com$`) will usually fail to match a full URL,
// and patterns with neither a suffix nor an anchor fall under the purview of MissingRegExpAnchor.
seq.getChild(0) instanceof RegExpCaret and
not seq.getAChild() instanceof RegExpDollar and
seq.getChild([i .. i + 1]).(RegExpConstant).getValue().regexpMatch(".*[/?#].*") and
msg =
"has an unrestricted wildcard '" + unescapedDot.getParent().(RegExpQuantifier).getRawValue()
+ "' which may cause '" + hostname +
"' to be matched anywhere in the URL, outside the hostname."
else
msg =
"has an unescaped '.' before '" + hostname +
"', so it might match more hosts than expected."
)
}
predicate incompleteHostnameRegExp(
RegExpSequence hostSequence, string message, DataFlow::Node aux, string label
) {
exists(RegExpPatternSource re, RegExpTerm regexp, string msg, string kind |
regexp = re.getRegExpTerm() and
isIncompleteHostNameRegExpPattern(regexp, hostSequence, msg) and
(
if re.getAParse() != re
then (
kind = "string, which is used as a regular expression $@," and
aux = re.getAParse()
) else (
kind = "regular expression" and aux = re
)
)
|
message = "This " + kind + " " + msg and label = "here"
)
}

View File

@@ -0,0 +1,3 @@
import semmle.python.security.performance.RegExpTreeView
import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.Regexp

View File

@@ -8,35 +8,9 @@
* @id py/incomplete-hostname-regexp
* @tags correctness
* security
* external/cwe/cwe-20
* external/cwe/cwe-020
*/
import python
import semmle.python.regex
import HostnameRegexpShared
private string commonTopLevelDomainRegex() { result = "com|org|edu|gov|uk|net|io" }
/**
* Holds if `pattern` is a regular expression pattern for URLs with a host matched by `hostPart`,
* and `pattern` contains a subtle mistake that allows it to match unexpected hosts.
*/
bindingset[pattern]
predicate isIncompleteHostNameRegExpPattern(string pattern, string hostPart) {
hostPart =
pattern
.regexpCapture("(?i).*" +
// an unescaped single `.`
"(?<!\\\\)[.]" +
// immediately followed by a sequence of subdomains, perhaps with some regex characters mixed in, followed by a known TLD
"([():|?a-z0-9-]+(\\\\)?[.](" + commonTopLevelDomainRegex() + "))" + ".*", 1)
}
from Regex r, string pattern, string hostPart
where
r.getText() = pattern and
isIncompleteHostNameRegExpPattern(pattern, hostPart) and
// ignore patterns with capture groups after the TLD
not pattern.regexpMatch("(?i).*[.](" + commonTopLevelDomainRegex() + ").*[(][?]:.*[)].*")
select r,
"This regular expression has an unescaped '.' before '" + hostPart +
"', so it might match more hosts than expected."
query predicate problems = incompleteHostnameRegExp/4;

View File

@@ -55,6 +55,7 @@
| Dict | 46 | 54 | 46 | 55 |
| Dict | 48 | 9 | 48 | 19 |
| DictUnpacking | 46 | 52 | 46 | 55 |
| DjangoViewClassHelper | 4 | 1 | 4 | 8 |
| Ellipsis | 7 | 7 | 7 | 9 |
| Ellipsis | 50 | 14 | 50 | 16 |
| ExceptStmt | 32 | 9 | 32 | 31 |

View File

@@ -44,6 +44,7 @@
| Dict | 46 | 54 | 46 | 55 |
| Dict | 48 | 9 | 48 | 19 |
| DictUnpacking | 46 | 52 | 46 | 55 |
| DjangoViewClassHelper | 4 | 1 | 4 | 8 |
| Ellipsis | 7 | 7 | 7 | 9 |
| Ellipsis | 50 | 14 | 50 | 16 |
| ExceptStmt | 32 | 9 | 32 | 31 |

View File

@@ -11,6 +11,4 @@
| test.py:4:5:4:17 | CtxManager3() |
| test.py:4:5:4:29 | With |
| test.py:4:22:4:29 | example3 |
| test.py:4:31:4:30 | |
| test.py:4:31:4:30 | With |
| test.py:6:5:6:8 | Pass |

View File

@@ -0,0 +1,5 @@
db.sqlite3
# The testapp/migrations/ folder needs to be comitted to git,
# but we don't care to store the actual migrations
testapp/migrations/

View File

@@ -0,0 +1,2 @@
missingAnnotationOnSINK
failures

View File

@@ -0,0 +1,2 @@
import python
import experimental.dataflow.TestUtil.NormalDataflowTest

View File

@@ -0,0 +1,26 @@
The interesting ORM tests files can be found under `testapp/orm_*.py`. These are set up to be executed by the [testapp/tests.py](testapp/tests.py) file.
List of interesting tests files (that might go out of date if it is forgotten :flushed:):
- [testapp/orm_tests.py](testapp/orm_tests.py): which tests flow from source to sink
- [testapp/orm_form_test.py](testapp/orm_form_test.py): shows how forms can be used to save Models to the DB
- [testapp/orm_security_tests.py](testapp/orm_security_tests.py): which highlights some interesting interactions with security queries
- [testapp/orm_inheritance.py](testapp/orm_inheritance.py): which highlights how inheritance of ORM models works
## Setup
```
pip install django pytest pytest-django django-polymorphic
```
## Run server
```
python manage.py makemigrations && python manage.py migrate && python manage.py runserver
```
## Run tests
```
pytest
```

View File

@@ -0,0 +1,101 @@
edges
| testapp/orm_security_tests.py:15:1:15:27 | [orm-model] Class Person [Attribute age] | testapp/orm_security_tests.py:42:23:42:42 | ControlFlowNode for Attribute() [List element, Attribute age] |
| testapp/orm_security_tests.py:15:1:15:27 | [orm-model] Class Person [Attribute age] | testapp/orm_security_tests.py:51:14:51:53 | ControlFlowNode for Attribute() [Attribute age] |
| testapp/orm_security_tests.py:15:1:15:27 | [orm-model] Class Person [Attribute name] | testapp/orm_security_tests.py:42:23:42:42 | ControlFlowNode for Attribute() [List element, Attribute name] |
| testapp/orm_security_tests.py:15:1:15:27 | [orm-model] Class Person [Attribute name] | testapp/orm_security_tests.py:47:14:47:53 | ControlFlowNode for Attribute() [Attribute name] |
| testapp/orm_security_tests.py:19:12:19:18 | ControlFlowNode for request | testapp/orm_security_tests.py:22:23:22:34 | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:19:12:19:18 | ControlFlowNode for request | testapp/orm_security_tests.py:23:22:23:33 | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:22:9:22:14 | [post store] ControlFlowNode for person [Attribute name] | testapp/orm_security_tests.py:23:9:23:14 | ControlFlowNode for person [Attribute name] |
| testapp/orm_security_tests.py:22:23:22:34 | ControlFlowNode for Attribute | testapp/orm_security_tests.py:22:23:22:42 | ControlFlowNode for Subscript |
| testapp/orm_security_tests.py:22:23:22:42 | ControlFlowNode for Subscript | testapp/orm_security_tests.py:22:9:22:14 | [post store] ControlFlowNode for person [Attribute name] |
| testapp/orm_security_tests.py:23:9:23:14 | ControlFlowNode for person [Attribute name] | testapp/orm_security_tests.py:28:9:28:14 | ControlFlowNode for person [Attribute name] |
| testapp/orm_security_tests.py:23:9:23:14 | [post store] ControlFlowNode for person [Attribute age] | testapp/orm_security_tests.py:28:9:28:14 | ControlFlowNode for person [Attribute age] |
| testapp/orm_security_tests.py:23:22:23:33 | ControlFlowNode for Attribute | testapp/orm_security_tests.py:23:22:23:40 | ControlFlowNode for Subscript |
| testapp/orm_security_tests.py:23:22:23:40 | ControlFlowNode for Subscript | testapp/orm_security_tests.py:23:9:23:14 | [post store] ControlFlowNode for person [Attribute age] |
| testapp/orm_security_tests.py:28:9:28:14 | ControlFlowNode for person [Attribute age] | testapp/orm_security_tests.py:15:1:15:27 | [orm-model] Class Person [Attribute age] |
| testapp/orm_security_tests.py:28:9:28:14 | ControlFlowNode for person [Attribute name] | testapp/orm_security_tests.py:15:1:15:27 | [orm-model] Class Person [Attribute name] |
| testapp/orm_security_tests.py:42:13:42:18 | SSA variable person [Attribute age] | testapp/orm_security_tests.py:43:62:43:67 | ControlFlowNode for person [Attribute age] |
| testapp/orm_security_tests.py:42:13:42:18 | SSA variable person [Attribute name] | testapp/orm_security_tests.py:43:49:43:54 | ControlFlowNode for person [Attribute name] |
| testapp/orm_security_tests.py:42:23:42:42 | ControlFlowNode for Attribute() [List element, Attribute age] | testapp/orm_security_tests.py:42:13:42:18 | SSA variable person [Attribute age] |
| testapp/orm_security_tests.py:42:23:42:42 | ControlFlowNode for Attribute() [List element, Attribute name] | testapp/orm_security_tests.py:42:13:42:18 | SSA variable person [Attribute name] |
| testapp/orm_security_tests.py:43:49:43:54 | ControlFlowNode for person [Attribute name] | testapp/orm_security_tests.py:43:49:43:59 | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:43:49:43:59 | ControlFlowNode for Attribute | testapp/orm_security_tests.py:44:29:44:37 | ControlFlowNode for resp_text |
| testapp/orm_security_tests.py:43:62:43:67 | ControlFlowNode for person [Attribute age] | testapp/orm_security_tests.py:43:62:43:71 | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:43:62:43:71 | ControlFlowNode for Attribute | testapp/orm_security_tests.py:44:29:44:37 | ControlFlowNode for resp_text |
| testapp/orm_security_tests.py:47:14:47:53 | ControlFlowNode for Attribute() [Attribute name] | testapp/orm_security_tests.py:48:46:48:51 | ControlFlowNode for person [Attribute name] |
| testapp/orm_security_tests.py:48:46:48:51 | ControlFlowNode for person [Attribute name] | testapp/orm_security_tests.py:48:46:48:56 | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:48:46:48:56 | ControlFlowNode for Attribute | testapp/orm_security_tests.py:48:25:48:57 | ControlFlowNode for Attribute() |
| testapp/orm_security_tests.py:51:14:51:53 | ControlFlowNode for Attribute() [Attribute age] | testapp/orm_security_tests.py:55:45:55:50 | ControlFlowNode for person [Attribute age] |
| testapp/orm_security_tests.py:55:45:55:50 | ControlFlowNode for person [Attribute age] | testapp/orm_security_tests.py:55:45:55:54 | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:55:45:55:54 | ControlFlowNode for Attribute | testapp/orm_security_tests.py:55:25:55:55 | ControlFlowNode for Attribute() |
| testapp/orm_security_tests.py:92:1:92:44 | [orm-model] Class CommentValidatorNotUsed [Attribute text] | testapp/orm_security_tests.py:101:15:101:52 | ControlFlowNode for Attribute() [Attribute text] |
| testapp/orm_security_tests.py:95:37:95:43 | ControlFlowNode for request | testapp/orm_security_tests.py:96:44:96:55 | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:96:15:96:64 | ControlFlowNode for CommentValidatorNotUsed() [Attribute text] | testapp/orm_security_tests.py:97:5:97:11 | ControlFlowNode for comment [Attribute text] |
| testapp/orm_security_tests.py:96:44:96:55 | ControlFlowNode for Attribute | testapp/orm_security_tests.py:96:44:96:63 | ControlFlowNode for Subscript |
| testapp/orm_security_tests.py:96:44:96:63 | ControlFlowNode for Subscript | testapp/orm_security_tests.py:96:15:96:64 | ControlFlowNode for CommentValidatorNotUsed() [Attribute text] |
| testapp/orm_security_tests.py:97:5:97:11 | ControlFlowNode for comment [Attribute text] | testapp/orm_security_tests.py:92:1:92:44 | [orm-model] Class CommentValidatorNotUsed [Attribute text] |
| testapp/orm_security_tests.py:101:15:101:52 | ControlFlowNode for Attribute() [Attribute text] | testapp/orm_security_tests.py:102:25:102:31 | ControlFlowNode for comment [Attribute text] |
| testapp/orm_security_tests.py:102:25:102:31 | ControlFlowNode for comment [Attribute text] | testapp/orm_security_tests.py:102:25:102:36 | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:111:1:111:41 | [orm-model] Class CommentValidatorUsed [Attribute text] | testapp/orm_security_tests.py:120:15:120:49 | ControlFlowNode for Attribute() [Attribute text] |
| testapp/orm_security_tests.py:114:33:114:39 | ControlFlowNode for request | testapp/orm_security_tests.py:115:41:115:52 | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:115:15:115:61 | ControlFlowNode for CommentValidatorUsed() [Attribute text] | testapp/orm_security_tests.py:117:5:117:11 | ControlFlowNode for comment [Attribute text] |
| testapp/orm_security_tests.py:115:41:115:52 | ControlFlowNode for Attribute | testapp/orm_security_tests.py:115:41:115:60 | ControlFlowNode for Subscript |
| testapp/orm_security_tests.py:115:41:115:60 | ControlFlowNode for Subscript | testapp/orm_security_tests.py:115:15:115:61 | ControlFlowNode for CommentValidatorUsed() [Attribute text] |
| testapp/orm_security_tests.py:117:5:117:11 | ControlFlowNode for comment [Attribute text] | testapp/orm_security_tests.py:111:1:111:41 | [orm-model] Class CommentValidatorUsed [Attribute text] |
| testapp/orm_security_tests.py:120:15:120:49 | ControlFlowNode for Attribute() [Attribute text] | testapp/orm_security_tests.py:121:25:121:31 | ControlFlowNode for comment [Attribute text] |
| testapp/orm_security_tests.py:121:25:121:31 | ControlFlowNode for comment [Attribute text] | testapp/orm_security_tests.py:121:25:121:36 | ControlFlowNode for Attribute |
nodes
| testapp/orm_security_tests.py:15:1:15:27 | [orm-model] Class Person [Attribute age] | semmle.label | [orm-model] Class Person [Attribute age] |
| testapp/orm_security_tests.py:15:1:15:27 | [orm-model] Class Person [Attribute name] | semmle.label | [orm-model] Class Person [Attribute name] |
| testapp/orm_security_tests.py:19:12:19:18 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
| testapp/orm_security_tests.py:22:9:22:14 | [post store] ControlFlowNode for person [Attribute name] | semmle.label | [post store] ControlFlowNode for person [Attribute name] |
| testapp/orm_security_tests.py:22:23:22:34 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:22:23:22:42 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
| testapp/orm_security_tests.py:23:9:23:14 | ControlFlowNode for person [Attribute name] | semmle.label | ControlFlowNode for person [Attribute name] |
| testapp/orm_security_tests.py:23:9:23:14 | [post store] ControlFlowNode for person [Attribute age] | semmle.label | [post store] ControlFlowNode for person [Attribute age] |
| testapp/orm_security_tests.py:23:22:23:33 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:23:22:23:40 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
| testapp/orm_security_tests.py:28:9:28:14 | ControlFlowNode for person [Attribute age] | semmle.label | ControlFlowNode for person [Attribute age] |
| testapp/orm_security_tests.py:28:9:28:14 | ControlFlowNode for person [Attribute name] | semmle.label | ControlFlowNode for person [Attribute name] |
| testapp/orm_security_tests.py:42:13:42:18 | SSA variable person [Attribute age] | semmle.label | SSA variable person [Attribute age] |
| testapp/orm_security_tests.py:42:13:42:18 | SSA variable person [Attribute name] | semmle.label | SSA variable person [Attribute name] |
| testapp/orm_security_tests.py:42:23:42:42 | ControlFlowNode for Attribute() [List element, Attribute age] | semmle.label | ControlFlowNode for Attribute() [List element, Attribute age] |
| testapp/orm_security_tests.py:42:23:42:42 | ControlFlowNode for Attribute() [List element, Attribute name] | semmle.label | ControlFlowNode for Attribute() [List element, Attribute name] |
| testapp/orm_security_tests.py:43:49:43:54 | ControlFlowNode for person [Attribute name] | semmle.label | ControlFlowNode for person [Attribute name] |
| testapp/orm_security_tests.py:43:49:43:59 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:43:62:43:67 | ControlFlowNode for person [Attribute age] | semmle.label | ControlFlowNode for person [Attribute age] |
| testapp/orm_security_tests.py:43:62:43:71 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:44:29:44:37 | ControlFlowNode for resp_text | semmle.label | ControlFlowNode for resp_text |
| testapp/orm_security_tests.py:47:14:47:53 | ControlFlowNode for Attribute() [Attribute name] | semmle.label | ControlFlowNode for Attribute() [Attribute name] |
| testapp/orm_security_tests.py:48:25:48:57 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| testapp/orm_security_tests.py:48:46:48:51 | ControlFlowNode for person [Attribute name] | semmle.label | ControlFlowNode for person [Attribute name] |
| testapp/orm_security_tests.py:48:46:48:56 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:51:14:51:53 | ControlFlowNode for Attribute() [Attribute age] | semmle.label | ControlFlowNode for Attribute() [Attribute age] |
| testapp/orm_security_tests.py:55:25:55:55 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| testapp/orm_security_tests.py:55:45:55:50 | ControlFlowNode for person [Attribute age] | semmle.label | ControlFlowNode for person [Attribute age] |
| testapp/orm_security_tests.py:55:45:55:54 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:92:1:92:44 | [orm-model] Class CommentValidatorNotUsed [Attribute text] | semmle.label | [orm-model] Class CommentValidatorNotUsed [Attribute text] |
| testapp/orm_security_tests.py:95:37:95:43 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
| testapp/orm_security_tests.py:96:15:96:64 | ControlFlowNode for CommentValidatorNotUsed() [Attribute text] | semmle.label | ControlFlowNode for CommentValidatorNotUsed() [Attribute text] |
| testapp/orm_security_tests.py:96:44:96:55 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:96:44:96:63 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
| testapp/orm_security_tests.py:97:5:97:11 | ControlFlowNode for comment [Attribute text] | semmle.label | ControlFlowNode for comment [Attribute text] |
| testapp/orm_security_tests.py:101:15:101:52 | ControlFlowNode for Attribute() [Attribute text] | semmle.label | ControlFlowNode for Attribute() [Attribute text] |
| testapp/orm_security_tests.py:102:25:102:31 | ControlFlowNode for comment [Attribute text] | semmle.label | ControlFlowNode for comment [Attribute text] |
| testapp/orm_security_tests.py:102:25:102:36 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:111:1:111:41 | [orm-model] Class CommentValidatorUsed [Attribute text] | semmle.label | [orm-model] Class CommentValidatorUsed [Attribute text] |
| testapp/orm_security_tests.py:114:33:114:39 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
| testapp/orm_security_tests.py:115:15:115:61 | ControlFlowNode for CommentValidatorUsed() [Attribute text] | semmle.label | ControlFlowNode for CommentValidatorUsed() [Attribute text] |
| testapp/orm_security_tests.py:115:41:115:52 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:115:41:115:60 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
| testapp/orm_security_tests.py:117:5:117:11 | ControlFlowNode for comment [Attribute text] | semmle.label | ControlFlowNode for comment [Attribute text] |
| testapp/orm_security_tests.py:120:15:120:49 | ControlFlowNode for Attribute() [Attribute text] | semmle.label | ControlFlowNode for Attribute() [Attribute text] |
| testapp/orm_security_tests.py:121:25:121:31 | ControlFlowNode for comment [Attribute text] | semmle.label | ControlFlowNode for comment [Attribute text] |
| testapp/orm_security_tests.py:121:25:121:36 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
subpaths
#select
| testapp/orm_security_tests.py:44:29:44:37 | ControlFlowNode for resp_text | testapp/orm_security_tests.py:19:12:19:18 | ControlFlowNode for request | testapp/orm_security_tests.py:44:29:44:37 | ControlFlowNode for resp_text | Cross-site scripting vulnerability due to $@. | testapp/orm_security_tests.py:19:12:19:18 | ControlFlowNode for request | a user-provided value |
| testapp/orm_security_tests.py:48:25:48:57 | ControlFlowNode for Attribute() | testapp/orm_security_tests.py:19:12:19:18 | ControlFlowNode for request | testapp/orm_security_tests.py:48:25:48:57 | ControlFlowNode for Attribute() | Cross-site scripting vulnerability due to $@. | testapp/orm_security_tests.py:19:12:19:18 | ControlFlowNode for request | a user-provided value |
| testapp/orm_security_tests.py:55:25:55:55 | ControlFlowNode for Attribute() | testapp/orm_security_tests.py:19:12:19:18 | ControlFlowNode for request | testapp/orm_security_tests.py:55:25:55:55 | ControlFlowNode for Attribute() | Cross-site scripting vulnerability due to $@. | testapp/orm_security_tests.py:19:12:19:18 | ControlFlowNode for request | a user-provided value |
| testapp/orm_security_tests.py:102:25:102:36 | ControlFlowNode for Attribute | testapp/orm_security_tests.py:95:37:95:43 | ControlFlowNode for request | testapp/orm_security_tests.py:102:25:102:36 | ControlFlowNode for Attribute | Cross-site scripting vulnerability due to $@. | testapp/orm_security_tests.py:95:37:95:43 | ControlFlowNode for request | a user-provided value |
| testapp/orm_security_tests.py:121:25:121:36 | ControlFlowNode for Attribute | testapp/orm_security_tests.py:114:33:114:39 | ControlFlowNode for request | testapp/orm_security_tests.py:121:25:121:36 | ControlFlowNode for Attribute | Cross-site scripting vulnerability due to $@. | testapp/orm_security_tests.py:114:33:114:39 | ControlFlowNode for request | a user-provided value |

View File

@@ -0,0 +1 @@
Security/CWE-079/ReflectedXss.ql

View File

@@ -0,0 +1,18 @@
uniqueEnclosingCallable
uniqueType
uniqueNodeLocation
missingLocation
uniqueNodeToString
missingToString
parameterCallable
localFlowIsLocal
compatibleTypesReflexive
unreachableNodeCCtx
localCallNodes
postIsNotPre
postHasUniquePre
uniquePostUpdate
postIsInSameCallable
reverseRead
argHasPostUpdate
postWithInFlow

View File

@@ -0,0 +1 @@
import semmle.python.dataflow.new.internal.DataFlowImplConsistency::Consistency

View File

@@ -0,0 +1,5 @@
# to force extractor to see files. since we use `--max-import-depth=1`, we use this
# "fake" import that doesn't actually work, but tricks the python extractor to look at
# all the files
from testapp import *

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'testproj.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,5 @@
[pytest]
DJANGO_SETTINGS_MODULE = testproj.settings
python_files = tests.py
# don't require that you have manually run `python manage.py makemigrations`
addopts = --no-migrations --ignore-glob=*.testproj/ -v

Some files were not shown because too many files have changed in this diff Show More