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

Add option to dump taint graph (#5080)

* Add option to dump taint graph

* Fix types

* Simplify types

Co-authored-by: Matthew Brown <github@muglug.com>
This commit is contained in:
Adrien LUCAS 2021-01-22 22:04:15 +01:00 committed by Daniil Gentili
parent 99148f4d99
commit 6c0b2f8cb9
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
6 changed files with 79 additions and 0 deletions

View File

@ -81,3 +81,12 @@ To generate a SARIF report run Psalm with the `--report` flag and a `.sarif` ext
```bash
psalm --report=results.sarif
```
## Debugging the taint graph
Psalm can output the taint graph using the DOT language. This is useful when expected taints are not detected. To generate a DOT graph run Psalm with the `--dump-taint-graph` flag. For example:
```bash
psalm --taint-analysis --dump-taint-graph=taints.dot
dot -Tsvg -o taints.svg taints.dot
```

View File

@ -8,6 +8,9 @@ use function substr;
use function strlen;
use function array_reverse;
use function array_sum;
use function array_walk;
use function array_merge;
use function array_keys;
abstract class DataFlowGraph
{
@ -134,4 +137,18 @@ abstract class DataFlowGraph
return [$count, \count($origin_counts), \count($destination_counts), $mean];
}
/**
* @psalm-return list<list<string>>
*/
public function summarizeEdges(): array
{
$edges = [];
foreach ($this->forward_edges as $source => $destinations) {
$edges[] = array_merge([$source], array_keys($destinations));
}
return $edges;
}
}

View File

@ -345,6 +345,9 @@ Surfacing issues:
--taint-analysis
Run Psalm in taint analysis mode see https://psalm.dev/docs/security_analysis for more info
--dump-taint-graph=OUTPUT_PATH
Output the taint graph using the DOT language requires --taint-analysis
Issue baselines:
--set-baseline=PATH
Save all current error level issues to a file, to mark them as info in subsequent runs

View File

@ -126,6 +126,7 @@ $valid_long_options = [
'track-tainted-input',
'taint-analysis',
'security-analysis',
'dump-taint-graph:',
'find-unused-psalm-suppress',
'error-level:',
];
@ -282,6 +283,9 @@ $run_taint_analysis = (isset($options['track-tainted-input'])
|| isset($options['security-analysis'])
|| isset($options['taint-analysis']));
/** @var string|null $dump_taint_graph */
$dump_taint_graph = $options['dump-taint-graph'] ?? null;
if (array_key_exists('v', $options)) {
echo 'Psalm ' . PSALM_VERSION . PHP_EOL;
exit;
@ -683,6 +687,18 @@ if ($find_references_to) {
$project_analyzer->findReferencesTo($find_references_to);
}
$flow_graph = $project_analyzer->getCodebase()->taint_flow_graph;
if ($flow_graph !== null && $dump_taint_graph !== null) {
file_put_contents($dump_taint_graph, "digraph Taints {\n\t".
implode("\n\t", array_map(
function (array $edges) {
return '"'.implode('" -> "', $edges).'"';
},
$flow_graph->summarizeEdges()
)) .
"\n}\n");
}
if (isset($options['set-baseline']) && is_string($options['set-baseline'])) {
fwrite(STDERR, 'Writing error baseline to file...' . PHP_EOL);

View File

@ -169,6 +169,25 @@ class PsalmEndToEndTest extends TestCase
$this->assertSame(1, $result['CODE']);
}
public function testTaintGraphDumping(): void
{
$this->runPsalmInit(1);
$result = $this->runPsalm(
[
'--taint-analysis',
'--dump-taint-graph='.self::$tmpDir.'/taints.dot',
],
self::$tmpDir,
true
);
$this->assertSame(1, $result['CODE']);
$this->assertFileEquals(
__DIR__ . '/../fixtures/expected_taint_graph.dot',
self::$tmpDir.'/taints.dot'
);
}
public function testLegacyConfigWithoutresolveFromConfigFile(): void
{
$this->runPsalmInit(1);

15
tests/fixtures/expected_taint_graph.dot vendored Normal file
View File

@ -0,0 +1,15 @@
digraph Taints {
"$_GET:src/FileWithErrors.php:345" -> "$_GET['abc']-src/FileWithErrors.php:345-349"
"$_GET['abc']-src/FileWithErrors.php:345-349" -> "coalesce-src/FileWithErrors.php:345-363"
"$s-src/FileWithErrors.php:109-110" -> "acme\sampleproject\bar"
"$s-src/FileWithErrors.php:162-163" -> "acme\sampleproject\baz"
"$s-src/FileWithErrors.php:215-216" -> "acme\sampleproject\bat"
"$s-src/FileWithErrors.php:269-270" -> "acme\sampleproject\bang"
"acme\sampleproject\bang#1" -> "$s-src/FileWithErrors.php:269-270"
"acme\sampleproject\bar#1" -> "$s-src/FileWithErrors.php:109-110"
"acme\sampleproject\bat#1" -> "$s-src/FileWithErrors.php:215-216"
"acme\sampleproject\baz#1" -> "$s-src/FileWithErrors.php:162-163"
"acme\sampleproject\foo#1" -> "$s-src/FileWithErrors.php:57-58"
"call to echo-src/FileWithErrors.php:335-364" -> "echo#1-src/filewitherrors.php:330"
"coalesce-src/FileWithErrors.php:345-363" -> "call to echo-src/FileWithErrors.php:335-364"
}