Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 104 additions & 1 deletion docker/base-php/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,106 @@
# https://hub.docker.com/layers/library/php/7.4-apache/images/sha256-c44681664d111addef2a2b6598901609990d45047b874c87d7d40fc7ce269195
FROM php:7.4-apache

# Debian 11 (bullseye) reached end of life in August 2026 and its packages,
# including bullseye-security, now live only on archive.debian.org. Without
# this every apt-get below fails with 404s.
RUN set -eux; \
sed -i \
-e 's|http://deb.debian.org/debian-security|http://archive.debian.org/debian-security|g' \
-e 's|http://deb.debian.org/debian|http://archive.debian.org/debian|g' \
/etc/apt/sources.list; \
echo 'Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/99-archive-debian

# ---------------------------------------------------------------------------
# Rebuild PHP from the PHP security team's minimal backport branch for
# CVE-2026-109618 (RCE via OOM corruption) and CVE-2026-109619 (RCE via stale
# global state). See https://www.php.net/archive/2026.php#2026-10-21-5
#
# PHP 7.4 is end-of-life, so there is no 7.4 release containing the fix and the
# official php:7.4-apache image will never be rebuilt. The backport branch is
# https://github.com/php/php-src/tree/PHP-7.4-CVE-2026-109618-109619
#
# We recompile PHP in place, on top of the official image, using the exact
# configure flags the official image was built with (php-config
# --configure-options) so the layout, apache module, ini dirs and extension
# tooling (docker-php-ext-*, install-php-extensions) keep working unchanged.
# Extensions are installed further down and so build against the new headers.
#
# To move to a newer commit on the branch, update PHP_SRC_REF to the new SHA.
# ---------------------------------------------------------------------------
ARG PHP_SRC_REPO=https://github.com/php/php-src.git
ARG PHP_SRC_BRANCH=PHP-7.4-CVE-2026-109618-109619
ARG PHP_SRC_REF=712e60f9edfd03f0960fc4fc3c2c375f42b90aa2
RUN set -eux; \
savedAptMark="$(apt-mark showmanual)"; \
apt-get update; \
apt-get install -y --no-install-recommends \
git \
bison \
apache2-dev \
libargon2-dev \
libcurl4-openssl-dev \
libonig-dev \
libreadline-dev \
libsodium-dev \
libsqlite3-dev \
libssl-dev \
libxml2-dev \
zlib1g-dev \
; \
\
git clone --depth 1 --branch "$PHP_SRC_BRANCH" "$PHP_SRC_REPO" /usr/src/php-src; \
cd /usr/src/php-src; \
# fail the build if the branch has moved away from the reviewed commit
test "$(git rev-parse HEAD)" = "$PHP_SRC_REF"; \
\
export \
CFLAGS="$PHP_CFLAGS" \
CPPFLAGS="$PHP_CPPFLAGS" \
LDFLAGS="$PHP_LDFLAGS" \
; \
debMultiarch="$(dpkg-architecture --query DEB_BUILD_MULTIARCH)"; \
# https://bugs.php.net/bug.php?id=74125
if [ ! -d /usr/include/curl ] && [ ! -e /usr/local/include/curl ]; then \
ln -sT "/usr/include/$debMultiarch/curl" /usr/local/include/curl; \
fi; \
\
./buildconf --force; \
eval ./configure "$(php-config --configure-options)"; \
make -j "$(nproc)"; \
find -type f -name '*.a' -delete; \
make install; \
find \
/usr/local \
-type f \
-perm '/0111' \
-exec sh -euxc ' \
strip --strip-all "$@" || : \
' -- '{}' + \
; \
cp -v php.ini-* "$PHP_INI_DIR/"; \
cd /; \
rm -rf /usr/src/php-src; \
\
# remove build dependencies again, same approach as the official image
apt-mark auto '.*' > /dev/null; \
[ -z "$savedAptMark" ] || apt-mark manual $savedAptMark; \

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It's possible for this to fail if the $savedAptMark variable is extremely long. In theory, to be safe, it should be piped through xargs, which would split the command into two (or more) commands if the argument length is too long. But it would take thousands of manually-installed packages to exceed the default 2 MB limit for command line length, so that's not a serious concern here.

This can stand as-is.

find /usr/local -type f -executable -exec ldd '{}' ';' \
| awk '/=>/ { print $(NF-1) }' \
| sort -u \
| xargs -r dpkg-query --search \
| cut -d: -f1 \
| sort -u \
| xargs -r apt-mark manual \
; \
apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \
Comment on lines +89 to +97

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is attempting to trim the volume down to the minimally-required set of packages. Normally I would say this is worth doing, but it's possible that this might end up removing some Debian packages that we assume are there. I would actually remove this find command and the apt-get purge (and then the savedAptMark and apt-mark auto business can go away too), and just remove the packages that were explicitly installed by the script, such as git and the various *-dev packages. If the purpose is to reduce the available attack surface, then only removing git might actually make more sense.

However, that can also wait for later. For now, this should work, so let's let this also stand as-is. If it turns out that this ends up removing something that LfMerge relied on, we can switch to just uninstalling git and nothing else.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Note that LfMerge runs in its own container, so removing things from the app container shouldn't affect it. So I should have said "something that Language Forge code relied on". Which we'll find out when we try to test this.

rm -rf /var/lib/apt/lists/*; \
\
# smoke test: the rebuilt binary and apache module must load
php --version; \
php -m; \
apache2ctl -M | grep -q php7_module

# install apt packages
# gnupg - for installing Mongo GPG key below
# git - for use by composer to install dependencies
Expand All @@ -21,7 +121,10 @@ RUN apt-get update && apt-get -y install mongodb-org-tools mongodb-mongosh && rm
# see https://github.com/mlocati/docker-php-extension-installer
# PHP extensions required by the LF application
COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/
RUN install-php-extensions mongodb intl @composer
# mongodb is pinned to the version the previously published image shipped with:
# 1.20 deprecates the integer ReadPreference modes still used by the
# mongodb/mongodb 1.15 library in composer.lock, which floods the logs.
RUN install-php-extensions mongodb-1.19.0 intl @composer

# php customizations
COPY docker/base-php/customizations.php.ini $PHP_INI_DIR/conf.d/
Expand Down
6 changes: 6 additions & 0 deletions docker/base-php/customizations.php.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,9 @@ memory_limit = 256M
post_max_size = 60M
upload_max_filesize = 60M
xdebug.log_level = 0

; Defence in depth for CVE-2026-109619 (RCE via stale global state).
; The session upload-progress feature is one of the confirmed attack paths and
; Language Forge does not use it. Must be set here (not via ini_set) so it
; applies before request initialisation.
session.upload_progress.enabled = Off
6 changes: 6 additions & 0 deletions docs/RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ Update the image tags in `prod/kustomization.yaml`
Production deployments can be manually run with `make deploy-prod`.
Note, this command assumes that the staging k8s context is named aws-rke

### Base PHP image

The `app` image is built on top of `sillsdev/web-languageforge:base-php`, which is **not** rebuilt by the staging or production workflows. It is built and pushed only by the manual workflow `.github/workflows/build-and-publish-base-php.yml` (Actions, "Build and publish a base PHP image for LF", "Run workflow", pick the branch).

Rebuild and publish it whenever `docker/base-php/` changes, **before** merging that change to `develop` or `master`, otherwise staging and production will be built on the old base image. The PHP interpreter itself is compiled from a pinned php-src commit in `docker/base-php/Dockerfile` (see the comments there); to pick up a newer security backport, update `PHP_SRC_REF`, re-run the base PHP workflow, then release as normal.

### Revert

Various tagged images are maintained in Docker Hub. If you need to revert to a previous version, you can do so by running the deployments scripts with the appropriate permissions or utilizing the Kubernetes UI to change the image of a deployment at any time.
Expand Down
Loading