Merge branch 'master' into python-support-django2

This commit is contained in:
Rasmus Wriedt Larsen
2020-03-11 11:21:47 +01:00
705 changed files with 35598 additions and 19550 deletions

View File

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

View File

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

View File

@@ -19,7 +19,7 @@ predicate doesnt_reraise(ExceptStmt ex) {
}
predicate catches_base_exception(ExceptStmt ex) {
ex.getType().refersTo(theBaseExceptionType())
ex.getType().pointsTo(ClassValue::baseException())
or
not exists(ex.getType())
}

View File

@@ -31,7 +31,7 @@ predicate no_comment(ExceptStmt ex) {
}
predicate non_local_control_flow(ExceptStmt ex) {
ex.getType().refersTo(theStopIterationType())
ex.getType().pointsTo(ClassValue::stopIteration())
}
predicate try_has_normal_exit(Try try) {
@@ -64,32 +64,29 @@ predicate subscript(Stmt s) {
s.(Delete).getATarget() instanceof Subscript
}
predicate encode_decode(Expr ex, ClassObject type) {
predicate encode_decode(Call ex, ClassValue type) {
exists(string name |
ex.(Call).getFunc().(Attribute).getName() = name |
name = "encode" and type = Object::builtin("UnicodeEncodeError")
ex.getFunc().(Attribute).getName() = name |
name = "encode" and type = ClassValue::unicodeEncodeError()
or
name = "decode" and type = Object::builtin("UnicodeDecodeError")
name = "decode" and type = ClassValue::unicodeDecodeError()
)
}
predicate small_handler(ExceptStmt ex, Stmt s, ClassObject type) {
predicate small_handler(ExceptStmt ex, Stmt s, ClassValue type) {
not exists(ex.getTry().getStmt(1)) and
s = ex.getTry().getStmt(0) and
ex.getType().refersTo(type)
ex.getType().pointsTo(type)
}
/** Holds if this exception handler is sufficiently small in scope to not need a comment
* as to what it is doing.
*/
predicate focussed_handler(ExceptStmt ex) {
exists(Stmt s, ClassObject type |
exists(Stmt s, ClassValue type |
small_handler(ex, s, type) |
subscript(s) and type.getAnImproperSuperType() = theLookupErrorType()
subscript(s) and type.getASuperType() = ClassValue::lookupError()
or
attribute_access(s) and type = theAttributeErrorType()
attribute_access(s) and type = ClassValue::attributeError()
or
s.(ExprStmt).getValue() instanceof Name and type = theNameErrorType()
s.(ExprStmt).getValue() instanceof Name and type = ClassValue::nameError()
or
encode_decode(s.(ExprStmt).getValue(), type)
)

View File

@@ -15,7 +15,7 @@ import python
import Raising
import Exceptions.NotImplemented
from Raise r, ClassObject t
where type_or_typeof(r, t, _) and not t.isLegalExceptionType() and not t.failedInference() and not use_of_not_implemented_in_raise(r, _)
from Raise r, ClassValue t
where type_or_typeof(r, t, _) and not t.isLegalExceptionType() and not t.failedInference(_) and not use_of_not_implemented_in_raise(r, _)
select r, "Illegal class '" + t.getName() + "' raised; will result in a TypeError being raised instead."

View File

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

View File

@@ -1,14 +1,16 @@
import python
/** Whether the raise statement 'r' raises 'type' from origin 'orig' */
predicate type_or_typeof(Raise r, ClassObject type, AstNode orig) {
predicate type_or_typeof(Raise r, ClassValue type, AstNode orig) {
exists(Expr exception |
exception = r.getRaised() |
exception.refersTo(type, _, orig)
exception.pointsTo(type, orig)
or
not exists(ClassObject exc_type | exception.refersTo(exc_type)) and
not type = theTypeType() and // First value is an unknown exception type
exception.refersTo(_, type, orig)
not exists(ClassValue exc_type | exception.pointsTo(exc_type)) and
not type = ClassValue::type() and // First value is an unknown exception type
exists(Value val | exception.pointsTo(val, orig) |
val.getClass() = type
)
)
}

View File

@@ -11,8 +11,9 @@
import python
from Raise r, AstNode origin
where r.getException().refersTo(_, theTupleType(), origin) and
from Raise r, Value v, AstNode origin
where r.getException().pointsTo(v, origin) and
v.getClass() = ClassValue::tuple() and
major_version() = 2 /* Raising a tuple is a type error in Python 3, so is handled by the IllegalRaise query. */
select r, "Raising $@ will result in the first element (recursively) being raised and all other elements being discarded.", origin, "a tuple"

View File

@@ -2,7 +2,7 @@ import python
import Testing.Mox
private int varargs_length(Call call) {
private int varargs_length_objectapi(Call call) {
not exists(call.getStarargs()) and result = 0
or
exists(TupleObject t |
@@ -13,67 +13,131 @@ private int varargs_length(Call call) {
result = count(call.getStarargs().(List).getAnElt())
}
private int varargs_length(Call call) {
not exists(call.getStarargs()) and result = 0
or
exists(TupleValue t |
call.getStarargs().pointsTo(t) |
result = t.length()
)
or
result = count(call.getStarargs().(List).getAnElt())
}
/** Gets a keyword argument that is not a keyword-only parameter. */
private Keyword not_keyword_only_arg(Call call, FunctionObject func) {
private Keyword not_keyword_only_arg_objectapi(Call call, FunctionObject func) {
func.getACall().getNode() = call and
result = call.getAKeyword() and
not func.getFunction().getAKeywordOnlyArg().getId() = result.getArg()
}
/** Gets a keyword argument that is not a keyword-only parameter. */
private Keyword not_keyword_only_arg(Call call, FunctionValue func) {
func.getACall().getNode() = call and
result = call.getAKeyword() and
not func.getScope().getAKeywordOnlyArg().getId() = result.getArg()
}
/** Gets the count of arguments that are passed as positional parameters even if they
* are named in the call.
* This is the sum of the number of positional arguments, the number of elements in any explicit tuple passed as *arg
* plus the number of keyword arguments that do not match keyword-only arguments (if the function does not take **kwargs).
*/
private int positional_arg_count_for_call(Call call, Object callable) {
call = get_a_call(callable).getNode() and
private int positional_arg_count_for_call_objectapi(Call call, Object callable) {
call = get_a_call_objectapi(callable).getNode() and
exists(int positional_keywords |
exists(FunctionObject func | func = get_function_or_initializer(callable) |
exists(FunctionObject func | func = get_function_or_initializer_objectapi(callable) |
not func.getFunction().hasKwArg() and
positional_keywords = count(not_keyword_only_arg(call, func))
positional_keywords = count(not_keyword_only_arg_objectapi(call, func))
or
func.getFunction().hasKwArg() and positional_keywords = 0
)
|
result = count(call.getAnArg()) + varargs_length(call) + positional_keywords
result = count(call.getAnArg()) + varargs_length_objectapi(call) + positional_keywords
)
}
/** Gets the count of arguments that are passed as positional parameters even if they
* are named in the call.
* This is the sum of the number of positional arguments, the number of elements in any explicit tuple passed as *arg
* plus the number of keyword arguments that do not match keyword-only arguments (if the function does not take **kwargs).
*/
private int positional_arg_count_for_call(Call call, Value callable) {
call = get_a_call(callable).getNode() and
exists(int positional_keywords |
exists(FunctionValue func | func = get_function_or_initializer(callable) |
not func.getScope().hasKwArg() and
positional_keywords = count(not_keyword_only_arg(call, func))
or
func.getScope().hasKwArg() and positional_keywords = 0
)
|
result = count(call.getAnArg()) + varargs_length_objectapi(call) + positional_keywords
)
}
int arg_count_objectapi(Call call) {
result = count(call.getAnArg()) + varargs_length_objectapi(call) + count(call.getAKeyword())
}
int arg_count(Call call) {
result = count(call.getAnArg()) + varargs_length(call) + count(call.getAKeyword())
}
/* Gets a call corresponding to the given class or function*/
private ControlFlowNode get_a_call(Object callable) {
private ControlFlowNode get_a_call_objectapi(Object callable) {
result = callable.(ClassObject).getACall()
or
result = callable.(FunctionObject).getACall()
}
/* Gets a call corresponding to the given class or function*/
private ControlFlowNode get_a_call(Value callable) {
result = callable.(ClassValue).getACall()
or
result = callable.(FunctionValue).getACall()
}
/* Gets the function object corresponding to the given class or function*/
FunctionObject get_function_or_initializer(Object func_or_cls) {
FunctionObject get_function_or_initializer_objectapi(Object func_or_cls) {
result = func_or_cls.(FunctionObject)
or
result = func_or_cls.(ClassObject).declaredAttribute("__init__")
}
/* Gets the function object corresponding to the given class or function*/
FunctionValue get_function_or_initializer(Value func_or_cls) {
result = func_or_cls.(FunctionValue)
or
result = func_or_cls.(ClassValue).declaredAttribute("__init__")
}
/**Whether there is an illegally named parameter called `name` in the `call` to `func` */
predicate illegally_named_parameter(Call call, Object func, string name) {
predicate illegally_named_parameter_objectapi(Call call, Object func, string name) {
not func.isC() and
name = call.getANamedArgumentName() and
call.getAFlowNode() = get_a_call_objectapi(func) and
not get_function_or_initializer_objectapi(func).isLegalArgumentName(name)
}
/**Whether there is an illegally named parameter called `name` in the `call` to `func` */
predicate illegally_named_parameter(Call call, Value func, string name) {
not func.isBuiltin() and
name = call.getANamedArgumentName() and
call.getAFlowNode() = get_a_call(func) and
not get_function_or_initializer(func).isLegalArgumentName(name)
}
/**Whether there are too few arguments in the `call` to `callable` where `limit` is the lowest number of legal arguments */
predicate too_few_args(Call call, Object callable, int limit) {
predicate too_few_args_objectapi(Call call, Object callable, int limit) {
// Exclude cases where an incorrect name is used as that is covered by 'Wrong name for an argument in a call'
not illegally_named_parameter(call, callable, _) and
not illegally_named_parameter_objectapi(call, callable, _) and
not exists(call.getStarargs()) and not exists(call.getKwargs()) and
arg_count(call) < limit and
exists(FunctionObject func | func = get_function_or_initializer(callable) |
arg_count_objectapi(call) < limit and
exists(FunctionObject func | func = get_function_or_initializer_objectapi(callable) |
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
@@ -84,16 +148,37 @@ predicate too_few_args(Call call, Object callable, int limit) {
call = func.getAMethodCall().getNode() and limit = func.minParameters() - 1
or
callable instanceof ClassObject and
call.getAFlowNode() = get_a_call_objectapi(callable) and limit = func.minParameters() - 1
)
}
/**Whether there are too few arguments in the `call` to `callable` where `limit` is the lowest number of legal arguments */
predicate too_few_args(Call call, Value callable, int limit) {
// Exclude cases where an incorrect name is used as that is covered by 'Wrong name for an argument in a call'
not illegally_named_parameter(call, callable, _) and
not exists(call.getStarargs()) and not exists(call.getKwargs()) and
arg_count(call) < limit and
exists(FunctionValue func | func = get_function_or_initializer(callable) |
call = func.getACall().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.
*/
not (useOfMoxInModule(call.getEnclosingModule()) and func.isNormalMethod())
or
call = func.getACall().getNode() and limit = func.minParameters() - 1
or
callable instanceof ClassValue and
call.getAFlowNode() = get_a_call(callable) and limit = func.minParameters() - 1
)
}
/**Whether there are too many arguments in the `call` to `func` where `limit` is the highest number of legal arguments */
predicate too_many_args(Call call, Object callable, int limit) {
predicate too_many_args_objectapi(Call call, Object callable, int limit) {
// Exclude cases where an incorrect name is used as that is covered by 'Wrong name for an argument in a call'
not illegally_named_parameter(call, callable, _) and
not illegally_named_parameter_objectapi(call, callable, _) and
exists(FunctionObject func |
func = get_function_or_initializer(callable) and
func = get_function_or_initializer_objectapi(callable) and
not func.getFunction().hasVarArg() and limit >= 0
|
call = func.getAFunctionCall().getNode() and limit = func.maxParameters()
@@ -101,13 +186,38 @@ predicate too_many_args(Call call, Object callable, int limit) {
call = func.getAMethodCall().getNode() and limit = func.maxParameters() - 1
or
callable instanceof ClassObject and
call.getAFlowNode() = get_a_call_objectapi(callable) and limit = func.maxParameters() - 1
) and
positional_arg_count_for_call_objectapi(call, callable) > limit
}
/**Whether there are too many arguments in the `call` to `func` where `limit` is the highest number of legal arguments */
predicate too_many_args(Call call, Value callable, int limit) {
// Exclude cases where an incorrect name is used as that is covered by 'Wrong name for an argument in a call'
not illegally_named_parameter(call, callable, _) and
exists(FunctionValue func |
func = get_function_or_initializer(callable) and
not func.getScope().hasVarArg() and limit >= 0
|
call = func.getACall().getNode() and limit = func.maxParameters()
or
call = func.getACall().getNode() and limit = func.maxParameters() - 1
or
callable instanceof ClassValue and
call.getAFlowNode() = get_a_call(callable) and limit = func.maxParameters() - 1
) and
positional_arg_count_for_call(call, callable) > limit
}
/** Holds if `call` has too many or too few arguments for `func` */
predicate wrong_args(Call call, FunctionObject func, int limit, string too) {
predicate wrong_args_objectapi(Call call, FunctionObject func, int limit, string too) {
too_few_args_objectapi(call, func, limit) and too = "too few"
or
too_many_args_objectapi(call, func, limit) and too = "too many"
}
/** Holds if `call` has too many or too few arguments for `func` */
predicate wrong_args(Call call, FunctionValue func, int limit, string too) {
too_few_args(call, func, limit) and too = "too few"
or
too_many_args(call, func, limit) and too = "too many"
@@ -117,14 +227,31 @@ predicate wrong_args(Call call, FunctionObject func, int limit, string too) {
* Implies nothing about whether `call` could call `func`.
*/
bindingset[call, func]
predicate correct_args_if_called_as_method(Call call, FunctionObject func) {
predicate correct_args_if_called_as_method_objectapi(Call call, FunctionObject func) {
arg_count_objectapi(call)+1 >= func.minParameters()
and
arg_count_objectapi(call) < func.maxParameters()
}
/** Holds if `call` has correct number of arguments for `func`.
* Implies nothing about whether `call` could call `func`.
*/
bindingset[call, func]
predicate correct_args_if_called_as_method(Call call, FunctionValue func) {
arg_count(call)+1 >= func.minParameters()
and
arg_count(call) < func.maxParameters()
}
/** Holds if `call` is a call to `overriding`, which overrides `func`. */
predicate overridden_call(FunctionObject func, FunctionObject overriding, Call call) {
predicate overridden_call_objectapi(FunctionObject func, FunctionObject overriding, Call call) {
overriding.overrides(func) and
overriding.getACall().getNode() = call
}
/** Holds if `call` is a call to `overriding`, which overrides `func`. */
predicate overridden_call(FunctionValue func, FunctionValue overriding, Call call) {
overriding.overrides(func) and
overriding.getACall().getNode() = call
}

View File

@@ -17,13 +17,13 @@ import python
from CallNode call_to_super, string name
where
exists(GlobalVariable gv, ControlFlowNode cn |
call_to_super = theSuperType().getACall() and
call_to_super = ClassValue::super_().getACall() and
gv.getId() = "super" and
cn = call_to_super.getArg(0) and
name = call_to_super.getScope().getScope().(Class).getName() and
exists(ClassObject other |
cn.refersTo(other) and
not other.getPyClass().getName() = name
exists(ClassValue other |
cn.pointsTo(other) and
not other.getScope().getName() = name
)
)
select call_to_super.getNode(), "First argument to super() should be " + name + "."

View File

@@ -107,10 +107,10 @@ private predicate brace_pair(PossibleAdvancedFormatString fmt, int start, int en
private predicate advanced_format_call(Call format_expr, PossibleAdvancedFormatString fmt, int args) {
exists(CallNode call |
call = format_expr.getAFlowNode() |
call.getFunction().refersTo(Object::builtin("format")) and call.getArg(0).refersTo(_, fmt.getAFlowNode()) and
call.getFunction().pointsTo(Value::named("format")) and call.getArg(0).pointsTo(_, fmt.getAFlowNode()) and
args = count(format_expr.getAnArg()) - 1
or
call.getFunction().(AttrNode).getObject("format").refersTo(_, fmt.getAFlowNode()) and
call.getFunction().(AttrNode).getObject("format").pointsTo(_, fmt.getAFlowNode()) and
args = count(format_expr.getAnArg())
)
}
@@ -139,4 +139,3 @@ class AdvancedFormattingCall extends Call {
}
}

View File

@@ -69,7 +69,7 @@ predicate is_unhashable(ControlFlowNode f, ClassValue cls, ControlFlowNode origi
predicate typeerror_is_caught(ControlFlowNode f) {
exists (Try try |
try.getBody().contains(f.getNode()) and
try.getAHandler().getType().refersTo(theTypeErrorType()))
try.getAHandler().getType().pointsTo(ClassValue::typeError()))
}
from ControlFlowNode f, ClassValue c, ControlFlowNode origin

View File

@@ -28,14 +28,14 @@ predicate probablySingleton(ClassValue cls) {
predicate invalid_to_use_is_portably(ClassValue c) {
overrides_eq_or_cmp(c) and
/* Exclude type/builtin-function/bool as it is legitimate to compare them using 'is' but they implement __eq__ */
// Exclude type/builtin-function/bool as it is legitimate to compare them using 'is' but they implement __eq__
not c = Value::named("type") and not c = ClassValue::builtinFunction() and not c = Value::named("bool") and
/* OK to compare with 'is' if a singleton */
// OK to compare with 'is' if a singleton
not probablySingleton(c)
}
predicate simple_constant(ControlFlowNode f) {
exists(Object obj | f.refersTo(obj) | obj = theTrueObject() or obj = theFalseObject() or obj = theNoneObject())
exists(Value val | f.pointsTo(val) | val = Value::named("True") or val = Value::named("False") or val = Value::named("None"))
}
private predicate cpython_interned_value(Expr e) {
@@ -66,14 +66,14 @@ private predicate universally_interned_value(Expr e) {
predicate cpython_interned_constant(Expr e) {
exists(Expr const |
e.refersTo(_, const) |
e.pointsTo(_, const) |
cpython_interned_value(const)
)
}
predicate universally_interned_constant(Expr e) {
exists(Expr const |
e.refersTo(_, const) |
e.pointsTo(_, const) |
universally_interned_value(const)
)
}
@@ -95,7 +95,7 @@ private predicate comparison_one_type(Compare comp, Cmpop op, ClassValue cls) {
}
predicate invalid_portable_is_comparison(Compare comp, Cmpop op, ClassValue cls) {
/* OK to use 'is' when defining '__eq__' */
// OK to use 'is' when defining '__eq__'
not exists(Function eq | eq.getName() = "__eq__" or eq.getName() = "__ne__" | eq = comp.getScope().getScope*())
and
(
@@ -107,24 +107,24 @@ predicate invalid_portable_is_comparison(Compare comp, Cmpop op, ClassValue cls)
)
)
and
/* OK to use 'is' when comparing items from a known set of objects */
not exists(Expr left, Expr right, Object obj |
// OK to use 'is' when comparing items from a known set of objects
not exists(Expr left, Expr right, Value val |
comp.compares(left, op, right) and
exists(ImmutableLiteral il | il.getLiteralObject() = obj) |
left.refersTo(obj) and right.refersTo(obj)
exists(ImmutableLiteral il | il.getLiteralValue() = val) |
left.pointsTo(val) and right.pointsTo(val)
or
/* Simple constant in module, probably some sort of sentinel */
// Simple constant in module, probably some sort of sentinel
exists(AstNode origin |
not left.refersTo(_) and right.refersTo(obj, origin) and
not left.pointsTo(_) and right.pointsTo(val, origin) and
origin.getScope().getEnclosingModule() = comp.getScope().getEnclosingModule()
)
)
and
/* OK to use 'is' when comparing with a member of an enum */
// OK to use 'is' when comparing with a member of an enum
not exists(Expr left, Expr right, AstNode origin |
comp.compares(left, op, right) and
enum_member(origin) |
left.refersTo(_, origin) or right.refersTo(_, origin)
left.pointsTo(_, origin) or right.pointsTo(_, origin)
)
}
@@ -135,4 +135,3 @@ private predicate enum_member(AstNode obj) {
asgn.getValue() = obj
)
}

View File

@@ -18,19 +18,20 @@ where
// Only relevant for Python 2, as all later versions implement true division
major_version() = 2
and
exists(BinaryExprNode bin, Object lobj, Object robj |
exists(BinaryExprNode bin, Value lval, Value rval |
bin = div.getAFlowNode()
and bin.getNode().getOp() instanceof Div
and bin.getLeft().refersTo(lobj, theIntType(), left)
and bin.getRight().refersTo(robj, theIntType(), right)
and bin.getLeft().pointsTo(lval, left)
and lval.getClass() = ClassValue::int_()
and bin.getRight().pointsTo(rval, right)
and rval.getClass() = ClassValue::int_()
// Ignore instances where integer division leaves no remainder
and not lobj.(NumericObject).intValue() % robj.(NumericObject).intValue() = 0
and not lval.(NumericValue).getIntValue() % rval.(NumericValue).getIntValue() = 0
and not bin.getNode().getEnclosingModule().hasFromFuture("division")
// Filter out results wrapped in `int(...)`
and not exists(CallNode c, ClassObject cls |
c.getAnArg() = bin
and c.getFunction().refersTo(cls)
and cls.getName() = "int"
and not exists(CallNode c |
c = ClassValue::int_().getACall()
and c.getAnArg() = bin
)
)
select div, "Result of division may be truncated as its $@ and $@ arguments may both be integers.",

View File

@@ -38,14 +38,14 @@ predicate unnecessary_lambda(Lambda l, Expr e) {
simple_wrapper(l, e) and
(
/* plain class */
exists(ClassObject c | e.refersTo(c))
exists(ClassValue c | e.pointsTo(c))
or
/* plain function */
exists(FunctionObject f | e.refersTo(f))
exists(FunctionValue f | e.pointsTo(f))
or
/* bound-method of enclosing instance */
exists(ClassObject cls, Attribute a |
cls.getPyClass() = l.getScope().getScope() and a = e |
exists(ClassValue cls, Attribute a |
cls.getScope() = l.getScope().getScope() and a = e |
((Name)a.getObject()).getId() = "self" and
cls.hasAttribute(a.getName())
)

View File

@@ -10,8 +10,8 @@
*/
import python
private import semmle.python.types.Builtins
from CallNode call, ControlFlowNode func
where
major_version() = 2 and call.getFunction() = func and func.refersTo(Object::builtin("apply"))
where major_version() = 2 and call.getFunction() = func and func.pointsTo(Value::named("apply"))
select call, "Call to the obsolete builtin function 'apply'."

View File

@@ -3,20 +3,20 @@
"qhelp.dtd">
<qhelp>
<overview>
<p>A call to the input() function, <code>input(prompt)</code> is equivalent to <code>eval(raw_input(prompt))</code>. Evaluating user input without any checking can be a serious security flaw.</p>
<p>In Python 2, a call to the <code>input()</code> function, <code>input(prompt)</code> is equivalent to <code>eval(raw_input(prompt))</code>. Evaluating user input without any checking can be a serious security flaw.</p>
</overview>
<recommendation>
<p> Get user input with <code>raw_input(prompt)</code> and then validate that input before evaluating. If the expected input is a number or
<p>Get user input with <code>raw_input(prompt)</code> and then validate that input before evaluating. If the expected input is a number or
string, then <code>ast.literal_eval()</code> can always be used safely.</p>
</recommendation>
<references>
<li>Python Standard Library: <a href="http://docs.python.org/library/functions.html#input">input</a>,
<a href="http://docs.python.org/library/ast.html#ast.literal_eval">ast.literal_eval</a>.</li>
<li>Python Standard Library: <a href="http://docs.python.org/2/library/functions.html#input">input</a>,
<a href="http://docs.python.org/2/library/ast.html#ast.literal_eval">ast.literal_eval</a>.</li>
<li>Wikipedia: <a href="http://en.wikipedia.org/wiki/Data_validation">Data validation</a>.</li>
</references>

View File

@@ -1,6 +1,6 @@
/**
* @name 'input' function used
* @description The built-in function 'input' is used which can allow arbitrary code to be run.
* @name 'input' function used in Python 2
* @description The built-in function 'input' is used which, in Python 2, can allow arbitrary code to be run.
* @kind problem
* @tags security
* correctness
@@ -18,4 +18,4 @@ where
call.getFunction() = func and
func.pointsTo(context, Value::named("input"), _) and
not func.pointsTo(context, Value::named("raw_input"), _)
select call, "The unsafe built-in function 'input' is used."
select call, "The unsafe built-in function 'input' is used in Python 2."

View File

@@ -19,7 +19,7 @@ import Expressions.CallArgs
from Call call, FunctionObject func, string name
where
illegally_named_parameter(call, func, name) and
illegally_named_parameter_objectapi(call, func, name) and
not func.isAbstract() and
not exists(FunctionObject overridden | func.overrides(overridden) and overridden.getFunction().getAnArg().(Name).getId() = name)
select

View File

@@ -17,12 +17,12 @@ import CallArgs
from Call call, FunctionObject func, string too, string should, int limit
where
(
too_many_args(call, func, limit) and too = "too many arguments" and should = "no more than "
too_many_args_objectapi(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 "
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(call, overridden))
not exists(FunctionObject overridden | func.overrides(overridden) and correct_args_if_called_as_method_objectapi(call, overridden))
/* The semantics of `__new__` can be a bit subtle, so we simply exclude `__new__` methods */
and not func.getName() = "__new__"

View File

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

View File

@@ -18,11 +18,11 @@ where
not func.getName() = "__init__" and
overriding.overrides(func) and
call = overriding.getAMethodCall().getNode() and
correct_args_if_called_as_method(call, overriding) and
correct_args_if_called_as_method_objectapi(call, overriding) and
(
arg_count(call)+1 < func.minParameters() and problem = "too few arguments"
arg_count_objectapi(call)+1 < func.minParameters() and problem = "too few arguments"
or
arg_count(call) >= func.maxParameters() and problem = "too many arguments"
arg_count_objectapi(call) >= func.maxParameters() and problem = "too many arguments"
or
exists(string name | call.getAKeyword().getArg() = name and
overriding.getFunction().getAnArg().(Name).getId() = name and

View File

@@ -23,12 +23,12 @@ predicate is_used(Call c) {
)
}
from Call c, FunctionObject func
from Call c, FunctionValue func
where
/* Call result is used, but callee is a procedure */
is_used(c) and c.getFunc().refersTo(func) and func.getFunction().isProcedure() and
is_used(c) and c.getFunc().pointsTo(func) and func.getScope().isProcedure() and
/* All callees are procedures */
forall(FunctionObject callee | c.getFunc().refersTo(callee) | callee.getFunction().isProcedure()) and
forall(FunctionValue callee | c.getFunc().pointsTo(callee) | callee.getScope().isProcedure()) and
/* Mox return objects have an `AndReturn` method */
not useOfMoxInModule(c.getEnclosingModule())
select c, "The result of '$@' is used even though it is always None.", func, func.getQualifiedName()

View File

@@ -12,9 +12,12 @@
import python
/**
* The module `name` was deprecated in Python version `major`.`minor`,
* and module `instead` should be used instead (or `instead = "no replacement"`)
*/
predicate deprecated_module(string name, string instead, int major, int minor) {
name = "posixfile" and instead = "email" and major = 1 and minor = 5
name = "posixfile" and instead = "fcntl" and major = 1 and minor = 5
or
name = "gopherlib" and instead = "no replacement" and major = 2 and minor = 5
or
@@ -34,40 +37,49 @@ predicate deprecated_module(string name, string instead, int major, int minor) {
or
name = "rotor" and instead = "no replacement" and major = 2 and minor = 4
or
name = "statcache" and instead = "no replacement" and major = 2 and minor = 2
name = "statcache" and instead = "no replacement" and major = 2 and minor = 2
or
name = "mpz" and instead = "a third party" and major = 2 and minor = 2
name = "mpz" and instead = "a third party" and major = 2 and minor = 2
or
name = "xreadlines" and instead = "no replacement" and major = 2 and minor = 3
or
name = "multifile" and instead = "email" and major = 2 and minor = 5
or
name = "sets" and instead = "builtins" and major = 2 and minor = 6
name = "sets" and instead = "builtins" and major = 2 and minor = 6
or
name = "buildtools" and instead = "no replacement" and major = 2 and minor = 3
or
name = "cfmfile" and instead = "no replacement" and major = 2 and minor = 4
name = "cfmfile" and instead = "no replacement" and major = 2 and minor = 4
or
name = "macfs" and instead = "no replacement" and major = 2 and minor = 3
or
name = "md5" and instead = "hashlib" and major = 2 and minor = 5
name = "md5" and instead = "hashlib" and major = 2 and minor = 5
or
name = "sha" and instead = "hashlib" and major = 2 and minor = 5
name = "sha" and instead = "hashlib" and major = 2 and minor = 5
}
string deprecation_message(string mod) {
exists(int major, int minor | deprecated_module(mod, _, major, minor) |
result = "The " + mod + " module was deprecated in version " + major.toString() + "." + minor.toString() + ".")
exists(int major, int minor | deprecated_module(mod, _, major, minor) |
result = "The " + mod + " module was deprecated in version " + major.toString() + "." +
minor.toString() + "."
)
}
string replacement_message(string mod) {
exists(string instead | deprecated_module(mod, instead, _, _) |
result = " Use " + instead + " module instead." and not instead = "no replacement"
or
result = "" and instead = "no replacement"
)
exists(string instead | deprecated_module(mod, instead, _, _) |
result = " Use " + instead + " module instead." and not instead = "no replacement"
or
result = "" and instead = "no replacement"
)
}
from ImportExpr imp, Stmt s, Expr e
where s.getASubExpression() = e and (e = imp or e.contains(imp))
select s, deprecation_message(imp.getName()) + replacement_message(imp.getName())
from ImportExpr imp, string name, string instead
where
name = imp.getName() and
deprecated_module(name, instead, _, _) and
not exists(Try try, ExceptStmt except | except = try.getAHandler()
|
except.getType().pointsTo(ClassValue::importError()) and
except.containsInScope(imp)
)
select imp, deprecation_message(name) + replacement_message(name)

View File

@@ -15,24 +15,24 @@ import python
Value aSocket() { result.getClass() = Value::named("socket.socket") }
CallNode socketBindCall() {
result = aSocket().attr("bind").(CallableValue).getACall() and major_version() = 3
or
result.getFunction().(AttrNode).getObject("bind").pointsTo(aSocket()) and
major_version() = 2
result = aSocket().attr("bind").(CallableValue).getACall() and major_version() = 3
or
result.getFunction().(AttrNode).getObject("bind").pointsTo(aSocket()) and
major_version() = 2
}
string allInterfaces() { result = "0.0.0.0" or result = "" }
Value getTextValue(string address) {
result = Value::forUnicode(address) and major_version() = 3
or
result = Value::forString(address) and major_version() = 2
result = Value::forUnicode(address) and major_version() = 3
or
result = Value::forString(address) and major_version() = 2
}
from CallNode call, TupleValue args, string address
where
call = socketBindCall() and
call.getArg(0).pointsTo(args) and
args.getItem(0) = getTextValue(address) and
address = allInterfaces()
call = socketBindCall() and
call.getArg(0).pointsTo(args) and
args.getItem(0) = getTextValue(address) and
address = allInterfaces()
select call.getNode(), "'" + address + "' binds a socket to all interfaces."

View File

@@ -13,9 +13,7 @@
import python
import semmle.python.regex
private string commonTopLevelDomainRegex() {
result = "com|org|edu|gov|uk|net|io"
}
private string commonTopLevelDomainRegex() { result = "com|org|edu|gov|uk|net|io" }
/**
* Holds if `pattern` is a regular expression pattern for URLs with a host matched by `hostPart`,
@@ -23,22 +21,20 @@ private string commonTopLevelDomainRegex() {
*/
bindingset[pattern]
predicate isIncompleteHostNameRegExpPattern(string pattern, string hostPart) {
hostPart = pattern
.regexpCapture("(?i).*" +
// an unescaped single `.`
"(?<!\\\\)[.]" +
// immediately followed by a sequence of subdomains, perhaps with some regex characters mixed in, followed by a known TLD
"([():|?a-z0-9-]+(\\\\)?[.](" + commonTopLevelDomainRegex() + "))" + ".*", 1)
hostPart = pattern
.regexpCapture("(?i).*" +
// an unescaped single `.`
"(?<!\\\\)[.]" +
// immediately followed by a sequence of subdomains, perhaps with some regex characters mixed in, followed by a known TLD
"([():|?a-z0-9-]+(\\\\)?[.](" + commonTopLevelDomainRegex() + "))" + ".*", 1)
}
from Regex r, string pattern, string hostPart
where
(
r.getText() = pattern
) and
isIncompleteHostNameRegExpPattern(pattern, hostPart) and
// ignore patterns with capture groups after the TLD
not pattern.regexpMatch("(?i).*[.](" + commonTopLevelDomainRegex() + ").*[(][?]:.*[)].*")
r.getText() = pattern and
isIncompleteHostNameRegExpPattern(pattern, hostPart) and
// ignore patterns with capture groups after the TLD
not pattern.regexpMatch("(?i).*[.](" + commonTopLevelDomainRegex() + ").*[(][?]:.*[)].*")
select r,
"This regular expression has an unescaped '.' before '" + hostPart +
"', so it might match more hosts than expected."
"This regular expression has an unescaped '.' before '" + hostPart +
"', so it might match more hosts than expected."

View File

@@ -10,20 +10,16 @@
* external/cwe/cwe-20
*/
import python
import semmle.python.regex
private string commonTopLevelDomainRegex() {
result = "com|org|edu|gov|uk|net|io"
}
private string commonTopLevelDomainRegex() { result = "com|org|edu|gov|uk|net|io" }
predicate looksLikeUrl(StrConst s) {
exists(string text |
text = s.getText()
|
text.regexpMatch("(?i)([a-z]*:?//)?\\.?([a-z0-9-]+\\.)+(" +
commonTopLevelDomainRegex() +")(:[0-9]+)?/?")
exists(string text | text = s.getText() |
text
.regexpMatch("(?i)([a-z]*:?//)?\\.?([a-z0-9-]+\\.)+(" + commonTopLevelDomainRegex() +
")(:[0-9]+)?/?")
or
// target is a HTTP URL to a domain on any TLD
text.regexpMatch("(?i)https?://([a-z0-9-]+\\.)+([a-z]+)(:[0-9]+)?/?")
@@ -42,10 +38,8 @@ predicate incomplete_sanitization(Expr sanitizer, StrConst url) {
}
predicate unsafe_call_to_startswith(Call sanitizer, StrConst url) {
sanitizer.getFunc().(Attribute).getName() = "startswith"
and
sanitizer.getArg(0) = url
and
sanitizer.getFunc().(Attribute).getName() = "startswith" and
sanitizer.getArg(0) = url and
not url.getText().regexpMatch("(?i)https?://[\\.a-z0-9-]+/.*")
}

View File

@@ -18,18 +18,17 @@
import python
import semmle.python.security.Paths
/* Sources */
import semmle.python.web.HttpRequest
/* Sinks */
import semmle.python.security.injection.Path
class PathInjectionConfiguration extends TaintTracking::Configuration {
PathInjectionConfiguration() { this = "Path injection configuration" }
override predicate isSource(TaintTracking::Source source) { source instanceof HttpRequestTaintSource }
override predicate isSource(TaintTracking::Source source) {
source instanceof HttpRequestTaintSource
}
override predicate isSink(TaintTracking::Sink sink) { sink instanceof OpenNode }
@@ -41,10 +40,9 @@ class PathInjectionConfiguration extends TaintTracking::Configuration {
override predicate isExtension(TaintTracking::Extension extension) {
extension instanceof AbsPath
}
}
from PathInjectionConfiguration config, TaintedPathSource src, TaintedPathSink sink
where config.hasFlowPath(src, sink)
select sink.getSink(), src, sink, "This path depends on $@.", src.getSource(), "a user-provided value"
select sink.getSink(), src, sink, "This path depends on $@.", src.getSource(),
"a user-provided value"

View File

@@ -3,7 +3,7 @@
* @description Extracting files from a malicious tar archive without validating that the
* destination file path is within the destination directory can cause files outside
* the destination directory to be overwritten.
* @kind path-problem
* @kind path-problem
* @id py/tarslip
* @problem.severity error
* @precision medium
@@ -13,15 +13,12 @@
import python
import semmle.python.security.Paths
import semmle.python.security.TaintTracking
import semmle.python.security.strings.Basic
/** A TaintKind to represent open tarfile objects. That is, the result of calling `tarfile.open(...)` */
class OpenTarFile extends TaintKind {
OpenTarFile() {
this = "tarfile.open"
}
OpenTarFile() { this = "tarfile.open" }
override TaintKind getTaintOfMethodResult(string name) {
name = "getmember" and result instanceof TarFileInfo
@@ -29,60 +26,45 @@ class OpenTarFile extends TaintKind {
name = "getmembers" and result.(SequenceKind).getItem() instanceof TarFileInfo
}
override ClassValue getType() {
result = Module::named("tarfile").attr("TarFile")
}
override TaintKind getTaintForIteration() {
result instanceof TarFileInfo
}
override ClassValue getType() { result = Value::named("tarfile.TarFile") }
override TaintKind getTaintForIteration() { result instanceof TarFileInfo }
}
/** The source of open tarfile objects. That is, any call to `tarfile.open(...)` */
class TarfileOpen extends TaintSource {
TarfileOpen() {
Module::named("tarfile").attr("open").getACall() = this
and
/* If argument refers to a string object, then it's a hardcoded path and
Value::named("tarfile.open").getACall() = this and
/*
* If argument refers to a string object, then it's a hardcoded path and
* this tarfile is safe.
*/
not this.(CallNode).getAnArg().refersTo(any(StringObject str))
and
not this.(CallNode).getAnArg().pointsTo(any(StringValue str)) and
/* Ignore opens within the tarfile module itself */
not this.(ControlFlowNode).getLocation().getFile().getBaseName() = "tarfile.py"
}
override predicate isSourceOf(TaintKind kind) {
kind instanceof OpenTarFile
}
override predicate isSourceOf(TaintKind kind) { kind instanceof OpenTarFile }
}
class TarFileInfo extends TaintKind {
TarFileInfo() { this = "tarfile.entry" }
TarFileInfo() {
this = "tarfile.entry"
}
override TaintKind getTaintOfMethodResult(string name) {
name = "next" and result = this
}
override TaintKind getTaintOfMethodResult(string name) { name = "next" and result = this }
override TaintKind getTaintOfAttribute(string name) {
name = "name" and result instanceof TarFileInfo
}
}
/*
* For efficiency we don't want to track the flow of taint
* around the tarfile module.
*/
/* For efficiency we don't want to track the flow of taint
* around the tarfile module. */
class ExcludeTarFilePy extends Sanitizer {
ExcludeTarFilePy() {
this = "Tar sanitizer"
}
ExcludeTarFilePy() { this = "Tar sanitizer" }
override predicate sanitizingNode(TaintKind taint, ControlFlowNode node) {
node.getLocation().getFile().getBaseName() = "tarfile.py" and
@@ -94,12 +76,10 @@ class ExcludeTarFilePy extends Sanitizer {
taint.(SequenceKind).getItem() instanceof TarFileInfo
)
}
}
/* Any call to an extractall method */
class ExtractAllSink extends TaintSink {
CallNode call;
ExtractAllSink() {
@@ -107,15 +87,11 @@ class ExtractAllSink extends TaintSink {
count(call.getAnArg()) = 0
}
override predicate sinks(TaintKind kind) {
kind instanceof OpenTarFile
}
override predicate sinks(TaintKind kind) { kind instanceof OpenTarFile }
}
/* Argument to extract method */
class ExtractSink extends TaintSink {
CallNode call;
ExtractSink() {
@@ -123,16 +99,11 @@ class ExtractSink extends TaintSink {
this = call.getArg(0)
}
override predicate sinks(TaintKind kind) {
kind instanceof TarFileInfo
}
override predicate sinks(TaintKind kind) { kind instanceof TarFileInfo }
}
/* Members argument to extract method */
class ExtractMembersSink extends TaintSink {
CallNode call;
ExtractMembersSink() {
@@ -145,21 +116,15 @@ class ExtractMembersSink extends TaintSink {
or
kind instanceof OpenTarFile
}
}
class TarFileInfoSanitizer extends Sanitizer {
TarFileInfoSanitizer() {
this = "TarInfo sanitizer"
}
TarFileInfoSanitizer() { this = "TarInfo sanitizer" }
override predicate sanitizingEdge(TaintKind taint, PyEdgeRefinement test) {
path_sanitizing_test(test.getTest()) and
taint instanceof TarFileInfo
}
}
private predicate path_sanitizing_test(ControlFlowNode test) {
@@ -170,7 +135,6 @@ private predicate path_sanitizing_test(ControlFlowNode test) {
}
class TarSlipConfiguration extends TaintTracking::Configuration {
TarSlipConfiguration() { this = "TarSlip configuration" }
override predicate isSource(TaintTracking::Source source) { source instanceof TarfileOpen }
@@ -193,7 +157,7 @@ class TarSlipConfiguration extends TaintTracking::Configuration {
node.asVariable().getDefinition() = def
or
node.asCfgNode() = def.getDefiningNode()
|
|
def.getScope() = Value::named("tarfile.open").(CallableValue).getScope()
or
def.isSelf() and def.getScope().getEnclosingModule().getName() = "tarfile"
@@ -201,8 +165,7 @@ class TarSlipConfiguration extends TaintTracking::Configuration {
}
}
from TarSlipConfiguration config, TaintedPathSource src, TaintedPathSink sink
where config.hasFlowPath(src, sink)
select sink.getSink(), src, sink, "Extraction of tarfile from $@", src.getSource(), "a potentially untrusted source"
select sink.getSink(), src, sink, "Extraction of tarfile from $@", src.getSource(),
"a potentially untrusted source"

View File

@@ -11,7 +11,6 @@ urlpatterns = [
def user_picture1(request):
"""A view that is vulnerable to malicious file access."""
base_path = '/server/static/images'
filename = request.GET.get('p')
# BAD: This could read any file on the file system
data = open(filename, 'rb').read()

View File

@@ -16,18 +16,17 @@
import python
import semmle.python.security.Paths
/* Sources */
import semmle.python.web.HttpRequest
/* Sinks */
import semmle.python.security.injection.Command
class CommandInjectionConfiguration extends TaintTracking::Configuration {
CommandInjectionConfiguration() { this = "Command injection configuration" }
override predicate isSource(TaintTracking::Source source) { source instanceof HttpRequestTaintSource }
override predicate isSource(TaintTracking::Source source) {
source instanceof HttpRequestTaintSource
}
override predicate isSink(TaintTracking::Sink sink) {
sink instanceof OsCommandFirstArgument or
@@ -37,9 +36,9 @@ class CommandInjectionConfiguration extends TaintTracking::Configuration {
override predicate isExtension(TaintTracking::Extension extension) {
extension instanceof FirstElementFlow
}
}
from CommandInjectionConfiguration config, TaintedPathSource src, TaintedPathSink sink
where config.hasFlowPath(src, sink)
select sink.getSink(), src, sink, "This command depends on $@.", src.getSource(), "a user-provided value"
select sink.getSink(), src, sink, "This command depends on $@.", src.getSource(),
"a user-provided value"

View File

@@ -20,5 +20,4 @@ def command_execution_safe(request):
if request.method == 'POST':
action = request.POST.get('action', '')
#GOOD -- Use a whitelist
subprocess.call(["application", COMMAND[action]])
subprocess.call(["application", COMMANDS[action]])

View File

@@ -12,37 +12,39 @@
import python
ClassObject jinja2EnvironmentOrTemplate() {
exists(ModuleObject jinja2, string name |
jinja2.getName() = "jinja2" and
jinja2.attr(name) = result |
name = "Environment" or
name = "Template"
)
/* Jinja 2 Docs:
* https://jinja.palletsprojects.com/en/2.11.x/api/#jinja2.Environment
* https://jinja.palletsprojects.com/en/2.11.x/api/#jinja2.Template
*
* Although the docs doesn't say very clearly, autoescape is a valid argument when constructing
* a Template manually
*
* unsafe_tmpl = Template('Hello {{ name }}!')
* safe1_tmpl = Template('Hello {{ name }}!', autoescape=True)
*/
ClassValue jinja2EnvironmentOrTemplate() {
result = Value::named("jinja2.Environment")
or
result = Value::named("jinja2.Template")
}
ControlFlowNode getAutoEscapeParameter(CallNode call) {
exists(Object callable |
call.getFunction().refersTo(callable) |
callable = jinja2EnvironmentOrTemplate() and
result = call.getArgByName("autoescape")
)
result = call.getArgByName("autoescape")
}
from CallNode call
where
not exists(call.getNode().getStarargs()) and
not exists(call.getNode().getKwargs()) and
(
not exists(getAutoEscapeParameter(call)) and
exists(Object env |
call.getFunction().refersTo(env) and
env = jinja2EnvironmentOrTemplate()
call.getFunction().pointsTo(jinja2EnvironmentOrTemplate()) and
not exists(call.getNode().getStarargs()) and
not exists(call.getNode().getKwargs()) and
(
not exists(getAutoEscapeParameter(call))
or
exists(Value isFalse |
getAutoEscapeParameter(call).pointsTo(isFalse) and
isFalse.getDefiniteBooleanValue() = false
)
)
or
exists(Object isFalse |
getAutoEscapeParameter(call).refersTo(isFalse) and isFalse.booleanValue() = false
)
)
select call, "Using jinja2 templates with autoescape=False can potentially allow XSS attacks."

View File

@@ -14,28 +14,24 @@
import python
import semmle.python.security.Paths
/* Sources */
import semmle.python.web.HttpRequest
/* Sinks */
import semmle.python.web.HttpResponse
/* Flow */
import semmle.python.security.strings.Untrusted
class ReflectedXssConfiguration extends TaintTracking::Configuration {
ReflectedXssConfiguration() { this = "Reflected XSS configuration" }
override predicate isSource(TaintTracking::Source source) { source instanceof HttpRequestTaintSource }
override predicate isSource(TaintTracking::Source source) {
source instanceof HttpRequestTaintSource
}
override predicate isSink(TaintTracking::Sink sink) { sink instanceof HttpResponseTaintSink }
}
from ReflectedXssConfiguration config, TaintedPathSource src, TaintedPathSink sink
where config.hasFlowPath(src, sink)
select sink.getSink(), src, sink, "Cross-site scripting vulnerability due to $@.", src.getSource(), "user-provided value"
select sink.getSink(), src, sink, "Cross-site scripting vulnerability due to $@.", src.getSource(),
"a user-provided value"

View File

@@ -13,37 +13,37 @@
import python
import semmle.python.security.Paths
/* Sources */
import semmle.python.web.HttpRequest
/* Sinks */
import semmle.python.security.injection.Sql
import semmle.python.web.django.Db
import semmle.python.web.django.Model
class SQLInjectionConfiguration extends TaintTracking::Configuration {
SQLInjectionConfiguration() { this = "SQL injection configuration" }
override predicate isSource(TaintTracking::Source source) { source instanceof HttpRequestTaintSource }
override predicate isSource(TaintTracking::Source source) {
source instanceof HttpRequestTaintSource
}
override predicate isSink(TaintTracking::Sink sink) { sink instanceof SqlInjectionSink }
}
/* Additional configuration to support tracking of DB objects. Connections, cursors, etc. */
/* Additional configuration to support tracking of DB objects. Connections, cursors, etc.
* Without this configuration (or the LegacyConfiguration), the pattern of
* `any(MyTaintKind k).taints(control_flow_node)` used in DbConnectionExecuteArgument would not work.
*/
class DbConfiguration extends TaintTracking::Configuration {
DbConfiguration() { this = "DB configuration" }
override predicate isSource(TaintTracking::Source source) {
source instanceof DjangoModelObjects or
source instanceof DbConnectionSource
}
}
from SQLInjectionConfiguration config, TaintedPathSource src, TaintedPathSink sink
where config.hasFlowPath(src, sink)
select sink.getSink(), src, sink, "This SQL query depends on $@.", src.getSource(), "a user-provided value"
select sink.getSink(), src, sink, "This SQL query depends on $@.", src.getSource(),
"a user-provided value"

View File

@@ -16,24 +16,22 @@
import python
import semmle.python.security.Paths
/* Sources */
import semmle.python.web.HttpRequest
/* Sinks */
import semmle.python.security.injection.Exec
class CodeInjectionConfiguration extends TaintTracking::Configuration {
CodeInjectionConfiguration() { this = "Code injection configuration" }
override predicate isSource(TaintTracking::Source source) { source instanceof HttpRequestTaintSource }
override predicate isSource(TaintTracking::Source source) {
source instanceof HttpRequestTaintSource
}
override predicate isSink(TaintTracking::Sink sink) { sink instanceof StringEvaluationNode }
}
from CodeInjectionConfiguration config, TaintedPathSource src, TaintedPathSink sink
where config.hasFlowPath(src, sink)
select sink.getSink(), src, sink, "$@ flows to here and is interpreted as code.", src.getSource(), "User-provided value"
select sink.getSink(), src, sink, "$@ flows to here and is interpreted as code.", src.getSource(),
"A user-provided value"

View File

@@ -2,7 +2,7 @@
* @name Information exposure through an exception
* @description Leaking information about an exception, such as messages and stack traces, to an
* external user can expose implementation details that are useful to an attacker for
* developing a subsequent exploit.
* developing a subsequent exploit.
* @kind path-problem
* @problem.severity error
* @precision high
@@ -14,22 +14,18 @@
import python
import semmle.python.security.Paths
import semmle.python.security.Exceptions
import semmle.python.web.HttpResponse
class StackTraceExposureConfiguration extends TaintTracking::Configuration {
StackTraceExposureConfiguration() { this = "Stack trace exposure configuration" }
override predicate isSource(TaintTracking::Source source) { source instanceof ErrorInfoSource }
override predicate isSink(TaintTracking::Sink sink) {
sink instanceof HttpResponseTaintSink
}
override predicate isSink(TaintTracking::Sink sink) { sink instanceof HttpResponseTaintSink }
}
from StackTraceExposureConfiguration config, TaintedPathSource src, TaintedPathSink sink
where config.hasFlowPath(src, sink)
select sink.getSink(), src, sink, "$@ may be exposed to an external user", src.getSource(), "Error information"
select sink.getSink(), src, sink, "$@ may be exposed to an external user", src.getSource(),
"Error information"

View File

@@ -11,13 +11,12 @@
*/
import python
import semmle.python.web.flask.General
from CallNode call, Object isTrue
from CallNode call, Value isTrue
where
call = theFlaskClass().declaredAttribute("run").(FunctionValue).getACall() and
call.getArgByName("debug").refersTo(isTrue) and
isTrue.booleanValue() = true
select call, "A Flask app appears to be run in debug mode. This may allow an attacker to run arbitrary code through the debugger."
call.getArgByName("debug").pointsTo(isTrue) and
isTrue.getDefiniteBooleanValue() = true
select call,
"A Flask app appears to be run in debug mode. This may allow an attacker to run arbitrary code through the debugger."

View File

@@ -11,13 +11,13 @@
import python
private ModuleObject theParamikoClientModule() { result = ModuleObject::named("paramiko.client") }
private ModuleValue theParamikoClientModule() { result = Value::named("paramiko.client") }
private ClassObject theParamikoSSHClientClass() {
private ClassValue theParamikoSSHClientClass() {
result = theParamikoClientModule().attr("SSHClient")
}
private ClassObject unsafe_paramiko_policy(string name) {
private ClassValue unsafe_paramiko_policy(string name) {
(name = "AutoAddPolicy" or name = "WarningPolicy") and
result = theParamikoClientModule().attr(name)
}
@@ -25,12 +25,12 @@ private ClassObject unsafe_paramiko_policy(string name) {
from CallNode call, ControlFlowNode arg, string name
where
call = theParamikoSSHClientClass()
.lookupAttribute("set_missing_host_key_policy")
.(FunctionObject)
.getACall() and
.lookup("set_missing_host_key_policy")
.(FunctionValue)
.getACall() and
arg = call.getAnArg() and
(
arg.refersTo(unsafe_paramiko_policy(name)) or
arg.refersTo(_, unsafe_paramiko_policy(name), _)
arg.pointsTo(unsafe_paramiko_policy(name)) or
arg.pointsTo().getClass() = unsafe_paramiko_policy(name)
)
select call, "Setting missing host key policy to " + name + " may be unsafe."

View File

@@ -10,24 +10,17 @@
*/
import python
import semmle.python.web.Http
FunctionObject requestFunction() {
result = ModuleObject::named("requests").attr(httpVerbLower())
}
FunctionValue requestFunction() { result = Module::named("requests").attr(httpVerbLower()) }
/** requests treats None as the default and all other "falsey" values as False */
predicate falseNotNone(Object o) {
o.booleanValue() = false and not o = theNoneObject()
}
from CallNode call, FunctionObject func, Object falsey, ControlFlowNode origin
where
func = requestFunction() and
func.getACall() = call and
falseNotNone(falsey) and
call.getArgByName("verify").refersTo(falsey, origin)
predicate falseNotNone(Value v) { v.getDefiniteBooleanValue() = false and not v = Value::none_() }
from CallNode call, FunctionValue func, Value falsey, ControlFlowNode origin
where
func = requestFunction() and
func.getACall() = call and
falseNotNone(falsey) and
call.getArgByName("verify").pointsTo(falsey, origin)
select call, "Call to $@ with verify=$@", func, "requests." + func.getName(), origin, "False"

View File

@@ -14,15 +14,12 @@
import python
import semmle.python.security.Paths
import semmle.python.security.TaintTracking
import semmle.python.security.SensitiveData
import semmle.python.security.ClearText
class CleartextLoggingConfiguration extends TaintTracking::Configuration {
CleartextLoggingConfiguration() { this = "ClearTextLogging" }
CleartextLoggingConfiguration() { this = "ClearTextLogging" }
override predicate isSource(DataFlow::Node src, TaintKind kind) {
src.asCfgNode().(SensitiveData::Source).isSourceOf(kind)
@@ -32,11 +29,9 @@ class CleartextLoggingConfiguration extends TaintTracking::Configuration {
sink.asCfgNode() instanceof ClearTextLogging::Sink and
kind instanceof SensitiveData
}
}
from CleartextLoggingConfiguration config, TaintedPathSource source, TaintedPathSink sink
where config.hasFlowPath(source, sink)
select sink.getSink(), source, sink, "Sensitive data returned by $@ is logged here.",
source.getSource(), source.getCfgNode().(SensitiveData::Source).repr()
source.getSource(), source.getCfgNode().(SensitiveData::Source).repr()

View File

@@ -14,14 +14,12 @@
import python
import semmle.python.security.Paths
import semmle.python.security.TaintTracking
import semmle.python.security.SensitiveData
import semmle.python.security.ClearText
class CleartextStorageConfiguration extends TaintTracking::Configuration {
CleartextStorageConfiguration() { this = "ClearTextStorage" }
CleartextStorageConfiguration() { this = "ClearTextStorage" }
override predicate isSource(DataFlow::Node src, TaintKind kind) {
src.asCfgNode().(SensitiveData::Source).isSourceOf(kind)
@@ -31,11 +29,9 @@ class CleartextStorageConfiguration extends TaintTracking::Configuration {
sink.asCfgNode() instanceof ClearTextStorage::Sink and
kind instanceof SensitiveData
}
}
from CleartextStorageConfiguration config, TaintedPathSource source, TaintedPathSink sink
where config.hasFlowPath(source, sink)
select sink.getSink(), source, sink, "Sensitive data from $@ is stored here.",
source.getSource(), source.getCfgNode().(SensitiveData::Source).repr()
select sink.getSink(), source, sink, "Sensitive data from $@ is stored here.", source.getSource(),
source.getCfgNode().(SensitiveData::Source).repr()

View File

@@ -19,63 +19,63 @@ int minimumSecureKeySize(string algo) {
algo = "ECC" and result = 224
}
predicate dsaRsaKeySizeArg(FunctionObject obj, string algorithm, string arg) {
exists(ModuleObject mod |
mod.attr(_) = obj |
predicate dsaRsaKeySizeArg(FunctionValue func, string algorithm, string arg) {
exists(ModuleValue mod | func = mod.attr(_) |
algorithm = "DSA" and
(
mod.getName() = "cryptography.hazmat.primitives.asymmetric.dsa" and arg = "key_size"
mod = Module::named("cryptography.hazmat.primitives.asymmetric.dsa") and arg = "key_size"
or
mod.getName() = "Crypto.PublicKey.DSA" and arg = "bits"
mod = Module::named("Crypto.PublicKey.DSA") and arg = "bits"
or
mod.getName() = "Cryptodome.PublicKey.DSA" and arg = "bits"
mod = Module::named("Cryptodome.PublicKey.DSA") and arg = "bits"
)
or
algorithm = "RSA" and
(
mod.getName() = "cryptography.hazmat.primitives.asymmetric.rsa" and arg = "key_size"
mod = Module::named("cryptography.hazmat.primitives.asymmetric.rsa") and arg = "key_size"
or
mod.getName() = "Crypto.PublicKey.RSA" and arg = "bits"
mod = Module::named("Crypto.PublicKey.RSA") and arg = "bits"
or
mod.getName() = "Cryptodome.PublicKey.RSA" and arg = "bits"
mod = Module::named("Cryptodome.PublicKey.RSA") and arg = "bits"
)
)
}
predicate ecKeySizeArg(FunctionObject obj, string arg) {
exists(ModuleObject mod |
mod.attr(_) = obj |
mod.getName() = "cryptography.hazmat.primitives.asymmetric.ec" and arg = "curve"
predicate ecKeySizeArg(FunctionValue func, string arg) {
exists(ModuleValue mod | func = mod.attr(_) |
mod = Module::named("cryptography.hazmat.primitives.asymmetric.ec") and arg = "curve"
)
}
int keySizeFromCurve(ClassObject curveClass) {
result = curveClass.declaredAttribute("key_size").(NumericObject).intValue()
int keySizeFromCurve(ClassValue curveClass) {
result = curveClass.declaredAttribute("key_size").(NumericValue).getIntValue()
}
predicate algorithmAndKeysizeForCall(CallNode call, string algorithm, int keySize, ControlFlowNode keyOrigin) {
exists(FunctionObject func, string argname, ControlFlowNode arg |
arg = func.getNamedArgumentForCall(call, argname) |
exists(NumericObject key |
arg.refersTo(key, keyOrigin) and
predicate algorithmAndKeysizeForCall(
CallNode call, string algorithm, int keySize, ControlFlowNode keyOrigin
) {
exists(FunctionValue func, string argname, ControlFlowNode arg |
arg = func.getNamedArgumentForCall(call, argname)
|
exists(NumericValue key |
arg.pointsTo(key, keyOrigin) and
dsaRsaKeySizeArg(func, algorithm, argname) and
keySize = key.intValue()
keySize = key.getIntValue()
)
or
exists(ClassObject curve |
arg.refersTo(_, curve, keyOrigin) and
ecKeySizeArg(func, argname) and
exists(Value curveClassInstance |
algorithm = "ECC" and
keySize = keySizeFromCurve(curve)
ecKeySizeArg(func, argname) and
arg.pointsTo(_, curveClassInstance, keyOrigin) and
keySize = keySizeFromCurve(curveClassInstance.getClass())
)
)
}
from CallNode call, ControlFlowNode origin, string algo, int keySize
from CallNode call, string algo, int keySize, ControlFlowNode origin
where
algorithmAndKeysizeForCall(call, algo, keySize, origin) and
keySize < minimumSecureKeySize(algo)
select call, "Creation of an " + algo + " key uses $@ bits, which is below " + minimumSecureKeySize(algo) + " and considered breakable.", origin, keySize.toString()
select call,
"Creation of an " + algo + " key uses $@ bits, which is below " + minimumSecureKeySize(algo) +
" and considered breakable.", origin, keySize.toString()

View File

@@ -12,13 +12,9 @@
import python
FunctionObject ssl_wrap_socket() {
result = ModuleObject::named("ssl").attr("wrap_socket")
}
FunctionValue ssl_wrap_socket() { result = Value::named("ssl.wrap_socket") }
ClassObject ssl_Context_class() {
result = ModuleObject::named("ssl").attr("SSLContext")
}
ClassValue ssl_Context_class() { result = Value::named("ssl.SSLContext") }
CallNode unsafe_call(string method_name) {
result = ssl_wrap_socket().getACall() and
@@ -32,10 +28,7 @@ CallNode unsafe_call(string method_name) {
}
from CallNode call, string method_name
where
call = unsafe_call(method_name)
select call, "Call to " + method_name + " does not specify a protocol, which may result in an insecure default being used."
where call = unsafe_call(method_name)
select call,
"Call to " + method_name +
" does not specify a protocol, which may result in an insecure default being used."

View File

@@ -11,12 +11,16 @@
import python
FunctionObject ssl_wrap_socket() {
result = the_ssl_module().attr("wrap_socket")
}
private ModuleValue the_ssl_module() { result = Module::named("ssl") }
ClassObject ssl_Context_class() {
result = the_ssl_module().attr("SSLContext")
FunctionValue ssl_wrap_socket() { result = the_ssl_module().attr("wrap_socket") }
ClassValue ssl_Context_class() { result = the_ssl_module().attr("SSLContext") }
private ModuleValue the_pyOpenSSL_module() { result = Value::named("pyOpenSSL.SSL") }
ClassValue the_pyOpenSSL_Context_class() {
result = Value::named("pyOpenSSL.SSL.Context")
}
string insecure_version_name() {
@@ -33,25 +37,21 @@ string insecure_version_name() {
result = "PROTOCOL_TLSv1"
}
private ModuleObject the_ssl_module() {
result = ModuleObject::named("ssl")
}
private ModuleObject the_pyOpenSSL_module() {
result = ModuleObject::named("pyOpenSSL.SSL")
}
/* A syntactic check for cases where points-to analysis cannot infer the presence of
/*
* A syntactic check for cases where points-to analysis cannot infer the presence of
* a protocol constant, e.g. if it has been removed in later versions of the `ssl`
* library.
*/
bindingset[named_argument]
predicate probable_insecure_ssl_constant(CallNode call, string insecure_version, string named_argument) {
predicate probable_insecure_ssl_constant(
CallNode call, string insecure_version, string named_argument
) {
exists(ControlFlowNode arg |
arg = call.getArgByName(named_argument) or
arg = call.getArg(0)
|
arg.(AttrNode).getObject(insecure_version).refersTo(the_ssl_module())
arg.(AttrNode).getObject(insecure_version).pointsTo(the_ssl_module())
or
arg.(NameNode).getId() = insecure_version and
exists(Import imp |
@@ -61,7 +61,9 @@ predicate probable_insecure_ssl_constant(CallNode call, string insecure_version,
)
}
predicate unsafe_ssl_wrap_socket_call(CallNode call, string method_name, string insecure_version, string named_argument) {
predicate unsafe_ssl_wrap_socket_call(
CallNode call, string method_name, string insecure_version, string named_argument
) {
(
call = ssl_wrap_socket().getACall() and
method_name = "deprecated method ssl.wrap_socket" and
@@ -70,30 +72,26 @@ predicate unsafe_ssl_wrap_socket_call(CallNode call, string method_name, string
call = ssl_Context_class().getACall() and
named_argument = "protocol" and
method_name = "ssl.SSLContext"
)
and
insecure_version = insecure_version_name()
and
) and
insecure_version = insecure_version_name() and
(
call.getArgByName(named_argument).refersTo(the_ssl_module().attr(insecure_version))
call.getArgByName(named_argument).pointsTo(the_ssl_module().attr(insecure_version))
or
probable_insecure_ssl_constant(call, insecure_version, named_argument)
)
}
ClassObject the_pyOpenSSL_Context_class() {
result = ModuleObject::named("pyOpenSSL.SSL").attr("Context")
}
predicate unsafe_pyOpenSSL_Context_call(CallNode call, string insecure_version) {
call = the_pyOpenSSL_Context_class().getACall() and
insecure_version = insecure_version_name() and
call.getArg(0).refersTo(the_pyOpenSSL_module().attr(insecure_version))
call.getArg(0).pointsTo(the_pyOpenSSL_module().attr(insecure_version))
}
from CallNode call, string method_name, string insecure_version
where
unsafe_ssl_wrap_socket_call(call, method_name, insecure_version, _)
or
or
unsafe_pyOpenSSL_Context_call(call, insecure_version) and method_name = "pyOpenSSL.SSL.Context"
select call, "Insecure SSL/TLS protocol version " + insecure_version + " specified in call to " + method_name + "."
select call,
"Insecure SSL/TLS protocol version " + insecure_version + " specified in call to " + method_name +
"."

View File

@@ -12,7 +12,7 @@
import python
FunctionObject temporary_name_function(string mod, string function) {
FunctionValue temporary_name_function(string mod, string function) {
(
mod = "tempfile" and function = "mktemp"
or
@@ -23,10 +23,9 @@ FunctionObject temporary_name_function(string mod, string function) {
function = "tempnam"
)
) and
result = ModuleObject::named(mod).attr(function)
result = Module::named(mod).attr(function)
}
from Call c, string mod, string function
where
temporary_name_function(mod, function).getACall().getNode() = c
where temporary_name_function(mod, function).getACall().getNode() = c
select c, "Call to deprecated function " + mod + "." + function + " may be insecure."

View File

@@ -10,28 +10,26 @@
* security
* serialization
*/
import python
import python
import semmle.python.security.Paths
// Sources -- Any untrusted input
import semmle.python.web.HttpRequest
import semmle.python.security.Paths
// Flow -- untrusted string
import semmle.python.security.strings.Untrusted
// Sink -- Unpickling and other deserialization formats.
import semmle.python.security.injection.Pickle
import semmle.python.security.injection.Marshal
import semmle.python.security.injection.Yaml
class UnsafeDeserializationConfiguration extends TaintTracking::Configuration {
class UnsafeDeserializationConfiguration extends TaintTracking::Configuration {
UnsafeDeserializationConfiguration() { this = "Unsafe deserialization configuration" }
override predicate isSource(TaintTracking::Source source) { source instanceof HttpRequestTaintSource }
override predicate isSource(TaintTracking::Source source) {
source instanceof HttpRequestTaintSource
}
override predicate isSink(TaintTracking::Sink sink) { sink instanceof DeserializationSink }
}
from UnsafeDeserializationConfiguration config, TaintedPathSource src, TaintedPathSink sink

View File

@@ -13,34 +13,29 @@
import python
import semmle.python.security.Paths
import semmle.python.web.HttpRedirect
import semmle.python.web.HttpRequest
import semmle.python.security.strings.Untrusted
/** Url redirection is a problem only if the user controls the prefix of the URL */
class UntrustedPrefixStringKind extends UntrustedStringKind {
override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) {
result = UntrustedStringKind.super.getTaintForFlowStep(fromnode, tonode) and
not tonode.(BinaryExprNode).getRight() = fromnode
}
}
class UrlRedirectConfiguration extends TaintTracking::Configuration {
UrlRedirectConfiguration() { this = "URL redirect configuration" }
override predicate isSource(TaintTracking::Source source) { source instanceof HttpRequestTaintSource }
override predicate isSink(TaintTracking::Sink sink) {
sink instanceof HttpRedirectTaintSink
override predicate isSource(TaintTracking::Source source) {
source instanceof HttpRequestTaintSource
}
override predicate isSink(TaintTracking::Sink sink) { sink instanceof HttpRedirectTaintSink }
}
from UrlRedirectConfiguration config, TaintedPathSource src, TaintedPathSink sink
where config.hasFlowPath(src, sink)
select sink.getSink(), src, sink, "Untrusted URL redirection due to $@.", src.getSource(), "a user-provided value"
select sink.getSink(), src, sink, "Untrusted URL redirection due to $@.", src.getSource(),
"a user-provided value"

View File

@@ -3,7 +3,7 @@
<overview>
<p>
When creating a file POSIX systems allow permissions to be specified
When creating a file, POSIX systems allow permissions to be specified
for owner, group and others separately. Permissions should be kept as
strict as possible, preventing access to the files contents by other users.
</p>

View File

@@ -9,22 +9,20 @@
* @tags external/cwe/cwe-732
* security
*/
import python
bindingset[p]
int world_permission(int p) {
result = p % 8
}
int world_permission(int p) { result = p % 8 }
bindingset[p]
int group_permission(int p) {
result = (p/8) % 8
}
int group_permission(int p) { result = (p / 8) % 8 }
bindingset[p]
string access(int p) {
p%4 >= 2 and result = "writable" or
p%4 < 2 and p != 0 and result = "readable"
p % 4 >= 2 and result = "writable"
or
p % 4 < 2 and p != 0 and result = "readable"
}
bindingset[p]
@@ -34,20 +32,20 @@ string permissive_permission(int p) {
world_permission(p) = 0 and result = "group " + access(group_permission(p))
}
predicate chmod_call(CallNode call, FunctionObject chmod, NumericObject num) {
ModuleObject::named("os").attr("chmod") = chmod and
chmod.getACall() = call and call.getArg(1).refersTo(num)
predicate chmod_call(CallNode call, FunctionValue chmod, NumericValue num) {
Value::named("os.chmod") = chmod and
chmod.getACall() = call and
call.getArg(1).pointsTo(num)
}
predicate open_call(CallNode call, FunctionObject open, NumericObject num) {
ModuleObject::named("os").attr("open") = open and
open.getACall() = call and call.getArg(2).refersTo(num)
predicate open_call(CallNode call, FunctionValue open, NumericValue num) {
Value::named("os.open") = open and
open.getACall() = call and
call.getArg(2).pointsTo(num)
}
from CallNode call, FunctionObject func, NumericObject num, string permission
from CallNode call, FunctionValue func, NumericValue num, string permission
where
(chmod_call(call, func, num) or open_call(call, func, num))
and
permission = permissive_permission(num.intValue())
(chmod_call(call, func, num) or open_call(call, func, num)) and
permission = permissive_permission(num.getIntValue())
select call, "Overly permissive mask in " + func.getName() + " sets file to " + permission + "."

View File

@@ -33,15 +33,15 @@ predicate fewer_characters_than(StrConst str, string char, float fraction) {
}
predicate possible_reflective_name(string name) {
exists(any(ModuleObject m).attr(name))
exists(any(ModuleValue m).attr(name))
or
exists(any(ClassObject c).lookupAttribute(name))
exists(any(ClassValue c).lookup(name))
or
any(ClassObject c).getName() = name
any(ClassValue c).getName() = name
or
exists(ModuleObject::named(name))
exists(Module::named(name))
or
exists(Object::builtin(name))
exists(Value::named(name))
}
int char_count(StrConst str) { result = count(string c | c = str.getText().charAt(_)) }
@@ -89,7 +89,7 @@ class CredentialSink extends TaintSink {
name.regexpMatch(getACredentialRegex()) and
not name.suffix(name.length() - 4) = "file"
|
any(FunctionObject func).getNamedArgumentForCall(_, name) = this
any(FunctionValue func).getNamedArgumentForCall(_, name) = this
or
exists(Keyword k | k.getArg() = name and k.getValue().getAFlowNode() = this)
or

View File

@@ -0,0 +1 @@
This directory contains [experimental](../../../../docs/experimental.md) CodeQL queries and libraries.

View File

@@ -1,3 +1,8 @@
/*
* This library file is auto-generated by 'semmle/query_gen.py'.
* WARNING: Any modifications to this file will be lost.
* Relations can be changed by modifying master.py.
*/
import python
library class Add_ extends @py_Add, Operator {
@@ -1781,6 +1786,37 @@ library class Slice_ extends @py_Slice, Expr {
}
library class SpecialOperation_ extends @py_SpecialOperation, Expr {
/** Gets the name of this special operation. */
string getName() {
py_strs(result, this, 2)
}
/** Gets the arguments of this special operation. */
ExprList getArguments() {
py_expr_lists(result, this, 3)
}
/** Gets the nth argument of this special operation. */
Expr getArgument(int index) {
result = this.getArguments().getItem(index)
}
/** Gets an argument of this special operation. */
Expr getAnArgument() {
result = this.getArguments().getAnItem()
}
override string toString() {
result = "SpecialOperation"
}
}
library class Starred_ extends @py_Starred, Expr {

View File

@@ -753,9 +753,8 @@ class DefinitionNode extends ControlFlowNode {
or
augstore(_, this)
or
exists(Assign a | a.getATarget().(Tuple).getAnElt().getAFlowNode() = this)
or
exists(Assign a | a.getATarget().(List).getAnElt().getAFlowNode() = this)
// `x, y = 1, 2` where LHS is a combination of list or tuples
exists(Assign a | list_or_tuple_nested_element(a.getATarget()).getAFlowNode() = this)
or
exists(For for | for.getTarget().getAFlowNode() = this)
}
@@ -768,6 +767,18 @@ class DefinitionNode extends ControlFlowNode {
}
}
private Expr list_or_tuple_nested_element(Expr list_or_tuple) {
exists(Expr elt |
elt = list_or_tuple.(Tuple).getAnElt()
or
elt = list_or_tuple.(List).getAnElt()
|
result = elt
or
result = list_or_tuple_nested_element(elt)
)
}
/** A control flow node corresponding to a deletion statement, such as `del x`.
* There can be multiple `DeletionNode`s for each `Delete` such that each
* target has own `DeletionNode`. The CFG for `del a, x.y` looks like:
@@ -887,18 +898,42 @@ private AstNode assigned_value(Expr lhs) {
/* lhs += x => result = (lhs + x) */
exists(AugAssign a, BinaryExpr b | b = a.getOperation() and result = b and lhs = b.getLeft())
or
/* ..., lhs, ... = ..., result, ... */
exists(Assign a, Tuple target, Tuple values, int index |
a.getATarget() = target and
a.getValue() = values and
lhs = target.getElt(index) and
result = values.getElt(index)
)
/* ..., lhs, ... = ..., result, ...
* or
* ..., (..., lhs, ...), ... = ..., (..., result, ...), ...
*/
exists(Assign a | nested_sequence_assign(a.getATarget(), a.getValue(), lhs, result))
or
/* for lhs in seq: => `result` is the `for` node, representing the `iter(next(seq))` operation. */
result.(For).getTarget() = lhs
}
predicate nested_sequence_assign(Expr left_parent, Expr right_parent,
Expr left_result, Expr right_result) {
exists(Assign a |
a.getATarget().getASubExpression*() = left_parent and
a.getValue().getASubExpression*() = right_parent
) and
exists(int i, Expr left_elem, Expr right_elem
|
(
left_elem = left_parent.(Tuple).getElt(i)
or
left_elem = left_parent.(List).getElt(i)
)
and
(
right_elem = right_parent.(Tuple).getElt(i)
or
right_elem = right_parent.(List).getElt(i)
)
|
left_result = left_elem and right_result = right_elem
or
nested_sequence_assign(left_elem, right_elem, left_result, right_result)
)
}
/** A flow node for a `for` statement. */
class ForNode extends ControlFlowNode {
@@ -1037,6 +1072,17 @@ class NameConstantNode extends NameNode {
*/
}
/** A control flow node correspoinding to a starred expression, `*a`. */
class StarredNode extends ControlFlowNode {
StarredNode() {
toAst(this) instanceof Starred
}
ControlFlowNode getValue() {
toAst(result) = toAst(this).(Starred).getValue()
}
}
private module Scopes {
private predicate fast_local(NameNode n) {

View File

@@ -4,7 +4,6 @@ private import semmle.python.objects.ObjectInternal
private import semmle.python.dataflow.Implementation
module TaintTracking {
class Source = TaintSource;
class Sink = TaintSink;
@@ -16,13 +15,11 @@ module TaintTracking {
class PathSink = TaintTrackingNode;
abstract class Configuration extends string {
/* Required to prevent compiler warning */
bindingset[this]
Configuration() { this = this }
/* Old implementation API */
predicate isSource(Source src) { none() }
predicate isSink(Sink sink) { none() }
@@ -32,7 +29,6 @@ module TaintTracking {
predicate isExtension(Extension extension) { none() }
/* New implementation API */
/**
* Holds if `src` is a source of taint of `kind` that is relevant
* for this configuration.
@@ -66,7 +62,9 @@ module TaintTracking {
/**
* Holds if `src -> dest` is a flow edge converting taint from `srckind` to `destkind`.
*/
predicate isAdditionalFlowStep(DataFlow::Node src, DataFlow::Node dest, TaintKind srckind, TaintKind destkind) {
predicate isAdditionalFlowStep(
DataFlow::Node src, DataFlow::Node dest, TaintKind srckind, TaintKind destkind
) {
none()
}
@@ -79,9 +77,7 @@ module TaintTracking {
* Holds if `node` should be considered as a barrier to flow of `kind`.
*/
predicate isBarrier(DataFlow::Node node, TaintKind kind) {
exists(Sanitizer sanitizer |
this.isSanitizer(sanitizer)
|
exists(Sanitizer sanitizer | this.isSanitizer(sanitizer) |
sanitizer.sanitizingNode(kind, node.asCfgNode())
or
sanitizer.sanitizingEdge(kind, node.asVariable())
@@ -112,16 +108,18 @@ module TaintTracking {
* Holds if flow from `src` to `dest` is prohibited when the incoming taint is `srckind` and the outgoing taint is `destkind`.
* Note that `srckind` and `destkind` can be the same.
*/
predicate isBarrierEdge(DataFlow::Node src, DataFlow::Node dest, TaintKind srckind, TaintKind destkind) { none() }
predicate isBarrierEdge(
DataFlow::Node src, DataFlow::Node dest, TaintKind srckind, TaintKind destkind
) {
none()
}
/* Common query API */
predicate hasFlowPath(PathSource src, PathSink sink) {
this.(TaintTrackingImplementation).hasFlowPath(src, sink)
}
/* Old query API */
/* deprecated */
deprecated predicate hasFlow(Source src, Sink sink) {
exists(PathSource psrc, PathSink psink |
@@ -132,7 +130,6 @@ module TaintTracking {
}
/* New query API */
predicate hasSimpleFlow(DataFlow::Node src, DataFlow::Node sink) {
exists(PathSource psrc, PathSink psink |
this.hasFlowPath(psrc, psink) and
@@ -140,7 +137,5 @@ module TaintTracking {
sink = psink.getNode()
)
}
}
}

View File

@@ -1 +1 @@
import semmle.python.security.TaintTracking
import semmle.python.security.TaintTracking

View File

@@ -2,25 +2,18 @@ import python
import semmle.python.security.TaintTracking
class OpenFile extends TaintKind {
OpenFile() { this = "file.open" }
override string repr() { result = "an open file" }
}
class OpenFileConfiguration extends TaintTracking::Configuration {
OpenFileConfiguration() { this = "Open file configuration" }
OpenFileConfiguration() { this = "Open file configuration" }
override predicate isSource(DataFlow::Node src, TaintKind kind) {
theOpenFunction().(FunctionObject).getACall() = src.asCfgNode() and
kind instanceof OpenFile
}
override predicate isSink(DataFlow::Node sink, TaintKind kind) {
none()
}
override predicate isSink(DataFlow::Node sink, TaintKind kind) { none() }
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,12 +3,8 @@ private import semmle.python.objects.ObjectInternal
import semmle.python.dataflow.Implementation
/* For backwards compatibility -- Use `TaintTrackingContext` instead. */
deprecated
class CallContext extends TaintTrackingContext {
TaintTrackingContext getCallee(CallNode call) {
result.getCaller(call) = this
}
deprecated class CallContext extends TaintTrackingContext {
TaintTrackingContext getCallee(CallNode call) { result.getCaller(call) = this }
predicate appliesToScope(Scope s) {
exists(PythonFunctionObjectInternal func, TaintKind param, AttributePath path, int n |
@@ -21,33 +17,23 @@ class CallContext extends TaintTrackingContext {
or
this.isTop()
}
}
/* Backwards compatibility with config-less taint-tracking */
private class LegacyConfiguration extends TaintTracking::Configuration {
LegacyConfiguration() {
/* A name that won't be accidentally chosen by users */
this = "Semmle: Internal legacy configuration"
}
override predicate isSource(TaintSource src) {
src = src
}
override predicate isSource(TaintSource src) { src = src }
override predicate isSink(TaintSink sink) {
sink = sink
}
override predicate isSink(TaintSink sink) { sink = sink }
override predicate isSanitizer(Sanitizer sanitizer) {
sanitizer = sanitizer
}
override predicate isSanitizer(Sanitizer sanitizer) { sanitizer = sanitizer }
override predicate isAdditionalFlowStep(DataFlow::Node src, DataFlow::Node dest) {
exists(DataFlowExtension::DataFlowNode legacyExtension |
src.asCfgNode() = legacyExtension
|
exists(DataFlowExtension::DataFlowNode legacyExtension | src.asCfgNode() = legacyExtension |
dest.asCfgNode() = legacyExtension.getASuccessorNode()
or
dest.asVariable() = legacyExtension.getASuccessorVariable()
@@ -58,10 +44,10 @@ private class LegacyConfiguration extends TaintTracking::Configuration {
)
}
override predicate isAdditionalFlowStep(DataFlow::Node src, DataFlow::Node dest, TaintKind srckind, TaintKind destkind) {
exists(DataFlowExtension::DataFlowNode legacyExtension |
src.asCfgNode() = legacyExtension
|
override predicate isAdditionalFlowStep(
DataFlow::Node src, DataFlow::Node dest, TaintKind srckind, TaintKind destkind
) {
exists(DataFlowExtension::DataFlowNode legacyExtension | src.asCfgNode() = legacyExtension |
dest.asCfgNode() = legacyExtension.getASuccessorNode(srckind, destkind)
)
}
@@ -79,5 +65,4 @@ private class LegacyConfiguration extends TaintTracking::Configuration {
)
)
}
}

View File

@@ -1,5 +1,6 @@
/** Provides classes and predicates for tracking global state across the control flow and call graphs.
*
/**
* Provides classes and predicates for tracking global state across the control flow and call graphs.
*
* NOTE: State tracking tracks both whether a state may apply to a given node in a given context *and*
* whether it may not apply.
* That `state.appliesTo(f, ctx)` holds implies nothing about whether `state.mayNotApplyTo(f, ctx)` holds.
@@ -15,14 +16,11 @@ private import semmle.python.objects.ObjectInternal
/** A state that should be tracked. */
abstract class TrackableState extends string {
bindingset[this]
TrackableState() { this = this }
/** Holds if this state may apply to the control flow node `f`, regardless of the context. */
final predicate appliesTo(ControlFlowNode f) {
this.appliesTo(f, _)
}
final predicate appliesTo(ControlFlowNode f) { this.appliesTo(f, _) }
/** Holds if this state may not apply to the control flow node `f`, given the context `ctx`. */
final predicate appliesTo(ControlFlowNode f, Context ctx) {
@@ -35,60 +33,56 @@ abstract class TrackableState extends string {
}
/** Holds if this state may apply to the control flow node `f`, regardless of the context. */
final predicate mayNotApplyTo(ControlFlowNode f) {
this.mayNotApplyTo(f, _)
}
final predicate mayNotApplyTo(ControlFlowNode f) { this.mayNotApplyTo(f, _) }
/** Holds if `test` shows value to be untainted with `taint`, given the context `ctx`. */
predicate testsFor(PyEdgeRefinement test, Context ctx, boolean sense) {
predicate testsFor(PyEdgeRefinement test, Context ctx, boolean sense) {
ctx.appliesToScope(test.getScope()) and this.testsFor(test, sense)
}
/** Holds if `test` shows value to be untainted with `taint` */
predicate testsFor(PyEdgeRefinement test, boolean sense) { none() }
/** Holds if state starts at `f`.
/**
* Holds if state starts at `f`.
* Either this predicate or `startsAt(ControlFlowNode f, Context ctx)`
* should be overriden by sub-classes.
*/
predicate startsAt(ControlFlowNode f) { none() }
/** Holds if state starts at `f` given context `ctx`.
/**
* Holds if state starts at `f` given context `ctx`.
* Either this predicate or `startsAt(ControlFlowNode f)`
* should be overriden by sub-classes.
*/
pragma [noinline]
predicate startsAt(ControlFlowNode f, Context ctx) {
ctx.appliesTo(f) and this.startsAt(f)
}
pragma[noinline]
predicate startsAt(ControlFlowNode f, Context ctx) { ctx.appliesTo(f) and this.startsAt(f) }
/** Holds if state ends at `f`.
/**
* Holds if state ends at `f`.
* Either this predicate or `endsAt(ControlFlowNode f, Context ctx)`
* may be overriden by sub-classes.
*/
predicate endsAt(ControlFlowNode f) { none() }
/** Holds if state ends at `f` given context `ctx`.
/**
* Holds if state ends at `f` given context `ctx`.
* Either this predicate or `endsAt(ControlFlowNode f)`
* may be overriden by sub-classes.
*/
pragma [noinline]
predicate endsAt(ControlFlowNode f, Context ctx) {
ctx.appliesTo(f) and this.endsAt(f)
}
pragma[noinline]
predicate endsAt(ControlFlowNode f, Context ctx) { ctx.appliesTo(f) and this.endsAt(f) }
}
module StateTracking {
private predicate not_allowed(TrackableState state, ControlFlowNode f, Context ctx, boolean sense) {
state.endsAt(f, ctx) and sense = true
or
state.startsAt(f, ctx) and sense = false
}
/** Holds if `state` may apply (with `sense` = true) or may not apply (with `sense` = false) to
/**
* Holds if `state` may apply (with `sense` = true) or may not apply (with `sense` = false) to
* control flow node `f` given the context `ctx`.
*/
predicate appliesToNode(TrackableState state, ControlFlowNode f, Context ctx, boolean sense) {
@@ -96,8 +90,7 @@ module StateTracking {
or
state.startsAt(f, ctx) and sense = true
or
not not_allowed(state, f, ctx, sense)
and
not not_allowed(state, f, ctx, sense) and
(
exists(BasicBlock b |
/* First node in a block */
@@ -106,7 +99,7 @@ module StateTracking {
/* Other nodes in block, except trackable calls */
exists(int n |
f = b.getNode(n) and
appliesToNode(state, b.getNode(n-1), ctx, sense) and
appliesToNode(state, b.getNode(n - 1), ctx, sense) and
not exists(PythonFunctionObjectInternal func, Context callee |
callee.fromCall(f, func, ctx)
)
@@ -127,27 +120,32 @@ module StateTracking {
)
or
/* Other scope entries */
exists(Scope s |
exists(Scope s |
s.getEntryNode() = f and
ctx.appliesToScope(s)
|
|
not exists(Scope pred | pred.precedes(s)) and
(ctx.isImport() or ctx.isRuntime()) and sense = false
(ctx.isImport() or ctx.isRuntime()) and
sense = false
or
exists(Scope pred, Context pred_ctx |
appliesToNode(state, pred.getANormalExit(), pred_ctx, sense) and
pred.precedes(s) and
ctx.isRuntime() |
ctx.isRuntime()
|
pred_ctx.isRuntime() or pred_ctx.isImport()
)
)
)
}
/** Holds if `state` may apply (with `sense` = true) or may not apply (with `sense` = false) at the
/**
* Holds if `state` may apply (with `sense` = true) or may not apply (with `sense` = false) at the
* start of basic block `block` given the context `ctx`.
*/
private predicate appliesAtBlockStart(TrackableState state, BasicBlock block, Context ctx, boolean sense) {
private predicate appliesAtBlockStart(
TrackableState state, BasicBlock block, Context ctx, boolean sense
) {
exists(PyEdgeRefinement test |
test.getSuccessor() = block and
state.testsFor(test, ctx, sense)
@@ -164,12 +162,13 @@ module StateTracking {
)
}
/** Holds if `state` may apply (with `sense` = true) or may not apply (with `sense` = false) at the
/**
* Holds if `state` may apply (with `sense` = true) or may not apply (with `sense` = false) at the
* end of basic block `block` given the context `ctx`.
*/
private predicate appliesAtBlockEnd(TrackableState state, BasicBlock block, Context ctx, boolean sense) {
private predicate appliesAtBlockEnd(
TrackableState state, BasicBlock block, Context ctx, boolean sense
) {
appliesToNode(state, block.getLastNode(), ctx, sense)
}
}

View File

@@ -92,7 +92,8 @@ private import semmle.python.objects.ObjectInternal
private import semmle.python.dataflow.Implementation
import semmle.python.dataflow.Configuration
/** A 'kind' of taint. This may be almost anything,
/**
* A 'kind' of taint. This may be almost anything,
* but it is typically something like a "user-defined string".
* Examples include, data from a http request object,
* data from an SMS or other mobile data source,
@@ -100,33 +101,38 @@ import semmle.python.dataflow.Configuration
* the local file system.
*/
abstract class TaintKind extends string {
bindingset[this]
TaintKind() { any() }
/** Gets the kind of taint that the named attribute will have if an object is tainted with this taint.
/**
* Gets the kind of taint that the named attribute will have if an object is tainted with this taint.
* In other words, if `x` has this kind of taint then it implies that `x.name`
* has `result` kind of taint.
*/
TaintKind getTaintOfAttribute(string name) { none() }
/** Gets the kind of taint results from calling the named method if an object is tainted with this taint.
/**
* Gets the kind of taint results from calling the named method if an object is tainted with this taint.
* In other words, if `x` has this kind of taint then it implies that `x.name()`
* has `result` kind of taint.
*/
TaintKind getTaintOfMethodResult(string name) { none() }
/** Gets the taint resulting from the flow step `fromnode` -> `tonode`.
/**
* Gets the taint resulting from the flow step `fromnode` -> `tonode`.
*/
TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) { none() }
/** Gets the taint resulting from the flow step `fromnode` -> `tonode`, with `edgeLabel`
/**
* Gets the taint resulting from the flow step `fromnode` -> `tonode`, with `edgeLabel`
*/
TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode, string edgeLabel) {
result = this.getTaintForFlowStep(fromnode, tonode) and edgeLabel = "custom taint flow step for " + this
result = this.getTaintForFlowStep(fromnode, tonode) and
edgeLabel = "custom taint flow step for " + this
}
/** DEPRECATED -- Use `TaintFlow.additionalFlowStepVar(EssaVariable fromvar, EssaVariable tovar, TaintKind kind)` instead.
/**
* 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`
@@ -134,40 +140,40 @@ abstract class TaintKind extends string {
*/
deprecated predicate additionalFlowStepVar(EssaVariable fromvar, EssaVariable tovar) { none() }
/** Holds if this kind of taint "taints" `expr`.
/**
* Holds if this kind of taint "taints" `expr`.
*/
final predicate taints(ControlFlowNode expr) {
exists(TaintedNode n |
n.getTaintKind() = this and n.getCfgNode() = expr
)
exists(TaintedNode n | n.getTaintKind() = this and n.getCfgNode() = expr)
}
/** DEPRECATED -- Use getType() instead */
deprecated ClassObject getClass() {
none()
}
deprecated ClassObject getClass() { none() }
/** Gets the class of this kind of taint.
/**
* Gets the class of this kind of taint.
* For example, if this were a kind of string taint
* the `result` would be `theStrType()`.
*/
ClassValue getType() {
result.(ClassObjectInternal).getSource() = this.getClass()
}
ClassValue getType() { result.(ClassObjectInternal).getSource() = this.getClass() }
/** Gets the boolean values (may be one, neither, or both) that
/**
* Gets the boolean values (may be one, neither, or both) that
* may result from the Python expression `bool(this)`
*/
boolean booleanValue() {
/* Default to true as the vast majority of taint is strings and
/*
* Default to true as the vast majority of taint is strings and
* the empty string is almost always benign.
*/
result = true
}
string repr() { result = this }
/** Gets the taint resulting from iterating over this kind of taint.
/**
* Gets the taint resulting from iterating over this kind of taint.
* For example iterating over a text file produces lines. So iterating
* over a tainted file would result in tainted strings
*/
@@ -184,20 +190,21 @@ abstract class TaintKind extends string {
*/
class FlowLabel = TaintKind;
/** Taint kinds representing collections of other taint kind.
/**
* Taint kinds representing collections of other taint kind.
* We use `{kind}` to represent a mapping of string to `kind` and
* `[kind]` to represent a flat collection of `kind`.
* The use of `{` and `[` is chosen to reflect dict and list literals
* `[kind]` to represent a flat collection of `kind`.
* The use of `{` and `[` is chosen to reflect dict and list literals
* in Python. We choose a single character prefix and suffix for simplicity
* and ease of preventing infinite recursion.
*/
abstract class CollectionKind extends TaintKind {
bindingset[this]
CollectionKind() {
(this.charAt(0) = "[" or this.charAt(0) = "{") and
/* Prevent any collection kinds more than 2 deep */
not this.charAt(2) = "[" and not this.charAt(2) = "{"
not this.charAt(2) = "[" and
not this.charAt(2) = "{"
}
abstract TaintKind getMember();
@@ -207,20 +214,16 @@ abstract class CollectionKind extends TaintKind {
abstract predicate flowToMember(DataFlow::Node fromnode, DataFlow::Node tonode);
}
/** A taint kind representing a flat collections of kinds.
/**
* A taint kind representing a flat collections of kinds.
* Typically a sequence, but can include sets.
*/
class SequenceKind extends CollectionKind {
TaintKind itemKind;
SequenceKind() {
this = "[" + itemKind + "]"
}
SequenceKind() { this = "[" + itemKind + "]" }
TaintKind getItem() {
result = itemKind
}
TaintKind getItem() { result = itemKind }
override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) {
exists(BinaryExprNode mod |
@@ -236,17 +239,11 @@ class SequenceKind extends CollectionKind {
name = "pop" and result = this.getItem()
}
override string repr() {
result = "sequence of " + itemKind
}
override string repr() { result = "sequence of " + itemKind }
override TaintKind getTaintForIteration() {
result = itemKind
}
override TaintKind getTaintForIteration() { result = itemKind }
override TaintKind getMember() {
result = itemKind
}
override TaintKind getMember() { result = itemKind }
override predicate flowFromMember(DataFlow::Node fromnode, DataFlow::Node tonode) {
sequence_construct(fromnode.asCfgNode(), tonode.asCfgNode())
@@ -255,12 +252,9 @@ class SequenceKind extends CollectionKind {
override predicate flowToMember(DataFlow::Node fromnode, DataFlow::Node tonode) {
SequenceKind::itemFlowStep(fromnode.asCfgNode(), tonode.asCfgNode())
}
}
module SequenceKind {
predicate flowStep(ControlFlowNode fromnode, ControlFlowNode tonode, string edgeLabel) {
tonode.(BinaryExprNode).getAnOperand() = fromnode and edgeLabel = "binary operation"
or
@@ -275,11 +269,10 @@ module SequenceKind {
predicate itemFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) {
subscript_index(fromnode, tonode)
}
}
module DictKind {
predicate flowStep(ControlFlowNode fromnode, ControlFlowNode tonode, string edgeLabel) {
predicate flowStep(ControlFlowNode fromnode, ControlFlowNode tonode, string edgeLabel) {
Implementation::copyCall(fromnode, tonode) and
edgeLabel = "dict copy"
or
@@ -289,37 +282,31 @@ module DictKind {
}
}
/* Helper for sequence flow steps */
pragma [noinline]
pragma[noinline]
private predicate subscript_index(ControlFlowNode obj, SubscriptNode sub) {
sub.isLoad() and
sub.getValue() = obj and
not sub.getNode().getIndex() instanceof Slice
}
pragma [noinline]
pragma[noinline]
private predicate subscript_slice(ControlFlowNode obj, SubscriptNode sub) {
sub.isLoad() and
sub.getValue() = obj and
sub.getNode().getIndex() instanceof Slice
}
/** A taint kind representing a mapping of objects to kinds.
/**
* A taint kind representing a mapping of objects to kinds.
* Typically a dict, but can include other mappings.
*/
class DictKind extends CollectionKind {
TaintKind valueKind;
DictKind() {
this = "{" + valueKind + "}"
}
DictKind() { this = "{" + valueKind + "}" }
TaintKind getValue() {
result = valueKind
}
TaintKind getValue() { result = valueKind }
override TaintKind getTaintOfMethodResult(string name) {
name = "get" and result = valueKind
@@ -329,13 +316,9 @@ class DictKind extends CollectionKind {
name = "itervalues" and result.(SequenceKind).getItem() = valueKind
}
override string repr() {
result = "dict of " + valueKind
}
override string repr() { result = "dict of " + valueKind }
override TaintKind getMember() {
result = valueKind
}
override TaintKind getMember() { result = valueKind }
override predicate flowFromMember(DataFlow::Node fromnode, DataFlow::Node tonode) {
dict_construct(fromnode.asCfgNode(), tonode.asCfgNode())
@@ -344,17 +327,15 @@ class DictKind extends CollectionKind {
override predicate flowToMember(DataFlow::Node fromnode, DataFlow::Node tonode) {
subscript_index(fromnode.asCfgNode(), tonode.asCfgNode())
}
}
/** A type of sanitizer of untrusted data.
/**
* A type of sanitizer of untrusted data.
* Examples include sanitizers for http responses, for DB access or for shell commands.
* Usually a sanitizer can only sanitize data for one particular use.
* For example, a sanitizer for DB commands would not be safe to use for http responses.
*/
abstract class Sanitizer extends string {
bindingset[this]
Sanitizer() { any() }
@@ -372,42 +353,49 @@ abstract class Sanitizer extends string {
/** Holds if `def` shows value to be untainted with `taint` */
predicate sanitizingDefinition(TaintKind taint, EssaDefinition def) { none() }
}
/** DEPRECATED -- Use DataFlowExtension instead.
/**
* 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.
*/
deprecated abstract class TaintFlow extends string {
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`.
/**
* 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() }
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`.
/**
* 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() }
predicate additionalFlowStepVar(EssaVariable fromvar, EssaVariable tovar, TaintKind kind) {
none()
}
/** Holds if the given `kind` of taint cannot pass from variable `fromvar` to variable `tovar`.
/**
* 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() }
predicate prunedFlowStepVar(EssaVariable fromvar, EssaVariable tovar, TaintKind kind) { none() }
}
/** A source of taintedness.
/**
* A source of taintedness.
* Users of the taint tracking library should override this
* class to provide their own sources.
*/
abstract class TaintSource extends @py_flow_node {
string toString() { result = "Taint source" }
/**
@@ -429,9 +417,7 @@ abstract class TaintSource extends @py_flow_node {
context.isTop() and this.isSourceOf(kind)
}
Location getLocation() {
result = this.(ControlFlowNode).getLocation()
}
Location getLocation() { result = this.(ControlFlowNode).getLocation() }
predicate hasLocationInfo(string fp, int bl, int bc, int el, int ec) {
this.getLocation().hasLocationInfo(fp, bl, bc, el, ec)
@@ -459,19 +445,16 @@ abstract class TaintSource extends @py_flow_node {
}
/** Holds if taint can flow from this source to taint sink `sink` */
final predicate flowsToSink(TaintSink sink) {
this.flowsToSink(_, sink)
}
final predicate flowsToSink(TaintSink sink) { this.flowsToSink(_, sink) }
}
/** Warning: Advanced feature. Users are strongly recommended to use `TaintSource` instead.
/**
* Warning: Advanced feature. Users are strongly recommended to use `TaintSource` instead.
* A source of taintedness on the ESSA data-flow graph.
* Users of the taint tracking library can override this
* class to provide their own sources on the ESSA graph.
*/
abstract class TaintedDefinition extends EssaNodeDefinition {
/**
* Holds if `this` is a source of taint kind `kind`
*
@@ -490,48 +473,36 @@ abstract class TaintedDefinition extends EssaNodeDefinition {
predicate isSourceOf(TaintKind kind, TaintTrackingContext context) {
context.isTop() and this.isSourceOf(kind)
}
}
private class DictUpdate extends DataFlowExtension::DataFlowNode {
MethodCallsiteRefinement call;
DictUpdate() {
exists(CallNode c |
c = call.getCall()
|
exists(CallNode c | c = call.getCall() |
c.getFunction().(AttrNode).getName() = "update" and
c.getArg(0) = this
)
}
override EssaVariable getASuccessorVariable() {
call.getVariable() = result
}
override EssaVariable getASuccessorVariable() { call.getVariable() = result }
}
private class SequenceExtends extends DataFlowExtension::DataFlowNode {
MethodCallsiteRefinement call;
SequenceExtends() {
exists(CallNode c |
c = call.getCall()
|
exists(CallNode c | c = call.getCall() |
c.getFunction().(AttrNode).getName() = "extend" and
c.getArg(0) = this
)
}
override EssaVariable getASuccessorVariable() {
call.getVariable() = result
}
override EssaVariable getASuccessorVariable() { call.getVariable() = result }
}
/** A node that is vulnerable to one or more types of taint.
/**
* A node that is vulnerable to one or more types of taint.
* These nodes provide the sinks when computing the taint flow graph.
* An example would be an argument to a write to a http response object,
* such an argument would be vulnerable to unsanitized user-input (XSS).
@@ -539,8 +510,7 @@ private class SequenceExtends extends DataFlowExtension::DataFlowNode {
* Users of the taint tracking library should extend this
* class to provide their own sink nodes.
*/
abstract class TaintSink extends @py_flow_node {
abstract class TaintSink extends @py_flow_node {
string toString() { result = "Taint sink" }
/**
@@ -551,134 +521,119 @@ abstract class TaintSink extends @py_flow_node {
*/
abstract predicate sinks(TaintKind taint);
Location getLocation() {
result = this.(ControlFlowNode).getLocation()
}
Location getLocation() { result = this.(ControlFlowNode).getLocation() }
predicate hasLocationInfo(string fp, int bl, int bc, int el, int ec) {
this.getLocation().hasLocationInfo(fp, bl, bc, el, ec)
}
}
/** Extension for data-flow, to help express data-flow paths that are
/**
* Extension for data-flow, to help express data-flow paths that are
* library or framework specific and cannot be inferred by the general
* data-flow machinery.
*/
module DataFlowExtension {
/** A control flow node that modifies the basic data-flow. */
abstract class DataFlowNode extends @py_flow_node {
string toString() { result = "Dataflow extension node" }
string toString() {
result = "Dataflow extension node"
}
/** Gets a successor node for data-flow.
/**
* Gets a successor node for data-flow.
* Data (all forms) is assumed to flow from `this` to `result`
*/
ControlFlowNode getASuccessorNode() { none() }
/** Gets a successor variable for data-flow.
/**
* Gets a successor variable for data-flow.
* Data (all forms) is assumed to flow from `this` to `result`.
* Note: This is an unlikely form of flow. See `DataFlowVariable.getASuccessorVariable()`
*/
EssaVariable getASuccessorVariable() { none() }
/** Holds if data cannot flow from `this` to `succ`,
/**
* Holds if data cannot flow from `this` to `succ`,
* even though it would normally do so.
*/
predicate prunedSuccessor(ControlFlowNode succ) { none() }
/** Gets a successor node, where the successor node will be tainted with `tokind`
/**
* Gets a successor node, where the successor node will be tainted with `tokind`
* when `this` is tainted with `fromkind`.
* Extensions to `DataFlowNode` should override this to provide additional taint steps.
*/
ControlFlowNode getASuccessorNode(TaintKind fromkind, TaintKind tokind) { none() }
/** Gets a successor node for data-flow with a change of context from callee to caller
/**
* Gets a successor node for data-flow with a change of context from callee to caller
* (going *up* the call-stack) across call-site `call`.
* Data (all forms) is assumed to flow from `this` to `result`
* Extensions to `DataFlowNode` should override this to provide additional taint steps.
*/
ControlFlowNode getAReturnSuccessorNode(CallNode call) { none() }
/** Gets a successor node for data-flow with a change of context from caller to callee
/**
* Gets a successor node for data-flow with a change of context from caller to callee
* (going *down* the call-stack) across call-site `call`.
* Data (all forms) is assumed to flow from `this` to `result`
* Extensions to `DataFlowNode` should override this to provide additional taint steps.
*/
ControlFlowNode getACalleeSuccessorNode(CallNode call) { none() }
}
/** Data flow variable that modifies the basic data-flow. */
class DataFlowVariable extends EssaVariable {
/** Gets a successor node for data-flow.
/**
* Gets a successor node for data-flow.
* Data (all forms) is assumed to flow from `this` to `result`
* Note: This is an unlikely form of flow. See `DataFlowNode.getASuccessorNode()`
*/
ControlFlowNode getASuccessorNode() { none() }
/** Gets a successor variable for data-flow.
/**
* Gets a successor variable for data-flow.
* Data (all forms) is assumed to flow from `this` to `result`.
*/
EssaVariable getASuccessorVariable() { none() }
/** Holds if data cannot flow from `this` to `succ`,
/**
* Holds if data cannot flow from `this` to `succ`,
* even though it would normally do so.
*/
predicate prunedSuccessor(EssaVariable succ) { none() }
}
}
class TaintedPathSource extends TaintTrackingNode {
TaintedPathSource() { this.isSource() }
TaintedPathSource() {
this.isSource()
}
DataFlow::Node getSource() {
result = this.getNode()
}
DataFlow::Node getSource() { result = this.getNode() }
}
class TaintedPathSink extends TaintTrackingNode {
TaintedPathSink() { this.isSink() }
TaintedPathSink() {
this.isSink()
}
DataFlow::Node getSink() {
result = this.getNode()
}
DataFlow::Node getSink() { result = this.getNode() }
}
/* Backwards compatible name */
class TaintedNode = TaintTrackingNode;
/* Helpers for Validating classes */
private import semmle.python.pointsto.PointsTo
/** Data flow module providing an interface compatible with
/**
* Data flow module providing an interface compatible with
* the other language implementations.
*/
module DataFlow {
/** Generic taint kind, source and sink classes for convenience and
/**
* Generic taint kind, source and sink classes for convenience and
* compatibility with other language libraries
*/
class Extension = DataFlowExtension::DataFlowNode;
deprecated abstract class Configuration extends string {
abstract deprecated class Configuration extends string {
bindingset[this]
Configuration() { this = this }
@@ -702,14 +657,10 @@ module DataFlow {
this.hasFlowPath(psource, psink)
)
}
}
private class ConfigurationAdapter extends TaintTracking::Configuration {
ConfigurationAdapter() {
this instanceof Configuration
}
ConfigurationAdapter() { this instanceof Configuration }
override predicate isSource(DataFlow::Node node, TaintKind kind) {
this.(Configuration).isSource(node.asCfgNode()) and
@@ -720,16 +671,13 @@ module DataFlow {
this.(Configuration).isSink(node.asCfgNode()) and
kind instanceof DataFlowType
}
}
private newtype TDataFlowNode =
TEssaNode(EssaVariable var)
or
TEssaNode(EssaVariable var) or
TCfgNode(ControlFlowNode node)
abstract class Node extends TDataFlowNode {
abstract ControlFlowNode asCfgNode();
abstract EssaVariable asVariable();
@@ -742,86 +690,51 @@ module DataFlow {
abstract Location getLocation();
AstNode asAstNode() {
result = this.asCfgNode().getNode()
}
AstNode asAstNode() { result = this.asCfgNode().getNode() }
/** For backwards compatibility -- Use asAstNode() instead */
deprecated AstNode getNode() {
result = this.asAstNode()
}
deprecated AstNode getNode() { result = this.asAstNode() }
}
class CfgNode extends Node, TCfgNode {
override ControlFlowNode asCfgNode() { this = TCfgNode(result) }
override ControlFlowNode asCfgNode() {
this = TCfgNode(result)
}
override EssaVariable asVariable() { none() }
override EssaVariable asVariable() {
none()
}
override string toString() { result = this.asAstNode().toString() }
override string toString() {
result = this.asAstNode().toString()
}
override Scope getScope() { result = this.asCfgNode().getScope() }
override Scope getScope() {
result = this.asCfgNode().getScope()
}
override BasicBlock getBasicBlock() {
result = this.asCfgNode().getBasicBlock()
}
override Location getLocation() {
result = this.asCfgNode().getLocation()
}
override BasicBlock getBasicBlock() { result = this.asCfgNode().getBasicBlock() }
override Location getLocation() { result = this.asCfgNode().getLocation() }
}
class EssaNode extends Node, TEssaNode {
override ControlFlowNode asCfgNode() { none() }
override ControlFlowNode asCfgNode() {
none()
}
override EssaVariable asVariable() { this = TEssaNode(result) }
override EssaVariable asVariable() {
this = TEssaNode(result)
}
override string toString() { result = this.asVariable().toString() }
override string toString() {
result = this.asVariable().toString()
}
override Scope getScope() {
result = this.asVariable().getScope()
}
override Scope getScope() { result = this.asVariable().getScope() }
override BasicBlock getBasicBlock() {
result = this.asVariable().getDefinition().getBasicBlock()
}
override Location getLocation() {
result = this.asVariable().getDefinition().getLocation()
}
override Location getLocation() { result = this.asVariable().getDefinition().getLocation() }
}
}
private class DataFlowType extends TaintKind {
DataFlowType() {
this = "Data flow" and
this = "Data flow" and
exists(DataFlow::Configuration c)
}
}
pragma [noinline]
pragma[noinline]
private predicate dict_construct(ControlFlowNode itemnode, ControlFlowNode dictnode) {
dictnode.(DictNode).getAValue() = itemnode
or
@@ -829,7 +742,7 @@ private predicate dict_construct(ControlFlowNode itemnode, ControlFlowNode dictn
dictnode.(CallNode).getArgByName(_) = itemnode
}
pragma [noinline]
pragma[noinline]
private predicate sequence_construct(ControlFlowNode itemnode, ControlFlowNode seqnode) {
seqnode.isLoad() and
(
@@ -841,13 +754,11 @@ private predicate sequence_construct(ControlFlowNode itemnode, ControlFlowNode s
)
}
/* A call to construct a sequence from a sequence or iterator*/
pragma [noinline]
pragma[noinline]
private predicate sequence_call(ControlFlowNode fromnode, CallNode tonode) {
tonode.getArg(0) = fromnode and
exists(ControlFlowNode cls |
cls = tonode.getFunction() |
exists(ControlFlowNode cls | cls = tonode.getFunction() |
cls.pointsTo(ObjectInternal::builtin("list"))
or
cls.pointsTo(ObjectInternal::builtin("tuple"))
@@ -855,4 +766,3 @@ private predicate sequence_call(ControlFlowNode fromnode, CallNode tonode) {
cls.pointsTo(ObjectInternal::builtin("set"))
)
}

View File

@@ -37,7 +37,7 @@ class EssaVariable extends TEssaDefinition {
result = "SSA variable " + this.getName()
}
/** Gets a string representation of this variable.
/** Gets a string representation of this variable.
* WARNING: The format of this may change and it may be very inefficient to compute.
* To used for debugging and testing only.
*/
@@ -77,7 +77,7 @@ class EssaVariable extends TEssaDefinition {
}
/* Helper for location_string
/* Helper for location_string
* NOTE: This is Python specific, to make `getRepresentation()` portable will require further work.
*/
private int exception_handling(BasicBlock b) {
@@ -161,10 +161,10 @@ abstract class EssaDefinition extends TEssaDefinition {
abstract predicate reachesEndOfBlock(BasicBlock b);
/** Gets the location of a control flow node that is indicative of this definition.
* Since definitions may occur on edges of the control flow graph, the given location may
* Since definitions may occur on edges of the control flow graph, the given location may
* be imprecise.
* Distinct `EssaDefinitions` may return the same ControlFlowNode even for
* the same variable.
* the same variable.
*/
abstract Location getLocation();
@@ -182,9 +182,9 @@ abstract class EssaDefinition extends TEssaDefinition {
}
/** An ESSA definition corresponding to an edge refinement of the underlying variable.
/** An ESSA definition corresponding to an edge refinement of the underlying variable.
* For example, the edges leaving a test on a variable both represent refinements of that
* variable. On one edge the test is true, on the other it is false.
* variable. On one edge the test is true, on the other it is false.
*/
class EssaEdgeRefinement extends EssaDefinition, TEssaEdgeDefinition {
@@ -361,7 +361,7 @@ class PhiFunction extends EssaDefinition, TPhiFunction {
}
private EssaEdgeRefinement piInputDefinition(EssaVariable input) {
input = this.getAnInput() and
input = this.getAnInput() and
result = input.getDefinition()
or
input = this.getAnInput() and result = input.getDefinition().(PhiFunction).piInputDefinition(_)
@@ -864,4 +864,3 @@ class PyEdgeRefinement extends EssaEdgeRefinement {
}
}

View File

@@ -1,4 +1,4 @@
/** Provides classes and predicates for determining the uses and definitions of
/** Provides classes and predicates for determining the uses and definitions of
* variables for ESSA form.
*/
@@ -30,7 +30,7 @@ cached module SsaSource {
/** Holds if `v` is defined by a with statement. */
cached predicate with_definition(Variable v, ControlFlowNode defn) {
exists(With with, Name var |
exists(With with, Name var |
with.getOptionalVars() = var and
var.getAFlowNode() = defn |
var = v.getAStore()
@@ -39,7 +39,11 @@ cached module SsaSource {
/** Holds if `v` is defined by multiple assignment at `defn`. */
cached predicate multi_assignment_definition(Variable v, ControlFlowNode defn, int n, SequenceNode lhs) {
defn.(NameNode).defines(v) and
(
defn.(NameNode).defines(v)
or
defn.(StarredNode).getValue().(NameNode).defines(v)
) and
not exists(defn.(DefinitionNode).getValue()) and
lhs.getElement(n) = defn and
lhs.getBasicBlock().dominates(defn.getBasicBlock())

View File

@@ -78,7 +78,7 @@ class Value extends TObject {
predicate isBuiltin() {
this.(ObjectInternal).isBuiltin()
}
predicate hasLocationInfo(string filepath, int bl, int bc, int el, int ec) {
this.(ObjectInternal).getOrigin().getLocation().hasLocationInfo(filepath, bl, bc, el, ec)
or
@@ -118,10 +118,20 @@ class Value extends TObject {
)
}
/** Gets the boolean value of this value. */
boolean booleanValue() {
/** Gets the boolean interpretation of this value.
* Could be both `true` and `false`, if we can't determine the result more precisely.
*/
boolean getABooleanValue() {
result = this.(ObjectInternal).booleanValue()
}
/** Gets the boolean interpretation of this value, only if we can determine the result precisely.
* The result can be `none()`, but never both `true` and `false`.
*/
boolean getDefiniteBooleanValue() {
result = getABooleanValue() and
not (getABooleanValue() = true and getABooleanValue() = false)
}
}
/** Class representing modules in the Python program
@@ -241,14 +251,14 @@ module Value {
name = "False" and result = TFalse()
}
/** Gets the `Value` for the integer constant `i`, if it exists.
* There will be no `Value` for most integers, but the following are
/** Gets the `NumericValue` for the integer constant `i`, if it exists.
* There will be no `NumericValue` for most integers, but the following are
* guaranteed to exist:
* * From zero to 511 inclusive.
* * All powers of 2 (up to 2**30)
* * Any integer explicitly mentioned in the source program.
*/
Value forInt(int i) {
NumericValue forInt(int i) {
result.(IntObjectInternal).intValue() = i
}
@@ -256,7 +266,7 @@ module Value {
* There will be no `Value` for most byte strings, unless it is explicitly
* declared in the source program.
*/
Value forBytes(string bytes) {
StringValue forBytes(string bytes) {
result.(BytesObjectInternal).strValue() = bytes
}
@@ -264,7 +274,7 @@ module Value {
* There will be no `Value` for most text strings, unless it is explicitly
* declared in the source program.
*/
Value forUnicode(string text) {
StringValue forUnicode(string text) {
result.(UnicodeObjectInternal).strValue() = text
}
@@ -272,7 +282,7 @@ module Value {
* There will be no `Value` for most strings, unless it is explicitly
* declared in the source program.
*/
Value forString(string text) {
StringValue forString(string text) {
result.(UnicodeObjectInternal).strValue() = text
or
major_version() = 2 and
@@ -471,6 +481,15 @@ class ClassValue extends Value {
predicate declaresAttribute(string name) {
this.(ClassObjectInternal).getClassDeclaration().declaresAttribute(name)
}
/** Whether this class is a legal exception class.
* What constitutes a legal exception class differs between major versions */
predicate isLegalExceptionType() {
not this.isNewStyle() or
this.getASuperType() = ClassValue::baseException()
or
major_version() = 2 and this = ClassValue::tuple()
}
}
@@ -495,6 +514,28 @@ abstract class FunctionValue extends CallableValue {
predicate isOverriddenMethod() {
exists(Value f | f.overrides(this))
}
/** Whether `name` is a legal argument name for this function */
bindingset[name]
predicate isLegalArgumentName(string name) {
this.getScope().getAnArg().asName().getId() = name
or
this.getScope().getAKeywordOnlyArg().getId() = name
or
this.getScope().hasKwArg()
}
/** Whether this is a "normal" method, that is, it is exists as a class attribute
* which is not a lambda and not the __new__ method. */
predicate isNormalMethod() {
exists(ClassValue cls, string name |
cls.declaredAttribute(name) = this and
name != "__new__" and
exists(Expr expr, AstNode origin | expr.pointsTo(this, origin) |
not origin instanceof Lambda
)
)
}
}
/** Class representing Python functions */
@@ -620,6 +661,26 @@ class StringValue extends Value {
}
}
/** A class representing numbers (ints and floats), either present in the source as a literal,
* or in a builtin as a value.
*/
class NumericValue extends Value {
NumericValue() {
this instanceof IntObjectInternal or
this instanceof FloatObjectInternal
}
/** Gets the integer-value if it is a constant integer, and it fits in a QL int */
int getIntValue() {
result = this.(IntObjectInternal).intValue()
}
/** Gets the float-value if it is a constant float */
int getFloatValue() {
result = this.(FloatObjectInternal).floatValue()
}
}
/** A method-resolution-order sequence of classes */
class MRO extends TClassList {
@@ -668,40 +729,57 @@ module ClassValue {
ClassValue bool() {
result = TBuiltinClassObject(Builtin::special("bool"))
}
/** Get the `ClassValue` for the `tuple` class. */
ClassValue tuple() {
result = TBuiltinClassObject(Builtin::special("tuple"))
}
/** Get the `ClassValue` for the `list` class. */
ClassValue list() {
result = TBuiltinClassObject(Builtin::special("list"))
}
/** Get the `ClassValue` for `xrange` (Python 2), or `range` (only Python 3) */
ClassValue range() {
major_version() = 2 and result = TBuiltinClassObject(Builtin::special("xrange"))
or
major_version() = 3 and result = TBuiltinClassObject(Builtin::special("range"))
}
/** Get the `ClassValue` for the `dict` class. */
ClassValue dict() {
result = TBuiltinClassObject(Builtin::special("dict"))
}
/** Get the `ClassValue` for the class of Python functions. */
ClassValue function() {
result = TBuiltinClassObject(Builtin::special("FunctionType"))
/** Get the `ClassValue` for the `set` class. */
ClassValue set() {
result = TBuiltinClassObject(Builtin::special("set"))
}
/** Get the `ClassValue` for the `type` class. */
ClassValue type() {
result = TType()
}
/** Get the `ClassValue` for the class of builtin functions. */
ClassValue builtinFunction() {
result = Value::named("len").getClass()
/** Get the `ClassValue` for the `object` class. */
ClassValue object() {
result = TBuiltinClassObject(Builtin::special("object"))
}
/** Get the `ClassValue` for the `int` class. */
ClassValue int_() {
result = TBuiltinClassObject(Builtin::special("int"))
}
/** Get the `ClassValue` for the `long` class. */
ClassValue long() {
result = TBuiltinClassObject(Builtin::special("long"))
}
/** Get the `ClassValue` for the `float` class. */
ClassValue float_() {
result = TBuiltinClassObject(Builtin::special("float"))
}
/** Get the `ClassValue` for the `list` class. */
ClassValue list() {
result = TBuiltinClassObject(Builtin::special("list"))
/** Get the `ClassValue` for the `complex` class. */
ClassValue complex() {
result = TBuiltinClassObject(Builtin::special("complex"))
}
/** Get the `ClassValue` for the `bytes` class (also called `str` in Python 2). */
@@ -723,7 +801,47 @@ module ClassValue {
else
result = unicode()
}
/** Get the `ClassValue` for the `property` class. */
ClassValue property() {
result = TBuiltinClassObject(Builtin::special("property"))
}
/** Get the `ClassValue` for the class of Python functions. */
ClassValue functionType() {
result = TBuiltinClassObject(Builtin::special("FunctionType"))
}
/** Get the `ClassValue` for the class of builtin functions. */
ClassValue builtinFunction() {
result = Value::named("len").getClass()
}
/** Get the `ClassValue` for the `generatorType` class. */
ClassValue generator() {
result = TBuiltinClassObject(Builtin::special("generator"))
}
/** Get the `ClassValue` for the `type` class. */
ClassValue type() {
result = TType()
}
/** Get the `ClassValue` for `ClassType`. */
ClassValue classType() {
result = TBuiltinClassObject(Builtin::special("ClassType"))
}
/** Get the `ClassValue` for `InstanceType`. */
ClassValue instanceType() {
result = TBuiltinClassObject(Builtin::special("InstanceType"))
}
/** Get the `ClassValue` for `super`. */
ClassValue super_() {
result = TBuiltinClassObject(Builtin::special("super"))
}
/** Get the `ClassValue` for the `classmethod` class. */
ClassValue classmethod() {
result = TBuiltinClassObject(Builtin::special("ClassMethod"))
@@ -733,20 +851,96 @@ module ClassValue {
ClassValue staticmethod() {
result = TBuiltinClassObject(Builtin::special("StaticMethod"))
}
/** Get the `ClassValue` for the `MethodType` class. */
pragma [noinline]
ClassValue methodType() {
result = TBuiltinClassObject(Builtin::special("MethodType"))
}
/** Get the `ClassValue` for the `MethodDescriptorType` class. */
ClassValue methodDescriptorType() {
result = TBuiltinClassObject(Builtin::special("MethodDescriptorType"))
}
/** Get the `ClassValue` for the `GetSetDescriptorType` class. */
ClassValue getSetDescriptorType() {
result = TBuiltinClassObject(Builtin::special("GetSetDescriptorType"))
}
/** Get the `ClassValue` for the `StopIteration` class. */
ClassValue stopIteration() {
result = TBuiltinClassObject(Builtin::builtin("StopIteration"))
}
/** Get the `ClassValue` for the class of modules. */
ClassValue module_() {
result = TBuiltinClassObject(Builtin::special("ModuleType"))
}
/** Get the `ClassValue` for the `Exception` class. */
ClassValue exception() {
result = TBuiltinClassObject(Builtin::special("Exception"))
}
/** Get the `ClassValue` for the `BaseException` class. */
ClassValue baseException() {
result = TBuiltinClassObject(Builtin::special("BaseException"))
}
/** Get the `ClassValue` for the `NoneType` class. */
ClassValue nonetype() {
result = TBuiltinClassObject(Builtin::special("NoneType"))
}
/** Get the `ClassValue` for the `TypeError` class */
ClassValue typeError() {
result = TBuiltinClassObject(Builtin::special("TypeError"))
}
/** Get the `ClassValue` for the `NameError` class. */
ClassValue nameError() {
result = TBuiltinClassObject(Builtin::builtin("NameError"))
}
/** Get the `ClassValue` for the `AttributeError` class. */
ClassValue attributeError() {
result = TBuiltinClassObject(Builtin::builtin("AttributeError"))
}
/** Get the `ClassValue` for the `KeyError` class. */
ClassValue keyError() {
result = TBuiltinClassObject(Builtin::builtin("KeyError"))
}
/** Get the `ClassValue` for the `LookupError` class. */
ClassValue lookupError() {
result = TBuiltinClassObject(Builtin::builtin("LookupError"))
}
/** Get the `ClassValue` for the `IOError` class. */
ClassValue ioError() {
result = TBuiltinClassObject(Builtin::builtin("IOError"))
}
/** Get the `ClassValue` for the `NotImplementedError` class. */
ClassValue notImplementedError() {
result = TBuiltinClassObject(Builtin::builtin("NotImplementedError"))
}
/** Get the `ClassValue` for the `ImportError` class. */
ClassValue importError() {
result = TBuiltinClassObject(Builtin::builtin("ImportError"))
}
/** Get the `ClassValue` for the `UnicodeEncodeError` class. */
ClassValue unicodeEncodeError() {
result = TBuiltinClassObject(Builtin::builtin("UnicodeEncodeError"))
}
/** Get the `ClassValue` for the `UnicodeDecodeError` class. */
ClassValue unicodeDecodeError() {
result = TBuiltinClassObject(Builtin::builtin("UnicodeDecodeError"))
}
}

View File

@@ -12,25 +12,27 @@ private predicate re_module_function(string name, int flags) {
name = "subn" and flags = 4
}
/**
* Holds if `s` is used as a regex with the `re` module, with the regex-mode `mode` (if known).
* If regex mode is not known, `mode` will be `"None"`.
*/
predicate used_as_regex(Expr s, string mode) {
(s instanceof Bytes or s instanceof Unicode)
and
exists(ModuleValue re | re.getName() = "re" |
/* Call to re.xxx(regex, ... [mode]) */
exists(CallNode call, string name |
call.getArg(0).refersTo(_, _, s.getAFlowNode()) and
call.getFunction().pointsTo(re.attr(name)) |
mode = "None"
or
exists(Value obj |
mode = mode_from_mode_object(obj) |
exists(int flags_arg |
re_module_function(name, flags_arg) and
call.getArg(flags_arg).pointsTo(obj)
)
or
call.getArgByName("flags").pointsTo(obj)
/* Call to re.xxx(regex, ... [mode]) */
exists(CallNode call, string name |
call.getArg(0).refersTo(_, _, s.getAFlowNode()) and
call.getFunction().pointsTo(Module::named("re").attr(name)) |
mode = "None"
or
exists(Value obj |
mode = mode_from_mode_object(obj) |
exists(int flags_arg |
re_module_function(name, flags_arg) and
call.getArg(flags_arg).pointsTo(obj)
)
or
call.getArgByName("flags").pointsTo(obj)
)
)
}

View File

@@ -2,45 +2,40 @@ import python
import Basic
private import Common
/** An extensible kind of taint representing an externally controlled string.
/**
* An extensible kind of taint representing an externally controlled string.
*/
abstract class ExternalStringKind extends StringKind {
bindingset[this]
ExternalStringKind() {
this = this
}
ExternalStringKind() { this = this }
override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) {
result = StringKind.super.getTaintForFlowStep(fromnode, tonode)
or
tonode.(SequenceNode).getElement(_) = fromnode and result.(ExternalStringSequenceKind).getItem() = this
tonode.(SequenceNode).getElement(_) = fromnode and
result.(ExternalStringSequenceKind).getItem() = this
or
json_load(fromnode, tonode) and result.(ExternalJsonKind).getValue() = this
or
tonode.(DictNode).getAValue() = fromnode and result.(ExternalStringDictKind).getValue() = this
or
urlsplit(fromnode, tonode) and result.(ExternalUrlSplitResult).getItem() = this
or
urlparse(fromnode, tonode) and result.(ExternalUrlParseResult).getItem() = this
}
}
/** A kind of "taint", representing a sequence, with a "taint" member */
class ExternalStringSequenceKind extends SequenceKind {
ExternalStringSequenceKind() {
this.getItem() instanceof ExternalStringKind
}
ExternalStringSequenceKind() { this.getItem() instanceof ExternalStringKind }
}
/** An hierachical dictionary or list where the entire structure is externally controlled
/**
* An hierachical dictionary or list where the entire structure is externally controlled
* This is typically a parsed JSON object.
*/
class ExternalJsonKind extends TaintKind {
ExternalJsonKind() {
this = "json[" + any(ExternalStringKind key) + "]"
}
ExternalJsonKind() { this = "json[" + any(ExternalStringKind key) + "]" }
/** Gets the taint kind for item in this sequence */
TaintKind getValue() {
@@ -54,65 +49,225 @@ class ExternalJsonKind extends TaintKind {
json_subscript_taint(tonode, fromnode, this, result)
or
result = this and copy_call(fromnode, tonode)
}
}
override TaintKind getTaintOfMethodResult(string name) {
name = "get" and result = this.getValue()
}
}
}
/** A kind of "taint", representing a dictionary mapping str->"taint" */
class ExternalStringDictKind extends DictKind {
ExternalStringDictKind() {
this.getValue() instanceof ExternalStringKind
}
ExternalStringDictKind() { this.getValue() instanceof ExternalStringKind }
}
/** A kind of "taint", representing a dictionary mapping strings to sequences of
* tainted strings */
/**
* A kind of "taint", representing a dictionary mapping strings to sequences of
* tainted strings
*/
class ExternalStringSequenceDictKind extends DictKind {
ExternalStringSequenceDictKind() {
this.getValue() instanceof ExternalStringSequenceKind
ExternalStringSequenceDictKind() { this.getValue() instanceof ExternalStringSequenceKind }
}
/** TaintKind for the result of `urlsplit(tainted_string)` */
class ExternalUrlSplitResult extends ExternalStringSequenceKind {
// https://docs.python.org/3/library/urllib.parse.html#urllib.parse.urlsplit
override TaintKind getTaintOfAttribute(string name) {
result = super.getTaintOfAttribute(name)
or
(
// namedtuple field names
name = "scheme" or
name = "netloc" or
name = "path" or
name = "query" or
name = "fragment" or
// class methods
name = "username" or
name = "password" or
name = "hostname"
) and
result instanceof ExternalStringKind
}
override TaintKind getTaintOfMethodResult(string name) {
result = super.getTaintOfMethodResult(name)
or
name = "geturl" and
result instanceof ExternalStringKind
}
}
/** TaintKind for the result of `urlparse(tainted_string)` */
class ExternalUrlParseResult extends ExternalStringSequenceKind {
// https://docs.python.org/3/library/urllib.parse.html#urllib.parse.urlparse
override TaintKind getTaintOfAttribute(string name) {
result = super.getTaintOfAttribute(name)
or
(
// namedtuple field names
name = "scheme" or
name = "netloc" or
name = "path" or
name = "params" or
name = "query" or
name = "fragment" or
// class methods
name = "username" or
name = "password" or
name = "hostname"
) and
result instanceof ExternalStringKind
}
override TaintKind getTaintOfMethodResult(string name) {
result = super.getTaintOfMethodResult(name)
or
name = "geturl" and
result instanceof ExternalStringKind
}
}
/* Helper for getTaintForStep() */
pragma [noinline]
private predicate json_subscript_taint(SubscriptNode sub, ControlFlowNode obj, ExternalJsonKind seq, TaintKind key) {
pragma[noinline]
private predicate json_subscript_taint(
SubscriptNode sub, ControlFlowNode obj, ExternalJsonKind seq, TaintKind key
) {
sub.isLoad() and
sub.getValue() = obj and
key = seq.getValue()
}
private predicate json_load(ControlFlowNode fromnode, CallNode tonode) {
exists(FunctionObject json_loads |
ModuleObject::named("json").attr("loads") = json_loads and
json_loads.getACall() = tonode and tonode.getArg(0) = fromnode
json_loads.getACall() = tonode and
tonode.getArg(0) = fromnode
)
}
private predicate urlsplit(ControlFlowNode fromnode, CallNode tonode) {
// This could be implemented as `exists(FunctionValue` without the explicit six part,
// but then our tests will need to import +100 modules, so for now this slightly
// altered version gets to live on.
exists(Value urlsplit |
(
urlsplit = Value::named("six.moves.urllib.parse.urlsplit")
or
// Python 2
urlsplit = Value::named("urlparse.urlsplit")
or
// Python 3
urlsplit = Value::named("urllib.parse.urlsplit")
) and
tonode = urlsplit.getACall() and
tonode.getArg(0) = fromnode
)
}
private predicate urlparse(ControlFlowNode fromnode, CallNode tonode) {
// This could be implemented as `exists(FunctionValue` without the explicit six part,
// but then our tests will need to import +100 modules, so for now this slightly
// altered version gets to live on.
exists(Value urlparse |
(
urlparse = Value::named("six.moves.urllib.parse.urlparse")
or
// Python 2
urlparse = Value::named("urlparse.urlparse")
or
// Python 3
urlparse = Value::named("urllib.parse.urlparse")
) and
tonode = urlparse.getACall() and
tonode.getArg(0) = fromnode
)
}
/** A kind of "taint", representing an open file-like object from an external source. */
class ExternalFileObject extends TaintKind {
ExternalFileObject() {
this = "file[" + any(ExternalStringKind key) + "]"
}
ExternalFileObject() { this = "file[" + any(ExternalStringKind key) + "]" }
/** Gets the taint kind for the contents of this file */
TaintKind getValue() {
this = "file[" + result + "]"
}
TaintKind getValue() { this = "file[" + result + "]" }
override TaintKind getTaintOfMethodResult(string name) {
name = "read" and result = this.getValue()
}
}
/**
* Temporary sanitizer for the tainted result from `urlsplit` and `urlparse`. Can be used to reduce FPs until
* we have better support for namedtuples.
*
* Will clear **all** taint on a test of the kind. That is, on the true edge of any matching test,
* all fields/indexes will be cleared of taint.
*
* Handles:
* - `if splitres.netloc == "KNOWN_VALUE"`
* - `if splitres[0] == "KNOWN_VALUE"`
*/
class UrlsplitUrlparseTempSanitizer extends Sanitizer {
// TODO: remove this once we have better support for named tuples
UrlsplitUrlparseTempSanitizer() { this = "UrlsplitUrlparseTempSanitizer" }
override predicate sanitizingEdge(TaintKind taint, PyEdgeRefinement test) {
(
taint instanceof ExternalUrlSplitResult
or
taint instanceof ExternalUrlParseResult
) and
exists(ControlFlowNode full_use |
full_use.(SubscriptNode).getObject() = test.getInput().getAUse()
or
full_use.(AttrNode).getObject() = test.getInput().getAUse()
|
clears_taint(full_use, test.getTest(), test.getSense())
)
}
private predicate clears_taint(ControlFlowNode tainted, ControlFlowNode test, boolean sense) {
test_equality_with_const(test, tainted, sense)
or
test_in_const_seq(test, tainted, sense)
or
test.(UnaryExprNode).getNode().getOp() instanceof Not and
exists(ControlFlowNode nested_test |
nested_test = test.(UnaryExprNode).getOperand() and
clears_taint(tainted, nested_test, sense.booleanNot())
)
}
/** holds for `== "KNOWN_VALUE"` on `true` edge, and `!= "KNOWN_VALUE"` on `false` edge */
private predicate test_equality_with_const(CompareNode cmp, ControlFlowNode tainted, boolean sense) {
exists(ControlFlowNode const, Cmpop op |
const.getNode() instanceof StrConst
|
(
cmp.operands(const, op, tainted)
or
cmp.operands(tainted, op, const)
) and
(
op instanceof Eq and sense = true
or
op instanceof NotEq and sense = false
)
)
}
/** holds for `in ["KNOWN_VALUE", ...]` on `true` edge, and `not in ["KNOWN_VALUE", ...]` on `false` edge */
private predicate test_in_const_seq(CompareNode cmp, ControlFlowNode tainted, boolean sense) {
exists(SequenceNode const_seq, Cmpop op |
forall(ControlFlowNode elem | elem = const_seq.getAnElement() | elem.getNode() instanceof StrConst)
|
cmp.operands(tainted, op, const_seq) and
(
op instanceof In and sense = true
or
op instanceof NotIn and sense = false
)
)
}
}

View File

@@ -0,0 +1,2 @@
import semmle.python.web.client.StdLib
import semmle.python.web.client.Requests

View File

@@ -89,7 +89,7 @@ abstract class CookieSet extends CookieOperation {}
/** Generic taint sink in a http response */
abstract class HttpResponseTaintSink extends TaintSink {
override predicate sinks(TaintKind kind) {
override predicate sinks(TaintKind kind) {
kind instanceof ExternalStringKind
}
@@ -97,9 +97,51 @@ abstract class HttpResponseTaintSink extends TaintSink {
abstract class HttpRedirectTaintSink extends TaintSink {
override predicate sinks(TaintKind kind) {
override predicate sinks(TaintKind kind) {
kind instanceof ExternalStringKind
}
}
module Client {
// TODO: user-input in other than URL:
// - `data`, `json` for `requests.post`
// - `body` for `HTTPConnection.request`
// - headers?
// TODO: Add more library support
// - urllib3 https://github.com/urllib3/urllib3
// - httpx https://github.com/encode/httpx
/**
* An outgoing http request
*
* For example:
* conn = HTTPConnection('example.com')
conn.request('GET', '/path')
*/
abstract class HttpRequest extends ControlFlowNode {
/** Get any ControlFlowNode that is used to construct the final URL.
*
* In the HTTPConnection example, there is a result for both `'example.com'` and for `'/path'`.
*/
abstract ControlFlowNode getAUrlPart();
abstract string getMethodUpper();
}
/** Taint sink for the URL-part of an outgoing http request */
class HttpRequestUrlTaintSink extends TaintSink {
HttpRequestUrlTaintSink() {
this = any(HttpRequest r).getAUrlPart()
}
override predicate sinks(TaintKind kind) {
kind instanceof ExternalStringKind
}
}
}

View File

@@ -0,0 +1,22 @@
/**
* Modeling outgoing HTTP requests using the `requests` package
* https://pypi.org/project/requests/
*/
import python
private import semmle.python.web.Http
class RequestsHttpRequest extends Client::HttpRequest, CallNode {
CallableValue func;
string method;
RequestsHttpRequest() {
method = httpVerbLower() and
func = Module::named("requests").attr(method) and
this = func.getACall()
}
override ControlFlowNode getAUrlPart() { result = func.getNamedArgumentForCall(this, "url") }
override string getMethodUpper() { result = method.toUpperCase() }
}

View File

@@ -0,0 +1,55 @@
import python
private import semmle.python.web.Http
ClassValue httpConnectionClass() {
// Python 2
result = Value::named("httplib.HTTPConnection")
or
result = Value::named("httplib.HTTPSConnection")
or
// Python 3
result = Value::named("http.client.HTTPConnection")
or
result = Value::named("http.client.HTTPSConnection")
or
// six
result = Value::named("six.moves.http_client.HTTPConnection")
or
result = Value::named("six.moves.http_client.HTTPSConnection")
}
class HttpConnectionHttpRequest extends Client::HttpRequest, CallNode {
CallNode constructor_call;
CallableValue func;
HttpConnectionHttpRequest() {
exists(ClassValue cls, AttrNode call_origin, Value constructor_call_value |
cls = httpConnectionClass() and
func = cls.lookup("request") and
this = func.getACall() and
// since you can do `r = conn.request; r('GET', path)`, we need to find the origin
this.getFunction().pointsTo(_, _, call_origin) and
// Since HTTPSConnection is a subtype of HTTPConnection, up until this point, `cls` could be either class,
// because `HTTPSConnection.request == HTTPConnection.request`. To avoid generating 2 results, we filter
// on the actual class used as the constructor
call_origin.getObject().pointsTo(_, constructor_call_value, constructor_call) and
cls = constructor_call_value.getClass() and
constructor_call = cls.getACall()
)
}
override ControlFlowNode getAUrlPart() {
result = func.getNamedArgumentForCall(this, "url")
or
result = constructor_call.getArg(0)
or
result = constructor_call.getArgByName("host")
}
override string getMethodUpper() {
exists(string method |
result = method.toUpperCase() and
func.getNamedArgumentForCall(this, "method").pointsTo(Value::forString(method))
)
}
}

View File

@@ -1,2 +1 @@
semmle-extractor-options: --lang=2 --max-import-depth=4
optimize: true

View File

@@ -1,2 +1,2 @@
| TruncatedDivision_test.py:8:12:8:16 | BinaryExpr | Result of division may be truncated as its $@ and $@ arguments may both be integers. | TruncatedDivision_test.py:8:12:8:12 | TruncatedDivision_test.py:8 | left | TruncatedDivision_test.py:8:16:8:16 | TruncatedDivision_test.py:8 | right |
| TruncatedDivision_test.py:11:12:11:40 | BinaryExpr | Result of division may be truncated as its $@ and $@ arguments may both be integers. | TruncatedDivision_test.py:2:12:2:12 | TruncatedDivision_test.py:2 | left | TruncatedDivision_test.py:5:12:5:12 | TruncatedDivision_test.py:5 | right |
| TruncatedDivision_test.py:65:7:65:11 | BinaryExpr | Result of division may be truncated as its $@ and $@ arguments may both be integers. | TruncatedDivision_test.py:65:7:65:7 | TruncatedDivision_test.py:65 | left | TruncatedDivision_test.py:65:11:65:11 | TruncatedDivision_test.py:65 | right |
| TruncatedDivision_test.py:72:7:72:35 | BinaryExpr | Result of division may be truncated as its $@ and $@ arguments may both be integers. | TruncatedDivision_test.py:25:12:25:12 | TruncatedDivision_test.py:25 | left | TruncatedDivision_test.py:28:12:28:12 | TruncatedDivision_test.py:28 | right |

View File

@@ -1,24 +1,88 @@
#### TruncatedDivision.ql
# NOTE: The following test case will only work under Python 2.
# Truncated division occurs when two integers are divided. This causes the
# fractional part, if there is one, to be discared. So for example, `2 / 3` will
# evaluate to `0` instead of `0.666...`.
## Negative Cases
# This case is good, and is a minimal obvious case that should be good. It
# SHOULD NOT be found by the query.
print(3.0 / 2.0)
# This case is good, because it explicitly converts the possibly-truncated
# value to an integer. It SHOULD NOT be found by the query.
def return_three():
return 3
def return_two():
return 2
def f1():
return 3 / 2
print(int(return_three() / return_two()))
def f2():
return return_three() / return_two()
def f3(x):
# These cases are good, because `halve` checks the type, and if the type would
# truncate, it explicitly converts to a float first before doing the division.
# These SHOULD NOT be found by the query.
def halve(x):
if isinstance(x, float):
return x / 2
else:
return (1.0 * x) / 2
def f4():
do_stuff(f3(1))
do_stuff(f3(1.0))
print(halve(1))
print(halve(1.0))
def f5():
return int(return_three() / return_two())
# This case is good, because the sum is `3.0`, which is a float, and will not
# truncate. This case SHOULD NOT be found by the query.
print(average([1.0, 2.0]))
## Positive Cases
# This case is bad, and is a minimal obvious case that should be bad. It
# SHOULD be found by the query.
print(3 / 2)
# This case is bad. It uses indirect returns of integers through function calls
# to produce the problem. I
print(return_three() / return_two())
# This case is bad, because the sum is `3`, which is an integer, and will
# truncate when divided by the length `2`. This case SHOULD be found by the
# query.
# NOTE (2020-02-20):
# The current version of the Value/pointsTo API doesn't permit this detection,
# unfortunately, but we preserve this example in the hopes that future
# versions will catch it. That will necessitate changing the expected results.
def average(l):
return sum(l) / len(l)
print(average([1,2]))

View File

@@ -1 +1,2 @@
| UseofApply.py:19:3:19:17 | ControlFlowNode for apply() | Call to the obsolete builtin function 'apply'. |
| expressions_test.py:3:5:3:21 | ControlFlowNode for apply() | Call to the obsolete builtin function 'apply'. |

View File

@@ -0,0 +1,30 @@
#### UseofApply.ql
# Use of the builtin function `apply` is generally considered bad now that the
# ability to destructure lists of arguments is possible, but we should not flag
# cases where the function is merely named `apply` rather than being the actual
# builtin `apply` function.
def useofapply():
def foo():
pass
# Positive Cases
# This use of `apply` is a reference to the builtin function and so SHOULD be
# caught by the query.
apply(foo, [1])
# Negative Cases
# This use of `apply` is a reference to the locally defined function inside of
# `local`, and so SHOULD NOT be caught by the query.
def local():
def apply(f):
pass
apply(foo)([1])

View File

@@ -1 +1 @@
| expressions_test.py:6:12:6:18 | ControlFlowNode for input() | The unsafe built-in function 'input' is used. |
| expressions_test.py:6:12:6:18 | ControlFlowNode for input() | The unsafe built-in function 'input' is used in Python 2. |

View File

@@ -1,2 +1 @@
semmle-extractor-options: --lang=3 -p ../../lib/ --max-import-depth=3
optimize: true

View File

@@ -1,2 +1 @@
semmle-extractor-options: --lang=3 --max-import-depth=4
optimize: true

View File

@@ -0,0 +1,27 @@
import python
import semmle.python.security.TaintTracking
import semmle.python.security.strings.Untrusted
class SimpleSource extends TaintSource {
SimpleSource() { this.(NameNode).getId() = "TAINTED_STRING" }
override predicate isSourceOf(TaintKind kind) { kind instanceof ExternalStringKind }
override string toString() { result = "taint source" }
}
class ListSource extends TaintSource {
ListSource() { this.(NameNode).getId() = "TAINTED_LIST" }
override predicate isSourceOf(TaintKind kind) { kind instanceof ExternalStringSequenceKind }
override string toString() { result = "list taint source" }
}
class DictSource extends TaintSource {
DictSource() { this.(NameNode).getId() = "TAINTED_DICT" }
override predicate isSourceOf(TaintKind kind) { kind instanceof ExternalStringDictKind }
override string toString() { result = "dict taint source" }
}

View File

@@ -0,0 +1,9 @@
| test.py:11 | extended_unpacking | first | NO TAINT |
| test.py:11 | extended_unpacking | last | NO TAINT |
| test.py:11 | extended_unpacking | rest | NO TAINT |
| test.py:16 | also_allowed | a | NO TAINT |
| test.py:24 | also_allowed | b | NO TAINT |
| test.py:24 | also_allowed | c | NO TAINT |
| test.py:31 | nested | x | NO TAINT |
| test.py:31 | nested | xs | NO TAINT |
| test.py:31 | nested | ys | NO TAINT |

View File

@@ -0,0 +1,18 @@
import python
import semmle.python.security.TaintTracking
import Taint
from Call call, Expr arg, string taint_string
where
call.getLocation().getFile().getShortName() = "test.py" and
call.getFunc().(Name).getId() = "test" and
arg = call.getAnArg() and
(
not exists(TaintedNode tainted | tainted.getAstNode() = arg) and
taint_string = "NO TAINT"
or
exists(TaintedNode tainted | tainted.getAstNode() = arg |
taint_string = tainted.getTaintKind().toString()
)
)
select arg.getLocation().toString(), call.getScope().(Function).getName(), arg.toString(), taint_string

View File

@@ -0,0 +1,31 @@
# Extended Iterable Unpacking -- PEP 3132
# https://www.python.org/dev/peps/pep-3132/
def test(*args):
pass
def extended_unpacking():
first, *rest, last = TAINTED_LIST
test(first, rest, last)
def also_allowed():
*a, = TAINTED_LIST
test(a)
# for b, *c in [(1, 2, 3), (4, 5, 6, 7)]:
# print(c)
# i=0; c=[2,3]
# i=1; c=[5,6,7]
for b, *c in [TAINTED_LIST, TAINTED_LIST]:
test(b, c) # TODO: mark `c` as [taint]
def nested():
l = TAINTED_LIST
ll = [l,l]
[[x, *xs], ys] = ll
test(x, xs, ys)

View File

@@ -0,0 +1,30 @@
#### UseofApply.ql
# Use of the builtin function `apply` is generally considered bad now that the
# ability to destructure lists of arguments is possible, but we should not flag
# cases where the function is merely named `apply` rather than being the actual
# builtin `apply` function.
def useofapply():
def foo():
pass
# Positive Cases
# This use of `apply` is a reference to the builtin function and so SHOULD be
# caught by the query.
apply(foo, [1])
# Negative Cases
# This use of `apply` is a reference to the locally defined function inside of
# `local`, and so SHOULD NOT be caught by the query.
def local():
def apply(f):
pass
apply(foo)([1])

View File

@@ -0,0 +1 @@
Expressions/UseofApply.ql

View File

@@ -0,0 +1,7 @@
123456789
223456789
323456789
423456789
5234567ø9
623456789
723456789

View File

@@ -0,0 +1 @@
Imports/EncodingError.ql

View File

@@ -0,0 +1 @@
| bad_encoding.py:11:19:11:19 | Encoding Error | 'utf-8' codec can't decode byte 0x82 in position 82: invalid start byte |

View File

@@ -0,0 +1 @@
Imports/EncodingError.ql

View File

@@ -0,0 +1 @@
| nonsense.py:1:2:1:2 | Syntax Error | Syntax Error (in Python 3.5). |

View File

@@ -0,0 +1 @@
Imports/SyntaxError.ql

View File

@@ -0,0 +1,12 @@
"""Multi-line docstring
"""
# encoding:shift-jis
def f():
print "Python <20>̊J<CC8A><4A><EFBFBD>́A1990 <20>N<EFBFBD><4E><EFBFBD><EFBFBD><EB82A9><EFBFBD>J<EFBFBD>n<EFBFBD><6E><EFBFBD><EFBFBD><EFBFBD>Ă<EFBFBD><C482>܂<EFBFBD>"
"""

View File

@@ -0,0 +1,37 @@
`Twas brillig, and the slithy toves
Did gyre and gimble in the wabe:
All mimsy were the borogoves,
And the mome raths outgrabe.
"Beware the Jabberwock, my son!
The jaws that bite, the claws that catch!
Beware the Jubjub bird, and shun
The frumious Bandersnatch!"
He took his vorpal sword in hand:
Long time the manxome foe he sought --
So rested he by the Tumtum tree,
And stood awhile in thought.
And, as in uffish thought he stood,
The Jabberwock, with eyes of flame,
Came whiffling through the tulgey wood,
And burbled as it came!
One, two! One, two! And through and through
The vorpal blade went snicker-snack!
He left it dead, and with its head
He went galumphing back.
"And, has thou slain the Jabberwock?
Come to my arms, my beamish boy!
O frabjous day! Callooh! Callay!'
He chortled in his joy.
`Twas brillig, and the slithy toves
Did gyre and gimble in the wabe;
All mimsy were the borogoves,
And the mome raths outgrabe.

View File

@@ -0,0 +1 @@
print "Hello World"

View File

@@ -0,0 +1 @@
This directory contains tests for [experimental](../../../../docs/experimental.md) CodeQL queries and libraries.

View File

@@ -12,15 +12,19 @@
| 21 | file://:0:0:0:0 | bool True |
| 22 | file://:0:0:0:0 | bool False |
| 22 | file://:0:0:0:0 | bool True |
| 24 | file://:0:0:0:0 | bool False |
| 24 | file://:0:0:0:0 | bool True |
| 25 | file://:0:0:0:0 | bool False |
| 26 | file://:0:0:0:0 | bool False |
| 27 | file://:0:0:0:0 | bool True |
| 28 | file://:0:0:0:0 | bool True |
| 25 | file://:0:0:0:0 | bool True |
| 28 | file://:0:0:0:0 | bool False |
| 29 | file://:0:0:0:0 | bool False |
| 30 | file://:0:0:0:0 | bool True |
| 33 | file://:0:0:0:0 | bool False |
| 34 | file://:0:0:0:0 | bool True |
| 35 | file://:0:0:0:0 | bool False |
| 31 | file://:0:0:0:0 | bool True |
| 32 | file://:0:0:0:0 | bool False |
| 33 | file://:0:0:0:0 | bool True |
| 36 | file://:0:0:0:0 | bool False |
| 37 | file://:0:0:0:0 | bool True |
| 38 | file://:0:0:0:0 | bool True |
| 38 | file://:0:0:0:0 | bool False |
| 39 | file://:0:0:0:0 | bool False |
| 40 | file://:0:0:0:0 | bool True |
| 41 | file://:0:0:0:0 | bool True |

View File

@@ -21,6 +21,9 @@ len(unknown()) < 7
len(unknown()) == len(unknown())
len(unknown()) < len(unknown())
0+0 == 0
0+1 == 0
#All ops
2 > 3
2 == 3

View File

@@ -1,2 +1 @@
semmle-extractor-options: --max-import-depth=3
optimize: true

View File

@@ -1,2 +1 @@
semmle-extractor-options: --max-import-depth=4
optimize: true

Some files were not shown because too many files have changed in this diff Show More