Merge remote-tracking branch 'origin/main' into nickrolfe/regex_injection

This commit is contained in:
Nick Rolfe
2021-11-03 11:55:42 +00:00
326 changed files with 7481 additions and 3231 deletions

View File

@@ -1,64 +0,0 @@
## Contributing
Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great.
Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [project's open source license](LICENSE).
Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.
## Building and testing
See [Developer information](docs/HOWTO.md) for information on building the Ruby extractor. There is no need to rebuild the extractor if you are only developing queries.
1. Install the CodeQL CLI as described in [Getting started with the CodeQL CLI](https://codeql.github.com/docs/codeql-cli/getting-started-with-the-codeql-cli/).
2. Ensure that `<extraction-root>/codeql` is in your `PATH`.
3. Clone this repository into `<extraction-root>/codeql-ruby` and change to this directory.
4. To run all tests in a directory and its subdirectories, run `codeql test run <directory>`, for example `codeql test run ql/test/query-tests/security`.
6. To run an individual test, run `codeql test run <filename>`, where `<filename>` is a `.ql` or `.qlref` file, for example `codeql test run ql/test/query-tests/security/cwe-078/CommandInjection.qlref`.
## Adding a new query
If you have an idea for a query that you would like to share with other CodeQL users, please open a pull request to add it to this repository.
Follow the steps below to help other users understand what your query does, and to ensure that your query is consistent with the other CodeQL queries.
1. **Consult the documentation for query writers**
There is lots of useful documentation to help you write CodeQL queries, ranging from information about query file structure to language-specific tutorials. For more information on the documentation available, see [Writing CodeQL queries](https://codeql.github.com/docs/writing-codeql-queries/) and the [CodeQL documentation](https://codeql.github.com/docs).
2. **Format your code correctly**
All of the standard CodeQL queries and libraries are uniformly formatted for clarity and consistency, so we strongly recommend that all contributions follow the same formatting guidelines. If you use the CodeQL extension for Visual Studio Code, you can auto-format your query using the [Format Document command](https://code.visualstudio.com/docs/editor/codebasics#_formatting). For more information, see the [QL style guide](https://github.com/github/codeql/blob/main/docs/ql-style-guide.md).
3. **Make sure your query has the correct metadata**
Query metadata is used to identify your query and make sure the query results are displayed properly.
The most important metadata to include are the `@name`, `@description`, and the `@kind`.
Other metadata properties (`@precision`, `@severity`, and `@tags`) are usually added after the query has been reviewed by the maintainers.
For more information on writing query metadata, see the [Query metadata style guide](https://github.com/github/codeql/blob/main/docs/query-metadata-style-guide.md).
4. **Make sure the `select` statement is compatible with the query type**
The `select` statement of your query must be compatible with the query type (determined by the `@kind` metadata property) for alert or path results to be displayed correctly in LGTM and Visual Studio Code.
For more information on `select` statement format, see [About CodeQL queries](https://codeql.github.com/docs/writing-codeql-queries/about-codeql-queries/#select-clause) on the [CodeQL documentation](https://codeql.github.com/docs) site.
5. **Write a query help file**
Query help files explain the purpose of your query to other users. Write your query help in a `.qhelp` file and save it in the same directory as your new query.
For more information on writing query help, see the [Query help style guide](https://github.com/github/codeql/blob/main/docs/query-help-style-guide.md).
6. **Maintain backwards compatibility**
The standard CodeQL libraries must evolve in a backwards compatible manner. If any backwards incompatible changes need to be made, the existing API must first be marked as deprecated. This is done by adding a `deprecated` annotation along with a QLDoc reference to the replacement API. Only after at least one full release cycle has elapsed may the old API be removed.
In addition to contributions to our standard queries and libraries, we also welcome contributions of a more experimental nature, which do not need to fulfill all the requirements listed above. See the guidelines for [experimental queries and libraries](ql/docs/experimental.md) for details.
## Resources
- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/)
- [GitHub Help](https://help.github.com)
- [A Note About Git Commit Messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)

BIN
ruby/Cargo.lock generated

Binary file not shown.

View File

@@ -1,50 +1,14 @@
# Ruby analysis support for CodeQL
This open-source repository contains the extractor, CodeQL libraries, and queries that power Ruby
This directory contains the extractor, CodeQL libraries, and queries that power Ruby
support in [LGTM](https://lgtm.com) and the other CodeQL products that [GitHub](https://github.com)
makes available to its customers worldwide.
It contains two major components:
- an extractor, written in Rust, that parses Ruby source code and converts it into a database
that can be queried using CodeQL.
- static analysis libraries and queries written in [CodeQL](https://codeql.github.com/docs/) that can be
used to analyze such a database to find coding mistakes or security vulnerabilities.
The goal of this project is to provide comprehensive static analysis support for Ruby in CodeQL.
For the queries and libraries that power CodeQL support for other languages, visit [the CodeQL
repository](https://github.com/github/codeql).
## Installation
Simply clone this repository. There are no external dependencies.
If you want to use the CodeQL extension for Visual Studio Code, import this repository into your VS
Code workspace.
## Usage
To analyze a Ruby codebase, either use the [CodeQL command-line
interface](https://codeql.github.com/docs/codeql-cli/) to create a database yourself, or
download a pre-built database from [LGTM.com](https://lgtm.com/). You can then run any of the
queries contained in this repository either on the command line or using the VS Code extension.
Note that the [lgtm.com](https://github.com/github/codeql-ruby/tree/lgtm.com) branch of this
repository corresponds to the version of the queries that is currently deployed on LGTM.com.
The [main](https://github.com/github/codeql-ruby/tree/main) branch may contain changes that
have not been deployed yet, so you may need to upgrade databases downloaded from [LGTM.com](https://lgtm.com) before
running queries on them.
## Contributions
Contributions are welcome! Please see our [contribution guidelines](CONTRIBUTING.md) and our
[code of conduct](CODE_OF_CONDUCT.md) for details on how to participate in our community.
## Licensing
The code in this repository is licensed under the [MIT license](LICENSE).
## Resources
- [Writing CodeQL queries](https://codeql.github.com/docs/writing-codeql-queries/)
- [CodeQL documentation](https://codeql.github.com/docs/)
1. static analysis libraries and queries written in
[CodeQL](https://codeql.github.com/docs/) that can be used to analyze such
a database to find coding mistakes or security vulnerabilities.
2. an extractor, written in Rust, that parses Ruby source code and converts it
into a database that can be queried using CodeQL. See [Developer
information](doc/HOWTO.md) for information on building the extractor (you
do not need to do this if you are only developing queries).

View File

@@ -14,7 +14,7 @@ tree-sitter-embedded-template = "0.19"
tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "bb6a42e42b048627a74a127d3e0184c1eef01de9" }
clap = "2.33"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing-subscriber = { version = "0.2", features = ["env-filter"] }
rayon = "1.5.0"
num_cpus = "1.13.0"
regex = "1.4.3"

View File

@@ -10,6 +10,6 @@ edition = "2018"
clap = "2.33"
node-types = { path = "../node-types" }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing-subscriber = { version = "0.2", features = ["env-filter"] }
tree-sitter-embedded-template = "0.19"
tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "bb6a42e42b048627a74a127d3e0184c1eef01de9" }

View File

@@ -2,7 +2,7 @@
* @name If statements with empty then branch
* @description Finds 'if' statements where the 'then' branch is
* an empty block statement
* @id ruby/examples/emptythen
* @id rb/examples/emptythen
* @tags if
* then
* empty

View File

@@ -74,7 +74,7 @@ class AstCfgNode extends CfgNode, TElementNode {
override Location getLocation() { result = n.getLocation() }
final override string toString() {
exists(string s | s = n.(AstNode).toString() |
exists(string s | s = n.toString() |
result = "[" + this.getSplitsString() + "] " + s
or
not exists(this.getSplitsString()) and result = s
@@ -108,6 +108,7 @@ class ExprCfgNode extends AstCfgNode {
}
/** Gets the textual (constant) value of this expression, if any. */
cached
string getValueText() { result = this.getSource().getValueText() }
}
@@ -247,7 +248,18 @@ module ExprNodes {
result = (left.toFloat() + right.toFloat()).toString()
or
not (exists(left.toFloat()) and exists(right.toFloat())) and
result = left + right
exists(int l, int r, int limit |
l = left.length() and
r = right.length() and
limit = 10000
|
if l > limit
then result = left.prefix(limit) + "..."
else
if l + r > limit
then result = left + right.prefix(limit - l) + "..."
else result = left + right
)
)
or
op = "-" and

View File

@@ -10,6 +10,7 @@
private import DataFlowImplCommon
private import DataFlowImplSpecific::Private
import DataFlowImplSpecific::Public
import DataFlowImplCommonPublic
/**
* A configuration of interprocedural data flow analysis. This defines
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
*/
int fieldFlowBranchLimit() { result = 2 }
/**
* Gets a data flow configuration feature to add restrictions to the set of
* valid flow paths.
*
* - `FeatureHasSourceCallContext`:
* Assume that sources have some existing call context to disallow
* conflicting return-flow directly following the source.
* - `FeatureHasSinkCallContext`:
* Assume that sinks have some existing call context to disallow
* conflicting argument-to-parameter flow directly preceding the sink.
* - `FeatureEqualSourceSinkCallContext`:
* Implies both of the above and additionally ensures that the entire flow
* path preserves the call context.
*/
FlowFeature getAFeature() { none() }
/**
* Holds if data may flow from `source` to `sink` for this configuration.
*/
@@ -349,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
not fullBarrier(node2, config)
not fullBarrier(node2, config) and
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -365,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
not fullBarrier(node2, config)
not fullBarrier(node2, config) and
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -401,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
*/
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
private predicate hasSourceCallCtx(Configuration config) {
exists(FlowFeature feature | feature = config.getAFeature() |
feature instanceof FeatureHasSourceCallContext or
feature instanceof FeatureEqualSourceSinkCallContext
)
}
private predicate hasSinkCallCtx(Configuration config) {
exists(FlowFeature feature | feature = config.getAFeature() |
feature instanceof FeatureHasSinkCallContext or
feature instanceof FeatureEqualSourceSinkCallContext
)
}
private module Stage1 {
class ApApprox = Unit;
@@ -421,7 +454,7 @@ private module Stage1 {
not fullBarrier(node, config) and
(
sourceNode(node, config) and
cc = false
if hasSourceCallCtx(config) then cc = true else cc = false
or
exists(NodeEx mid |
fwdFlow(mid, cc, config) and
@@ -551,7 +584,7 @@ private module Stage1 {
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
fwdFlow(node, config) and
sinkNode(node, config) and
toReturn = false
if hasSinkCallCtx(config) then toReturn = true else toReturn = false
or
exists(NodeEx mid |
localFlowStep(node, mid, config) and
@@ -937,6 +970,8 @@ private module Stage2 {
Cc ccNone() { result instanceof CallContextAny }
CcCall ccSomeCall() { result instanceof CallContextSomeCall }
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -1004,7 +1039,7 @@ private module Stage2 {
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
cc = ccNone() and
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1215,7 +1250,7 @@ private module Stage2 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
toReturn = false and
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -1616,6 +1651,8 @@ private module Stage3 {
Cc ccNone() { result = false }
CcCall ccSomeCall() { result = true }
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -1697,7 +1734,7 @@ private module Stage3 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
cc = ccNone() and
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1908,7 +1945,7 @@ private module Stage3 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
toReturn = false and
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2366,6 +2403,8 @@ private module Stage4 {
Cc ccNone() { result instanceof CallContextAny }
CcCall ccSomeCall() { result instanceof CallContextSomeCall }
private class LocalCc = LocalCallContext;
bindingset[call, c, outercc]
@@ -2461,7 +2500,7 @@ private module Stage4 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
cc = ccNone() and
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -2672,7 +2711,7 @@ private module Stage4 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
toReturn = false and
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -3064,7 +3103,11 @@ private newtype TPathNode =
// A PathNode is introduced by a source ...
Stage4::revFlow(node, config) and
sourceNode(node, config) and
cc instanceof CallContextAny and
(
if hasSourceCallCtx(config)
then cc instanceof CallContextSomeCall
else cc instanceof CallContextAny
) and
sc instanceof SummaryCtxNone and
ap = TAccessPathNil(node.getDataFlowType())
or
@@ -3076,17 +3119,10 @@ private newtype TPathNode =
)
} or
TPathNodeSink(NodeEx node, Configuration config) {
sinkNode(node, pragma[only_bind_into](config)) and
Stage4::revFlow(node, pragma[only_bind_into](config)) and
(
// A sink that is also a source ...
sourceNode(node, config)
or
// ... or a sink that can be reached from a source
exists(PathNodeMid mid |
pathStep(mid, node, _, _, TAccessPathNil(_)) and
pragma[only_bind_into](config) = mid.getConfiguration()
)
exists(PathNodeMid sink |
sink.isAtSink() and
node = sink.getNodeEx() and
config = sink.getConfiguration()
)
}
@@ -3403,22 +3439,46 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
// an intermediate step to another intermediate node
result = this.getSuccMid()
or
// a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
exists(PathNodeMid mid, PathNodeSink sink |
mid = this.getSuccMid() and
mid.getNodeEx() = sink.getNodeEx() and
mid.getAp() instanceof AccessPathNil and
sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
result = sink
)
// a final step to a sink
result = this.getSuccMid().projectToSink()
}
override predicate isSource() {
sourceNode(node, config) and
cc instanceof CallContextAny and
(
if hasSourceCallCtx(config)
then cc instanceof CallContextSomeCall
else cc instanceof CallContextAny
) and
sc instanceof SummaryCtxNone and
ap instanceof AccessPathNil
}
predicate isAtSink() {
sinkNode(node, config) and
ap instanceof AccessPathNil and
if hasSinkCallCtx(config)
then
// For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
// is exactly what we need to check. This also implies
// `sc instanceof SummaryCtxNone`.
// For `FeatureEqualSourceSinkCallContext` the initial call context was
// set to `CallContextSomeCall` and jumps are disallowed, so
// `cc instanceof CallContextNoCall` never holds. On the other hand,
// in this case there's never any need to enter a call except to identify
// a summary, so the condition in `pathIntoCallable` enforces this, which
// means that `sc instanceof SummaryCtxNone` holds if and only if we are
// in the call context of the source.
sc instanceof SummaryCtxNone or
cc instanceof CallContextNoCall
else any()
}
PathNodeSink projectToSink() {
this.isAtSink() and
result.getNodeEx() = node and
result.getConfiguration() = unbindConf(config)
}
}
/**
@@ -3572,7 +3632,7 @@ private predicate pathIntoArg(
)
}
pragma[noinline]
pragma[nomagic]
private predicate parameterCand(
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
) {
@@ -3613,7 +3673,11 @@ private predicate pathIntoCallable(
sc = TSummaryCtxSome(p, ap)
or
not exists(TSummaryCtxSome(p, ap)) and
sc = TSummaryCtxNone()
sc = TSummaryCtxNone() and
// When the call contexts of source and sink needs to match then there's
// never any reason to enter a callable except to find a summary. See also
// the comment in `PathNodeMid::isAtSink`.
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
|
if recordDataFlowCallSite(call, callable)

View File

@@ -10,6 +10,7 @@
private import DataFlowImplCommon
private import DataFlowImplSpecific::Private
import DataFlowImplSpecific::Public
import DataFlowImplCommonPublic
/**
* A configuration of interprocedural data flow analysis. This defines
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
*/
int fieldFlowBranchLimit() { result = 2 }
/**
* Gets a data flow configuration feature to add restrictions to the set of
* valid flow paths.
*
* - `FeatureHasSourceCallContext`:
* Assume that sources have some existing call context to disallow
* conflicting return-flow directly following the source.
* - `FeatureHasSinkCallContext`:
* Assume that sinks have some existing call context to disallow
* conflicting argument-to-parameter flow directly preceding the sink.
* - `FeatureEqualSourceSinkCallContext`:
* Implies both of the above and additionally ensures that the entire flow
* path preserves the call context.
*/
FlowFeature getAFeature() { none() }
/**
* Holds if data may flow from `source` to `sink` for this configuration.
*/
@@ -349,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
not fullBarrier(node2, config)
not fullBarrier(node2, config) and
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -365,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
not fullBarrier(node2, config)
not fullBarrier(node2, config) and
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -401,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
*/
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
private predicate hasSourceCallCtx(Configuration config) {
exists(FlowFeature feature | feature = config.getAFeature() |
feature instanceof FeatureHasSourceCallContext or
feature instanceof FeatureEqualSourceSinkCallContext
)
}
private predicate hasSinkCallCtx(Configuration config) {
exists(FlowFeature feature | feature = config.getAFeature() |
feature instanceof FeatureHasSinkCallContext or
feature instanceof FeatureEqualSourceSinkCallContext
)
}
private module Stage1 {
class ApApprox = Unit;
@@ -421,7 +454,7 @@ private module Stage1 {
not fullBarrier(node, config) and
(
sourceNode(node, config) and
cc = false
if hasSourceCallCtx(config) then cc = true else cc = false
or
exists(NodeEx mid |
fwdFlow(mid, cc, config) and
@@ -551,7 +584,7 @@ private module Stage1 {
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
fwdFlow(node, config) and
sinkNode(node, config) and
toReturn = false
if hasSinkCallCtx(config) then toReturn = true else toReturn = false
or
exists(NodeEx mid |
localFlowStep(node, mid, config) and
@@ -937,6 +970,8 @@ private module Stage2 {
Cc ccNone() { result instanceof CallContextAny }
CcCall ccSomeCall() { result instanceof CallContextSomeCall }
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -1004,7 +1039,7 @@ private module Stage2 {
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
cc = ccNone() and
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1215,7 +1250,7 @@ private module Stage2 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
toReturn = false and
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -1616,6 +1651,8 @@ private module Stage3 {
Cc ccNone() { result = false }
CcCall ccSomeCall() { result = true }
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -1697,7 +1734,7 @@ private module Stage3 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
cc = ccNone() and
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1908,7 +1945,7 @@ private module Stage3 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
toReturn = false and
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2366,6 +2403,8 @@ private module Stage4 {
Cc ccNone() { result instanceof CallContextAny }
CcCall ccSomeCall() { result instanceof CallContextSomeCall }
private class LocalCc = LocalCallContext;
bindingset[call, c, outercc]
@@ -2461,7 +2500,7 @@ private module Stage4 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
cc = ccNone() and
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -2672,7 +2711,7 @@ private module Stage4 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
toReturn = false and
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -3064,7 +3103,11 @@ private newtype TPathNode =
// A PathNode is introduced by a source ...
Stage4::revFlow(node, config) and
sourceNode(node, config) and
cc instanceof CallContextAny and
(
if hasSourceCallCtx(config)
then cc instanceof CallContextSomeCall
else cc instanceof CallContextAny
) and
sc instanceof SummaryCtxNone and
ap = TAccessPathNil(node.getDataFlowType())
or
@@ -3076,17 +3119,10 @@ private newtype TPathNode =
)
} or
TPathNodeSink(NodeEx node, Configuration config) {
sinkNode(node, pragma[only_bind_into](config)) and
Stage4::revFlow(node, pragma[only_bind_into](config)) and
(
// A sink that is also a source ...
sourceNode(node, config)
or
// ... or a sink that can be reached from a source
exists(PathNodeMid mid |
pathStep(mid, node, _, _, TAccessPathNil(_)) and
pragma[only_bind_into](config) = mid.getConfiguration()
)
exists(PathNodeMid sink |
sink.isAtSink() and
node = sink.getNodeEx() and
config = sink.getConfiguration()
)
}
@@ -3403,22 +3439,46 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
// an intermediate step to another intermediate node
result = this.getSuccMid()
or
// a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
exists(PathNodeMid mid, PathNodeSink sink |
mid = this.getSuccMid() and
mid.getNodeEx() = sink.getNodeEx() and
mid.getAp() instanceof AccessPathNil and
sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
result = sink
)
// a final step to a sink
result = this.getSuccMid().projectToSink()
}
override predicate isSource() {
sourceNode(node, config) and
cc instanceof CallContextAny and
(
if hasSourceCallCtx(config)
then cc instanceof CallContextSomeCall
else cc instanceof CallContextAny
) and
sc instanceof SummaryCtxNone and
ap instanceof AccessPathNil
}
predicate isAtSink() {
sinkNode(node, config) and
ap instanceof AccessPathNil and
if hasSinkCallCtx(config)
then
// For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
// is exactly what we need to check. This also implies
// `sc instanceof SummaryCtxNone`.
// For `FeatureEqualSourceSinkCallContext` the initial call context was
// set to `CallContextSomeCall` and jumps are disallowed, so
// `cc instanceof CallContextNoCall` never holds. On the other hand,
// in this case there's never any need to enter a call except to identify
// a summary, so the condition in `pathIntoCallable` enforces this, which
// means that `sc instanceof SummaryCtxNone` holds if and only if we are
// in the call context of the source.
sc instanceof SummaryCtxNone or
cc instanceof CallContextNoCall
else any()
}
PathNodeSink projectToSink() {
this.isAtSink() and
result.getNodeEx() = node and
result.getConfiguration() = unbindConf(config)
}
}
/**
@@ -3572,7 +3632,7 @@ private predicate pathIntoArg(
)
}
pragma[noinline]
pragma[nomagic]
private predicate parameterCand(
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
) {
@@ -3613,7 +3673,11 @@ private predicate pathIntoCallable(
sc = TSummaryCtxSome(p, ap)
or
not exists(TSummaryCtxSome(p, ap)) and
sc = TSummaryCtxNone()
sc = TSummaryCtxNone() and
// When the call contexts of source and sink needs to match then there's
// never any reason to enter a callable except to find a summary. See also
// the comment in `PathNodeMid::isAtSink`.
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
|
if recordDataFlowCallSite(call, callable)

View File

@@ -2,6 +2,42 @@ private import DataFlowImplSpecific::Private
private import DataFlowImplSpecific::Public
import Cached
module DataFlowImplCommonPublic {
private newtype TFlowFeature =
TFeatureHasSourceCallContext() or
TFeatureHasSinkCallContext() or
TFeatureEqualSourceSinkCallContext()
/** A flow configuration feature for use in `Configuration::getAFeature()`. */
class FlowFeature extends TFlowFeature {
string toString() { none() }
}
/**
* A flow configuration feature that implies that sources have some existing
* call context.
*/
class FeatureHasSourceCallContext extends FlowFeature, TFeatureHasSourceCallContext {
override string toString() { result = "FeatureHasSourceCallContext" }
}
/**
* A flow configuration feature that implies that sinks have some existing
* call context.
*/
class FeatureHasSinkCallContext extends FlowFeature, TFeatureHasSinkCallContext {
override string toString() { result = "FeatureHasSinkCallContext" }
}
/**
* A flow configuration feature that implies that source-sink pairs have some
* shared existing call context.
*/
class FeatureEqualSourceSinkCallContext extends FlowFeature, TFeatureEqualSourceSinkCallContext {
override string toString() { result = "FeatureEqualSourceSinkCallContext" }
}
}
/**
* The cost limits for the `AccessPathFront` to `AccessPathApprox` expansion.
*
@@ -251,7 +287,7 @@ private module Cached {
predicate forceCachingInSameStage() { any() }
cached
predicate nodeEnclosingCallable(Node n, DataFlowCallable c) { c = n.getEnclosingCallable() }
predicate nodeEnclosingCallable(Node n, DataFlowCallable c) { c = nodeGetEnclosingCallable(n) }
cached
predicate callEnclosingCallable(DataFlowCall call, DataFlowCallable c) {
@@ -316,9 +352,7 @@ private module Cached {
}
cached
predicate parameterNode(Node n, DataFlowCallable c, int i) {
n.(ParameterNode).isParameterOf(c, i)
}
predicate parameterNode(Node p, DataFlowCallable c, int pos) { isParameterNode(p, c, pos) }
cached
predicate argumentNode(Node n, DataFlowCall call, int pos) {

View File

@@ -31,7 +31,7 @@ module Consistency {
query predicate uniqueEnclosingCallable(Node n, string msg) {
exists(int c |
n instanceof RelevantNode and
c = count(n.getEnclosingCallable()) and
c = count(nodeGetEnclosingCallable(n)) and
c != 1 and
msg = "Node should have one enclosing callable but has " + c + "."
)
@@ -85,13 +85,13 @@ module Consistency {
}
query predicate parameterCallable(ParameterNode p, string msg) {
exists(DataFlowCallable c | p.isParameterOf(c, _) and c != p.getEnclosingCallable()) and
exists(DataFlowCallable c | isParameterNode(p, c, _) and c != nodeGetEnclosingCallable(p)) and
msg = "Callable mismatch for parameter."
}
query predicate localFlowIsLocal(Node n1, Node n2, string msg) {
simpleLocalFlowStep(n1, n2) and
n1.getEnclosingCallable() != n2.getEnclosingCallable() and
nodeGetEnclosingCallable(n1) != nodeGetEnclosingCallable(n2) and
msg = "Local flow step does not preserve enclosing callable."
}
@@ -106,7 +106,7 @@ module Consistency {
query predicate unreachableNodeCCtx(Node n, DataFlowCall call, string msg) {
isUnreachableInCall(n, call) and
exists(DataFlowCallable c |
c = n.getEnclosingCallable() and
c = nodeGetEnclosingCallable(n) and
not viableCallable(call) = c
) and
msg = "Call context for isUnreachableInCall is inconsistent with call graph."
@@ -120,7 +120,7 @@ module Consistency {
n.(ArgumentNode).argumentOf(call, _) and
msg = "ArgumentNode and call does not share enclosing callable."
) and
n.getEnclosingCallable() != call.getEnclosingCallable()
nodeGetEnclosingCallable(n) != call.getEnclosingCallable()
}
// This predicate helps the compiler forget that in some languages
@@ -151,7 +151,7 @@ module Consistency {
}
query predicate postIsInSameCallable(PostUpdateNode n, string msg) {
n.getEnclosingCallable() != n.getPreUpdateNode().getEnclosingCallable() and
nodeGetEnclosingCallable(n) != nodeGetEnclosingCallable(n.getPreUpdateNode()) and
msg = "PostUpdateNode does not share callable with its pre-update node."
}

View File

@@ -6,6 +6,12 @@ private import DataFlowDispatch
private import SsaImpl as SsaImpl
private import FlowSummaryImpl as FlowSummaryImpl
/** Gets the callable in which this node occurs. */
DataFlowCallable nodeGetEnclosingCallable(Node n) { result = n.getEnclosingCallable() }
/** Holds if `p` is a `ParameterNode` of `c` with position `pos`. */
predicate isParameterNode(ParameterNode p, DataFlowCallable c, int pos) { p.isParameterOf(c, pos) }
abstract class NodeImpl extends Node {
/** Do not call: use `getEnclosingCallable()` instead. */
abstract CfgScope getCfgScope();

View File

@@ -131,7 +131,7 @@ private DataFlow::LocalSourceNode trackFeature(Feature f, boolean enable, TypeTr
// same code.
exists(CfgNodes::ExprNodes::OperationCfgNode operation |
bitWiseAndOr(operation) and
operation = result.asExpr().(CfgNodes::ExprNodes::OperationCfgNode) and
operation = result.asExpr() and
operation.getAnOperand() = trackFeature(f, enable).asExpr()
)
or

View File

@@ -52,6 +52,24 @@ private module Cached {
)
}
/** Gets the summary resulting from prepending `step` to this type-tracking summary. */
cached
TypeBackTracker prepend(TypeBackTracker tbt, StepSummary step) {
exists(Boolean hasReturn, string content | tbt = MkTypeBackTracker(hasReturn, content) |
step = LevelStep() and result = tbt
or
step = CallStep() and hasReturn = false and result = tbt
or
step = ReturnStep() and result = MkTypeBackTracker(true, content)
or
exists(string p |
step = LoadStep(p) and content = "" and result = MkTypeBackTracker(hasReturn, p)
)
or
step = StoreStep(content) and result = MkTypeBackTracker(hasReturn, "")
)
}
/**
* Gets the summary that corresponds to having taken a forwards
* heap and/or intra-procedural step from `nodeFrom` to `nodeTo`.
@@ -365,19 +383,7 @@ class TypeBackTracker extends TTypeBackTracker {
TypeBackTracker() { this = MkTypeBackTracker(hasReturn, content) }
/** Gets the summary resulting from prepending `step` to this type-tracking summary. */
TypeBackTracker prepend(StepSummary step) {
step = LevelStep() and result = this
or
step = CallStep() and hasReturn = false and result = this
or
step = ReturnStep() and result = MkTypeBackTracker(true, content)
or
exists(string p |
step = LoadStep(p) and content = "" and result = MkTypeBackTracker(hasReturn, p)
)
or
step = StoreStep(content) and result = MkTypeBackTracker(hasReturn, "")
}
TypeBackTracker prepend(StepSummary step) { result = prepend(this, step) }
/** Gets a textual representation of this summary. */
string toString() {
@@ -459,6 +465,19 @@ class TypeBackTracker extends TTypeBackTracker {
simpleLocalFlowStep(nodeFrom, nodeTo) and
this = result
}
/**
* Gets a forwards summary that is compatible with this backwards summary.
* That is, if this summary describes the steps needed to back-track a value
* from `sink` to `mid`, and the result is a valid summary of the steps needed
* to track a value from `source` to `mid`, then the value from `source` may
* also flow to `sink`.
*/
TypeTracker getACompatibleTypeTracker() {
exists(boolean hasCall | result = MkTypeTracker(hasCall, content) |
hasCall = false or this.hasReturn() = false
)
}
}
/** Provides predicates for implementing custom `TypeBackTracker`s. */

View File

@@ -132,7 +132,7 @@ private string getSetterCallAttributeName(AST::SetterMethodCall call) {
predicate basicLoadStep(Node nodeFrom, Node nodeTo, string content) {
exists(ExprNodes::MethodCallCfgNode call |
call.getExpr().getNumberOfArguments() = 0 and
content = call.getExpr().(AST::MethodCall).getMethodName() and
content = call.getExpr().getMethodName() and
nodeFrom.asExpr() = call.getReceiver() and
nodeTo.asExpr() = call
)

View File

@@ -3,7 +3,7 @@
* @description Generates use-definition pairs that provide the data
* for jump-to-definition in the code viewer.
* @kind definitions
* @id ruby/ide-jump-to-definition
* @id rb/ide-jump-to-definition
* @tags ide-contextual-queries/local-definitions
*/

View File

@@ -3,7 +3,7 @@
* @description Generates use-definition pairs that provide the data
* for find-references in the code viewer.
* @kind definitions
* @id ruby/ide-find-references
* @id rb/ide-find-references
* @tags ide-contextual-queries/local-references
*/

View File

@@ -2,7 +2,7 @@
* @name Print AST
* @description Produces a representation of a file's Abstract Syntax Tree.
* This query is used by the VS Code extension.
* @id ruby/print-ast
* @id rb/print-ast
* @kind graph
* @tags ide-contextual-queries/print-ast
*/

View File

@@ -30,7 +30,7 @@ string access(int p) {
p.bitAnd(4) != 0 and result = "readable"
}
/** An expression specifing a file permission that allows group/others read or write access */
/** An expression specifying a file permission that allows group/others read or write access */
class PermissivePermissionsExpr extends Expr {
// TODO: non-literal expressions?
PermissivePermissionsExpr() {