mirror of
https://github.com/github/codeql.git
synced 2025-12-20 18:56:32 +01:00
Merge branch 'master' into python-support-django2
This commit is contained in:
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
@@ -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."
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 + "."
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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())
|
||||
)
|
||||
|
||||
@@ -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'."
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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__"
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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-]+/.*")
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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]])
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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 +
|
||||
"."
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 + "."
|
||||
|
||||
@@ -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
|
||||
|
||||
1
python/ql/src/experimental/README.md
Normal file
1
python/ql/src/experimental/README.md
Normal file
@@ -0,0 +1 @@
|
||||
This directory contains [experimental](../../../../docs/experimental.md) CodeQL queries and libraries.
|
||||
@@ -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 {
|
||||
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
import semmle.python.security.TaintTracking
|
||||
import semmle.python.security.TaintTracking
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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"))
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
2
python/ql/src/semmle/python/web/ClientHttpRequest.qll
Normal file
2
python/ql/src/semmle/python/web/ClientHttpRequest.qll
Normal file
@@ -0,0 +1,2 @@
|
||||
import semmle.python.web.client.StdLib
|
||||
import semmle.python.web.client.Requests
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
22
python/ql/src/semmle/python/web/client/Requests.qll
Normal file
22
python/ql/src/semmle/python/web/client/Requests.qll
Normal 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() }
|
||||
}
|
||||
55
python/ql/src/semmle/python/web/client/StdLib.qll
Normal file
55
python/ql/src/semmle/python/web/client/StdLib.qll
Normal 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))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1 @@
|
||||
semmle-extractor-options: --lang=2 --max-import-depth=4
|
||||
optimize: true
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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]))
|
||||
|
||||
@@ -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'. |
|
||||
|
||||
30
python/ql/test/2/query-tests/Expressions/UseofApply.py
Normal file
30
python/ql/test/2/query-tests/Expressions/UseofApply.py
Normal 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])
|
||||
@@ -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. |
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
semmle-extractor-options: --lang=3 -p ../../lib/ --max-import-depth=3
|
||||
optimize: true
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
semmle-extractor-options: --lang=3 --max-import-depth=4
|
||||
optimize: true
|
||||
|
||||
27
python/ql/test/3/library-tests/taint/unpacking/Taint.qll
Normal file
27
python/ql/test/3/library-tests/taint/unpacking/Taint.qll
Normal 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" }
|
||||
}
|
||||
@@ -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 |
|
||||
18
python/ql/test/3/library-tests/taint/unpacking/TestTaint.ql
Normal file
18
python/ql/test/3/library-tests/taint/unpacking/TestTaint.ql
Normal 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
|
||||
31
python/ql/test/3/library-tests/taint/unpacking/test.py
Normal file
31
python/ql/test/3/library-tests/taint/unpacking/test.py
Normal 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)
|
||||
@@ -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])
|
||||
@@ -0,0 +1 @@
|
||||
Expressions/UseofApply.ql
|
||||
@@ -0,0 +1,7 @@
|
||||
123456789
|
||||
223456789
|
||||
323456789
|
||||
423456789
|
||||
5234567ø9
|
||||
623456789
|
||||
723456789
|
||||
@@ -0,0 +1 @@
|
||||
Imports/EncodingError.ql
|
||||
@@ -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 |
|
||||
@@ -0,0 +1 @@
|
||||
Imports/EncodingError.ql
|
||||
@@ -0,0 +1 @@
|
||||
| nonsense.py:1:2:1:2 | Syntax Error | Syntax Error (in Python 3.5). |
|
||||
@@ -0,0 +1 @@
|
||||
Imports/SyntaxError.ql
|
||||
@@ -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>"
|
||||
"""
|
||||
@@ -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.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
print "Hello World"
|
||||
1
python/ql/test/experimental/README.md
Normal file
1
python/ql/test/experimental/README.md
Normal file
@@ -0,0 +1 @@
|
||||
This directory contains tests for [experimental](../../../../docs/experimental.md) CodeQL queries and libraries.
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
semmle-extractor-options: --max-import-depth=3
|
||||
optimize: true
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user