Merge branch 'master' into python-iter-str-seq-with-tests

This commit is contained in:
Rasmus Wriedt Larsen
2020-04-27 17:20:06 +02:00
523 changed files with 19066 additions and 15998 deletions

View File

@@ -22,9 +22,9 @@ predicate does_not_define_special_method(Class cls) {
}
predicate no_inheritance(Class c) {
not exists(ClassObject cls, ClassObject other |
cls.getPyClass() = c and
other != theObjectType()
not exists(ClassValue cls, ClassValue other |
cls.getScope() = c and
other != ClassValue::object()
|
other.getABaseType() = cls or
cls.getABaseType() = other

View File

@@ -16,9 +16,9 @@
import python
import Expressions.CallArgs
from Call call, ClassObject cls, string name, FunctionObject init
from Call call, ClassValue cls, string name, FunctionValue init
where
illegally_named_parameter_objectapi(call, cls, name) and
init = get_function_or_initializer_objectapi(cls)
illegally_named_parameter(call, cls, name) and
init = get_function_or_initializer(cls)
select call, "Keyword argument '" + name + "' is not a supported parameter name of $@.", init,
init.getQualifiedName()

View File

@@ -15,17 +15,17 @@
import python
import Expressions.CallArgs
from Call call, ClassObject cls, string too, string should, int limit, FunctionObject init
from Call call, ClassValue cls, string too, string should, int limit, FunctionValue init
where
(
too_many_args_objectapi(call, cls, limit) and
too_many_args(call, cls, limit) and
too = "too many arguments" and
should = "no more than "
or
too_few_args_objectapi(call, cls, limit) and
too_few_args(call, cls, limit) and
too = "too few arguments" and
should = "no fewer than "
) and
init = get_function_or_initializer_objectapi(cls)
init = get_function_or_initializer(cls)
select call, "Call to $@ with " + too + "; should be " + should + limit.toString() + ".", init,
init.getQualifiedName()

View File

@@ -154,14 +154,17 @@ predicate too_few_args(Call call, Value callable, int limit) {
not exists(call.getKwargs()) and
arg_count(call) < limit and
exists(FunctionValue func | func = get_function_or_initializer(callable) |
call = func.getACall().getNode() and
call = func.getAFunctionCall().getNode() and
limit = func.minParameters() and
// The combination of misuse of `mox.Mox().StubOutWithMock()`
// and a bug in mox's implementation of methods results in having to
// pass 1 too few arguments to the mocked function.
/*
* The combination of misuse of `mox.Mox().StubOutWithMock()`
* and a bug in mox's implementation of methods results in having to
* pass 1 too few arguments to the mocked function.
*/
not (useOfMoxInModule(call.getEnclosingModule()) and func.isNormalMethod())
or
call = func.getACall().getNode() and limit = func.minParameters() - 1
call = func.getAMethodCall().getNode() and limit = func.minParameters() - 1
or
callable instanceof ClassValue and
call.getAFlowNode() = get_a_call(callable) and
@@ -198,9 +201,9 @@ predicate too_many_args(Call call, Value callable, int limit) {
not func.getScope().hasVarArg() and
limit >= 0
|
call = func.getACall().getNode() and limit = func.maxParameters()
call = func.getAFunctionCall().getNode() and limit = func.maxParameters()
or
call = func.getACall().getNode() and limit = func.maxParameters() - 1
call = func.getAMethodCall().getNode() and limit = func.maxParameters() - 1
or
callable instanceof ClassValue and
call.getAFlowNode() = get_a_call(callable) and
@@ -254,3 +257,8 @@ predicate overridden_call(FunctionValue func, FunctionValue overriding, Call cal
overriding.overrides(func) and
overriding.getACall().getNode() = call
}
/** Holds if `func` will raise a `NotImplemented` error. */
predicate isAbstract(FunctionValue func) {
func.getARaisedType() = ClassValue::notImplementedError()
}

View File

@@ -14,22 +14,17 @@
import python
import CallArgs
from Call call, FunctionObject func, string too, string should, int limit
from Call call, FunctionValue func, string too, string should, int limit
where
(
too_many_args_objectapi(call, func, limit) and
too = "too many arguments" and
should = "no more than "
or
too_few_args_objectapi(call, func, limit) and
too = "too few arguments" and
should = "no fewer than "
) and
not func.isAbstract() and
not exists(FunctionObject overridden |
func.overrides(overridden) and correct_args_if_called_as_method_objectapi(call, overridden)
) and
/* The semantics of `__new__` can be a bit subtle, so we simply exclude `__new__` methods */
not func.getName() = "__new__"
select call, "Call to $@ with " + too + "; should be " + should + limit.toString() + ".", func,
func.descriptiveString()
(
too_many_args(call, func, limit) and too = "too many arguments" and should = "no more than "
or
too_few_args(call, func, limit) and too = "too few arguments" and should = "no fewer than "
) and
not isAbstract(func) and
not exists(FunctionValue overridden | func.overrides(overridden) and correct_args_if_called_as_method(call, overridden))
/* The semantics of `__new__` can be a bit subtle, so we simply exclude `__new__` methods */
and not func.getName() = "__new__"
select call, "Call to $@ with " + too + "; should be " + should + limit.toString() + ".", func, func.descriptiveString()

View File

@@ -12,17 +12,17 @@
import python
import Expressions.CallArgs
from Call call, FunctionObject func, FunctionObject overridden, string problem
from Call call, FunctionValue func, FunctionValue overridden, string problem
where
func.overrides(overridden) and
(
wrong_args_objectapi(call, func, _, problem) and
correct_args_if_called_as_method_objectapi(call, overridden)
wrong_args(call, func, _, problem) and
correct_args_if_called_as_method(call, overridden)
or
exists(string name |
illegally_named_parameter_objectapi(call, func, name) and
illegally_named_parameter(call, func, name) and
problem = "an argument named '" + name + "'" and
overridden.getFunction().getAnArg().(Name).getId() = name
overridden.getScope().getAnArg().(Name).getId() = name
)
)
select func,

View File

@@ -13,21 +13,21 @@
import python
import Expressions.CallArgs
from Call call, FunctionObject func, FunctionObject overriding, string problem
from Call call, FunctionValue func, FunctionValue overriding, string problem
where
not func.getName() = "__init__" and
overriding.overrides(func) and
call = overriding.getAMethodCall().getNode() and
correct_args_if_called_as_method_objectapi(call, overriding) and
correct_args_if_called_as_method(call, overriding) and
(
arg_count_objectapi(call) + 1 < func.minParameters() and problem = "too few arguments"
arg_count(call) + 1 < func.minParameters() and problem = "too few arguments"
or
arg_count_objectapi(call) >= func.maxParameters() and problem = "too many arguments"
arg_count(call) >= func.maxParameters() and problem = "too many arguments"
or
exists(string name |
call.getAKeyword().getArg() = name and
overriding.getFunction().getAnArg().(Name).getId() = name and
not func.getFunction().getAnArg().(Name).getId() = name and
overriding.getScope().getAnArg().(Name).getId() = name and
not func.getScope().getAnArg().(Name).getId() = name and
problem = "an argument named '" + name + "'"
)
)

View File

@@ -12,18 +12,10 @@
import python
ClassObject return_type(FunctionObject f) {
exists(ControlFlowNode n, Return ret |
ret.getScope() = f.getFunction() and
ret.getValue() = n.getNode() and
n.refersTo(_, result, _)
)
}
from ClassObject iterable, FunctionObject iter, ClassObject iterator
from ClassValue iterable, FunctionValue iter, ClassValue iterator
where
iter = iterable.lookupAttribute("__iter__") and
iterator = return_type(iter) and
iter = iterable.lookup("__iter__") and
iterator = iter.getAnInferredReturnType() and
not iterator.isIterator()
select iterator,
"Class " + iterator.getName() +

View File

@@ -15,10 +15,10 @@
import python
from FunctionObject method
from FunctionValue method
where
exists(ClassObject c |
exists(ClassValue c |
c.declaredAttribute("__del__") = method and
method.getFunction().getMetrics().getCyclomaticComplexity() > 3
method.getScope().getMetrics().getCyclomaticComplexity() > 3
)
select method, "Overly complex '__del__' method."

View File

@@ -62,7 +62,7 @@ private string doctest_in_scope(Scope scope) {
}
pragma[noinline]
private string typehint_annotation_in_file(File file) {
private string typehint_annotation_in_module(Module module_scope) {
exists(StrConst annotation |
annotation = any(Arguments a).getAnAnnotation().getASubExpression*()
or
@@ -71,7 +71,7 @@ private string typehint_annotation_in_file(File file) {
annotation = any(FunctionExpr f).getReturns().getASubExpression*()
|
annotation.pointsTo(Value::forString(result)) and
file = annotation.getLocation().getFile()
annotation.getEnclosingModule() = module_scope
)
}
@@ -84,17 +84,19 @@ private string typehint_comment_in_file(File file) {
)
}
predicate imported_module_used_in_typehint(Import imp) {
exists(string modname, File file |
imp.getAName().getAsname().(Name).getId() = modname and
file = imp.getScope().(Module).getFile()
/** Holds if the imported alias `name` from `imp` is used in a typehint (in the same file as `imp`) */
predicate imported_alias_used_in_typehint(Import imp, Variable name) {
imp.getAName().getAsname().(Name).getVariable() = name and
exists(File file, Module module_scope |
module_scope = imp.getEnclosingModule() and
file = module_scope.getFile()
|
// Look for type hints containing the patterns:
// # type: …name…
typehint_comment_in_file(file).regexpMatch("# type:.*" + modname + ".*")
typehint_comment_in_file(file).regexpMatch("# type:.*" + name.getId() + ".*")
or
// Type hint is inside a string annotation, as needed for forward references
typehint_annotation_in_file(file).regexpMatch(".*\\b" + modname + "\\b.*")
typehint_annotation_in_module(module_scope).regexpMatch(".*\\b" + name.getId() + "\\b.*")
)
}
@@ -114,7 +116,7 @@ predicate unused_import(Import imp, Variable name) {
// Assume that opaque `__all__` includes imported module
not all_not_understood(imp.getEnclosingModule()) and
not imported_module_used_in_doctest(imp) and
not imported_module_used_in_typehint(imp) and
not imported_alias_used_in_typehint(imp, name) and
// Only consider import statements that actually point-to something (possibly an unknown module).
// If this is not the case, it's likely that the import statement never gets executed.
imp.getAName().getValue().pointsTo(_)

View File

@@ -79,7 +79,7 @@ predicate def_is_open(EssaDefinition def, ControlFlowNode open) {
passes_open_files(refinement)
)
or
exists(PyNodeRefinement refinement | refinement = def |
exists(EssaNodeRefinement refinement | refinement = def |
not closes_file(def) and
not wraps_file(refinement.getDefiningNode(), refinement.getInput()) and
var_is_open(refinement.getInput(), open)

View File

@@ -66,7 +66,7 @@ class Copy extends @duplication_or_similarity {
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
sourceFile().getName() = filepath and
sourceFile().getAbsolutePath() = filepath and
startline = sourceStartLine() and
startcolumn = sourceStartColumn() and
endline = sourceEndLine() and

View File

@@ -26,7 +26,7 @@ class DefectResult extends int {
/** Gets the file in which this query result was reported. */
File getFile() {
exists(string path | defectResults(this, _, path, _, _, _, _, _) and result.getName() = path)
exists(string path | defectResults(this, _, path, _, _, _, _, _) and result.getAbsolutePath() = path)
}
/** Gets the file path in which this query result was reported. */
@@ -54,7 +54,7 @@ class DefectResult extends int {
/** Gets the URL corresponding to the location of this query result. */
string getURL() {
result =
"file://" + getFile().getName() + ":" + getStartLine() + ":" + getStartColumn() + ":" +
"file://" + getFile().getAbsolutePath() + ":" + getStartLine() + ":" + getStartColumn() + ":" +
getEndLine() + ":" + getEndColumn()
}
}

View File

@@ -23,7 +23,7 @@ class Commit extends @svnentry {
string getMessage() { svnentrymsg(this, result) }
string getAnAffectedFilePath(string action) {
exists(File rawFile | svnaffectedfiles(this, rawFile, action) | result = rawFile.getName())
exists(File rawFile | svnaffectedfiles(this, rawFile, action) | result = rawFile.getAbsolutePath())
}
string getAnAffectedFilePath() { result = getAnAffectedFilePath(_) }

View File

@@ -2,3 +2,4 @@ name: codeql-python
version: 0.0.0
dbscheme: semmlecode.python.dbscheme
suites: codeql-suites
extractor: python

View File

@@ -11,7 +11,7 @@ class OpenFileConfiguration extends TaintTracking::Configuration {
OpenFileConfiguration() { this = "Open file configuration" }
override predicate isSource(DataFlow::Node src, TaintKind kind) {
theOpenFunction().(FunctionObject).getACall() = src.asCfgNode() and
src.asCfgNode() = Value::named("open").getACall() and
kind instanceof OpenFile
}

View File

@@ -131,15 +131,6 @@ abstract class TaintKind extends string {
edgeLabel = "custom taint flow step for " + this
}
/**
* DEPRECATED -- Use `TaintFlow.additionalFlowStepVar(EssaVariable fromvar, EssaVariable tovar, TaintKind kind)` instead.
*
* Holds if this kind of taint passes from variable `fromvar` to variable `tovar`
* This predicate is present for completeness. It is unlikely that any `TaintKind`
* implementation will ever need to override it.
*/
deprecated predicate additionalFlowStepVar(EssaVariable fromvar, EssaVariable tovar) { none() }
/**
* Holds if this kind of taint "taints" `expr`.
*/
@@ -155,7 +146,7 @@ abstract class TaintKind extends string {
* For example, if this were a kind of string taint
* the `result` would be `theStrType()`.
*/
ClassValue getType() { result.(ClassObjectInternal).getSource() = this.getClass() }
ClassValue getType() { none() }
/**
* Gets the boolean values (may be one, neither, or both) that
@@ -180,7 +171,10 @@ abstract class TaintKind extends string {
TaintKind getTaintForIteration() { none() }
predicate flowStep(DataFlow::Node fromnode, DataFlow::Node tonode, string edgeLabel) {
this.additionalFlowStepVar(fromnode.asVariable(), tonode.asVariable()) and
exists(DataFlowExtension::DataFlowVariable v |
v = fromnode.asVariable() and
v.getASuccessorVariable() = tonode.asVariable()
) and
edgeLabel = "custom taint variable step"
}
}
@@ -355,41 +349,6 @@ abstract class Sanitizer extends string {
predicate sanitizingDefinition(TaintKind taint, EssaDefinition def) { none() }
}
/**
* DEPRECATED -- Use DataFlowExtension instead.
* An extension to taint-flow. For adding library or framework specific flows.
* Examples include flow from a request to untrusted part of that request or
* from a socket to data from that socket.
*/
abstract deprecated class TaintFlow extends string {
bindingset[this]
TaintFlow() { any() }
/**
* Holds if `fromnode` being tainted with `fromkind` will result in `tonode` being tainted with `tokind`.
* Extensions to `TaintFlow` should override this to provide additional taint steps.
*/
predicate additionalFlowStep(
ControlFlowNode fromnode, TaintKind fromkind, ControlFlowNode tonode, TaintKind tokind
) {
none()
}
/**
* Holds if the given `kind` of taint passes from variable `fromvar` to variable `tovar`.
* This predicate is present for completeness. Most `TaintFlow` implementations will not need to override it.
*/
predicate additionalFlowStepVar(EssaVariable fromvar, EssaVariable tovar, TaintKind kind) {
none()
}
/**
* Holds if the given `kind` of taint cannot pass from variable `fromvar` to variable `tovar`.
* This predicate is present for completeness. Most `TaintFlow` implementations will not need to override it.
*/
predicate prunedFlowStepVar(EssaVariable fromvar, EssaVariable tovar, TaintKind kind) { none() }
}
/**
* A source of taintedness.
* Users of the taint tracking library should override this
@@ -659,7 +618,7 @@ module DataFlow {
}
}
private class ConfigurationAdapter extends TaintTracking::Configuration {
deprecated private class ConfigurationAdapter extends TaintTracking::Configuration {
ConfigurationAdapter() { this instanceof Configuration }
override predicate isSource(DataFlow::Node node, TaintKind kind) {
@@ -727,7 +686,7 @@ module DataFlow {
}
}
private class DataFlowType extends TaintKind {
deprecated private class DataFlowType extends TaintKind {
DataFlowType() {
this = "Data flow" and
exists(DataFlow::Configuration c)

View File

@@ -170,7 +170,7 @@ string from_mako_import(Module m) {
/** File generated by Google's protobuf tool. */
class ProtobufGeneratedFile extends SpecificGeneratedFile {
ProtobufGeneratedFile() {
this.getName().regexpMatch(".*_pb2?.py") and
this.getAbsolutePath().regexpMatch(".*_pb2?.py") and
exists(Module m | m.getFile() = this |
exists(ImportExpr imp | imp.getEnclosingModule() = m |
imp.getImportedModuleName() = "google.net.proto2.python.public"

View File

@@ -540,10 +540,10 @@ class ClassValue extends Value {
Value declaredAttribute(string name) { Types::declaredAttribute(this, name, result, _) }
/**
* Holds if this class has the attribute `name`, including
* attributes declared by super classes.
* Holds if this class has the attribute `name`, including attributes
* declared by super classes.
*/
predicate hasAttribute(string name) { this.getMro().declares(name) }
override predicate hasAttribute(string name) { this.getMro().declares(name) }
/**
* Holds if this class declares the attribute `name`,
@@ -573,6 +573,9 @@ class ClassValue extends Value {
abstract class FunctionValue extends CallableValue {
abstract string getQualifiedName();
/** Gets a longer, more descriptive version of toString() */
abstract string descriptiveString();
/** Gets the minimum number of parameters that can be correctly passed to this function */
abstract int minParameters();
@@ -605,6 +608,20 @@ abstract class FunctionValue extends CallableValue {
)
}
/** Gets a class that may be raised by this function */
abstract ClassValue getARaisedType();
/** Gets a call-site from where this function is called as a function */
CallNode getAFunctionCall() { result.getFunction().pointsTo() = this }
/** Gets a call-site from where this function is called as a method */
CallNode getAMethodCall() {
exists(BoundMethodObjectInternal bm |
result.getFunction().pointsTo() = bm and
bm.getFunction() = this
)
}
/** Gets a class that this function may return */
abstract ClassValue getAnInferredReturnType();
}
@@ -617,6 +634,15 @@ class PythonFunctionValue extends FunctionValue {
result = this.(PythonFunctionObjectInternal).getScope().getQualifiedName()
}
override string descriptiveString() {
if this.getScope().isMethod()
then
exists(Class cls | this.getScope().getScope() = cls |
result = "method " + this.getQualifiedName()
)
else result = "function " + this.getQualifiedName()
}
override int minParameters() {
exists(Function f |
f = this.getScope() and
@@ -636,6 +662,8 @@ class PythonFunctionValue extends FunctionValue {
/** Gets a control flow node corresponding to a return statement in this function */
ControlFlowNode getAReturnedNode() { result = this.getScope().getAReturnValueFlowNode() }
override ClassValue getARaisedType() { scope_raises(result, this.getScope()) }
override ClassValue getAnInferredReturnType() {
/* We have to do a special version of this because builtin functions have no
* explicit return nodes that we can query and get the class of.
@@ -650,10 +678,17 @@ class BuiltinFunctionValue extends FunctionValue {
override string getQualifiedName() { result = this.(BuiltinFunctionObjectInternal).getName() }
override string descriptiveString() { result = "builtin-function " + this.getName() }
override int minParameters() { none() }
override int maxParameters() { none() }
override ClassValue getARaisedType() {
/* Information is unavailable for C code in general */
none()
}
override ClassValue getAnInferredReturnType() {
/* We have to do a special version of this because builtin functions have no
* explicit return nodes that we can query and get the class of.
@@ -674,10 +709,17 @@ class BuiltinMethodValue extends FunctionValue {
)
}
override string descriptiveString() { result = "builtin-method " + this.getQualifiedName() }
override int minParameters() { none() }
override int maxParameters() { none() }
override ClassValue getARaisedType() {
/* Information is unavailable for C code in general */
none()
}
override ClassValue getAnInferredReturnType() {
result = TBuiltinClassObject(this.(BuiltinMethodObjectInternal).getReturnType())
}
@@ -905,6 +947,9 @@ module ClassValue {
/** Get the `ClassValue` for the `LookupError` class. */
ClassValue lookupError() { result = TBuiltinClassObject(Builtin::builtin("LookupError")) }
/** Get the `ClassValue` for the `IndexError` class. */
ClassValue indexError() { result = TBuiltinClassObject(Builtin::builtin("IndexError")) }
/** Get the `ClassValue` for the `IOError` class. */
ClassValue ioError() { result = TBuiltinClassObject(Builtin::builtin("IOError")) }
@@ -925,4 +970,7 @@ module ClassValue {
ClassValue unicodeDecodeError() {
result = TBuiltinClassObject(Builtin::builtin("UnicodeDecodeError"))
}
/** Get the `ClassValue` for the `SystemExit` class. */
ClassValue systemExit() { result = TBuiltinClassObject(Builtin::builtin("SystemExit")) }
}

View File

@@ -114,7 +114,7 @@ module PointsTo {
/* Backwards compatibility */
cached
deprecated predicate points_to(
predicate points_to(
ControlFlowNode f, PointsToContext context, Object obj, ClassObject cls, ControlFlowNode origin
) {
exists(ObjectInternal value |

View File

@@ -10,12 +10,6 @@ import python
import semmle.python.security.TaintTracking
import semmle.python.security.strings.Untrusted
private FunctionObject exec_or_eval() {
result = Object::builtin("exec")
or
result = Object::builtin("eval")
}
/**
* A taint sink that represents an argument to exec or eval that is vulnerable to malicious input.
* The `vuln` in `exec(vuln)` or similar.
@@ -26,10 +20,9 @@ class StringEvaluationNode extends TaintSink {
StringEvaluationNode() {
exists(Exec exec | exec.getASubExpression().getAFlowNode() = this)
or
exists(CallNode call |
exec_or_eval().getACall() = call and
call.getAnArg() = this
)
Value::named("exec").getACall().getAnArg() = this
or
Value::named("eval").getACall().getAnArg() = this
}
override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }

View File

@@ -64,8 +64,12 @@ class OpenNode extends TaintSink {
OpenNode() {
exists(CallNode call |
call.getFunction().refersTo(Object::builtin("open")) and
call.getAnArg() = this
call = Value::named("open").getACall() and
(
call.getArg(0) = this
or
call.getArgByName("file") = this
)
)
}

View File

@@ -7,10 +7,21 @@ abstract class StringKind extends TaintKind {
bindingset[this]
StringKind() { this = this }
override TaintKind getTaintOfMethodResult(string name) {
name in ["capitalize", "casefold", "center", "expandtabs", "format", "format_map", "ljust",
"lstrip", "lower", "replace", "rjust", "rstrip", "strip", "swapcase", "title", "upper",
"zfill",
/* encode/decode is technically not correct, but close enough */
"encode", "decode"] and
result = this
or
name in ["partition", "rpartition", "rsplit", "split", "splitlines"] and
result.(SequenceKind).getItem() = this
}
override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) {
result = this and
(
str_method_call(fromnode, tonode) or
slice(fromnode, tonode) or
tonode.(BinaryExprNode).getAnOperand() = fromnode or
os_path_join(fromnode, tonode) or
@@ -50,20 +61,6 @@ private class StringEqualitySanitizer extends Sanitizer {
}
}
/* tonode = fromnode.xxx() where the call to xxx returns an identical or similar string */
private predicate str_method_call(ControlFlowNode fromnode, CallNode tonode) {
exists(string method_name | tonode.getFunction().(AttrNode).getObject(method_name) = fromnode |
method_name = "strip" or
method_name = "format" or
method_name = "lstrip" or
method_name = "rstrip" or
method_name = "ljust" or
method_name = "rjust" or
method_name = "title" or
method_name = "capitalize"
)
}
/* tonode = ....format(fromnode) */
private predicate str_format(ControlFlowNode fromnode, CallNode tonode) {
tonode.getFunction().(AttrNode).getName() = "format" and

View File

@@ -36,19 +36,36 @@ class RaisingNode extends ControlFlowNode {
* Gets the type of an exception that may be raised
* at this control flow node
*/
ClassObject getARaisedType() {
result = this.localRaisedType()
ClassObject getARaisedType_objectapi() {
result = this.localRaisedType_objectapi()
or
exists(FunctionObject func | this = func.getACall() | result = func.getARaisedType())
or
result = systemExitRaise_objectapi()
}
/**
* Gets the type of an exception that may be raised
* at this control flow node
*/
ClassValue getARaisedType() {
result = this.localRaisedType()
or
exists(FunctionValue func | this = func.getACall() | result = func.getARaisedType())
or
result = systemExitRaise()
}
pragma[noinline]
private ClassObject systemExitRaise() { this.quits() and result = Object::builtin("SystemExit") }
private ClassObject systemExitRaise_objectapi() {
this.quits() and result = Object::builtin("SystemExit")
}
pragma[noinline]
private ClassValue systemExitRaise() { this.quits() and result = ClassValue::systemExit() }
pragma[noinline, nomagic]
private ClassObject localRaisedType() {
private ClassObject localRaisedType_objectapi() {
result.isSubclassOf(theBaseExceptionType()) and
(
exists(ControlFlowNode ex |
@@ -62,8 +79,8 @@ class RaisingNode extends ControlFlowNode {
or
exists(ExceptFlowNode except |
except = this.getAnExceptionalSuccessor() and
except.handles(result) and
result = this.innateException()
except.handles_objectapi(result) and
result = this.innateException_objectapi()
)
or
not exists(ExceptFlowNode except | except = this.getAnExceptionalSuccessor()) and
@@ -74,8 +91,36 @@ class RaisingNode extends ControlFlowNode {
)
}
pragma[noinline, nomagic]
private ClassValue localRaisedType() {
result.getASuperType() = ClassValue::baseException() and
(
exists(ControlFlowNode ex |
ex = this.getExceptionNode() and
(ex.pointsTo(result) or ex.pointsTo().getClass() = result)
)
or
this.getNode() instanceof ImportExpr and result = ClassValue::importError()
or
this.getNode() instanceof Print and result = ClassValue::ioError()
or
exists(ExceptFlowNode except |
except = this.getAnExceptionalSuccessor() and
except.handles(result) and
result = this.innateException()
)
or
not exists(ExceptFlowNode except | except = this.getAnExceptionalSuccessor()) and
sequence_or_mapping(this) and
result = ClassValue::lookupError()
or
this.read_write_call() and result = ClassValue::ioError()
)
}
/** Holds if this is an innate exception (AttributeError, NameError, IndexError, or KeyError). */
pragma[noinline]
ClassObject innateException() {
ClassObject innateException_objectapi() {
this.getNode() instanceof Attribute and result = theAttributeErrorType()
or
this.getNode() instanceof Name and result = theNameErrorType()
@@ -85,6 +130,18 @@ class RaisingNode extends ControlFlowNode {
this.getNode() instanceof Subscript and result = theKeyErrorType()
}
/** Holds if this is an innate exception (AttributeError, NameError, IndexError, or KeyError). */
pragma[noinline]
ClassValue innateException() {
this.getNode() instanceof Attribute and result = ClassValue::attributeError()
or
this.getNode() instanceof Name and result = ClassValue::nameError()
or
this.getNode() instanceof Subscript and result = ClassValue::indexError()
or
this.getNode() instanceof Subscript and result = ClassValue::keyError()
}
/**
* Whether this control flow node raises an exception,
* but the type of the exception it raises cannot be inferred.
@@ -114,7 +171,7 @@ class RaisingNode extends ControlFlowNode {
/** Whether (as inferred by type inference) it is highly unlikely (or impossible) for control to flow from this to succ. */
predicate unlikelySuccessor(ControlFlowNode succ) {
succ = this.getAnExceptionalSuccessor() and
not this.viableExceptionEdge(succ, _) and
not this.viableExceptionEdge_objectapi(succ, _) and
not this.raisesUnknownType()
or
exists(FunctionObject func |
@@ -132,7 +189,30 @@ class RaisingNode extends ControlFlowNode {
}
/** Whether it is considered plausible that 'raised' can be raised across the edge this-succ */
predicate viableExceptionEdge(ControlFlowNode succ, ClassObject raised) {
predicate viableExceptionEdge_objectapi(ControlFlowNode succ, ClassObject raised) {
raised.isLegalExceptionType() and
raised = this.getARaisedType_objectapi() and
succ = this.getAnExceptionalSuccessor() and
(
/* An 'except' that handles raised and there is no more previous handler */
succ.(ExceptFlowNode).handles_objectapi(raised) and
not exists(ExceptFlowNode other, StmtList s, int i, int j |
not other = succ and
other.handles_objectapi(raised) and
s.getItem(i) = succ.getNode() and
s.getItem(j) = other.getNode()
|
j < i
)
or
/* Any successor that is not an 'except', provided that 'raised' is not handled by a different successor. */
not this.getAnExceptionalSuccessor().(ExceptFlowNode).handles_objectapi(raised) and
not succ instanceof ExceptFlowNode
)
}
/** Whether it is considered plausible that 'raised' can be raised across the edge this-succ */
predicate viableExceptionEdge(ControlFlowNode succ, ClassValue raised) {
raised.isLegalExceptionType() and
raised = this.getARaisedType() and
succ = this.getAnExceptionalSuccessor() and
@@ -159,7 +239,19 @@ class RaisingNode extends ControlFlowNode {
* plausible that the scope `s` can be exited with exception `raised`
* at this point.
*/
predicate viableExceptionalExit(Scope s, ClassObject raised) {
predicate viableExceptionalExit_objectapi(Scope s, ClassObject raised) {
raised.isLegalExceptionType() and
raised = this.getARaisedType_objectapi() and
this.isExceptionalExit(s) and
not this.getAnExceptionalSuccessor().(ExceptFlowNode).handles_objectapi(raised)
}
/**
* Whether this exceptional exit is viable. That is, is it
* plausible that the scope `s` can be exited with exception `raised`
* at this point.
*/
predicate viableExceptionalExit(Scope s, ClassValue raised) {
raised.isLegalExceptionType() and
raised = this.getARaisedType() and
this.isExceptionalExit(s) and
@@ -170,7 +262,29 @@ class RaisingNode extends ControlFlowNode {
/** Is this a sequence or mapping subscript x[i]? */
private predicate sequence_or_mapping(RaisingNode r) { r.getNode() instanceof Subscript }
private predicate current_exception(ClassObject ex, BasicBlock b) {
private predicate current_exception_objectapi(ClassObject ex, BasicBlock b) {
exists(RaisingNode r |
r.viableExceptionEdge_objectapi(b.getNode(0), ex) and not b.getNode(0) instanceof ExceptFlowNode
)
or
exists(BasicBlock prev |
current_exception_objectapi(ex, prev) and
exists(ControlFlowNode pred, ControlFlowNode succ |
pred = prev.getLastNode() and succ = b.getNode(0)
|
pred.getASuccessor() = succ and
(
/* Normal control flow */
not pred.getAnExceptionalSuccessor() = succ
or
/* Re-raise the current exception, propagating to the successor */
pred instanceof ReraisingNode
)
)
)
}
private predicate current_exception(ClassValue ex, BasicBlock b) {
exists(RaisingNode r |
r.viableExceptionEdge(b.getNode(0), ex) and not b.getNode(0) instanceof ExceptFlowNode
)
@@ -211,7 +325,19 @@ private predicate unknown_current_exception(BasicBlock b) {
}
/** INTERNAL -- Use FunctionObject.getARaisedType() instead */
predicate scope_raises(ClassObject ex, Scope s) {
predicate scope_raises_objectapi(ClassObject ex, Scope s) {
exists(BasicBlock b |
current_exception_objectapi(ex, b) and
b.getLastNode().isExceptionalExit(s)
|
b.getLastNode() instanceof ReraisingNode
)
or
exists(RaisingNode r | r.viableExceptionalExit_objectapi(s, ex))
}
/** INTERNAL -- Use FunctionObject.getARaisedType() instead */
predicate scope_raises(ClassValue ex, Scope s) {
exists(BasicBlock b |
current_exception(ex, b) and
b.getLastNode().isExceptionalExit(s)
@@ -299,11 +425,18 @@ class ExceptFlowNode extends ControlFlowNode {
}
/** Whether this `except` handles `cls` */
predicate handles(ClassObject cls) {
predicate handles_objectapi(ClassObject cls) {
exists(ClassObject handled | this.handledException_objectapi(handled, _, _) |
cls.getAnImproperSuperType() = handled
)
}
/** Whether this `except` handles `cls` */
predicate handles(ClassValue cls) {
exists(ClassValue handled | this.handledException(handled, _, _) |
cls.getASuperType() = handled
)
}
}
private ControlFlowNode element_from_tuple_objectapi(Object tuple) {
@@ -324,7 +457,15 @@ class ReraisingNode extends RaisingNode {
}
/** Gets a class that may be raised by this node */
override ClassObject getARaisedType() {
override ClassObject getARaisedType_objectapi() {
exists(BasicBlock b |
current_exception_objectapi(result, b) and
b.getNode(_) = this
)
}
/** Gets a class that may be raised by this node */
override ClassValue getARaisedType() {
exists(BasicBlock b |
current_exception(result, b) and
b.getNode(_) = this

View File

@@ -37,8 +37,8 @@ abstract deprecated class CustomPointsToFact extends @py_flow_node {
abstract predicate pointsTo(Context context, Object value, ClassObject cls, ControlFlowNode origin);
}
/* For backwards compatibility */
class FinalCustomPointsToFact = CustomPointsToFact;
/** DEPRECATED -- Use PointsToExtension instead */
deprecated class FinalCustomPointsToFact = CustomPointsToFact;
abstract deprecated class CustomPointsToOriginFact extends CustomPointsToFact {
abstract predicate pointsTo(Object value, ClassObject cls);
@@ -151,7 +151,7 @@ class ReModulePointToExtension extends PointsToExtension {
private predicate pointsTo_helper(Context context) { context.appliesTo(this) }
}
private class BackwardCompatiblePointToExtension extends PointsToExtension {
deprecated private class BackwardCompatiblePointToExtension extends PointsToExtension {
BackwardCompatiblePointToExtension() { this instanceof CustomPointsToFact }
override predicate pointsTo(Context context, ObjectInternal value, ControlFlowNode origin) {
@@ -174,7 +174,7 @@ private class BackwardCompatiblePointToExtension extends PointsToExtension {
}
}
private predicate additionalAttribute(
deprecated private predicate additionalAttribute(
ObjectInternal owner, string name, ObjectInternal value, ControlFlowNode origin
) {
exists(Object obj, ClassObject cls |

View File

@@ -135,7 +135,7 @@ class PyFunctionObject extends FunctionObject {
/** Whether this function is a procedure, that is, it has no explicit return statement and is not a generator function */
override predicate isProcedure() { this.getFunction().isProcedure() }
override ClassObject getARaisedType() { scope_raises(result, this.getFunction()) }
override ClassObject getARaisedType() { scope_raises_objectapi(result, this.getFunction()) }
override predicate raisesUnknownType() { scope_raises_unknown(this.getFunction()) }

View File

@@ -7,3 +7,4 @@ import semmle.python.web.bottle.Request
import semmle.python.web.turbogears.Request
import semmle.python.web.falcon.Request
import semmle.python.web.cherrypy.Request
import semmle.python.web.stdlib.Request

View File

@@ -7,3 +7,4 @@ import semmle.python.web.bottle.Response
import semmle.python.web.turbogears.Response
import semmle.python.web.falcon.Response
import semmle.python.web.cherrypy.Response
import semmle.python.web.stdlib.Response

View File

@@ -0,0 +1,124 @@
/**
* Provides the sources and taint-flow for HTTP servers defined using the standard library (stdlib).
* Specifically, we model `HttpRequestTaintSource`s from instances of `BaseHTTPRequestHandler`
* (or subclasses) and form parsing using `cgi.FieldStorage`.
*/
import python
import semmle.python.security.TaintTracking
import semmle.python.web.Http
/** Source of BaseHTTPRequestHandler instances. */
class StdLibRequestSource extends HttpRequestTaintSource {
StdLibRequestSource() {
exists(ClassValue cls |
cls.getABaseType+() = Value::named("BaseHTTPServer.BaseHTTPRequestHandler")
or
cls.getABaseType+() = Value::named("http.server.BaseHTTPRequestHandler")
|
this.(ControlFlowNode).pointsTo().getClass() = cls
)
}
override predicate isSourceOf(TaintKind kind) { kind instanceof BaseHTTPRequestHandlerKind }
}
/** TaintKind for an instance of BaseHTTPRequestHandler. */
class BaseHTTPRequestHandlerKind extends TaintKind {
BaseHTTPRequestHandlerKind() { this = "BaseHTTPRequestHandlerKind" }
override TaintKind getTaintOfAttribute(string name) {
name in ["requestline", "path"] and
result instanceof ExternalStringKind
or
name = "headers" and
result instanceof HTTPMessageKind
or
name = "rfile" and
result instanceof ExternalFileObject
}
}
/** TaintKind for headers (instance of HTTPMessage). */
class HTTPMessageKind extends ExternalStringDictKind {
override TaintKind getTaintOfMethodResult(string name) {
result = super.getTaintOfMethodResult(name)
or
name = "get_all" and
result.(SequenceKind).getItem() = this.getValue()
or
name in ["as_bytes", "as_string"] and
result instanceof ExternalStringKind
}
override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) {
result = super.getTaintForFlowStep(fromnode, tonode)
or
exists(ClassValue cls | cls = ClassValue::unicode() or cls = ClassValue::bytes() |
tonode = cls.getACall() and
tonode.(CallNode).getArg(0) = fromnode and
result instanceof ExternalStringKind
)
}
}
/** Source of parsed HTTP forms (by using the `cgi` module). */
class CgiFieldStorageSource extends HttpRequestTaintSource {
CgiFieldStorageSource() { this = Value::named("cgi.FieldStorage").getACall() }
override predicate isSourceOf(TaintKind kind) { kind instanceof CgiFieldStorageFormKind }
}
/** TaintKind for a parsed HTTP form. */
class CgiFieldStorageFormKind extends TaintKind {
/*
* There is a slight difference between how we model form/fields and how it is handled by the code.
* In the code
* ```
* form = cgi.FieldStorage()
* field = form['myfield']
* ```
* both `form` and `field` have the type `cgi.FieldStorage`. This allows the code to represent
* nested forms as `form['nested_form']['myfield']`. However, since HTML forms can't be nested
* we ignore that detail since it allows for a more clean modeling.
*/
CgiFieldStorageFormKind() { this = "CgiFieldStorageFormKind" }
override TaintKind getTaintOfAttribute(string name) {
name = "value" and result.(SequenceKind).getItem() instanceof CgiFieldStorageFieldKind
}
override TaintKind getTaintOfMethodResult(string name) {
name = "getvalue" and
(
result instanceof ExternalStringKind
or
result.(SequenceKind).getItem() instanceof ExternalStringKind
)
or
name = "getfirst" and
result instanceof ExternalStringKind
or
name = "getlist" and
result.(SequenceKind).getItem() instanceof ExternalStringKind
}
override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) {
tonode.(SubscriptNode).getObject() = fromnode and
(
result instanceof CgiFieldStorageFieldKind
or
result.(SequenceKind).getItem() instanceof CgiFieldStorageFieldKind
)
}
}
/** TaintKind for the field of a parsed HTTP form. */
class CgiFieldStorageFieldKind extends TaintKind {
CgiFieldStorageFieldKind() { this = "CgiFieldStorageFieldKind" }
override TaintKind getTaintOfAttribute(string name) {
name in ["filename", "value"] and result instanceof ExternalStringKind
or
name = "file" and result instanceof ExternalFileObject
}
}

View File

@@ -0,0 +1,43 @@
/**
* Provides the sinks for HTTP servers defined with standard library (stdlib).
*/
import python
import semmle.python.security.TaintTracking
import semmle.python.web.Http
private predicate is_wfile(AttrNode wfile) {
exists(ClassValue cls |
// Python 2
cls.getABaseType+() = Value::named("BaseHTTPServer.BaseHTTPRequestHandler")
or
// Python 3
cls.getABaseType+() = Value::named("http.server.BaseHTTPRequestHandler")
|
wfile.getObject("wfile").pointsTo().getClass() = cls
)
}
/** Sink for `h.wfile.write` where `h` is an instance of BaseHTTPRequestHandler. */
class StdLibWFileWriteSink extends HttpResponseTaintSink {
StdLibWFileWriteSink() {
exists(CallNode call |
is_wfile(call.getFunction().(AttrNode).getObject("write")) and
call.getArg(0) = this
)
}
override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
}
/** Sink for `h.wfile.writelines` where `h` is an instance of BaseHTTPRequestHandler. */
class StdLibWFileWritelinesSink extends HttpResponseTaintSink {
StdLibWFileWritelinesSink() {
exists(CallNode call |
is_wfile(call.getFunction().(AttrNode).getObject("writelines")) and
call.getArg(0) = this
)
}
override predicate sinks(TaintKind kind) { kind instanceof ExternalStringSequenceKind }
}

View File

@@ -1,5 +1,5 @@
import python
from RaisingNode r, Scope s, ClassObject cls
where r.viableExceptionalExit(s, cls)
where r.viableExceptionalExit_objectapi(s, cls)
select r.getLocation().getStartLine(), r.toString(), s.toString(), cls.toString()

View File

@@ -1,5 +1,3 @@
WARNING: Predicate points_to has been deprecated and may be removed in future (BooleanConstants.ql:6,25-44)
WARNING: Predicate points_to has been deprecated and may be removed in future (BooleanConstants.ql:7,29-48)
| module.py | 2 | ControlFlowNode for ImportExpr | import | true |
| module.py | 2 | ControlFlowNode for sys | import | true |
| module.py | 3 | ControlFlowNode for Compare | import | false |

View File

@@ -1,6 +1,6 @@
import python
from RaisingNode r, ControlFlowNode n, ClassObject ex
where r.viableExceptionEdge(n, ex)
where r.viableExceptionEdge_objectapi(n, ex)
select r.getLocation().getStartLine(), n.getLocation().getStartLine(), r.getNode().toString(),
n.getNode().toString(), ex.toString()

View File

@@ -1,4 +1,3 @@
WARNING: Predicate points_to has been deprecated and may be removed in future (Extend.ql:50,32-51)
WARNING: Type CustomPointsToAttribute has been deprecated and may be removed in future (Extend.ql:22,34-57)
WARNING: Type CustomPointsToObjectFact has been deprecated and may be removed in future (Extend.ql:38,32-56)
WARNING: Type CustomPointsToOriginFact has been deprecated and may be removed in future (Extend.ql:5,28-52)

View File

@@ -1,4 +1,3 @@
WARNING: Predicate points_to has been deprecated and may be removed in future (PointsToWithContext.ql:7,7-26)
| a_simple.py:2 | ControlFlowNode for FloatLiteral | float 1.0 | builtin-class float | 2 | import |
| a_simple.py:2 | ControlFlowNode for f1 | float 1.0 | builtin-class float | 2 | import |
| a_simple.py:3 | ControlFlowNode for dict | builtin-class dict | builtin-class type | 3 | import |

View File

@@ -1,4 +1,3 @@
WARNING: Predicate points_to has been deprecated and may be removed in future (PointsToWithType.ql:6,7-26)
| a_simple.py:2 | ControlFlowNode for FloatLiteral | float 1.0 | builtin-class float | 2 |
| a_simple.py:2 | ControlFlowNode for f1 | float 1.0 | builtin-class float | 2 |
| a_simple.py:3 | ControlFlowNode for dict | builtin-class dict | builtin-class type | 3 |

View File

@@ -1,10 +1,10 @@
import python
from NameNode name, CallNode call, string debug
from ControlFlowNode arg, CallNode call, string debug
where
call.getAnArg() = name and
call.getAnArg() = arg and
call.getFunction().(NameNode).getId() = "check" and
if exists(name.pointsTo())
then debug = name.pointsTo().toString()
if exists(arg.pointsTo())
then debug = arg.pointsTo().toString()
else debug = "<MISSING pointsTo()>"
select name, debug
select arg, debug

View File

@@ -1,10 +1,10 @@
import python
from NameNode name, CallNode call, string debug
from ControlFlowNode arg, CallNode call, string debug
where
call.getAnArg() = name and
call.getAnArg() = arg and
call.getFunction().(NameNode).getId() = "check" and
if exists(name.pointsTo())
then debug = name.pointsTo().toString()
if exists(arg.pointsTo())
then debug = arg.pointsTo().toString()
else debug = "<MISSING pointsTo()>"
select name, debug
select arg, debug

View File

@@ -0,0 +1,2 @@
| test.py:10:11:10:14 | ControlFlowNode for open | <MISSING pointsTo()> |
| test.py:14:11:14:14 | ControlFlowNode for open | Builtin-function open |

View File

@@ -0,0 +1,10 @@
import python
from ControlFlowNode arg, CallNode call, string debug
where
call.getAnArg() = arg and
call.getFunction().(NameNode).getId() = "check" and
if exists(arg.pointsTo())
then debug = arg.pointsTo().toString()
else debug = "<MISSING pointsTo()>"
select arg, debug

View File

@@ -0,0 +1,18 @@
# Points-to information seems to be missing if our analysis thinks the enclosing function
# is never called. However, as illustrated by the code below, it's easy to fool our
# analysis :(
# This was inspired by a problem in real code, where our analysis doesn't have any
# points-to information about the `open` call in
# https://google-gruyere.appspot.com/code/gruyere.py on line 227
def _func_not_called(filename, mode='rb'):
check(open)
return open(filename, mode)
def _func_called(filename, mode='rb'):
check(open)
return open(filename, mode)
globals()['_func_not_called']('test.txt')
_func_called('test.txt')

View File

@@ -1,5 +1,10 @@
| Taint [externally controlled string] | test.py:67 | test.py:67:20:67:43 | urlsplit() | | --> | Taint [externally controlled string] | test.py:69 | test.py:69:10:69:21 | urlsplit_res | |
| Taint [externally controlled string] | test.py:68 | test.py:68:20:68:43 | urlparse() | | --> | Taint [externally controlled string] | test.py:69 | test.py:69:24:69:35 | urlparse_res | |
| Taint [externally controlled string] | test.py:98 | test.py:98:9:98:37 | Attribute() | | --> | Taint externally controlled string | test.py:98 | test.py:98:9:98:40 | Subscript | |
| Taint [externally controlled string] | test.py:102 | test.py:102:9:102:38 | Attribute() | | --> | Taint externally controlled string | test.py:102 | test.py:102:9:102:41 | Subscript | |
| Taint [externally controlled string] | test.py:104 | test.py:104:9:104:37 | Attribute() | | --> | Taint externally controlled string | test.py:104 | test.py:104:9:104:41 | Subscript | |
| Taint [externally controlled string] | test.py:107 | test.py:107:9:107:30 | Attribute() | | --> | Taint externally controlled string | test.py:107 | test.py:107:9:107:33 | Subscript | |
| Taint [externally controlled string] | test.py:109 | test.py:109:9:109:35 | Attribute() | | --> | Taint externally controlled string | test.py:109 | test.py:109:9:109:38 | Subscript | |
| Taint exception.info | test.py:44 | test.py:44:22:44:26 | taint | p1 = exception.info | --> | Taint exception.info | test.py:45 | test.py:45:17:45:21 | taint | p1 = exception.info |
| Taint exception.info | test.py:45 | test.py:45:17:45:21 | taint | p1 = exception.info | --> | Taint exception.info | test.py:45 | test.py:45:12:45:22 | func() | p1 = exception.info |
| Taint exception.info | test.py:45 | test.py:45:17:45:21 | taint | p1 = exception.info | --> | Taint exception.info | test.py:52 | test.py:52:19:52:21 | arg | p0 = exception.info |
@@ -62,6 +67,71 @@
| Taint externally controlled string | test.py:66 | test.py:66:22:66:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:68 | test.py:68:29:68:42 | tainted_string | |
| Taint externally controlled string | test.py:67 | test.py:67:29:67:42 | tainted_string | | --> | Taint [externally controlled string] | test.py:67 | test.py:67:20:67:43 | urlsplit() | |
| Taint externally controlled string | test.py:68 | test.py:68:29:68:42 | tainted_string | | --> | Taint [externally controlled string] | test.py:68 | test.py:68:20:68:43 | urlparse() | |
| Taint externally controlled string | test.py:72 | test.py:72:22:72:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:74 | test.py:74:9:74:22 | tainted_string | |
| Taint externally controlled string | test.py:72 | test.py:72:22:72:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:76 | test.py:76:12:76:25 | tainted_string | |
| Taint externally controlled string | test.py:74 | test.py:74:9:74:22 | tainted_string | | --> | Taint externally controlled string | test.py:74 | test.py:74:9:74:30 | Attribute() | |
| Taint externally controlled string | test.py:74 | test.py:74:9:74:30 | Attribute() | | --> | Taint externally controlled string | test.py:79 | test.py:79:10:79:10 | a | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:85 | test.py:85:9:85:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:86 | test.py:86:9:86:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:87 | test.py:87:9:87:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:88 | test.py:88:9:88:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:89 | test.py:89:9:89:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:90 | test.py:90:9:90:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:91 | test.py:91:9:91:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:92 | test.py:92:9:92:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:93 | test.py:93:9:93:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:94 | test.py:94:9:94:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:95 | test.py:95:9:95:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:96 | test.py:96:9:96:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:97 | test.py:97:9:97:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:98 | test.py:98:9:98:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:99 | test.py:99:9:99:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:100 | test.py:100:9:100:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:101 | test.py:101:9:101:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:102 | test.py:102:9:102:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:103 | test.py:103:9:103:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:104 | test.py:104:9:104:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:105 | test.py:105:9:105:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:106 | test.py:106:9:106:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:107 | test.py:107:9:107:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:108 | test.py:108:9:108:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:109 | test.py:109:9:109:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:110 | test.py:110:9:110:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:111 | test.py:111:9:111:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:112 | test.py:112:9:112:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:115 | test.py:115:9:115:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:116 | test.py:116:9:116:22 | tainted_string | |
| Taint externally controlled string | test.py:85 | test.py:85:9:85:22 | tainted_string | | --> | Taint externally controlled string | test.py:85 | test.py:85:9:85:35 | Attribute() | |
| Taint externally controlled string | test.py:86 | test.py:86:9:86:22 | tainted_string | | --> | Taint externally controlled string | test.py:86 | test.py:86:9:86:33 | Attribute() | |
| Taint externally controlled string | test.py:87 | test.py:87:9:87:22 | tainted_string | | --> | Taint externally controlled string | test.py:87 | test.py:87:9:87:31 | Attribute() | |
| Taint externally controlled string | test.py:88 | test.py:88:9:88:22 | tainted_string | | --> | Taint externally controlled string | test.py:88 | test.py:88:9:88:38 | Attribute() | |
| Taint externally controlled string | test.py:89 | test.py:89:9:89:22 | tainted_string | | --> | Taint externally controlled string | test.py:89 | test.py:89:9:89:38 | Attribute() | |
| Taint externally controlled string | test.py:89 | test.py:89:9:89:38 | Attribute() | | --> | Taint externally controlled string | test.py:89 | test.py:89:9:89:54 | Attribute() | |
| Taint externally controlled string | test.py:90 | test.py:90:9:90:22 | tainted_string | | --> | Taint externally controlled string | test.py:90 | test.py:90:9:90:35 | Attribute() | |
| Taint externally controlled string | test.py:91 | test.py:91:9:91:22 | tainted_string | | --> | Taint externally controlled string | test.py:91 | test.py:91:9:91:37 | Attribute() | |
| Taint externally controlled string | test.py:92 | test.py:92:9:92:22 | tainted_string | | --> | Taint externally controlled string | test.py:92 | test.py:92:9:92:46 | Attribute() | |
| Taint externally controlled string | test.py:93 | test.py:93:9:93:22 | tainted_string | | --> | Taint externally controlled string | test.py:93 | test.py:93:9:93:33 | Attribute() | |
| Taint externally controlled string | test.py:94 | test.py:94:9:94:22 | tainted_string | | --> | Taint externally controlled string | test.py:94 | test.py:94:9:94:30 | Attribute() | |
| Taint externally controlled string | test.py:95 | test.py:95:9:95:22 | tainted_string | | --> | Taint externally controlled string | test.py:95 | test.py:95:9:95:31 | Attribute() | |
| Taint externally controlled string | test.py:96 | test.py:96:9:96:22 | tainted_string | | --> | Taint externally controlled string | test.py:96 | test.py:96:9:96:35 | Attribute() | |
| Taint externally controlled string | test.py:97 | test.py:97:9:97:22 | tainted_string | | --> | Taint [externally controlled string] | test.py:97 | test.py:97:9:97:37 | Attribute() | |
| Taint externally controlled string | test.py:98 | test.py:98:9:98:22 | tainted_string | | --> | Taint [externally controlled string] | test.py:98 | test.py:98:9:98:37 | Attribute() | |
| Taint externally controlled string | test.py:99 | test.py:99:9:99:22 | tainted_string | | --> | Taint externally controlled string | test.py:99 | test.py:99:9:99:42 | Attribute() | |
| Taint externally controlled string | test.py:100 | test.py:100:9:100:22 | tainted_string | | --> | Taint externally controlled string | test.py:100 | test.py:100:9:100:33 | Attribute() | |
| Taint externally controlled string | test.py:101 | test.py:101:9:101:22 | tainted_string | | --> | Taint [externally controlled string] | test.py:101 | test.py:101:9:101:38 | Attribute() | |
| Taint externally controlled string | test.py:102 | test.py:102:9:102:22 | tainted_string | | --> | Taint [externally controlled string] | test.py:102 | test.py:102:9:102:38 | Attribute() | |
| Taint externally controlled string | test.py:103 | test.py:103:9:103:22 | tainted_string | | --> | Taint [externally controlled string] | test.py:103 | test.py:103:9:103:37 | Attribute() | |
| Taint externally controlled string | test.py:104 | test.py:104:9:104:22 | tainted_string | | --> | Taint [externally controlled string] | test.py:104 | test.py:104:9:104:37 | Attribute() | |
| Taint externally controlled string | test.py:105 | test.py:105:9:105:22 | tainted_string | | --> | Taint externally controlled string | test.py:105 | test.py:105:9:105:31 | Attribute() | |
| Taint externally controlled string | test.py:106 | test.py:106:9:106:22 | tainted_string | | --> | Taint [externally controlled string] | test.py:106 | test.py:106:9:106:30 | Attribute() | |
| Taint externally controlled string | test.py:107 | test.py:107:9:107:22 | tainted_string | | --> | Taint [externally controlled string] | test.py:107 | test.py:107:9:107:30 | Attribute() | |
| Taint externally controlled string | test.py:108 | test.py:108:9:108:22 | tainted_string | | --> | Taint [externally controlled string] | test.py:108 | test.py:108:9:108:35 | Attribute() | |
| Taint externally controlled string | test.py:109 | test.py:109:9:109:22 | tainted_string | | --> | Taint [externally controlled string] | test.py:109 | test.py:109:9:109:35 | Attribute() | |
| Taint externally controlled string | test.py:110 | test.py:110:9:110:22 | tainted_string | | --> | Taint externally controlled string | test.py:110 | test.py:110:9:110:30 | Attribute() | |
| Taint externally controlled string | test.py:111 | test.py:111:9:111:22 | tainted_string | | --> | Taint externally controlled string | test.py:111 | test.py:111:9:111:33 | Attribute() | |
| Taint externally controlled string | test.py:112 | test.py:112:9:112:22 | tainted_string | | --> | Taint externally controlled string | test.py:112 | test.py:112:9:112:30 | Attribute() | |
| Taint externally controlled string | test.py:115 | test.py:115:9:115:22 | tainted_string | | --> | Taint externally controlled string | test.py:115 | test.py:115:9:115:30 | Attribute() | |
| Taint externally controlled string | test.py:116 | test.py:116:9:116:22 | tainted_string | | --> | Taint externally controlled string | test.py:116 | test.py:116:9:116:33 | Attribute() | |
| Taint json[externally controlled string] | test.py:6 | test.py:6:20:6:45 | Attribute() | | --> | Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:20 | tainted_json | |
| Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:20 | tainted_json | | --> | Taint externally controlled string | test.py:7 | test.py:7:9:7:25 | Subscript | |
| Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:20 | tainted_json | | --> | Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:25 | Subscript | |

View File

@@ -22,3 +22,35 @@
| test.py:58 | test_untrusted | res | externally controlled string |
| test.py:69 | test_urlsplit_urlparse | urlparse_res | [externally controlled string] |
| test.py:69 | test_urlsplit_urlparse | urlsplit_res | [externally controlled string] |
| test.py:79 | test_method_reference | a | externally controlled string |
| test.py:79 | test_method_reference | b | NO TAINT |
| test.py:85 | test_str_methods | Attribute() | externally controlled string |
| test.py:86 | test_str_methods | Attribute() | externally controlled string |
| test.py:87 | test_str_methods | Attribute() | externally controlled string |
| test.py:88 | test_str_methods | Attribute() | externally controlled string |
| test.py:89 | test_str_methods | Attribute() | externally controlled string |
| test.py:90 | test_str_methods | Attribute() | externally controlled string |
| test.py:91 | test_str_methods | Attribute() | externally controlled string |
| test.py:92 | test_str_methods | Attribute() | externally controlled string |
| test.py:93 | test_str_methods | Attribute() | externally controlled string |
| test.py:94 | test_str_methods | Attribute() | externally controlled string |
| test.py:95 | test_str_methods | Attribute() | externally controlled string |
| test.py:96 | test_str_methods | Attribute() | externally controlled string |
| test.py:97 | test_str_methods | Attribute() | [externally controlled string] |
| test.py:98 | test_str_methods | Subscript | externally controlled string |
| test.py:99 | test_str_methods | Attribute() | externally controlled string |
| test.py:100 | test_str_methods | Attribute() | externally controlled string |
| test.py:101 | test_str_methods | Attribute() | [externally controlled string] |
| test.py:102 | test_str_methods | Subscript | externally controlled string |
| test.py:103 | test_str_methods | Attribute() | [externally controlled string] |
| test.py:104 | test_str_methods | Subscript | externally controlled string |
| test.py:105 | test_str_methods | Attribute() | externally controlled string |
| test.py:106 | test_str_methods | Attribute() | [externally controlled string] |
| test.py:107 | test_str_methods | Subscript | externally controlled string |
| test.py:108 | test_str_methods | Attribute() | [externally controlled string] |
| test.py:109 | test_str_methods | Subscript | externally controlled string |
| test.py:110 | test_str_methods | Attribute() | externally controlled string |
| test.py:111 | test_str_methods | Attribute() | externally controlled string |
| test.py:112 | test_str_methods | Attribute() | externally controlled string |
| test.py:115 | test_str_methods | Attribute() | externally controlled string |
| test.py:116 | test_str_methods | Attribute() | externally controlled string |

View File

@@ -67,3 +67,51 @@ def test_urlsplit_urlparse():
urlsplit_res = urlsplit(tainted_string)
urlparse_res = urlparse(tainted_string)
test(urlsplit_res, urlparse_res)
def test_method_reference():
tainted_string = TAINTED_STRING
a = tainted_string.title()
func = tainted_string.title
b = func()
test(a, b) # TODO: `b` not tainted
def test_str_methods():
tainted_string = TAINTED_STRING
test(
tainted_string.capitalize(),
tainted_string.casefold(),
tainted_string.center(),
tainted_string.encode('utf-8'),
tainted_string.encode('utf-8').decode('utf-8'),
tainted_string.expandtabs(),
tainted_string.format(foo=42),
tainted_string.format_map({'foo': 42}),
tainted_string.ljust(100),
tainted_string.lower(),
tainted_string.lstrip(),
tainted_string.lstrip('w.'),
tainted_string.partition(';'),
tainted_string.partition(';')[0],
tainted_string.replace('/', '', 1),
tainted_string.rjust(100),
tainted_string.rpartition(';'),
tainted_string.rpartition(';')[2],
tainted_string.rsplit(';', 4),
tainted_string.rsplit(';', 4)[-1],
tainted_string.rstrip(),
tainted_string.split(),
tainted_string.split()[0],
tainted_string.splitlines(),
tainted_string.splitlines()[0],
tainted_string.strip(),
tainted_string.swapcase(),
tainted_string.title(),
# ignoring, as I have never seen this in practice
# tainted_string.translate(translation_table),
tainted_string.upper(),
tainted_string.zfill(100),
)

View File

@@ -1,5 +1,5 @@
import python
from RaisingNode r, Scope s, ClassObject cls
where r.viableExceptionalExit(s, cls)
where r.viableExceptionalExit_objectapi(s, cls)
select r.getLocation().getStartLine(), r, s.toString(), cls

View File

@@ -1,5 +1,5 @@
import python
from ExceptFlowNode n, ClassObject cls
where n.handles(cls)
where n.handles_objectapi(cls)
select n.getLocation().getStartLine(), cls.toString()

View File

@@ -1,6 +1,6 @@
import python
from RaisingNode r, ControlFlowNode n, ClassObject ex
where r.viableExceptionEdge(n, ex)
where r.viableExceptionEdge_objectapi(n, ex)
select r.getLocation().getStartLine(), n.getLocation().getStartLine(), r.getNode().toString(),
n.getNode().toString(), ex.toString()

View File

@@ -0,0 +1,2 @@
| test.py:72:26:72:58 | Taint sink | externally controlled string |
| test.py:73:31:73:54 | Taint sink | [externally controlled string] |

View File

@@ -0,0 +1,7 @@
import python
import semmle.python.web.HttpResponse
import semmle.python.security.strings.Untrusted
from HttpResponseTaintSink sink, TaintKind kind
where sink.sinks(kind)
select sink, kind

View File

@@ -0,0 +1,34 @@
| test.py:18:13:18:16 | self | BaseHTTPRequestHandlerKind |
| test.py:20:13:20:16 | self | BaseHTTPRequestHandlerKind |
| test.py:22:13:22:16 | self | BaseHTTPRequestHandlerKind |
| test.py:24:13:24:16 | self | BaseHTTPRequestHandlerKind |
| test.py:25:13:25:16 | self | BaseHTTPRequestHandlerKind |
| test.py:26:13:26:16 | self | BaseHTTPRequestHandlerKind |
| test.py:27:13:27:16 | self | BaseHTTPRequestHandlerKind |
| test.py:28:13:28:16 | self | BaseHTTPRequestHandlerKind |
| test.py:29:13:29:16 | self | BaseHTTPRequestHandlerKind |
| test.py:30:13:30:16 | self | BaseHTTPRequestHandlerKind |
| test.py:31:13:31:16 | self | BaseHTTPRequestHandlerKind |
| test.py:32:13:32:16 | self | BaseHTTPRequestHandlerKind |
| test.py:33:17:33:20 | self | BaseHTTPRequestHandlerKind |
| test.py:34:19:34:22 | self | BaseHTTPRequestHandlerKind |
| test.py:36:13:36:16 | self | BaseHTTPRequestHandlerKind |
| test.py:37:13:37:16 | self | BaseHTTPRequestHandlerKind |
| test.py:40:16:44:9 | Attribute() | CgiFieldStorageFormKind |
| test.py:41:13:41:16 | self | BaseHTTPRequestHandlerKind |
| test.py:42:13:42:16 | self | BaseHTTPRequestHandlerKind |
| test.py:43:64:43:67 | self | BaseHTTPRequestHandlerKind |
| test.py:69:9:69:12 | self | BaseHTTPRequestHandlerKind |
| test.py:70:9:70:12 | self | BaseHTTPRequestHandlerKind |
| test.py:71:9:71:12 | self | BaseHTTPRequestHandlerKind |
| test.py:72:9:72:12 | self | BaseHTTPRequestHandlerKind |
| test.py:73:9:73:12 | self | BaseHTTPRequestHandlerKind |
| test.py:74:15:74:18 | self | BaseHTTPRequestHandlerKind |
| test.py:78:16:82:9 | Attribute() | CgiFieldStorageFormKind |
| test.py:79:13:79:16 | self | BaseHTTPRequestHandlerKind |
| test.py:80:13:80:16 | self | BaseHTTPRequestHandlerKind |
| test.py:81:64:81:67 | self | BaseHTTPRequestHandlerKind |
| test.py:85:13:85:16 | self | BaseHTTPRequestHandlerKind |
| test.py:86:13:86:16 | self | BaseHTTPRequestHandlerKind |
| test.py:96:9:96:12 | self | BaseHTTPRequestHandlerKind |
| test.py:97:9:97:12 | self | BaseHTTPRequestHandlerKind |

View File

@@ -0,0 +1,9 @@
import python
import semmle.python.web.HttpRequest
import semmle.python.security.strings.Untrusted
from HttpRequestTaintSource source, TaintKind kind
where
source.isSourceOf(kind) and
source.getLocation().getFile().getShortName() != "cgi.py"
select source.(ControlFlowNode).getNode(), kind

View File

@@ -0,0 +1,32 @@
| test.py:18 | ok | taint_sources | self | BaseHTTPRequestHandlerKind |
| test.py:20 | ok | taint_sources | Attribute | externally controlled string |
| test.py:22 | ok | taint_sources | Attribute | externally controlled string |
| test.py:24 | ok | taint_sources | Attribute | {externally controlled string} |
| test.py:25 | ok | taint_sources | Subscript | externally controlled string |
| test.py:26 | ok | taint_sources | Attribute() | externally controlled string |
| test.py:27 | ok | taint_sources | Attribute() | [externally controlled string] |
| test.py:28 | fail | taint_sources | Attribute() | <NO TAINT> |
| test.py:29 | ok | taint_sources | Attribute() | [externally controlled string] |
| test.py:30 | fail | taint_sources | Attribute() | <NO TAINT> |
| test.py:31 | ok | taint_sources | Attribute() | externally controlled string |
| test.py:32 | ok | taint_sources | Attribute() | externally controlled string |
| test.py:33 | ok | taint_sources | str() | externally controlled string |
| test.py:34 | ok | taint_sources | bytes() | externally controlled string |
| test.py:36 | ok | taint_sources | Attribute | file[externally controlled string] |
| test.py:37 | ok | taint_sources | Attribute() | externally controlled string |
| test.py:47 | ok | taint_sources | form | CgiFieldStorageFormKind |
| test.py:49 | ok | taint_sources | Subscript | CgiFieldStorageFieldKind |
| test.py:49 | ok | taint_sources | Subscript | [CgiFieldStorageFieldKind] |
| test.py:50 | ok | taint_sources | Attribute | externally controlled string |
| test.py:51 | ok | taint_sources | Attribute | file[externally controlled string] |
| test.py:52 | ok | taint_sources | Attribute | externally controlled string |
| test.py:53 | ok | taint_sources | Subscript | CgiFieldStorageFieldKind |
| test.py:54 | ok | taint_sources | Attribute | externally controlled string |
| test.py:55 | ok | taint_sources | Attribute | file[externally controlled string] |
| test.py:56 | ok | taint_sources | Attribute | externally controlled string |
| test.py:58 | ok | taint_sources | Attribute() | [externally controlled string] |
| test.py:58 | ok | taint_sources | Attribute() | externally controlled string |
| test.py:59 | ok | taint_sources | Subscript | externally controlled string |
| test.py:61 | ok | taint_sources | Attribute() | externally controlled string |
| test.py:63 | ok | taint_sources | Attribute() | [externally controlled string] |
| test.py:64 | ok | taint_sources | Subscript | externally controlled string |

View File

@@ -0,0 +1,32 @@
import python
import semmle.python.security.TaintTracking
import semmle.python.web.HttpRequest
import semmle.python.security.strings.Untrusted
from
Call call, Expr arg, boolean expected_taint, boolean has_taint, string test_res,
string taint_string
where
call.getLocation().getFile().getShortName() = "test.py" and
(
call.getFunc().(Name).getId() = "ensure_tainted" and
expected_taint = true
or
call.getFunc().(Name).getId() = "ensure_not_tainted" and
expected_taint = false
) and
arg = call.getAnArg() and
(
not exists(TaintedNode tainted | tainted.getAstNode() = arg) and
taint_string = "<NO TAINT>" and
has_taint = false
or
exists(TaintedNode tainted | tainted.getAstNode() = arg |
taint_string = tainted.getTaintKind().toString()
) and
has_taint = true
) and
if expected_taint = has_taint then test_res = "ok " else test_res = "fail"
// if expected_taint = has_taint then test_res = "✓" else test_res = "✕"
select arg.getLocation().toString(), test_res, call.getScope().(Function).getName(), arg.toString(),
taint_string

View File

@@ -0,0 +1,108 @@
import sys
import os
import cgi
if sys.version_info[0] == 2:
from BaseHTTPServer import BaseHTTPRequestHandler
from BaseHTTPServer import HTTPServer
if sys.version_info[0] == 3:
from http.server import HTTPServer, BaseHTTPRequestHandler
class MyHandler(BaseHTTPRequestHandler):
def taint_sources(self):
ensure_tainted(
self,
self.requestline,
self.path,
self.headers,
self.headers['Foo'],
self.headers.get('Foo'),
self.headers.get_all('Foo'),
self.headers.keys(),
self.headers.values(),
self.headers.items(),
self.headers.as_bytes(),
self.headers.as_string(),
str(self.headers),
bytes(self.headers),
self.rfile,
self.rfile.read(),
)
form = cgi.FieldStorage(
self.rfile,
self.headers,
environ={'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': self.headers.get('content-type')},
)
ensure_tainted(
form,
form['key'],
form['key'].value,
form['key'].file,
form['key'].filename,
form['key'][0], # will be a list, if multiple fields named "key" are provided
form['key'][0].value,
form['key'][0].file,
form['key'][0].filename,
form.getvalue('key'),
form.getvalue('key')[0], # will be a list, if multiple fields named "key" are provided
form.getfirst('key'),
form.getlist('key'),
form.getlist('key')[0],
)
def do_GET(self):
# send_response will log a line to stderr
self.send_response(200)
self.send_header("Content-type", "text/plain; charset=utf-8")
self.end_headers()
self.wfile.write(b"Hello BaseHTTPRequestHandler\n")
self.wfile.writelines([b"1\n", b"2\n", b"3\n"])
print(self.headers)
def do_POST(self):
form = cgi.FieldStorage(
self.rfile,
self.headers,
environ={'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': self.headers.get('content-type')},
)
if 'myfile' not in form:
self.send_response(422)
self.end_headers()
return
field = form['myfile']
field.file.seek(0, os.SEEK_END)
filesize = field.file.tell()
print("Uploaded {!r} with {} bytes".format(field.filename, filesize))
self.send_response(200)
self.end_headers()
if __name__ == "__main__":
server = HTTPServer(("127.0.0.1", 8080), MyHandler)
server.serve_forever()
# Headers works case insensitvely, so self.headers['foo'] == self.headers['FOO']
# curl localhost:8080 --header "Foo: 1" --header "foo: 2"
# To test file submission through forms, use
# curl -F myfile=@<yourfile> localhost:8080

View File

@@ -1,3 +1,4 @@
name: codeql-python-tests
version: 0.0.0
libraryPathDependencies: codeql-python
extractor: python

View File

@@ -1,4 +1,4 @@
| wrong_arguments.py:65:1:65:7 | F0() | Keyword argument 'y' is not a supported parameter name of $@. | wrong_arguments.py:4:5:4:26 | Function __init__ | F0.__init__ |
| wrong_arguments.py:66:1:66:7 | F1() | Keyword argument 'z' is not a supported parameter name of $@. | wrong_arguments.py:8:5:8:36 | Function __init__ | F1.__init__ |
| wrong_arguments.py:67:1:67:12 | F2() | Keyword argument 'y' is not a supported parameter name of $@. | wrong_arguments.py:12:5:12:30 | Function __init__ | F2.__init__ |
| wrong_arguments.py:92:1:92:27 | F6() | Keyword argument 'z' is not a supported parameter name of $@. | wrong_arguments.py:28:5:28:30 | Function __init__ | F6.__init__ |
| wrong_arguments.py:65:1:65:7 | F0() | Keyword argument 'y' is not a supported parameter name of $@. | wrong_arguments.py:4:5:4:26 | Function F0.__init__ | F0.__init__ |
| wrong_arguments.py:66:1:66:7 | F1() | Keyword argument 'z' is not a supported parameter name of $@. | wrong_arguments.py:8:5:8:36 | Function F1.__init__ | F1.__init__ |
| wrong_arguments.py:67:1:67:12 | F2() | Keyword argument 'y' is not a supported parameter name of $@. | wrong_arguments.py:12:5:12:30 | Function F2.__init__ | F2.__init__ |
| wrong_arguments.py:92:1:92:27 | F6() | Keyword argument 'z' is not a supported parameter name of $@. | wrong_arguments.py:28:5:28:30 | Function F6.__init__ | F6.__init__ |

View File

@@ -1,15 +1,15 @@
| wrong_arguments.py:37:1:37:4 | F0() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:4:5:4:26 | Function __init__ | F0.__init__ |
| wrong_arguments.py:38:1:38:4 | F1() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:8:5:8:36 | Function __init__ | F1.__init__ |
| wrong_arguments.py:39:1:39:4 | F2() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:12:5:12:30 | Function __init__ | F2.__init__ |
| wrong_arguments.py:40:1:40:4 | F3() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:16:5:16:40 | Function __init__ | F3.__init__ |
| wrong_arguments.py:41:1:41:4 | F4() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:20:5:20:31 | Function __init__ | F4.__init__ |
| wrong_arguments.py:42:1:42:4 | F5() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:24:5:24:42 | Function __init__ | F5.__init__ |
| wrong_arguments.py:43:1:43:5 | F6() | Call to $@ with too few arguments; should be no fewer than 2. | wrong_arguments.py:28:5:28:30 | Function __init__ | F6.__init__ |
| wrong_arguments.py:44:1:44:7 | F7() | Call to $@ with too few arguments; should be no fewer than 3. | wrong_arguments.py:32:5:32:33 | Function __init__ | F7.__init__ |
| wrong_arguments.py:48:1:48:7 | F0() | Call to $@ with too many arguments; should be no more than 1. | wrong_arguments.py:4:5:4:26 | Function __init__ | F0.__init__ |
| wrong_arguments.py:49:1:49:9 | F1() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:8:5:8:36 | Function __init__ | F1.__init__ |
| wrong_arguments.py:50:1:50:9 | F5() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:24:5:24:42 | Function __init__ | F5.__init__ |
| wrong_arguments.py:51:1:51:9 | F6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:28:5:28:30 | Function __init__ | F6.__init__ |
| wrong_arguments.py:52:1:52:11 | F6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:28:5:28:30 | Function __init__ | F6.__init__ |
| wrong_arguments.py:85:1:85:12 | F6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:28:5:28:30 | Function __init__ | F6.__init__ |
| wrong_arguments.py:86:1:86:7 | F6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:28:5:28:30 | Function __init__ | F6.__init__ |
| wrong_arguments.py:37:1:37:4 | F0() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:4:5:4:26 | Function F0.__init__ | F0.__init__ |
| wrong_arguments.py:38:1:38:4 | F1() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:8:5:8:36 | Function F1.__init__ | F1.__init__ |
| wrong_arguments.py:39:1:39:4 | F2() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:12:5:12:30 | Function F2.__init__ | F2.__init__ |
| wrong_arguments.py:40:1:40:4 | F3() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:16:5:16:40 | Function F3.__init__ | F3.__init__ |
| wrong_arguments.py:41:1:41:4 | F4() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:20:5:20:31 | Function F4.__init__ | F4.__init__ |
| wrong_arguments.py:42:1:42:4 | F5() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:24:5:24:42 | Function F5.__init__ | F5.__init__ |
| wrong_arguments.py:43:1:43:5 | F6() | Call to $@ with too few arguments; should be no fewer than 2. | wrong_arguments.py:28:5:28:30 | Function F6.__init__ | F6.__init__ |
| wrong_arguments.py:44:1:44:7 | F7() | Call to $@ with too few arguments; should be no fewer than 3. | wrong_arguments.py:32:5:32:33 | Function F7.__init__ | F7.__init__ |
| wrong_arguments.py:48:1:48:7 | F0() | Call to $@ with too many arguments; should be no more than 1. | wrong_arguments.py:4:5:4:26 | Function F0.__init__ | F0.__init__ |
| wrong_arguments.py:49:1:49:9 | F1() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:8:5:8:36 | Function F1.__init__ | F1.__init__ |
| wrong_arguments.py:50:1:50:9 | F5() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:24:5:24:42 | Function F5.__init__ | F5.__init__ |
| wrong_arguments.py:51:1:51:9 | F6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:28:5:28:30 | Function F6.__init__ | F6.__init__ |
| wrong_arguments.py:52:1:52:11 | F6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:28:5:28:30 | Function F6.__init__ | F6.__init__ |
| wrong_arguments.py:85:1:85:12 | F6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:28:5:28:30 | Function F6.__init__ | F6.__init__ |
| wrong_arguments.py:86:1:86:7 | F6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:28:5:28:30 | Function F6.__init__ | F6.__init__ |

View File

@@ -1,7 +1,7 @@
| use_mox.py:28:1:28:4 | f0() | Call to $@ with too few arguments; should be no fewer than 1. | use_mox.py:7:1:7:10 | Function f0 | function f0 |
| use_mox.py:29:1:29:5 | f1() | Call to $@ with too few arguments; should be no fewer than 2. | use_mox.py:10:1:10:13 | Function f1 | function f1 |
| use_mox.py:32:1:32:8 | Attribute() | Call to $@ with too few arguments; should be no fewer than 1. | use_mox.py:15:5:15:20 | Function m0 | method C.m0 |
| use_mox.py:33:1:33:9 | Attribute() | Call to $@ with too few arguments; should be no fewer than 2. | use_mox.py:18:5:18:23 | Function m1 | method C.m1 |
| use_mox.py:32:1:32:8 | Attribute() | Call to $@ with too few arguments; should be no fewer than 1. | use_mox.py:15:5:15:20 | Function C.m0 | method C.m0 |
| use_mox.py:33:1:33:9 | Attribute() | Call to $@ with too few arguments; should be no fewer than 2. | use_mox.py:18:5:18:23 | Function C.m1 | method C.m1 |
| wrong_arguments.py:29:1:29:4 | f0() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:3:1:3:10 | Function f0 | function f0 |
| wrong_arguments.py:30:1:30:4 | f1() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:6:1:6:20 | Function f1 | function f1 |
| wrong_arguments.py:31:1:31:4 | f2() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:9:1:9:14 | Function f2 | function f2 |
@@ -21,5 +21,5 @@
| wrong_arguments.py:86:1:86:4 | l1() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:71:6:71:21 | Function lambda | function lambda |
| wrong_arguments.py:96:1:96:12 | f6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:21:1:21:13 | Function f6 | function f6 |
| wrong_arguments.py:97:1:97:7 | f6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:21:1:21:13 | Function f6 | function f6 |
| wrong_arguments.py:130:1:130:9 | Attribute() | Call to $@ with too few arguments; should be no fewer than 2. | wrong_arguments.py:126:5:126:31 | Function spam | method Eggs2.spam |
| wrong_arguments.py:130:1:130:9 | Attribute() | Call to $@ with too many arguments; should be no more than 0. | wrong_arguments.py:121:5:121:19 | Function spam | method Eggs1.spam |
| wrong_arguments.py:130:1:130:9 | Attribute() | Call to $@ with too few arguments; should be no fewer than 2. | wrong_arguments.py:126:5:126:31 | Function Eggs2.spam | method Eggs2.spam |
| wrong_arguments.py:130:1:130:9 | Attribute() | Call to $@ with too many arguments; should be no more than 0. | wrong_arguments.py:121:5:121:19 | Function Eggs1.spam | method Eggs1.spam |

View File

@@ -1,4 +1,4 @@
| file://:Compiled Code:0:0:0:0 | builtin-class object | Class object is returned as an iterator (by $@) but does not fully implement the iterator interface. | protocols.py:16:5:16:23 | Function __iter__ | __iter__ |
| protocols.py:20:1:20:26 | class IteratorMissingNext | Class IteratorMissingNext is returned as an iterator (by $@) but does not fully implement the iterator interface. | protocols.py:22:5:22:23 | Function __iter__ | __iter__ |
| protocols.py:20:1:20:26 | class IteratorMissingNext | Class IteratorMissingNext is returned as an iterator (by $@) but does not fully implement the iterator interface. | protocols.py:27:5:27:23 | Function __iter__ | __iter__ |
| protocols.py:30:1:30:26 | class IteratorMissingIter | Class IteratorMissingIter is returned as an iterator (by $@) but does not fully implement the iterator interface. | protocols.py:40:5:40:23 | Function __iter__ | __iter__ |
| file://:0:0:0:0 | builtin-class object | Class object is returned as an iterator (by $@) but does not fully implement the iterator interface. | protocols.py:16:5:16:23 | Function X.__iter__ | __iter__ |
| protocols.py:20:1:20:26 | class IteratorMissingNext | Class IteratorMissingNext is returned as an iterator (by $@) but does not fully implement the iterator interface. | protocols.py:22:5:22:23 | Function IteratorMissingNext.__iter__ | __iter__ |
| protocols.py:20:1:20:26 | class IteratorMissingNext | Class IteratorMissingNext is returned as an iterator (by $@) but does not fully implement the iterator interface. | protocols.py:27:5:27:23 | Function IterableMissingNext.__iter__ | __iter__ |
| protocols.py:30:1:30:26 | class IteratorMissingIter | Class IteratorMissingIter is returned as an iterator (by $@) but does not fully implement the iterator interface. | protocols.py:40:5:40:23 | Function IterableMissingIter.__iter__ | __iter__ |

View File

@@ -1 +1 @@
| protocols.py:74:5:74:22 | Function __del__ | Overly complex '__del__' method. |
| protocols.py:74:5:74:22 | Function MegaDel.__del__ | Overly complex '__del__' method. |

View File

@@ -1,6 +1,6 @@
| test.py:24:5:24:26 | Function meth1 | Overriding method signature does not match $@, where it is passed too few. Overridden method $@ is correctly specified. | test.py:15:9:15:20 | Attribute() | here | test.py:5:5:5:20 | Function meth1 | method Base.meth1 |
| test.py:24:5:24:26 | Function meth1 | Overriding method signature does not match $@, where it is passed too few. Overridden method $@ is correctly specified. | test.py:34:9:34:20 | Attribute() | here | test.py:5:5:5:20 | Function meth1 | method Base.meth1 |
| test.py:27:5:27:20 | Function meth2 | Overriding method signature does not match $@, where it is passed an argument named 'spam'. Overridden method $@ is correctly specified. | test.py:20:9:20:31 | Attribute() | here | test.py:8:5:8:26 | Function meth2 | method Base.meth2 |
| test.py:27:5:27:20 | Function meth2 | Overriding method signature does not match $@, where it is passed an argument named 'spam'. Overridden method $@ is correctly specified. | test.py:39:9:39:31 | Attribute() | here | test.py:8:5:8:26 | Function meth2 | method Base.meth2 |
| test.py:27:5:27:20 | Function meth2 | Overriding method signature does not match $@, where it is passed too many. Overridden method $@ is correctly specified. | test.py:18:9:18:21 | Attribute() | here | test.py:8:5:8:26 | Function meth2 | method Base.meth2 |
| test.py:27:5:27:20 | Function meth2 | Overriding method signature does not match $@, where it is passed too many. Overridden method $@ is correctly specified. | test.py:37:9:37:21 | Attribute() | here | test.py:8:5:8:26 | Function meth2 | method Base.meth2 |
| test.py:24:5:24:26 | Function Derived.meth1 | Overriding method signature does not match $@, where it is passed too few. Overridden method $@ is correctly specified. | test.py:15:9:15:20 | Attribute() | here | test.py:5:5:5:20 | Function Base.meth1 | method Base.meth1 |
| test.py:24:5:24:26 | Function Derived.meth1 | Overriding method signature does not match $@, where it is passed too few. Overridden method $@ is correctly specified. | test.py:34:9:34:20 | Attribute() | here | test.py:5:5:5:20 | Function Base.meth1 | method Base.meth1 |
| test.py:27:5:27:20 | Function Derived.meth2 | Overriding method signature does not match $@, where it is passed an argument named 'spam'. Overridden method $@ is correctly specified. | test.py:20:9:20:31 | Attribute() | here | test.py:8:5:8:26 | Function Base.meth2 | method Base.meth2 |
| test.py:27:5:27:20 | Function Derived.meth2 | Overriding method signature does not match $@, where it is passed an argument named 'spam'. Overridden method $@ is correctly specified. | test.py:39:9:39:31 | Attribute() | here | test.py:8:5:8:26 | Function Base.meth2 | method Base.meth2 |
| test.py:27:5:27:20 | Function Derived.meth2 | Overriding method signature does not match $@, where it is passed too many. Overridden method $@ is correctly specified. | test.py:18:9:18:21 | Attribute() | here | test.py:8:5:8:26 | Function Base.meth2 | method Base.meth2 |
| test.py:27:5:27:20 | Function Derived.meth2 | Overriding method signature does not match $@, where it is passed too many. Overridden method $@ is correctly specified. | test.py:37:9:37:21 | Attribute() | here | test.py:8:5:8:26 | Function Base.meth2 | method Base.meth2 |

View File

@@ -1,9 +1,9 @@
| test.py:5:5:5:20 | Function meth1 | Overridden method signature does not match $@, where it is passed an argument named 'spam'. Overriding method $@ matches the call. | test.py:19:9:19:31 | Attribute() | call | test.py:24:5:24:26 | Function meth1 | method Derived.meth1 |
| test.py:5:5:5:20 | Function meth1 | Overridden method signature does not match $@, where it is passed an argument named 'spam'. Overriding method $@ matches the call. | test.py:38:9:38:31 | Attribute() | call | test.py:24:5:24:26 | Function meth1 | method Derived.meth1 |
| test.py:5:5:5:20 | Function meth1 | Overridden method signature does not match $@, where it is passed too many arguments. Overriding method $@ matches the call. | test.py:16:9:16:21 | Attribute() | call | test.py:24:5:24:26 | Function meth1 | method Derived.meth1 |
| test.py:5:5:5:20 | Function meth1 | Overridden method signature does not match $@, where it is passed too many arguments. Overriding method $@ matches the call. | test.py:19:9:19:31 | Attribute() | call | test.py:24:5:24:26 | Function meth1 | method Derived.meth1 |
| test.py:5:5:5:20 | Function meth1 | Overridden method signature does not match $@, where it is passed too many arguments. Overriding method $@ matches the call. | test.py:35:9:35:21 | Attribute() | call | test.py:24:5:24:26 | Function meth1 | method Derived.meth1 |
| test.py:5:5:5:20 | Function meth1 | Overridden method signature does not match $@, where it is passed too many arguments. Overriding method $@ matches the call. | test.py:38:9:38:31 | Attribute() | call | test.py:24:5:24:26 | Function meth1 | method Derived.meth1 |
| test.py:8:5:8:26 | Function meth2 | Overridden method signature does not match $@, where it is passed too few arguments. Overriding method $@ matches the call. | test.py:17:9:17:20 | Attribute() | call | test.py:27:5:27:20 | Function meth2 | method Derived.meth2 |
| test.py:8:5:8:26 | Function meth2 | Overridden method signature does not match $@, where it is passed too few arguments. Overriding method $@ matches the call. | test.py:36:9:36:20 | Attribute() | call | test.py:27:5:27:20 | Function meth2 | method Derived.meth2 |
| test.py:64:5:64:19 | Function meth | Overridden method signature does not match $@, where it is passed too many arguments. Overriding method $@ matches the call. | test.py:78:1:78:12 | Attribute() | call | test.py:74:5:74:24 | Function meth | method Correct2.meth |
| test.py:5:5:5:20 | Function Base.meth1 | Overridden method signature does not match $@, where it is passed an argument named 'spam'. Overriding method $@ matches the call. | test.py:19:9:19:31 | Attribute() | call | test.py:24:5:24:26 | Function Derived.meth1 | method Derived.meth1 |
| test.py:5:5:5:20 | Function Base.meth1 | Overridden method signature does not match $@, where it is passed an argument named 'spam'. Overriding method $@ matches the call. | test.py:38:9:38:31 | Attribute() | call | test.py:24:5:24:26 | Function Derived.meth1 | method Derived.meth1 |
| test.py:5:5:5:20 | Function Base.meth1 | Overridden method signature does not match $@, where it is passed too many arguments. Overriding method $@ matches the call. | test.py:16:9:16:21 | Attribute() | call | test.py:24:5:24:26 | Function Derived.meth1 | method Derived.meth1 |
| test.py:5:5:5:20 | Function Base.meth1 | Overridden method signature does not match $@, where it is passed too many arguments. Overriding method $@ matches the call. | test.py:19:9:19:31 | Attribute() | call | test.py:24:5:24:26 | Function Derived.meth1 | method Derived.meth1 |
| test.py:5:5:5:20 | Function Base.meth1 | Overridden method signature does not match $@, where it is passed too many arguments. Overriding method $@ matches the call. | test.py:35:9:35:21 | Attribute() | call | test.py:24:5:24:26 | Function Derived.meth1 | method Derived.meth1 |
| test.py:5:5:5:20 | Function Base.meth1 | Overridden method signature does not match $@, where it is passed too many arguments. Overriding method $@ matches the call. | test.py:38:9:38:31 | Attribute() | call | test.py:24:5:24:26 | Function Derived.meth1 | method Derived.meth1 |
| test.py:8:5:8:26 | Function Base.meth2 | Overridden method signature does not match $@, where it is passed too few arguments. Overriding method $@ matches the call. | test.py:17:9:17:20 | Attribute() | call | test.py:27:5:27:20 | Function Derived.meth2 | method Derived.meth2 |
| test.py:8:5:8:26 | Function Base.meth2 | Overridden method signature does not match $@, where it is passed too few arguments. Overriding method $@ matches the call. | test.py:36:9:36:20 | Attribute() | call | test.py:27:5:27:20 | Function Derived.meth2 | method Derived.meth2 |
| test.py:64:5:64:19 | Function BlameBase.meth | Overridden method signature does not match $@, where it is passed too many arguments. Overriding method $@ matches the call. | test.py:78:1:78:12 | Attribute() | call | test.py:74:5:74:24 | Function Correct2.meth | method Correct2.meth |

View File

@@ -1,2 +1,2 @@
| test.py:16:9:16:21 | Attribute() | Call to $@ with too many arguments; should be no more than 0. | test.py:5:5:5:20 | Function meth1 | method Base.meth1 |
| test.py:17:9:17:20 | Attribute() | Call to $@ with too few arguments; should be no fewer than 1. | test.py:8:5:8:26 | Function meth2 | method Base.meth2 |
| test.py:16:9:16:21 | Attribute() | Call to $@ with too many arguments; should be no more than 0. | test.py:5:5:5:20 | Function Base.meth1 | method Base.meth1 |
| test.py:17:9:17:20 | Attribute() | Call to $@ with too few arguments; should be no fewer than 1. | test.py:8:5:8:26 | Function Base.meth2 | method Base.meth2 |

View File

@@ -1,3 +1,5 @@
| import_structure_1.py:5:1:5:28 | Import | Import of 'bar' is not used. |
| import_structure_2.py:6:1:6:23 | Import | Import of 'bar' is not used. |
| imports_test.py:2:1:2:23 | Import | Import of 'module2' is not used. |
| imports_test.py:6:1:6:12 | Import | Import of 'cycle' is not used. |
| imports_test.py:10:1:10:22 | Import | Import of 'top_level_cycle' is not used. |

View File

@@ -0,0 +1,8 @@
# there should be no difference whether you import 2 things on 1 line, or use 2
# lines
from typing import Optional
from unknown import foo, bar
var: Optional['foo'] = None

View File

@@ -0,0 +1,8 @@
# there should be no difference whether you import 2 things on 1 line, or use 2
# lines
from typing import Optional
from unknown import foo
from unknown import bar
var: Optional['foo'] = None