mirror of
https://github.com/github/codeql.git
synced 2026-04-30 19:26:02 +02:00
Merge branch 'main' into incomplete-url-string-sanitization
This commit is contained in:
@@ -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**
|
||||
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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 }
|
||||
|
||||
/**
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
21
cpp/ql/src/Diagnostics/Internal/ExtractionErrors.ql
Normal file
21
cpp/ql/src/Diagnostics/Internal/ExtractionErrors.ql
Normal 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()
|
||||
137
cpp/ql/src/Diagnostics/Internal/ExtractionErrors.qll
Normal file
137
cpp/ql/src/Diagnostics/Internal/ExtractionErrors.qll
Normal 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() }
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
46
cpp/ql/src/experimental/Security/CWE/CWE-362/double-fetch.ql
Normal file
46
cpp/ql/src/experimental/Security/CWE/CWE-362/double-fetch.ql
Normal 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()
|
||||
@@ -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>:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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 + */ ";" //
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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*()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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() + "]"
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
@@ -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() }
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
|
||||
@@ -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 |
|
||||
|
||||
45
csharp/ql/test/utils/model-generator/NoSummaries.cs
Normal file
45
csharp/ql/test/utils/model-generator/NoSummaries.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
1
csharp/ql/test/utils/model-generator/options
Normal file
1
csharp/ql/test/utils/model-generator/options
Normal file
@@ -0,0 +1 @@
|
||||
semmle-extractor-options: /r:System.Linq.dll
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/javascript-experimental-atm-lib
|
||||
version: 0.1.1
|
||||
version: 0.2.1
|
||||
extractor: javascript
|
||||
library: true
|
||||
groups:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
dependencies:
|
||||
codeql/javascript-experimental-atm-model:
|
||||
version: 0.0.6
|
||||
version: 0.1.0
|
||||
compiled: false
|
||||
lockVersion: 1.0.0
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
dependencies:
|
||||
codeql/javascript-experimental-atm-model:
|
||||
version: 0.0.6
|
||||
version: 0.1.0
|
||||
compiled: false
|
||||
lockVersion: 1.0.0
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
dependencies:
|
||||
codeql/javascript-experimental-atm-model:
|
||||
version: 0.0.6
|
||||
version: 0.1.0
|
||||
compiled: false
|
||||
lockVersion: 1.0.0
|
||||
|
||||
@@ -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
|
||||
@@ -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" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 +
|
||||
"']"
|
||||
|
||||
@@ -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()) }
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Provides JS-specific imports needed for `TaintedFormatStringQuery` and `TaintedFormatStringCustomizations`.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.DOM
|
||||
@@ -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 */
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)).*"
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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.
|
||||
@@ -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(
|
||||
|
||||
@@ -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))))
|
||||
);
|
||||
};
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)))) |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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:"><img src=1 onerror="alert(1)">" />`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(function () {
|
||||
const src = document.getElementById("#link").src;
|
||||
$("#id").html(src); // NOT OK.
|
||||
|
||||
$("#id").attr("src", src); // OK
|
||||
})();
|
||||
|
||||
@@ -27,3 +27,4 @@
|
||||
query path:
|
||||
- /^experimental\/.*/
|
||||
- Metrics/Summaries/FrameworkCoverage.ql
|
||||
- /Diagnostics/Internal/.*/
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: majorAnalysis
|
||||
---
|
||||
* Added data-flow for Django ORM models that are saved in a database (no `models.ForeignKey` support).
|
||||
@@ -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()) }
|
||||
|
||||
|
||||
41
python/ql/lib/semmle/python/dataflow/new/Regexp.qll
Normal file
41
python/ql/lib/semmle/python/dataflow/new/Regexp.qll
Normal 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 }
|
||||
}
|
||||
@@ -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. */
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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() }
|
||||
|
||||
@@ -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 [
|
||||
|
||||
6
python/ql/lib/semmle/python/internal/ConceptsImports.qll
Normal file
6
python/ql/lib/semmle/python/internal/ConceptsImports.qll
Normal 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
|
||||
13
python/ql/lib/semmle/python/internal/ConceptsShared.qll
Normal file
13
python/ql/lib/semmle/python/internal/ConceptsShared.qll
Normal 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
|
||||
@@ -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)).*"
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
202
python/ql/src/Security/CWE-020/HostnameRegexpShared.qll
Normal file
202
python/ql/src/Security/CWE-020/HostnameRegexpShared.qll
Normal 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"
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import semmle.python.security.performance.RegExpTreeView
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.dataflow.new.Regexp
|
||||
@@ -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;
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
5
python/ql/test/library-tests/frameworks/django-orm/.gitignore
vendored
Normal file
5
python/ql/test/library-tests/frameworks/django-orm/.gitignore
vendored
Normal 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/
|
||||
@@ -0,0 +1,2 @@
|
||||
missingAnnotationOnSINK
|
||||
failures
|
||||
@@ -0,0 +1,2 @@
|
||||
import python
|
||||
import experimental.dataflow.TestUtil.NormalDataflowTest
|
||||
26
python/ql/test/library-tests/frameworks/django-orm/README.md
Normal file
26
python/ql/test/library-tests/frameworks/django-orm/README.md
Normal 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
|
||||
```
|
||||
@@ -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 |
|
||||
@@ -0,0 +1 @@
|
||||
Security/CWE-079/ReflectedXss.ql
|
||||
@@ -0,0 +1,18 @@
|
||||
uniqueEnclosingCallable
|
||||
uniqueType
|
||||
uniqueNodeLocation
|
||||
missingLocation
|
||||
uniqueNodeToString
|
||||
missingToString
|
||||
parameterCallable
|
||||
localFlowIsLocal
|
||||
compatibleTypesReflexive
|
||||
unreachableNodeCCtx
|
||||
localCallNodes
|
||||
postIsNotPre
|
||||
postHasUniquePre
|
||||
uniquePostUpdate
|
||||
postIsInSameCallable
|
||||
reverseRead
|
||||
argHasPostUpdate
|
||||
postWithInFlow
|
||||
@@ -0,0 +1 @@
|
||||
import semmle.python.dataflow.new.internal.DataFlowImplConsistency::Consistency
|
||||
@@ -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 *
|
||||
22
python/ql/test/library-tests/frameworks/django-orm/manage.py
Executable file
22
python/ql/test/library-tests/frameworks/django-orm/manage.py
Executable 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()
|
||||
@@ -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
Reference in New Issue
Block a user