Skip to content
Permalink
Browse files

Add support for different taint types ref #1990

  • Loading branch information...
muglug committed Aug 13, 2019
1 parent 58b6d8b commit d5b026839cdc83b063e321aac184a448ca1538c5
@@ -53,6 +53,8 @@ public static function getReturnTypeFromCallMapWithArgs(
$call_map = CallMap::getCallMap();
$codebase = $statements_analyzer->getCodebase();
if (!isset($call_map[$call_map_key])) {
throw new \InvalidArgumentException('Function ' . $function_id . ' was not found in callmap');
}
@@ -85,8 +87,6 @@ public static function getReturnTypeFromCallMapWithArgs(
return new Type\Union([new Type\Atomic\TClassString($context->self ?: 'object')]);
case 'get_parent_class':
$codebase = $statements_analyzer->getCodebase();
if ($context->self && $codebase->classExists($context->self)) {
$classlike_storage = $codebase->classlike_storage_provider->get($context->self);
@@ -264,8 +264,6 @@ public static function getReturnTypeFromCallMapWithArgs(
new Type\Atomic\TFalse
]);
$codebase = $statements_analyzer->getCodebase();
if ($codebase->config->ignore_internal_falsable_issues) {
$falsable_array->ignore_falsable_issues = true;
}
@@ -380,8 +378,6 @@ public static function getReturnTypeFromCallMapWithArgs(
break;
default:
$codebase = $statements_analyzer->getCodebase();
if ($call_map_return_type->isFalsable()
&& $codebase->config->ignore_internal_falsable_issues
) {
@@ -391,4 +387,57 @@ public static function getReturnTypeFromCallMapWithArgs(
return $call_map_return_type;
}
/**
* @param array<PhpParser\Node\Arg> $call_args
*/
public static function taintBuiltinFunctionReturn(
StatementsAnalyzer $statements_analyzer,
string $function_id,
array $call_args,
Type\Union $return_type
) : void {
$codebase = $statements_analyzer->getCodebase();
if (!$codebase->taint) {
return;
}
switch ($function_id) {
case 'htmlspecialchars':
if (isset($call_args[0]->value->inferredType)
&& $call_args[0]->value->inferredType->tainted
) {
// input is now safe from tainted sql and html
$return_type->tainted = $call_args[0]->value->inferredType->tainted
& ~(Type\Union::TAINTED_INPUT_SQL | Type\Union::TAINTED_INPUT_HTML);
$return_type->sources = $call_args[0]->value->inferredType->sources;
}
break;
case 'strtolower':
case 'strtoupper':
case 'print_r':
if (isset($call_args[0]->value->inferredType)
&& $call_args[0]->value->inferredType->tainted
) {
$return_type->tainted = $call_args[0]->value->inferredType->tainted;
$return_type->sources = $call_args[0]->value->inferredType->sources;
}
break;
case 'htmlentities':
case 'striptags':
if (isset($call_args[0]->value->inferredType)
&& $call_args[0]->value->inferredType->tainted
) {
// input is now safe from tainted html
$return_type->tainted = $call_args[0]->value->inferredType->tainted
& ~Type\Union::TAINTED_INPUT_HTML;
$return_type->sources = $call_args[0]->value->inferredType->sources;
}
break;
}
}
}
@@ -601,8 +601,9 @@ function (FunctionLikeParameter $p) {
);
$var_type->sources = [$type_source];
if ($codebase->taint->hasExistingSource($type_source)) {
$var_type->tainted = 1;
if ($tainted_source = $codebase->taint->hasExistingSource($type_source)) {
$var_type->tainted = $tainted_source->taint;
$type_source->taint = $tainted_source->taint;
}
}
@@ -489,6 +489,15 @@ public static function analyze(
$stmt->args,
$context
);
if ($codebase->taint) {
FunctionAnalyzer::taintBuiltinFunctionReturn(
$statements_analyzer,
$function_id,
$stmt->args,
$stmt->inferredType
);
}
}
}
}
@@ -1236,9 +1236,10 @@ function (Assertion $assertion) use ($class_template_params) : Assertion {
);
}
if ($codebase->taint->hasPreviousSource($method_source)) {
$return_type_candidate->tainted = 1;
if ($tainted_source = $codebase->taint->hasPreviousSource($method_source)) {
$return_type_candidate->tainted = $tainted_source->taint;
$return_type_candidate->sources = [$method_source];
$method_source->taint = $tainted_source->taint;
}
}
}
@@ -1009,22 +1009,24 @@ function (Assertion $assertion) use ($found_generic_params) : Assertion {
);
}
if ($codebase->taint->hasPreviousSource($method_source, $suffixes)) {
$return_type_candidate->tainted = 1;
if ($tainted_source = $codebase->taint->hasPreviousSource($method_source, $suffixes)) {
$return_type_candidate->tainted = $tainted_source->taint;
if ($suffixes !== null) {
$specialized_sources = [];
foreach ($suffixes as $suffix) {
$specialized_sources[] = new TypeSource(
$method_source->id . '-' . $suffix,
$method_source->code_location
$method_source->code_location,
$tainted_source->taint
);
}
$return_type_candidate->sources = $specialized_sources;
} else {
$return_type_candidate->sources = [$method_source];
$method_source->taint = $tainted_source->taint;
}
}
}
@@ -2753,6 +2753,9 @@ public static function checkFunctionArgumentType(
return null;
}
/**
* @psalm-suppress UnusedVariable due to #2014
*/
private static function processTaintedness(
StatementsAnalyzer $statements_analyzer,
string $cased_method_id,
@@ -2775,7 +2778,9 @@ private static function processTaintedness(
$code_location
);
if (($function_param->is_sink || $codebase->taint->hasPreviousSink($method_source))
$found_sink = null;
if (($function_param->sink || ($found_sink = $codebase->taint->hasPreviousSink($method_source)))
&& $input_type->sources
) {
$all_possible_sinks = [];
@@ -2797,22 +2802,30 @@ private static function processTaintedness(
$class_storage = $codebase->classlike_storage_provider->get($fq_classlike_name);
foreach ($class_storage->dependent_classlikes as $dependent_classlike => $_) {
$all_possible_sinks[] = TypeSource::getForMethodArgument(
$new_sink = TypeSource::getForMethodArgument(
$dependent_classlike . '::' . $method_name,
(int) $method_name_parts[1] - 1,
$code_location,
null
);
$new_sink->taint = $found_sink ? $found_sink->taint : $function_param->sink;
$all_possible_sinks[] = $new_sink;
}
if (isset($class_storage->overridden_method_ids[$method_name])) {
foreach ($class_storage->overridden_method_ids[$method_name] as $parent_method_id) {
$all_possible_sinks[] = TypeSource::getForMethodArgument(
$new_sink = TypeSource::getForMethodArgument(
$parent_method_id,
(int) $method_name_parts[1] - 1,
$code_location,
null
);
$new_sink->taint = $found_sink ? $found_sink->taint : $function_param->sink;
$all_possible_sinks[] = $new_sink;
}
}
}
@@ -2826,7 +2839,11 @@ private static function processTaintedness(
);
}
if ($function_param->is_sink && $input_type->tainted && $input_type->sources) {
if ($function_param->sink
&& $input_type->tainted
&& ($function_param->sink & $input_type->tainted)
&& $input_type->sources
) {
foreach ($input_type->sources as $input_source) {
if (IssueBuffer::accepts(
new TaintedInput(
@@ -162,9 +162,13 @@ public static function analyze(
);
if ($array_var_id === '$_GET' || $array_var_id === '$_POST') {
$stmt->inferredType->tainted = Type\Union::TAINTED;
$stmt->inferredType->tainted = (int) Type\Union::TAINTED_INPUT;
$stmt->inferredType->sources = [
new TypeSource('$_GET', new CodeLocation($statements_analyzer->getSource(), $stmt))
new TypeSource(
'$_GET',
new CodeLocation($statements_analyzer->getSource(), $stmt),
(int) Type\Union::TAINTED_INPUT
)
];
}
@@ -857,8 +857,8 @@ private static function processTaints(
$type->sources = [$method_source];
if ($codebase->taint->hasPreviousSource($method_source)) {
$type->tainted = 1;
if ($tainted_source = $codebase->taint->hasPreviousSource($method_source)) {
$type->tainted = $tainted_source->taint;
}
}
}
@@ -506,7 +506,10 @@ function ($line) {
'var',
false
);
$echo_param->is_sink = true;
$echo_param->sink = Type\Union::TAINTED_INPUT_HTML
| Type\Union::TAINTED_USER_SECRET
| Type\Union::TAINTED_SYSTEM_SECRET;
foreach ($stmt->exprs as $i => $expr) {
ExpressionAnalyzer::analyze($this, $expr, $context);
@@ -268,7 +268,7 @@ public function extractReflectionMethodInfo(\ReflectionMethod $method)
}
if ($declaring_method_id === 'PDO::exec' && $i === 0) {
$param->is_sink = true;
$param->sink = Type\Union::TAINTED_INPUT_SQL;
}
}
@@ -70,41 +70,41 @@ public function hasExistingSource(TypeSource $source) : ?TypeSource
/**
* @param ?array<string> $suffixes
*/
public function hasPreviousSink(TypeSource $source, ?array &$suffixes = null) : bool
public function hasPreviousSink(TypeSource $source, ?array &$suffixes = null) : ?TypeSource
{
if (isset($this->specializations[$source->id])) {
$suffixes = $this->specializations[$source->id];
foreach ($suffixes as $suffix) {
if (isset(self::$previous_sinks[$source->id . '-' . $suffix])) {
return true;
return self::$previous_sinks[$source->id . '-' . $suffix];
}
}
return false;
return null;
}
return isset(self::$previous_sinks[$source->id]);
return self::$previous_sinks[$source->id] ?? null;
}
/**
* @param ?array<string> $suffixes
*/
public function hasPreviousSource(TypeSource $source, ?array &$suffixes = null) : bool
public function hasPreviousSource(TypeSource $source, ?array &$suffixes = null) : ?TypeSource
{
if (isset($this->specializations[$source->id])) {
$suffixes = $this->specializations[$source->id];
foreach ($suffixes as $suffix) {
if (isset(self::$previous_sources[$source->id . '-' . $suffix])) {
return true;
return self::$previous_sources[$source->id . '-' . $suffix];
}
}
return false;
return null;
}
return isset(self::$previous_sources[$source->id]);
return self::$previous_sources[$source->id] ?? null;
}
public function addSpecialization(string $base_id, string $suffix) : void
@@ -12,10 +12,14 @@ class TypeSource
/** @var ?CodeLocation */
public $code_location;
public function __construct(string $id, ?CodeLocation $code_location)
/** @var int */
public $taint;
public function __construct(string $id, ?CodeLocation $code_location, int $taint = 0)
{
$this->id = $id;
$this->code_location = $code_location;
$this->taint = $taint;
}
public static function getForMethodArgument(
@@ -2289,7 +2289,7 @@ function (FunctionLikeParameter $p) {
foreach ($storage->params as $param_storage) {
if ($param_storage->name === $param_name) {
$param_storage->is_sink = true;
$param_storage->sink = (int) Type\Union::TAINTED_INPUT;
}
}
}
@@ -69,9 +69,9 @@ class FunctionLikeParameter
public $is_variadic;
/**
* @var bool
* @var int
*/
public $is_sink = false;
public $sink = 0;
/**
* @var bool
@@ -31,9 +31,21 @@
class Union
{
const TAINTED = 1;
const TAINTED_MYSQL_SAFE = 2;
const TAINTED_HTML_SAFE = 4;
const TAINTED_INPUT_SQL = 1;
const TAINTED_INPUT_HTML = 2;
const TAINTED_INPUT_SHELL = 4;
const TAINTED_USER_SECRET = 8;
const TAINTED_SYSTEM_SECRET = 16;
const TAINTED_INPUT = self::TAINTED_INPUT_SQL
| self::TAINTED_INPUT_HTML
| self::TAINTED_INPUT_SHELL;
const TAINTED = self::TAINTED_INPUT_SQL
| self::TAINTED_INPUT_HTML
| self::TAINTED_INPUT_SHELL
| self::TAINTED_USER_SECRET
| self::TAINTED_SYSTEM_SECRET;
/**
* @var array<string, Atomic>

0 comments on commit d5b0268

Please sign in to comment.
You can’t perform that action at this time.