Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 105
0.00% covered (danger)
0.00%
0 / 23
CRAP
0.00% covered (danger)
0.00%
0 / 1
FunctionTaintedness
0.00% covered (danger)
0.00%
0 / 105
0.00% covered (danger)
0.00%
0 / 23
4290
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 emptySingleton
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 withOverall
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 getOverall
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 canOverrideOverall
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 withParamSinkTaint
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 withParamPreservedTaint
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 withVariadicParamSinkTaint
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 withVariadicParamPreservedTaint
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 getParamSinkTaint
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
30
 getParamPreservedTaint
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
30
 getParamFlags
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
 canOverrideNonVariadicParam
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getVariadicParamSinkTaint
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getVariadicParamPreservedTaint
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getVariadicParamIndex
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 canOverrideVariadicParam
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSinkParamKeysNoVariadic
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPreserveParamKeysNoVariadic
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 hasParamPreserve
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
 asMergedWith
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
210
 withoutPreserved
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 asOnlyPreserved
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 toString
n/a
0 / 0
n/a
0 / 0
7
 flagsToString
n/a
0 / 0
n/a
0 / 0
4
 __toString
n/a
0 / 0
n/a
0 / 0
1
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    public function __construct( Taintedness $overall, int $overallFlags = 0 ) {
40        $this->overall = $overall;
41        $this->overallFlags = $overallFlags;
42    }
43
44    public static function emptySingleton(): self {
45        static $singleton;
46        if ( !$singleton ) {
47            $singleton = new self( Taintedness::safeSingleton() );
48        }
49        return $singleton;
50    }
51
52    public function withOverall( Taintedness $val, int $flags = 0 ): self {
53        $ret = clone $this;
54        $ret->overall = $val;
55        $ret->overallFlags |= $flags;
56        return $ret;
57    }
58
59    /**
60     * Get the overall taint (NOT a clone)
61     */
62    public function getOverall(): Taintedness {
63        return $this->overall;
64    }
65
66    public function canOverrideOverall(): bool {
67        return ( $this->overallFlags & SecurityCheckPlugin::NO_OVERRIDE ) === 0;
68    }
69
70    /**
71     * Set the sink taint for a given param
72     */
73    public function withParamSinkTaint( int $param, Taintedness $taint, int $flags = 0 ): self {
74        $ret = clone $this;
75        assert( $param !== $ret->variadicParamIndex );
76        $ret->paramSinkTaints[$param] = $taint;
77        $ret->paramFlags[$param] = ( $ret->paramFlags[$param] ?? 0 ) | $flags;
78        return $ret;
79    }
80
81    /**
82     * Set the preserved taint for a given param
83     */
84    public function withParamPreservedTaint( int $param, PreservedTaintedness $taint, int $flags = 0 ): self {
85        $ret = clone $this;
86        assert( $param !== $ret->variadicParamIndex );
87        $ret->paramPreserveTaints[$param] = $taint;
88        $ret->paramFlags[$param] = ( $ret->paramFlags[$param] ?? 0 ) | $flags;
89        return $ret;
90    }
91
92    public function withVariadicParamSinkTaint( int $index, Taintedness $taint, int $flags = 0 ): self {
93        $ret = clone $this;
94        assert( !isset( $ret->paramPreserveTaints[$index] ) && !isset( $ret->paramSinkTaints[$index] ) );
95        $ret->variadicParamIndex = $index;
96        $ret->variadicParamSinkTaint = $taint;
97        $ret->variadicParamFlags |= $flags;
98        return $ret;
99    }
100
101    public function withVariadicParamPreservedTaint( int $index, PreservedTaintedness $taint, int $flags = 0 ): self {
102        $ret = clone $this;
103        assert( !isset( $ret->paramPreserveTaints[$index] ) && !isset( $ret->paramSinkTaints[$index] ) );
104        $ret->variadicParamIndex = $index;
105        $ret->variadicParamPreserveTaint = $taint;
106        $ret->variadicParamFlags |= $flags;
107        return $ret;
108    }
109
110    /**
111     * Get the sink taintedness of the given param (NOT a clone), and NO_TAINT if not set.
112     */
113    public function getParamSinkTaint( int $param ): Taintedness {
114        if ( isset( $this->paramSinkTaints[$param] ) ) {
115            return $this->paramSinkTaints[$param];
116        }
117        if (
118            $this->variadicParamIndex !== null && $param >= $this->variadicParamIndex &&
119            $this->variadicParamSinkTaint
120        ) {
121            return $this->variadicParamSinkTaint;
122        }
123        return Taintedness::safeSingleton();
124    }
125
126    /**
127     * Get the preserved taintedness of the given param (NOT a clone), and NO_TAINT if not set.
128     */
129    public function getParamPreservedTaint( int $param ): PreservedTaintedness {
130        if ( isset( $this->paramPreserveTaints[$param] ) ) {
131            return $this->paramPreserveTaints[$param];
132        }
133        if (
134            $this->variadicParamIndex !== null && $param >= $this->variadicParamIndex &&
135            $this->variadicParamPreserveTaint
136        ) {
137            return $this->variadicParamPreserveTaint;
138        }
139        return PreservedTaintedness::emptySingleton();
140    }
141
142    public function getParamFlags( int $param ): int {
143        if ( isset( $this->paramFlags[$param] ) ) {
144            return $this->paramFlags[$param];
145        }
146        if ( $this->variadicParamIndex !== null && $param >= $this->variadicParamIndex ) {
147            return $this->variadicParamFlags;
148        }
149        return 0;
150    }
151
152    public function canOverrideNonVariadicParam( int $param ): bool {
153        return ( ( $this->paramFlags[$param] ?? 0 ) & SecurityCheckPlugin::NO_OVERRIDE ) === 0;
154    }
155
156    public function getVariadicParamSinkTaint(): ?Taintedness {
157        return $this->variadicParamSinkTaint;
158    }
159
160    /**
161     * @suppress PhanUnreferencedPublicMethod
162     */
163    public function getVariadicParamPreservedTaint(): ?PreservedTaintedness {
164        return $this->variadicParamPreserveTaint;
165    }
166
167    public function getVariadicParamIndex(): ?int {
168        return $this->variadicParamIndex;
169    }
170
171    public function canOverrideVariadicParam(): bool {
172        return ( $this->variadicParamFlags & SecurityCheckPlugin::NO_OVERRIDE ) === 0;
173    }
174
175    /**
176     * Get the *keys* of the params for which we have sink data, excluding variadic parameters
177     *
178     * @return int[]
179     */
180    public function getSinkParamKeysNoVariadic(): array {
181        return array_keys( $this->paramSinkTaints );
182    }
183
184    /**
185     * Get the *keys* of the params for which we have preserve data, excluding variadic parameters
186     *
187     * @return int[]
188     */
189    public function getPreserveParamKeysNoVariadic(): array {
190        return array_keys( $this->paramPreserveTaints );
191    }
192
193    /**
194     * Check whether we have preserve taint data for the given param
195     */
196    public function hasParamPreserve( int $param ): bool {
197        if ( isset( $this->paramPreserveTaints[$param] ) ) {
198            return true;
199        }
200        if ( $this->variadicParamIndex !== null && $param >= $this->variadicParamIndex ) {
201            return (bool)$this->variadicParamPreserveTaint;
202        }
203        return false;
204    }
205
206    /**
207     * Merge this object with another. This respects NO_OVERRIDE, since it doesn't touch any element
208     * where it's set. If the overall taint has UNKNOWN, it's cleared if we're setting it now.
209     */
210    public function asMergedWith( self $other ): self {
211        $ret = clone $this;
212
213        foreach ( $other->paramSinkTaints as $index => $baseT ) {
214            if ( ( ( $ret->paramFlags[$index] ?? 0 ) & SecurityCheckPlugin::NO_OVERRIDE ) === 0 ) {
215                if ( isset( $ret->paramSinkTaints[$index] ) ) {
216                    $ret->paramSinkTaints[$index] = $ret->paramSinkTaints[$index]->asMergedWith( $baseT );
217                } else {
218                    $ret->paramSinkTaints[$index] = $baseT;
219                }
220                $ret->paramFlags[$index] = ( $ret->paramFlags[$index] ?? 0 ) | ( $other->paramFlags[$index] ?? 0 );
221            }
222        }
223        foreach ( $other->paramPreserveTaints as $index => $baseT ) {
224            if ( ( ( $ret->paramFlags[$index] ?? 0 ) & SecurityCheckPlugin::NO_OVERRIDE ) === 0 ) {
225                if ( isset( $ret->paramPreserveTaints[$index] ) ) {
226                    $ret->paramPreserveTaints[$index] = $ret->paramPreserveTaints[$index]->asMergedWith( $baseT );
227                } else {
228                    $ret->paramPreserveTaints[$index] = $baseT;
229                }
230                $ret->paramFlags[$index] = ( $ret->paramFlags[$index] ?? 0 ) | ( $other->paramFlags[$index] ?? 0 );
231            }
232        }
233
234        if ( ( $ret->variadicParamFlags & SecurityCheckPlugin::NO_OVERRIDE ) === 0 ) {
235            $variadicIndex = $other->variadicParamIndex;
236            if ( $variadicIndex !== null ) {
237                $ret->variadicParamIndex = $variadicIndex;
238                $sinkVariadic = $other->variadicParamSinkTaint;
239                if ( $sinkVariadic ) {
240                    if ( $ret->variadicParamSinkTaint ) {
241                        $ret->variadicParamSinkTaint = $ret->variadicParamSinkTaint->asMergedWith( $sinkVariadic );
242                    } else {
243                        $ret->variadicParamSinkTaint = $sinkVariadic;
244                    }
245                }
246                $presVariadic = $other->variadicParamPreserveTaint;
247                if ( $presVariadic ) {
248                    if ( $ret->variadicParamPreserveTaint ) {
249                        $ret->variadicParamPreserveTaint = $ret->variadicParamPreserveTaint
250                            ->asMergedWith( $presVariadic );
251                    } else {
252                        $ret->variadicParamPreserveTaint = $presVariadic;
253                    }
254                }
255                $ret->variadicParamFlags |= $other->variadicParamFlags;
256            }
257        }
258
259        if ( ( $ret->overallFlags & SecurityCheckPlugin::NO_OVERRIDE ) === 0 ) {
260            // Remove UNKNOWN, which could be added e.g. when building func taint from the return type.
261            $ret->overall = $ret->overall->without( SecurityCheckPlugin::UNKNOWN_TAINT )
262                ->asMergedWith( $other->overall );
263            $ret->overallFlags |= $other->overallFlags;
264        }
265
266        return $ret;
267    }
268
269    public function withoutPreserved(): self {
270        $ret = clone $this;
271        $ret->paramPreserveTaints = [];
272        $ret->variadicParamPreserveTaint = null;
273        return $ret;
274    }
275
276    public function asOnlyPreserved(): self {
277        $ret = new self( Taintedness::safeSingleton() );
278        $ret->paramPreserveTaints = $this->paramPreserveTaints;
279        $ret->variadicParamPreserveTaint = $this->variadicParamPreserveTaint;
280        return $ret;
281    }
282
283    /**
284     * @codeCoverageIgnore
285     */
286    public function toString(): string {
287        $str = "[\n\toverall: " . $this->overall->toShortString() .
288            self::flagsToString( $this->overallFlags ) . ",\n";
289        $parKeys = array_unique( array_merge(
290            array_keys( $this->paramSinkTaints ),
291            array_keys( $this->paramPreserveTaints )
292        ) );
293        foreach ( $parKeys as $par ) {
294            $str .= "\t$par: {";
295            if ( isset( $this->paramSinkTaints[$par] ) ) {
296                $str .= "Sink: " . $this->paramSinkTaints[$par]->toShortString() . ', ';
297            }
298            if ( isset( $this->paramPreserveTaints[$par] ) ) {
299                $str .= "Preserve: " . $this->paramPreserveTaints[$par]->toShortString();
300            }
301            $str .= '} ' . self::flagsToString( $this->paramFlags[$par] ?? 0 ) . ",\n";
302        }
303        if ( $this->variadicParamIndex !== null ) {
304            $str .= "\t...{$this->variadicParamIndex}: {";
305            if ( $this->variadicParamSinkTaint ) {
306                 $str .= "Sink: " . $this->variadicParamSinkTaint->toShortString() . ', ';
307            }
308            if ( $this->variadicParamPreserveTaint ) {
309                $str .= "Preserve: " . $this->variadicParamPreserveTaint->toShortString();
310            }
311            $str .= '} ' . self::flagsToString( $this->variadicParamFlags ) . "\n";
312        }
313        return "$str]";
314    }
315
316    /**
317     * @codeCoverageIgnore
318     */
319    private static function flagsToString( int $flags ): string {
320        $bits = [];
321        if ( $flags & SecurityCheckPlugin::NO_OVERRIDE ) {
322            $bits[] = 'no override';
323        }
324        if ( $flags & SecurityCheckPlugin::ARRAY_OK ) {
325            $bits[] = 'array ok';
326        }
327        return $bits ? ' (' . implode( ', ', $bits ) . ')' : '';
328    }
329
330    /**
331     * @codeCoverageIgnore
332     */
333    public function __toString(): string {
334        return $this->toString();
335    }
336}