Merge branch 'master' into python-unsafe-use-of-mktemp

This commit is contained in:
Taus Brock-Nannestad
2019-02-26 13:23:38 +01:00
1572 changed files with 109513 additions and 95738 deletions

View File

@@ -14,8 +14,13 @@ import python
predicate mutates_descriptor(ClassObject cls, SelfAttributeStore s) {
cls.isDescriptorType() and
exists(PyFunctionObject f |
cls.lookupAttribute(_) = f and
exists(PyFunctionObject f, PyFunctionObject get_set |
exists(string name |
cls.lookupAttribute(name) = get_set |
name = "__get__" or name = "__set__" or name = "__delete__"
) and
cls.lookupAttribute(_) = f and
get_set.getACallee*() = f and
not f.getName() = "__init__" and
s.getScope() = f.getFunction()
)

View File

@@ -67,9 +67,9 @@ predicate subscript(Stmt s) {
predicate encode_decode(Expr ex, ClassObject type) {
exists(string name |
ex.(Call).getFunc().(Attribute).getName() = name |
name = "encode" and type = builtin_object("UnicodeEncodeError")
name = "encode" and type = Object::builtin("UnicodeEncodeError")
or
name = "decode" and type = builtin_object("UnicodeDecodeError")
name = "decode" and type = Object::builtin("UnicodeDecodeError")
)
}

View File

@@ -3,9 +3,9 @@ import python
/** Holds if `notimpl` refers to `NotImplemented` or `NotImplemented()` in the `raise` statement */
predicate use_of_not_implemented_in_raise(Raise raise, Expr notimpl) {
notimpl.refersTo(theNotImplementedObject()) and
notimpl.refersTo(Object::notImplemented()) and
(
notimpl = raise.getException() or
notimpl = raise.getException() or
notimpl = raise.getException().(Call).getFunc()
)
}

View File

@@ -13,11 +13,11 @@
import python
FunctionObject iter() {
result = builtin_object("iter")
result = Object::builtin("iter")
}
FunctionObject next() {
result = builtin_object("next")
result = Object::builtin("next")
}
predicate call_to_iter(CallNode call, EssaVariable sequence) {

View File

@@ -107,7 +107,7 @@ private predicate brace_pair(PossibleAdvancedFormatString fmt, int start, int en
private predicate advanced_format_call(Call format_expr, PossibleAdvancedFormatString fmt, int args) {
exists(CallNode call |
call = format_expr.getAFlowNode() |
call.getFunction().refersTo(theFormatFunction()) and call.getArg(0).refersTo(_, fmt.getAFlowNode()) and
call.getFunction().refersTo(Object::builtin("format")) and call.getArg(0).refersTo(_, fmt.getAFlowNode()) and
args = count(format_expr.getAnArg()) - 1
or
call.getFunction().(AttrNode).getObject("format").refersTo(_, fmt.getAFlowNode()) and

View File

@@ -19,7 +19,7 @@ import python
predicate numpy_array_type(ClassObject na) {
exists(ModuleObject np | np.getName() = "numpy" or np.getName() = "numpy.core" |
na.getAnImproperSuperType() = np.getAttribute("ndarray")
na.getAnImproperSuperType() = np.attr("ndarray")
)
}

View File

@@ -13,5 +13,5 @@ import python
from CallNode call, ControlFlowNode func
where
major_version() = 2 and call.getFunction() = func and func.refersTo(theApplyFunction())
major_version() = 2 and call.getFunction() = func and func.refersTo(Object::builtin("apply"))
select call, "Call to the obsolete builtin function 'apply'."

View File

@@ -14,5 +14,5 @@ import python
from CallNode call, Context context, ControlFlowNode func
where
context.getAVersion().includes(2, _) and call.getFunction() = func and func.refersTo(context, theInputFunction(), _, _)
context.getAVersion().includes(2, _) and call.getFunction() = func and func.refersTo(context, Object::builtin("input"), _, _)
select call, "The unsafe built-in function 'input' is used."

View File

@@ -70,11 +70,11 @@ predicate correct_raise(string name, ClassObject ex) {
predicate preferred_raise(string name, ClassObject ex) {
attribute_method(name) and ex = theAttributeErrorType()
or
indexing_method(name) and ex = builtin_object("LookupError")
indexing_method(name) and ex = Object::builtin("LookupError")
or
ordering_method(name) and ex = theTypeErrorType()
or
arithmetic_method(name) and ex = builtin_object("ArithmeticError")
arithmetic_method(name) and ex = Object::builtin("ArithmeticError")
}
predicate no_need_to_raise(string name, string message) {

View File

@@ -128,7 +128,7 @@ predicate function_should_close_parameter(Function func) {
}
predicate function_opens_file(FunctionObject f) {
f = theOpenFunction()
f = Object::builtin("open")
or
exists(EssaVariable v, Return ret |
ret.getScope() = f.getFunction() |

View File

@@ -0,0 +1,69 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Sanitizing untrusted URLs is an important technique for
preventing attacks such as request forgeries and malicious
redirections. Often, this is done by checking that the host of a URL
is in a set of allowed hosts.
</p>
<p>
If a regular expression implements such a check, it is
easy to accidentally make the check too permissive by not escaping the
<code>.</code> meta-characters appropriately.
Even if the check is not used in a security-critical
context, the incomplete check may still cause undesirable behaviors
when it accidentally succeeds.
</p>
</overview>
<recommendation>
<p>
Escape all meta-characters appropriately when constructing
regular expressions for security checks, pay special attention to the
<code>.</code> meta-character.
</p>
</recommendation>
<example>
<p>
The following example code checks that a URL redirection
will reach the <code>example.com</code> domain, or one of its
subdomains.
</p>
<sample src="examples/IncompleteHostnameRegExp.py"/>
<p>
The <code>unsafe</code> check is easy to bypass because the unescaped
<code>.</code> allows for any character before
<code>example.com</code>, effectively allowing the redirect to go to
an attacker-controlled domain such as <code>wwwXexample.com</code>.
</p>
<p>
The <code>safe</code> check closes this vulnerability by escaping the <code>.</code>
so that URLs of the form <code>wwwXexample.com</code> are rejected.
</p>
</example>
<references>
<li>OWASP: <a href="https://www.owasp.org/index.php/Server_Side_Request_Forgery">SSRF</a></li>
<li>OWASP: <a href="https://www.owasp.org/index.php/Unvalidated_Redirects_and_Forwards_Cheat_Sheet">XSS Unvalidated Redirects and Forwards Cheat Sheet</a>.</li>
</references>
</qhelp>

View File

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

View File

@@ -0,0 +1,85 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Sanitizing untrusted URLs is an important technique for
preventing attacks such as request forgeries and malicious
redirections. Usually, this is done by checking that the host of a URL
is in a set of allowed hosts.
</p>
<p>
However, treating the URL as a string and checking if one of the
allowed hosts is a substring of the URL is very prone to errors.
Malicious URLs can bypass such security checks by embedding one
of the allowed hosts in an unexpected location.
</p>
<p>
Even if the substring check is not used in a
security-critical context, the incomplete check may still cause
undesirable behaviors when the check succeeds accidentally.
</p>
</overview>
<recommendation>
<p>
Parse a URL before performing a check on its host value,
and ensure that the check handles arbitrary subdomain sequences
correctly.
</p>
</recommendation>
<example>
<p>
The following example code checks that a URL redirection
will reach the <code>example.com</code> domain.
</p>
<sample src="examples/IncompleteUrlSubstringSanitization.py"/>
<p>
The first two examples show unsafe checks that are easily bypassed.
In <code>unsafe1</code> the attacker can simply add
<code>example.com</code> anywhere in the url. For example,
<code>http://evil-example.net/example.com</code>.
</p>
<p>
In <code>unsafe2</code> the attacker must use a hostname ending in
<code>example.com</code>, but that is easy to do. For example,
<code>http://benign-looking-prefix-example.com</code>.
</p>
<p>
The second two examples show safe checks.
In <code>safe1</code>, a white-list is used. Although fairly inflexible,
this is easy to get right and is most likely to be safe.
</p>
<p>
In <code>safe2</code>, <code>urlparse</code> is used to parse the URL,
then the hostname is checked to make sure it ends with <code>.example.com</code>.
</p>
</example>
<references>
<li>OWASP: <a href="https://www.owasp.org/index.php/Server_Side_Request_Forgery">SSRF</a></li>
<li>OWASP: <a href="https://www.owasp.org/index.php/Unvalidated_Redirects_and_Forwards_Cheat_Sheet">XSS Unvalidated Redirects and Forwards Cheat Sheet</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,58 @@
/**
* @name Incomplete URL substring sanitization
* @description Security checks on the substrings of an unparsed URL are often vulnerable to bypassing.
* @kind problem
* @problem.severity warning
* @precision high
* @id py/incomplete-url-substring-sanitization
* @tags correctness
* security
* external/cwe/cwe-20
*/
import python
import semmle.python.regex
private string commonTopLevelDomainRegex() {
result = "com|org|edu|gov|uk|net|io"
}
predicate looksLikeUrl(StrConst s) {
exists(string text |
text = s.getText()
|
text.regexpMatch("(?i)([a-z]*:?//)?\\.?([a-z0-9-]+\\.)+(" +
commonTopLevelDomainRegex() +")(:[0-9]+)?/?")
or
// target is a HTTP URL to a domain on any TLD
text.regexpMatch("(?i)https?://([a-z0-9-]+\\.)+([a-z]+)(:[0-9]+)?/?")
)
}
predicate incomplete_sanitization(Expr sanitizer, StrConst url) {
looksLikeUrl(url) and
(
sanitizer.(Compare).compares(url, any(In i), _)
or
call_to_startswith(sanitizer, url)
or
unsafe_call_to_endswith(sanitizer, url)
)
}
predicate call_to_startswith(Call sanitizer, StrConst url) {
sanitizer.getFunc().(Attribute).getName() = "startswith"
and
sanitizer.getArg(0) = url
}
predicate unsafe_call_to_endswith(Call sanitizer, StrConst url) {
sanitizer.getFunc().(Attribute).getName() = "endswith" and
sanitizer.getArg(0) = url and
not url.getText().regexpMatch("(?i)\\.([a-z0-9-]+)(\\.[a-z0-9-]+)+")
}
from Expr sanitizer, StrConst url
where incomplete_sanitization(sanitizer, url)
select sanitizer, "'$@' may be at an arbitrary position in the sanitized URL.", url, url.getText()

View File

@@ -0,0 +1,19 @@
from flask import Flask, request, redirect
import re
app = Flask(__name__)
UNSAFE_REGEX = re.compile("(www|beta).example.com/")
SAFE_REGEX = re.compile(r"(www|beta)\.example\.com/")
@app.route('/some/path/bad')
def unsafe(request):
target = request.args.get('target', '')
if UNSAFE_REGEX.match(target):
return redirect(target)
@app.route('/some/path/good')
def safe(request):
target = request.args.get('target', '')
if SAFE_REGEX.match(target):
return redirect(target)

View File

@@ -0,0 +1,45 @@
from flask import Flask, request, redirect
from urllib.parse import urlparse
app = Flask(__name__)
# Not safe, as "evil-example.net/example.com" would be accepted
@app.route('/some/path/bad1')
def unsafe1(request):
target = request.args.get('target', '')
if "example.com" in target:
return redirect(target)
# Not safe, as "benign-looking-prefix-example.com" would be accepted
@app.route('/some/path/bad2')
def unsafe2(request):
target = request.args.get('target', '')
if target.endswith("example.com"):
return redirect(target)
#Simplest and safest approach is to use a white-list
@app.route('/some/path/good1')
def safe1(request):
whitelist = [
"example.com/home",
"example.com/login",
]
target = request.args.get('target', '')
if target in whitelist:
return redirect(target)
#More complex example allowing sub-domains.
@app.route('/some/path/good2')
def safe2(request):
target = request.args.get('target', '')
host = urlparse(target).hostname
#Note the '.' preceding example.com
if host and host.endswith(".example.com"):
return redirect(target)

View File

@@ -15,7 +15,7 @@ import python
ClassObject jinja2EnvironmentOrTemplate() {
exists(ModuleObject jinja2, string name |
jinja2.getName() = "jinja2" and
jinja2.getAttribute(name) = result |
jinja2.attr(name) = result |
name = "Environment" or
name = "Template"
)

View File

@@ -17,7 +17,7 @@ import semmle.python.web.Http
FunctionObject requestFunction() {
exists(ModuleObject req |
req.getName() = "requests" and
result = req.getAttribute(httpVerbLower())
result = req.attr(httpVerbLower())
)
}

View File

@@ -21,7 +21,7 @@ int minimumSecureKeySize(string algo) {
predicate dsaRsaKeySizeArg(FunctionObject obj, string algorithm, string arg) {
exists(ModuleObject mod |
mod.getAttribute(_) = obj |
mod.attr(_) = obj |
algorithm = "DSA" and
(
mod.getName() = "cryptography.hazmat.primitives.asymmetric.dsa" and arg = "key_size"
@@ -44,7 +44,7 @@ predicate dsaRsaKeySizeArg(FunctionObject obj, string algorithm, string arg) {
predicate ecKeySizeArg(FunctionObject obj, string arg) {
exists(ModuleObject mod |
mod.getAttribute(_) = obj |
mod.attr(_) = obj |
mod.getName() = "cryptography.hazmat.primitives.asymmetric.ec" and arg = "curve"
)
}

View File

@@ -13,11 +13,11 @@
import python
FunctionObject ssl_wrap_socket() {
result = any(ModuleObject ssl | ssl.getName() = "ssl").getAttribute("wrap_socket")
result = any(ModuleObject ssl | ssl.getName() = "ssl").attr("wrap_socket")
}
ClassObject ssl_Context_class() {
result = any(ModuleObject ssl | ssl.getName() = "ssl").getAttribute("SSLContext")
result = any(ModuleObject ssl | ssl.getName() = "ssl").attr("SSLContext")
}
CallNode unsafe_call(string method_name) {

View File

@@ -12,11 +12,11 @@
import python
FunctionObject ssl_wrap_socket() {
result = the_ssl_module().getAttribute("wrap_socket")
result = the_ssl_module().attr("wrap_socket")
}
ClassObject ssl_Context_class() {
result = the_ssl_module().getAttribute("SSLContext")
result = the_ssl_module().attr("SSLContext")
}
string insecure_version_name() {
@@ -69,20 +69,20 @@ predicate unsafe_ssl_wrap_socket_call(CallNode call, string method_name, string
insecure_version = insecure_version_name()
and
(
call.getArgByName("ssl_version").refersTo(the_ssl_module().getAttribute(insecure_version))
call.getArgByName("ssl_version").refersTo(the_ssl_module().attr(insecure_version))
or
probable_insecure_ssl_constant(call, insecure_version)
)
}
ClassObject the_pyOpenSSL_Context_class() {
result = any(ModuleObject m | m.getName() = "pyOpenSSL.SSL").getAttribute("Context")
result = any(ModuleObject m | m.getName() = "pyOpenSSL.SSL").attr("Context")
}
predicate unsafe_pyOpenSSL_Context_call(CallNode call, string insecure_version) {
call = the_pyOpenSSL_Context_class().getACall() and
insecure_version = insecure_version_name() and
call.getArg(0).refersTo(the_pyOpenSSL_module().getAttribute(insecure_version))
call.getArg(0).refersTo(the_pyOpenSSL_module().attr(insecure_version))
}
from CallNode call, string method_name, string insecure_version

View File

@@ -35,12 +35,12 @@ string permissive_permission(int p) {
}
predicate chmod_call(CallNode call, FunctionObject chmod, NumericObject num) {
any(ModuleObject os | os.getName() = "os").getAttribute("chmod") = chmod and
any(ModuleObject os | os.getName() = "os").attr("chmod") = chmod and
chmod.getACall() = call and call.getArg(1).refersTo(num)
}
predicate open_call(CallNode call, FunctionObject open, NumericObject num) {
any(ModuleObject os | os.getName() = "os").getAttribute("open") = open and
any(ModuleObject os | os.getName() = "os").attr("open") = open and
open.getACall() = call and call.getArg(2).refersTo(num)
}

View File

@@ -34,7 +34,7 @@ predicate fewer_characters_than(StrConst str, string char, float fraction) {
}
predicate possible_reflective_name(string name) {
exists(any(ModuleObject m).getAttribute(name))
exists(any(ModuleObject m).attr(name))
or
exists(any(ClassObject c).lookupAttribute(name))
or
@@ -42,7 +42,7 @@ predicate possible_reflective_name(string name) {
or
any(ModuleObject m).getName() = name
or
exists(builtin_object(name))
exists(Object::builtin(name))
}
int char_count(StrConst str) {

View File

@@ -39,14 +39,14 @@ predicate maybe_defined_in_outer_scope(Name n) {
}
Variable relevant_var(Name n) {
n.getVariable() = result and
(corresponding(n, _) or corresponding(_, n))
n.getVariable() = result and
(corresponding(n, _) or corresponding(_, n))
}
predicate same_name(Name n1, Name n2) {
corresponding(n1, n2) and
relevant_var(n1) = relevant_var(n2) and
not exists(builtin_object(n1.getId())) and
not exists(Object::builtin(n1.getId())) and
not maybe_defined_in_outer_scope(n2)
}

View File

@@ -84,7 +84,7 @@ predicate in_notebook(Expr e) {
}
FunctionObject assertRaises() {
result = ModuleObject::named("unittest").getAttribute("TestCase").(ClassObject).lookupAttribute("assertRaises")
result = ModuleObject::named("unittest").attr("TestCase").(ClassObject).lookupAttribute("assertRaises")
}
/** Holds if expression `e` is in a `with` block that tests for exceptions being raised. */

View File

@@ -12,5 +12,5 @@
import python
from CallNode call, string name
where call.getFunction().refersTo(quitterObject(name))
where call.getFunction().refersTo(Object::quitter(name))
select call, "The '" + name + "' site.Quitter object may not exist if the 'site' module is not loaded or is modified."

View File

@@ -15,7 +15,7 @@ predicate monkey_patched_builtin(string name) {
bltn.refersTo(theBuiltinModuleObject()) and
call.getArg(1).getNode() = s and
s.getText() = name and
call.getFunction().refersTo(builtin_object("setattr"))
call.getFunction().refersTo(Object::builtin("setattr"))
)
or
exists(AttrNode attr |

View File

@@ -45,7 +45,7 @@ predicate white_list(string name) {
predicate shadows(Name d, string name, Scope scope, int line) {
exists(LocalVariable l | d.defines(l) and scope instanceof Function and
l.getId() = name and
exists(builtin_object(l.getId()))
exists(Object::builtin(l.getId()))
) and
d.getScope() = scope and
d.getLocation().getStartLine() = line and

View File

@@ -21,7 +21,7 @@ predicate shadows(Name d, GlobalVariable g, Scope scope, int line) {
not exists(Import il, Import ig, Name gd | il.contains(d) and gd.defines(g) and ig.contains(gd)) and
not exists(Assign a | a.getATarget() = d and a.getValue() = g.getAnAccess())
) and
not exists(builtin_object(g.getId())) and
not exists(Object::builtin(g.getId())) and
d.getScope() = scope and
d.getLocation().getStartLine() = line and
exists(Name defn | defn.defines(g) |

View File

@@ -47,8 +47,8 @@ predicate one_item_only(For f) {
predicate points_to_call_to_range(ControlFlowNode f) {
/* (x)range is a function in Py2 and a class in Py3, so we must treat it as a plain object */
exists(Object range, Object call |
range = builtin_object("range") or
range = builtin_object("xrange")
range = Object::builtin("range") or
range = Object::builtin("xrange")
|
f.refersTo(call) and
call.(CallNode).getFunction().refersTo(range)

View File

@@ -24,7 +24,7 @@ predicate declaredInAll(Module m, StrConst name)
predicate mutates_globals(PythonModuleObject m) {
exists(CallNode globals |
globals = theGlobalsFunction().(FunctionObject).getACall() and
globals = Object::builtin("globals").(FunctionObject).getACall() and
globals.getScope() = m.getModule() |
exists(AttrNode attr | attr.getObject() = globals)
or

View File

@@ -90,8 +90,8 @@ predicate use_of_exec(Module m) {
or
exists(CallNode call, FunctionObject exec |
exec.getACall() = call and call.getScope() = m |
exec = builtin_object("exec") or
exec = builtin_object("execfile")
exec = Object::builtin("exec") or
exec = Object::builtin("execfile")
)
}

View File

@@ -106,7 +106,7 @@ class SuppressionScope extends @py_comment {
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [LGTM locations](https://lgtm.com/help/ql/locations).
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
predicate hasLocationInfo(string filepath, int startline, int startcolumn, int endline, int endcolumn) {
this.(SuppressionComment).covers(filepath, startline, startcolumn, endline, endcolumn)

View File

@@ -109,7 +109,7 @@ private Object attribute_in_scope(Object obj, string name) {
or
exists(ModuleObject mod |
mod = obj |
mod.getAttribute(name) = result and result.(ControlFlowNode).getScope() = mod.getModule()
mod.attr(name) = result and result.(ControlFlowNode).getScope() = mod.getModule()
and not result.(ControlFlowNode).isEntryNode()
)
}

View File

@@ -396,7 +396,7 @@ private predicate attribute_assignment_jump_to_defn_attribute(AttributeAssignmen
private predicate sets_attribute(ArgumentRefinement def, string name) {
exists(CallNode call |
call = def.getDefiningNode() and
call.getFunction().refersTo(builtin_object("setattr")) and
call.getFunction().refersTo(Object::builtin("setattr")) and
def.getInput().getAUse() = call.getArg(0) and
call.getArg(1).getNode().(StrConst).getText() = name
)

View File

@@ -162,14 +162,14 @@ predicate function_object_sanity(string clsname, string problem, string what) {
predicate multiple_origins_per_object(Object obj) {
not obj.isC() and not obj instanceof ModuleObject and
exists(ControlFlowNode use | strictcount(ControlFlowNode orig | use.refersTo(obj, orig)) > 1)
exists(ControlFlowNode use, Context ctx | strictcount(ControlFlowNode orig | use.refersTo(ctx, obj, _, orig)) > 1)
}
predicate intermediate_origins(ControlFlowNode use, ControlFlowNode inter, Object obj) {
exists(ControlFlowNode orig |
exists(ControlFlowNode orig, Context ctx |
not inter = orig |
use.refersTo(obj, inter) and
inter.refersTo(obj, orig) and
use.refersTo(ctx, obj, _, inter) and
inter.refersTo(ctx, obj, _, orig) and
// It can sometimes happen that two different modules (e.g. cPickle and Pickle)
// have the same attribute, but different origins.
not strictcount(Object val | inter.(AttrNode).getObject().refersTo(val)) > 1

View File

@@ -12,7 +12,7 @@ string relativePath(File file) {
* Holds if the `index`-th token of block `copy` is in file `file`, spanning
* column `sc` of line `sl` to column `ec` of line `el`.
*
* For more information, see [LGTM locations](https://lgtm.com/help/ql/locations).
* For more information, see [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
pragma[noinline, nomagic]
private predicate tokenLocation(File file, int sl, int sc, int ec, int el, Copy copy, int index) {
@@ -82,7 +82,7 @@ class Copy extends @duplication_or_similarity
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [LGTM locations](https://lgtm.com/help/ql/locations).
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
predicate hasLocationInfo(string filepath, int startline, int startcolumn, int endline, int endcolumn) {
sourceFile().getName() = filepath and

View File

@@ -8,7 +8,7 @@ import semmle.python.Files
* column `startcol` of line `startline` to column `endcol` of line `endline`
* in file `filepath`.
*
* For more information, see [LGTM locations](https://lgtm.com/help/ql/locations).
* For more information, see [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
external predicate defectResults(int id, string queryPath, string filepath, int startline,
int startcol, int endline, int endcol, string message);

View File

@@ -321,7 +321,7 @@ abstract class Container extends @container {
/**
* Gets a URL representing the location of this container.
*
* For more information see https://lgtm.com/help/ql/locations#providing-urls.
* For more information see [Providing URLs](https://help.semmle.com/QL/learn-ql/ql/locations.html#providing-urls).
*/
abstract string getURL();
@@ -391,10 +391,14 @@ class Location extends @location {
/** Gets the file for this location */
File getFile() {
result = this.getPath()
}
private Container getPath() {
locations_default(this, result, _, _, _, _)
or
exists(Module m | locations_ast(this, m, _, _, _, _) |
result = m.getFile()
result = m.getPath()
)
}
@@ -423,7 +427,7 @@ class Location extends @location {
}
string toString() {
result = this.getFile().getName() + ":" + this.getStartLine().toString()
result = this.getPath().getName() + ":" + this.getStartLine().toString()
}
predicate hasLocationInfo(string filepath, int bl, int bc, int el, int ec) {

View File

@@ -1023,6 +1023,20 @@ class BasicBlock extends @py_flow_node {
predicate likelyReachable() {
start_bb_likely_reachable(this)
}
/** Gets the `ConditionBlock`, if any, that controls this block and
* does not control any other `ConditionBlock`s that control this block.
* That is the `ConditionBlock` that is closest dominator.
*/
ConditionBlock getImmediatelyControllingBlock() {
result = this.nonControllingImmediateDominator*().getImmediateDominator()
}
private BasicBlock nonControllingImmediateDominator() {
result = this.getImmediateDominator() and
not result.(ConditionBlock).controls(this, _)
}
}
private predicate start_bb_likely_reachable(BasicBlock b) {

View File

@@ -115,6 +115,9 @@ class Module extends Module_, Scope, AstNode {
override Location getLocation() {
py_scope_location(result, this)
or
not py_scope_location(_, this) and
locations_ast(result, this, 0, 0, 0, 0)
}
/** Gets a child module or package of this package */

View File

@@ -201,7 +201,7 @@ private predicate gettext_installed() {
}
private predicate builtin_constant(string name) {
exists(builtin_object(name))
exists(Object::builtin(name))
or
name = "WindowsError"
or

View File

@@ -300,6 +300,15 @@ class EscapingGlobalVariable extends ModuleVariable {
}
class EscapingAssignmentGlobalVariable extends EscapingGlobalVariable {
EscapingAssignmentGlobalVariable() {
exists(NameNode n | n.defines(this) and not n.getScope() = this.getScope())
}
}
class SpecialSsaSourceVariable extends PythonSsaSourceVariable {
SpecialSsaSourceVariable() {
@@ -337,7 +346,7 @@ private predicate class_with_global_metaclass(Class cls, GlobalVariable metaclas
/** Holds if this variable is implicitly defined */
private predicate implicit_definition(Variable v) {
v.getId() = "*"
v.getId() = "*" or v.getId() = "$"
or
exists(ImportStar is | is.getScope() = v.getScope())
}

View File

@@ -21,7 +21,7 @@ abstract class ExternalPackage extends Object {
abstract string getVersion();
Object getAttribute(string name) {
result = this.(ModuleObject).getAttribute(name)
result = this.(ModuleObject).attr(name)
}
PackageObject getPackage() {

View File

@@ -7,7 +7,7 @@ class UnitTestClass extends TestScope {
UnitTestClass() {
exists(ClassObject c |
this = c.getPyClass() |
c.getASuperType() = theUnitTestPackage().getAttribute(_)
c.getASuperType() = theUnitTestPackage().attr(_)
or
c.getASuperType().getName().toLowerCase() = "testcase"
)

View File

@@ -8,7 +8,7 @@ class ZopeInterfaceMethod extends PyFunctionObject {
/** Holds if this method belongs to a class that sub-classes `zope.interface.Interface` */
ZopeInterfaceMethod() {
exists(Object interface, ClassObject owner |
ModuleObject::named("zope.interface").getAttribute("Interface") = interface and
interface = ModuleObject::named("zope.interface").attr("Interface") and
owner.declaredAttribute(_) = this and
owner.getAnImproperSuperType().getABaseType() = interface
)

View File

@@ -196,7 +196,7 @@ predicate function_can_never_return(FunctionObject func) {
not exists(f.getAnExitNode())
)
or
func = theExitFunctionObject()
func = ModuleObject::named("sys").attr("exit")
}
/** Python specific sub-class of generic EssaNodeDefinition */
@@ -570,13 +570,13 @@ predicate potential_builtin_points_to(NameNode f, Object value, ClassObject cls,
(
builtin_name_points_to(f.getId(), value, cls)
or
not exists(builtin_object(f.getId())) and value = unknownValue() and cls = theUnknownType()
not exists(Object::builtin(f.getId())) and value = unknownValue() and cls = theUnknownType()
)
}
pragma [noinline]
predicate builtin_name_points_to(string name, Object value, ClassObject cls) {
value = builtin_object(name) and py_cobjecttypes(value, cls)
value = Object::builtin(name) and py_cobjecttypes(value, cls)
}
module BaseFlow {

View File

@@ -31,14 +31,84 @@ private import Filters as BaseFilters
import semmle.dataflow.SSA
private import MRO
/** Get a `ControlFlowNode` from an object or `here`.
* If the object is a ControlFlowNode then use that, otherwise fall back on `here`
*/
pragma[inline]
private ControlFlowNode origin_from_object_or_here(ObjectOrCfg object, ControlFlowNode here) {
result = object
or
not object instanceof ControlFlowNode and result = here
/* Use this version for speed */
library class CfgOrigin extends @py_object {
string toString() {
/* Not to be displayed */
none()
}
/** Get a `ControlFlowNode` from `this` or `here`.
* If `this` is a ControlFlowNode then use that, otherwise fall back on `here`
*/
pragma[inline]
ControlFlowNode asCfgNodeOrHere(ControlFlowNode here) {
result = this
or
not this instanceof ControlFlowNode and result = here
}
ControlFlowNode toCfgNode() {
result = this
}
pragma[inline]
CfgOrigin fix(ControlFlowNode here) {
if this = unknownValue() then
result = here
else
result = this
}
}
/* Use this version for stronger type-checking */
//private newtype TCfgOrigin =
// TUnknownOrigin()
// or
// TCfgOrigin(ControlFlowNode f)
//
//library class CfgOrigin extends TCfgOrigin {
//
// string toString() {
// /* Not to be displayed */
// none()
// }
//
// /** Get a `ControlFlowNode` from `this` or `here`.
// * If `this` is a ControlFlowNode then use that, otherwise fall back on `here`
// */
// pragma[inline]
// ControlFlowNode asCfgNodeOrHere(ControlFlowNode here) {
// this = TUnknownOrigin() and result = here
// or
// this = TCfgOrigin(result)
// }
//
// ControlFlowNode toCfgNode() {
// this = TCfgOrigin(result)
// }
//
// CfgOrigin fix(ControlFlowNode here) {
// this = TUnknownOrigin() and result = TCfgOrigin(here)
// or
// not this = TUnknownOrigin() and result = this
// }
//}
//
module CfgOrigin {
CfgOrigin fromCfgNode(ControlFlowNode f) {
result = f
}
CfgOrigin unknown() {
result = unknownValue()
}
}
module PointsTo {
@@ -114,38 +184,41 @@ module PointsTo {
/** INTERNAL -- Do not use.
*
* Holds if `package.name` points to `(value, cls, origin)`, where `package` is a package object. */
cached predicate package_attribute_points_to(PackageObject package, string name, Object value, ClassObject cls, ControlFlowNode origin) {
cached predicate package_attribute_points_to(PackageObject package, string name, Object value, ClassObject cls, CfgOrigin origin) {
py_module_attributes(package.getInitModule().getModule(), name, value, cls, origin)
or
exists(Module init |
init = package.getInitModule().getModule() |
not exists(PythonSsaSourceVariable v | v.getScope() = init | v.getName() = name or v.getName() = "*")
or
exists(EssaVariable v, PointsToContext imp |
v.getScope() = init and v.getName() = "*" and v.getAUse() = init.getANormalExit() |
SSA::ssa_variable_named_attribute_points_to(v, imp, name, undefinedVariable(), _, _) and
imp.isImport()
init = package.getInitModule().getModule() and
not exists(EssaVariable var | var.getAUse() = init.getANormalExit() and var.getSourceVariable().getName() = name) and
exists(EssaVariable var, Context context |
isModuleStateVariable(var) and var.getAUse() = init.getANormalExit() and
context.isImport() and
SSA::ssa_variable_named_attribute_points_to(var, context, name, undefinedVariable(), _, _) and
origin = value and
value = package.submodule(name) and
cls = theModuleType()
)
) and explicitly_imported(value) and
value = package.submodule(name) and cls = theModuleType() and origin = value
)
or
package.hasNoInitModule() and
value = package.submodule(name) and cls = theModuleType() and origin = CfgOrigin::fromCfgNode(value)
}
/** INTERNAL -- `Use m.attributeRefersTo(name, obj, origin)` instead.
*
* Holds if `m.name` points to `(value, cls, origin)`, where `m` is a (source) module. */
cached predicate py_module_attributes(Module m, string name, Object obj, ClassObject cls, ControlFlowNode origin) {
exists(EssaVariable var, ControlFlowNode exit, ObjectOrCfg orig, PointsToContext imp |
cached predicate py_module_attributes(Module m, string name, Object obj, ClassObject cls, CfgOrigin origin) {
exists(EssaVariable var, ControlFlowNode exit, PointsToContext imp |
exit = m.getANormalExit() and var.getAUse() = exit and
var.getSourceVariable().getName() = name and
ssa_variable_points_to(var, imp, obj, cls, orig) and
ssa_variable_points_to(var, imp, obj, cls, origin) and
imp.isImport() and
obj != undefinedVariable() |
origin = origin_from_object_or_here(orig, exit)
obj != undefinedVariable()
)
or
not exists(EssaVariable var | var.getAUse() = m.getANormalExit() and var.getSourceVariable().getName() = name) and
exists(EssaVariable var, PointsToContext imp |
var.getAUse() = m.getANormalExit() and var.getName() = "*" |
var.getAUse() = m.getANormalExit() and isModuleStateVariable(var) |
SSA::ssa_variable_named_attribute_points_to(var, imp, name, obj, cls, origin) and
imp.isImport() and obj != undefinedVariable()
)
@@ -249,7 +322,7 @@ module PointsTo {
/** Holds if `call` is of the form `getattr(arg, "name")`. */
cached predicate getattr(CallNode call, ControlFlowNode arg, string name) {
points_to(call.getFunction(), _, builtin_object("getattr"), _, _) and
points_to(call.getFunction(), _, Object::builtin("getattr"), _, _) and
call.getArg(1).getNode().(StrConst).getText() = name and
arg = call.getArg(0)
}
@@ -262,7 +335,7 @@ module PointsTo {
}
/** Holds if `var` refers to `(value, cls, origin)` given the context `context`. */
cached predicate ssa_variable_points_to(EssaVariable var, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) {
cached predicate ssa_variable_points_to(EssaVariable var, PointsToContext context, Object value, ClassObject cls, CfgOrigin origin) {
SSA::ssa_definition_points_to(var.getDefinition(), context, value, cls, origin)
}
@@ -374,10 +447,13 @@ module PointsTo {
private module Layer {
/* Holds if BasicBlock `b` is reachable, given the context `context`. */
predicate reachableBlock(BasicBlock b, PointsToContext context) {
context.appliesToScope(b.getScope()) and
forall(ConditionBlock guard |
guard.controls(b, _) |
predicate reachableBlock(BasicBlock b, PointsToContext context) {
context.appliesToScope(b.getScope()) and not exists(ConditionBlock guard | guard.controls(b, _))
or
exists(ConditionBlock guard |
guard = b.getImmediatelyControllingBlock() and
reachableBlock(guard, context)
|
exists(Object value |
points_to(guard.getLastNode(), context, value, _, _)
|
@@ -413,12 +489,12 @@ module PointsTo {
}
/** Holds if `mod.name` points to `(value, cls, origin)`, where `mod` is a module object. */
predicate module_attribute_points_to(ModuleObject mod, string name, Object value, ClassObject cls, ObjectOrCfg origin) {
predicate module_attribute_points_to(ModuleObject mod, string name, Object value, ClassObject cls, CfgOrigin origin) {
py_module_attributes(mod.getModule(), name, value, cls, origin)
or
package_attribute_points_to(mod, name, value, cls, origin)
or
builtin_module_attribute(mod, name, value, cls) and origin = value
builtin_module_attribute(mod, name, value, cls) and origin = CfgOrigin::unknown()
}
}
@@ -457,7 +533,10 @@ module PointsTo {
or
attribute_load_points_to(f, context, value, cls, origin)
or
getattr_points_to(f, context, value, cls, origin)
exists(CfgOrigin orig |
getattr_points_to(f, context, value, cls, orig) and
origin = orig.asCfgNodeOrHere(f)
)
or
if_exp_points_to(f, context, value, cls, origin)
or
@@ -521,7 +600,7 @@ module PointsTo {
result.getSourceVariable() instanceof GlobalVariable
}
private predicate use_points_to_maybe_origin(NameNode f, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin_or_obj) {
private predicate use_points_to_maybe_origin(NameNode f, PointsToContext context, Object value, ClassObject cls, CfgOrigin origin_or_obj) {
ssa_variable_points_to(fast_local_variable(f), context, value, cls, origin_or_obj)
or
name_lookup_points_to_maybe_origin(f, context, value, cls, origin_or_obj)
@@ -535,7 +614,7 @@ module PointsTo {
ssa_variable_points_to(name_local_variable(f), context, undefinedVariable(), _, _)
}
private predicate name_lookup_points_to_maybe_origin(NameNode f, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin_or_obj) {
private predicate name_lookup_points_to_maybe_origin(NameNode f, PointsToContext context, Object value, ClassObject cls, CfgOrigin origin_or_obj) {
exists(EssaVariable var | var = name_local_variable(f) |
ssa_variable_points_to(var, context, value, cls, origin_or_obj)
)
@@ -544,22 +623,26 @@ module PointsTo {
global_lookup_points_to_maybe_origin(f, context, value, cls, origin_or_obj)
}
private predicate global_lookup_points_to_maybe_origin(NameNode f, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin_or_obj) {
private predicate global_lookup_points_to_maybe_origin(NameNode f, PointsToContext context, Object value, ClassObject cls, CfgOrigin origin_or_obj) {
ssa_variable_points_to(global_variable(f), context, value, cls, origin_or_obj)
or
ssa_variable_points_to(global_variable(f), context, undefinedVariable(), _, _) and
potential_builtin_points_to(f, value, cls, origin_or_obj)
or
not exists(global_variable(f)) and context.appliesToScope(f.getScope()) and
potential_builtin_points_to(f, value, cls, origin_or_obj)
exists(ControlFlowNode origin |
origin_or_obj = CfgOrigin::fromCfgNode(origin)
|
ssa_variable_points_to(global_variable(f), context, undefinedVariable(), _, _) and
potential_builtin_points_to(f, value, cls, origin)
or
not exists(global_variable(f)) and context.appliesToScope(f.getScope()) and
potential_builtin_points_to(f, value, cls, origin)
)
}
/** Gets an object pointed to by a use (of a variable). */
private predicate use_points_to(NameNode f, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) {
exists(ObjectOrCfg origin_or_obj |
exists(CfgOrigin origin_or_obj |
value != undefinedVariable() and
use_points_to_maybe_origin(f, context, value, cls, origin_or_obj) |
origin = origin_from_object_or_here(origin_or_obj, f)
origin = origin_or_obj.asCfgNodeOrHere(f)
)
}
@@ -579,12 +662,15 @@ module PointsTo {
/** Holds if `obj.name` points to `(value, cls, orig)`. */
pragma [noinline]
private predicate class_or_module_attribute(Object obj, string name, Object value, ClassObject cls, ObjectOrCfg orig) {
private predicate class_or_module_attribute(Object obj, string name, Object value, ClassObject cls, CfgOrigin orig) {
/* Normal class attributes */
Types::class_attribute_lookup(obj, name, value, cls, orig) and cls != theStaticMethodType() and cls != theClassMethodType()
or
/* Static methods of the class */
exists(CallNode sm | Types::class_attribute_lookup(obj, name, sm, theStaticMethodType(), _) and sm.getArg(0) = value and cls = thePyFunctionType() and orig = value)
exists(CallNode sm |
Types::class_attribute_lookup(obj, name, sm, theStaticMethodType(), _) and sm.getArg(0) = value and
cls = thePyFunctionType() and orig = CfgOrigin::fromCfgNode(sm.getArg(0))
)
or
/* Module attributes */
Layer::module_attribute_points_to(obj, name, value, cls, orig)
@@ -595,7 +681,10 @@ module PointsTo {
private predicate instance_attribute_load_points_to(AttrNode f, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) {
f.isLoad() and
exists(string name |
named_attribute_points_to(f.getObject(name), context, name, value, cls, origin)
exists(CfgOrigin orig |
origin = orig.asCfgNodeOrHere(f) and
named_attribute_points_to(f.getObject(name), context, name, value, cls, orig)
)
or
/* Static methods on the class of the instance */
exists(CallNode sm, ClassObject icls |
@@ -610,7 +699,6 @@ module PointsTo {
points_to(obj_node, context, x, icls, _) and
(not x instanceof ModuleObject and not x instanceof ClassObject) and
not icls.isBuiltin() and
Types::class_has_attribute_bool(icls, name) = false and
value = unknownValue() and cls = theUnknownType() and origin = f
)
)
@@ -631,10 +719,10 @@ module PointsTo {
private predicate attribute_load_points_to(AttrNode f, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) {
instance_attribute_load_points_to(f, context, value, cls, origin)
or
exists(Object cls_or_mod, string name, ObjectOrCfg orig |
exists(Object cls_or_mod, string name, CfgOrigin orig |
receiver_object(f, context, cls_or_mod, name) and
class_or_module_attribute(cls_or_mod, name, value, cls, orig) and
origin = origin_from_object_or_here(orig, f)
origin = orig.asCfgNodeOrHere(f)
)
or
points_to(f.getObject(), context, unknownValue(), theUnknownType(), origin) and value = unknownValue() and cls = theUnknownType()
@@ -662,24 +750,38 @@ module PointsTo {
/** Holds if `f` is a "from import" expression, `from mod import x` and points to `(value, cls, origin)`. */
pragma [nomagic]
private predicate from_import_points_to(ImportMemberNode f, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) {
exists(EssaVariable var, ObjectOrCfg orig |
live_import_from_dot_in_init(f, var) and
ssa_variable_points_to(var, context, value, cls, orig) and
origin = origin_from_object_or_here(orig, f)
exists(string name, ModuleObject mod, CfgOrigin orig |
points_to(f.getModule(name), context, mod, _, _) and
origin = orig.asCfgNodeOrHere(f)
|
mod.getSourceModule() = f.getEnclosingModule() and
not exists(EssaVariable var | var.getSourceVariable().getName() = name and var.getAUse() = f) and
exists(EssaVariable dollar |
isModuleStateVariable(dollar) and dollar.getAUse() = f and
SSA::ssa_variable_named_attribute_points_to(dollar, context, name, value, cls, orig)
)
or
not mod.getSourceModule() = f.getEnclosingModule() and
Layer::module_attribute_points_to(mod, name, value, cls, orig)
)
or
not live_import_from_dot_in_init(f, _) and
exists(string name, ModuleObject mod |
points_to(f.getModule(name), context, mod, _, _) |
exists(ObjectOrCfg orig |
Layer::module_attribute_points_to(mod, name, value, cls, orig) and
origin = origin_from_object_or_here(orig, f)
)
exists(EssaVariable var, CfgOrigin orig |
var = ssa_variable_for_module_attribute(f, context) and
ssa_variable_points_to(var, context, value, cls, orig) and
origin = orig.asCfgNodeOrHere(f)
)
}
private EssaVariable ssa_variable_for_module_attribute(ImportMemberNode f, PointsToContext context) {
exists(string name, ModuleObject mod, Module m |
mod.getSourceModule() = m and m = f.getEnclosingModule() and m = result.getScope() and
points_to(f.getModule(name), context, mod, _, _) and
result.getSourceVariable().getName() = name and result.getAUse() = f
)
}
/** Holds if `f` is of the form `getattr(x, "name")` and x.name points to `(value, cls, origin)`. */
private predicate getattr_points_to(CallNode f, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) {
private predicate getattr_points_to(CallNode f, PointsToContext context, Object value, ClassObject cls, CfgOrigin origin) {
exists(ControlFlowNode arg, string name |
named_attribute_points_to(arg, context, name, value, cls, origin) and
getattr(f, arg, name)
@@ -967,7 +1069,7 @@ module PointsTo {
)
}
predicate named_attribute_points_to(ControlFlowNode f, PointsToContext context, string name, Object value, ClassObject cls, ControlFlowNode origin) {
predicate named_attribute_points_to(ControlFlowNode f, PointsToContext context, string name, Object value, ClassObject cls, CfgOrigin origin) {
exists(EssaVariable var |
var.getAUse() = f |
SSA::ssa_variable_named_attribute_points_to(var, context, name, value, cls, origin)
@@ -1011,9 +1113,9 @@ module PointsTo {
pragma [noinline]
predicate call_points_to_builtin_function(CallNode f, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) {
exists(BuiltinCallable b |
b != builtin_object("isinstance") and
b != builtin_object("issubclass") and
b != builtin_object("callable") and
b != Object::builtin("isinstance") and
b != Object::builtin("issubclass") and
b != Object::builtin("callable") and
f = get_a_call(b, context) and
cls = b.getAReturnType()
) and
@@ -1373,7 +1475,7 @@ module PointsTo {
/** Holds if the phi-function `phi` refers to `(value, cls, origin)` given the context `context`. */
pragma [noinline]
private predicate ssa_phi_points_to(PhiFunction phi, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) {
private predicate ssa_phi_points_to(PhiFunction phi, PointsToContext context, Object value, ClassObject cls, CfgOrigin origin) {
exists(EssaVariable input, BasicBlock pred |
input = phi.getInput(pred) and
ssa_variable_points_to(input, context, value, cls, origin)
@@ -1387,10 +1489,13 @@ module PointsTo {
}
/** Holds if the ESSA definition `def` refers to `(value, cls, origin)` given the context `context`. */
predicate ssa_definition_points_to(EssaDefinition def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) {
predicate ssa_definition_points_to(EssaDefinition def, PointsToContext context, Object value, ClassObject cls, CfgOrigin origin) {
ssa_phi_points_to(def, context, value, cls, origin)
or
ssa_node_definition_points_to(def, context, value, cls, origin)
exists(ControlFlowNode orig |
ssa_node_definition_points_to(def, context, value, cls, orig) and
origin = CfgOrigin::fromCfgNode(orig)
)
or
Filters::ssa_filter_definition_points_to(def, context, value, cls, origin)
or
@@ -1398,7 +1503,7 @@ module PointsTo {
}
pragma [nomagic]
private predicate ssa_node_definition_points_to_unpruned(EssaNodeDefinition def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) {
private predicate ssa_node_definition_points_to_unpruned(EssaNodeDefinition def, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) {
assignment_points_to(def, context, value, cls, origin)
or
parameter_points_to(def, context, value, cls, origin)
@@ -1407,12 +1512,12 @@ module PointsTo {
or
delete_points_to(def, context, value, cls, origin)
or
module_name_points_to(def, context, value, cls, origin)
or
scope_entry_points_to(def, context, value, cls, origin)
or
implicit_submodule_points_to(def, context, value, cls, origin)
or
module_name_points_to(def, context, value, cls, origin)
or
iteration_definition_points_to(def, context, value, cls, origin)
/*
* No points-to for non-local function entry definitions yet.
@@ -1425,13 +1530,13 @@ module PointsTo {
}
pragma [noinline]
private predicate ssa_node_definition_points_to(EssaNodeDefinition def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) {
private predicate ssa_node_definition_points_to(EssaNodeDefinition def, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) {
reachable_definitions(def) and
ssa_node_definition_points_to_unpruned(def, context, value, cls, origin)
}
pragma [noinline]
private predicate ssa_node_refinement_points_to(EssaNodeRefinement def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) {
private predicate ssa_node_refinement_points_to(EssaNodeRefinement def, PointsToContext context, Object value, ClassObject cls, CfgOrigin origin) {
method_callsite_points_to(def, context, value, cls, origin)
or
import_star_points_to(def, context, value, cls, origin)
@@ -1570,8 +1675,9 @@ module PointsTo {
neither_class_nor_static_method(scope)
)
or
exists(EssaVariable obj, PointsToContext caller |
ssa_variable_points_to(obj, caller, value, cls, origin) and
exists(EssaVariable obj, PointsToContext caller, CfgOrigin orig |
origin = orig.asCfgNodeOrHere(def.getDefiningNode()) and
ssa_variable_points_to(obj, caller, value, cls, orig) and
callsite_self_argument_transfer(obj, caller, def, context)
)
or
@@ -1627,7 +1733,7 @@ module PointsTo {
* PointsTo isn't exactly how the interpreter works, but is the best approximation we can manage statically.
*/
pragma [noinline]
private predicate implicit_submodule_points_to(ImplicitSubModuleDefinition def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) {
private predicate implicit_submodule_points_to(ImplicitSubModuleDefinition def, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) {
exists(PackageObject package |
package.getInitModule().getModule() = def.getDefiningNode().getScope() |
value = package.submodule(def.getSourceVariable().getName()) and
@@ -1639,7 +1745,7 @@ module PointsTo {
/** Implicit "definition" of `__name__` at the start of a module. */
pragma [noinline]
private predicate module_name_points_to(ScopeEntryDefinition def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) {
private predicate module_name_points_to(ScopeEntryDefinition def, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) {
def.getVariable().getName() = "__name__" and
exists(Module m |
m = def.getScope()
@@ -1663,24 +1769,25 @@ module PointsTo {
/** Definition of iteration variable in loop */
pragma [noinline]
private predicate iteration_definition_points_to(IterationDefinition def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) {
private predicate iteration_definition_points_to(IterationDefinition def, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) {
points_to(def.getSequence(), context, unknownValue(), _, _) and
value = unknownValue() and cls = theUnknownType() and origin = def.getDefiningNode()
}
/** Points-to for implicit variable declarations at scope-entry. */
pragma [noinline]
private predicate scope_entry_points_to(ScopeEntryDefinition def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) {
private predicate scope_entry_points_to(ScopeEntryDefinition def, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) {
/* Transfer from another scope */
exists(EssaVariable var, PointsToContext outer |
exists(EssaVariable var, PointsToContext outer, CfgOrigin orig |
Flow::scope_entry_value_transfer(var, outer, def, context) and
ssa_variable_points_to(var, outer, value, cls, origin)
ssa_variable_points_to(var, outer, value, cls, orig) and
origin = orig.asCfgNodeOrHere(def.getDefiningNode())
)
or
/* Undefined variable */
exists(Scope scope |
not def.getVariable().getName() = "__name__" and
not def.getVariable().getName() = "*" and
not def.getVariable().getName() = "$" and
def.getScope() = scope and context.appliesToScope(scope) |
def.getSourceVariable() instanceof GlobalVariable and scope instanceof Module
or
@@ -1694,7 +1801,7 @@ module PointsTo {
mod = def.getScope().getEnclosingModule() and
context.appliesToScope(def.getScope()) and
not exists(EssaVariable v | v.getSourceVariable() = var and v.getScope() = mod) and
builtin_name_points_to(var.getId(), value, cls) and origin = value
builtin_name_points_to(var.getId(), value, cls) and origin = def.getDefiningNode()
)
}
@@ -1703,39 +1810,54 @@ module PointsTo {
* Where var may be redefined in call to `foo` if `var` escapes (is global or non-local).
*/
pragma [noinline]
private predicate callsite_points_to(CallsiteRefinement def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) {
exists(EssaVariable var, PointsToContext callee |
Flow::callsite_exit_value_transfer(var, callee, def, context) and
ssa_variable_points_to(var, callee, value, cls, origin)
private predicate callsite_points_to(CallsiteRefinement def, PointsToContext context, Object value, ClassObject cls, CfgOrigin origin) {
exists(SsaSourceVariable srcvar |
srcvar = def.getSourceVariable() |
if srcvar instanceof EscapingAssignmentGlobalVariable then (
/* If global variable can be reassigned, we need to track it through calls */
exists(EssaVariable var, PointsToContext callee |
Flow::callsite_exit_value_transfer(var, callee, def, context) and
ssa_variable_points_to(var, callee, value, cls, origin)
)
or
callsite_points_to_python(def, context, value, cls, origin)
or
callsite_points_to_builtin(def, context, value, cls, origin)
) else (
/* Otherwise we can assume its value (but not those of its attributes or members) has not changed. */
ssa_variable_points_to(def.getInput(), context, value, cls, origin)
)
)
or
callsite_points_to_python(def, context, value, cls, origin)
or
callsite_points_to_builtin(def, context, value, cls, origin)
}
pragma [noinline]
private predicate callsite_points_to_python(CallsiteRefinement def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) {
ssa_variable_points_to(def.getInput(), context, value, cls, origin) and
exists(CallNode call, PythonSsaSourceVariable var |
/** Holds if `call`, in `context` is a call to a function that does not modify the refined variable */
private predicate call_to_safe_function(CallsiteRefinement def, PointsToContext context) {
exists(CallNode call |
call = def.getCall() and
var = def.getSourceVariable() and
context.untrackableCall(call) and
exists(PyFunctionObject modifier, Function f |
f = modifier.getFunction() and
call = get_a_call(modifier, context) and
not modifies_escaping_variable(f, var)
not modifies_escaping_variable(f, def.getSourceVariable())
)
)
}
private predicate callsite_points_to_python(CallsiteRefinement def, PointsToContext context, Object value, ClassObject cls, CfgOrigin origin) {
exists(EssaVariable input |
input = def.getInput() and
ssa_variable_points_to(input, context, value, cls, origin) and
call_to_safe_function(def, context)
)
}
private predicate modifies_escaping_variable(Function modifier, PythonSsaSourceVariable var) {
exists(var.redefinedAtCallSite()) and
modifier.getBody().contains(var.(Variable).getAStore())
}
pragma [noinline]
private predicate callsite_points_to_builtin(CallsiteRefinement def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) {
private predicate callsite_points_to_builtin(CallsiteRefinement def, PointsToContext context, Object value, ClassObject cls, CfgOrigin origin) {
ssa_variable_points_to(def.getInput(), context, value, cls, origin) and
exists(CallNode call |
call = def.getCall() |
@@ -1745,51 +1867,55 @@ module PointsTo {
}
/** Pass through for `self` for the implicit re-definition of `self` in `self.foo()`. */
private predicate method_callsite_points_to(MethodCallsiteRefinement def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) {
private predicate method_callsite_points_to(MethodCallsiteRefinement def, PointsToContext context, Object value, ClassObject cls, CfgOrigin origin) {
/* The value of self remains the same, only the attributes may change */
ssa_variable_points_to(def.getInput(), context, value, cls, origin)
}
/** Points-to for `from ... import *`. */
private predicate import_star_points_to(ImportStarRefinement def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) {
exists(ModuleObject mod, string name |
Flow::module_and_name_for_import_star(mod, name, def, context) |
/* Attribute from imported module */
module_exports(mod, name) and
Layer::module_attribute_points_to(mod, name, value, cls, origin)
)
or
exists(EssaVariable var |
/* Retain value held before import */
Flow::variable_not_redefined_by_import_star(var, context, def) and
ssa_variable_points_to(var, context, value, cls, origin)
private predicate import_star_points_to(ImportStarRefinement def, PointsToContext context, Object value, ClassObject cls, CfgOrigin origin) {
exists(CfgOrigin orig |
origin = orig.fix(def.getDefiningNode())
|
exists(ModuleObject mod, string name |
Flow::module_and_name_for_import_star(mod, name, def, context) |
/* Attribute from imported module */
module_exports(mod, name) and
Layer::module_attribute_points_to(mod, name, value, cls, orig)
)
or
exists(EssaVariable var |
/* Retain value held before import */
Flow::variable_not_redefined_by_import_star(var, context, def) and
ssa_variable_points_to(var, context, value, cls, orig)
)
)
}
/** Attribute assignments have no effect as far as value tracking is concerned, except for `__class__`. */
pragma [noinline]
private predicate attribute_assignment_points_to(AttributeAssignment def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) {
private predicate attribute_assignment_points_to(AttributeAssignment def, PointsToContext context, Object value, ClassObject cls, CfgOrigin origin) {
if def.getName() = "__class__" then
ssa_variable_points_to(def.getInput(), context, value, _, _) and points_to(def.getValue(), _, cls, _,_) and
origin = def.getDefiningNode()
origin = CfgOrigin::fromCfgNode(def.getDefiningNode())
else
ssa_variable_points_to(def.getInput(), context, value, cls, origin)
}
/** Ignore the effects of calls on their arguments. PointsTo is an approximation, but attempting to improve accuracy would be very expensive for very little gain. */
pragma [noinline]
private predicate argument_points_to(ArgumentRefinement def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) {
private predicate argument_points_to(ArgumentRefinement def, PointsToContext context, Object value, ClassObject cls, CfgOrigin origin) {
ssa_variable_points_to(def.getInput(), context, value, cls, origin)
}
/** Attribute deletions have no effect as far as value tracking is concerned. */
pragma [noinline]
private predicate attribute_delete_points_to(EssaAttributeDeletion def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) {
private predicate attribute_delete_points_to(EssaAttributeDeletion def, PointsToContext context, Object value, ClassObject cls, CfgOrigin origin) {
ssa_variable_points_to(def.getInput(), context, value, cls, origin)
}
/* Data flow for attributes. These mirror the "normal" points-to predicates.
* For each points-to predicate `xxx_points_to(XXX def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin)`
* For each points-to predicate `xxx_points_to(XXX def, PointsToContext context, Object value, ClassObject cls, CfgOrigin origin)`
* There is an equivalent predicate that tracks the values in attributes:
* `xxx_named_attribute_points_to(XXX def, PointsToContext context, string name, Object value, ClassObject cls, ControlFlowNode origin)`
* */
@@ -1798,15 +1924,18 @@ module PointsTo {
*
* Hold if the attribute `name` of the ssa variable `var` refers to `(value, cls, origin)`.
*/
predicate ssa_variable_named_attribute_points_to(EssaVariable var, PointsToContext context, string name, Object value, ClassObject cls, ControlFlowNode origin) {
predicate ssa_variable_named_attribute_points_to(EssaVariable var, PointsToContext context, string name, Object value, ClassObject cls, CfgOrigin origin) {
ssa_definition_named_attribute_points_to(var.getDefinition(), context, name, value, cls, origin)
}
/** Helper for `ssa_variable_named_attribute_points_to`. */
private predicate ssa_definition_named_attribute_points_to(EssaDefinition def, PointsToContext context, string name, Object value, ClassObject cls, ControlFlowNode origin) {
private predicate ssa_definition_named_attribute_points_to(EssaDefinition def, PointsToContext context, string name, Object value, ClassObject cls, CfgOrigin origin) {
ssa_phi_named_attribute_points_to(def, context, name, value, cls, origin)
or
ssa_node_definition_named_attribute_points_to(def, context, name, value, cls, origin)
exists(ControlFlowNode orig |
ssa_node_definition_named_attribute_points_to(def, context, name, value, cls, orig) and
origin = CfgOrigin::fromCfgNode(orig)
)
or
ssa_node_refinement_named_attribute_points_to(def, context, name, value, cls, origin)
or
@@ -1815,7 +1944,7 @@ module PointsTo {
/** Holds if the attribute `name` of the ssa phi-function definition `phi` refers to `(value, cls, origin)`. */
pragma[noinline]
private predicate ssa_phi_named_attribute_points_to(PhiFunction phi, PointsToContext context, string name, Object value, ClassObject cls, ControlFlowNode origin) {
private predicate ssa_phi_named_attribute_points_to(PhiFunction phi, PointsToContext context, string name, Object value, ClassObject cls, CfgOrigin origin) {
ssa_variable_named_attribute_points_to(phi.getAnInput(), context, name, value, cls, origin)
}
@@ -1833,7 +1962,7 @@ module PointsTo {
/** Helper for `ssa_definition_named_attribute_points_to`. */
pragma[noinline]
private predicate ssa_node_refinement_named_attribute_points_to(EssaNodeRefinement def, PointsToContext context, string name, Object value, ClassObject cls, ControlFlowNode origin) {
private predicate ssa_node_refinement_named_attribute_points_to(EssaNodeRefinement def, PointsToContext context, string name, Object value, ClassObject cls, CfgOrigin origin) {
attribute_assignment_named_attribute_points_to(def, context, name, value, cls, origin)
or
attribute_delete_named_attribute_points_to(def, context, name, value, cls, origin)
@@ -1847,13 +1976,14 @@ module PointsTo {
pragma[noinline]
private predicate scope_entry_named_attribute_points_to(ScopeEntryDefinition def, PointsToContext context, string name, Object value, ClassObject cls, ControlFlowNode origin) {
exists(EssaVariable var, PointsToContext outer |
exists(EssaVariable var, PointsToContext outer, CfgOrigin orig |
Flow::scope_entry_value_transfer(var, outer, def, context) and
ssa_variable_named_attribute_points_to(var, outer, name, value, cls, origin)
ssa_variable_named_attribute_points_to(var, outer, name, value, cls, orig) and
origin = orig.asCfgNodeOrHere(def.getDefiningNode())
)
or
origin = def.getDefiningNode() and
def.getSourceVariable().getName() = "*" and
isModuleStateVariable(def.getVariable()) and
context.isImport() and
exists(PackageObject package |
package.getInitModule().getModule() = def.getScope() |
@@ -1865,12 +1995,15 @@ module PointsTo {
pragma[noinline]
private predicate assignment_named_attribute_points_to(AssignmentDefinition def, PointsToContext context, string name, Object value, ClassObject cls, ControlFlowNode origin) {
named_attribute_points_to(def.getValue(), context, name, value, cls, origin)
exists(CfgOrigin orig |
named_attribute_points_to(def.getValue(), context, name, value, cls, orig) and
origin = orig.asCfgNodeOrHere(def.getDefiningNode())
)
}
pragma[noinline]
private predicate attribute_assignment_named_attribute_points_to(AttributeAssignment def, PointsToContext context, string name, Object value, ClassObject cls, ControlFlowNode origin) {
points_to(def.getValue(), context, value, cls, origin) and name = def.getName()
private predicate attribute_assignment_named_attribute_points_to(AttributeAssignment def, PointsToContext context, string name, Object value, ClassObject cls, CfgOrigin origin) {
points_to(def.getValue(), context, value, cls, origin.toCfgNode()) and name = def.getName()
or
ssa_variable_named_attribute_points_to(def.getInput(), context, name, value, cls, origin) and not name = def.getName()
}
@@ -1883,9 +2016,9 @@ module PointsTo {
exists(ControlFlowNode func, Object obj |
two_args_first_arg_string(def, func, name) and
points_to(func, _, obj, _, _) |
obj = builtin_object("setattr") and result = true
obj = Object::builtin("setattr") and result = true
or
obj != builtin_object("setattr") and result = false
obj != Object::builtin("setattr") and result = false
)
}
@@ -1899,10 +2032,10 @@ module PointsTo {
}
pragma[noinline]
private predicate argument_named_attribute_points_to(ArgumentRefinement def, PointsToContext context, string name, Object value, ClassObject cls, ObjectOrCfg origin) {
private predicate argument_named_attribute_points_to(ArgumentRefinement def, PointsToContext context, string name, Object value, ClassObject cls, CfgOrigin origin) {
not two_args_first_arg_string(def, _, name) and ssa_variable_named_attribute_points_to(def.getInput(), context, name, value, cls, origin)
or
sets_attribute(def, name) = true and points_to(def.getDefiningNode().(CallNode).getArg(2), context, value, cls, origin)
sets_attribute(def, name) = true and points_to(def.getDefiningNode().(CallNode).getArg(2), context, value, cls, origin.toCfgNode())
or
sets_attribute(def, name) = false and ssa_variable_named_attribute_points_to(def.getInput(), context, name, value, cls, origin)
}
@@ -1920,7 +2053,7 @@ module PointsTo {
}
pragma[noinline]
private predicate self_callsite_named_attribute_points_to(SelfCallsiteRefinement def, PointsToContext context, string name, Object value, ClassObject cls, ObjectOrCfg origin) {
private predicate self_callsite_named_attribute_points_to(SelfCallsiteRefinement def, PointsToContext context, string name, Object value, ClassObject cls, CfgOrigin origin) {
exists(EssaVariable var, PointsToContext callee |
ssa_variable_named_attribute_points_to(var, callee, name, value, cls, origin) and
callee_self_variable(var, callee, def, context)
@@ -1939,17 +2072,28 @@ module PointsTo {
)
}
/** Maps the caller object/context to callee parameter/context for self in calls to methods */
private predicate self_in_method_call(ControlFlowNode obj, PointsToContext caller, ParameterDefinition self, PointsToContext callee) {
self.isSelf() and
exists(FunctionObject meth, CallNode call |
meth.getFunction() = self.getScope() and
callee.fromCall(call, meth, caller) and
call.getFunction().(AttrNode).getObject() = obj
)
}
pragma [noinline]
private predicate self_parameter_named_attribute_points_to(ParameterDefinition def, PointsToContext context, string name, Object value, ClassObject vcls, ControlFlowNode origin) {
context.isRuntime() and executes_in_runtime_context(def.getScope()) and
ssa_variable_named_attribute_points_to(preceding_self_variable(def), context, name, value, vcls, origin)
or
exists(FunctionObject meth, CallNode call, PointsToContext caller_context, ControlFlowNode obj |
meth.getFunction() = def.getScope() and
method_call(meth, caller_context, call) and
call.getFunction().(AttrNode).getObject() = obj and
context.fromCall(call, meth, caller_context) and
named_attribute_points_to(obj, caller_context, name, value, vcls, origin)
exists(CfgOrigin orig |
origin = orig.toCfgNode()
|
context.isRuntime() and executes_in_runtime_context(def.getScope()) and
ssa_variable_named_attribute_points_to(preceding_self_variable(def), context, name, value, vcls, orig)
or
exists(PointsToContext caller_context, ControlFlowNode obj |
self_in_method_call(obj, caller_context, def, context) and
named_attribute_points_to(obj, caller_context, name, value, vcls, orig)
)
)
}
@@ -1957,14 +2101,14 @@ module PointsTo {
none()
}
private predicate attribute_delete_named_attribute_points_to(EssaAttributeDeletion def, PointsToContext context, string name, Object value, ClassObject cls, ControlFlowNode origin) {
private predicate attribute_delete_named_attribute_points_to(EssaAttributeDeletion def, PointsToContext context, string name, Object value, ClassObject cls, CfgOrigin origin) {
none()
}
/* Helper for import_star_named_attribute_points_to */
pragma [noinline]
private predicate star_variable_import_star_module(ImportStarRefinement def, ImportStarNode imp, PointsToContext context, ModuleObject mod) {
def.getSourceVariable().getName() = "*" and
isModuleStateVariable(def.getVariable()) and
exists(ControlFlowNode fmod |
fmod = imp.getModule() and
imp = def.getDefiningNode() and
@@ -1974,7 +2118,7 @@ module PointsTo {
/* Helper for import_star_named_attribute_points_to */
pragma [noinline, nomagic]
private predicate ssa_star_variable_input_points_to(ImportStarRefinement def, PointsToContext context, string name, Object value, ClassObject cls, ControlFlowNode origin) {
private predicate ssa_star_variable_input_points_to(ImportStarRefinement def, PointsToContext context, string name, Object value, ClassObject cls, CfgOrigin origin) {
exists(EssaVariable var |
ssa_star_import_star_input(def, var) and
ssa_variable_named_attribute_points_to(var, context, name, value, cls, origin)
@@ -1984,24 +2128,22 @@ module PointsTo {
/* Helper for ssa_star_variable_input_points_to */
pragma [noinline]
private predicate ssa_star_import_star_input(ImportStarRefinement def, EssaVariable var) {
def.getSourceVariable().getName() = "*" and var = def.getInput()
isModuleStateVariable(def.getVariable()) and var = def.getInput()
}
pragma [noinline]
private predicate import_star_named_attribute_points_to(ImportStarRefinement def, PointsToContext context, string name, Object value, ClassObject cls, ControlFlowNode origin) {
exists(ImportStarNode imp, ModuleObject mod |
private predicate import_star_named_attribute_points_to(ImportStarRefinement def, PointsToContext context, string name, Object value, ClassObject cls, CfgOrigin origin) {
exists(ImportStarNode imp, ModuleObject mod, CfgOrigin orig |
origin = orig.fix(imp) and
star_variable_import_star_module(def, imp, context, mod) |
/* Attribute from imported module */
module_exports_boolean(mod, name) = true and
exists(ObjectOrCfg obj |
Layer::module_attribute_points_to(mod, name, value, cls, obj) and
not exists(Variable v | v.getId() = name and v.getScope() = imp.getScope()) and
origin = origin_from_object_or_here(obj, imp)
)
Layer::module_attribute_points_to(mod, name, value, cls, orig) and
not exists(Variable v | v.getId() = name and v.getScope() = imp.getScope())
or
/* Retain value held before import */
module_exports_boolean(mod, name) = false and
ssa_star_variable_input_points_to(def, context, name, value, cls, origin)
ssa_star_variable_input_points_to(def, context, name, value, cls, orig)
)
}
@@ -2117,10 +2259,15 @@ module PointsTo {
private boolean truth_test_evaluates_boolean(ControlFlowNode expr, ControlFlowNode use, PointsToContext context, Object val, ClassObject cls, ControlFlowNode origin) {
contains_interesting_expression_within_test(expr, use) and
points_to(use, context, val, cls, origin) and
expr = use and
(
expr = use and val.booleanValue() = result
val.booleanValue() = result
or
expr = use and Types::instances_always_true(cls) and result = true
Types::instances_always_true(cls) and result = true
or
val.maybe() and result = true
or
val.maybe() and result = false
)
}
@@ -2211,7 +2358,7 @@ module PointsTo {
(
exists(CallNode call |
call = expr and
points_to(call.getFunction(), context, theLenFunction(), _, _) and
points_to(call.getFunction(), context, Object::builtin("len"), _, _) and
use = call.getArg(0) and
val.(SequenceObject).getLength() = result
)
@@ -2227,9 +2374,9 @@ module PointsTo {
}
/** Holds if ESSA edge refinement, `def`, refers to `(value, cls, origin)`. */
predicate ssa_filter_definition_points_to(PyEdgeRefinement def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) {
predicate ssa_filter_definition_points_to(PyEdgeRefinement def, PointsToContext context, Object value, ClassObject cls, CfgOrigin origin) {
exists(ControlFlowNode test, ControlFlowNode use |
refinement_test(test, use, test_evaluates_boolean(test, use, context, value, cls, origin), def)
refinement_test(test, use, test_evaluates_boolean(test, use, context, value, cls, origin.toCfgNode()), def)
)
or
/* If we can't understand the test, assume that value passes through.
@@ -2244,23 +2391,23 @@ module PointsTo {
/** Holds if ESSA definition, `uniphi`, refers to `(value, cls, origin)`. */
pragma [noinline]
predicate uni_edged_phi_points_to(SingleSuccessorGuard uniphi, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) {
predicate uni_edged_phi_points_to(SingleSuccessorGuard uniphi, PointsToContext context, Object value, ClassObject cls, CfgOrigin origin) {
exists(ControlFlowNode test, ControlFlowNode use |
/* Because calls such as `len` may create a new variable, we need to go via the source variable
* That is perfectly safe as we are only dealing with calls that do not mutate their arguments.
*/
use = uniphi.getInput().getSourceVariable().(Variable).getAUse() and
test = uniphi.getDefiningNode() and
uniphi.getSense() = test_evaluates_boolean(test, use, context, value, cls, origin)
uniphi.getSense() = test_evaluates_boolean(test, use, context, value, cls, origin.toCfgNode())
)
}
/** Holds if the named attibute of ESSA edge refinement, `def`, refers to `(value, cls, origin)`. */
pragma[noinline]
predicate ssa_filter_definition_named_attribute_points_to(PyEdgeRefinement def, PointsToContext context, string name, Object value, ClassObject cls, ObjectOrCfg origin) {
predicate ssa_filter_definition_named_attribute_points_to(PyEdgeRefinement def, PointsToContext context, string name, Object value, ClassObject cls, CfgOrigin origin) {
exists(ControlFlowNode test, AttrNode use, boolean sense |
edge_refinement_attr_use_sense(def, test, use, name, sense) and
sense = test_evaluates_boolean(test, use, context, value, cls, origin)
sense = test_evaluates_boolean(test, use, context, value, cls, origin.toCfgNode())
)
or
exists(EssaVariable input |
@@ -2489,7 +2636,7 @@ module PointsTo {
}
/** INTERNAL -- Use `ClassObject.declaredAttribute(name). instead. */
cached predicate class_declared_attribute(ClassObject owner, string name, Object value, ClassObject vcls, ObjectOrCfg origin) {
cached predicate class_declared_attribute(ClassObject owner, string name, Object value, ClassObject vcls, CfgOrigin origin) {
/* Note that src_var must be a local variable, we aren't interested in the value that any global variable may hold */
value != undefinedVariable() and
exists(EssaVariable var, LocalVariable src_var |
@@ -2500,7 +2647,7 @@ module PointsTo {
)
or
value = builtin_class_attribute(owner, name) and class_declares_attribute(owner, name) and
origin = value and vcls = builtin_object_type(value)
origin = CfgOrigin::unknown() and vcls = builtin_object_type(value)
}
private predicate interesting_class_attribute(ClassList mro, string name) {
@@ -2551,7 +2698,7 @@ module PointsTo {
/** INTERNAL -- Use `ClassObject.attributeRefersTo(name, value, vlcs, origin). instead.
*/
cached predicate class_attribute_lookup(ClassObject cls, string name, Object value, ClassObject vcls, ObjectOrCfg origin) {
cached predicate class_attribute_lookup(ClassObject cls, string name, Object value, ClassObject vcls, CfgOrigin origin) {
exists(ClassObject defn |
defn = get_mro(cls).findDeclaringClass(name) and
class_declared_attribute(defn, name, value, vcls, origin)
@@ -2773,6 +2920,15 @@ module PointsTo {
}
/** Get the ESSA pseudo-variable used to retain module state
* during module initialization. Module attributes are handled
* as attributes of this variable, allowing the SSA form to track
* mutations of the module during its creation.
*/
private predicate isModuleStateVariable(EssaVariable var) {
var.getName() = "$" and var.getScope() instanceof Module
}
/** INTERNAL -- Public for testing only */
module Test {
@@ -2840,19 +2996,21 @@ class SuperBoundMethod extends Object {
SuperCall superObject;
string name;
ClassObject startType;
cached
SuperBoundMethod() {
exists(ControlFlowNode object |
this.(AttrNode).getObject(name) = object |
PointsTo::points_to(object, _, superObject, _, _)
PointsTo::points_to(object, _, superObject, _, _) and
startType = superObject.startType()
)
}
FunctionObject getFunction(PointsToContext ctx) {
exists(ClassList mro |
mro = PointsTo::Types::get_mro(superObject.selfType(ctx)) |
result = mro.startingAt(superObject.startType()).getTail().lookup(name)
result = mro.startingAt(startType).getTail().lookup(name)
)
}

View File

@@ -18,7 +18,7 @@ predicate used_as_regex(Expr s, string mode) {
/* Call to re.xxx(regex, ... [mode]) */
exists(CallNode call, string name |
call.getArg(0).refersTo(_, _, s.getAFlowNode()) and
call.getFunction().refersTo(re.getAttribute(name)) |
call.getFunction().refersTo(re.attr(name)) |
mode = "None"
or
exists(Object obj |
@@ -40,7 +40,7 @@ string mode_from_mode_object(Object obj) {
result = "MULTILINE" or result = "DOTALL" or result = "UNICODE" or
result = "VERBOSE"
) and
ModuleObject::named("sre_constants").getAttribute("SRE_FLAG_" + result) = obj
obj = ModuleObject::named("sre_constants").attr("SRE_FLAG_" + result)
or
exists(BinaryExpr be, Object sub | obj.getOrigin() = be |
be.getOp() instanceof BitOr and

View File

@@ -95,7 +95,7 @@ module Cryptography {
class CipherClass extends ClassObject {
CipherClass() {
ciphers().getAttribute("Cipher") = this
ciphers().attr("Cipher") = this
}
}
@@ -103,7 +103,7 @@ module Cryptography {
class AlgorithmClass extends ClassObject {
AlgorithmClass() {
ciphers().submodule("algorithms").getAttribute(_) = this
ciphers().submodule("algorithms").attr(_) = this
}
string getAlgorithmName() {

View File

@@ -12,7 +12,7 @@ private ModuleObject theTracebackModule() {
}
private FunctionObject traceback_function(string name) {
result = theTracebackModule().getAttribute(name)
result = theTracebackModule().attr(name)
}
/**

View File

@@ -242,11 +242,11 @@ private predicate copy_call(ControlFlowNode fromnode, CallNode tonode) {
or
exists(ModuleObject copy, string name |
name = "copy" or name = "deepcopy" |
copy.getAttribute(name).(FunctionObject).getACall() = tonode and
copy.attr(name).(FunctionObject).getACall() = tonode and
tonode.getArg(0) = fromnode
)
or
tonode.getFunction().refersTo(builtin_object("reversed")) and
tonode.getFunction().refersTo(Object::builtin("reversed")) and
tonode.getArg(0) = fromnode
}
@@ -937,7 +937,7 @@ library module TaintFlowImplementation {
pragma [noinline]
predicate getattr_step(TaintedNode fromnode, TrackedValue totaint, CallContext tocontext, CallNode tonode) {
exists(ControlFlowNode arg, string name |
tonode.getFunction().refersTo(builtin_object("getattr")) and
tonode.getFunction().refersTo(Object::builtin("getattr")) and
arg = tonode.getArg(0) and
name = tonode.getArg(1).getNode().(StrConst).getText() and
arg = fromnode.getNode() and

View File

@@ -11,10 +11,6 @@ import semmle.python.security.TaintTracking
import semmle.python.security.strings.Untrusted
private ModuleObject subprocessModule() {
result.getName() = "subprocess"
}
private ModuleObject osOrPopenModule() {
result.getName() = "os" or
result.getName() = "popen2"
@@ -22,7 +18,7 @@ private ModuleObject osOrPopenModule() {
private Object makeOsCall() {
exists(string name |
result = subprocessModule().getAttribute(name) |
result = ModuleObject::named("subprocess").attr(name) |
name = "Popen" or
name = "call" or
name = "check_call" or
@@ -79,7 +75,7 @@ class ShellCommand extends TaintSink {
or
exists(CallNode call, string name |
call.getAnArg() = this and
call.getFunction().refersTo(osOrPopenModule().getAttribute(name)) |
call.getFunction().refersTo(osOrPopenModule().attr(name)) |
name = "system" or
name = "popen" or
name.matches("popen_")

View File

@@ -12,9 +12,9 @@ import semmle.python.security.strings.Untrusted
private FunctionObject exec_or_eval() {
result = builtin_object("exec")
result = Object::builtin("exec")
or
result = builtin_object("eval")
result = Object::builtin("eval")
}
/** A taint sink that represents an argument to exec or eval that is vulnerable to malicious input.

View File

@@ -12,7 +12,7 @@ import semmle.python.security.strings.Untrusted
private FunctionObject marshalLoads() {
result = any(ModuleObject marshal | marshal.getName() = "marshal").getAttribute("loads")
result = ModuleObject::named("marshal").attr("loads")
}

View File

@@ -20,12 +20,12 @@ private class PathSanitizer extends Sanitizer {
}
private FunctionObject abspath() {
exists(ModuleObject os, ModuleObject os_path |
os.getName() = "os" and
os.getAttribute("path") = os_path |
os_path.getAttribute("abspath") = result
exists(ModuleObject os_path |
ModuleObject::named("os").attr("path") = os_path
|
os_path.attr("abspath") = result
or
os_path.getAttribute("normpath") = result
os_path.attr("normpath") = result
)
}
@@ -84,7 +84,7 @@ class OpenNode extends TaintSink {
OpenNode() {
exists(CallNode call |
call.getFunction().refersTo(builtin_object("open")) and
call.getFunction().refersTo(Object::builtin("open")) and
call.getAnArg() = this
)
}

View File

@@ -20,7 +20,7 @@ private ModuleObject pickleModule() {
}
private FunctionObject pickleLoads() {
result = pickleModule().getAttribute("loads")
result = pickleModule().attr("loads")
}
/** `pickle.loads(untrusted)` vulnerability. */

View File

@@ -33,7 +33,7 @@ private class ExpatParser extends TaintKind {
}
private FunctionObject expatCreateParseFunction() {
result = ModuleObject::named("xml.parsers.expat").getAttribute("ParserCreate")
result = ModuleObject::named("xml.parsers.expat").attr("ParserCreate")
}
private class ExpatCreateParser extends TaintSource {
@@ -52,13 +52,13 @@ private class ExpatCreateParser extends TaintSource {
}
private FunctionObject xmlFromString() {
result = xmlElementTreeModule().getAttribute("fromstring")
result = xmlElementTreeModule().attr("fromstring")
or
result = xmlMiniDomModule().getAttribute("parseString")
result = xmlMiniDomModule().attr("parseString")
or
result = xmlPullDomModule().getAttribute("parseString")
result = xmlPullDomModule().attr("parseString")
or
result = xmlSaxModule().getAttribute("parseString")
result = xmlSaxModule().attr("parseString")
}
/** A (potentially) malicious XML string. */

View File

@@ -12,13 +12,8 @@ import semmle.python.security.TaintTracking
import semmle.python.security.strings.Untrusted
private ModuleObject yamlModule() {
result.getName() = "yaml"
}
private FunctionObject yamlLoad() {
result = yamlModule().getAttribute("load")
result = ModuleObject::named("yaml").attr("load")
}
/** `yaml.load(untrusted)` vulnerability. */

View File

@@ -110,9 +110,8 @@ private predicate slice(ControlFlowNode fromnode, SubscriptNode tonode) {
/* tonode = os.path.join(..., fromnode, ...) */
private predicate os_path_join(ControlFlowNode fromnode, CallNode tonode) {
exists(FunctionObject path_join |
exists(ModuleObject os | os.getName() = "os" |
os.getAttribute("path").(ModuleObject).getAttribute("join") = path_join
) |
path_join = ModuleObject::named("os").attr("path").(ModuleObject).attr("join")
and
tonode = path_join.getACall() and tonode.getAnArg() = fromnode
)
}

View File

@@ -7,10 +7,10 @@ predicate copy_call(ControlFlowNode fromnode, CallNode tonode) {
or
exists(ModuleObject copy, string name |
name = "copy" or name = "deepcopy" |
copy.getAttribute(name).(FunctionObject).getACall() = tonode and
copy.attr(name).(FunctionObject).getACall() = tonode and
tonode.getArg(0) = fromnode
)
or
tonode.getFunction().refersTo(builtin_object("reversed")) and
tonode.getFunction().refersTo(Object::builtin("reversed")) and
tonode.getArg(0) = fromnode
}

View File

@@ -91,7 +91,7 @@ private predicate json_subscript_taint(SubscriptNode sub, ControlFlowNode obj, E
private predicate json_load(ControlFlowNode fromnode, CallNode tonode) {
exists(FunctionObject json_loads |
any(ModuleObject json | json.getName() = "json").getAttribute("loads") = json_loads and
any(ModuleObject json | json.getName() = "json").attr("loads") = json_loads and
json_loads.getACall() = tonode and tonode.getArg(0) = fromnode
)
}

View File

@@ -10,19 +10,6 @@ predicate is_c_metaclass(Object o) {
}
library class ObjectOrCfg extends @py_object {
string toString() {
/* Not to be displayed */
none()
}
ControlFlowNode getOrigin() {
result = this
}
}
/** A class whose instances represents Python classes.
* Instances of this class represent either builtin classes
* such as `list` or `str`, or program-defined Python classes
@@ -147,13 +134,19 @@ class ClassObject extends Object {
/** Whether the named attribute refers to the object and origin */
predicate attributeRefersTo(string name, Object obj, ControlFlowNode origin) {
PointsTo::Types::class_attribute_lookup(this, name, obj, _, origin)
exists(CfgOrigin orig |
origin = orig.toCfgNode() and
PointsTo::Types::class_attribute_lookup(this, name, obj, _, orig)
)
}
/** Whether the named attribute refers to the object, class and origin */
predicate attributeRefersTo(string name, Object obj, ClassObject cls, ControlFlowNode origin) {
not obj = unknownValue() and
PointsTo::Types::class_attribute_lookup(this, name, obj, cls, origin)
exists(CfgOrigin orig |
origin = orig.toCfgNode() and
PointsTo::Types::class_attribute_lookup(this, name, obj, cls, orig)
)
}
/** Whether this class has a attribute named `name`, either declared or inherited.*/
@@ -502,9 +495,9 @@ ClassObject theUnicodeType() {
/** The builtin class '(x)range' */
ClassObject theRangeType() {
result = builtin_object("xrange")
result = Object::builtin("xrange")
or
major_version() = 3 and result = builtin_object("range")
major_version() = 3 and result = Object::builtin("range")
}
/** The builtin class for bytes. str in Python2, bytes in Python3 */
@@ -597,20 +590,20 @@ ClassObject theBuiltinPropertyType() {
/** The builtin class 'IOError' */
ClassObject theIOErrorType() {
result = builtin_object("IOError")
result = Object::builtin("IOError")
}
/** The builtin class 'super' */
ClassObject theSuperType() {
result = builtin_object("super")
result = Object::builtin("super")
}
/** The builtin class 'StopIteration' */
ClassObject theStopIterationType() {
result = builtin_object("StopIteration")
result = Object::builtin("StopIteration")
}
/** The builtin class 'NotImplementedError' */
ClassObject theNotImplementedErrorType() {
result = builtin_object("NotImplementedError")
result = Object::builtin("NotImplementedError")
}

View File

@@ -31,7 +31,7 @@ class RaisingNode extends ControlFlowNode {
}
private predicate quits() {
this.(CallNode).getFunction().refersTo(quitterObject(_))
this.(CallNode).getFunction().refersTo(Object::quitter(_))
}
/** Gets the type of an exception that may be raised
@@ -49,7 +49,7 @@ class RaisingNode extends ControlFlowNode {
pragma[noinline]
private ClassObject systemExitRaise() {
this.quits() and result = builtin_object("SystemExit")
this.quits() and result = Object::builtin("SystemExit")
}
pragma [noinline, nomagic]
@@ -62,7 +62,7 @@ class RaisingNode extends ControlFlowNode {
(ex.refersTo(result) or ex.refersTo(_, result, _))
)
or
this.getNode() instanceof ImportExpr and result = builtin_object("ImportError")
this.getNode() instanceof ImportExpr and result = Object::builtin("ImportError")
or
this.getNode() instanceof Print and result = theIOErrorType()
or

View File

@@ -348,21 +348,21 @@ class BuiltinFunctionObject extends BuiltinCallable {
override ClassObject getAReturnType() {
/* Enumerate the types of a few builtin functions, that the CPython analysis misses.
*/
this = builtin_object("hex") and result = theStrType()
this = Object::builtin("hex") and result = theStrType()
or
this = builtin_object("oct") and result = theStrType()
this = Object::builtin("oct") and result = theStrType()
or
this = builtin_object("intern") and result = theStrType()
this = Object::builtin("intern") and result = theStrType()
or
/* Fix a few minor inaccuracies in the CPython analysis */
ext_rettype(this, result) and not (
this = builtin_object("__import__") and result = theNoneType()
this = Object::builtin("__import__") and result = theNoneType()
or
this = builtin_object("compile") and result = theNoneType()
this = Object::builtin("compile") and result = theNoneType()
or
this = builtin_object("sum")
this = Object::builtin("sum")
or
this = builtin_object("filter")
this = Object::builtin("filter")
)
}
@@ -376,4 +376,53 @@ class BuiltinFunctionObject extends BuiltinCallable {
}
/** DEPRECATED -- Use `Object::builtin("apply")` instead. */
Object theApplyFunction() {
result = Object::builtin("apply")
}
/** DEPRECATED -- Use `Object::builtin("hasattr")` instead. */
Object theHasattrFunction() {
result = Object::builtin("hasattr")
}
/** DEPRECATED -- Use `Object::builtin("len")` instead. */
Object theLenFunction() {
result = Object::builtin("len")
}
/** DEPRECATED -- Use `Object::builtin("format")` instead. */
Object theFormatFunction() {
result = Object::builtin("format")
}
/** DEPRECATED -- Use `Object::builtin("open")` instead. */
Object theOpenFunction() {
result = Object::builtin("open")
}
/** DEPRECATED -- Use `Object::builtin("print")` instead. */
Object thePrintFunction() {
result = Object::builtin("print")
}
/** DEPRECATED -- Use `Object::builtin("input")` instead. */
Object theInputFunction() {
result = Object::builtin("input")
}
/** DEPRECATED -- Use `Object::builtin("locals")` instead. */
Object theLocalsFunction() {
result = Object::builtin("locals")
}
/** DEPRECATED -- Use `Object::builtin("globals")()` instead. */
Object theGlobalsFunction() {
result = Object::builtin("globals")
}
/** DEPRECATED -- Use `Object::builtin("sysExit()` instead. */
Object theExitFunctionObject() {
result = ModuleObject::named("sys").attr("exit")
}

View File

@@ -16,6 +16,11 @@ abstract class ModuleObject extends Object {
none()
}
/** Gets the source scope corresponding to this module, if this is a Python module */
Module getSourceModule() {
none()
}
Container getPath() {
none()
}
@@ -28,10 +33,18 @@ abstract class ModuleObject extends Object {
}
/** Gets the named attribute of this module. Using attributeRefersTo() instead
* may provide better results for presentation. */
* may provide better results for presentation.
* */
pragma [noinline]
abstract Object getAttribute(string name);
/** Gets the named attribute of this module.
* Synonym for `getAttribute(name)` */
pragma [inline]
final Object attr(string name) {
result = this.getAttribute(name)
}
/** Whether the named attribute of this module "refers-to" value, with a known origin.
*/
abstract predicate attributeRefersTo(string name, Object value, ControlFlowNode origin);
@@ -136,6 +149,10 @@ class PythonModuleObject extends ModuleObject {
result = this.getOrigin()
}
override Module getSourceModule() {
result = this.getOrigin()
}
override Container getPath() {
result = this.getModule().getFile()
}
@@ -165,11 +182,17 @@ class PythonModuleObject extends ModuleObject {
}
override predicate attributeRefersTo(string name, Object value, ControlFlowNode origin) {
PointsTo::py_module_attributes(this.getModule(), name, value, _, origin)
exists(CfgOrigin orig |
origin = orig.toCfgNode() and
PointsTo::py_module_attributes(this.getModule(), name, value, _, orig)
)
}
override predicate attributeRefersTo(string name, Object value, ClassObject cls, ControlFlowNode origin) {
PointsTo::py_module_attributes(this.getModule(), name, value, cls, origin)
exists(CfgOrigin orig |
origin = orig.toCfgNode() and
PointsTo::py_module_attributes(this.getModule(), name, value, cls, orig)
)
}
}
@@ -206,6 +229,10 @@ class PackageObject extends ModuleObject {
result = this.getOrigin()
}
override Module getSourceModule() {
result = this.getModule().getInitModule()
}
override Container getPath() {
exists(ModuleObject m |
m.getPackage() = this |
@@ -247,11 +274,17 @@ class PackageObject extends ModuleObject {
}
override predicate attributeRefersTo(string name, Object value, ControlFlowNode origin) {
PointsTo::package_attribute_points_to(this, name, value, _, origin)
exists(CfgOrigin orig |
origin = orig.toCfgNode() and
PointsTo::package_attribute_points_to(this, name, value, _, orig)
)
}
override predicate attributeRefersTo(string name, Object value, ClassObject cls, ControlFlowNode origin) {
PointsTo::package_attribute_points_to(this, name, value, cls, origin)
exists(CfgOrigin orig |
origin = orig.toCfgNode() and
PointsTo::package_attribute_points_to(this, name, value, cls, orig)
)
}
Location getLocation() {

View File

@@ -192,7 +192,7 @@ private Object findByName1(string longName) {
exists(string owner, string attrname |
longName = owner + "." + attrname
|
result = findByName0(owner).(ModuleObject).getAttribute(attrname)
result = findByName0(owner).(ModuleObject).attr(attrname)
or
result = findByName0(owner).(ClassObject).lookupAttribute(attrname)
)
@@ -204,7 +204,7 @@ private Object findByName2(string longName) {
exists(string owner, string attrname |
longName = owner + "." + attrname
|
result = findByName1(owner).(ModuleObject).getAttribute(attrname)
result = findByName1(owner).(ModuleObject).attr(attrname)
or
result = findByName1(owner).(ClassObject).lookupAttribute(attrname)
)
@@ -216,7 +216,7 @@ private Object findByName3(string longName) {
exists(string owner, string attrname |
longName = owner + "." + attrname
|
result = findByName2(owner).(ModuleObject).getAttribute(attrname)
result = findByName2(owner).(ModuleObject).attr(attrname)
or
result = findByName2(owner).(ClassObject).lookupAttribute(attrname)
)
@@ -354,6 +354,14 @@ class TupleObject extends SequenceObject {
}
module TupleObject {
TupleObject empty() {
py_cobjecttypes(result, theTupleType()) and not py_citems(result, _, _)
}
}
class NonEmptyTupleObject extends TupleObject {
NonEmptyTupleObject() {
@@ -389,108 +397,82 @@ BuiltinModuleObject theSysModuleObject() {
py_special_objects(result, "sys")
}
/** DEPRECATED -- Use `Object::builtin(name)` instead. */
deprecated
Object builtin_object(string name) {
py_cmembers_versioned(theBuiltinModuleObject(), name, result, major_version().toString())
result = Object::builtin(name)
}
/** The built-in object None */
Object theNoneObject() {
Object theNoneObject() {
py_special_objects(result, "None")
}
/** The built-in object True */
Object theTrueObject() {
Object theTrueObject() {
py_special_objects(result, "True")
}
/** The built-in object False */
Object theFalseObject() {
Object theFalseObject() {
py_special_objects(result, "False")
}
/** The builtin function apply (Python 2 only) */
Object theApplyFunction() {
result = builtin_object("apply")
}
/** The builtin function hasattr */
Object theHasattrFunction() {
result = builtin_object("hasattr")
}
/** The builtin function len */
Object theLenFunction() {
result = builtin_object("len")
}
/** The builtin function format */
Object theFormatFunction() {
result = builtin_object("format")
}
/** The builtin function open */
Object theOpenFunction() {
result = builtin_object("open")
}
/** The builtin function print (Python 2.7 upwards) */
Object thePrintFunction() {
result = builtin_object("print")
}
/** The builtin function input (Python 2 only) */
Object theInputFunction() {
result = builtin_object("input")
}
/** The builtin function locals */
Object theLocalsFunction() {
py_special_objects(result, "locals")
}
/** The builtin function globals */
Object theGlobalsFunction() {
py_special_objects(result, "globals")
}
/** The builtin function sys.exit */
Object theExitFunctionObject() {
py_cmembers_versioned(theSysModuleObject(), "exit", result, major_version().toString())
}
/** The NameError class */
Object theNameErrorType() {
result = builtin_object("NameError")
result = Object::builtin("NameError")
}
/** The StandardError class */
Object theStandardErrorType() {
result = builtin_object("StandardError")
result = Object::builtin("StandardError")
}
/** The IndexError class */
Object theIndexErrorType() {
result = builtin_object("IndexError")
result = Object::builtin("IndexError")
}
/** The LookupError class */
Object theLookupErrorType() {
result = builtin_object("LookupError")
result = Object::builtin("LookupError")
}
/** The named quitter object (quit or exit) in the builtin namespace */
/** DEPRECATED -- Use `Object::quitter(name)` instead. */
deprecated
Object quitterObject(string name) {
(name = "quit" or name = "exit") and
result = builtin_object(name)
result = Object::quitter(name)
}
/** The builtin object `NotImplemented`. Not be confused with `NotImplementedError`. */
/** DEPRECATED -- Use `Object::notImplemented()` instead. */
deprecated
Object theNotImplementedObject() {
result = builtin_object("NotImplemented")
result = Object::builtin("NotImplemented")
}
/** DEPRECATED -- Use `TupleObject::empty()` instead. */
deprecated
Object theEmptyTupleObject() {
py_cobjecttypes(result, theTupleType()) and not py_citems(result, _, _)
result = TupleObject::empty()
}
module Object {
Object builtin(string name) {
py_cmembers_versioned(theBuiltinModuleObject(), name, result, major_version().toString())
}
/** The named quitter object (quit or exit) in the builtin namespace */
Object quitter(string name) {
(name = "quit" or name = "exit") and
result = builtin(name)
}
/** The builtin object `NotImplemented`. Not be confused with `NotImplementedError`. */
Object notImplemented() {
result = builtin("NotImplemented")
}
}

View File

@@ -18,7 +18,7 @@ predicate tracked_object(ControlFlowNode obj, string attr) {
}
predicate open_file(Object obj) {
obj.(CallNode).getFunction().refersTo(theOpenFunction())
obj.(CallNode).getFunction().refersTo(Object::builtin("open"))
}
predicate string_attribute_any(ControlFlowNode n, string attr) {
@@ -26,9 +26,9 @@ predicate string_attribute_any(ControlFlowNode n, string attr) {
exists(Object input |
n.(CallNode).getFunction().refersTo(input) |
if major_version() = 2 then
input = builtin_object("raw_input")
input = Object::builtin("raw_input")
else
input = theInputFunction()
input = Object::builtin("input")
)
or
attr = "file-input" and

View File

@@ -6,3 +6,4 @@ import semmle.python.web.django.Redirect
import semmle.python.web.flask.Redirect
import semmle.python.web.tornado.Redirect
import semmle.python.web.pyramid.Redirect
import semmle.python.web.bottle.Redirect

View File

@@ -3,3 +3,4 @@ import semmle.python.web.flask.Request
import semmle.python.web.tornado.Request
import semmle.python.web.pyramid.Request
import semmle.python.web.twisted.Request
import semmle.python.web.bottle.Request

View File

@@ -3,3 +3,4 @@ import semmle.python.web.flask.Response
import semmle.python.web.pyramid.Response
import semmle.python.web.tornado.Response
import semmle.python.web.twisted.Response
import semmle.python.web.bottle.Response

View File

@@ -0,0 +1,79 @@
import python
import semmle.python.web.Http
import semmle.python.types.Extensions
/** The bottle module */
ModuleObject theBottleModule() {
result = ModuleObject::named("bottle")
}
/** The bottle.Bottle class */
ClassObject theBottleClass() {
result = ModuleObject::named("bottle").attr("Bottle")
}
/** Holds if `route` is routed to `func`
* by decorating `func` with `app.route(route)` or `route(route)`
*/
predicate bottle_route(CallNode route_call, ControlFlowNode route, Function func) {
exists(CallNode decorator_call, string name |
route_call.getFunction().(AttrNode).getObject(name).refersTo(_, theBottleClass(), _) or
route_call.getFunction().refersTo(theBottleModule().attr(name))
|
(name = "route" or name = httpVerbLower()) and
decorator_call.getFunction() = route_call and
route_call.getArg(0) = route and
decorator_call.getArg(0).getNode().(FunctionExpr).getInnerScope() = func
)
}
class BottleRoute extends ControlFlowNode {
BottleRoute() {
bottle_route(this, _, _)
}
string getUrl() {
exists(StrConst url |
bottle_route(this, url.getAFlowNode(), _) and
result = url.getText()
)
}
Function getFunction() {
bottle_route(this, _, result)
}
Parameter getNamedArgument() {
exists(string name, Function func |
func = this.getFunction() and
func.getArgByName(name) = result and
this.getUrl().matches("%<" + name + ">%")
)
}
}
/* bottle module route constants */
class BottleRoutePointToExtension extends CustomPointsToFact {
string name;
BottleRoutePointToExtension() {
exists(DefinitionNode defn |
defn.getScope().(Module).getName() = "bottle" and
this = defn.getValue() and
name = defn.(NameNode).getId()
|
name = "route" or
name = httpVerbLower()
)
}
override predicate pointsTo(Context context, Object value, ClassObject cls, ControlFlowNode origin) {
context.isImport() and
ModuleObject::named("bottle").attr("Bottle").(ClassObject).attributeRefersTo(name, value, cls, origin)
}
}

View File

@@ -0,0 +1,35 @@
/** Provides class representing the `bottle.redirect` function.
* This module is intended to be imported into a taint-tracking query
* to extend `TaintSink`.
*/
import python
import semmle.python.security.TaintTracking
import semmle.python.security.strings.Basic
import semmle.python.web.bottle.General
FunctionObject bottle_redirect() {
result = theBottleModule().attr("redirect")
}
/**
* Represents an argument to the `bottle.redirect` function.
*/
class BottleRedirect extends TaintSink {
override string toString() {
result = "bottle.redirect"
}
BottleRedirect() {
exists(CallNode call |
bottle_redirect().getACall() = call and
this = call.getAnArg()
)
}
override predicate sinks(TaintKind kind) {
kind instanceof StringKind
}
}

View File

@@ -0,0 +1,115 @@
import python
import semmle.python.security.TaintTracking
import semmle.python.security.strings.Untrusted
import semmle.python.web.Http
import semmle.python.web.bottle.General
private Object theBottleRequestObject() {
result = theBottleModule().attr("request")
}
class BottleRequestKind extends TaintKind {
BottleRequestKind() {
this = "bottle.request"
}
override TaintKind getTaintOfAttribute(string name) {
result instanceof BottleFormsDict and
(name = "cookies" or name = "query" or name = "form")
or
result instanceof UntrustedStringKind and
(name = "query_string" or name = "url_args")
or
result.(DictKind).getValue() instanceof FileUpload and
name = "files"
}
}
private class RequestSource extends TaintSource {
RequestSource() {
this.(ControlFlowNode).refersTo(theBottleRequestObject())
}
override predicate isSourceOf(TaintKind kind) {
kind instanceof BottleRequestKind
}
}
class BottleFormsDict extends TaintKind {
BottleFormsDict() {
this = "bottle.FormsDict"
}
override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) {
/* Cannot use `getTaintOfAttribute(name)` as it wouldn't bind `name` */
exists(string name |
fromnode = tonode.(AttrNode).getObject(name) and
result instanceof UntrustedStringKind
|
name != "get" and name != "getunicode" and name != "getall"
)
}
override TaintKind getTaintOfMethodResult(string name) {
(name = "get" or name = "getunicode") and
result instanceof UntrustedStringKind
or
name = "getall" and result.(SequenceKind).getItem() instanceof UntrustedStringKind
}
}
class FileUpload extends TaintKind {
FileUpload() {
this = "bottle.FileUpload"
}
override TaintKind getTaintOfAttribute(string name) {
name = "filename" and result instanceof UntrustedStringKind
or
name = "raw_filename" and result instanceof UntrustedStringKind
or
name = "file" and result instanceof UntrustedFile
}
}
class UntrustedFile extends TaintKind {
UntrustedFile() { this = "Untrusted file" }
}
//
// TO DO.. File uploads -- Should check about file uploads for other frameworks as well.
// Move UntrustedFile to shared location
//
/** Parameter to a bottle request handler function */
class BottleRequestParameter extends TaintSource {
BottleRequestParameter() {
exists(BottleRoute route |
route.getNamedArgument() = this.(ControlFlowNode).getNode()
)
}
override predicate isSourceOf(TaintKind kind) {
kind instanceof UntrustedStringKind
}
override string toString() {
result = "bottle handler function argument"
}
}

View File

@@ -0,0 +1,58 @@
import python
import semmle.python.security.TaintTracking
import semmle.python.security.strings.Untrusted
import semmle.python.web.Http
import semmle.python.web.bottle.General
/** A bottle.Response object
* This isn't really a "taint", but we use the value tracking machinery to
* track the flow of response objects.
*/
class BottleResponse extends TaintKind {
BottleResponse() {
this = "bottle.response"
}
}
private Object theBottleResponseObject() {
result = theBottleModule().attr("response")
}
class BottleResponseBodyAssignment extends TaintSink {
BottleResponseBodyAssignment() {
exists(DefinitionNode lhs |
lhs.getValue() = this and
lhs.(AttrNode).getObject("body").refersTo(theBottleResponseObject())
)
}
override predicate sinks(TaintKind kind) {
kind instanceof UntrustedStringKind
}
}
class BottleHandlerFunctionResult extends TaintSink {
BottleHandlerFunctionResult() {
exists(BottleRoute route, Return ret |
ret.getScope() = route.getFunction() and
ret.getValue().getAFlowNode() = this
)
}
override predicate sinks(TaintKind kind) {
kind instanceof UntrustedStringKind
}
override string toString() {
result = "bottle handler function result"
}
}

View File

@@ -12,7 +12,7 @@ class DjangoDbCursor extends DbCursor {
}
private Object theDjangoConnectionObject() {
any(ModuleObject m | m.getName() = "django.db").getAttribute("connection") = result
any(ModuleObject m | m.getName() = "django.db").attr("connection") = result
}
/** A kind of taint source representing sources of django cursor objects.
@@ -38,7 +38,7 @@ class DjangoDbCursorSource extends DbConnectionSource {
ClassObject theDjangoRawSqlClass() {
result = any(ModuleObject m | m.getName() = "django.db.models.expressions").getAttribute("RawSQL")
result = any(ModuleObject m | m.getName() = "django.db.models.expressions").attr("RawSQL")
}
/**

View File

@@ -8,7 +8,7 @@ import semmle.python.web.Http
class DjangoModel extends ClassObject {
DjangoModel() {
any(ModuleObject m | m.getName() = "django.db.models").getAttribute("Model") = this.getAnImproperSuperType()
any(ModuleObject m | m.getName() = "django.db.models").attr("Model") = this.getAnImproperSuperType()
}
}

View File

@@ -82,7 +82,7 @@ private class DjangoFunctionBasedViewRequestArgument extends DjangoRequestSource
private class DjangoView extends ClassObject {
DjangoView() {
any(ModuleObject m | m.getName() = "django.views.generic").getAttribute("View") = this.getAnImproperSuperType()
any(ModuleObject m | m.getName() = "django.views.generic").attr("View") = this.getAnImproperSuperType()
}
}
@@ -109,7 +109,7 @@ class DjangoClassBasedViewRequestArgument extends DjangoRequestSource {
/* Function based views */
predicate url_dispatch(CallNode call, ControlFlowNode regex, FunctionObject view) {
exists(FunctionObject url |
any(ModuleObject m | m.getName() = "django.conf.urls").getAttribute("url") = url and
any(ModuleObject m | m.getName() = "django.conf.urls").attr("url") = url and
url.getArgumentForCall(call, 0) = regex and
url.getArgumentForCall(call, 1).refersTo(view)
)

View File

@@ -17,7 +17,7 @@ class DjangoResponse extends TaintKind {
}
private ClassObject theDjangoHttpResponseClass() {
result = any(ModuleObject m | m.getName() = "django.http.response").getAttribute("HttpResponse") and
result = any(ModuleObject m | m.getName() = "django.http.response").attr("HttpResponse") and
not result = theDjangoHttpRedirectClass()
}

View File

@@ -1,9 +1,9 @@
import python
FunctionObject redirect() {
result = any(ModuleObject m | m.getName() = "django.shortcuts").getAttribute("redirect")
result = any(ModuleObject m | m.getName() = "django.shortcuts").attr("redirect")
}
ClassObject theDjangoHttpRedirectClass() {
result = any(ModuleObject m | m.getName() = "django.http.response").getAttribute("HttpResponseRedirectBase")
result = any(ModuleObject m | m.getName() = "django.http.response").attr("HttpResponseRedirectBase")
}

View File

@@ -8,16 +8,16 @@ ModuleObject theFlaskModule() {
/** The flask app class */
ClassObject theFlaskClass() {
result = theFlaskModule().getAttribute("Flask")
result = theFlaskModule().attr("Flask")
}
/** The flask MethodView class */
ClassObject theFlaskMethodViewClass() {
result = any(ModuleObject m | m.getName() = "flask.views").getAttribute("MethodView")
result = any(ModuleObject m | m.getName() = "flask.views").attr("MethodView")
}
ClassObject theFlaskReponseClass() {
result = theFlaskModule().getAttribute("Response")
result = theFlaskModule().attr("Response")
}
/** Holds if `route` is routed to `func`

View File

@@ -9,7 +9,7 @@ import semmle.python.security.strings.Basic
import semmle.python.web.flask.General
FunctionObject flask_redirect() {
result = theFlaskModule().getAttribute("redirect")
result = theFlaskModule().attr("redirect")
}
/**

View File

@@ -5,7 +5,7 @@ import semmle.python.web.Http
import semmle.python.web.flask.General
private Object theFlaskRequestObject() {
result = theFlaskModule().getAttribute("request")
result = theFlaskModule().attr("request")
}

View File

@@ -10,9 +10,9 @@ import semmle.python.security.strings.Basic
private ClassObject redirectClass() {
exists(ModuleObject ex |
ex.getName() = "pyramid.httpexceptions" |
ex.getAttribute("HTTPFound") = result
ex.attr("HTTPFound") = result
or
ex.getAttribute("HTTPTemporaryRedirect") = result
ex.attr("HTTPTemporaryRedirect") = result
)
}

View File

@@ -12,7 +12,7 @@ class PyramidRequest extends BaseWebobRequest {
}
override ClassObject getClass() {
result = any(ModuleObject m | m.getName() = "pyramid.request").getAttribute("Request")
result = any(ModuleObject m | m.getName() = "pyramid.request").attr("Request")
}
}

View File

@@ -5,7 +5,7 @@ ModuleObject thePyramidViewModule() {
}
Object thePyramidViewConfig() {
result = thePyramidViewModule().getAttribute("view_config")
result = thePyramidViewModule().attr("view_config")
}
predicate is_pyramid_view_function(Function func) {

View File

@@ -3,7 +3,7 @@ import python
import semmle.python.security.TaintTracking
private ClassObject theTornadoRequestHandlerClass() {
result = any(ModuleObject m | m.getName() = "tornado.web").getAttribute("RequestHandler")
result = any(ModuleObject m | m.getName() = "tornado.web").attr("RequestHandler")
}
ClassObject aTornadoRequestHandlerClass() {

View File

@@ -3,11 +3,11 @@ import python
import semmle.python.security.TaintTracking
private ClassObject theTwistedHttpRequestClass() {
result = any(ModuleObject m | m.getName() = "twisted.web.http").getAttribute("Request")
result = any(ModuleObject m | m.getName() = "twisted.web.http").attr("Request")
}
private ClassObject theTwistedHttpResourceClass() {
result = any(ModuleObject m | m.getName() = "twisted.web.resource").getAttribute("Resource")
result = any(ModuleObject m | m.getName() = "twisted.web.resource").attr("Resource")
}
ClassObject aTwistedRequestHandlerClass() {

View File

@@ -45,7 +45,7 @@ class WebobRequest extends BaseWebobRequest {
}
override ClassObject getClass() {
result = any(ModuleObject m | m.getName() = "webob.request").getAttribute("Request")
result = any(ModuleObject m | m.getName() = "webob.request").attr("Request")
}
}

View File

@@ -431,6 +431,11 @@ py_extracted_version(int module : @py_Module ref,
/* <Field> Slice.stop = 3, expr */
/* <Field> Slice.step = 4, expr */
/* <Field> SpecialOperation.location = 0, location */
/* <Field> SpecialOperation.parenthesised = 1, bool */
/* <Field> SpecialOperation.name = 2, str */
/* <Field> SpecialOperation.arguments = 3, expr_list */
/* <Field> Starred.location = 0, location */
/* <Field> Starred.parenthesised = 1, bool */
/* <Field> Starred.value = 2, expr */
@@ -718,7 +723,8 @@ case @py_expr.kind of
| 34 = @py_Await
| 35 = @py_Fstring
| 36 = @py_FormattedValue
| 37 = @py_AssignExpr;
| 37 = @py_AssignExpr
| 38 = @py_SpecialOperation;
case @py_expr_context.kind of
0 = @py_AugLoad
@@ -789,7 +795,7 @@ case @py_unaryop.kind of
@py_expr_context_parent = @py_Attribute | @py_List | @py_Name | @py_PlaceHolder | @py_Starred | @py_Subscript | @py_TemplateDottedNotation | @py_Tuple;
@py_expr_list_parent = @py_Assign | @py_BoolExpr | @py_Call | @py_ClassExpr | @py_Compare | @py_Delete | @py_Fstring | @py_Function | @py_List | @py_Print | @py_Set | @py_Tuple | @py_arguments | @py_comprehension;
@py_expr_list_parent = @py_Assign | @py_BoolExpr | @py_Call | @py_ClassExpr | @py_Compare | @py_Delete | @py_Fstring | @py_Function | @py_List | @py_Print | @py_Set | @py_SpecialOperation | @py_Tuple | @py_arguments | @py_comprehension;
@py_expr_or_stmt = @py_expr | @py_stmt;
@@ -805,7 +811,7 @@ case @py_unaryop.kind of
@py_str_list_parent = @py_Global | @py_Nonlocal;
@py_str_parent = @py_Attribute | @py_Class | @py_ClassExpr | @py_FormattedValue | @py_Function | @py_FunctionExpr | @py_ImportExpr | @py_ImportMember | @py_Module | @py_Str | @py_StringPart | @py_TemplateDottedNotation | @py_keyword | @py_str_list;
@py_str_parent = @py_Attribute | @py_Class | @py_ClassExpr | @py_FormattedValue | @py_Function | @py_FunctionExpr | @py_ImportExpr | @py_ImportMember | @py_Module | @py_SpecialOperation | @py_Str | @py_StringPart | @py_TemplateDottedNotation | @py_keyword | @py_str_list;
@py_variable_parent = @py_Name | @py_PlaceHolder;

View File

@@ -324,6 +324,10 @@
<v>200</v>
</e>
<e>
<k>@py_SpecialOperation</k>
<v>100</v>
</e>
<e>
<k>@py_expr_context</k>
<v>1140675</v>
</e>