---
title: "CVE-2024-3094: How a Backdoored XZ Release Reached OpenSSH"
description: "A clear reconstruction of the XZ Utils supply-chain attack: what was changed, how malicious release tarballs altered liblzma, why selected OpenSSH servers became reachable, and which systems were actually exposed."
date: 2025-03-27
analysisType: "Root Cause Analysis"
cves: ["CVE-2024-3094"]
author: Sevban Dönmez (@jankesec)
canonical: https://jankesec.com/research/xz-utils-release-artifact-cve-2024-3094/
---

## CVE-2024-3094 in 60 seconds

XZ Utils is a compression project used throughout the Linux ecosystem. Its `liblzma` library can
be loaded indirectly by processes that do not appear to use XZ themselves. In March 2024, XZ Utils
5.6.0 and 5.6.1 release tarballs were found to contain a deliberately constructed backdoor.

The attack did not simply add an obvious malicious source file. A trigger existed in the published
release archive, while additional stages were concealed inside files that looked like compression
test fixtures. Under a narrow set of Linux distribution build conditions, those pieces modified
the resulting `liblzma` library.

On some distributions, OpenSSH's `sshd` process loaded `libsystemd` for systemd notification, and
`libsystemd` in turn loaded `liblzma`. That transitive dependency carried the modified library into
the privileged SSH server before authentication. Public analysis showed the implant interfering
with the RSA verification path. Its intended result was unauthorized remote access — effectively
a pre-authentication backdoor for an attacker holding the corresponding secret key material.

The shortest accurate summary is:

```text
malicious XZ release tarball
  -> distribution package build activates hidden stages
  -> modified liblzma is installed
  -> selected sshd builds load liblzma through libsystemd
  -> SSH authentication path is intercepted
  -> attacker can seek unauthorized remote access
```

This was therefore both a **software supply-chain compromise** and an **OpenSSH authentication
attack path**. It was assigned CVE-2024-3094.

## What are XZ, liblzma, and sshd?

`xz` is a common command-line compression format and tool. `liblzma` is the reusable software
library behind much of that functionality. Package managers and operating-system components can
depend on the library even when administrators never invoke the `xz` command themselves.

`sshd` is the server process that accepts incoming SSH connections. OpenSSH did not directly
depend on XZ. The surprising connection existed on distributions that patched OpenSSH to notify
systemd when the service was ready:

```text
sshd -> libsystemd -> liblzma
```

That indirect edge is essential to understanding the incident. The attackers did not need XZ to
be an SSH component. They needed their modified library to be loaded into the SSH server process,
where it could influence authentication before a user logged in.

## What happened?

The public incident record supports the following sequence:

1. A contributor using the identity Jia Tan held release responsibility in the XZ project.
2. Binary-looking files were added to the repository as compression test fixtures. On their own,
   they did not activate the full backdoor.
3. XZ Utils 5.6.0 and 5.6.1 release tarballs were created and signed by Jia Tan. These downloadable
   archives included activation logic that was absent from an ordinary build of the corresponding
   Git source.
4. When a targeted x86-64 Linux package was built with the expected GNU toolchain and Debian or RPM
   packaging signals, the release-only logic extracted hidden stages from the test files.
5. Those stages changed the `liblzma` build. On a compatible runtime dependency path, the modified
   library entered `sshd` and redirected part of its RSA verification behavior.
6. PostgreSQL developer Andres Freund noticed that SSH logins on Debian unstable consumed unusual
   CPU time and produced Valgrind errors. His investigation traced the anomaly to the backdoored
   XZ release and was published on 29 March 2024.
7. Distributions removed or downgraded the affected packages, the compromised releases were
   withdrawn, and XZ Utils 5.6.2 was published as a cleanup release.

This chronology is why describing the incident merely as an “XZ vulnerability” is misleading. It
was not an accidental coding mistake found in normal compression logic. It was a patient,
selective attempt to make trusted release infrastructure produce a malicious library.

## Who was actually affected?

The presence of an `xz` package alone did not prove exposure. The known attack required a chain of
conditions:

- a package derived from the malicious 5.6.0 or 5.6.1 release tarball;
- the targeted x86-64 Linux and GNU build environment;
- compatible glibc and linker behavior;
- distribution packaging conditions that activated the hidden stages;
- an `sshd` runtime path that loaded the resulting `liblzma`, commonly through `libsystemd`.

The releases were caught before broad adoption by stable Linux distributions. Debian unstable,
Fedora Rawhide, Fedora 40 beta, and some openSUSE testing packages were central to the initial
response, but package presence and working exploit reachability were not identical. Red Hat stated
that RHEL was unaffected and that Fedora 40 beta contained affected XZ packages while the actual
malware exploit did not appear to activate in those builds.

For an operator, the correct question is not only “Is the version 5.6.x?” It is “Which exact
artifact was packaged, how was it built, and can the installed library reach the exposed SSH
process?”

## What I verified

This article now separates the public incident from my own validation boundary:

- The maintainer's incident record confirms that the 5.6.0 and 5.6.1 release tarballs contained
  the backdoor and were created and signed by Jia Tan.
- The cleanup commit says it removes the backdoor and deletes suspicious fixtures including
  `bad-3-corrupt_lzma2.xz` and `good-large_compressed.lzma`.
- The initial disclosure documents release-only activation logic, selective build conditions, the
  dependency path into `sshd`, and observed interception of RSA verification.
- I compared the clean-release notes, repository cleanup, initial disclosure, and distribution
  response instead of relying on a single narrative source.
- This remains a static artifact and patch reproduction. I did not unpack or execute malicious
  stages, trigger the implant, or claim host-level exploitation evidence.

## Deep dive: the release artifact was a separate security boundary

The initial oss-security report identified a crucial asymmetry. Part of the activation logic was
present in the 5.6.0 and 5.6.1 release tarballs but absent from the corresponding Git source. The
tarballs included a modified `m4/build-to-host.m4`; generated archives from the Git hosting service
did not contain the same injected line.

That release-only macro extracted additional stages from files that looked like compression test
inputs. Under selected build conditions, the stages altered the generated `liblzma` build. The
result was not simply “malicious source compiles into malicious software.” It was a multi-object
construction in which no single benign-looking object explained the final binary.

<figure class="diagram">
<svg viewBox="0 0 760 304" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="A clean-looking source tag and a modified release tarball diverge. The tarball adds a build macro that extracts hidden test data and changes the library linked into a server process.">
<defs><marker id="xz-a" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L8,4 L0,8 z" class="dg-arrow"/></marker></defs>
<text x="0" y="24" class="dg-accent">ARTIFACT DIVERGENCE</text>
<rect x="0" y="50" width="168" height="68" rx="8" class="dg-box"/><text x="16" y="78" class="dg-label">Repository tag</text><text x="16" y="100" class="dg-muted">reviewed source</text>
<line x1="170" y1="84" x2="218" y2="84" class="dg-line" marker-end="url(#xz-a)"/>
<rect x="222" y="50" width="168" height="68" rx="8" class="dg-box-accent"/><text x="238" y="78" class="dg-label">Git build</text><text x="238" y="100" class="dg-accent">implant absent</text>
<rect x="0" y="190" width="168" height="68" rx="8" class="dg-box-crit"/><text x="16" y="218" class="dg-label">Release tarball</text><text x="16" y="240" class="dg-crit">extra macro</text>
<line x1="170" y1="224" x2="218" y2="224" class="dg-line dg-crit-line" marker-end="url(#xz-a)"/>
<rect x="222" y="190" width="168" height="68" rx="8" class="dg-box-crit"/><text x="238" y="218" class="dg-label">Test fixtures</text><text x="238" y="240" class="dg-crit">hidden stages</text>
<line x1="392" y1="224" x2="440" y2="224" class="dg-line dg-crit-line" marker-end="url(#xz-a)"/>
<rect x="444" y="190" width="142" height="68" rx="8" class="dg-box-crit"/><text x="460" y="218" class="dg-label">liblzma</text><text x="460" y="240" class="dg-crit">modified build</text>
<line x1="588" y1="224" x2="622" y2="224" class="dg-line dg-crit-line" marker-end="url(#xz-a)"/>
<rect x="626" y="190" width="134" height="68" rx="8" class="dg-box-crit"/><text x="642" y="218" class="dg-label">sshd path</text><text x="642" y="240" class="dg-muted">pre-auth reach</text>
<line x1="390" y1="84" x2="626" y2="190" class="dg-line dg-dash"/>
<text x="444" y="82" class="dg-muted">Same version label, different security result</text>
</svg>
<figcaption>The decisive comparison is not only vulnerable commit versus fixed commit. It is signed release artifact versus the source tree users believed it represented.</figcaption>
</figure>

This is why “we built the tag and saw nothing” was a weak negative control. It exercised a
different input. For projects that publish generated files, the release archive is executable
supply-chain state and requires its own provenance, reproducibility, and review.

## Selection logic reduced noise and delayed discovery

The activation stage checked several environmental properties. Public analysis documented an
x86-64 Linux GNU build, GCC and GNU `ld`, and Debian or RPM packaging signals among the conditions.
The backdoor therefore avoided many developer workstations, alternative toolchains, direct Git
builds, and unsupported targets.

Selectivity served two purposes. First, it concentrated the payload where distribution packages
would place it in realistic dependency graphs. Second, it made failed reproduction look like
evidence of safety. An investigator changing the compiler, build entry point, architecture, or
packaging directory could silently take a clean branch.

The observation is reusable: anti-analysis does not require detecting a debugger. A malicious
build can simply demand a narrow set of normal production properties. Each property is reasonable
alone; their conjunction separates release infrastructure from casual inspection.

## The dependency edge into sshd was distribution-specific

OpenSSH did not directly depend on `liblzma`. The public report explains that some Linux
distributions patched OpenSSH for systemd notification. That integration pulled `libsystemd` into
the server, and `libsystemd` depended on `liblzma`. The implant exploited this transitive route.

This corrects two misleading inventories. A list of direct OpenSSH dependencies would miss the
edge. A fleet search for any XZ 5.6 package would overstate reachability because the malicious
archive, build conditions, architecture, libc, and linkage all mattered.

<figure class="diagram">
<svg viewBox="0 0 760 292" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="An exposure decision combines the exact release tarball, selected build conditions, a glibc Linux target, and a transitive dependency from patched sshd through libsystemd to liblzma.">
<text x="0" y="24" class="dg-accent">REACHABILITY, NOT VERSION PANIC</text>
<rect x="0" y="52" width="154" height="62" rx="8" class="dg-box-crit"/><text x="16" y="78" class="dg-label">5.6.x package</text><text x="16" y="98" class="dg-crit">which artifact?</text>
<rect x="202" y="52" width="154" height="62" rx="8" class="dg-box-crit"/><text x="218" y="78" class="dg-label">Build context</text><text x="218" y="98" class="dg-muted">conditions match?</text>
<rect x="404" y="52" width="154" height="62" rx="8" class="dg-box-crit"/><text x="420" y="78" class="dg-label">Runtime graph</text><text x="420" y="98" class="dg-muted">library reachable?</text>
<rect x="606" y="52" width="154" height="62" rx="8" class="dg-box-accent"/><text x="622" y="78" class="dg-label">Exposure</text><text x="622" y="98" class="dg-accent">all must hold</text>
<line x1="154" y1="83" x2="202" y2="83" class="dg-line dg-crit-line"/><line x1="356" y1="83" x2="404" y2="83" class="dg-line dg-crit-line"/><line x1="558" y1="83" x2="606" y2="83" class="dg-line"/>
<rect x="0" y="190" width="154" height="62" rx="8" class="dg-box"/><text x="16" y="216" class="dg-label">sshd</text><text x="16" y="236" class="dg-muted">patched build</text>
<rect x="303" y="190" width="154" height="62" rx="8" class="dg-box"/><text x="319" y="216" class="dg-label">libsystemd</text><text x="319" y="236" class="dg-muted">transitive edge</text>
<rect x="606" y="190" width="154" height="62" rx="8" class="dg-box-crit"/><text x="622" y="216" class="dg-label">liblzma</text><text x="622" y="236" class="dg-crit">modified object</text>
<line x1="154" y1="221" x2="303" y2="221" class="dg-line"/><line x1="457" y1="221" x2="606" y2="221" class="dg-line dg-crit-line"/>
</svg>
<figcaption>A package name is an inventory clue, not a complete finding. Artifact identity and the final runtime dependency graph decide whether the malicious code can reach the exposed process.</figcaption>
</figure>

## What the clean release changes

XZ Utils 5.6.2 explicitly removed the backdoor. The response also removed compromised test files,
reviewed release machinery, and restored project control. For operators, upgrading is necessary,
but replacement alone is not the full incident response. Systems that installed a malicious build
should be treated as having executed untrusted upstream code during packaging or runtime.

The important fixed property is **artifact equivalence with reviewed source**. A clean version
number helps only when the package came from a trusted channel and the distributor confirms its
provenance. Rebuilding a known-good source, verifying package signatures, and comparing distributor
advisories provide stronger evidence than searching for one file name left by the implant.

## Evidence matrix

| Question | What I checked | Confidence | Limit |
| --- | --- | ---: | --- |
| Which releases contained the backdoor? | I matched the project response and initial disclosure on the 5.6.0 and 5.6.1 tarballs. | High | Repacked downstream artifacts require distributor-specific verification. |
| Why could a Git build look clean? | I verified the reported divergence between release-only macro logic and repository source. | High | Mirrors and cached archives may preserve different combinations. |
| How did code reach sshd? | I reconstructed the distribution-specific path from sshd through libsystemd to liblzma. | High | The exact dependency graph differs by platform and build flags. |
| Was every XZ 5.6 installation exploitable? | I found no support for that blanket claim; artifact, build, target, and linkage were selective. | High | Public records cannot enumerate every custom build. |
| What release removes the known implant? | I checked the 5.6.2 cleanup release and its backdoor-removal commit. | High | A clean package does not by itself prove that an earlier compromise had no impact. |
| Does this analysis prove a host was accessed? | No. It establishes technical reachability from published evidence. | High | Host-specific impact requires logs, package provenance and forensic evidence. |

## Safe validation without loading the implant

Begin with package provenance, not a network probe. Record the installed package version, package
hash, repository channel, signing identity, installation time, and the distribution advisory. A
version string is insufficient because some distributions never shipped the compromised releases,
while development channels and manually imported tarballs may have done so briefly.

Next, reconstruct the build input. Compare the exact source archive against the distributor's
known-good manifest and inspect whether it contains the release-only macro and compromised fixture
history described by the maintainers. Perform this work on inert copies. Do not run `configure`,
decompress the embedded stages into a shell, or load the resulting library.

Then inspect the final binary relationship using static package metadata and dependency tools.
The question is whether the deployed server process could load the affected `liblzma`, directly or
transitively. This is safer and more precise than repeatedly connecting to SSH and interpreting
timing variance as proof.

Useful negative controls are:

- an archive generated directly from the clean Git tag rather than the malicious release tarball;
- XZ 5.6.2 from the same trusted distributor;
- a target whose server binary has no runtime path to `liblzma`;
- a package hash the distributor explicitly lists as unaffected;
- the same static inspection performed without executing any archive-provided build script.

Timing anomalies helped discover the incident, but they are not a reliable fleet verdict. CPU
load and login latency have many causes, while the implant deliberately varied its behavior under
analysis. Preserve timing as supporting telemetry, not the sole exposure decision.

## My conclusion

Replace affected packages with a trusted 5.6.2-or-later build or the distribution's documented
clean package. If a compromised artifact was installed, rotate relevant credentials and perform
incident response according to the process's exposure and available evidence; absence of a known
trigger in logs is not proof that the system remained untouched.

My conclusion is that repository review alone could not answer what downstream users actually
installed. The release tarball, generated build logic, distributor environment, and final library
relationship all had to be checked as one chain. Source review ends too early when users execute a
different artifact.

## Public sources

- [Andres Freund: initial oss-security disclosure](https://www.openwall.com/lists/oss-security/2024/03/29/4)
- [Tukaani Project: response to the XZ backdoor](https://tukaani.org/xz-backdoor/)
- [Red Hat: affected packages, attack path, and distribution scope](https://www.redhat.com/en/blog/urgent-security-alert-fedora-40-and-rawhide-users)
- [XZ Utils 5.6.2 release](https://github.com/tukaani-project/xz/releases/tag/v5.6.2)
- [CVE.org record for CVE-2024-3094](https://www.cve.org/CVERecord?id=CVE-2024-3094)