From f6605bbd875b46b3e950bb185551e90ea3917d09 Mon Sep 17 00:00:00 2001 From: William Correa Date: Fri, 23 Oct 2026 10:12:58 -0300 Subject: [PATCH] fix(php): Add patched php-nginx 8.1 image --- .github/workflows/php-nginx-81-security.yml | 32 +++++ php-nginx/8.1/.scripts/apply-patches.sh | 18 +++ php-nginx/8.1/.scripts/build-php.sh | 51 ++++++++ php-nginx/8.1/.scripts/patches.list | 2 + php-nginx/8.1/.scripts/smoke.py | 127 ++++++++++++++++++++ php-nginx/8.1/.scripts/verify.php | 17 +++ php-nginx/8.1/Dockerfile | 29 +++++ php-nginx/8.1/README.md | 31 +++++ php-nginx/8.1/variants.yaml | 3 + 9 files changed, 310 insertions(+) create mode 100644 .github/workflows/php-nginx-81-security.yml create mode 100644 php-nginx/8.1/.scripts/apply-patches.sh create mode 100644 php-nginx/8.1/.scripts/build-php.sh create mode 100644 php-nginx/8.1/.scripts/patches.list create mode 100644 php-nginx/8.1/.scripts/smoke.py create mode 100644 php-nginx/8.1/.scripts/verify.php create mode 100644 php-nginx/8.1/Dockerfile create mode 100644 php-nginx/8.1/README.md create mode 100644 php-nginx/8.1/variants.yaml diff --git a/.github/workflows/php-nginx-81-security.yml b/.github/workflows/php-nginx-81-security.yml new file mode 100644 index 0000000..2835fd8 --- /dev/null +++ b/.github/workflows/php-nginx-81-security.yml @@ -0,0 +1,32 @@ +name: Validate php-nginx 8.1 security backport + +on: + pull_request: + paths: + - php-nginx/8.1/** + - .github/workflows/php-nginx-81-security.yml + workflow_dispatch: + +permissions: + contents: read + +jobs: + compatibility: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f + - name: Build patched image + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 + with: + context: ./php-nginx/8.1 + target: php-nginx + platforms: linux/amd64 + load: true + tags: devitools/php-nginx:8.1 + cache-from: type=gha,scope=php-nginx-81-security + cache-to: type=gha,mode=max,scope=php-nginx-81-security + - name: Verify compatibility and security behavior + run: python3 php-nginx/8.1/.scripts/smoke.py devitools/php-nginx:8.1 diff --git a/php-nginx/8.1/.scripts/apply-patches.sh b/php-nginx/8.1/.scripts/apply-patches.sh new file mode 100644 index 0000000..928c548 --- /dev/null +++ b/php-nginx/8.1/.scripts/apply-patches.sh @@ -0,0 +1,18 @@ +set -euo pipefail +cd "$1" +mkdir -p /security/patches /security/sury-patches +while read -r name selected_file; do + test -n "$name" || continue + original="/tmp/sury/debian/patches/$name" + cp "$original" "/security/sury-patches/$name" + if [ -n "$selected_file" ]; then + awk -v target="diff --git a/$selected_file b/$selected_file" ' + /^diff --git / { keep = ($0 == target) } + keep { print } + ' "$original" > "/security/patches/$name" + test -s "/security/patches/$name" + else + cp "$original" "/security/patches/$name" + fi + patch --batch --fuzz=0 -p1 < "/security/patches/$name" +done < /tmp/build/patches.list diff --git a/php-nginx/8.1/.scripts/build-php.sh b/php-nginx/8.1/.scripts/build-php.sh new file mode 100644 index 0000000..fa9ab74 --- /dev/null +++ b/php-nginx/8.1/.scripts/build-php.sh @@ -0,0 +1,51 @@ +set -euxo pipefail + +php -r '$m = get_loaded_extensions(); sort($m); file_put_contents("/tmp/upstream-modules.json", json_encode($m));' +php-config --configure-options > /tmp/configure-options +. /etc/os-release +case "$VERSION_CODENAME" in + stretch|buster) + find /etc/apt -type f \( -name '*.list' -o -name '*.sources' \) -exec sed -i \ + -e 's|http://deb.debian.org/debian|http://archive.debian.org/debian|g' \ + -e 's|http://security.debian.org/debian-security|http://archive.debian.org/debian-security|g' {} + + ;; +esac +apt-get -o Acquire::Check-Valid-Until=false update +build_deps=(libcurl4-openssl-dev libedit-dev libreadline-dev libsqlite3-dev libxml2-dev zlib1g-dev) +case "$EXPECTED_PHP_VERSION" in + 5.6.*|7.0.*) build_deps+=(libssl1.0-dev) ;; + *) build_deps+=(libargon2-dev libonig-dev libsodium-dev libssl-dev) ;; +esac +apt-get install -y --no-install-recommends $PHPIZE_DEPS patch "${build_deps[@]}" + +mkdir -p /security /tmp/sury /usr/src/php /patched +curl -fsSL --retry 3 "$SURY_SOURCE_URL" -o /tmp/sury.tar.xz +printf '%s %s\n' "$SURY_SOURCE_SHA256" /tmp/sury.tar.xz | sha256sum -c - +tar -xJf /tmp/sury.tar.xz -C /tmp/sury +printf '%s %s\n' "$PHP_SOURCE_SHA256" /usr/src/php.tar.xz | sha256sum -c - +tar -xJf /usr/src/php.tar.xz -C /usr/src/php --strip-components=1 +bash /tmp/build/apply-patches.sh /usr/src/php +cp /tmp/sury/debian/changelog /security/sury-changelog +cp /tmp/build/patches.list /security/patches.list +cp /tmp/upstream-modules.json /security/upstream-modules.json +printf '%s\n' "$SURY_SOURCE_URL" > /security/source-url +printf '%s\n' "$SURY_SOURCE_SHA256" > /security/source-sha256 +printf '%s\n' "$PHP_SOURCE_SHA256" > /security/upstream-source-sha256 +export CFLAGS="$PHP_CFLAGS" CPPFLAGS="$PHP_CPPFLAGS" LDFLAGS="$PHP_LDFLAGS" +read -r -a original_options < /tmp/configure-options +configure_options=() +for option in "${original_options[@]}"; do + case "$option" in --*) configure_options+=("$option") ;; esac +done +cd /usr/src/php +./configure "${configure_options[@]}" --enable-opcache=shared +make -j"$BUILD_JOBS" +make install INSTALL_ROOT=/patched +rm -rf /patched/usr/local/etc /patched/usr/local/php /patched/usr/local/var +cp /tmp/configure-options /security/upstream-configure-options +test "$(php-config --extension-dir)" = "$(/patched/usr/local/bin/php-config --extension-dir)" +test "$(/patched/usr/local/bin/php -n -r 'echo PHP_VERSION;')" = "$EXPECTED_PHP_VERSION" +mkdir -p /tmp/patched-source/php +tar -xJf /usr/src/php.tar.xz -C /tmp/patched-source/php --strip-components=1 +bash /tmp/build/apply-patches.sh /tmp/patched-source/php +tar -cJf /tmp/php-patched.tar.xz -C /tmp/patched-source php diff --git a/php-nginx/8.1/.scripts/patches.list b/php-nginx/8.1/.scripts/patches.list new file mode 100644 index 0000000..8e0ad49 --- /dev/null +++ b/php-nginx/8.1/.scripts/patches.list @@ -0,0 +1,2 @@ +0057-GHSA-m8rr-4c36-8gq4-Consistently-pass-unsigned-char-.patch main/rfc1867.c +0083-Avoid-state-corruption-via-bailout.patch diff --git a/php-nginx/8.1/.scripts/smoke.py b/php-nginx/8.1/.scripts/smoke.py new file mode 100644 index 0000000..eb6e366 --- /dev/null +++ b/php-nginx/8.1/.scripts/smoke.py @@ -0,0 +1,127 @@ +import json +import pathlib +import subprocess +import sys +import tempfile +import time +import urllib.error +import urllib.request + +image = sys.argv[1] if len(sys.argv) > 1 else "devitools/php-nginx:8.1" + + +def docker(*args, check=True): + return subprocess.run(["docker", *args], check=check, text=True, capture_output=True) + + +def require(condition, message): + if not condition: + raise RuntimeError(message) + + +with tempfile.TemporaryDirectory(prefix="php73-sury-") as directory: + root = pathlib.Path(directory) + app = root / "app" + app.mkdir() + app.joinpath("index.php").write_text(''' PHP_VERSION, + 'sapi' => PHP_SAPI, + 'memory_limit' => ini_get('memory_limit'), + 'mounted' => ini_get('precision'), + 'upload_progress' => ini_get('session.upload_progress.enabled'), + 'multipart_limit' => ini_get('max_multipart_body_parts'), + 'sqlite' => $db->query('SELECT 42')->fetchColumn(), + 'gd' => strlen($png) > 0, + 'mbstring' => mb_strlen('รก', 'UTF-8'), + 'modules' => $modules, + 'post' => $_POST, + 'files' => count($_FILES), +]); +''') + app.joinpath("oom.php").write_text(''' /usr/local/etc/php/conf.d/zz-security.ini \ + && php /usr/local/share/php-security/verify.php \ + && php-fpm -t + +LABEL org.opencontainers.image.title="PHP 8.1 php-nginx with Sury security backports" \ + org.opencontainers.image.source="https://github.com/devitools/dockerfile" \ + devitools.php.security-fixes="CVE-2026-109618,CVE-2026-109619" \ + devitools.php.patch-source="php8.1_8.1.34-9+0~20261021.82+debian12~1.gbp4e430a.debian.tar.xz" diff --git a/php-nginx/8.1/README.md b/php-nginx/8.1/README.md new file mode 100644 index 0000000..8c0c5ae --- /dev/null +++ b/php-nginx/8.1/README.md @@ -0,0 +1,31 @@ +# php-nginx:8.1 security compatibility image + +`devitools/php-nginx:8.1` is a linux/amd64 replacement for `webdevops/php-nginx:8.1`. Merging this directory triggers the existing build-images workflow to publish exactly this tag; no latest alias is added. + +## Security and provenance + +The upstream image is pinned to `sha256:d9ee3ec3b8795b443581c5f3f724c6b91d3a7db42775d4d5286ec9c5884a9f65` and PHP source to `ffa9e0982e82eeaea848f57687b425ed173aa278fe563001310ae2638db5c251`. PHP remains `8.1.34` and is rebuilt under `/usr/local` with its original configure options and compiler flags. Original trailing configure environment assignments are restored through the image's compiler environment rather than parsed as options. + +The checksum-pinned [Sury source package](https://packages.sury.org/php/pool/main/p/php8.1/php8.1_8.1.34-9%2B0~20261021.82%2Bdebian12~1.gbp4e430a.debian.tar.xz) provides the CVE-2026-109618 / CVE-2026-109619 backport and these prerequisites: + +- `0057-GHSA-m8rr-4c36-8gq4-Consistently-pass-unsigned-char-.patch main/rfc1867.c` (only the `main/rfc1867.c` diff section is applied) +- `0083-Avoid-state-corruption-via-bailout.patch` + +Source package SHA-256: `03a83aeb5b1912d7b427e331c1287860d31a3a6c8b49ff40ac83df371283979c`. + +Only the listed patches are applied, not the entire Sury package or its unrelated updates. Selected file sections are extracted without modifying their hunks. Full original patches, applied sections, source checksums, Sury changelog, configure options and the original extension inventory are retained in `/usr/local/share/php-security/`. + +The builder recompiles CLI, FPM, OPcache and any shared extensions selected by the original configure command. Other extension binaries, including vendor loaders and debugging extensions when present, remain inherited from the pinned image. Updated headers and `/usr/src/php.tar.xz` support downstream extension builds. Obsolete signatures of the original source archive are removed. + +`session.upload_progress.enabled=0` is set at startup through `zz-security.ini`, following the [PHP advisory](https://www.php.net/archive/2026.php). OOM now exits the worker immediately; shutdown callbacks may not run. The multipart part limit defaults to `max_input_vars + max_file_uploads`. + +## Compatibility and validation + +The original entrypoint, Supervisor, web server, FPM configuration, `/opt/docker` paths, `/app`, user IDs, ports, mounts and environment handling are inherited. The build requires exact CLI extension-name parity. The smoke test checks FPM extension parity allowing its SAPI-specific `cgi-fcgi` registration, web server/FPM configuration, HTTP, file upload, document root, mounted INI, memory environment values, PHP_DISMOD, OOM callback suppression and subsequent HTTP availability. + +```sh +docker build --platform linux/amd64 -t devitools/php-nginx:8.1 php-nginx/8.1 +python3 php-nginx/8.1/.scripts/smoke.py devitools/php-nginx:8.1 +``` + +For deployment, replace the Compose image reference and recreate the container while keeping application configuration. Custom INI mounts must preserve the disabled upload-progress setting. Encoded applications, vendor tools and application-specific extension behavior need staging validation. PHP remains end of life; this is a compatibility bridge for the named CVEs, not a complete security update or a maintained PHP distribution. diff --git a/php-nginx/8.1/variants.yaml b/php-nginx/8.1/variants.yaml new file mode 100644 index 0000000..52d34f6 --- /dev/null +++ b/php-nginx/8.1/variants.yaml @@ -0,0 +1,3 @@ +- target: php-nginx + suffix: "" + platforms: linux/amd64