
Memory Safe PHP
PHP 8.4.27 · Fil-C 0.686 · also 8.5.12 and 7.4.33 · October 2026
以下正文同步自 Hacker News 最新,版权归原站所有,已转换为易读排版。
PHP 8.4.27 · Fil-C 0.686 · also 8.5.12 and 7.4.33 · October 2026
PHP 8.4, its bundled extensions, the common PECL ones, and every C library under them — all compiled with Fil-C. An out-of-bounds access or a use after free in the interpreter or an extension stops the worker with a panic instead of corrupting its memory. Your PHP code doesn't change. This page is the short version; the full notes are in the docs.
Docker — no Nix needed. The layout follows the official php images, so it slots in wherever php:fpm does:
docker run --rm -p 9000:9000 -v $PWD:/var/www/html
ghcr.io/mbrock/filnix-php:8.4.27 php-fpm # FastCGI on 9000
docker run --rm -it ghcr.io/mbrock/filnix-php:8.4.27 # php -a
Tarball — php-8.4.27-filc-x86_64-linux.tar.xz from the releases. It carries php, php-fpm, php-cgi and phpdbg, its own glibc and runtime, and every library its extensions load; unpack it anywhere on x86-64 Linux. Tested on Debian 12/13, Ubuntu 22.04 and Alpine 3, as root and unprivileged.
Nix — the package is one attribute of the Fil-C package set, and withExtensions works as in Nixpkgs:
nix build github:mbrock/filnix#pkgsFilc.php # PHP 8.4, default extensions nix build github:mbrock/filnix#php-dist # the tarball above nix build github:mbrock/filnix#php-docker # the image above
The default extension set loads Nixpkgs' defaults plus apcu, redis, memcached, mongodb, imagick (with a Fil-C ImageMagick and its image libraries), protobuf, yaml, zstd and more — about fifty PECL extensions are ported and run their own test suites in the build; the extension list has the details. The interpreter's own suites pass too: on 8.4.27 the core and standard-library sweep passes 13,345 tests with no unexpected failures, and OPcache passes 864.
Filnix ships a NixOS module, nixosModules.filc, that swaps chosen services for their Fil-C builds and leaves the rest of the system — systemd, glibc, everything else — on the ordinary NixOS builds. For PHP-FPM it is three lines on top of the pool configuration you already have:
flake.nix
inputs.filnix.url = "github:mbrock/filnix";
pass filnix.nixosModules.filc into your nixosSystem modules, then:
configuration.nix
imports = [ filnix.nixosModules.filc ];
filc.phpfpm.enable = true; # pools now run pkgsFilc.php
services.phpfpm.pools.www = {
user, group, listen, settings… exactly as usual
};
The switch sets services.phpfpm.phpPackage to pkgsFilc.php. A pool that names its own phpPackage keeps it, which is also how you give one pool extra extensions — set its phpPackage to a pkgsFilc.php.withExtensions … build. The same module has a switch for lighttpd (filc.lighttpd.enable) to serve it and for several other daemons, but none for nginx: its worker processes share pointer-containing memory, which Fil-C capabilities don't survive, so nginx in front of a Fil-C PHP-FPM stays the ordinary build; filc.enable = true turns them all on. The Fil-C packages come from filnix's own pinned Nixpkgs and its binary cache, so a rebuild mostly downloads. The NixOS notes list every switch, and two VM tests run against this module: Nixpkgs' own php-fpm test, with native nginx in front, and one of ours that serves GET and POST requests through Fil-C lighttpd and Fil-C PHP-FPM. After each, a check confirms the running services really are the Fil-C builds.
Three differences matter, and the first is the interesting one.
Each PHP value gets its own bounds. Ordinary PHP allocates request memory from Zend's allocator out of 2 MiB chunks. Fil-C checks accesses against the object a pointer refers to — and with Zend's allocator that object is the whole 2 MiB chunk, so an overflow from one PHP value into its neighbour, or a read of a value freed within the same request, looks perfectly legal. This build therefore defaults to the system allocator (PHP's tracked variant, the one its own AddressSanitizer CI uses, so memory_limit and memory_get_usage() still work). Every value is then its own object, and Fil-C can actually see the bug:
That is not hypothetical. The var_destroy use after free in unserialize() (php-src#21824, not yet fixed in a release) runs silently to completion on this build with USE_ZEND_ALLOC=1 — and stops with a Fil-C panic on the default allocator. The default costs about 2× on allocation-heavy code versus Zend's; USE_TRACKED_ALLOC=0 (plain malloc) is about a third faster but gives up memory_limit.
No native JIT. Fil-C cannot run generated machine code, so opcache's JIT and PCRE's JIT are off. There is an experimental build (#php-jit) whose opcache JIT emits C instead, compiles it with the Fil-C compiler and dlopens the result: CPU-bound benchmarks run 4–60× faster than the interpreter, but each hot function costs a compiler invocation, so it suits small CPU-bound scripts, not applications.
Opcache is per-worker. Capabilities don't cross processes, so opcache maps its memory privately. FPM workers share what the master compiled before forking, but each has its own cache after that, and opcache_reset() affects only the worker that calls it.
And the everyday caveats: native extensions must be Fil-C builds (binary PECL downloads won't load), and PHP runs 1.5–4× slower and uses more memory than an ordinary build.
PHP 8.4.27 fixed two request-handling memory bugs, CVE-2026-109618 (an OOM unwinding a request mid-construction) and CVE-2026-109619 (request globals surviving into the next request). That gave a clean experiment: run the 235 regression tests PHP shipped with the fixes against the vulnerable version — Fil-C PHP 8.4.25, which filnix keeps for exactly this, marked insecure — and see whether Fil-C notices anything on its own. It stopped two of them dead, at the corrupted accesses the CVEs start from, where native PHP runs on:
filc safety error: cannot read pointer with null object. pointer: 0x4,<null> semantic origin: (session.so) mod_user.c:264:3: ps_validate_sid_user ... (session.so) session.c:2515:9: zif_session_create_id filc panic: thwarted a futile attempt to violate memory safety.
On 8.4.27 none of the 235 panics. The honest limits: these are PHP's regression tests, not groomed exploits; one route to CVE-2026-109618 needs mod_php or FrankenPHP, which have no Fil-C build, so it wasn't tested; and a trap is still a crash — an attacker who can trigger one can kill workers. Run patched releases; Fil-C is the net, not the fix. The point is that the net was already there before anyone knew the bugs were.
A research artifact. These are not php.net builds and come with no support or warranty. Fil-C turns memory corruption into a crash; it does not help with logic bugs, injection, or anything your PHP code does on purpose. Keep upgrading PHP — and if a killed worker is better than a compromised one, and 1.5–4× the CPU is affordable, this may be worth evaluating. The full notes cover the JIT-through-C experiment, the 8.5 and 7.4 builds, upstream's own Projeny port, and every test count; the build recipe is in mbrock/filnix.
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。