psf-haproxy-check(8)
HAProxy truncated HTTP/2 PING frame denial of service: checker (v1)
CVE-2026-110353
If this software is helpful, consider a donation to our 501c3 nonprofit:
https://privacysafe.foundation
HAProxy's HTTP/2 multiplexer did not treat a truncated PING frame as a short
read. A PING frame cannot be consumed until it is complete, but its presence
was enough to wake the connection up, so the worker thread never went back to
idle. A remote, unauthenticated client that opens a connection and keeps a
partial PING frame pending can hold a thread at 100% CPU for as long as the
connection stays open (or until "timeout http-request" expires, when one is
set). The impact is moderate and grows with the share of such connections
among all concurrent connections.
AFFECTED RANGE: two sources disagree.
The CVE description says "3.2 before 3.4.6". HAProxy's own release notes say
the bug was introduced in 3.1 (the short-read handling was fixed in 2.5 but
PING was missed, and it only became a problem in 3.1 when the wake-up logic
changed), and that the fixes shipped on 14 October 2026 in 3.4.7, 3.3.17 and
3.2.26. 3.4.6 is therefore NOT fixed. This script follows the vendor:
3.1.x affected, branch is end of life: move to a supported branch
3.2.x affected before 3.2.26
3.3.x affected before 3.3.17
3.4.x affected before 3.4.7
3.0 and older outside this CVE's range (the vendor says 3.1 introduced it)
3.5-dev development builds: check the commit yourself
The same releases fix a second CPU-exhaustion flaw in the HTTP/1 parser (a
chunked request with a chunk-size line longer than the buffer). The vendor
does not state its affected range in the release notes, so this script does
not rule any HTTP-mode HAProxy out because of it.
Vendor workaround (documented for the PING flaw): setting "timeout http-request"
to a low value such as 1 to 5 seconds limits the CPU consumption.
HTTP/2 reachability, which decides exposure: TLS listeners offer HTTP/2 only
when the bind line has "alpn h2". Clear-text HTTP listeners accept the HTTP/2
connection preface by default and upgrade the connection, unless the frontend
has "option disable-h2-upgrade". "proto h2" on a bind line forces HTTP/2.
"check" is read-only; "mitigate" and "rollback" are the only commands that change
anything. "check" finds HAProxy binaries (PATH, usual locations and
running processes), reads the version from "haproxy -v", reads the
configuration files the running process was started with (or the defaults) and
reports HTTP/2 reachability and the http-request timeout. It never reloads or
restarts HAProxy.
Usage:
./haproxy-check.sh [command] [PATH-TO-haproxy ...]
check (default) read-only. Exit 0 = nothing exposed, 1 = an affected version
is exposed or its configuration could not be read.
mitigate [options] (root) changes the host. Applies the vendor's workaround
for the PING flaw: adds "timeout http-request 5s" to every
"defaults" section that has no such line (it never lowers or
changes a value you set). It limits CPU use; it does NOT fix
the bug, so upgrade as well. The configuration is checked with
"haproxy -c" before and after, every file is copied first, and
if HAProxy rejects the result the originals are put back at
once. Nothing is reloaded unless you add --reload. Options:
--timeout SECONDS value to add, 1 to 60 (default 5)
--disable-h2-upgrade also add "option disable-h2-upgrade"
to those sections, so clear-text
connections cannot be upgraded to
HTTP/2. Leaves "alpn h2" on TLS binds
alone. Breaks only clients that use
HTTP/2 over clear text (h2c).
--dry-run show the changes, change nothing
--reload graceful reload afterwards (systemd)
--force apply even if this version is outside the affected
range (3.1 to 3.4 before the fixed releases)
rollback [--dry-run] [--reload] put back every file mitigate changed.
version print the script version.
STATE_DIR=/var/lib/psf-security-patches/haproxy holds the backups and manifest.
HAPROXY_CFG (space separated files or directories) replaces configuration discovery.
Containers: with no argument the check also looks in running docker and podman containers
(SCAN_CONTAINERS=1, the default; 0 skips them). A read-only probe captures the daemon's version
output and its configuration (secrets are redacted inside the container before anything is
copied) into a private temporary directory, and this script then runs once per container, in
isolation, against that copy. Paths shown as .../docker_NAME/... are inside the container NAME.
PSF_LOCK_WAIT seconds a run that changes the host waits for another one that is already
changing it (default 30), then stops with exit 2; the lock is STATE_DIR/.lockThe Security Patches software and this documentation are dedicated to the public domain under CC0 1.0 Universal. Other pages on privacysafe.app/docs are CC BY-SA 4.0 unless the page says otherwise.