CVE-2026-5223: prohibit unpacking symlinks and other unexpected entries
authorJosh Triplett <josh@joshtriplett.org>
Mon, 30 Mar 2026 17:35:55 +0000 (10:35 -0700)
committerFabian Grünbichler <debian@fabian.gruenbichler.email>
Thu, 28 May 2026 18:58:07 +0000 (20:58 +0200)
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.

FG: adapted paths
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
Gbp-Pq: Topic cargo
Gbp-Pq: Name CVE-2026-5223-prohibit-unpacking-symlinks-and-other-unexp.patch

src/tools/cargo/src/cargo/sources/registry/mod.rs
src/tools/cargo/tests/testsuite/registry.rs

index 04d4ec304a5cb98965c5d2c7d46d23e918f88853..d152a1e21ab8738d37de69d585db9fa9b25db1c6 100644 (file)
@@ -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;
@@ -1088,6 +1088,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()
index 5a30dad6462e90fb4aa59e3fb30b7e3f19591854..4a4e83663bf9054af935aa7e39963780aeaae63e 100644 (file)
@@ -3274,8 +3274,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",
@@ -3298,21 +3297,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]
@@ -4751,13 +4752,7 @@ Caused by:
   failed to unpack package `bar v1.0.0 (registry `dummy-registry`)`
 
 Caused by:
-  failed to unpack entry at `bar-1.0.0/smuggled`
-
-Caused by:
-  failed to unpack `[ROOT]/home/.cargo/registry/src/-[HASH]/bar-1.0.0/smuggled`
-
-Caused by:
-  [..] when creating dir [ROOT]/home/.cargo/registry/src/-[HASH]/bar-1.0.0/smuggled
+  invalid tarball downloaded, contains an entry at "bar-1.0.0/smuggled" with invalid type Symlink
 
 "#]])
         .run();