1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +01:00

Track taints in static properties

This commit is contained in:
Matt Brown 2022-06-23 16:43:42 -04:00
parent cbc597f80c
commit 15387d19cd
6 changed files with 211 additions and 97 deletions

View File

@ -463,9 +463,6 @@ class InstancePropertyAssignmentAnalyzer
$data_flow_graph = $statements_analyzer->data_flow_graph;
$var_location = new CodeLocation($statements_analyzer->getSource(), $stmt->var);
$property_location = new CodeLocation($statements_analyzer->getSource(), $stmt);
if ($class_storage->specialize_instance) {
$var_id = ExpressionIdentifier::getExtendedVarId(
$stmt->var,
@ -487,6 +484,8 @@ class InstancePropertyAssignmentAnalyzer
return;
}
$var_location = new CodeLocation($statements_analyzer->getSource(), $stmt->var);
$var_node = DataFlowNode::getForAssignment(
$var_id,
$var_location
@ -494,6 +493,8 @@ class InstancePropertyAssignmentAnalyzer
$data_flow_graph->addNode($var_node);
$property_location = new CodeLocation($statements_analyzer->getSource(), $stmt);
$property_node = DataFlowNode::getForAssignment(
$var_property_id ?: $var_id . '->$property',
$property_location
@ -547,77 +548,108 @@ class InstancePropertyAssignmentAnalyzer
$statements_analyzer
);
$localized_property_node = DataFlowNode::getForAssignment(
self::taintUnspecializedProperty(
$statements_analyzer,
$stmt,
$property_id,
$class_storage,
$assignment_value_type,
$context,
$var_property_id
?: $property_id . '-' . $property_location->file_name . ':' . $property_location->raw_file_start,
$property_location
);
}
}
$data_flow_graph->addNode($localized_property_node);
public static function taintUnspecializedProperty(
StatementsAnalyzer $statements_analyzer,
PhpParser\Node\Expr $stmt,
string $property_id,
ClassLikeStorage $class_storage,
Union $assignment_value_type,
Context $context,
?string $var_property_id
): void {
$codebase = $statements_analyzer->getCodebase();
$property_node = new DataFlowNode(
$property_id,
$property_id,
null,
null
);
$data_flow_graph = $statements_analyzer->data_flow_graph;
$data_flow_graph->addNode($property_node);
if (!$data_flow_graph) {
return;
}
$event = new AddRemoveTaintsEvent($stmt, $context, $statements_analyzer, $codebase);
$property_location = new CodeLocation($statements_analyzer->getSource(), $stmt);
$added_taints = $codebase->config->eventDispatcher->dispatchAddTaints($event);
$removed_taints = $codebase->config->eventDispatcher->dispatchRemoveTaints($event);
$localized_property_node = DataFlowNode::getForAssignment(
$var_property_id ?: $property_id,
$property_location
);
$data_flow_graph->addPath(
$localized_property_node,
$property_node,
'property-assignment',
$added_taints,
$removed_taints
);
$data_flow_graph->addNode($localized_property_node);
if ($assignment_value_type->parent_nodes) {
foreach ($assignment_value_type->parent_nodes as $parent_node) {
$data_flow_graph->addPath(
$parent_node,
$localized_property_node,
'=',
$added_taints,
$removed_taints
);
}
}
$property_node = new DataFlowNode(
$property_id,
$property_id,
null,
null
);
$declaring_property_class = $codebase->properties->getDeclaringClassForProperty(
$property_id,
false,
$statements_analyzer
);
$data_flow_graph->addNode($property_node);
if ($statements_analyzer->data_flow_graph instanceof TaintFlowGraph
&& $declaring_property_class
&& $declaring_property_class !== $class_storage->name
&& $stmt->name instanceof PhpParser\Node\Identifier
) {
$declaring_property_node = new DataFlowNode(
$declaring_property_class . '::$' . $stmt->name,
$declaring_property_class . '::$' . $stmt->name,
null,
null
);
$event = new AddRemoveTaintsEvent($stmt, $context, $statements_analyzer, $codebase);
$data_flow_graph->addNode($declaring_property_node);
$added_taints = $codebase->config->eventDispatcher->dispatchAddTaints($event);
$removed_taints = $codebase->config->eventDispatcher->dispatchRemoveTaints($event);
$data_flow_graph->addPath(
$localized_property_node,
$property_node,
'property-assignment',
$added_taints,
$removed_taints
);
if ($assignment_value_type->parent_nodes) {
foreach ($assignment_value_type->parent_nodes as $parent_node) {
$data_flow_graph->addPath(
$property_node,
$declaring_property_node,
'property-assignment',
$parent_node,
$localized_property_node,
'=',
$added_taints,
$removed_taints
);
}
}
$declaring_property_class = $codebase->properties->getDeclaringClassForProperty(
$property_id,
false,
$statements_analyzer
);
if ($statements_analyzer->data_flow_graph instanceof TaintFlowGraph
&& $declaring_property_class
&& $declaring_property_class !== $class_storage->name
&& ($stmt instanceof PhpParser\Node\Expr\PropertyFetch
|| $stmt instanceof PhpParser\Node\Expr\StaticPropertyFetch)
&& $stmt->name instanceof PhpParser\Node\Identifier
) {
$declaring_property_node = new DataFlowNode(
$declaring_property_class . '::$' . $stmt->name,
$declaring_property_class . '::$' . $stmt->name,
null,
null
);
$data_flow_graph->addNode($declaring_property_node);
$data_flow_graph->addPath(
$property_node,
$declaring_property_node,
'property-assignment',
$added_taints,
$removed_taints
);
}
}
/**

View File

@ -196,6 +196,16 @@ class StaticPropertyAssignmentAnalyzer
$context->vars_in_scope[$var_id] = $assignment_value_type;
}
InstancePropertyAssignmentAnalyzer::taintUnspecializedProperty(
$statements_analyzer,
$stmt,
$property_id,
$class_storage,
$assignment_value_type,
$context,
null
);
$class_property_type = $codebase->properties->getPropertyType(
$property_id,
true,

View File

@ -773,9 +773,6 @@ class AtomicPropertyFetchAnalyzer
$data_flow_graph = $statements_analyzer->data_flow_graph;
$var_location = new CodeLocation($statements_analyzer->getSource(), $stmt->var);
$property_location = new CodeLocation($statements_analyzer->getSource(), $stmt);
$added_taints = [];
$removed_taints = [];
@ -811,6 +808,9 @@ class AtomicPropertyFetchAnalyzer
return;
}
$var_location = new CodeLocation($statements_analyzer->getSource(), $stmt->var);
$property_location = new CodeLocation($statements_analyzer->getSource(), $stmt);
$var_node = DataFlowNode::getForAssignment(
$var_id,
$var_location
@ -849,51 +849,82 @@ class AtomicPropertyFetchAnalyzer
$type->parent_nodes = [$property_node->id => $property_node];
}
} else {
$var_property_id = ExpressionIdentifier::getExtendedVarId(
self::processUnspecialTaints(
$statements_analyzer,
$stmt,
null,
$statements_analyzer
);
$localized_property_node = DataFlowNode::getForAssignment(
$var_property_id
?: $property_id . '-' . $property_location->file_name . ':' . $property_location->raw_file_start,
$property_location
);
$data_flow_graph->addNode($localized_property_node);
$property_node = new DataFlowNode(
$type,
$property_id,
$property_id,
null,
null
$in_assignment,
$added_taints,
$removed_taints
);
$data_flow_graph->addNode($property_node);
if ($in_assignment) {
$data_flow_graph->addPath(
$localized_property_node,
$property_node,
'property-assignment',
$added_taints,
$removed_taints
);
} else {
$data_flow_graph->addPath(
$property_node,
$localized_property_node,
'property-fetch',
$added_taints,
$removed_taints
);
}
$type->parent_nodes = [$localized_property_node->id => $localized_property_node];
}
}
/**
* @param ?array<string> $added_taints
* @param ?array<string> $removed_taints
*/
public static function processUnspecialTaints(
StatementsAnalyzer $statements_analyzer,
PhpParser\Node\Expr $stmt,
Union $type,
string $property_id,
bool $in_assignment,
?array $added_taints,
?array $removed_taints
): void {
if (!$statements_analyzer->data_flow_graph) {
return;
}
$data_flow_graph = $statements_analyzer->data_flow_graph;
$var_property_id = ExpressionIdentifier::getExtendedVarId(
$stmt,
null,
$statements_analyzer
);
$property_location = new CodeLocation($statements_analyzer->getSource(), $stmt);
$localized_property_node = DataFlowNode::getForAssignment(
$var_property_id ?: $property_id,
$property_location
);
$data_flow_graph->addNode($localized_property_node);
$property_node = new DataFlowNode(
$property_id,
$property_id,
null,
null
);
$data_flow_graph->addNode($property_node);
if ($in_assignment) {
$data_flow_graph->addPath(
$localized_property_node,
$property_node,
'property-assignment',
$added_taints,
$removed_taints
);
} else {
$data_flow_graph->addPath(
$property_node,
$localized_property_node,
'property-fetch',
$added_taints,
$removed_taints
);
}
$type->parent_nodes = [$localized_property_node->id => $localized_property_node];
}
private static function handleEnumName(
StatementsAnalyzer $statements_analyzer,
PropertyFetch $stmt,

View File

@ -231,6 +231,16 @@ class StaticPropertyFetchAnalyzer
);
}
AtomicPropertyFetchAnalyzer::processUnspecialTaints(
$statements_analyzer,
$stmt,
$stmt_type,
$property_id,
false,
[],
[]
);
return true;
}
@ -389,6 +399,16 @@ class StaticPropertyFetchAnalyzer
$stmt_type->getId()
);
}
AtomicPropertyFetchAnalyzer::processUnspecialTaints(
$statements_analyzer,
$stmt,
$stmt_type,
$property_id,
false,
[],
[]
);
} else {
$statements_analyzer->node_data->setType($stmt, Type::getMixed());
}

View File

@ -2,6 +2,7 @@
namespace Psalm\Internal\Codebase;
use Exception;
use Psalm\Internal\DataFlow\DataFlowNode;
use Psalm\Internal\DataFlow\Path;
@ -43,6 +44,10 @@ abstract class DataFlowGraph
return;
}
if ($from_id === 'A::$last-src/somefile.php:255-src/somefile.php:255-265') {
throw new Exception('bad');
}
$length = 0;
if ($from->code_location

View File

@ -2349,6 +2349,22 @@ class TaintTest extends TestCase
}',
'error_message' => 'TaintedHtml',
],
'checkMemoizedStaticMethodCallTaints' => [
'code' => '<?php
class A {
private static string $last = "";
public static function getInputOrLast(string $s): string {
$last = self::$last;
self::$last = $s;
return $last;
}
}
A::getInputOrLast($_GET["a"]);
echo A::getInputOrLast("foo");',
'error_message' => 'TaintedHtml',
],
];
}