Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
70.80% covered (warning)
70.80%
80 / 113
78.57% covered (warning)
78.57%
22 / 28
CRAP
0.00% covered (danger)
0.00%
0 / 1
FunctionTaintedness
70.80% covered (warning)
70.80%
80 / 113
78.57% covered (warning)
78.57%
22 / 28
192.04
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setOverall
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addOverallFlags
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getOverall
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 canOverrideOverall
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setParamSinkTaint
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setParamPreservedTaint
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addParamFlags
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setVariadicParamSinkTaint
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 setVariadicParamPreservedTaint
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 addVariadicParamFlags
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getParamSinkTaint
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
5
 getParamPreservedTaint
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
5.12
 getParamFlags
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 canOverrideNonVariadicParam
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getVariadicParamSinkTaint
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getVariadicParamPreservedTaint
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getVariadicParamIndex
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 canOverrideVariadicParam
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSinkParamKeysNoVariadic
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPreserveParamKeysNoVariadic
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasParamPreserve
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 mergeWith
90.32% covered (success)
90.32%
28 / 31
0.00% covered (danger)
0.00%
0 / 1
14.18
 asMergedWith
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 __clone
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
5
 toString
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
56
 flagsToString
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 __toString
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php declare( strict_types=1 );
2
3namespace SecurityCheckPlugin;
4
5/**
6 * Value object used to store taintedness of functions.
7 * The $overall prop specifies what taint the function returns
8 *   irrespective of its arguments.
9 *
10 *   For 'overall': only TAINT flags, what taint the output has
11 *   For param keys: EXEC flags for what taints are unsafe here
12 *                     TAINT flags for what taint gets passed through func.
13 * As a special case, if the overall key has self::PRESERVE_TAINT
14 * then any unspecified keys behave like they are self::YES_TAINT
15 *
16 * If func has no info for a parameter, the UnionType will be used to determine its taintedness.
17 * The $overall taintedness must always be set.
18 */
19class FunctionTaintedness {
20    /** @var Taintedness Overall taintedness of the func */
21    private $overall;
22    /** @var Taintedness[] EXEC taintedness for each param */
23    private $paramSinkTaints = [];
24    /** @var PreservedTaintedness[] Preserved taintedness for each param */
25    private $paramPreserveTaints = [];
26    /** @var int|null Index of a variadic parameter, if any */
27    private $variadicParamIndex;
28    /** @var Taintedness|null EXEC taintedness for a variadic parameter, if any */
29    private $variadicParamSinkTaint;
30    /** @var PreservedTaintedness|null Preserved taintedness for a variadic parameter, if any */
31    private $variadicParamPreserveTaint;
32    /** @var int Special overall flags */
33    private $overallFlags = 0;
34    /** @var int[] Special flags for parameters */
35    private $paramFlags = [];
36    /** @var int */
37    private $variadicParamFlags = 0;
38
39    /**
40     * @param Taintedness $overall
41     */
42    public function __construct( Taintedness $overall ) {
43        $this->overall = $overall;
44    }
45
46    /**
47     * @param Taintedness $val
48     */
49    public function setOverall( Taintedness $val ): void {
50        $this->overall = $val;
51    }
52
53    /**
54     * @param int $flags
55     */
56    public function addOverallFlags( int $flags ): void {
57        $this->overallFlags |= $flags;
58    }
59
60    /**
61     * Get the overall taint (NOT a clone)
62     *
63     * @return Taintedness
64     */
65    public function getOverall(): Taintedness {
66        return $this->overall;
67    }
68
69    /**
70     * @return bool
71     */
72    public function canOverrideOverall(): bool {
73        return ( $this->overallFlags & SecurityCheckPlugin::NO_OVERRIDE ) === 0;
74    }
75
76    /**
77     * Set the sink taint for a given param
78     *
79     * @param int $param
80     * @param Taintedness $taint
81     */
82    public function setParamSinkTaint( int $param, Taintedness $taint ): void {
83        assert( $param !== $this->variadicParamIndex );
84        $this->paramSinkTaints[$param] = $taint;
85    }
86
87    /**
88     * Set the preserved taint for a given param
89     *
90     * @param int $param
91     * @param PreservedTaintedness $taint
92     */
93    public function setParamPreservedTaint( int $param, PreservedTaintedness $taint ): void {
94        assert( $param !== $this->variadicParamIndex );
95        $this->paramPreserveTaints[$param] = $taint;
96    }
97
98    /**
99     * @param int $param
100     * @param int $flags
101     */
102    public function addParamFlags( int $param, int $flags ): void {
103        $this->paramFlags[$param] = ( $this->paramFlags[$param] ?? 0 ) | $flags;
104    }
105
106    /**
107     * @param int $index
108     * @param Taintedness $taint
109     */
110    public function setVariadicParamSinkTaint( int $index, Taintedness $taint ): void {
111        assert( !isset( $this->paramPreserveTaints[$index] ) && !isset( $this->paramSinkTaints[$index] ) );
112        $this->variadicParamIndex = $index;
113        $this->variadicParamSinkTaint = $taint;
114    }
115
116    /**
117     * @param int $index
118     * @param PreservedTaintedness $taint
119     */
120    public function setVariadicParamPreservedTaint( int $index, PreservedTaintedness $taint ): void {
121        assert( !isset( $this->paramPreserveTaints[$index] ) && !isset( $this->paramSinkTaints[$index] ) );
122        $this->variadicParamIndex = $index;
123        $this->variadicParamPreserveTaint = $taint;
124    }
125
126    /**
127     * @param int $flags
128     */
129    public function addVariadicParamFlags( int $flags ): void {
130        $this->variadicParamFlags |= $flags;
131    }
132
133    /**
134     * Get the sink taintedness of the given param (NOT a clone), and NO_TAINT if not set.
135     *
136     * @param int $param
137     * @return Taintedness
138     */
139    public function getParamSinkTaint( int $param ): Taintedness {
140        if ( isset( $this->paramSinkTaints[$param] ) ) {
141            return $this->paramSinkTaints[$param];
142        }
143        if (
144            $this->variadicParamIndex !== null && $param >= $this->variadicParamIndex &&
145            $this->variadicParamSinkTaint
146        ) {
147            return $this->variadicParamSinkTaint;
148        }
149        return Taintedness::newSafe();
150    }
151
152    /**
153     * Get the preserved taintedness of the given param (NOT a clone), and NO_TAINT if not set.
154     *
155     * @param int $param
156     * @return PreservedTaintedness
157     */
158    public function getParamPreservedTaint( int $param ): PreservedTaintedness {
159        if ( isset( $this->paramPreserveTaints[$param] ) ) {
160            return $this->paramPreserveTaints[$param];
161        }
162        if (
163            $this->variadicParamIndex !== null && $param >= $this->variadicParamIndex &&
164            $this->variadicParamPreserveTaint
165        ) {
166            return $this->variadicParamPreserveTaint;
167        }
168        return PreservedTaintedness::newEmpty();
169    }
170
171    /**
172     * @param int $param
173     * @return int
174     */
175    public function getParamFlags( int $param ): int {
176        if ( isset( $this->paramFlags[$param] ) ) {
177            return $this->paramFlags[$param];
178        }
179        if ( $this->variadicParamIndex !== null && $param >= $this->variadicParamIndex ) {
180            return $this->variadicParamFlags;
181        }
182        return 0;
183    }
184
185    /**
186     * @param int $param
187     * @return bool
188     */
189    public function canOverrideNonVariadicParam( int $param ): bool {
190        return ( ( $this->paramFlags[$param] ?? 0 ) & SecurityCheckPlugin::NO_OVERRIDE ) === 0;
191    }
192
193    /**
194     * @return Taintedness|null
195     */
196    public function getVariadicParamSinkTaint(): ?Taintedness {
197        return $this->variadicParamSinkTaint;
198    }
199
200    /**
201     * @return PreservedTaintedness|null
202     * @suppress PhanUnreferencedPublicMethod
203     */
204    public function getVariadicParamPreservedTaint(): ?PreservedTaintedness {
205        return $this->variadicParamPreserveTaint;
206    }
207
208    /**
209     * @return int|null
210     */
211    public function getVariadicParamIndex(): ?int {
212        return $this->variadicParamIndex;
213    }
214
215    /**
216     * @return bool
217     */
218    public function canOverrideVariadicParam(): bool {
219        return ( $this->variadicParamFlags & SecurityCheckPlugin::NO_OVERRIDE ) === 0;
220    }
221
222    /**
223     * Get the *keys* of the params for which we have sink data, excluding variadic parameters
224     *
225     * @return int[]
226     */
227    public function getSinkParamKeysNoVariadic(): array {
228        return array_keys( $this->paramSinkTaints );
229    }
230
231    /**
232     * Get the *keys* of the params for which we have preserve data, excluding variadic parameters
233     *
234     * @return int[]
235     */
236    public function getPreserveParamKeysNoVariadic(): array {
237        return array_keys( $this->paramPreserveTaints );
238    }
239
240    /**
241     * Check whether we have preserve taint data for the given param
242     *
243     * @param int $param
244     * @return bool
245     */
246    public function hasParamPreserve( int $param ): bool {
247        if ( isset( $this->paramPreserveTaints[$param] ) ) {
248            return true;
249        }
250        if ( $this->variadicParamIndex !== null && $param >= $this->variadicParamIndex ) {
251            return (bool)$this->variadicParamPreserveTaint;
252        }
253        return false;
254    }
255
256    /**
257     * Merge this object with another. This respects NO_OVERRIDE, since it doesn't touch any element
258     * where it's set. If the overall taint has UNKNOWN, it's cleared if we're setting it now.
259     * @param self $other
260     */
261    public function mergeWith( self $other ): void {
262        foreach ( $other->paramSinkTaints as $index => $baseT ) {
263            if ( ( ( $this->paramFlags[$index] ?? 0 ) & SecurityCheckPlugin::NO_OVERRIDE ) === 0 ) {
264                if ( isset( $this->paramSinkTaints[$index] ) ) {
265                    $this->paramSinkTaints[$index]->mergeWith( $baseT );
266                } else {
267                    $this->paramSinkTaints[$index] = $baseT;
268                }
269                $this->paramFlags[$index] = ( $this->paramFlags[$index] ?? 0 ) | ( $other->paramFlags[$index] ?? 0 );
270            }
271        }
272        foreach ( $other->paramPreserveTaints as $index => $baseT ) {
273            if ( ( ( $this->paramFlags[$index] ?? 0 ) & SecurityCheckPlugin::NO_OVERRIDE ) === 0 ) {
274                if ( isset( $this->paramPreserveTaints[$index] ) ) {
275                    $this->paramPreserveTaints[$index]->mergeWith( $baseT );
276                } else {
277                    $this->paramPreserveTaints[$index] = $baseT;
278                }
279                $this->paramFlags[$index] = ( $this->paramFlags[$index] ?? 0 ) | ( $other->paramFlags[$index] ?? 0 );
280            }
281        }
282
283        if ( ( $this->variadicParamFlags & SecurityCheckPlugin::NO_OVERRIDE ) === 0 ) {
284            $variadicIndex = $other->variadicParamIndex;
285            if ( $variadicIndex !== null ) {
286                $this->variadicParamIndex = $variadicIndex;
287                $sinkVariadic = $other->variadicParamSinkTaint;
288                if ( $sinkVariadic ) {
289                    if ( $this->variadicParamSinkTaint ) {
290                        $this->variadicParamSinkTaint->mergeWith( $sinkVariadic );
291                    } else {
292                        $this->variadicParamSinkTaint = $sinkVariadic;
293                    }
294                }
295                $presVariadic = $other->variadicParamPreserveTaint;
296                if ( $presVariadic ) {
297                    if ( $this->variadicParamPreserveTaint ) {
298                        $this->variadicParamPreserveTaint->mergeWith( $presVariadic );
299                    } else {
300                        $this->variadicParamPreserveTaint = $presVariadic;
301                    }
302                }
303                $this->variadicParamFlags |= $other->variadicParamFlags;
304            }
305        }
306
307        if ( ( $this->overallFlags & SecurityCheckPlugin::NO_OVERRIDE ) === 0 ) {
308            // Remove UNKNOWN, which could be added e.g. when building func taint from the return type.
309            $this->overall->remove( SecurityCheckPlugin::UNKNOWN_TAINT );
310            $this->overall->mergeWith( $other->overall );
311            $this->overallFlags |= $other->overallFlags;
312        }
313    }
314
315    /**
316     * @param self $other
317     * @return self
318     */
319    public function asMergedWith( self $other ): self {
320        $ret = clone $this;
321        $ret->mergeWith( $other );
322        return $ret;
323    }
324
325    /**
326     * Make sure to clone properties when cloning the instance
327     */
328    public function __clone() {
329        $this->overall = clone $this->overall;
330        foreach ( $this->paramSinkTaints as $k => $e ) {
331            $this->paramSinkTaints[$k] = clone $e;
332        }
333        foreach ( $this->paramPreserveTaints as $k => $t ) {
334            $this->paramPreserveTaints[$k] = clone $t;
335        }
336        if ( $this->variadicParamSinkTaint ) {
337            $this->variadicParamSinkTaint = clone $this->variadicParamSinkTaint;
338        }
339        if ( $this->variadicParamPreserveTaint ) {
340            $this->variadicParamPreserveTaint = clone $this->variadicParamPreserveTaint;
341        }
342    }
343
344    /**
345     * @return string
346     */
347    public function toString(): string {
348        $str = "[\n\toverall: " . $this->overall->toShortString() .
349            self::flagsToString( $this->overallFlags ) . ",\n";
350        $parKeys = array_unique( array_merge(
351            array_keys( $this->paramSinkTaints ),
352            array_keys( $this->paramPreserveTaints )
353        ) );
354        foreach ( $parKeys as $par ) {
355            $str .= "\t$par: {";
356            if ( isset( $this->paramSinkTaints[$par] ) ) {
357                $str .= "Sink: " . $this->paramSinkTaints[$par]->toShortString() . ', ';
358            }
359            if ( isset( $this->paramPreserveTaints[$par] ) ) {
360                $str .= "Preserve: " . $this->paramPreserveTaints[$par]->toShortString();
361            }
362            $str .= '} ' . self::flagsToString( $this->paramFlags[$par] ?? 0 ) . ",\n";
363        }
364        if ( $this->variadicParamIndex !== null ) {
365            $str .= "\t...{$this->variadicParamIndex}: {";
366            if ( $this->variadicParamSinkTaint ) {
367                 $str .= "Sink: " . $this->variadicParamSinkTaint->toShortString() . ', ';
368            }
369            if ( $this->variadicParamPreserveTaint ) {
370                $str .= "Preserve: " . $this->variadicParamPreserveTaint->toShortString();
371            }
372            $str .= '} ' . self::flagsToString( $this->variadicParamFlags ) . "\n";
373        }
374        return "$str]";
375    }
376
377    /**
378     * @param int $flags
379     * @return string
380     */
381    private static function flagsToString( int $flags ): string {
382        $bits = [];
383        if ( $flags & SecurityCheckPlugin::NO_OVERRIDE ) {
384            $bits[] = 'no override';
385        }
386        if ( $flags & SecurityCheckPlugin::ARRAY_OK ) {
387            $bits[] = 'array ok';
388        }
389        return $bits ? ' (' . implode( ', ', $bits ) . ')' : '';
390    }
391
392    /**
393     * @return string
394     */
395    public function __toString(): string {
396        return $this->toString();
397    }
398}