php / php/php-src

file_exists() returns true for a deleted file in a long-lived FPM worker (PHP 8.5.2, open_basedir enabled) — stat()/is_file()/scandir() disagree

Đang mở
#23,544 2 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Bug Status: Needs Triage
Ngôn ngữ chính
C
Star
40.4k
Fork
8.2k
Merge trung bình
2 ngày 13 giờ
Pull request đã merge (30 ngày)
96

Mô tả

Description

In a long-lived PHP-FPM worker, after a file has been created, required and then unlink()ed (the WordPress .maintenance cycle), file_exists() keeps returning true for it, while is_file(), stat(), realpath(), fopen() and scandir() all report the file as gone. clearstatcache(true, $path) has no effect; the stale answer survives until the worker process exits. Creating the file again (even empty) clears the state.

The trigger in production is WordPress' updater: it writes ABSPATH/.maintenance, core requires it on every request in wp_is_maintenance_mode(), and the updater unlink()s it when done. Afterwards, some workers still answer file_exists()true, WordPress does require '.maintenance' and dies with Failed opening required '.../.maintenance'.

The following code, run over HTTP in an affected worker while the file is absent on disk:

<?php
header('Content-Type: text/plain');
$m = __DIR__ . '/.maintenance';
clearstatcache(true, $m);
var_dump(file_exists($m));
var_dump(is_file($m));
var_dump(@stat($m));
var_dump(realpath($m));
var_dump(@fopen($m, 'r'));
var_dump(in_array('.maintenance', scandir(__DIR__), true));
stream_wrapper_restore('file');
var_dump(file_exists($m));
echo shell_exec('test -e ' . escapeshellarg($m) . ' && echo EXISTS || echo MISSING');

Resulted in this output:

bool(true)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(true)
MISSING

But I expected this output instead:

bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
MISSING

The shell_exec() child inherits uid, cwd and mount namespace from the worker and confirms the file does not exist, so the kernel is consistent and the true originates inside the PHP process.

Ruled out
  • Filesystem/caching layers: FPM worker and an SSH shell are on the same host (gethostname()), same mount namespace (/proc/self/ns/mnt identical), docroot on a local ext4 mount. From the shell, os.access(path, F_OK) and os.path.exists(path) both return False.
  • OPcache disabled.
  • Custom stream wrappers / auto_prepend_file: only built-in wrappers registered, stream_wrapper_restore('file') changes nothing, auto_prepend_file empty.
  • Realpath cache TTL: state persists far beyond realpath_cache_ttl=120 with no requests in between.
  • Same worker across requests verified with getmypid().
  • PHP 8.4.17 (fpm-fcgi) on the same host and pool configuration: not reproducible; downgrading the pool fixed the issue.
Relevant configuration
  • open_basedir set for the pool (docroot, session and tmp dirs, /tmp:/usr/bin)
  • realpath_cache_ttl=120
  • disable_functions: pcntl_*, leak, dl, stream_socket_server, stream_socket_sendto
  • Extensions: Core, date, lexbor, openssl, pcre, zlib, filter, hash, json, uri, Zend OPcache, random, Reflection, SPL, session, standard, sodium, libxml, cgi-fcgi, apcu, bcmath, bz2, calendar, ctype, curl, dba, dom, fileinfo, ftp, gd, gmp, gettext, iconv, igbinary, imagick, imap, intl, mbstring, exif, msgpack, memcached, mysqlnd, mysqli, pgsql, sqlite3, PDO, pdo_mysql, pdo_pgsql, pdo_sqlite, Phar, posix, redis, shmop, SimpleXML, sockets, sysvmsg, sysvsem, sysvshm, tokenizer, xml, xmlreader, xmlwriter, xsl, soap, zip
PHP Version
8.5.2 fpm-fcgi
Operating System

Debian GNU/Linux 12 (bookworm), kernel 4.19.0-27-amd64, Apache + PHP-FPM

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Bắt đầu bằng cách tái hiện HTTP request trong một PHP-FPM worker chạy lâu dài với open_basedir được bật, theo trình tự file_exists(), clearstatcache(), unlink(), stat(), realpath(), fopen() và scandir() trong báo cáo. So sánh PHP 8.5.2 với 8.4.17 và xác nhận rằng fix khiến file_exists() trả về false sau khi xóa mà không yêu cầu worker phải thoát.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
c, php
Lĩnh vực
backend, operating-systems
Loại issue
Lỗi
Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức độ hoạt động
Sôi nổi
Độ rõ ràng
Khá rõ ràng
Mức phù hợp với người mới
48/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.