CVE-2024-3094 — xz-utils backdoor (Jia Tan / liblzma)
TL;DR: A multi-year social-engineering and supply-chain operation embedded a covert SSH-server backdoor in
liblzma(part ofxz-utils). The build script for the tarball releases (not the git tree) injected the malicious payload via test fixtures during build, then redirected theRSA_public_decrypt@@…symbol resolution at runtime to attacker-controlled code. Discovered by Andres Freund noticing 500 ms of extra latency in SSH logins on Debian unstable. The defining supply-chain incident of 2024. Companion to npm-postinstall-and-typosquat-audit and python-pypi-supply-chain-audit.
Why this matters
- It’s the closest a backdoor has come to mass-deploy in a base distro in recent memory.
- The technique is social-engineering-first, not vuln-first: a maintainer was groomed for two years.
- The build artifact differed from the git tree — a generic class of supply-chain risk no
gitaudit catches. - Detection was incidental (latency); without that observation, the rollout would have hit stable distros.
The kill chain at a glance
- Social grooming — “Jia Tan” appeared in 2021, contributed quality patches, built reputation. Eventually took over maintenance from the burnt-out original maintainer.
- Innocuous-looking build infra changes — adjustments to
m4/build-to-host.m4and binary test fixtures. - Backdoor lives in test fixtures — payload bytes shipped as
tests/files/bad-3-corrupt_lzma2.xzandgood-large_compressed.lzmablobs. - Build script extracts and links — only in tarball releases, only on x86_64 Linux with glibc + systemd, only when building a Debian/Fedora-style RPM/deb.
- Runtime hook — modifies the GNU IFUNC resolver for
RSA_public_decrypt. Whensshd(linked againstlibsystemdwhich linksliblzma) handles SSH pubkey auth, the hook intercepts. - Authentication backdoor — supplies an attacker-controlled certificate signing key; valid signatures from attacker bypass SSH authentication.
The bug class: build-time supply chain
This is not a code vulnerability in the conventional sense. The published source on GitHub is benign. The tarball release that distros pulled differed. Build scripts assembled the payload from blob fixtures.
Class: release-tarball / build-script supply chain.
Related: see npm-postinstall-and-typosquat-audit for the npm equivalent (postinstall scripts assemble malware) and python-pypi-supply-chain-audit for PyPI.
Why it almost worked
- The payload was conditional: only deployed when build matched expected target. Researchers / fuzzers running on different distros wouldn’t see it.
- The payload was encrypted: the test fixtures decompress to garbage unless the extractor logic runs.
- The trigger was passive: only activated when an attacker connected with the right certificate. No process visibility unless you knew what to look for.
- The maintainer was trusted: the GitHub project had legitimate contributors and a real release cadence.
- Tooling like reproducible-builds and SBOM was rolled out but not universally applied at distro level.
What detection caught it
Andres Freund (a Postgres maintainer at Microsoft) noticed sshd taking 500 ms longer per connection than expected. He traced the latency to liblzma, then to the tarball-only build script discrepancy. Disclosed quietly to distros, then publicly.
This is one engineer being curious about a 500 ms latency. It nearly didn’t happen.
What’s left in the codebase
The payload was caught before reaching most stable distros:
- Hit unstable / testing in Debian, Fedora Rawhide, Kali, openSUSE Tumbleweed, Arch.
- Did not reach Debian stable, Ubuntu LTS, RHEL.
But many derived rolling distros pulled it transiently. Operators still need to verify their build pipelines did not consume affected versions (5.6.0, 5.6.1).
Defensive lessons
- Reproducible builds at distro level. If the tarball doesn’t match the git tree’s deterministic build, fail.
- Bus-factor on critical OSS.
xz-utilshad one maintainer. - Sigstore / SLSA / in-toto provenance attestation. The attacker still needs to produce signed provenance.
- Behavioural baselining of long-running services for latency / CPU changes after patches.
- Don’t ship binaries in tests. The vector relied on opaque test blobs that auditors had no reason to inspect.
- Audit GNU IFUNC resolver usage in security-critical libraries.
Reproducing in a lab
Pre-built VMs and reproductions exist (Akamai, JFrog, Binarly, Trail of Bits). Do not run on a production host.
Method:
- Pull a copy of the
5.6.1tarball (mirrors). - Build under the conditions the payload checks for (x86_64 + glibc + deb/rpm build).
- Strace
sshdstartup and observe the runtime hook installation. - Connect with the published reproduction certificate (community-derived).
This is purely educational. The cert+payload pair was reverse-engineered after disclosure.
How a similar attack would look elsewhere
Other plausible vectors of the same shape:
- A widely-included npm package with a maintainer takeover and obfuscated
postinstall. - A PyPI package shipping a
.whlthat differs from the sourcesdist. - A Go module proxy serving a tarball that differs from the public
git tag. - A Maven artifact with a malicious
META-INFnot in the source.
All of these have happened at small scale. xz was the warning shot for the scale-out version.