Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions SPECS/rust/CVE-2026-5222.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
From c4d63a44234de22dc745231c416b80ed848d997f Mon Sep 17 00:00:00 2001
From: Arlo Siemsen <arkixml@gmail.com>
Date: Mon, 25 May 2026 09:49:43 +0200
Subject: [PATCH] CVE-2026-5222: avoid stripping .git suffix when for non git
registries

Upstream Patch reference: https://github.com/rust-lang/cargo/commit/c4d63a44234de22dc745231c416b80ed848d997f.patch
---
.../cargo/src/cargo/sources/git/source.rs | 7 +++
.../cargo/src/cargo/util/canonical_url.rs | 44 ++++++++++---------
2 files changed, 31 insertions(+), 20 deletions(-)

diff --git a/src/tools/cargo/src/cargo/sources/git/source.rs b/src/tools/cargo/src/cargo/sources/git/source.rs
index 2459b87b9..2d3ea0327 100644
--- a/src/tools/cargo/src/cargo/sources/git/source.rs
+++ b/src/tools/cargo/src/cargo/sources/git/source.rs
@@ -455,6 +455,13 @@ mod test {
assert_eq!(ident1, ident2);
}

+ #[test]
+ fn test_canonicalize_idents_does_not_strip_dot_git_for_sparse() {
+ let ident1 = ident(&src("sparse+https://crates.io/fake-registry"));
+ let ident2 = ident(&src("sparse+https://crates.io/fake-registry.git"));
+ assert_ne!(ident1, ident2);
+ }
+
fn src(s: &str) -> SourceId {
SourceId::for_git(&s.into_url().unwrap(), GitReference::DefaultBranch).unwrap()
}
diff --git a/src/tools/cargo/src/cargo/util/canonical_url.rs b/src/tools/cargo/src/cargo/util/canonical_url.rs
index 7516e0356..2716d2d4f 100644
--- a/src/tools/cargo/src/cargo/util/canonical_url.rs
+++ b/src/tools/cargo/src/cargo/util/canonical_url.rs
@@ -33,27 +33,31 @@ impl CanonicalUrl {
url.path_segments_mut().unwrap().pop_if_empty();
}

- // For GitHub URLs specifically, just lower-case everything. GitHub
- // treats both the same, but they hash differently, and we're gonna be
- // hashing them. This wants a more general solution, and also we're
- // almost certainly not using the same case conversion rules that GitHub
- // does. (See issue #84)
- if url.host_str() == Some("github.com") {
- url = format!("https{}", &url[url::Position::AfterScheme..])
- .parse()
- .unwrap();
- let path = url.path().to_lowercase();
- url.set_path(&path);
- }
+ // Perform further canonicalization specific to git registries, which
+ // do not contain a `+` specifier.
+ if !url.scheme().contains('+') {
+ // For GitHub URLs specifically, just lower-case everything. GitHub
+ // treats both the same, but they hash differently, and we're gonna be
+ // hashing them. This wants a more general solution, and also we're
+ // almost certainly not using the same case conversion rules that GitHub
+ // does. (See issue #84)
+ if url.host_str() == Some("github.com") {
+ url = format!("https{}", &url[url::Position::AfterScheme..])
+ .parse()
+ .unwrap();
+ let path = url.path().to_lowercase();
+ url.set_path(&path);
+ }

- // Repos can generally be accessed with or without `.git` extension.
- let needs_chopping = url.path().ends_with(".git");
- if needs_chopping {
- let last = {
- let last = url.path_segments().unwrap().next_back().unwrap();
- last[..last.len() - 4].to_owned()
- };
- url.path_segments_mut().unwrap().pop().push(&last);
+ // Repos can generally be accessed with or without `.git` extension.
+ let needs_chopping = url.path().ends_with(".git");
+ if needs_chopping {
+ let last = {
+ let last = url.path_segments().unwrap().next_back().unwrap();
+ last[..last.len() - 4].to_owned()
+ };
+ url.path_segments_mut().unwrap().pop().push(&last);
+ }
}

Ok(CanonicalUrl(url))
--
2.43.0

89 changes: 89 additions & 0 deletions SPECS/rust/CVE-2026-5222_1.75.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
From c4d63a44234de22dc745231c416b80ed848d997f Mon Sep 17 00:00:00 2001
From: Arlo Siemsen <arkixml@gmail.com>
Date: Mon, 25 May 2026 09:49:43 +0200
Subject: [PATCH] CVE-2026-5222: avoid stripping .git suffix when for non git
registries

Upstream Patch reference: https://github.com/rust-lang/cargo/commit/c4d63a44234de22dc745231c416b80ed848d997f.patch
---
.../cargo/src/cargo/sources/git/source.rs | 7 +++
.../cargo/src/cargo/util/canonical_url.rs | 44 ++++++++++---------
2 files changed, 31 insertions(+), 20 deletions(-)

diff --git a/src/tools/cargo/src/cargo/sources/git/source.rs b/src/tools/cargo/src/cargo/sources/git/source.rs
index a75c1ec6d..1c8dbc836 100644
--- a/src/tools/cargo/src/cargo/sources/git/source.rs
+++ b/src/tools/cargo/src/cargo/sources/git/source.rs
@@ -377,6 +377,13 @@ mod test {
assert_eq!(ident1, ident2);
}

+ #[test]
+ fn test_canonicalize_idents_does_not_strip_dot_git_for_sparse() {
+ let ident1 = ident(&src("sparse+https://crates.io/fake-registry"));
+ let ident2 = ident(&src("sparse+https://crates.io/fake-registry.git"));
+ assert_ne!(ident1, ident2);
+ }
+
fn src(s: &str) -> SourceId {
SourceId::for_git(&s.into_url().unwrap(), GitReference::DefaultBranch).unwrap()
}
diff --git a/src/tools/cargo/src/cargo/util/canonical_url.rs b/src/tools/cargo/src/cargo/util/canonical_url.rs
index 7516e0356..2716d2d4f 100644
--- a/src/tools/cargo/src/cargo/util/canonical_url.rs
+++ b/src/tools/cargo/src/cargo/util/canonical_url.rs
@@ -33,27 +33,31 @@ impl CanonicalUrl {
url.path_segments_mut().unwrap().pop_if_empty();
}

- // For GitHub URLs specifically, just lower-case everything. GitHub
- // treats both the same, but they hash differently, and we're gonna be
- // hashing them. This wants a more general solution, and also we're
- // almost certainly not using the same case conversion rules that GitHub
- // does. (See issue #84)
- if url.host_str() == Some("github.com") {
- url = format!("https{}", &url[url::Position::AfterScheme..])
- .parse()
- .unwrap();
- let path = url.path().to_lowercase();
- url.set_path(&path);
- }
+ // Perform further canonicalization specific to git registries, which
+ // do not contain a `+` specifier.
+ if !url.scheme().contains('+') {
+ // For GitHub URLs specifically, just lower-case everything. GitHub
+ // treats both the same, but they hash differently, and we're gonna be
+ // hashing them. This wants a more general solution, and also we're
+ // almost certainly not using the same case conversion rules that GitHub
+ // does. (See issue #84)
+ if url.host_str() == Some("github.com") {
+ url = format!("https{}", &url[url::Position::AfterScheme..])
+ .parse()
+ .unwrap();
+ let path = url.path().to_lowercase();
+ url.set_path(&path);
+ }

- // Repos can generally be accessed with or without `.git` extension.
- let needs_chopping = url.path().ends_with(".git");
- if needs_chopping {
- let last = {
- let last = url.path_segments().unwrap().next_back().unwrap();
- last[..last.len() - 4].to_owned()
- };
- url.path_segments_mut().unwrap().pop().push(&last);
+ // Repos can generally be accessed with or without `.git` extension.
+ let needs_chopping = url.path().ends_with(".git");
+ if needs_chopping {
+ let last = {
+ let last = url.path_segments().unwrap().next_back().unwrap();
+ last[..last.len() - 4].to_owned()
+ };
+ url.path_segments_mut().unwrap().pop().push(&last);
+ }
}

Ok(CanonicalUrl(url))
--
2.43.0

217 changes: 217 additions & 0 deletions SPECS/rust/CVE-2026-5223.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
From 285cebf58911eca5b7f177f5d0b1c53e1f646577 Mon Sep 17 00:00:00 2001
From: Josh Triplett <josh@joshtriplett.org>
Date: Mon, 30 Mar 2026 10:35:55 -0700
Subject: [PATCH] CVE-2026-5223: prohibit unpacking symlinks and other
unexpected entries

Cargo has historically not allowed creating .crate packages containing
symlinks. (It packages the symlink target in place of the symlink,
instead.) So, any package containing a symlink would have to be
hand-constructed. Such packages are also not allowed on crates.io, so it
could only come from an alternate registry.

Rather than dealing with symlink traversal attacks when unpacking a
crate, just prohibit symlinks entirely.

In the process, also prohibit other kinds of unusual entries. As an
exception, allow character devices but warn about them, because some
exist in crates on crates.io.

Upstream Patch reference: https://github.com/rust-lang/cargo/commit/285cebf58911eca5b7f177f5d0b1c53e1f646577.patch
---
.../cargo/src/cargo/sources/registry/mod.rs | 10 +++++-
src/tools/cargo/tests/testsuite/registry.rs | 31 ++++++++++---------
.../src/cargo/sources/registry/mod.rs | 11 ++++++-
.../cargo-0.87.1/tests/testsuite/registry.rs | 31 ++++++++++---------
.../cargo/src/cargo/sources/registry/mod.rs | 9 +++++-
5 files changed, 59 insertions(+), 33 deletions(-)

diff --git a/src/tools/cargo/src/cargo/sources/registry/mod.rs b/src/tools/cargo/src/cargo/sources/registry/mod.rs
index 69bb2d875..dc587473c 100644
--- a/src/tools/cargo/src/cargo/sources/registry/mod.rs
+++ b/src/tools/cargo/src/cargo/sources/registry/mod.rs
@@ -196,7 +196,7 @@ use cargo_util::paths::{self, exclude_from_backups_and_indexing};
use flate2::read::GzDecoder;
use serde::Deserialize;
use serde::Serialize;
-use tar::Archive;
+use tar::{Archive, EntryType};
use tracing::debug;

use crate::core::dependency::Dependency;
@@ -1072,6 +1072,14 @@ fn unpack(
)
}

+ // Prevent unpacking symlinks and other unexpected entry types
+ match entry.header().entry_type() {
+ EntryType::Regular | EntryType::Directory => {}
+ t => anyhow::bail!(
+ "invalid tarball downloaded, contains an entry at {entry_path:?} with invalid type {t:?}",
+ ),
+ }
+
// Prevent unpacking the lockfile from the crate itself.
if entry_path
.file_name()
diff --git a/src/tools/cargo/tests/testsuite/registry.rs b/src/tools/cargo/tests/testsuite/registry.rs
index 60a01b045..906047af3 100644
--- a/src/tools/cargo/tests/testsuite/registry.rs
+++ b/src/tools/cargo/tests/testsuite/registry.rs
@@ -3273,8 +3273,7 @@ fn package_lock_inside_package_is_overwritten() {
}

#[cargo_test]
-fn package_lock_as_a_symlink_inside_package_is_overwritten() {
- let registry = registry::init();
+fn package_lock_as_a_symlink_inside_package_is_invalid() {
let p = project()
.file(
"Cargo.toml",
@@ -3297,21 +3296,23 @@ fn package_lock_as_a_symlink_inside_package_is_overwritten() {
.symlink(".cargo-ok", "src/lib.rs")
.publish();

- p.cargo("check").run();
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr_data(str![[r#"
+[UPDATING] `dummy-registry` index
+[LOCKING] 1 package to latest compatible version
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.0.1 (registry `dummy-registry`)
+[ERROR] failed to download replaced source registry `crates-io`

- let id = SourceId::for_registry(registry.index_url()).unwrap();
- let hash = cargo::util::hex::short_hash(&id);
- let pkg_root = paths::cargo_home()
- .join("registry")
- .join("src")
- .join(format!("-{}", hash))
- .join("bar-0.0.1");
- let ok = pkg_root.join(".cargo-ok");
- let librs = pkg_root.join("src/lib.rs");
+Caused by:
+ failed to unpack package `bar v0.0.1 (registry `dummy-registry`)`

- // Is correctly overwritten and doesn't affect the file linked to
- assert_eq!(ok.metadata().unwrap().len(), 7);
- assert_eq!(fs::read_to_string(librs).unwrap(), "pub fn f() {}");
+Caused by:
+ invalid tarball downloaded, contains an entry at "bar-0.0.1/.cargo-ok" with invalid type Symlink
+
+"#]])
+ .run();
}

#[cargo_test]
diff --git a/src/tools/rustc-perf/collector/compile-benchmarks/cargo-0.87.1/src/cargo/sources/registry/mod.rs b/src/tools/rustc-perf/collector/compile-benchmarks/cargo-0.87.1/src/cargo/sources/registry/mod.rs
index bf10f81fc..313258fc9 100644
--- a/src/tools/rustc-perf/collector/compile-benchmarks/cargo-0.87.1/src/cargo/sources/registry/mod.rs
+++ b/src/tools/rustc-perf/collector/compile-benchmarks/cargo-0.87.1/src/cargo/sources/registry/mod.rs
@@ -197,7 +197,7 @@ use cargo_util::paths::{self, exclude_from_backups_and_indexing};
use flate2::read::GzDecoder;
use serde::Deserialize;
use serde::Serialize;
-use tar::Archive;
+use tar::{Archive, EntryType};
use tracing::debug;

use crate::core::dependency::Dependency;
@@ -662,6 +662,15 @@ impl<'gctx> RegistrySource<'gctx> {
prefix
)
}
+
+ // Prevent unpacking symlinks and other unexpected entry types
+ match entry.header().entry_type() {
+ EntryType::Regular | EntryType::Directory => {}
+ t => anyhow::bail!(
+ "invalid tarball downloaded, contains an entry at {entry_path:?} with invalid type {t:?}",
+ ),
+ }
+
// Prevent unpacking the lockfile from the crate itself.
if entry_path
.file_name()
diff --git a/src/tools/rustc-perf/collector/compile-benchmarks/cargo-0.87.1/tests/testsuite/registry.rs b/src/tools/rustc-perf/collector/compile-benchmarks/cargo-0.87.1/tests/testsuite/registry.rs
index e4c23f5c3..3bf48299e 100644
--- a/src/tools/rustc-perf/collector/compile-benchmarks/cargo-0.87.1/tests/testsuite/registry.rs
+++ b/src/tools/rustc-perf/collector/compile-benchmarks/cargo-0.87.1/tests/testsuite/registry.rs
@@ -3286,8 +3286,7 @@ fn package_lock_inside_package_is_overwritten() {
}

#[cargo_test]
-fn package_lock_as_a_symlink_inside_package_is_overwritten() {
- let registry = registry::init();
+fn package_lock_as_a_symlink_inside_package_is_invalid() {
let p = project()
.file(
"Cargo.toml",
@@ -3310,21 +3309,23 @@ fn package_lock_as_a_symlink_inside_package_is_overwritten() {
.symlink(".cargo-ok", "src/lib.rs")
.publish();

- p.cargo("check").run();
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr_data(str![[r#"
+[UPDATING] `dummy-registry` index
+[LOCKING] 1 package to latest compatible version
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.0.1 (registry `dummy-registry`)
+[ERROR] failed to download replaced source registry `crates-io`

- let id = SourceId::for_registry(registry.index_url()).unwrap();
- let hash = cargo::util::hex::short_hash(&id);
- let pkg_root = paths::cargo_home()
- .join("registry")
- .join("src")
- .join(format!("-{}", hash))
- .join("bar-0.0.1");
- let ok = pkg_root.join(".cargo-ok");
- let librs = pkg_root.join("src/lib.rs");
+Caused by:
+ failed to unpack package `bar v0.0.1 (registry `dummy-registry`)`

- // Is correctly overwritten and doesn't affect the file linked to
- assert_eq!(ok.metadata().unwrap().len(), 7);
- assert_eq!(fs::read_to_string(librs).unwrap(), "pub fn f() {}");
+Caused by:
+ invalid tarball downloaded, contains an entry at "bar-0.0.1/.cargo-ok" with invalid type Symlink
+
+"#]])
+ .run();
}

#[cargo_test]
diff --git a/src/tools/rustc-perf/collector/compile-benchmarks/cargo/src/cargo/sources/registry/mod.rs b/src/tools/rustc-perf/collector/compile-benchmarks/cargo/src/cargo/sources/registry/mod.rs
index c967e2ebc..0788456ea 100644
--- a/src/tools/rustc-perf/collector/compile-benchmarks/cargo/src/cargo/sources/registry/mod.rs
+++ b/src/tools/rustc-perf/collector/compile-benchmarks/cargo/src/cargo/sources/registry/mod.rs
@@ -167,7 +167,7 @@ use std::path::{PathBuf, Path};
use flate2::read::GzDecoder;
use semver::Version;
use serde::de;
-use tar::Archive;
+use tar::{Archive, EntryType};

use core::{Source, SourceId, PackageId, Package, Summary, Registry};
use core::dependency::{Dependency, Kind};
@@ -333,6 +333,13 @@ impl<'cfg> RegistrySource<'cfg> {
entry_path, prefix).into())
}

+ // Prevent unpacking symlinks and other unexpected entry types
+ match entry.header().entry_type() {
+ EntryType::Regular | EntryType::Directory => {}
+ t => anyhow::bail!(
+ "invalid tarball downloaded, contains an entry at {entry_path:?} with invalid type {t:?}",
+ ),
+ }
// Once that's verified, unpack the entry as usual.
entry.unpack_in(parent).chain_err(|| {
format!("failed to unpack entry at `{}`", entry_path.display())
--
2.43.0

Loading
Loading