-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Python: port modification of default value #6557
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
RasmusWL
merged 26 commits into
github:main
from
yoff:python/port-modification-of-default-value
Sep 21, 2021
Merged
Changes from all commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
e3765ce
Python: Add tests for modification of defaults
yoff e865a29
Python: straight port of query
yoff 5bff518
Python: switch from negative to positive list
yoff 8614563
Python: More tests of syntactic constructs
yoff d834cec
Python: test simple sanitizer
yoff 097c23e
Python: add inline expectations test
yoff 49ae549
Python: Implement modifying syntax
yoff a762373
Python: Implement simple barrier guard
yoff 1903cb8
Python: Add change note
yoff 0de621e
Python: Add qldoc
yoff a855074
Python: Try to remove py2/3 differences
yoff c6eb795
Apply suggestions from code review
yoff 913990b
Python: Add suggested comments and test case
yoff 4998a48
Python: Fix simple guards
yoff ae8408b
Python: Add missing qldoc
yoff 29cb067
Python: Remember to update test expectations
yoff 8729701
Merge branch 'main' of github.com:github/codeql into python/port-modi…
yoff b48caaf
Python: fix reference to PrintNode.qll
yoff e8644f6
Python: coment out discriminating test
yoff 43effd2
Update python/ql/src/semmle/python/functions/ModificationOfParameterW…
yoff b20232d
Python: Simplify guards as suggested
yoff 7cfa08a
Python: Do not use BarrierGuards
yoff 02fd63c
Merge branch 'main' of github.com:github/codeql into python/port-modi…
yoff 2eb1173
Python: Subpaths in test output
yoff 758b6bd
Update python/ql/src/semmle/python/functions/ModificationOfParameterW…
yoff 8ea7a28
Python: Unexpose fields as suggested.
yoff File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
2 changes: 2 additions & 0 deletions
2
python/change-notes/2021-08-30-port-modifying-default-query.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
lgtm,codescanning | ||
* Updated _Modification of parameter with default_ (`py/modification-of-default-value`) query to use the new data flow library instead of the old taint tracking library and to remove the use of points-to analysis. You may see differences in the results found by the query, but overall this change should result in a more robust and accurate analysis. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
46 changes: 46 additions & 0 deletions
46
python/ql/src/semmle/python/functions/ModificationOfParameterWithDefault.qll
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
/** | ||
* Provides a data-flow configuration for detecting modifications of a parameters default value. | ||
* | ||
* Note, for performance reasons: only import this file if | ||
* `ModificationOfParameterWithDefault::Configuration` is needed, otherwise | ||
* `ModificationOfParameterWithDefaultCustomizations` should be imported instead. | ||
*/ | ||
|
||
private import python | ||
import semmle.python.dataflow.new.DataFlow | ||
|
||
/** | ||
* Provides a data-flow configuration for detecting modifications of a parameters default value. | ||
*/ | ||
module ModificationOfParameterWithDefault { | ||
import ModificationOfParameterWithDefaultCustomizations::ModificationOfParameterWithDefault | ||
|
||
/** | ||
* A data-flow configuration for detecting modifications of a parameters default value. | ||
*/ | ||
class Configuration extends DataFlow::Configuration { | ||
/** Record whether the default value being tracked is non-empty. */ | ||
boolean nonEmptyDefault; | ||
|
||
Configuration() { | ||
nonEmptyDefault in [true, false] and | ||
this = "ModificationOfParameterWithDefault:" + nonEmptyDefault.toString() | ||
} | ||
|
||
override predicate isSource(DataFlow::Node source) { | ||
source.(Source).isNonEmpty() = nonEmptyDefault | ||
} | ||
|
||
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink } | ||
|
||
override predicate isBarrier(DataFlow::Node node) { | ||
// if we are tracking a non-empty default, then it is ok to modify empty values, | ||
// so our tracking ends at those. | ||
nonEmptyDefault = true and node instanceof MustBeEmpty | ||
or | ||
// if we are tracking a empty default, then it is ok to modify non-empty values, | ||
// so our tracking ends at those. | ||
nonEmptyDefault = false and node instanceof MustBeNonEmpty | ||
} | ||
} | ||
} |
190 changes: 190 additions & 0 deletions
190
python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
/** | ||
* Provides default sources, sinks and sanitizers for detecting | ||
* modifications of a parameters default value, as well as extension points for adding your own. | ||
*/ | ||
|
||
private import python | ||
private import semmle.python.dataflow.new.DataFlow | ||
private import semmle.python.dataflow.new.BarrierGuards | ||
|
||
/** | ||
* Provides default sources, sinks and sanitizers for detecting | ||
* "command injection" | ||
* vulnerabilities, as well as extension points for adding your own. | ||
*/ | ||
module ModificationOfParameterWithDefault { | ||
/** | ||
* A data flow source for detecting modifications of a parameters default value, | ||
* that is a default value for some parameter. | ||
*/ | ||
abstract class Source extends DataFlow::Node { | ||
/** Result is true if the default value is non-empty for this source and false if not. */ | ||
abstract boolean isNonEmpty(); | ||
} | ||
|
||
/** | ||
* A data flow sink for detecting modifications of a parameters default value, | ||
* that is a node representing a modification. | ||
*/ | ||
abstract class Sink extends DataFlow::Node { } | ||
|
||
/** | ||
* A sanitizer for detecting modifications of a parameters default value | ||
* should determine if the node (which is perhaps about to be modified) | ||
* can be the default value or not. | ||
* | ||
* In this query we do not track the default value exactly, but rather wheter | ||
* it is empty or not (see `Source`). | ||
* | ||
* This is the extension point for determining that a node must be empty and | ||
* therefor is allowed to be modified if the tracked default value is non-empty. | ||
*/ | ||
abstract class MustBeEmpty extends DataFlow::Node { } | ||
|
||
/** | ||
* A sanitizer for detecting modifications of a parameters default value | ||
* should determine if the node (which is perhaps about to be modified) | ||
* can be the default value or not. | ||
* | ||
* In this query we do not track the default value exactly, but rather wheter | ||
* it is empty or not (see `Source`). | ||
* | ||
* This is the extension point for determining that a node must be non-empty | ||
* and therefor is allowed to be modified if the tracked default value is empty. | ||
*/ | ||
abstract class MustBeNonEmpty extends DataFlow::Node { } | ||
|
||
/** Gets the truthiness (non emptyness) of the default of `p` if that value is mutable */ | ||
private boolean mutableDefaultValue(Parameter p) { | ||
exists(Dict d | p.getDefault() = d | | ||
exists(d.getAKey()) and result = true | ||
or | ||
not exists(d.getAKey()) and result = false | ||
) | ||
or | ||
exists(List l | p.getDefault() = l | | ||
exists(l.getAnElt()) and result = true | ||
or | ||
not exists(l.getAnElt()) and result = false | ||
) | ||
} | ||
|
||
/** | ||
* A mutable default value for a parameter, considered as a flow source. | ||
*/ | ||
class MutableDefaultValue extends Source { | ||
boolean nonEmpty; | ||
|
||
MutableDefaultValue() { nonEmpty = mutableDefaultValue(this.asCfgNode().(NameNode).getNode()) } | ||
|
||
override boolean isNonEmpty() { result = nonEmpty } | ||
} | ||
|
||
/** | ||
* A name of a list function that modifies the list. | ||
* See https://docs.python.org/3/tutorial/datastructures.html#more-on-lists | ||
*/ | ||
string list_modifying_method() { | ||
result in ["append", "extend", "insert", "remove", "pop", "clear", "sort", "reverse"] | ||
} | ||
|
||
/** | ||
* A name of a dict function that modifies the dict. | ||
* See https://docs.python.org/3/library/stdtypes.html#dict | ||
*/ | ||
string dict_modifying_method() { result in ["clear", "pop", "popitem", "setdefault", "update"] } | ||
|
||
/** | ||
* A mutation of the default value is a flow sink. | ||
* | ||
* Syntactic constructs that modify a list are: | ||
* - s[i] = x | ||
* - s[i:j] = t | ||
* - del s[i:j] | ||
* - s[i:j:k] = t | ||
* - del s[i:j:k] | ||
* - s += t | ||
* - s *= n | ||
* See https://docs.python.org/3/library/stdtypes.html#mutable-sequence-types | ||
* | ||
* Syntactic constructs that modify a dictionary are: | ||
* - d[key] = value | ||
* - del d[key] | ||
* - d |= other | ||
* See https://docs.python.org/3/library/stdtypes.html#dict | ||
* | ||
* These are all covered by: | ||
* - assignment to a subscript (includes slices) | ||
* - deletion of a subscript | ||
* - augmented assignment to the value | ||
*/ | ||
class Mutation extends Sink { | ||
Mutation() { | ||
// assignment to a subscript (includes slices) | ||
exists(DefinitionNode d | d.(SubscriptNode).getObject() = this.asCfgNode()) | ||
or | ||
// deletion of a subscript | ||
exists(DeletionNode d | d.getTarget().(SubscriptNode).getObject() = this.asCfgNode()) | ||
or | ||
// augmented assignment to the value | ||
exists(AugAssign a | a.getTarget().getAFlowNode() = this.asCfgNode()) | ||
or | ||
// modifying function call | ||
exists(DataFlow::CallCfgNode c, DataFlow::AttrRead a | c.getFunction() = a | | ||
a.getObject() = this and | ||
a.getAttributeName() in [list_modifying_method(), dict_modifying_method()] | ||
) | ||
} | ||
} | ||
|
||
// This to reimplement some of the functionality of the DataFlow::BarrierGuard | ||
private import semmle.python.essa.SsaCompute | ||
|
||
/** | ||
* A data-flow node that is known to be either truthy or falsey. | ||
* | ||
* It handles the cases `if x` and `if not x`. | ||
* | ||
* For example, in the following code, `this` will be the `x` that is printed, | ||
* which we will know is truthy: | ||
* | ||
* ```py | ||
* if x: | ||
* print(x) | ||
* ``` | ||
*/ | ||
private class MustBe extends DataFlow::Node { | ||
boolean truthy; | ||
|
||
MustBe() { | ||
exists(DataFlow::GuardNode guard, NameNode guarded, boolean branch | | ||
// case: if x | ||
guard = guarded and | ||
branch = truthy | ||
or | ||
// case: if not x | ||
guard.(UnaryExprNode).getNode().getOp() instanceof Not and | ||
guarded = guard.(UnaryExprNode).getOperand() and | ||
branch = truthy.booleanNot() | ||
| | ||
// guard controls this | ||
guard.controlsBlock(this.asCfgNode().getBasicBlock(), branch) and | ||
// there is a definition tying the guarded value to this | ||
exists(EssaDefinition def | | ||
AdjacentUses::useOfDef(def, this.asCfgNode()) and | ||
AdjacentUses::useOfDef(def, guarded) | ||
) | ||
) | ||
} | ||
} | ||
|
||
/** Simple guard detecting truthy values. */ | ||
private class MustBeTruthy extends MustBe, MustBeNonEmpty { | ||
MustBeTruthy() { truthy = true } | ||
} | ||
|
||
/** Simple guard detecting falsey values. */ | ||
private class MustBeFalsey extends MustBe, MustBeEmpty { | ||
MustBeFalsey() { truthy = false } | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.