Skip to content

Patch PHP 7.4 for CVE-2026-109618 and CVE-2026-109619 on Alpine 3.15 - #47

Merged
rick-lam merged 7 commits into
masterfrom
46/patch-php74-cve-2026-109618
Oct 22, 2026
Merged

rick-lam merged 7 commits into
masterfrom
46/patch-php74-cve-2026-109618

Conversation

@rick-lam

Copy link
Copy Markdown
Contributor

Summary

Fixes #46

graze/php-alpine:7.4 runs PHP 7.4.26 on Alpine 3.13, which is affected by the critical remote code execution vulnerabilities CVE-2026-109618 and CVE-2026-109619 (https://www.php.net/archive/2026.php#2026-10-21-5). No Alpine release ships a fixed PHP 7.4 (3.15 was the last with php7, at 7.4.33-r1, and it is EOL), so this builds Alpine 3.15's own php7 recipe with the upstream fix and moves the image to Alpine 3.15. dispatch and stats web run mod_php on images built from this one.

Notable Changes

  • 7.4/aports/community/php7: Alpine 3.15's php7 recipe, first added unchanged (every file matches its sha512sums) so the following commits show only our changes.
  • The fix is php-src 712e60f from the PHP security team's PHP-7.4-CVE-2026-109618-109619 branch, minus four hunks that re-add max_multipart_body_parts (CVE-2023-0662), which Alpine already carries. All 15 Alpine patches are kept, and the added lines are identical to ppa:ondrej/php 7.4.33-31, which our EC2 boxes have run since 22 Oct. pkgrel goes to r2 so apk prefers our build.
  • The fix changes behaviour on purpose: out-of-memory errors are written to stderr and the process exits with _exit(1) (no shutdown functions, exit code 1 not 255), and session upload progress is switched off. Nothing in web, stats or planning-api uses session upload progress.
  • Test suite: a full run with and without the fix differs by exactly 9 tests, all from the fix. tests/lang/bug45392.phpt now expects a blank line before "Fatal error", and the 8 ext/session/tests/rfc1867*.phpt tests are disabled because the feature is off. 4 tests that fail with and without the fix in Docker (curl_basic_009, date/bug73837, streams/opendir-003 and 004) are also disabled. Everything else runs.
  • 7.4/Dockerfile: a builder stage runs abuild as a non-root user (as root, about 40 opcache preloading tests fail). The image moves to alpine:3.15 and keeps the signed packages as a local repo at /opt/php7-apk, listed first, so images built on top (for example php7-apache2 in graze/new-php) also get r2. /opt, not /srv, because new-php declares /srv as a volume.
  • gnu-libiconv 1.16 on Alpine 3.15 no longer ships preloadable_libiconv.so, so iconv(... '//TRANSLIT') returned false (Utils::slugify in graze/web would produce empty slugs). It is pinned to 1.15-r3 from the v3.13 repo, the package the current image uses. The pin uses a one-off --repository rather than a tagged repo, because a tag breaks later apk del calls without --no-cache (dispatch's Dockerfile does that).
  • Two new tests in 7.4/php.bats check that php7 is the r2 build and that php7-apache2 installed later resolves to r2. Both fail against the current graze/php-alpine:7.4.
  • CI is slower: the 7.4 build now compiles PHP and runs its test suite, about 10 minutes per architecture.

QA

Done locally on arm64 (CI builds amd64 natively):

  1. docker build 7.4/: abuild's full PHP test suite passes. Every core php7 package is 7.4.33-r2, PECL extensions come from Alpine 3.15.
  2. bats: common/php-arm64.bats, common/php.bats, 7.4/php.bats 19 of 19 pass, including iconv //TRANSLIT. Debug image: php7-phpdbg is r2 and 7.4/php_debug.bats passes.
  3. Control: the two new tests fail against the current image (php7-common-7.4.26-r0).
  4. Built graze/new-php:7.4 cli and apache on top (graze-docker new-php/7.x, unchanged): 13 + 13 graze-docker bats tests pass, php7-apache2 is r2, php7_module loads, Apache 2.4.58.
  5. Built the dispatch php74 image on that new-php: imagick 3.8.0 compiles against our php7-dev and renders, gd and iconv work, /healthCheck.php returns 200 through Apache.
  6. Built the stats web image (graze/stats#326) on this base: /healthCheck.php returns 200 OK 7.4.33 through mod_php.

For the reviewer: check the CI build for both architectures and that apk info -v | grep ^php7-common- in the pushed image shows 7.4.33-r2.

Deployment

  1. Merge: CI pushes graze/php-alpine:7.4 and 7.4-test.
  2. Rebuild graze/new-php:7.4 (cli and apache) from graze-docker.
  3. Release graze/web so the dispatch php74 image rebuilds on the new new-php, then deploy dispatch UAT, staging, live.
  4. Merge graze/stats#326 and release stats.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Uugex1w8qZoTbBRbcFwkcp

rick-lam and others added 5 commits October 22, 2026 03:33
Verbatim copy of community/php7 from aports 3.15-stable (php 7.4.33-r1),
fetched from git.alpinelinux.org. Every file matches the sha512sums in the
APKBUILD. The next commit adds the CVE fix on top, so its diff shows only
our changes.

Refs #46

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uugex1w8qZoTbBRbcFwkcp
Adds the upstream EOL fix (php-src 712e60f, "Avoid state corruption via
bailout") to the Alpine 3.15 php7 recipe and bumps pkgrel to r2 so apk
prefers our build over Alpine's r1.

The upstream branch is based on plain 7.4.33, so its fix also introduces
max_multipart_body_parts (CVE-2023-0662). Alpine already carries that in
CVE-2023-0662-2.patch, so those four hunks are dropped and every Alpine
patch is kept. The result applies cleanly on top of all 15 Alpine patches
and its added lines are identical to ppa:ondrej/php 7.4.33-31, which ships
the same fix.

Refs #46

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uugex1w8qZoTbBRbcFwkcp
A full test run with and without the fix (same recipe, non-root, arm64)
differs by exactly nine tests, all caused by the fix on purpose:

- tests/lang/bug45392.phpt: out-of-memory errors are now written straight
  to stderr before _exit(1), which adds a blank line before "Fatal error".
  The expected output now allows it, so the test still checks that
  buffered output is not leaked when memory runs out.
- ext/session/tests/rfc1867*.phpt (8 tests): the fix turns session upload
  progress off entirely, so these tests check a feature that is gone.
  Disabled.

Four more fail with and without the fix when built in Docker
(curl_basic_009, date bug73837, streams opendir-003 and opendir-004), so
they are disabled too.

Refs #46

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uugex1w8qZoTbBRbcFwkcp
A builder stage builds the patched php7 recipe as a non-root user
(running abuild as root makes the opcache preloading tests fail) with
Alpine's test suite enabled. The image then moves to alpine:3.15 and keeps
the signed packages as a local repo in /opt/php7-apk, listed first, so
images built on top get the patched 7.4.33-r2 too, for example
php7-apache2 in graze/new-php. /opt rather than /srv because new-php
declares /srv as a volume.

gnu-libiconv 1.16 on Alpine 3.15 no longer ships preloadable_libiconv.so,
so the LD_PRELOAD fix stopped working and iconv //TRANSLIT returned false
(Utils::slugify would produce empty slugs). It is pinned to 1.15-r3 from
the v3.13 repo, the same package the current image uses.

Two new tests check that php7 is the r2 build and that php7-apache2
added later resolves to r2. Both fail against the current
graze/php-alpine:7.4 and pass here, as do all 19 bats tests.

Refs #46

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uugex1w8qZoTbBRbcFwkcp
A tagged repository (@alpine313) put "gnu-libiconv@alpine313" in the apk
world file. Images keep no package indexes, so any later apk call
without --no-cache could not see the tag and refused to commit. The
dispatch image hit this at "apk del .imagick-build-deps".

Installing gnu-libiconv=1.15-r3 with a one-off --repository leaves a
plain version pin that apk satisfies from the installed package. Checked
by building graze/new-php:7.4 (cli and apache) and the dispatch php74
image on top: imagick 3.8.0 compiles and renders, iconv //TRANSLIT works,
mod_php loads, and the dispatch health check returns 200. All 19 bats
tests pass.

Refs #46

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uugex1w8qZoTbBRbcFwkcp
Copilot AI balanced review requested due to automatic review settings October 22, 2026 03:31
@rick-lam rick-lam self-assigned this Oct 22, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The signing-key exposure needs correction, and the end-of-life runtime security backport requires human sign-off on architecture-specific validation.

1 open issue
What changed in this PR

Updates the PHP 7.4 base image to address CVE-2026-109618 and CVE-2026-109619 while preserving compatibility with downstream CLI and Apache images.

Changes:

  • Builds patched PHP 7.4.33-r2 on Alpine 3.15 using Alpine’s existing recipe and patches.
  • Retains signed packages in a local repository for downstream installs and pins libiconv to preserve transliteration.
  • Adds package-selection checks and adjusts PHP tests for the mitigation’s intentional behavior changes.
File Description
7.4/​php.bats Checks installed and downstream package revisions.
7.4/​Dockerfile Builds patched packages and configures the runtime image.
7.4/​aports/​community/​php7/​xfail-openssl-1.1-test.patch Retains Alpine’s OpenSSL test adjustment.
7.4/​aports/​community/​php7/​sharedir.patch Sets the packaged PHP include path.
7.4/​aports/​community/​php7/​php7-module.conf Supplies Apache PHP module configuration.
7.4/​aports/​community/​php7/​php7-fpm.logrotate Supplies FPM log rotation configuration.
7.4/​aports/​community/​php7/​php7-fpm.initd Supplies the FPM OpenRC service script.
7.4/​aports/​community/​php7/​php7-fpm-version-suffix.patch Uses PHP 7-specific FPM paths.
7.4/​aports/​community/​php7/​install-pear.patch Enables XML during PEAR installation.
7.4/​aports/​community/​php7/​includedir.patch Uses versioned development-header paths.
7.4/​aports/​community/​php7/​fix-tests-devserver.patch Filters development-server test output.
7.4/​aports/​community/​php7/​fix-fpm-tests.patch Adjusts FPM regression-test logging.
7.4/​aports/​community/​php7/​fix-fpm-syntax.patch Makes backported tests PHP 7-compatible.
7.4/​aports/​community/​php7/​fix-curl-7.83-test.patch Accommodates changed curl error text.
7.4/​aports/​community/​php7/​enchant-2.patch Retains Enchant 2 compatibility.
7.4/​aports/​community/​php7/​disabled-tests.list Records inherited and mitigation-related test exclusions.
7.4/​aports/​community/​php7/​CVE-2026-109618-109619.patch Adds the runtime security mitigation.
7.4/​aports/​community/​php7/​CVE-2026-109618-109619-tests.patch Updates the memory-exhaustion output expectation.
7.4/​aports/​community/​php7/​CVE-2023-0662-2.patch Retains multipart limits and regression tests.
7.4/​aports/​community/​php7/​CVE-2023-0662-1.patch Retains upload-limit warning protection.
7.4/​aports/​community/​php7/​CVE-2023-0568.patch Retains path-buffer bounds fixes.
7.4/​aports/​community/​php7/​CVE-2023-0567-2.patch Retains short bcrypt hash validation.
7.4/​aports/​community/​php7/​CVE-2023-0567-1.patch Retains malformed bcrypt salt rejection.
7.4/​aports/​community/​php7/​APKBUILD Defines the patched build, tests, subpackages, and checksums.

Review effort: Balanced

Comment thread 7.4/Dockerfile Outdated
Copilot stopped work on behalf of rick-lam due to an error October 22, 2026 03:37
rick-lam added a commit that referenced this pull request Oct 22, 2026
The build and manifest jobs needed every test in the matrix to pass, so
one failing version stopped every version from publishing. Since 22 Oct
2026 pecl.php.net returns bad REST data for the 5.6, 7.0, 7.1, 7.2, 8.3
and 8.4 builds, which blocks the PHP 7.4 security fix in #47 even though
its own test passes.

With !cancelled() each version builds and publishes on its own. A version
whose build breaks fails only its own build and manifest jobs. Every PR
still runs all the tests.

Fixes #48


Claude-Session: https://claude.ai/code/session_01Uugex1w8qZoTbBRbcFwkcp

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The master build exports every layer to the registry build cache
(mode=max), so the builder layer would have carried the private key used
to sign the php7 packages. Anyone pulling the cache could then sign
packages and indexes that the images trust.

The key is now removed in the same RUN that generates and uses it, so no
layer contains it. The public key stays for the runtime image. Checked
locally: none of the five builder-stage layers contains a private .rsa
(a control tar with one is detected), and all 19 bats tests pass.

Refs #46

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uugex1w8qZoTbBRbcFwkcp
rick-lam added a commit that referenced this pull request Oct 22, 2026
Every PR built and tested all versions in versions.json, so a change to
one version's folder (such as #47, only 7.4/) also showed unrelated
failures from other versions.

On pull requests the matrix job now diffs the PR against its base. If
every changed file is inside a version folder listed in versions.json,
only those versions are tested. Any other change (common/, the workflow,
the Makefile, versions.json, the README), an unknown folder, or a failed
diff tests every version as before. Pushes to master are unchanged.

Fixes #51


Claude-Session: https://claude.ai/code/session_01Uugex1w8qZoTbBRbcFwkcp

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@rick-lam
rick-lam merged commit ce785c3 into master Oct 22, 2026
4 checks passed
@rick-lam
rick-lam deleted the 46/patch-php74-cve-2026-109618 branch October 22, 2026 04:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Patch the PHP 7.4 image for CVE-2026-109618 and CVE-2026-109619

2 participants