ShortStreamReadUnchecked
warning native on by defaultGroup: Probable bugs
Detect fread with a fixed positive byte count whose result reaches unpack requiring that exact length without a length check.
Example
<?php
$header = fread($stream, 4); unpack('Nsize', $header);Reported:
- line 2: Read the complete record before decoding it.
Detection
A sentinel or length guard applies only to the same reaching value. Reassignment or reevaluating an identical-looking call invalidates the earlier guard.
- Report fread with a fixed positive byte count whose result reaches unpack requiring that exact length without a length check.
- Resolve builtin calls and named arguments before checking the contract. Project-local callees are considered only when their bodies or immutable summaries establish the required facts. The default is enabled, with positive proof required.
- For flow-dependent conditions, require a reachable source-to-use path with no intervening invalidation. Unknown calls, ambiguous aliases, recursion or analysis-budget exhaustion cannot establish definite facts.
- D4. A length check is a condition known to hold on every path to the unpack call and applied to the same reaching read result. Because fread never returns more than the requested count N, each of these proves a complete read:
strlen($d)compared with a known integer so that the length is N:=== N,== N,>= N,> N-1, or the negation of!== N,!= N,< N,<= N-1; operands may be written in either order.mb_strlen($d, '8bit')counts bytes and is treated like strlen; mb_strlen with any other or no encoding is not.isset($d[N-1]), alone or beside other operands (isset holds only when every operand is set).- For a one-byte read only: a non-zero length, or the value being proven both non-empty and not
false(strict comparisons against''andfalsetogether, one loose comparison against either, a truthiness test, or!empty($d)).
- D5. A condition holds on the path when the unpack call is inside the true branch of an
if/elseifor in its condition after earlier failed ones, inside anelseafter the negated conditions, in the matching arm of a ternary, on the right of&&/||, inside awhileloop whose condition holds, or after an earlierifwhose branches that do not leave (return, throw, exit, break, continue) all keep it.!negates; a true&&and a false||establish both operands; a false&&or a true||establishes nothing. An unpack call in a branch the conditions before it contradict (a length both at most N-1 and exactly N) is never reached and not reported. - A check of a different value, of a wrong bound (for example
strlen($d) < 3before a four-byte decode), of character count, or one whose guarding branch does not leave, is not a length check: the report stays.
Exceptions (no report)
- Accumulation loops, length checks as defined by D4 and D5, bounded formats accepting shorter inputs, and unknown formats are excluded.
- Unresolved targets, incomplete required facts, suppressed findings and PHP versions lacking the referenced API or language feature produce no report. User declarations shadowing builtin names are not builtin contracts.
Fix
No automatic fix: EOF, timeouts and retry behavior require explicit handling.
PHP versions
Apply only on PHP versions supporting the referenced APIs and syntax (within PHP 5.3–8.5). Builtin behavior and argument contracts follow the configured PHP version. Extension-specific calls require resolved extension API symbols; do not treat same-named application functions as extension calls.
Options
None. Enable or disable this rule through normal rule configuration.
Configure
In custos.json:
{
"rules": {
"ShortStreamReadUnchecked": {
"enabled": false
}
}
}Suppress
Before the statement or declaration (or the first statement of the file):
// @custos-ignore ShortStreamReadUnchecked