返回资讯中心
外部精选
软件工程
#开发者实时流

Memory Safe PHP

PHP 8.4.27 · Fil-C 0.686 · also 8.5.12 and 7.4.33 · October 2026

Hacker News 最新pizlonator2026/10/22 · 23 天前17 分钟阅读

以下正文同步自 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 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。

打开原文
去社区讨论 →