mirror of
https://github.com/github/codeql.git
synced 2025-12-17 01:03:14 +01:00
Merge branch 'master' of github.com:github/codeql into OptionalSanitizer
This commit is contained in:
@@ -16,6 +16,7 @@ The following changes in version 1.25 affect C/C++ analysis in all applications.
|
||||
|
||||
## Changes to libraries
|
||||
|
||||
* The library `VCS.qll` and all queries that imported it have been removed.
|
||||
* The data-flow library has been improved, which affects most security queries by potentially
|
||||
adding more results. Flow through functions now takes nested field reads/writes into account.
|
||||
For example, the library is able to track flow from `taint()` to `sink()` via the method
|
||||
@@ -39,3 +40,5 @@ The following changes in version 1.25 affect C/C++ analysis in all applications.
|
||||
}
|
||||
};
|
||||
```
|
||||
* The security pack taint tracking library (`semmle.code.cpp.security.TaintTracking`) now considers that equality checks may block the flow of taint. This results in fewer false positive results from queries that use this library.
|
||||
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
## General improvements
|
||||
|
||||
* Support for the following frameworks and libraries has been improved:
|
||||
- [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
|
||||
- [bluebird](http://bluebirdjs.com/)
|
||||
- [express](https://www.npmjs.com/package/express)
|
||||
- [fastify](https://www.npmjs.com/package/fastify)
|
||||
- [fstream](https://www.npmjs.com/package/fstream)
|
||||
- [jGrowl](https://github.com/stanlemon/jGrowl)
|
||||
- [jQuery](https://jquery.com/)
|
||||
@@ -13,12 +15,11 @@
|
||||
- [mssql](https://www.npmjs.com/package/mssql)
|
||||
- [mysql](https://www.npmjs.com/package/mysql)
|
||||
- [pg](https://www.npmjs.com/package/pg)
|
||||
- [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
|
||||
- [sequelize](https://www.npmjs.com/package/sequelize)
|
||||
- [spanner](https://www.npmjs.com/package/spanner)
|
||||
- [sqlite](https://www.npmjs.com/package/sqlite)
|
||||
- [ssh2](https://www.npmjs.com/package/ssh2)
|
||||
- [ssh2-streams](https://www.npmjs.com/package/ssh2-streams)
|
||||
- [ssh2](https://www.npmjs.com/package/ssh2)
|
||||
|
||||
* TypeScript 3.9 is now supported.
|
||||
|
||||
@@ -35,42 +36,43 @@
|
||||
|
||||
| **Query** | **Expected impact** | **Change** |
|
||||
|--------------------------------|------------------------------|---------------------------------------------------------------------------|
|
||||
| Misspelled variable name (`js/misspelled-variable-name`) | Message changed | The message for this query now correctly identifies the misspelled variable in additional cases. |
|
||||
| Uncontrolled data used in path expression (`js/path-injection`) | More results | This query now recognizes additional file system calls. |
|
||||
| Uncontrolled command line (`js/command-line-injection`) | More results | This query now recognizes additional command execution calls. |
|
||||
| Client-side URL redirect (`js/client-side-unvalidated-url-redirection`) | Less results | This query now recognizes additional safe patterns of doing URL redirects. |
|
||||
| Client-side cross-site scripting (`js/xss`) | Less results | This query now recognizes additional safe strings based on URLs. |
|
||||
| Incomplete URL scheme check (`js/incomplete-url-scheme-check`) | More results | This query now recognizes additional url scheme checks. |
|
||||
| Prototype pollution in utility function (`js/prototype-pollution-utility`) | More results | This query now recognizes additional utility functions as vulnerable to prototype polution. |
|
||||
| Expression has no effect (`js/useless-expression`) | Less results | This query no longer flags an expression when that expression is the only content of the containing file. |
|
||||
| Unknown directive (`js/unknown-directive`) | Less results | This query no longer flags directives generated by the Babel compiler. |
|
||||
| Code injection (`js/code-injection`) | More results | More potential vulnerabilities involving NoSQL code operators are now recognized. |
|
||||
| Zip Slip (`js/zipslip`) | More results | This query now recognizes additional vulnerabilities. |
|
||||
| Unused property (`js/unused-property`) | Less results | This query no longer flags properties of objects that are operands of `yield` expressions. |
|
||||
| Client-side cross-site scripting (`js/xss`) | Less results | This query no longer flags optionally sanitized values. |
|
||||
| Client-side URL redirect (`js/client-side-unvalidated-url-redirection`) | Fewer results | This query now recognizes additional safe patterns of doing URL redirects. |
|
||||
| Client-side cross-site scripting (`js/xss`) | Fewer results | This query now recognizes additional safe strings based on URLs. |
|
||||
| Code injection (`js/code-injection`) | More results | More potential vulnerabilities involving NoSQL code operators are now recognized. |
|
||||
| Expression has no effect (`js/useless-expression`) | Fewer results | This query no longer flags an expression when that expression is the only content of the containing file. |
|
||||
| Incomplete URL scheme check (`js/incomplete-url-scheme-check`) | More results | This query now recognizes additional url scheme checks. |
|
||||
| Misspelled variable name (`js/misspelled-variable-name`) | Message changed | The message for this query now correctly identifies the misspelled variable in additional cases. |
|
||||
| Prototype pollution in utility function (`js/prototype-pollution-utility`) | More results | This query now recognizes additional utility functions as vulnerable to prototype polution. |
|
||||
| Prototype pollution in utility function (`js/prototype-pollution-utility`) | More results | This query now recognizes more coding patterns that are vulnerable to prototype pollution. |
|
||||
| Uncontrolled command line (`js/command-line-injection`) | More results | This query now recognizes additional command execution calls. |
|
||||
| Uncontrolled data used in path expression (`js/path-injection`) | More results | This query now recognizes additional file system calls. |
|
||||
| Unknown directive (`js/unknown-directive`) | Fewer results | This query no longer flags directives generated by the Babel compiler. |
|
||||
| Unused property (`js/unused-property`) | Fewer results | This query no longer flags properties of objects that are operands of `yield` expressions. |
|
||||
| Zip Slip (`js/zipslip`) | More results | This query now recognizes additional vulnerabilities. |
|
||||
|
||||
The following low-precision queries are no longer run by default on LGTM (their results already were not displayed):
|
||||
|
||||
- `js/angular/dead-event-listener`
|
||||
- `js/angular/unused-dependency`
|
||||
- `js/conflicting-html-attribute`
|
||||
- `js/useless-assignment-to-global`
|
||||
- `js/too-many-parameters`
|
||||
- `js/unused-property`
|
||||
- `js/bitwise-sign-check`
|
||||
- `js/comparison-of-identical-expressions`
|
||||
- `js/misspelled-identifier`
|
||||
- `js/jsdoc/malformed-param-tag`
|
||||
- `js/jsdoc/unknown-parameter`
|
||||
- `js/jsdoc/missing-parameter`
|
||||
- `js/omitted-array-element`
|
||||
- `js/conflicting-html-attribute`
|
||||
- `js/ignored-setter-parameter`
|
||||
- `js/jsdoc/malformed-param-tag`
|
||||
- `js/jsdoc/missing-parameter`
|
||||
- `js/jsdoc/unknown-parameter`
|
||||
- `js/json-in-javascript-file`
|
||||
- `js/misspelled-identifier`
|
||||
- `js/nested-loops-with-same-variable`
|
||||
- `js/node/cyclic-import`
|
||||
- `js/node/unused-npm-dependency`
|
||||
- `js/single-run-loop`
|
||||
- `js/nested-loops-with-same-variable`
|
||||
- `js/omitted-array-element`
|
||||
- `js/return-outside-function`
|
||||
- `js/single-run-loop`
|
||||
- `js/too-many-parameters`
|
||||
- `js/unused-property`
|
||||
- `js/useless-assignment-to-global`
|
||||
|
||||
## Changes to libraries
|
||||
|
||||
@@ -80,3 +82,4 @@ The following low-precision queries are no longer run by default on LGTM (their
|
||||
- `Parameter.flow()` now gets the correct data flow node for a parameter. Previously this had a result, but the node was disconnected from the data flow graph.
|
||||
- `ParameterNode.asExpr()` and `.getAstNode()` now gets the parameter's AST node, whereas previously it had no result.
|
||||
- `Expr.flow()` now has a more meaningful result for destructuring patterns. Previously this node was disconnected from the data flow graph. Now it represents the values being destructured by the pattern.
|
||||
* The global data-flow and taint-tracking libraries now model indirect parameter accesses through the `arguments` object in some cases, which may lead to additional results from some of the security queries, particularly "Prototype pollution in utility function".
|
||||
|
||||
@@ -6,29 +6,50 @@
|
||||
|
||||
import cpp
|
||||
|
||||
// True if function was ()-declared, but not (void)-declared or K&R-defined
|
||||
/**
|
||||
* Holds if `fde` has a parameter declaration that's clear on the minimum
|
||||
* number of parameters. This is essentially true for everything except
|
||||
* `()`-declarations.
|
||||
*/
|
||||
private predicate hasDefiniteNumberOfParameters(FunctionDeclarationEntry fde) {
|
||||
fde.hasVoidParamList()
|
||||
or
|
||||
fde.getNumberOfParameters() > 0
|
||||
or
|
||||
fde.isDefinition()
|
||||
}
|
||||
|
||||
/* Holds if function was ()-declared, but not (void)-declared or K&R-defined. */
|
||||
private predicate hasZeroParamDecl(Function f) {
|
||||
exists(FunctionDeclarationEntry fde | fde = f.getADeclarationEntry() |
|
||||
not fde.hasVoidParamList() and fde.getNumberOfParameters() = 0 and not fde.isDefinition()
|
||||
not hasDefiniteNumberOfParameters(fde)
|
||||
)
|
||||
}
|
||||
|
||||
// True if this file (or header) was compiled as a C file
|
||||
/* Holds if this file (or header) was compiled as a C file. */
|
||||
private predicate isCompiledAsC(File f) {
|
||||
f.compiledAsC()
|
||||
or
|
||||
exists(File src | isCompiledAsC(src) | src.getAnIncludedFile() = f)
|
||||
}
|
||||
|
||||
/** Holds if `fc` is a call to `f` with too few arguments. */
|
||||
predicate tooFewArguments(FunctionCall fc, Function f) {
|
||||
f = fc.getTarget() and
|
||||
not f.isVarargs() and
|
||||
not f instanceof BuiltInFunction and
|
||||
// This query should only have results on C (not C++) functions that have a
|
||||
// `()` parameter list somewhere. If it has results on other functions, then
|
||||
// it's probably because the extractor only saw a partial compilation.
|
||||
hasZeroParamDecl(f) and
|
||||
isCompiledAsC(f.getFile()) and
|
||||
// There is an explicit declaration of the function whose parameter count is larger
|
||||
// than the number of call arguments
|
||||
exists(FunctionDeclarationEntry fde | fde = f.getADeclarationEntry() |
|
||||
// Produce an alert when all declarations that are authoritative on the
|
||||
// parameter count specify a parameter count larger than the number of call
|
||||
// arguments.
|
||||
forex(FunctionDeclarationEntry fde |
|
||||
fde = f.getADeclarationEntry() and
|
||||
hasDefiniteNumberOfParameters(fde)
|
||||
|
|
||||
fde.getNumberOfParameters() > fc.getNumberOfArguments()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
/**
|
||||
* @name Churned lines per file
|
||||
* @description Number of churned lines per file, across the revision
|
||||
* history in the database.
|
||||
* @kind treemap
|
||||
* @id cpp/historical-churn
|
||||
* @treemap.warnOn highValues
|
||||
* @metricType file
|
||||
* @metricAggregate avg sum max
|
||||
* @tags external-data
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import external.VCS
|
||||
|
||||
from File f, int n
|
||||
where
|
||||
n =
|
||||
sum(Commit entry, int churn |
|
||||
churn = entry.getRecentChurnForFile(f) and
|
||||
not artificialChange(entry)
|
||||
|
|
||||
churn
|
||||
) and
|
||||
exists(f.getMetrics().getNumberOfLinesOfCode())
|
||||
select f, n order by n desc
|
||||
@@ -1,27 +0,0 @@
|
||||
/**
|
||||
* @name Added lines per file
|
||||
* @description Number of added lines per file, across the revision
|
||||
* history in the database.
|
||||
* @kind treemap
|
||||
* @id cpp/historical-lines-added
|
||||
* @treemap.warnOn highValues
|
||||
* @metricType file
|
||||
* @metricAggregate avg sum max
|
||||
* @tags external-data
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import external.VCS
|
||||
|
||||
from File f, int n
|
||||
where
|
||||
n =
|
||||
sum(Commit entry, int churn |
|
||||
churn = entry.getRecentAdditionsForFile(f) and
|
||||
not artificialChange(entry)
|
||||
|
|
||||
churn
|
||||
) and
|
||||
exists(f.getMetrics().getNumberOfLinesOfCode())
|
||||
select f, n order by n desc
|
||||
@@ -1,27 +0,0 @@
|
||||
/**
|
||||
* @name Deleted lines per file
|
||||
* @description Number of deleted lines per file, across the revision
|
||||
* history in the database.
|
||||
* @kind treemap
|
||||
* @id cpp/historical-lines-deleted
|
||||
* @treemap.warnOn highValues
|
||||
* @metricType file
|
||||
* @metricAggregate avg sum max
|
||||
* @tags external-data
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import external.VCS
|
||||
|
||||
from File f, int n
|
||||
where
|
||||
n =
|
||||
sum(Commit entry, int churn |
|
||||
churn = entry.getRecentDeletionsForFile(f) and
|
||||
not artificialChange(entry)
|
||||
|
|
||||
churn
|
||||
) and
|
||||
exists(f.getMetrics().getNumberOfLinesOfCode())
|
||||
select f, n order by n desc
|
||||
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* @name Number of authors
|
||||
* @description Number of distinct authors for each file.
|
||||
* @kind treemap
|
||||
* @id cpp/historical-number-of-authors
|
||||
* @treemap.warnOn highValues
|
||||
* @metricType file
|
||||
* @metricAggregate avg min max
|
||||
* @tags external-data
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import external.VCS
|
||||
|
||||
from File f
|
||||
where exists(f.getMetrics().getNumberOfLinesOfCode())
|
||||
select f, count(Author author | author.getAnEditedFile() = f)
|
||||
@@ -1,19 +0,0 @@
|
||||
/**
|
||||
* @name Number of file-level changes
|
||||
* @description The number of file-level changes made (by version
|
||||
* control history).
|
||||
* @kind treemap
|
||||
* @id cpp/historical-number-of-changes
|
||||
* @treemap.warnOn highValues
|
||||
* @metricType file
|
||||
* @metricAggregate avg min max sum
|
||||
* @tags external-data
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import external.VCS
|
||||
|
||||
from File f
|
||||
where exists(f.getMetrics().getNumberOfLinesOfCode())
|
||||
select f, count(Commit svn | f = svn.getAnAffectedFile() and not artificialChange(svn))
|
||||
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* @name Number of co-committed files
|
||||
* @description The average number of other files that are touched
|
||||
* whenever a file is affected by a commit.
|
||||
* @kind treemap
|
||||
* @id cpp/historical-number-of-co-commits
|
||||
* @treemap.warnOn highValues
|
||||
* @metricType file
|
||||
* @metricAggregate avg min max
|
||||
* @tags external-data
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import external.VCS
|
||||
|
||||
int committedFiles(Commit commit) { result = count(commit.getAnAffectedFile()) }
|
||||
|
||||
from File f
|
||||
where exists(f.getMetrics().getNumberOfLinesOfCode())
|
||||
select f, avg(Commit commit | commit.getAnAffectedFile() = f | committedFiles(commit) - 1)
|
||||
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
* @name Number of re-commits for each file
|
||||
* @description A re-commit is taken to mean a commit to a file that
|
||||
* was touched less than five days ago.
|
||||
* @kind treemap
|
||||
* @id cpp/historical-number-of-re-commits
|
||||
* @treemap.warnOn highValues
|
||||
* @metricType file
|
||||
* @metricAggregate avg min max
|
||||
* @tags external-data
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import external.VCS
|
||||
|
||||
predicate inRange(Commit first, Commit second) {
|
||||
first.getAnAffectedFile() = second.getAnAffectedFile() and
|
||||
first != second and
|
||||
exists(int n |
|
||||
n = first.getDate().daysTo(second.getDate()) and
|
||||
n >= 0 and
|
||||
n < 5
|
||||
)
|
||||
}
|
||||
|
||||
int recommitsForFile(File f) {
|
||||
result =
|
||||
count(Commit recommit |
|
||||
f = recommit.getAnAffectedFile() and
|
||||
exists(Commit prev | inRange(prev, recommit))
|
||||
)
|
||||
}
|
||||
|
||||
from File f
|
||||
where exists(f.getMetrics().getNumberOfLinesOfCode())
|
||||
select f, recommitsForFile(f)
|
||||
@@ -1,27 +0,0 @@
|
||||
/**
|
||||
* @name Number of recent authors
|
||||
* @description Number of distinct authors that have recently made
|
||||
* changes.
|
||||
* @kind treemap
|
||||
* @id cpp/historical-number-of-recent-authors
|
||||
* @treemap.warnOn highValues
|
||||
* @metricType file
|
||||
* @metricAggregate avg min max
|
||||
* @tags external-data
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import external.VCS
|
||||
|
||||
from File f
|
||||
where exists(f.getMetrics().getNumberOfLinesOfCode())
|
||||
select f,
|
||||
count(Author author |
|
||||
exists(Commit e |
|
||||
e = author.getACommit() and
|
||||
f = e.getAnAffectedFile() and
|
||||
e.daysToNow() <= 180 and
|
||||
not artificialChange(e)
|
||||
)
|
||||
)
|
||||
@@ -1,24 +0,0 @@
|
||||
/**
|
||||
* @name Recently changed files
|
||||
* @description Number of files recently edited.
|
||||
* @kind treemap
|
||||
* @id cpp/historical-number-of-recent-changed-files
|
||||
* @treemap.warnOn highValues
|
||||
* @metricType file
|
||||
* @metricAggregate avg min max sum
|
||||
* @tags external-data
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import external.VCS
|
||||
|
||||
from File f
|
||||
where
|
||||
exists(Commit e |
|
||||
e.getAnAffectedFile() = f and
|
||||
e.daysToNow() <= 180 and
|
||||
not artificialChange(e)
|
||||
) and
|
||||
exists(f.getMetrics().getNumberOfLinesOfCode())
|
||||
select f, 1
|
||||
@@ -1,25 +0,0 @@
|
||||
/**
|
||||
* @name Recent changes
|
||||
* @description Number of recent commits to this file.
|
||||
* @kind treemap
|
||||
* @id cpp/historical-number-of-recent-changes
|
||||
* @treemap.warnOn highValues
|
||||
* @metricType file
|
||||
* @metricAggregate avg min max sum
|
||||
* @tags external-data
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import external.VCS
|
||||
|
||||
from File f, int n
|
||||
where
|
||||
n =
|
||||
count(Commit e |
|
||||
e.getAnAffectedFile() = f and
|
||||
e.daysToNow() <= 180 and
|
||||
not artificialChange(e)
|
||||
) and
|
||||
exists(f.getMetrics().getNumberOfLinesOfCode())
|
||||
select f, n order by n desc
|
||||
@@ -1 +1,7 @@
|
||||
/**
|
||||
* DEPRECATED: use `import cpp` instead of `import default`.
|
||||
*
|
||||
* Provides classes and predicates for working with C/C++ code.
|
||||
*/
|
||||
|
||||
import cpp
|
||||
|
||||
65
cpp/ql/src/external/CodeDuplication.qll
vendored
65
cpp/ql/src/external/CodeDuplication.qll
vendored
@@ -1,3 +1,5 @@
|
||||
/** Provides classes for detecting duplicate or similar code. */
|
||||
|
||||
import cpp
|
||||
|
||||
private string relativePath(File file) { result = file.getRelativePath().replaceAll("\\", "/") }
|
||||
@@ -8,9 +10,12 @@ private predicate tokenLocation(string path, int sl, int sc, int ec, int el, Cop
|
||||
tokens(copy, index, sl, sc, ec, el)
|
||||
}
|
||||
|
||||
/** A token block used for detection of duplicate and similar code. */
|
||||
class Copy extends @duplication_or_similarity {
|
||||
/** Gets the index of the last token in this block. */
|
||||
private int lastToken() { result = max(int i | tokens(this, i, _, _, _, _) | i) }
|
||||
|
||||
/** Gets the index of the token in this block starting at the location `loc`, if any. */
|
||||
int tokenStartingAt(Location loc) {
|
||||
exists(string filepath, int startline, int startcol |
|
||||
loc.hasLocationInfo(filepath, startline, startcol, _, _) and
|
||||
@@ -18,6 +23,7 @@ class Copy extends @duplication_or_similarity {
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the index of the token in this block ending at the location `loc`, if any. */
|
||||
int tokenEndingAt(Location loc) {
|
||||
exists(string filepath, int endline, int endcol |
|
||||
loc.hasLocationInfo(filepath, _, _, endline, endcol) and
|
||||
@@ -25,24 +31,38 @@ class Copy extends @duplication_or_similarity {
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the line on which the first token in this block starts. */
|
||||
int sourceStartLine() { tokens(this, 0, result, _, _, _) }
|
||||
|
||||
/** Gets the column on which the first token in this block starts. */
|
||||
int sourceStartColumn() { tokens(this, 0, _, result, _, _) }
|
||||
|
||||
/** Gets the line on which the last token in this block ends. */
|
||||
int sourceEndLine() { tokens(this, lastToken(), _, _, result, _) }
|
||||
|
||||
/** Gets the column on which the last token in this block ends. */
|
||||
int sourceEndColumn() { tokens(this, lastToken(), _, _, _, result) }
|
||||
|
||||
/** Gets the number of lines containing at least (part of) one token in this block. */
|
||||
int sourceLines() { result = this.sourceEndLine() + 1 - this.sourceStartLine() }
|
||||
|
||||
/** Gets an opaque identifier for the equivalence class of this block. */
|
||||
int getEquivalenceClass() { duplicateCode(this, _, result) or similarCode(this, _, result) }
|
||||
|
||||
/** Gets the source file in which this block appears. */
|
||||
File sourceFile() {
|
||||
exists(string name | duplicateCode(this, name, _) or similarCode(this, name, _) |
|
||||
name.replaceAll("\\", "/") = relativePath(result)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this element is at the specified location.
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
|
||||
*/
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
@@ -53,25 +73,30 @@ class Copy extends @duplication_or_similarity {
|
||||
endcolumn = sourceEndColumn()
|
||||
}
|
||||
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
/** A block of duplicated code. */
|
||||
class DuplicateBlock extends Copy, @duplication {
|
||||
override string toString() { result = "Duplicate code: " + sourceLines() + " duplicated lines." }
|
||||
}
|
||||
|
||||
/** A block of similar code. */
|
||||
class SimilarBlock extends Copy, @similarity {
|
||||
override string toString() {
|
||||
result = "Similar code: " + sourceLines() + " almost duplicated lines."
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets a function with a body and a location. */
|
||||
FunctionDeclarationEntry sourceMethod() {
|
||||
result.isDefinition() and
|
||||
exists(result.getLocation()) and
|
||||
numlines(unresolveElement(result.getFunction()), _, _, _)
|
||||
}
|
||||
|
||||
/** Gets the number of member functions in `c` with a body and a location. */
|
||||
int numberOfSourceMethods(Class c) {
|
||||
result =
|
||||
count(FunctionDeclarationEntry m |
|
||||
@@ -108,6 +133,10 @@ private predicate duplicateStatement(
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `m1` is a function with `total` lines, and `m2` is a function
|
||||
* that has `duplicate` lines in common with `m1`.
|
||||
*/
|
||||
predicate duplicateStatements(
|
||||
FunctionDeclarationEntry m1, FunctionDeclarationEntry m2, int duplicate, int total
|
||||
) {
|
||||
@@ -115,13 +144,16 @@ predicate duplicateStatements(
|
||||
total = strictcount(statementInMethod(m1))
|
||||
}
|
||||
|
||||
/**
|
||||
* Find pairs of methods are identical
|
||||
*/
|
||||
/** Holds if `m` and other are identical functions. */
|
||||
predicate duplicateMethod(FunctionDeclarationEntry m, FunctionDeclarationEntry other) {
|
||||
exists(int total | duplicateStatements(m, other, total, total))
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: do not use.
|
||||
*
|
||||
* Holds if `line` in `f` is similar to a line somewhere else.
|
||||
*/
|
||||
predicate similarLines(File f, int line) {
|
||||
exists(SimilarBlock b | b.sourceFile() = f and line in [b.sourceStartLine() .. b.sourceEndLine()])
|
||||
}
|
||||
@@ -152,6 +184,7 @@ private predicate similarLinesCoveredFiles(File f, File otherFile) {
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `coveredLines` lines of `f` are similar to lines in `otherFile`. */
|
||||
predicate similarLinesCovered(File f, int coveredLines, File otherFile) {
|
||||
exists(int numLines | numLines = f.getMetrics().getNumberOfLines() |
|
||||
similarLinesCoveredFiles(f, otherFile) and
|
||||
@@ -166,6 +199,11 @@ predicate similarLinesCovered(File f, int coveredLines, File otherFile) {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: do not use.
|
||||
*
|
||||
* Holds if `line` in `f` is duplicated by a line somewhere else.
|
||||
*/
|
||||
predicate duplicateLines(File f, int line) {
|
||||
exists(DuplicateBlock b |
|
||||
b.sourceFile() = f and line in [b.sourceStartLine() .. b.sourceEndLine()]
|
||||
@@ -182,6 +220,7 @@ private predicate duplicateLinesPerEquivalenceClass(int equivClass, int lines, F
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `coveredLines` lines of `f` are duplicates of lines in `otherFile`. */
|
||||
predicate duplicateLinesCovered(File f, int coveredLines, File otherFile) {
|
||||
exists(int numLines | numLines = f.getMetrics().getNumberOfLines() |
|
||||
exists(int coveredApprox |
|
||||
@@ -206,6 +245,7 @@ predicate duplicateLinesCovered(File f, int coveredLines, File otherFile) {
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if most of `f` (`percent`%) is similar to `other`. */
|
||||
predicate similarFiles(File f, File other, int percent) {
|
||||
exists(int covered, int total |
|
||||
similarLinesCovered(f, covered, other) and
|
||||
@@ -216,6 +256,7 @@ predicate similarFiles(File f, File other, int percent) {
|
||||
not duplicateFiles(f, other, _)
|
||||
}
|
||||
|
||||
/** Holds if most of `f` (`percent`%) is duplicated by `other`. */
|
||||
predicate duplicateFiles(File f, File other, int percent) {
|
||||
exists(int covered, int total |
|
||||
duplicateLinesCovered(f, covered, other) and
|
||||
@@ -225,6 +266,10 @@ predicate duplicateFiles(File f, File other, int percent) {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if most member functions of `c` (`numDup` out of `total`) are
|
||||
* duplicates of member functions in `other`.
|
||||
*/
|
||||
predicate mostlyDuplicateClassBase(Class c, Class other, int numDup, int total) {
|
||||
numDup =
|
||||
strictcount(FunctionDeclarationEntry m1 |
|
||||
@@ -240,6 +285,11 @@ predicate mostlyDuplicateClassBase(Class c, Class other, int numDup, int total)
|
||||
(numDup * 100) / total > 80
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if most member functions of `c` are duplicates of member functions in
|
||||
* `other`. Provides the human-readable `message` to describe the amount of
|
||||
* duplication.
|
||||
*/
|
||||
predicate mostlyDuplicateClass(Class c, Class other, string message) {
|
||||
exists(int numDup, int total |
|
||||
mostlyDuplicateClassBase(c, other, numDup, total) and
|
||||
@@ -264,12 +314,21 @@ predicate mostlyDuplicateClass(Class c, Class other, string message) {
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `f` and `other` are similar or duplicates. */
|
||||
predicate fileLevelDuplication(File f, File other) {
|
||||
similarFiles(f, other, _) or duplicateFiles(f, other, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if most member functions of `c` are duplicates of member functions in
|
||||
* `other`.
|
||||
*/
|
||||
predicate classLevelDuplication(Class c, Class other) { mostlyDuplicateClass(c, other, _) }
|
||||
|
||||
/**
|
||||
* Holds if `line` in `f` should be allowed to be duplicated. This is the case
|
||||
* for `#include` directives.
|
||||
*/
|
||||
predicate whitelistedLineForDuplication(File f, int line) {
|
||||
exists(Include i | i.getFile() = f and i.getLocation().getStartLine() = line)
|
||||
}
|
||||
|
||||
92
cpp/ql/src/external/VCS.qll
vendored
92
cpp/ql/src/external/VCS.qll
vendored
@@ -1,92 +0,0 @@
|
||||
import cpp
|
||||
|
||||
class Commit extends @svnentry {
|
||||
Commit() {
|
||||
svnaffectedfiles(this, _, _) and
|
||||
exists(date svnDate, date snapshotDate |
|
||||
svnentries(this, _, _, svnDate, _) and
|
||||
snapshotDate(snapshotDate) and
|
||||
svnDate <= snapshotDate
|
||||
)
|
||||
}
|
||||
|
||||
string toString() { result = this.getRevisionName() }
|
||||
|
||||
string getRevisionName() { svnentries(this, result, _, _, _) }
|
||||
|
||||
string getAuthor() { svnentries(this, _, result, _, _) }
|
||||
|
||||
date getDate() { svnentries(this, _, _, result, _) }
|
||||
|
||||
int getChangeSize() { svnentries(this, _, _, _, result) }
|
||||
|
||||
string getMessage() { svnentrymsg(this, result) }
|
||||
|
||||
string getAnAffectedFilePath(string action) {
|
||||
exists(File rawFile | svnaffectedfiles(this, unresolveElement(rawFile), action) |
|
||||
result = rawFile.getAbsolutePath()
|
||||
)
|
||||
}
|
||||
|
||||
string getAnAffectedFilePath() { result = getAnAffectedFilePath(_) }
|
||||
|
||||
File getAnAffectedFile(string action) {
|
||||
// Workaround for incorrect keys in SVN data
|
||||
exists(File svnFile | svnFile.getAbsolutePath() = result.getAbsolutePath() |
|
||||
svnaffectedfiles(this, unresolveElement(svnFile), action)
|
||||
) and
|
||||
exists(result.getMetrics().getNumberOfLinesOfCode())
|
||||
}
|
||||
|
||||
File getAnAffectedFile() { exists(string action | result = this.getAnAffectedFile(action)) }
|
||||
|
||||
private predicate churnForFile(File f, int added, int deleted) {
|
||||
// Workaround for incorrect keys in SVN data
|
||||
exists(File svnFile | svnFile.getAbsolutePath() = f.getAbsolutePath() |
|
||||
svnchurn(this, unresolveElement(svnFile), added, deleted)
|
||||
) and
|
||||
exists(f.getMetrics().getNumberOfLinesOfCode())
|
||||
}
|
||||
|
||||
int getRecentChurnForFile(File f) {
|
||||
exists(int added, int deleted | churnForFile(f, added, deleted) and result = added + deleted)
|
||||
}
|
||||
|
||||
int getRecentAdditionsForFile(File f) { churnForFile(f, result, _) }
|
||||
|
||||
int getRecentDeletionsForFile(File f) { churnForFile(f, _, result) }
|
||||
|
||||
predicate isRecent() { recentCommit(this) }
|
||||
|
||||
int daysToNow() {
|
||||
exists(date now | snapshotDate(now) | result = getDate().daysTo(now) and result >= 0)
|
||||
}
|
||||
}
|
||||
|
||||
class Author extends string {
|
||||
Author() { exists(Commit e | this = e.getAuthor()) }
|
||||
|
||||
Commit getACommit() { result.getAuthor() = this }
|
||||
|
||||
File getAnEditedFile() { result = this.getACommit().getAnAffectedFile() }
|
||||
}
|
||||
|
||||
predicate recentCommit(Commit e) {
|
||||
exists(date snapshotDate, date commitDate, int days |
|
||||
snapshotDate(snapshotDate) and
|
||||
e.getDate() = commitDate and
|
||||
days = commitDate.daysTo(snapshotDate) and
|
||||
days >= 0 and
|
||||
days <= 60
|
||||
)
|
||||
}
|
||||
|
||||
date firstChange(File f) {
|
||||
result = min(Commit e, date toMin | f = e.getAnAffectedFile() and toMin = e.getDate() | toMin)
|
||||
}
|
||||
|
||||
predicate firstCommit(Commit e) {
|
||||
not exists(File f | f = e.getAnAffectedFile() | firstChange(f) < e.getDate())
|
||||
}
|
||||
|
||||
predicate artificialChange(Commit e) { firstCommit(e) or e.getChangeSize() >= 50000 }
|
||||
20
cpp/ql/src/external/tests/DefectFromSVN.ql
vendored
20
cpp/ql/src/external/tests/DefectFromSVN.ql
vendored
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* @name Defect from SVN
|
||||
* @description A test case for creating a defect from SVN data.
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @tags external-data
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import external.ExternalArtifact
|
||||
import external.VCS
|
||||
|
||||
predicate numCommits(File f, int i) { i = count(Commit e | e.getAnAffectedFile() = f) }
|
||||
|
||||
predicate maxCommits(int i) { i = max(File f, int j | numCommits(f, j) | j) }
|
||||
|
||||
from File f, int i
|
||||
where numCommits(f, i) and maxCommits(i)
|
||||
select f, "This file has " + i + " commits."
|
||||
17
cpp/ql/src/external/tests/MetricFromSVN.ql
vendored
17
cpp/ql/src/external/tests/MetricFromSVN.ql
vendored
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* @name Metric from SVN
|
||||
* @description Find number of commits for a file
|
||||
* @treemap.warnOn lowValues
|
||||
* @metricType file
|
||||
* @tags external-data
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import external.VCS
|
||||
|
||||
predicate numCommits(File f, int i) { i = count(Commit e | e.getAnAffectedFile() = f) }
|
||||
|
||||
from File f, int i
|
||||
where numCommits(f, i)
|
||||
select f, i
|
||||
@@ -1,25 +0,0 @@
|
||||
/**
|
||||
* @name Filter: exclude results from files that have not recently been
|
||||
* edited
|
||||
* @description Use this filter to return results only if they are
|
||||
* located in files that have been modified in the 60 days
|
||||
* before the date of the snapshot.
|
||||
* @kind problem
|
||||
* @id cpp/recent-defects-filter
|
||||
* @tags filter
|
||||
* external-data
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import external.DefectFilter
|
||||
import external.VCS
|
||||
|
||||
pragma[noopt]
|
||||
private predicate recent(File file) {
|
||||
exists(Commit e | file = e.getAnAffectedFile() | e.isRecent() and not artificialChange(e))
|
||||
}
|
||||
|
||||
from DefectResult res
|
||||
where recent(res.getFile())
|
||||
select res, res.getMessage()
|
||||
@@ -1,25 +0,0 @@
|
||||
/**
|
||||
* @name Metric filter: exclude results from files that have not
|
||||
* recently been edited
|
||||
* @description Use this filter to return results only if they are
|
||||
* located in files that have been modified in the 60 days
|
||||
* before the snapshot.
|
||||
* @kind treemap
|
||||
* @id cpp/recent-defects-for-metric-filter
|
||||
* @tags filter
|
||||
* external-data
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import external.MetricFilter
|
||||
import external.VCS
|
||||
|
||||
pragma[noopt]
|
||||
private predicate recent(File file) {
|
||||
exists(Commit e | file = e.getAnAffectedFile() | e.isRecent() and not artificialChange(e))
|
||||
}
|
||||
|
||||
from MetricResult res
|
||||
where recent(res.getFile())
|
||||
select res, res.getValue()
|
||||
@@ -1 +1,7 @@
|
||||
/**
|
||||
* DEPRECATED: Objective C is no longer supported.
|
||||
*
|
||||
* Import `cpp` instead of `objc`.
|
||||
*/
|
||||
|
||||
import cpp
|
||||
|
||||
@@ -98,7 +98,12 @@ class Declaration extends Locatable, @declaration {
|
||||
this.hasQualifiedName(namespaceQualifier, "", baseName)
|
||||
}
|
||||
|
||||
override string toString() { result = this.getName() }
|
||||
/**
|
||||
* Gets a description of this `Declaration` for display purposes.
|
||||
*/
|
||||
string getDescription() { result = this.getName() }
|
||||
|
||||
final override string toString() { result = this.getDescription() }
|
||||
|
||||
/**
|
||||
* Gets the name of this declaration.
|
||||
|
||||
@@ -79,7 +79,10 @@ class Namespace extends NameQualifyingElement, @namespace {
|
||||
/** Gets the metric namespace. */
|
||||
MetricNamespace getMetrics() { result = this }
|
||||
|
||||
override string toString() { result = this.getQualifiedName() }
|
||||
/** Gets a version of the `QualifiedName` that is more suitable for display purposes. */
|
||||
string getFriendlyName() { result = this.getQualifiedName() }
|
||||
|
||||
final override string toString() { result = getFriendlyName() }
|
||||
|
||||
/** Gets a declaration of (part of) this namespace. */
|
||||
NamespaceDeclarationEntry getADeclarationEntry() { result.getNamespace() = this }
|
||||
@@ -104,7 +107,7 @@ class NamespaceDeclarationEntry extends Locatable, @namespace_decl {
|
||||
namespace_decls(underlyingElement(this), unresolveElement(result), _, _)
|
||||
}
|
||||
|
||||
override string toString() { result = this.getNamespace().toString() }
|
||||
override string toString() { result = this.getNamespace().getFriendlyName() }
|
||||
|
||||
/**
|
||||
* Gets the location of the token preceding the namespace declaration
|
||||
@@ -150,7 +153,7 @@ class UsingDeclarationEntry extends UsingEntry {
|
||||
*/
|
||||
Declaration getDeclaration() { usings(underlyingElement(this), unresolveElement(result), _) }
|
||||
|
||||
override string toString() { result = "using " + this.getDeclaration().toString() }
|
||||
override string toString() { result = "using " + this.getDeclaration().getDescription() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,7 +172,7 @@ class UsingDirectiveEntry extends UsingEntry {
|
||||
*/
|
||||
Namespace getNamespace() { usings(underlyingElement(this), unresolveElement(result), _) }
|
||||
|
||||
override string toString() { result = "using namespace " + this.getNamespace().toString() }
|
||||
override string toString() { result = "using namespace " + this.getNamespace().getFriendlyName() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -204,7 +207,7 @@ class GlobalNamespace extends Namespace {
|
||||
*/
|
||||
deprecated string getFullName() { result = this.getName() }
|
||||
|
||||
override string toString() { result = "(global namespace)" }
|
||||
override string getFriendlyName() { result = "(global namespace)" }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -260,24 +260,33 @@ class ParameterDeclarationEntry extends VariableDeclarationEntry {
|
||||
*/
|
||||
int getIndex() { param_decl_bind(underlyingElement(this), result, _) }
|
||||
|
||||
private string getAnonymousParameterDescription() {
|
||||
not exists(getName()) and
|
||||
exists(string idx |
|
||||
idx =
|
||||
((getIndex() + 1).toString() + "th")
|
||||
.replaceAll("1th", "1st")
|
||||
.replaceAll("2th", "2nd")
|
||||
.replaceAll("3th", "3rd")
|
||||
.replaceAll("11st", "11th")
|
||||
.replaceAll("12nd", "12th")
|
||||
.replaceAll("13rd", "13th") and
|
||||
if exists(getCanonicalName())
|
||||
then result = "declaration of " + getCanonicalName() + " as anonymous " + idx + " parameter"
|
||||
else result = "declaration of " + idx + " parameter"
|
||||
)
|
||||
}
|
||||
|
||||
override string toString() {
|
||||
if exists(getName())
|
||||
then result = super.toString()
|
||||
else
|
||||
exists(string idx |
|
||||
idx =
|
||||
((getIndex() + 1).toString() + "th")
|
||||
.replaceAll("1th", "1st")
|
||||
.replaceAll("2th", "2nd")
|
||||
.replaceAll("3th", "3rd")
|
||||
.replaceAll("11st", "11th")
|
||||
.replaceAll("12nd", "12th")
|
||||
.replaceAll("13rd", "13th")
|
||||
|
|
||||
if exists(getCanonicalName())
|
||||
then result = "declaration of " + getCanonicalName() + " as anonymous " + idx + " parameter"
|
||||
else result = "declaration of " + idx + " parameter"
|
||||
)
|
||||
isDefinition() and
|
||||
result = "definition of " + getName()
|
||||
or
|
||||
not isDefinition() and
|
||||
if getName() = getCanonicalName()
|
||||
then result = "declaration of " + getName()
|
||||
else result = "declaration of " + getCanonicalName() + " as " + getName()
|
||||
or
|
||||
result = getAnonymousParameterDescription()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -116,7 +116,7 @@ class XMLFile extends XMLParent, File {
|
||||
XMLFile() { xmlEncoding(this, _) }
|
||||
|
||||
/** Gets a printable representation of this XML file. */
|
||||
override string toString() { result = XMLParent.super.toString() }
|
||||
override string toString() { result = getName() }
|
||||
|
||||
/** Gets the name of this XML file. */
|
||||
override string getName() { result = File.super.getAbsolutePath() }
|
||||
@@ -236,7 +236,7 @@ class XMLElement extends @xmlelement, XMLParent, XMLLocatable {
|
||||
string getAttributeValue(string name) { result = this.getAttribute(name).getValue() }
|
||||
|
||||
/** Gets a printable representation of this XML element. */
|
||||
override string toString() { result = XMLParent.super.toString() }
|
||||
override string toString() { result = getName() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -66,9 +66,6 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
predicate isBarrier(Node node) { none() }
|
||||
|
||||
/** DEPRECATED: override `isBarrierIn` and `isBarrierOut` instead. */
|
||||
deprecated predicate isBarrierEdge(Node node1, Node node2) { none() }
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isBarrierIn(Node node) { none() }
|
||||
|
||||
|
||||
@@ -66,9 +66,6 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
predicate isBarrier(Node node) { none() }
|
||||
|
||||
/** DEPRECATED: override `isBarrierIn` and `isBarrierOut` instead. */
|
||||
deprecated predicate isBarrierEdge(Node node1, Node node2) { none() }
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isBarrierIn(Node node) { none() }
|
||||
|
||||
|
||||
@@ -66,9 +66,6 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
predicate isBarrier(Node node) { none() }
|
||||
|
||||
/** DEPRECATED: override `isBarrierIn` and `isBarrierOut` instead. */
|
||||
deprecated predicate isBarrierEdge(Node node1, Node node2) { none() }
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isBarrierIn(Node node) { none() }
|
||||
|
||||
|
||||
@@ -66,9 +66,6 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
predicate isBarrier(Node node) { none() }
|
||||
|
||||
/** DEPRECATED: override `isBarrierIn` and `isBarrierOut` instead. */
|
||||
deprecated predicate isBarrierEdge(Node node1, Node node2) { none() }
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isBarrierIn(Node node) { none() }
|
||||
|
||||
|
||||
@@ -66,9 +66,6 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
predicate isBarrier(Node node) { none() }
|
||||
|
||||
/** DEPRECATED: override `isBarrierIn` and `isBarrierOut` instead. */
|
||||
deprecated predicate isBarrierEdge(Node node1, Node node2) { none() }
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isBarrierIn(Node node) { none() }
|
||||
|
||||
|
||||
@@ -79,13 +79,6 @@ abstract class Configuration extends DataFlow::Configuration {
|
||||
defaultTaintBarrier(node)
|
||||
}
|
||||
|
||||
/** DEPRECATED: override `isSanitizerIn` and `isSanitizerOut` instead. */
|
||||
deprecated predicate isSanitizerEdge(DataFlow::Node node1, DataFlow::Node node2) { none() }
|
||||
|
||||
deprecated final override predicate isBarrierEdge(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
isSanitizerEdge(node1, node2)
|
||||
}
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isSanitizerIn(DataFlow::Node node) { none() }
|
||||
|
||||
|
||||
@@ -79,13 +79,6 @@ abstract class Configuration extends DataFlow::Configuration {
|
||||
defaultTaintBarrier(node)
|
||||
}
|
||||
|
||||
/** DEPRECATED: override `isSanitizerIn` and `isSanitizerOut` instead. */
|
||||
deprecated predicate isSanitizerEdge(DataFlow::Node node1, DataFlow::Node node2) { none() }
|
||||
|
||||
deprecated final override predicate isBarrierEdge(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
isSanitizerEdge(node1, node2)
|
||||
}
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isSanitizerIn(DataFlow::Node node) { none() }
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ class Closure extends Class {
|
||||
result.getName() = "operator()"
|
||||
}
|
||||
|
||||
override string toString() { result = "decltype([...](...){...})" }
|
||||
override string getDescription() { result = "decltype([...](...){...})" }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,7 +99,7 @@ class Closure extends Class {
|
||||
* ```
|
||||
*/
|
||||
class LambdaCapture extends Locatable, @lambdacapture {
|
||||
override string toString() { result = getField().toString() }
|
||||
override string toString() { result = getField().getName() }
|
||||
|
||||
override string getCanonicalQLClass() { result = "LambdaCapture" }
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ private import semmle.code.cpp.ir.dataflow.DataFlow2
|
||||
private import semmle.code.cpp.ir.dataflow.DataFlow3
|
||||
private import semmle.code.cpp.ir.IR
|
||||
private import semmle.code.cpp.ir.dataflow.internal.DataFlowDispatch as Dispatch
|
||||
private import semmle.code.cpp.controlflow.IRGuards
|
||||
private import semmle.code.cpp.models.interfaces.Taint
|
||||
private import semmle.code.cpp.models.interfaces.DataFlow
|
||||
|
||||
@@ -170,11 +171,34 @@ private predicate hasUpperBoundsCheck(Variable var) {
|
||||
)
|
||||
}
|
||||
|
||||
private predicate nodeIsBarrierEqualityCandidate(
|
||||
DataFlow::Node node, Operand access, Variable checkedVar
|
||||
) {
|
||||
readsVariable(node.asInstruction(), checkedVar) and
|
||||
any(IRGuardCondition guard).ensuresEq(access, _, _, node.asInstruction().getBlock(), true)
|
||||
}
|
||||
|
||||
private predicate nodeIsBarrier(DataFlow::Node node) {
|
||||
exists(Variable checkedVar |
|
||||
readsVariable(node.asInstruction(), checkedVar) and
|
||||
hasUpperBoundsCheck(checkedVar)
|
||||
)
|
||||
or
|
||||
exists(Variable checkedVar, Operand access |
|
||||
/*
|
||||
* This node is guarded by a condition that forces the accessed variable
|
||||
* to equal something else. For example:
|
||||
* ```
|
||||
* x = taintsource()
|
||||
* if (x == 10) {
|
||||
* taintsink(x); // not considered tainted
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
|
||||
nodeIsBarrierEqualityCandidate(node, access, checkedVar) and
|
||||
readsVariable(access.getDef(), checkedVar)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate nodeIsBarrierIn(DataFlow::Node node) {
|
||||
|
||||
@@ -66,9 +66,6 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
predicate isBarrier(Node node) { none() }
|
||||
|
||||
/** DEPRECATED: override `isBarrierIn` and `isBarrierOut` instead. */
|
||||
deprecated predicate isBarrierEdge(Node node1, Node node2) { none() }
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isBarrierIn(Node node) { none() }
|
||||
|
||||
|
||||
@@ -66,9 +66,6 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
predicate isBarrier(Node node) { none() }
|
||||
|
||||
/** DEPRECATED: override `isBarrierIn` and `isBarrierOut` instead. */
|
||||
deprecated predicate isBarrierEdge(Node node1, Node node2) { none() }
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isBarrierIn(Node node) { none() }
|
||||
|
||||
|
||||
@@ -66,9 +66,6 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
predicate isBarrier(Node node) { none() }
|
||||
|
||||
/** DEPRECATED: override `isBarrierIn` and `isBarrierOut` instead. */
|
||||
deprecated predicate isBarrierEdge(Node node1, Node node2) { none() }
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isBarrierIn(Node node) { none() }
|
||||
|
||||
|
||||
@@ -66,9 +66,6 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
predicate isBarrier(Node node) { none() }
|
||||
|
||||
/** DEPRECATED: override `isBarrierIn` and `isBarrierOut` instead. */
|
||||
deprecated predicate isBarrierEdge(Node node1, Node node2) { none() }
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isBarrierIn(Node node) { none() }
|
||||
|
||||
|
||||
@@ -79,13 +79,6 @@ abstract class Configuration extends DataFlow::Configuration {
|
||||
defaultTaintBarrier(node)
|
||||
}
|
||||
|
||||
/** DEPRECATED: override `isSanitizerIn` and `isSanitizerOut` instead. */
|
||||
deprecated predicate isSanitizerEdge(DataFlow::Node node1, DataFlow::Node node2) { none() }
|
||||
|
||||
deprecated final override predicate isBarrierEdge(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
isSanitizerEdge(node1, node2)
|
||||
}
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isSanitizerIn(DataFlow::Node node) { none() }
|
||||
|
||||
|
||||
@@ -79,13 +79,6 @@ abstract class Configuration extends DataFlow::Configuration {
|
||||
defaultTaintBarrier(node)
|
||||
}
|
||||
|
||||
/** DEPRECATED: override `isSanitizerIn` and `isSanitizerOut` instead. */
|
||||
deprecated predicate isSanitizerEdge(DataFlow::Node node1, DataFlow::Node node2) { none() }
|
||||
|
||||
deprecated final override predicate isBarrierEdge(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
isSanitizerEdge(node1, node2)
|
||||
}
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isSanitizerIn(DataFlow::Node node) { none() }
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import default
|
||||
import cpp
|
||||
|
||||
from Class c, string n
|
||||
where n = count(Class x | x.getName() = c.getName()) + " distinct class(es) called " + c.getName()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import default
|
||||
import cpp
|
||||
import semmle.code.cpp.models.implementations.Allocation
|
||||
|
||||
query predicate newExprs(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import default
|
||||
import cpp
|
||||
|
||||
string getValueCategoryString(Expr expr) {
|
||||
if expr.isLValueCategory()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import default
|
||||
import cpp
|
||||
import semmle.code.cpp.ir.IR
|
||||
import semmle.code.cpp.ir.implementation.aliased_ssa.constant.ConstantAnalysis
|
||||
import semmle.code.cpp.ir.internal.IntegerConstant
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import default
|
||||
import cpp
|
||||
import semmle.code.cpp.ir.implementation.unaliased_ssa.internal.AliasAnalysis
|
||||
import semmle.code.cpp.ir.implementation.raw.IR
|
||||
import semmle.code.cpp.ir.implementation.UseSoundEscapeAnalysis
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import default
|
||||
import cpp
|
||||
import semmle.code.cpp.ir.implementation.aliased_ssa.internal.AliasAnalysis
|
||||
import semmle.code.cpp.ir.implementation.aliased_ssa.internal.AliasConfiguration
|
||||
import semmle.code.cpp.ir.implementation.unaliased_ssa.IR
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import default
|
||||
import cpp
|
||||
|
||||
query predicate classUuids(Class cls, string uuid) {
|
||||
if exists(cls.getUuid()) then uuid = cls.getUuid() else uuid = ""
|
||||
|
||||
@@ -2,7 +2,7 @@ import cpp
|
||||
|
||||
class FunctionMonkeyPatch extends Function {
|
||||
language[monotonicAggregates]
|
||||
override string toString() {
|
||||
override string getDescription() {
|
||||
exists(string name, string templateArgs, string args |
|
||||
result = name + templateArgs + args and
|
||||
name = this.getQualifiedName() and
|
||||
@@ -30,7 +30,9 @@ class FunctionMonkeyPatch extends Function {
|
||||
}
|
||||
|
||||
class ParameterMonkeyPatch extends Parameter {
|
||||
override string toString() { result = super.getType().getName() + " " + super.toString() }
|
||||
override string getDescription() {
|
||||
result = super.getType().getName() + " " + super.getDescription()
|
||||
}
|
||||
}
|
||||
|
||||
from Element e, Element ti
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
| templates.cpp:9:5:9:14 | using c |
|
||||
| usings.cpp:8:1:8:11 | using nf |
|
||||
| usings.cpp:9:1:9:17 | using namespace N |
|
||||
| usings.cpp:18:3:18:13 | using bf |
|
||||
| usings.cpp:21:5:21:14 | using gf |
|
||||
| usings.cpp:34:3:34:20 | using tbf |
|
||||
| usings.cpp:42:5:42:22 | using foo |
|
||||
| templates.cpp:9:5:9:14 | using c | UsingDeclarationEntry, enclosingElement:std |
|
||||
| usings.cpp:8:1:8:11 | using nf | UsingDeclarationEntry, enclosingElement:(global namespace) |
|
||||
| usings.cpp:9:1:9:17 | using namespace N | UsingDirectiveEntry, enclosingElement:(global namespace) |
|
||||
| usings.cpp:18:3:18:13 | using bf | UsingDeclarationEntry, enclosingElement:D |
|
||||
| usings.cpp:21:5:21:14 | using gf | UsingDeclarationEntry, enclosingElement:{ ... } |
|
||||
| usings.cpp:34:3:34:20 | using tbf | UsingDeclarationEntry, enclosingElement:TD |
|
||||
| usings.cpp:42:5:42:22 | using foo | UsingDeclarationEntry, enclosingElement:nsbar |
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
import cpp
|
||||
|
||||
string describe(UsingEntry ue) {
|
||||
ue instanceof UsingDeclarationEntry and
|
||||
result = "UsingDeclarationEntry"
|
||||
or
|
||||
ue instanceof UsingDirectiveEntry and
|
||||
result = "UsingDirectiveEntry"
|
||||
or
|
||||
result = "enclosingElement:" + ue.getEnclosingElement().toString()
|
||||
}
|
||||
|
||||
from UsingEntry ue
|
||||
select ue
|
||||
select ue, concat(describe(ue), ", ")
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
| templates.cpp:9:5:9:14 | using c | file://:0:0:0:0 | std |
|
||||
| usings.cpp:8:1:8:11 | using nf | file://:0:0:0:0 | (global namespace) |
|
||||
| usings.cpp:9:1:9:17 | using namespace N | file://:0:0:0:0 | (global namespace) |
|
||||
| usings.cpp:18:3:18:13 | using bf | usings.cpp:16:8:16:8 | D |
|
||||
| usings.cpp:21:5:21:14 | using gf | usings.cpp:20:13:23:3 | { ... } |
|
||||
| usings.cpp:34:3:34:20 | using tbf | usings.cpp:32:8:32:9 | TD |
|
||||
| usings.cpp:42:5:42:22 | using foo | usings.cpp:41:11:41:15 | nsbar |
|
||||
@@ -1,5 +0,0 @@
|
||||
import cpp
|
||||
|
||||
from UsingEntry ue, Element e
|
||||
where e = ue.getEnclosingElement()
|
||||
select ue, e
|
||||
@@ -68,6 +68,26 @@ edges
|
||||
| test.cpp:227:24:227:37 | (const char *)... | test.cpp:237:10:237:19 | (size_t)... |
|
||||
| test.cpp:235:11:235:20 | (size_t)... | test.cpp:214:23:214:23 | s |
|
||||
| test.cpp:237:10:237:19 | (size_t)... | test.cpp:220:21:220:21 | s |
|
||||
| test.cpp:241:2:241:32 | Chi | test.cpp:279:17:279:20 | get_size output argument |
|
||||
| test.cpp:241:2:241:32 | Chi | test.cpp:295:18:295:21 | get_size output argument |
|
||||
| test.cpp:241:18:241:23 | call to getenv | test.cpp:241:2:241:32 | Chi |
|
||||
| test.cpp:241:18:241:31 | (const char *)... | test.cpp:241:2:241:32 | Chi |
|
||||
| test.cpp:249:20:249:25 | call to getenv | test.cpp:253:11:253:29 | ... * ... |
|
||||
| test.cpp:249:20:249:25 | call to getenv | test.cpp:253:11:253:29 | ... * ... |
|
||||
| test.cpp:249:20:249:33 | (const char *)... | test.cpp:253:11:253:29 | ... * ... |
|
||||
| test.cpp:249:20:249:33 | (const char *)... | test.cpp:253:11:253:29 | ... * ... |
|
||||
| test.cpp:279:17:279:20 | get_size output argument | test.cpp:281:11:281:28 | ... * ... |
|
||||
| test.cpp:279:17:279:20 | get_size output argument | test.cpp:281:11:281:28 | ... * ... |
|
||||
| test.cpp:295:18:295:21 | get_size output argument | test.cpp:298:10:298:27 | ... * ... |
|
||||
| test.cpp:295:18:295:21 | get_size output argument | test.cpp:298:10:298:27 | ... * ... |
|
||||
| test.cpp:301:19:301:24 | call to getenv | test.cpp:305:11:305:28 | ... * ... |
|
||||
| test.cpp:301:19:301:24 | call to getenv | test.cpp:305:11:305:28 | ... * ... |
|
||||
| test.cpp:301:19:301:32 | (const char *)... | test.cpp:305:11:305:28 | ... * ... |
|
||||
| test.cpp:301:19:301:32 | (const char *)... | test.cpp:305:11:305:28 | ... * ... |
|
||||
| test.cpp:309:19:309:24 | call to getenv | test.cpp:314:10:314:27 | ... * ... |
|
||||
| test.cpp:309:19:309:24 | call to getenv | test.cpp:314:10:314:27 | ... * ... |
|
||||
| test.cpp:309:19:309:32 | (const char *)... | test.cpp:314:10:314:27 | ... * ... |
|
||||
| test.cpp:309:19:309:32 | (const char *)... | test.cpp:314:10:314:27 | ... * ... |
|
||||
nodes
|
||||
| field_conflation.c:12:22:12:27 | call to getenv | semmle.label | call to getenv |
|
||||
| field_conflation.c:12:22:12:34 | (const char *)... | semmle.label | (const char *)... |
|
||||
@@ -140,6 +160,32 @@ nodes
|
||||
| test.cpp:231:9:231:24 | call to get_tainted_size | semmle.label | call to get_tainted_size |
|
||||
| test.cpp:235:11:235:20 | (size_t)... | semmle.label | (size_t)... |
|
||||
| test.cpp:237:10:237:19 | (size_t)... | semmle.label | (size_t)... |
|
||||
| test.cpp:241:2:241:32 | Chi | semmle.label | Chi |
|
||||
| test.cpp:241:18:241:23 | call to getenv | semmle.label | call to getenv |
|
||||
| test.cpp:241:18:241:31 | (const char *)... | semmle.label | (const char *)... |
|
||||
| test.cpp:249:20:249:25 | call to getenv | semmle.label | call to getenv |
|
||||
| test.cpp:249:20:249:33 | (const char *)... | semmle.label | (const char *)... |
|
||||
| test.cpp:253:11:253:29 | ... * ... | semmle.label | ... * ... |
|
||||
| test.cpp:253:11:253:29 | ... * ... | semmle.label | ... * ... |
|
||||
| test.cpp:253:11:253:29 | ... * ... | semmle.label | ... * ... |
|
||||
| test.cpp:279:17:279:20 | get_size output argument | semmle.label | get_size output argument |
|
||||
| test.cpp:281:11:281:28 | ... * ... | semmle.label | ... * ... |
|
||||
| test.cpp:281:11:281:28 | ... * ... | semmle.label | ... * ... |
|
||||
| test.cpp:281:11:281:28 | ... * ... | semmle.label | ... * ... |
|
||||
| test.cpp:295:18:295:21 | get_size output argument | semmle.label | get_size output argument |
|
||||
| test.cpp:298:10:298:27 | ... * ... | semmle.label | ... * ... |
|
||||
| test.cpp:298:10:298:27 | ... * ... | semmle.label | ... * ... |
|
||||
| test.cpp:298:10:298:27 | ... * ... | semmle.label | ... * ... |
|
||||
| test.cpp:301:19:301:24 | call to getenv | semmle.label | call to getenv |
|
||||
| test.cpp:301:19:301:32 | (const char *)... | semmle.label | (const char *)... |
|
||||
| test.cpp:305:11:305:28 | ... * ... | semmle.label | ... * ... |
|
||||
| test.cpp:305:11:305:28 | ... * ... | semmle.label | ... * ... |
|
||||
| test.cpp:305:11:305:28 | ... * ... | semmle.label | ... * ... |
|
||||
| test.cpp:309:19:309:24 | call to getenv | semmle.label | call to getenv |
|
||||
| test.cpp:309:19:309:32 | (const char *)... | semmle.label | (const char *)... |
|
||||
| test.cpp:314:10:314:27 | ... * ... | semmle.label | ... * ... |
|
||||
| test.cpp:314:10:314:27 | ... * ... | semmle.label | ... * ... |
|
||||
| test.cpp:314:10:314:27 | ... * ... | semmle.label | ... * ... |
|
||||
#select
|
||||
| field_conflation.c:20:3:20:8 | call to malloc | field_conflation.c:12:22:12:27 | call to getenv | field_conflation.c:20:13:20:13 | x | This allocation size is derived from $@ and might overflow | field_conflation.c:12:22:12:27 | call to getenv | user input (getenv) |
|
||||
| test.cpp:42:31:42:36 | call to malloc | test.cpp:39:21:39:24 | argv | test.cpp:42:38:42:44 | tainted | This allocation size is derived from $@ and might overflow | test.cpp:39:21:39:24 | argv | user input (argv) |
|
||||
@@ -155,3 +201,8 @@ nodes
|
||||
| test.cpp:221:14:221:19 | call to malloc | test.cpp:227:24:227:29 | call to getenv | test.cpp:221:21:221:21 | s | This allocation size is derived from $@ and might overflow | test.cpp:227:24:227:29 | call to getenv | user input (getenv) |
|
||||
| test.cpp:229:2:229:7 | call to malloc | test.cpp:227:24:227:29 | call to getenv | test.cpp:229:9:229:18 | local_size | This allocation size is derived from $@ and might overflow | test.cpp:227:24:227:29 | call to getenv | user input (getenv) |
|
||||
| test.cpp:231:2:231:7 | call to malloc | test.cpp:201:14:201:19 | call to getenv | test.cpp:231:9:231:24 | call to get_tainted_size | This allocation size is derived from $@ and might overflow | test.cpp:201:14:201:19 | call to getenv | user input (getenv) |
|
||||
| test.cpp:253:4:253:9 | call to malloc | test.cpp:249:20:249:25 | call to getenv | test.cpp:253:11:253:29 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:249:20:249:25 | call to getenv | user input (getenv) |
|
||||
| test.cpp:281:4:281:9 | call to malloc | test.cpp:241:18:241:23 | call to getenv | test.cpp:281:11:281:28 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:241:18:241:23 | call to getenv | user input (getenv) |
|
||||
| test.cpp:298:3:298:8 | call to malloc | test.cpp:241:18:241:23 | call to getenv | test.cpp:298:10:298:27 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:241:18:241:23 | call to getenv | user input (getenv) |
|
||||
| test.cpp:305:4:305:9 | call to malloc | test.cpp:301:19:301:24 | call to getenv | test.cpp:305:11:305:28 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:301:19:301:24 | call to getenv | user input (getenv) |
|
||||
| test.cpp:314:3:314:8 | call to malloc | test.cpp:309:19:309:24 | call to getenv | test.cpp:314:10:314:27 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:309:19:309:24 | call to getenv | user input (getenv) |
|
||||
|
||||
@@ -236,3 +236,81 @@ void more_cases() {
|
||||
my_func(100); // GOOD
|
||||
my_func(local_size); // GOOD
|
||||
}
|
||||
|
||||
bool get_size(int &out_size) {
|
||||
out_size = atoi(getenv("USER"));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void equality_cases() {
|
||||
{
|
||||
int size1 = atoi(getenv("USER"));
|
||||
int size2 = atoi(getenv("USER"));
|
||||
|
||||
if (size1 == 100)
|
||||
{
|
||||
malloc(size2 * sizeof(int)); // BAD
|
||||
}
|
||||
if (size2 == 100)
|
||||
{
|
||||
malloc(size2 * sizeof(int)); // GOOD
|
||||
}
|
||||
}
|
||||
{
|
||||
int size = atoi(getenv("USER"));
|
||||
|
||||
if (size != 100)
|
||||
return;
|
||||
|
||||
malloc(size * sizeof(int)); // GOOD
|
||||
}
|
||||
{
|
||||
int size;
|
||||
|
||||
if ((get_size(size)) && (size == 100))
|
||||
{
|
||||
malloc(size * sizeof(int)); // GOOD
|
||||
}
|
||||
}
|
||||
{
|
||||
int size;
|
||||
|
||||
if ((get_size(size)) && (size != 100))
|
||||
{
|
||||
malloc(size * sizeof(int)); // BAD
|
||||
}
|
||||
}
|
||||
{
|
||||
int size;
|
||||
|
||||
if ((!get_size(size)) || (size != 100))
|
||||
return;
|
||||
|
||||
malloc(size * sizeof(int)); // GOOD
|
||||
}
|
||||
{
|
||||
int size;
|
||||
|
||||
if ((!get_size(size)) || (size == 100))
|
||||
return;
|
||||
|
||||
malloc(size * sizeof(int)); // BAD
|
||||
}
|
||||
{
|
||||
int size = atoi(getenv("USER"));
|
||||
|
||||
if ((size == 50) || (size == 100))
|
||||
{
|
||||
malloc(size * sizeof(int)); // GOOD [FALSE POSITIVE]
|
||||
}
|
||||
}
|
||||
{
|
||||
int size = atoi(getenv("USER"));
|
||||
|
||||
if (size != 50 && size != 100)
|
||||
return;
|
||||
|
||||
malloc(size * sizeof(int)); // GOOD [FALSE POSITIVE]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,5 +5,4 @@
|
||||
| test.c:63:3:63:5 | sc8 | $@ flows to here and is used in arithmetic, potentially causing an underflow. | test.c:62:9:62:16 | - ... | Extreme value |
|
||||
| test.c:75:3:75:5 | sc1 | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test.c:74:9:74:16 | 127 | Extreme value |
|
||||
| test.c:76:3:76:5 | sc1 | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test.c:74:9:74:16 | 127 | Extreme value |
|
||||
| test.c:114:9:114:9 | x | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test.c:108:17:108:23 | 2147483647 | Extreme value |
|
||||
| test.c:124:9:124:9 | x | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test.c:118:17:118:23 | 2147483647 | Extreme value |
|
||||
|
||||
@@ -111,7 +111,7 @@ void test_guards3(int cond) {
|
||||
|
||||
if (x != 0) return;
|
||||
|
||||
return x + 1; // GOOD [FALSE POSITIVE]
|
||||
return x + 1; // GOOD
|
||||
}
|
||||
|
||||
void test_guards4(int cond) {
|
||||
|
||||
@@ -1160,26 +1160,6 @@ class UsingStmt extends Stmt, @using_stmt {
|
||||
* ```
|
||||
*/
|
||||
Expr getAnExpr() { none() }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use UsingBlockStmt.getExpr() instead.
|
||||
* Gets the expression directly used by this `using` statement, if any. For
|
||||
* example, `f` on line 2 in
|
||||
*
|
||||
* ```
|
||||
* var f = File.Open("settings.xml");
|
||||
* using (f) {
|
||||
* ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
deprecated Expr getExpr() { none() }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use UsingBlockStmt.getBody() instead.
|
||||
* Gets the body of this `using` statement.
|
||||
*/
|
||||
deprecated Stmt getBody() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1212,7 +1192,7 @@ class UsingBlockStmt extends UsingStmt, @using_block_stmt {
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
override Expr getExpr() { result = this.getChild(0) }
|
||||
Expr getExpr() { result = this.getChild(0) }
|
||||
|
||||
override Expr getAnExpr() {
|
||||
result = this.getAVariableDeclExpr().getInitializer()
|
||||
@@ -1221,7 +1201,7 @@ class UsingBlockStmt extends UsingStmt, @using_block_stmt {
|
||||
}
|
||||
|
||||
/** Gets the body of this `using` statement. */
|
||||
override Stmt getBody() { result.getParent() = this }
|
||||
Stmt getBody() { result.getParent() = this }
|
||||
|
||||
override string toString() { result = "using (...) {...}" }
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ class XMLFile extends XMLParent, File {
|
||||
XMLFile() { xmlEncoding(this, _) }
|
||||
|
||||
/** Gets a printable representation of this XML file. */
|
||||
override string toString() { result = XMLParent.super.toString() }
|
||||
override string toString() { result = getName() }
|
||||
|
||||
/** Gets the name of this XML file. */
|
||||
override string getName() { result = File.super.getAbsolutePath() }
|
||||
@@ -236,7 +236,7 @@ class XMLElement extends @xmlelement, XMLParent, XMLLocatable {
|
||||
string getAttributeValue(string name) { result = this.getAttribute(name).getValue() }
|
||||
|
||||
/** Gets a printable representation of this XML element. */
|
||||
override string toString() { result = XMLParent.super.toString() }
|
||||
override string toString() { result = getName() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -66,9 +66,6 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
predicate isBarrier(Node node) { none() }
|
||||
|
||||
/** DEPRECATED: override `isBarrierIn` and `isBarrierOut` instead. */
|
||||
deprecated predicate isBarrierEdge(Node node1, Node node2) { none() }
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isBarrierIn(Node node) { none() }
|
||||
|
||||
|
||||
@@ -66,9 +66,6 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
predicate isBarrier(Node node) { none() }
|
||||
|
||||
/** DEPRECATED: override `isBarrierIn` and `isBarrierOut` instead. */
|
||||
deprecated predicate isBarrierEdge(Node node1, Node node2) { none() }
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isBarrierIn(Node node) { none() }
|
||||
|
||||
|
||||
@@ -66,9 +66,6 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
predicate isBarrier(Node node) { none() }
|
||||
|
||||
/** DEPRECATED: override `isBarrierIn` and `isBarrierOut` instead. */
|
||||
deprecated predicate isBarrierEdge(Node node1, Node node2) { none() }
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isBarrierIn(Node node) { none() }
|
||||
|
||||
|
||||
@@ -66,9 +66,6 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
predicate isBarrier(Node node) { none() }
|
||||
|
||||
/** DEPRECATED: override `isBarrierIn` and `isBarrierOut` instead. */
|
||||
deprecated predicate isBarrierEdge(Node node1, Node node2) { none() }
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isBarrierIn(Node node) { none() }
|
||||
|
||||
|
||||
@@ -66,9 +66,6 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
predicate isBarrier(Node node) { none() }
|
||||
|
||||
/** DEPRECATED: override `isBarrierIn` and `isBarrierOut` instead. */
|
||||
deprecated predicate isBarrierEdge(Node node1, Node node2) { none() }
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isBarrierIn(Node node) { none() }
|
||||
|
||||
|
||||
@@ -79,13 +79,6 @@ abstract class Configuration extends DataFlow::Configuration {
|
||||
defaultTaintBarrier(node)
|
||||
}
|
||||
|
||||
/** DEPRECATED: override `isSanitizerIn` and `isSanitizerOut` instead. */
|
||||
deprecated predicate isSanitizerEdge(DataFlow::Node node1, DataFlow::Node node2) { none() }
|
||||
|
||||
deprecated final override predicate isBarrierEdge(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
isSanitizerEdge(node1, node2)
|
||||
}
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isSanitizerIn(DataFlow::Node node) { none() }
|
||||
|
||||
|
||||
@@ -79,13 +79,6 @@ abstract class Configuration extends DataFlow::Configuration {
|
||||
defaultTaintBarrier(node)
|
||||
}
|
||||
|
||||
/** DEPRECATED: override `isSanitizerIn` and `isSanitizerOut` instead. */
|
||||
deprecated predicate isSanitizerEdge(DataFlow::Node node1, DataFlow::Node node2) { none() }
|
||||
|
||||
deprecated final override predicate isBarrierEdge(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
isSanitizerEdge(node1, node2)
|
||||
}
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isSanitizerIn(DataFlow::Node node) { none() }
|
||||
|
||||
|
||||
@@ -79,13 +79,6 @@ abstract class Configuration extends DataFlow::Configuration {
|
||||
defaultTaintBarrier(node)
|
||||
}
|
||||
|
||||
/** DEPRECATED: override `isSanitizerIn` and `isSanitizerOut` instead. */
|
||||
deprecated predicate isSanitizerEdge(DataFlow::Node node1, DataFlow::Node node2) { none() }
|
||||
|
||||
deprecated final override predicate isBarrierEdge(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
isSanitizerEdge(node1, node2)
|
||||
}
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isSanitizerIn(DataFlow::Node node) { none() }
|
||||
|
||||
|
||||
@@ -79,13 +79,6 @@ abstract class Configuration extends DataFlow::Configuration {
|
||||
defaultTaintBarrier(node)
|
||||
}
|
||||
|
||||
/** DEPRECATED: override `isSanitizerIn` and `isSanitizerOut` instead. */
|
||||
deprecated predicate isSanitizerEdge(DataFlow::Node node1, DataFlow::Node node2) { none() }
|
||||
|
||||
deprecated final override predicate isBarrierEdge(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
isSanitizerEdge(node1, node2)
|
||||
}
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isSanitizerIn(DataFlow::Node node) { none() }
|
||||
|
||||
|
||||
@@ -79,13 +79,6 @@ abstract class Configuration extends DataFlow::Configuration {
|
||||
defaultTaintBarrier(node)
|
||||
}
|
||||
|
||||
/** DEPRECATED: override `isSanitizerIn` and `isSanitizerOut` instead. */
|
||||
deprecated predicate isSanitizerEdge(DataFlow::Node node1, DataFlow::Node node2) { none() }
|
||||
|
||||
deprecated final override predicate isBarrierEdge(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
isSanitizerEdge(node1, node2)
|
||||
}
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isSanitizerIn(DataFlow::Node node) { none() }
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
{
|
||||
Uri uri = Uri.parse(url);
|
||||
// BAD: partial domain match, which allows an attacker to register a domain like myexample.com to circumvent the verification
|
||||
if (uri.getHost() != null && uri.getHost().endsWith("example.com")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
Uri uri = Uri.parse(url);
|
||||
// GOOD: full domain match
|
||||
if (uri.getHost() != null && uri.getHost().endsWith(".example.com")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>Apps that rely on URL Parsing to verify that a given URL is pointing to a trust server may be susceptible to many different ways to get URL parsing and verification wrong, which allows an attacker to register a fake site to break the access control.</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>Verify the whole host and domain (FQDN) or check endsWith dot+domain.</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>The following example shows two ways of verifying host domain. In the 'BAD' case,
|
||||
verification is implemented as partial domain match. In the 'GOOD' case, full domain is verified.</p>
|
||||
<sample src="IncorrectURLVerification.java" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
<a href="https://drive.google.com/file/d/0BwMN49Gzo3x6T1N5WGQ4TTNlMHBOb1ZRQTVEWnVBZjFUaE5N/view">Common Android app vulnerabilities from Sebastian Porst of Google</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.bugcrowd.com/resources/webinars/overview-of-common-android-app-vulnerabilities/">Common Android app vulnerabilities from bugcrowd</a>
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
97
java/ql/src/experimental/CWE-939/IncorrectURLVerification.ql
Normal file
97
java/ql/src/experimental/CWE-939/IncorrectURLVerification.ql
Normal file
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* @id java/incorrect-url-verification
|
||||
* @name Incorrect URL verification
|
||||
* @description Apps that rely on URL parsing to verify that a given URL is pointing to a trusted server are susceptible to wrong ways of URL parsing and verification.
|
||||
* @kind problem
|
||||
* @tags security
|
||||
* external/cwe-939
|
||||
*/
|
||||
|
||||
import java
|
||||
|
||||
/**
|
||||
* The Java class `android.R.string` specific to Android applications, which contains references to application specific resources defined in /res/values/strings.xml.
|
||||
* For example, <resources>...<string name="host">example.com</string>...</resources> in the application com.example.android.web can be referred as R.string.host with the type com.example.android.web.R$string
|
||||
*/
|
||||
class AndroidRString extends RefType {
|
||||
AndroidRString() { this.hasQualifiedName(_, "R$string") }
|
||||
}
|
||||
|
||||
/**
|
||||
* The Java class `android.net.Uri` and `java.net.URL`.
|
||||
*/
|
||||
class Uri extends RefType {
|
||||
Uri() {
|
||||
hasQualifiedName("android.net", "Uri") or
|
||||
hasQualifiedName("java.net", "URL")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The method `getHost()` declared in `android.net.Uri` and `java.net.URL`.
|
||||
*/
|
||||
class UriGetHostMethod extends Method {
|
||||
UriGetHostMethod() {
|
||||
getDeclaringType() instanceof Uri and
|
||||
hasName("getHost") and
|
||||
getNumberOfParameters() = 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The method access with incorrect string comparision
|
||||
*/
|
||||
class HostVerificationMethodAccess extends MethodAccess {
|
||||
HostVerificationMethodAccess() {
|
||||
(
|
||||
this.getMethod().hasName("endsWith") or
|
||||
this.getMethod().hasName("contains") or
|
||||
this.getMethod().hasName("indexOf")
|
||||
) and
|
||||
this.getMethod().getNumberOfParameters() = 1 and
|
||||
(
|
||||
this.getArgument(0).(StringLiteral).getRepresentedString().charAt(0) != "." //string constant comparison e.g. uri.getHost().endsWith("example.com")
|
||||
or
|
||||
this
|
||||
.getArgument(0)
|
||||
.(AddExpr)
|
||||
.getLeftOperand()
|
||||
.(VarAccess)
|
||||
.getVariable()
|
||||
.getAnAssignedValue()
|
||||
.(StringLiteral)
|
||||
.getRepresentedString()
|
||||
.charAt(0) != "." //var1+var2, check var1 starts with "." e.g. String domainName = "example"; Uri.parse(url).getHost().endsWith(domainName+".com")
|
||||
or
|
||||
this
|
||||
.getArgument(0)
|
||||
.(AddExpr)
|
||||
.getLeftOperand()
|
||||
.(StringLiteral)
|
||||
.getRepresentedString()
|
||||
.charAt(0) != "." //"."+var2, check string constant "." e.g. String domainName = "example.com"; Uri.parse(url).getHost().endsWith("www."+domainName)
|
||||
or
|
||||
exists(MethodAccess ma, Method m, Field f |
|
||||
this.getArgument(0) = ma and
|
||||
ma.getMethod() = m and
|
||||
m.hasName("getString") and
|
||||
m.getDeclaringType().getQualifiedName() = "android.content.res.Resources" and
|
||||
ma.getArgument(0).(FieldRead).getField() = f and
|
||||
f.getDeclaringType() instanceof AndroidRString
|
||||
) //Check resource properties in /res/values/strings.xml in Android mobile applications using res.getString(R.string.key)
|
||||
or
|
||||
this
|
||||
.getArgument(0)
|
||||
.(VarAccess)
|
||||
.getVariable()
|
||||
.getAnAssignedValue()
|
||||
.(StringLiteral)
|
||||
.getRepresentedString()
|
||||
.charAt(0) != "." //check variable starts with "." e.g. String domainName = "example.com"; Uri.parse(url).getHost().endsWith(domainName)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
from UriGetHostMethod um, MethodAccess uma, HostVerificationMethodAccess hma
|
||||
where hma.getQualifier() = uma and uma.getMethod() = um
|
||||
select hma, "Method has potentially $@ ", hma.getArgument(0), "improper URL verification"
|
||||
@@ -22,8 +22,7 @@ class TypeAuthorizedUrl extends Class {
|
||||
}
|
||||
|
||||
/**
|
||||
* The class
|
||||
* `org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry`.
|
||||
* The class `org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry`.
|
||||
*/
|
||||
class TypeAbstractRequestMatcherRegistry extends Class {
|
||||
TypeAbstractRequestMatcherRegistry() {
|
||||
@@ -34,38 +33,44 @@ class TypeAbstractRequestMatcherRegistry extends Class {
|
||||
}
|
||||
|
||||
/**
|
||||
* The class
|
||||
* `org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest.EndpointRequestMatcher`.
|
||||
* The class `org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest`.
|
||||
*/
|
||||
class TypeEndpointRequestMatcher extends Class {
|
||||
TypeEndpointRequestMatcher() {
|
||||
class TypeEndpointRequest extends Class {
|
||||
TypeEndpointRequest() {
|
||||
this
|
||||
.hasQualifiedName("org.springframework.boot.actuate.autoconfigure.security.servlet",
|
||||
"EndpointRequest$EndpointRequestMatcher")
|
||||
"EndpointRequest")
|
||||
}
|
||||
}
|
||||
|
||||
/** A call to `EndpointRequest.toAnyEndpoint` method. */
|
||||
class ToAnyEndpointCall extends MethodAccess {
|
||||
ToAnyEndpointCall() {
|
||||
getMethod().hasName("toAnyEndpoint") and
|
||||
getMethod().getDeclaringType() instanceof TypeEndpointRequest
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `HttpSecurity.requestMatcher` method with argument of type
|
||||
* `EndpointRequestMatcher`.
|
||||
* A call to `HttpSecurity.requestMatcher` method with argument `RequestMatcher.toAnyEndpoint()`.
|
||||
*/
|
||||
class RequestMatcherCall extends MethodAccess {
|
||||
RequestMatcherCall() {
|
||||
getMethod().hasName("requestMatcher") and
|
||||
getMethod().getDeclaringType() instanceof TypeHttpSecurity and
|
||||
getArgument(0).getType() instanceof TypeEndpointRequestMatcher
|
||||
getArgument(0) instanceof ToAnyEndpointCall
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `HttpSecurity.requestMatchers` method with lambda argument resolving to
|
||||
* `EndpointRequestMatcher` type.
|
||||
* A call to `HttpSecurity.requestMatchers` method with lambda argument
|
||||
* `RequestMatcher.toAnyEndpoint()`.
|
||||
*/
|
||||
class RequestMatchersCall extends MethodAccess {
|
||||
RequestMatchersCall() {
|
||||
getMethod().hasName("requestMatchers") and
|
||||
getMethod().getDeclaringType() instanceof TypeHttpSecurity and
|
||||
getArgument(0).(LambdaExpr).getExprBody().getType() instanceof TypeEndpointRequestMatcher
|
||||
getArgument(0).(LambdaExpr).getExprBody() instanceof ToAnyEndpointCall
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,9 +97,6 @@ class PermitAllCall extends MethodAccess {
|
||||
or
|
||||
// .requestMatchers(matcher -> EndpointRequest).authorizeRequests([...]).[...]
|
||||
authorizeRequestsCall.getQualifier() instanceof RequestMatchersCall
|
||||
or
|
||||
// http.authorizeRequests([...]).[...]
|
||||
authorizeRequestsCall.getQualifier() instanceof VarAccess
|
||||
|
|
||||
// [...].authorizeRequests(r -> r.anyRequest().permitAll()) or
|
||||
// [...].authorizeRequests(r -> r.requestMatchers(EndpointRequest).permitAll())
|
||||
@@ -117,6 +119,22 @@ class PermitAllCall extends MethodAccess {
|
||||
this.getQualifier() = anyRequestCall
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(AuthorizeRequestsCall authorizeRequestsCall |
|
||||
// http.authorizeRequests([...]).[...]
|
||||
authorizeRequestsCall.getQualifier() instanceof VarAccess
|
||||
|
|
||||
// [...].authorizeRequests(r -> r.requestMatchers(EndpointRequest).permitAll())
|
||||
authorizeRequestsCall.getArgument(0).(LambdaExpr).getExprBody() = this and
|
||||
this.getQualifier() instanceof RegistryRequestMatchersCall
|
||||
or
|
||||
// [...].authorizeRequests().requestMatchers(EndpointRequest).permitAll() or
|
||||
authorizeRequestsCall.getNumArgument() = 0 and
|
||||
exists(RegistryRequestMatchersCall registryRequestMatchersCall |
|
||||
registryRequestMatchersCall.getQualifier() = authorizeRequestsCall and
|
||||
this.getQualifier() = registryRequestMatchersCall
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,13 +147,13 @@ class AnyRequestCall extends MethodAccess {
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `AbstractRequestMatcherRegistry.requestMatchers` method with an argument of type
|
||||
* `EndpointRequestMatcher`.
|
||||
* A call to `AbstractRequestMatcherRegistry.requestMatchers` method with an argument
|
||||
* `RequestMatcher.toAnyEndpoint()`.
|
||||
*/
|
||||
class RegistryRequestMatchersCall extends MethodAccess {
|
||||
RegistryRequestMatchersCall() {
|
||||
getMethod().hasName("requestMatchers") and
|
||||
getMethod().getDeclaringType() instanceof TypeAbstractRequestMatcherRegistry and
|
||||
getAnArgument().getType() instanceof TypeEndpointRequestMatcher
|
||||
getAnArgument() instanceof ToAnyEndpointCall
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,9 +66,6 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
predicate isBarrier(Node node) { none() }
|
||||
|
||||
/** DEPRECATED: override `isBarrierIn` and `isBarrierOut` instead. */
|
||||
deprecated predicate isBarrierEdge(Node node1, Node node2) { none() }
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isBarrierIn(Node node) { none() }
|
||||
|
||||
|
||||
@@ -66,9 +66,6 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
predicate isBarrier(Node node) { none() }
|
||||
|
||||
/** DEPRECATED: override `isBarrierIn` and `isBarrierOut` instead. */
|
||||
deprecated predicate isBarrierEdge(Node node1, Node node2) { none() }
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isBarrierIn(Node node) { none() }
|
||||
|
||||
|
||||
@@ -66,9 +66,6 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
predicate isBarrier(Node node) { none() }
|
||||
|
||||
/** DEPRECATED: override `isBarrierIn` and `isBarrierOut` instead. */
|
||||
deprecated predicate isBarrierEdge(Node node1, Node node2) { none() }
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isBarrierIn(Node node) { none() }
|
||||
|
||||
|
||||
@@ -66,9 +66,6 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
predicate isBarrier(Node node) { none() }
|
||||
|
||||
/** DEPRECATED: override `isBarrierIn` and `isBarrierOut` instead. */
|
||||
deprecated predicate isBarrierEdge(Node node1, Node node2) { none() }
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isBarrierIn(Node node) { none() }
|
||||
|
||||
|
||||
@@ -66,9 +66,6 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
predicate isBarrier(Node node) { none() }
|
||||
|
||||
/** DEPRECATED: override `isBarrierIn` and `isBarrierOut` instead. */
|
||||
deprecated predicate isBarrierEdge(Node node1, Node node2) { none() }
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isBarrierIn(Node node) { none() }
|
||||
|
||||
|
||||
@@ -79,13 +79,6 @@ abstract class Configuration extends DataFlow::Configuration {
|
||||
defaultTaintBarrier(node)
|
||||
}
|
||||
|
||||
/** DEPRECATED: override `isSanitizerIn` and `isSanitizerOut` instead. */
|
||||
deprecated predicate isSanitizerEdge(DataFlow::Node node1, DataFlow::Node node2) { none() }
|
||||
|
||||
deprecated final override predicate isBarrierEdge(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
isSanitizerEdge(node1, node2)
|
||||
}
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isSanitizerIn(DataFlow::Node node) { none() }
|
||||
|
||||
|
||||
@@ -79,13 +79,6 @@ abstract class Configuration extends DataFlow::Configuration {
|
||||
defaultTaintBarrier(node)
|
||||
}
|
||||
|
||||
/** DEPRECATED: override `isSanitizerIn` and `isSanitizerOut` instead. */
|
||||
deprecated predicate isSanitizerEdge(DataFlow::Node node1, DataFlow::Node node2) { none() }
|
||||
|
||||
deprecated final override predicate isBarrierEdge(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
isSanitizerEdge(node1, node2)
|
||||
}
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isSanitizerIn(DataFlow::Node node) { none() }
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ class XMLFile extends XMLParent, File {
|
||||
XMLFile() { xmlEncoding(this, _) }
|
||||
|
||||
/** Gets a printable representation of this XML file. */
|
||||
override string toString() { result = XMLParent.super.toString() }
|
||||
override string toString() { result = getName() }
|
||||
|
||||
/** Gets the name of this XML file. */
|
||||
override string getName() { result = File.super.getAbsolutePath() }
|
||||
@@ -236,7 +236,7 @@ class XMLElement extends @xmlelement, XMLParent, XMLLocatable {
|
||||
string getAttributeValue(string name) { result = this.getAttribute(name).getValue() }
|
||||
|
||||
/** Gets a printable representation of this XML element. */
|
||||
override string toString() { result = XMLParent.super.toString() }
|
||||
override string toString() { result = getName() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -37,4 +37,68 @@ public class SpringBootActuators {
|
||||
protected void configureOk2(HttpSecurity http) throws Exception {
|
||||
http.requestMatchers().requestMatchers(EndpointRequest.toAnyEndpoint());
|
||||
}
|
||||
|
||||
protected void configureOk3(HttpSecurity http) throws Exception {
|
||||
http.authorizeRequests().anyRequest().permitAll();
|
||||
}
|
||||
|
||||
protected void configureOk4(HttpSecurity http) throws Exception {
|
||||
http.authorizeRequests(authz -> authz.anyRequest().permitAll());
|
||||
}
|
||||
|
||||
protected void configureOkSafeEndpoints1(HttpSecurity http) throws Exception {
|
||||
http.requestMatcher(EndpointRequest.to("health", "info")).authorizeRequests(requests -> requests.anyRequest().permitAll());
|
||||
}
|
||||
|
||||
protected void configureOkSafeEndpoints2(HttpSecurity http) throws Exception {
|
||||
http.requestMatcher(EndpointRequest.to("health")).authorizeRequests().requestMatchers(EndpointRequest.to("health")).permitAll();
|
||||
}
|
||||
|
||||
protected void configureOkSafeEndpoints3(HttpSecurity http) throws Exception {
|
||||
http.requestMatchers(matcher -> EndpointRequest.to("health", "info")).authorizeRequests().requestMatchers(EndpointRequest.to("health", "info")).permitAll();
|
||||
}
|
||||
|
||||
protected void configureOkSafeEndpoints4(HttpSecurity http) throws Exception {
|
||||
http.requestMatcher(EndpointRequest.to("health", "info")).authorizeRequests().anyRequest().permitAll();
|
||||
}
|
||||
|
||||
protected void configureOkSafeEndpoints5(HttpSecurity http) throws Exception {
|
||||
http.authorizeRequests().requestMatchers(EndpointRequest.to("health", "info")).permitAll();
|
||||
}
|
||||
|
||||
protected void configureOkSafeEndpoints6(HttpSecurity http) throws Exception {
|
||||
http.authorizeRequests(requests -> requests.requestMatchers(EndpointRequest.to("health", "info")).permitAll());
|
||||
}
|
||||
|
||||
protected void configureOkSafeEndpoints7(HttpSecurity http) throws Exception {
|
||||
http.requestMatchers(matcher -> EndpointRequest.to("health", "info")).authorizeRequests().anyRequest().permitAll();
|
||||
}
|
||||
|
||||
protected void configureOkNoPermitAll1(HttpSecurity http) throws Exception {
|
||||
http.requestMatcher(EndpointRequest.toAnyEndpoint()).authorizeRequests(requests -> requests.anyRequest());
|
||||
}
|
||||
|
||||
protected void configureOkNoPermitAll2(HttpSecurity http) throws Exception {
|
||||
http.requestMatcher(EndpointRequest.toAnyEndpoint()).authorizeRequests().requestMatchers(EndpointRequest.toAnyEndpoint());
|
||||
}
|
||||
|
||||
protected void configureOkNoPermitAll3(HttpSecurity http) throws Exception {
|
||||
http.requestMatchers(matcher -> EndpointRequest.toAnyEndpoint()).authorizeRequests().requestMatchers(EndpointRequest.toAnyEndpoint());
|
||||
}
|
||||
|
||||
protected void configureOkNoPermitAll4(HttpSecurity http) throws Exception {
|
||||
http.requestMatcher(EndpointRequest.toAnyEndpoint()).authorizeRequests().anyRequest();
|
||||
}
|
||||
|
||||
protected void configureOkNoPermitAll5(HttpSecurity http) throws Exception {
|
||||
http.authorizeRequests().requestMatchers(EndpointRequest.toAnyEndpoint());
|
||||
}
|
||||
|
||||
protected void configureOkNoPermitAll6(HttpSecurity http) throws Exception {
|
||||
http.authorizeRequests(requests -> requests.requestMatchers(EndpointRequest.toAnyEndpoint()));
|
||||
}
|
||||
|
||||
protected void configureOkNoPermitAll7(HttpSecurity http) throws Exception {
|
||||
http.requestMatchers(matcher -> EndpointRequest.toAnyEndpoint()).authorizeRequests().anyRequest();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,10 @@ public final class EndpointRequest {
|
||||
public static EndpointRequestMatcher toAnyEndpoint() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static EndpointRequestMatcher to(String... endpoints) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static final class EndpointRequestMatcher extends AbstractRequestMatcher {}
|
||||
|
||||
|
||||
14
javascript/ql/src/external/CodeDuplication.qll
vendored
14
javascript/ql/src/external/CodeDuplication.qll
vendored
@@ -261,6 +261,11 @@ predicate similarContainers(StmtContainer sc, StmtContainer other, float percent
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: do not use.
|
||||
*
|
||||
* Holds if `line` in `f` is similar to a line somewhere else.
|
||||
*/
|
||||
predicate similarLines(File f, int line) {
|
||||
exists(SimilarBlock b | b.sourceFile() = f and line in [b.sourceStartLine() .. b.sourceEndLine()])
|
||||
}
|
||||
@@ -275,6 +280,7 @@ private predicate similarLinesPerEquivalenceClass(int equivClass, int lines, Fil
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `coveredLines` lines of `f` are similar to lines in `otherFile`. */
|
||||
pragma[noopt]
|
||||
private predicate similarLinesCovered(File f, int coveredLines, File otherFile) {
|
||||
exists(int numLines | numLines = f.getNumberOfLines() |
|
||||
@@ -296,6 +302,11 @@ private predicate similarLinesCovered(File f, int coveredLines, File otherFile)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: do not use.
|
||||
*
|
||||
* Holds if `line` in `f` is duplicated by a line somewhere else.
|
||||
*/
|
||||
predicate duplicateLines(File f, int line) {
|
||||
exists(DuplicateBlock b |
|
||||
b.sourceFile() = f and line in [b.sourceStartLine() .. b.sourceEndLine()]
|
||||
@@ -312,6 +323,7 @@ private predicate duplicateLinesPerEquivalenceClass(int equivClass, int lines, F
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `coveredLines` lines of `f` are duplicates of lines in `otherFile`. */
|
||||
pragma[noopt]
|
||||
private predicate duplicateLinesCovered(File f, int coveredLines, File otherFile) {
|
||||
exists(int numLines | numLines = f.getNumberOfLines() |
|
||||
@@ -333,6 +345,7 @@ private predicate duplicateLinesCovered(File f, int coveredLines, File otherFile
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if most of `f` (`percent`%) is similar to `other`. */
|
||||
predicate similarFiles(File f, File other, int percent) {
|
||||
exists(int covered, int total |
|
||||
similarLinesCovered(f, covered, other) and
|
||||
@@ -343,6 +356,7 @@ predicate similarFiles(File f, File other, int percent) {
|
||||
not duplicateFiles(f, other, _)
|
||||
}
|
||||
|
||||
/** Holds if most of `f` (`percent`%) is duplicated by `other`. */
|
||||
predicate duplicateFiles(File f, File other, int percent) {
|
||||
exists(int covered, int total |
|
||||
duplicateLinesCovered(f, covered, other) and
|
||||
|
||||
@@ -152,6 +152,18 @@ private class RequireVariable extends Variable {
|
||||
*/
|
||||
private predicate moduleInFile(Module m, File f) { m.getFile() = f }
|
||||
|
||||
/**
|
||||
* Holds if `nd` may refer to `require`, either directly or modulo local data flow.
|
||||
*/
|
||||
cached
|
||||
private predicate isRequire(DataFlow::Node nd) {
|
||||
nd.asExpr() = any(RequireVariable req).getAnAccess() and
|
||||
// `mjs` files explicitly disallow `require`
|
||||
not nd.getFile().getExtension() = "mjs"
|
||||
or
|
||||
isRequire(nd.getAPredecessor())
|
||||
}
|
||||
|
||||
/**
|
||||
* A `require` import.
|
||||
*
|
||||
@@ -162,12 +174,7 @@ private predicate moduleInFile(Module m, File f) { m.getFile() = f }
|
||||
* ```
|
||||
*/
|
||||
class Require extends CallExpr, Import {
|
||||
cached
|
||||
Require() {
|
||||
any(RequireVariable req).getAnAccess() = getCallee() and
|
||||
// `mjs` files explicitly disallow `require`
|
||||
not getFile().getExtension() = "mjs"
|
||||
}
|
||||
Require() { isRequire(getCallee().flow()) }
|
||||
|
||||
override PathExpr getImportedPath() { result = getArgument(0) }
|
||||
|
||||
@@ -257,8 +264,8 @@ private class RequirePath extends PathExprCandidate {
|
||||
RequirePath() {
|
||||
this = any(Require req).getArgument(0)
|
||||
or
|
||||
exists(RequireVariable req, MethodCallExpr reqres |
|
||||
reqres.getReceiver() = req.getAnAccess() and
|
||||
exists(MethodCallExpr reqres |
|
||||
isRequire(reqres.getReceiver().flow()) and
|
||||
reqres.getMethodName() = "resolve" and
|
||||
this = reqres.getArgument(0)
|
||||
)
|
||||
|
||||
@@ -116,7 +116,7 @@ class XMLFile extends XMLParent, File {
|
||||
XMLFile() { xmlEncoding(this, _) }
|
||||
|
||||
/** Gets a printable representation of this XML file. */
|
||||
override string toString() { result = XMLParent.super.toString() }
|
||||
override string toString() { result = getName() }
|
||||
|
||||
/** Gets the name of this XML file. */
|
||||
override string getName() { result = File.super.getAbsolutePath() }
|
||||
@@ -236,7 +236,7 @@ class XMLElement extends @xmlelement, XMLParent, XMLLocatable {
|
||||
string getAttributeValue(string name) { result = this.getAttribute(name).getValue() }
|
||||
|
||||
/** Gets a printable representation of this XML element. */
|
||||
override string toString() { result = XMLParent.super.toString() }
|
||||
override string toString() { result = getName() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -151,11 +151,14 @@ private module CachedSteps {
|
||||
) {
|
||||
calls(invk, f) and
|
||||
(
|
||||
exists(int i, Parameter p |
|
||||
f.getParameter(i) = p and
|
||||
not p.isRestParameter() and
|
||||
arg = invk.getArgument(i) and
|
||||
parm = DataFlow::parameterNode(p)
|
||||
exists(int i | arg = invk.getArgument(i) |
|
||||
exists(Parameter p |
|
||||
f.getParameter(i) = p and
|
||||
not p.isRestParameter() and
|
||||
parm = DataFlow::parameterNode(p)
|
||||
)
|
||||
or
|
||||
parm = reflectiveParameterAccess(f, i)
|
||||
)
|
||||
or
|
||||
arg = invk.(DataFlow::CallNode).getReceiver() and
|
||||
@@ -185,6 +188,22 @@ private module CachedSteps {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data-flow node inside `f` that refers to the `arguments` object of `f`.
|
||||
*/
|
||||
private DataFlow::Node argumentsAccess(Function f) {
|
||||
result.getContainer().getEnclosingContainer*() = f and
|
||||
result.analyze().getAValue().(AbstractArguments).getFunction() = f
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data-flow node that refers to the `i`th parameter of `f` through its `arguments`
|
||||
* object.
|
||||
*/
|
||||
private DataFlow::SourceNode reflectiveParameterAccess(Function f, int i) {
|
||||
result.(DataFlow::PropRead).accesses(argumentsAccess(f), any(string p | i = p.toInt()))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a flow step from `pred` to `succ` through parameter passing
|
||||
* to a function call.
|
||||
|
||||
291
javascript/ql/src/semmle/javascript/frameworks/Fastify.qll
Normal file
291
javascript/ql/src/semmle/javascript/frameworks/Fastify.qll
Normal file
@@ -0,0 +1,291 @@
|
||||
/**
|
||||
* Provides classes for working with [Fastify](https://www.fastify.io/) applications.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.frameworks.HTTP
|
||||
|
||||
/**
|
||||
* Provides classes for working with [Fastify](https://www.fastify.io/) applications.
|
||||
*/
|
||||
module Fastify {
|
||||
/**
|
||||
* An expression that creates a new Fastify server.
|
||||
*/
|
||||
abstract class ServerDefinition extends HTTP::Servers::StandardServerDefinition { }
|
||||
|
||||
/**
|
||||
* A standard way to create a Fastify server.
|
||||
*/
|
||||
class StandardServerDefinition extends ServerDefinition {
|
||||
StandardServerDefinition() {
|
||||
this = DataFlow::moduleImport("fastify").getAnInvocation().asExpr()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A function used as a Fastify route handler.
|
||||
*
|
||||
* By default, only handlers installed by a Fastify route setup are recognized,
|
||||
* but support for other kinds of route handlers can be added by implementing
|
||||
* additional subclasses of this class.
|
||||
*/
|
||||
abstract class RouteHandler extends HTTP::Servers::StandardRouteHandler, DataFlow::ValueNode {
|
||||
/**
|
||||
* Gets the parameter of the route handler that contains the request object.
|
||||
*/
|
||||
abstract DataFlow::ParameterNode getRequestParameter();
|
||||
|
||||
/**
|
||||
* Gets the parameter of the route handler that contains the reply object.
|
||||
*/
|
||||
abstract DataFlow::ParameterNode getReplyParameter();
|
||||
}
|
||||
|
||||
/**
|
||||
* A Fastify route handler installed by a route setup.
|
||||
*/
|
||||
class StandardRouteHandler extends RouteHandler, DataFlow::FunctionNode {
|
||||
StandardRouteHandler() { this = any(RouteSetup setup).getARouteHandler() }
|
||||
|
||||
override DataFlow::ParameterNode getRequestParameter() { result = this.getParameter(0) }
|
||||
|
||||
override DataFlow::ParameterNode getReplyParameter() { result = this.getParameter(1) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A Fastify reply source, that is, the `reply` parameter of a
|
||||
* route handler.
|
||||
*/
|
||||
private class ReplySource extends HTTP::Servers::ResponseSource {
|
||||
RouteHandler rh;
|
||||
|
||||
ReplySource() { this = rh.getReplyParameter() }
|
||||
|
||||
/**
|
||||
* Gets the route handler that provides this response.
|
||||
*/
|
||||
override RouteHandler getRouteHandler() { result = rh }
|
||||
}
|
||||
|
||||
/**
|
||||
* A Fastify request source, that is, the request parameter of a
|
||||
* route handler.
|
||||
*/
|
||||
private class RequestSource extends HTTP::Servers::RequestSource {
|
||||
RouteHandler rh;
|
||||
|
||||
RequestSource() { this = rh.getRequestParameter() }
|
||||
|
||||
/**
|
||||
* Gets the route handler that handles this request.
|
||||
*/
|
||||
override RouteHandler getRouteHandler() { result = rh }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a Fastify method that sets up a route.
|
||||
*/
|
||||
class RouteSetup extends MethodCallExpr, HTTP::Servers::StandardRouteSetup {
|
||||
ServerDefinition server;
|
||||
string methodName;
|
||||
|
||||
RouteSetup() {
|
||||
this.getMethodName() = methodName and
|
||||
methodName = ["route", "get", "head", "post", "put", "delete", "options", "patch"] and
|
||||
server.flowsTo(this.getReceiver())
|
||||
}
|
||||
|
||||
override DataFlow::SourceNode getARouteHandler() {
|
||||
result = getARouteHandler(DataFlow::TypeBackTracker::end())
|
||||
}
|
||||
|
||||
private DataFlow::SourceNode getARouteHandler(DataFlow::TypeBackTracker t) {
|
||||
t.start() and
|
||||
result = this.getARouteHandlerExpr().getALocalSource()
|
||||
or
|
||||
exists(DataFlow::TypeBackTracker t2 | result = this.getARouteHandler(t2).backtrack(t2, t))
|
||||
}
|
||||
|
||||
override Expr getServer() { result = server }
|
||||
|
||||
/** Gets an argument that represents a route handler being registered. */
|
||||
private DataFlow::Node getARouteHandlerExpr() {
|
||||
if methodName = "route"
|
||||
then
|
||||
result =
|
||||
this
|
||||
.flow()
|
||||
.(DataFlow::MethodCallNode)
|
||||
.getOptionArgument(0,
|
||||
["onRequest", "preParsing", "preValidation", "preHandler", "preSerialization",
|
||||
"onSend", "onResponse", "handler"])
|
||||
else result = getLastArgument().flow()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An access to a user-controlled Fastify request input.
|
||||
*/
|
||||
private class RequestInputAccess extends HTTP::RequestInputAccess {
|
||||
RouteHandler rh;
|
||||
string kind;
|
||||
|
||||
RequestInputAccess() {
|
||||
exists(string name | this = rh.getARequestSource().ref().getAPropertyRead(name) |
|
||||
kind = "parameter" and
|
||||
name = ["params", "query"]
|
||||
or
|
||||
kind = "body" and
|
||||
name = "body"
|
||||
)
|
||||
}
|
||||
|
||||
override RouteHandler getRouteHandler() { result = rh }
|
||||
|
||||
override string getKind() { result = kind }
|
||||
|
||||
override predicate isUserControlledObject() {
|
||||
kind = "body" and
|
||||
(
|
||||
usesFastifyPlugin(rh,
|
||||
DataFlow::moduleImport(["fastify-xml-body-parser", "fastify-formbody"]))
|
||||
or
|
||||
usesMiddleware(rh,
|
||||
any(ExpressLibraries::BodyParser bodyParser | bodyParser.producesUserControlledObjects()))
|
||||
)
|
||||
or
|
||||
kind = "parameter" and
|
||||
usesFastifyPlugin(rh, DataFlow::moduleImport("fastify-qs"))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `rh` uses `plugin`.
|
||||
*/
|
||||
private predicate usesFastifyPlugin(RouteHandler rh, DataFlow::SourceNode plugin) {
|
||||
exists(RouteSetup setup |
|
||||
plugin
|
||||
.flowsTo(setup
|
||||
.getServer()
|
||||
.flow()
|
||||
.(DataFlow::SourceNode)
|
||||
.getAMethodCall("register")
|
||||
.getArgument(0)) and // only matches the plugins that apply to all routes
|
||||
rh = setup.getARouteHandler()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `rh` uses `plugin`.
|
||||
*/
|
||||
private predicate usesMiddleware(RouteHandler rh, DataFlow::SourceNode middleware) {
|
||||
exists(RouteSetup setup |
|
||||
middleware
|
||||
.flowsTo(setup
|
||||
.getServer()
|
||||
.flow()
|
||||
.(DataFlow::SourceNode)
|
||||
.getAMethodCall("use")
|
||||
.getArgument(0)) and // only matches the middlewares that apply to all routes
|
||||
rh = setup.getARouteHandler()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* An access to a header on a Fastify request.
|
||||
*/
|
||||
private class RequestHeaderAccess extends HTTP::RequestHeaderAccess {
|
||||
RouteHandler rh;
|
||||
|
||||
RequestHeaderAccess() {
|
||||
this = rh.getARequestSource().ref().getAPropertyRead("headers").getAPropertyRead()
|
||||
}
|
||||
|
||||
override string getAHeaderName() {
|
||||
result = this.(DataFlow::PropRead).getPropertyName().toLowerCase()
|
||||
}
|
||||
|
||||
override RouteHandler getRouteHandler() { result = rh }
|
||||
|
||||
override string getKind() { result = "header" }
|
||||
}
|
||||
|
||||
/**
|
||||
* An argument passed to the `send` or `end` method of an HTTP response object.
|
||||
*/
|
||||
private class ResponseSendArgument extends HTTP::ResponseSendArgument {
|
||||
RouteHandler rh;
|
||||
|
||||
ResponseSendArgument() {
|
||||
this = rh.getAResponseSource().ref().getAMethodCall("send").getArgument(0).asExpr()
|
||||
or
|
||||
this = rh.(DataFlow::FunctionNode).getAReturn().asExpr()
|
||||
}
|
||||
|
||||
override RouteHandler getRouteHandler() { result = rh }
|
||||
}
|
||||
|
||||
/**
|
||||
* An invocation of the `redirect` method of an HTTP response object.
|
||||
*/
|
||||
private class RedirectInvocation extends HTTP::RedirectInvocation, MethodCallExpr {
|
||||
RouteHandler rh;
|
||||
|
||||
RedirectInvocation() {
|
||||
this = rh.getAResponseSource().ref().getAMethodCall("redirect").asExpr()
|
||||
}
|
||||
|
||||
override Expr getUrlArgument() { result = this.getLastArgument() }
|
||||
|
||||
override RouteHandler getRouteHandler() { result = rh }
|
||||
}
|
||||
|
||||
/**
|
||||
* An invocation that sets a single header of the HTTP response.
|
||||
*/
|
||||
private class SetOneHeader extends HTTP::Servers::StandardHeaderDefinition,
|
||||
DataFlow::MethodCallNode {
|
||||
RouteHandler rh;
|
||||
|
||||
SetOneHeader() {
|
||||
this = rh.getAResponseSource().ref().getAMethodCall("header") and
|
||||
this.getNumArgument() = 2
|
||||
}
|
||||
|
||||
override RouteHandler getRouteHandler() { result = rh }
|
||||
}
|
||||
|
||||
/**
|
||||
* An invocation that sets any number of headers of the HTTP response.
|
||||
*/
|
||||
class SetMultipleHeaders extends HTTP::ExplicitHeaderDefinition, DataFlow::MethodCallNode {
|
||||
RouteHandler rh;
|
||||
|
||||
SetMultipleHeaders() {
|
||||
this = rh.getAResponseSource().ref().getAMethodCall("headers") and
|
||||
this.getNumArgument() = 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the multiple headers object that is to be set.
|
||||
*/
|
||||
private DataFlow::SourceNode getAHeaderSource() { result.flowsTo(this.getArgument(0)) }
|
||||
|
||||
override predicate definesExplicitly(string headerName, Expr headerValue) {
|
||||
exists(string header |
|
||||
getAHeaderSource().hasPropertyWrite(header, headerValue.flow()) and
|
||||
headerName = header.toLowerCase()
|
||||
)
|
||||
}
|
||||
|
||||
override RouteHandler getRouteHandler() { result = rh }
|
||||
|
||||
override Expr getNameExpr() {
|
||||
exists(DataFlow::PropWrite write |
|
||||
this.getAHeaderSource().flowsTo(write.getBase()) and
|
||||
result = write.getPropertyNameExpr()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,3 +4,4 @@ import semmle.javascript.frameworks.Koa
|
||||
import semmle.javascript.frameworks.NodeJSLib
|
||||
import semmle.javascript.frameworks.Restify
|
||||
import semmle.javascript.frameworks.Connect
|
||||
import semmle.javascript.frameworks.Fastify
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
| arguments.js:11:5:11:14 | f(1, 2, 3) | arguments.js:11:7:11:7 | 1 | arguments.js:2:5:10:5 | functio ... ;\\n } | arguments.js:2:16:2:16 | x |
|
||||
| arguments.js:11:5:11:14 | f(1, 2, 3) | arguments.js:11:7:11:7 | 1 | arguments.js:2:5:10:5 | functio ... ;\\n } | arguments.js:4:28:4:39 | arguments[0] |
|
||||
| arguments.js:11:5:11:14 | f(1, 2, 3) | arguments.js:11:10:11:10 | 2 | arguments.js:2:5:10:5 | functio ... ;\\n } | arguments.js:5:25:5:36 | arguments[1] |
|
||||
| arguments.js:11:5:11:14 | f(1, 2, 3) | arguments.js:11:13:11:13 | 3 | arguments.js:2:5:10:5 | functio ... ;\\n } | arguments.js:7:24:7:30 | args[2] |
|
||||
| sources.js:3:1:5:6 | (functi ... \\n})(23) | sources.js:5:4:5:5 | 23 | sources.js:3:2:5:1 | functio ... x+19;\\n} | sources.js:3:11:3:11 | x |
|
||||
| tst.js:16:1:20:9 | (functi ... ("arg") | tst.js:20:4:20:8 | "arg" | tst.js:16:2:20:1 | functio ... n "";\\n} | tst.js:16:13:16:13 | a |
|
||||
| tst.js:35:1:35:7 | g(true) | tst.js:35:3:35:6 | true | tst.js:32:1:34:1 | functio ... ables\\n} | tst.js:32:12:32:12 | b |
|
||||
| tst.js:44:1:44:5 | o.m() | tst.js:44:1:44:1 | o | tst.js:39:4:41:3 | () {\\n this;\\n } | tst.js:39:4:39:3 | this |
|
||||
| tst.js:87:1:96:2 | (functi ... r: 0\\n}) | tst.js:92:4:96:1 | {\\n p: ... r: 0\\n} | tst.js:87:2:92:1 | functio ... + z;\\n} | tst.js:87:11:87:24 | { p: x, ...o } |
|
||||
| tst.js:98:1:103:17 | (functi ... 3, 0 ]) | tst.js:103:4:103:16 | [ 19, 23, 0 ] | tst.js:98:2:103:1 | functio ... + z;\\n} | tst.js:98:11:98:24 | [ x, ...rest ] |
|
||||
@@ -0,0 +1,6 @@
|
||||
import javascript
|
||||
import semmle.javascript.dataflow.internal.FlowSteps as FlowSteps
|
||||
|
||||
from DataFlow::Node invk, DataFlow::Node arg, Function f, DataFlow::SourceNode parm
|
||||
where FlowSteps::argumentPassing(invk, arg, f, parm)
|
||||
select invk, arg, f, parm
|
||||
12
javascript/ql/test/library-tests/DataFlow/arguments.js
Normal file
12
javascript/ql/test/library-tests/DataFlow/arguments.js
Normal file
@@ -0,0 +1,12 @@
|
||||
(function() {
|
||||
function f(x) {
|
||||
let firstArg = x;
|
||||
let alsoFirstArg = arguments[0];
|
||||
let secondArg = arguments[1];
|
||||
let args = arguments;
|
||||
let thirdArg = args[2];
|
||||
arguments = {};
|
||||
let notFirstArg = arguments[0];
|
||||
}
|
||||
f(1, 2, 3);
|
||||
})();
|
||||
@@ -1,3 +1,42 @@
|
||||
| arguments.js:1:1:12:2 | (functi ... 3);\\n}) | arguments.js:1:1:12:2 | (functi ... 3);\\n}) |
|
||||
| arguments.js:1:1:12:4 | (functi ... );\\n})() | arguments.js:1:1:12:4 | (functi ... );\\n})() |
|
||||
| arguments.js:1:2:12:1 | functio ... , 3);\\n} | arguments.js:1:2:12:1 | functio ... , 3);\\n} |
|
||||
| arguments.js:2:14:2:14 | f | arguments.js:2:14:2:14 | f |
|
||||
| arguments.js:2:16:2:16 | x | arguments.js:2:16:2:16 | x |
|
||||
| arguments.js:3:13:3:20 | firstArg | arguments.js:3:13:3:20 | firstArg |
|
||||
| arguments.js:3:13:3:24 | firstArg = x | arguments.js:3:13:3:24 | firstArg = x |
|
||||
| arguments.js:3:24:3:24 | x | arguments.js:3:24:3:24 | x |
|
||||
| arguments.js:4:13:4:24 | alsoFirstArg | arguments.js:4:13:4:24 | alsoFirstArg |
|
||||
| arguments.js:4:13:4:39 | alsoFir ... ents[0] | arguments.js:4:13:4:39 | alsoFir ... ents[0] |
|
||||
| arguments.js:4:28:4:36 | arguments | arguments.js:4:28:4:36 | arguments |
|
||||
| arguments.js:4:28:4:39 | arguments[0] | arguments.js:4:28:4:39 | arguments[0] |
|
||||
| arguments.js:4:38:4:38 | 0 | arguments.js:4:38:4:38 | 0 |
|
||||
| arguments.js:5:13:5:21 | secondArg | arguments.js:5:13:5:21 | secondArg |
|
||||
| arguments.js:5:13:5:36 | secondA ... ents[1] | arguments.js:5:13:5:36 | secondA ... ents[1] |
|
||||
| arguments.js:5:25:5:33 | arguments | arguments.js:5:25:5:33 | arguments |
|
||||
| arguments.js:5:25:5:36 | arguments[1] | arguments.js:5:25:5:36 | arguments[1] |
|
||||
| arguments.js:5:35:5:35 | 1 | arguments.js:5:35:5:35 | 1 |
|
||||
| arguments.js:6:13:6:16 | args | arguments.js:6:13:6:16 | args |
|
||||
| arguments.js:6:13:6:28 | args = arguments | arguments.js:6:13:6:28 | args = arguments |
|
||||
| arguments.js:6:20:6:28 | arguments | arguments.js:6:20:6:28 | arguments |
|
||||
| arguments.js:7:13:7:20 | thirdArg | arguments.js:7:13:7:20 | thirdArg |
|
||||
| arguments.js:7:13:7:30 | thirdArg = args[2] | arguments.js:7:13:7:30 | thirdArg = args[2] |
|
||||
| arguments.js:7:24:7:27 | args | arguments.js:7:24:7:27 | args |
|
||||
| arguments.js:7:24:7:30 | args[2] | arguments.js:7:24:7:30 | args[2] |
|
||||
| arguments.js:7:29:7:29 | 2 | arguments.js:7:29:7:29 | 2 |
|
||||
| arguments.js:8:9:8:17 | arguments | arguments.js:8:9:8:17 | arguments |
|
||||
| arguments.js:8:9:8:22 | arguments = {} | arguments.js:8:9:8:22 | arguments = {} |
|
||||
| arguments.js:8:21:8:22 | {} | arguments.js:8:21:8:22 | {} |
|
||||
| arguments.js:9:13:9:23 | notFirstArg | arguments.js:9:13:9:23 | notFirstArg |
|
||||
| arguments.js:9:13:9:38 | notFirs ... ents[0] | arguments.js:9:13:9:38 | notFirs ... ents[0] |
|
||||
| arguments.js:9:27:9:35 | arguments | arguments.js:9:27:9:35 | arguments |
|
||||
| arguments.js:9:27:9:38 | arguments[0] | arguments.js:9:27:9:38 | arguments[0] |
|
||||
| arguments.js:9:37:9:37 | 0 | arguments.js:9:37:9:37 | 0 |
|
||||
| arguments.js:11:5:11:5 | f | arguments.js:11:5:11:5 | f |
|
||||
| arguments.js:11:5:11:14 | f(1, 2, 3) | arguments.js:11:5:11:14 | f(1, 2, 3) |
|
||||
| arguments.js:11:7:11:7 | 1 | arguments.js:11:7:11:7 | 1 |
|
||||
| arguments.js:11:10:11:10 | 2 | arguments.js:11:10:11:10 | 2 |
|
||||
| arguments.js:11:13:11:13 | 3 | arguments.js:11:13:11:13 | 3 |
|
||||
| eval.js:1:10:1:10 | k | eval.js:1:10:1:10 | k |
|
||||
| eval.js:2:7:2:7 | x | eval.js:2:7:2:7 | x |
|
||||
| eval.js:2:7:2:12 | x = 42 | eval.js:2:7:2:12 | x = 42 |
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
| arguments.js:1:2:12:1 | functio ... , 3);\\n} | arguments.js:1:1:12:2 | (functi ... 3);\\n}) |
|
||||
| arguments.js:2:5:2:5 | arguments | arguments.js:4:28:4:36 | arguments |
|
||||
| arguments.js:2:5:2:5 | arguments | arguments.js:5:25:5:33 | arguments |
|
||||
| arguments.js:2:5:2:5 | arguments | arguments.js:6:20:6:28 | arguments |
|
||||
| arguments.js:2:5:10:5 | functio ... ;\\n } | arguments.js:2:14:2:14 | f |
|
||||
| arguments.js:2:14:2:14 | f | arguments.js:11:5:11:5 | f |
|
||||
| arguments.js:2:16:2:16 | x | arguments.js:2:16:2:16 | x |
|
||||
| arguments.js:2:16:2:16 | x | arguments.js:3:24:3:24 | x |
|
||||
| arguments.js:6:13:6:28 | args | arguments.js:7:24:7:27 | args |
|
||||
| arguments.js:6:20:6:28 | arguments | arguments.js:6:13:6:28 | args |
|
||||
| arguments.js:8:9:8:22 | arguments | arguments.js:9:27:9:35 | arguments |
|
||||
| arguments.js:8:21:8:22 | {} | arguments.js:8:9:8:22 | arguments |
|
||||
| arguments.js:8:21:8:22 | {} | arguments.js:8:9:8:22 | arguments = {} |
|
||||
| eval.js:2:7:2:12 | x | eval.js:4:3:4:3 | x |
|
||||
| eval.js:2:11:2:12 | 42 | eval.js:2:7:2:12 | x |
|
||||
| sources.js:1:6:1:6 | x | sources.js:1:6:1:6 | x |
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
| arguments.js:4:38:4:38 | 0 | 0 |
|
||||
| arguments.js:5:35:5:35 | 1 | 1 |
|
||||
| arguments.js:7:29:7:29 | 2 | 2 |
|
||||
| arguments.js:9:37:9:37 | 0 | 0 |
|
||||
| arguments.js:11:7:11:7 | 1 | 1 |
|
||||
| arguments.js:11:10:11:10 | 2 | 2 |
|
||||
| arguments.js:11:13:11:13 | 3 | 3 |
|
||||
| eval.js:2:11:2:12 | 42 | 42 |
|
||||
| sources.js:4:12:4:13 | 19 | 19 |
|
||||
| sources.js:5:4:5:5 | 23 | 23 |
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
| arguments.js:1:1:12:4 | exceptional return of (functi ... );\\n})() | call |
|
||||
| arguments.js:1:2:12:1 | exceptional return of anonymous function | call |
|
||||
| arguments.js:2:5:10:5 | exceptional return of function f | call |
|
||||
| arguments.js:2:16:2:16 | x | call |
|
||||
| arguments.js:4:28:4:39 | arguments[0] | heap |
|
||||
| arguments.js:5:25:5:36 | arguments[1] | heap |
|
||||
| arguments.js:7:24:7:30 | args[2] | heap |
|
||||
| arguments.js:9:27:9:38 | arguments[0] | heap |
|
||||
| arguments.js:11:5:11:14 | exceptional return of f(1, 2, 3) | call |
|
||||
| arguments.js:11:5:11:14 | f(1, 2, 3) | call |
|
||||
| eval.js:1:1:5:1 | exceptional return of function k | call |
|
||||
| eval.js:2:7:2:12 | x | eval |
|
||||
| eval.js:3:3:3:6 | eval | global |
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
| arguments.js:2:16:2:16 | x |
|
||||
| sources.js:1:6:1:6 | x |
|
||||
| sources.js:3:11:3:11 | x |
|
||||
| sources.js:9:14:9:18 | array |
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
| arguments.js:1:1:1:0 | this |
|
||||
| arguments.js:1:1:12:4 | (functi ... );\\n})() |
|
||||
| arguments.js:1:2:1:1 | this |
|
||||
| arguments.js:1:2:12:1 | functio ... , 3);\\n} |
|
||||
| arguments.js:2:5:2:4 | this |
|
||||
| arguments.js:2:5:10:5 | functio ... ;\\n } |
|
||||
| arguments.js:2:16:2:16 | x |
|
||||
| arguments.js:4:28:4:39 | arguments[0] |
|
||||
| arguments.js:5:25:5:36 | arguments[1] |
|
||||
| arguments.js:7:24:7:30 | args[2] |
|
||||
| arguments.js:8:21:8:22 | {} |
|
||||
| arguments.js:9:27:9:38 | arguments[0] |
|
||||
| arguments.js:11:5:11:14 | f(1, 2, 3) |
|
||||
| eval.js:1:1:1:0 | this |
|
||||
| eval.js:1:1:1:0 | this |
|
||||
| eval.js:1:1:5:1 | functio ... eval`\\n} |
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
| d.js:7:1:7:14 | require('foo') |
|
||||
| e.js:5:1:5:18 | require("process") |
|
||||
| f.js:2:1:2:7 | r("fs") |
|
||||
| g.js:1:1:1:96 | (proces ... https") |
|
||||
| g.js:1:43:1:61 | require("electron") |
|
||||
| index.js:1:12:1:26 | require('path') |
|
||||
| index.js:2:1:2:41 | require ... b.js")) |
|
||||
| mjs-files/require-from-js.js:1:12:1:36 | require ... on-me') |
|
||||
|
||||
1
javascript/ql/test/library-tests/NodeJS/g.js
Normal file
1
javascript/ql/test/library-tests/NodeJS/g.js
Normal file
@@ -0,0 +1 @@
|
||||
(process && "renderer" === process.type ? require("electron").remote.require : require)("https");
|
||||
@@ -31,6 +31,7 @@ typeInferenceMismatch
|
||||
| callbacks.js:44:17:44:24 | source() | callbacks.js:41:10:41:10 | x |
|
||||
| callbacks.js:50:18:50:25 | source() | callbacks.js:30:29:30:29 | y |
|
||||
| callbacks.js:51:18:51:25 | source() | callbacks.js:30:29:30:29 | y |
|
||||
| capture-flow.js:9:11:9:18 | source() | capture-flow.js:14:10:14:16 | outer() |
|
||||
| captured-sanitizer.js:25:3:25:10 | source() | captured-sanitizer.js:15:10:15:10 | x |
|
||||
| closure.js:6:15:6:22 | source() | closure.js:8:8:8:31 | string. ... (taint) |
|
||||
| closure.js:6:15:6:22 | source() | closure.js:9:8:9:25 | string.trim(taint) |
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user