mirror of
https://github.com/danog/psalm.git
synced 2024-12-02 09:37:59 +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:
parent
99148f4d99
commit
6c0b2f8cb9
@ -81,3 +81,12 @@ To generate a SARIF report run Psalm with the `--report` flag and a `.sarif` ext
|
|||||||
```bash
|
```bash
|
||||||
psalm --report=results.sarif
|
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
|
||||||
|
```
|
||||||
|
@ -8,6 +8,9 @@ use function substr;
|
|||||||
use function strlen;
|
use function strlen;
|
||||||
use function array_reverse;
|
use function array_reverse;
|
||||||
use function array_sum;
|
use function array_sum;
|
||||||
|
use function array_walk;
|
||||||
|
use function array_merge;
|
||||||
|
use function array_keys;
|
||||||
|
|
||||||
abstract class DataFlowGraph
|
abstract class DataFlowGraph
|
||||||
{
|
{
|
||||||
@ -134,4 +137,18 @@ abstract class DataFlowGraph
|
|||||||
|
|
||||||
return [$count, \count($origin_counts), \count($destination_counts), $mean];
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -345,6 +345,9 @@ Surfacing issues:
|
|||||||
--taint-analysis
|
--taint-analysis
|
||||||
Run Psalm in taint analysis mode – see https://psalm.dev/docs/security_analysis for more info
|
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:
|
Issue baselines:
|
||||||
--set-baseline=PATH
|
--set-baseline=PATH
|
||||||
Save all current error level issues to a file, to mark them as info in subsequent runs
|
Save all current error level issues to a file, to mark them as info in subsequent runs
|
||||||
|
@ -126,6 +126,7 @@ $valid_long_options = [
|
|||||||
'track-tainted-input',
|
'track-tainted-input',
|
||||||
'taint-analysis',
|
'taint-analysis',
|
||||||
'security-analysis',
|
'security-analysis',
|
||||||
|
'dump-taint-graph:',
|
||||||
'find-unused-psalm-suppress',
|
'find-unused-psalm-suppress',
|
||||||
'error-level:',
|
'error-level:',
|
||||||
];
|
];
|
||||||
@ -282,6 +283,9 @@ $run_taint_analysis = (isset($options['track-tainted-input'])
|
|||||||
|| isset($options['security-analysis'])
|
|| isset($options['security-analysis'])
|
||||||
|| isset($options['taint-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)) {
|
if (array_key_exists('v', $options)) {
|
||||||
echo 'Psalm ' . PSALM_VERSION . PHP_EOL;
|
echo 'Psalm ' . PSALM_VERSION . PHP_EOL;
|
||||||
exit;
|
exit;
|
||||||
@ -683,6 +687,18 @@ if ($find_references_to) {
|
|||||||
$project_analyzer->findReferencesTo($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'])) {
|
if (isset($options['set-baseline']) && is_string($options['set-baseline'])) {
|
||||||
fwrite(STDERR, 'Writing error baseline to file...' . PHP_EOL);
|
fwrite(STDERR, 'Writing error baseline to file...' . PHP_EOL);
|
||||||
|
|
||||||
|
@ -169,6 +169,25 @@ class PsalmEndToEndTest extends TestCase
|
|||||||
$this->assertSame(1, $result['CODE']);
|
$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
|
public function testLegacyConfigWithoutresolveFromConfigFile(): void
|
||||||
{
|
{
|
||||||
$this->runPsalmInit(1);
|
$this->runPsalmInit(1);
|
||||||
|
15
tests/fixtures/expected_taint_graph.dot
vendored
Normal file
15
tests/fixtures/expected_taint_graph.dot
vendored
Normal 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"
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user