September 14, 2022·6 min read·engineeringrustsecuritycargoarchives

Cargo archive extraction crossed metadata and size boundaries

Two Cargo vulnerabilities affect malformed packages from alternate registries. A package-controlled completion marker could redirect a write, while unchecked decompression could exhaust disk space.

A package manager must extract files before it can compile their source. That preparatory work has its own authority over the machine, including paths the manager creates and the disk space an archive consumes. Cargo's September advisory describes two defects in that phase, before any build script or procedural macro needs to run.

The 14 September advisory identifies CVE-2022-36113 and CVE-2022-36114 in malformed packages from alternate registries. It says crates.io users are not affected by these particular defects because the registry already rejects these package forms. Fixes are planned for Rust 1.64 on 22 September, with patches available for people building Rust 1.63 toolchains.

I find Rust's ownership model valuable for systems code, but an archive name is still input interpreted through filesystem operations. These flaws concern the authority of the package manager while materializing source. A safe language does not by itself establish that the paths and resource limits match the manager's intended policy.

Completion metadata became package-controlled input

Cargo writes a .cargo-ok marker after successfully extracting a package. The advisory explains that a malicious archive could supply that name as a symbolic link. Cargo would extract it and then write its completion marker through the path, modifying the file named by the link.

The write was specific. It replaced the first two bytes of the target with the marker text. That is arbitrary-file corruption through a constrained write, rather than an interface for replacing a target with unrestricted attacker-chosen content. Preserving that distinction explains the actual operation the vulnerability exposed.

The problem comes from sharing a namespace between extracted contents and manager-owned state. The archive controls entries being materialized, while the package manager assumes it controls the completion path. If both can create the same path, an apparently internal bookkeeping write can operate on an object supplied by the package.

This is different from accepting a filename containing a parent-directory component. A path that appears local to the extracted package can still be a symbolic link whose target has another location. Reviewing the lexical name alone does not establish what object a later open operation will reach.

That is why completion metadata deserves a separate rule. The marker records a statement made by Cargo about the extraction operation. The package should not be able to install the object through which Cargo records that statement.

The patch reserves the marker name

The published file-corruption patch skips archive entries whose filename matches the marker name. It also changes marker creation to require a new file. The two changes address the package's opportunity to provide the path and the manager's behavior if something already exists there.

The first step prevents Cargo from installing the marker from the archive. The second makes the completion operation fail rather than reuse an existing object at that path. Together they express that Cargo creates this metadata itself after the extraction has finished.

The ordering matters. The patch leaves marker creation after unpacking, preserving its meaning as an indication that the operation completed. Moving it earlier would change that meaning and could leave a success marker for an incomplete source tree.

My preference is for internal state to have a creation rule that matches its ownership. A comment saying that a file is metadata does not stop another input from creating it first. The patch turns that expectation into extraction and creation behavior.

The code also illustrates why opening an existing path is not a neutral choice. An API flag that permits creation if necessary may also permit using an already existing filesystem object. When the path's previous creator matters to the policy, those two cases need different treatment.

Requiring a new file also makes an unexpected existing object observable as an error. That result gives the caller a reason to stop instead of recording success through a path it did not create. The important property is the creation operation's behavior when the name already exists, which can be assessed directly in the patch without assuming that the path is an ordinary file.

Compression hid the eventual disk demand

CVE-2022-36114 concerns the absence of a limit on extracted data. A small compressed archive can describe substantially more uncompressed content. Restricting the download size therefore does not establish how much disk space extraction will require.

The advisory describes an attacker uploading such an archive to an alternate registry and exhausting the downloader's filesystem. That identifies the relevant trust boundary. The problem occurs while Cargo downloads and extracts a selected dependency, regardless of whether the application later executes the dependency's ordinary API.

This distinction matters when assessing package-manager actions. Fetching source is often treated as less consequential than building or running it. It still writes files, interprets archive metadata and consumes storage. The absence of compilation does not make those effects disappear.

The resource being bounded also needs a unit. Compressed transfer bytes, decompressed stream bytes and extracted payload sizes describe related but different quantities. A correct explanation needs to identify where the implementation enforces its budget instead of saying only that packages now have a size limit.

The patch limits the decompressed stream

The published extraction-size patch sets a limit of 512 times 1024 times 1024 bytes. It wraps the gzip decoder in a limiting reader before constructing the tar archive reader. The enforced count therefore follows bytes produced by decompression and consumed as the archive stream.

The patch's description says it adopts the same protection and size allowance used by crates.io. This is a client-side check for the registry extraction path, which helps when the selected registry does not perform the corresponding server-side validation.

The helper uses a reader with a remaining allowance. When a read returns zero because that allowance is exhausted, it converts the condition into an error describing that the maximum limit was reached. That prevents the budget boundary from looking like an ordinary end of input.

The error is important because reaching a configured maximum is not evidence that the archive ended correctly. An extraction path should distinguish a complete stream from one that stopped because the client refused more bytes. Otherwise subsequent logic could mistake a partial operation for successful completion.

These two patches therefore interact conceptually. Resource-limited extraction must report failure when it stops prematurely, and the completion marker must remain a statement produced only after successful extraction. The patches preserve those roles at separate points in the operation.

Registry validation and client validation have different scope

The advisory states that crates.io implemented checks against these packages years earlier and had no packages exploiting the defects. That narrows the immediate exposure without establishing that every registry enforces identical archive rules. Alternate registries are the population the advisory identifies for these vulnerabilities.

Client checks are useful because the extraction operation occurs on the user's machine. A registry's validation can prevent malformed artifacts from reaching that path, while the client can also reject them at the point where their effects would occur. The two checks protect different locations in the distribution process.

The advisory rates the issues low in the context of alternate-registry use. It explains that Cargo intentionally permits code execution through build scripts and procedural macros, so malicious dependencies already have other opportunities to cause damage. This is part of the disclosed assessment, not a claim that archive corruption is desirable or harmless.

The fixes do not make malicious dependencies trustworthy

Rejecting a marker link and bounding decompression remove these particular extraction behaviors. They do not change what a deliberately malicious build script can do when the project later builds the dependency. Package trust remains relevant after the extraction defects are fixed.

The response therefore does not include a point release backporting the fixes. As of the disclosure date, the announced distribution path is the planned 1.64 release and published patches for toolchain builders. That is a statement about available remediation on 14 September, not a report that the later release has already happened.

I regard the marker's ownership as the clearest technical point in this disclosure. The package manager needs to distinguish files supplied by a package from state it creates about processing that package. The size patch applies a related discipline to resources by measuring the decompressed work at the extraction boundary where Cargo can enforce it.