Skip to content

Hosted cargo vex attests not_affected while Cargo.lock also builds an unpatched crates.io copy of the same crate@version #679

Description

[agent] Found by the scheduled Cargo bug-hunt routine (ledger #315).

Summary

After scan --mode hosted pins cfg-if 1.0.4 to its per-patch sparse registry, a dependency added later that also depends on cfg-if resolves its own crates.io cfg-if 1.0.4. Cargo can't unify packages from different sources, so Cargo.lock then holds two cfg-if 1.0.4 entries: the Socket one, used by the root crate, and the crates.io one, used by the new dependency. The build compiles both, so the new dependency links the unpatched bytes. socket-patch vex still emits pkg:cargo/[email protected] as not_affected ("Patched via Socket patch … (redirected)") with no warning.

scan already refuses this exact graph up front (redirect_cargo_transitive_dependents, "crc32fast compiled the unpatched crates.io copy while scan reported the crate redirected"). Post-scan vex has no equivalent guard, so the same graph is attested once it appears after the scan, for example after cargo add, cargo update or a merge.

Impact

This is a false VEX attestation: the shipped binary contains the vulnerable crate, and the VEX document says it doesn't. Adding a dependency after a hosted scan is the normal workflow. Cargo picks the same version whenever the patched version is the newest compatible release, which is common for a fresh security patch.

Repro (Linux, cargo 1.93.1 and 1.97.0)

I used a scratch copy of crates/socket-patch-cli/tests/e2e_redirect_cargo_shapes.rs (the wiremock sparse-registry harness), with the plain cfg-if = "1.0.4" consumer shape. After the harness's fresh checkout plus cargo fetch --locked and cargo build --locked --offline:

# fresh checkout of the hosted-scanned project
sed -i 's/^\[dependencies\]$/[dependencies]\ncrc32fast = "=1.5.0"/' Cargo.toml
cargo build                                              # crc32fast pulls crates.io cfg-if
cargo update -p [email protected] --precise 1.0.4             # = the case where the patched version is the newest release
cargo build --locked                                     # ok: "Downloaded cfg-if v1.0.4 … Compiling cfg-if v1.0.4" (crates.io)
socket-patch vex --output doc.vex.json --product pkg:cargo/[email protected] \
  --patch-server-url $MOCK --api-url $MOCK --org o --api-token fake --cwd .

Resulting Cargo.lock:

[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2…"

[[package]]
name = "cfg-if"
version = "1.0.4"
source = "sparse+http://127.0.0.1:34849/patch-registry/cargo/<token>/c1f90104-…/index/"
checksum = "cb5ea012…"

[[package]]
name = "crc32fast"
version = "1.5.0"
dependencies = [
 "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
]

cargo tree -i [email protected] reports the spec as ambiguous, listing both sources. The vex output (exit 0):

"products": [{"@id": "pkg:cargo/[email protected]", "subcomponents": [{"@id": "pkg:cargo/[email protected]"}]}],
"status": "not_affected",
"impact_statement": "Patched via Socket patch c1f90104-… (redirected)"

Reproduced 3 times: twice on 1.93.1 and once on stable 1.97.0.

Expected vs actual

  • Expected (CLI_CONTRACT, "Manifest-less VEX", Contested locks): when the build resolves the same name@version from a non-Socket source beside the Socket wiring, the reference is dropped (patched_ref_unattributable) or the lockfile basis is withheld. The vlt row spells out the same-lock case: "A same-name@version node on another registry … keeps the reference but withholds the lockfile basis: only an installed tree whose every store copy verifies attests". The redirect refusal in scan documents that this graph builds an unpatched copy.
  • Actual: the cargo extractor accepts the Socket-sourced entry and ignores the crates.io sibling. Verification hashes only the Socket-registry source dir, so the purl is attested not_affected.

Matrix

OS cargo lock Reproduces
Linux 1.93.1 v4 yes (×2)
Linux 1.97.0 (stable) v4 yes
macOS / Windows any – untested (the logic is OS-independent)

First bad release: not bisected. Main is 045d7ec.

Suspect code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions