원문 캡처 · mbrock.github.io
원문 캡처 · mbrock.github.io

Fil-C로 빌드한 PHP는 인터프리터나 확장 모듈에서 메모리 범위를 벗어난 접근이나 해제 후 사용이 발생하면 메모리를 손상시키는 대신 작업자를 패닉으로 멈춘다. PHP 코드 변경 없이 사용할 수 있지만, 일반 PHP보다 1.5~4배 느리고 메모리를 더 쓴다.

Docker 이미지, x86-64 Linux용 tarball, Nix 패키지로 제공된다. Docker 이미지 구성은 공식 php 이미지와 비슷해 php:fpm을 쓰던 곳에 적용할 수 있다.

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, php-fpm, php-cgi, phpdbg와 자체 glibc, 런타임 및 확장 모듈이 불러오는 라이브러리를 포함한다. 어디서든 압축을 풀어 사용할 수 있으며 Debian 12/13, Ubuntu 22.04, Alpine 3에서 루트 권한과 일반 사용자 권한으로 시험했다. Nix에서는 다음처럼 빌드한다.

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

기본 확장 구성은 Nixpkgs 기본값에 apcu, redis, memcached, mongodb, imagick, protobuf, yaml, zstd 등을 더한다. 약 50개의 PECL 확장을 포팅했으며 빌드 과정에서 각 확장의 테스트 스위트를 실행한다. 확장 목록에 자세한 내용이 있다. PHP 8.4.27에서는 코어와 표준 라이브러리 테스트 13,345개가 예상치 못한 실패 없이 통과했고, OPcache 테스트 864개도 통과했다.

NixOS에서는 nixosModules.filc 모듈로 PHP-FPM 등 선택한 서비스를 Fil-C 빌드로 바꿀 수 있다. 나머지 시스템 구성 요소는 일반 NixOS 빌드를 그대로 쓴다. PHP-FPM은 기존 풀 설정에 다음 옵션을 더하면 된다.

# 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
};

이 설정은 services.phpfpm.phpPackage를 pkgsFilc.php로 바꾼다. 풀에서 별도의 phpPackage를 지정하면 그 설정은 유지되므로, pkgsFilc.php.withExtensions … 빌드로 특정 풀에 확장을 추가할 수 있다. Fil-C 빌드의 lighttpd를 쓰는 설정도 있지만 nginx 설정은 없다. Fil-C의 capability는 포인터를 포함한 메모리를 프로세스 사이에서 공유할 수 없어, Fil-C PHP-FPM 앞의 nginx는 일반 빌드를 사용해야 한다. 자세한 설정은 NixOS 안내에 있다.

핵심은 각 PHP 값의 메모리 경계를 따로 검사하도록 기본 메모리 할당기를 바꾼 점이다. Zend 할당기는 요청 메모리를 2MiB 덩어리에서 할당하기 때문에, Fil-C가 포인터가 가리키는 객체의 경계를 확인해도 한 값에서 이웃 값으로 넘친 접근이나 같은 요청 안에서 해제된 값의 읽기를 놓칠 수 있다. 기본값인 PHP 추적 할당기는 각 값을 별도 객체로 다룬다. PHP의 AddressSanitizer CI에서도 쓰는 방식으로, memory_limit과 memory_get_usage()도 계속 동작한다.

이 차이는 unserialize()의 var_destroy 해제 후 사용 버그에서 드러났다. 아직 출시 버전에서 수정되지 않은 php-src#21824는 USE_ZEND_ALLOC=1에서 조용히 실행을 마치지만, 기본 할당기에서는 Fil-C 패닉을 일으킨다. 기본 설정은 할당이 많은 코드에서 Zend 할당기보다 약 2배 비용이 든다. USE_TRACKED_ALLOC=0으로 일반 malloc을 쓰면 약 3분의 1 빨라지지만 memory_limit 기능을 포기해야 한다.

Fil-C는 생성된 기계어 코드를 실행할 수 없어 OPcache와 PCRE의 네이티브 JIT는 꺼져 있다. 실험용 #php-jit 빌드는 OPcache JIT가 C 코드를 내보내고 Fil-C 컴파일러로 컴파일한 뒤 dlopen으로 불러온다. CPU 중심 벤치마크에서는 인터프리터보다 4~60배 빨랐지만, 핫 함수마다 컴파일러를 실행하므로 애플리케이션보다는 작은 CPU 중심 스크립트에 적합하다. 또 capability는 프로세스 사이를 넘지 않아 OPcache는 프로세스별로 메모리를 매핑한다. FPM 작업자는 포크 전에 마스터가 컴파일한 내용을 공유하지만, 이후에는 각자 캐시를 가지며 opcache_reset()도 호출한 작업자의 캐시에만 적용된다.

Fil-C가 실제 버그를 잡는지 확인하기 위해 PHP 8.4.27에서 수정된 요청 처리 메모리 버그 두 건(CVE-2026-109618, CVE-2026-109619)의 회귀 테스트 235개를 취약한 PHP 8.4.25 빌드에서 실행했다. Fil-C는 CVE의 출발점인 메모리 접근이 발생한 테스트 두 개를 중단했다. 수정된 8.4.27에서는 235개 테스트에서 패닉이 없었다. 다만 이는 PHP의 회귀 테스트이지 공격에 맞춰 만든 테스트가 아니며, CVE-2026-109618의 한 경로는 Fil-C 빌드가 없는 mod_php 또는 FrankenPHP가 필요해 시험하지 않았다. 패닉은 여전히 작업자 충돌이므로 공격자가 이를 유발하면 작업자를 종료시킬 수 있다. 취약점을 고치는 대신 패치된 PHP를 사용해야 하며, Fil-C는 추가 방어망이다.

빌드용도와 조건
8.4.27기본 빌드이자 현재 8.4 보안 릴리스. tarball과 Docker 이미지에 포함된다.
8.5.12선택 가능한 현재 8.5 빌드. 같은 패치와 기본 할당기 설정을 적용했으며 최근 성능 작업의 대상이다.
7.4.332022년 지원 종료된 버전. 두 CVE에 대한 PHP의 최소 완화 조치를 포함한다. PHP 8로 옮기는 동안의 완화책이지 지원되는 PHP는 아니다. unserialize() 해제 후 사용은 패닉을 일으키지만 SQL 인젝션 등은 일반 7.4와 똑같이 동작한다.
8.4.25위 실험에 사용한 의도적으로 취약한 빌드. 재현용이며 안전하지 않은 빌드로 표시돼 있어 운영용이 아니다.

Fil-C PHP는 php.net의 빌드가 아닌 연구용 산출물이며 지원이나 보증을 제공하지 않는다. 메모리 손상을 충돌로 바꿀 뿐, 로직 버그나 인젝션, PHP 코드가 의도적으로 수행하는 동작은 막지 못한다. 작업자 충돌이 침해보다 낫고 CPU 비용 1.5~4배를 감당할 수 있는 환경이라면 평가해 볼 수 있다. 자세한 내용은 전체 안내와 mbrock/filnix 저장소에 있다.

부록: 원문 예시

원문에 실린 예시 1

text
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.