Skip to content

FileStatCacheAfterExternalMutation ​

warning native off by default PHP 5.3–8.2

Group: Probable bugs

On PHP 8.2 and earlier, cached file metadata can survive changes made by a command run through exec or system. Clear the stat cache before reading that metadata again. This inspection is disabled by default because the intended policy or use can vary.

Example ​

With a PHP 8.2 or earlier target:

php
<?php
$before=filesize("/tmp/cache.bin");exec("touch /tmp/cache.bin");$after=filesize("/tmp/cache.bin");

Reported:

  • line 2: Clear cached file metadata after external file changes.

Detection ​

  • Repeated filesize/filemtime of same known literal path straddles resolved exec/system command with statically parseable literal touch/truncate/rm on that exact path. No intervening clearstatcache; unsupported shell quoting/operators excluded.
  • Only targets up to PHP 8.2 are inspected: from PHP 8.3 on, exec and system drop the stat cache themselves, so the second read is already fresh.
  • Resolve builtin functions, classes, constants and methods; user-defined lookalikes are excluded. Follow only bounded local proof in one lexical scope.
  • Emit one finding per violating operation, not per supporting evidence statement. When the proof is inconclusive, nothing is reported.

Exceptions (no report) ​

  • Do not report unresolved or shadowed builtin names, unknown values, or incomplete receiver/type provenance. Unknown calls, aliases escaping, and intervening writes invalidate local proof. Similar code outside the stated detection contract is not a finding.
  • Do not report when the target is PHP 8.3 or later.
  • Both standard suppression forms suppress this native ID.

Fix ​

None. Choosing a repair requires intent or additional runtime information.

PHP versions ​

Reported for targets PHP 5.3–8.2. On those versions exec and system leave the stat cache untouched, so a repeated filesize/filemtime returns the value cached before the command ran. Starting with PHP 8.3 both functions clear the cache on every call (observed on 8.3 and 8.5), which makes the repeated read fresh and a finding a false positive; the rule is silent there. Process channels that still bypass the cache on newer versions (popen, proc_open) are outside this rule's detection contract.

Configure ​

In custos.json:

json
{
  "rules": {
    "FileStatCacheAfterExternalMutation": {
      "enabled": true
    }
  }
}

Suppress ​

Before the statement or declaration (or the first statement of the file):

php
// @custos-ignore FileStatCacheAfterExternalMutation

Released under the MIT License. Native rules and rules modeled on Php Inspections (EA Extended); independent clean-room implementation.