rust/SOURCES/CVE-2023-38497-cargo-umask....

1469 lines
53 KiB
Diff

From 954b55b8ab90959335cd721b977e69263e07ff38 Mon Sep 17 00:00:00 2001
From: Weihang Lo <me@weihanglo.tw>
Date: Fri, 7 Jul 2023 14:44:11 +0100
Subject: [CVE-2023-38497 1/4] test: verify permissions bits are preserved when
unpacking
This is not secure and will be fixed in the next commit.
---
src/tools/cargo/tests/testsuite/registry.rs | 55 +++++++++++++++++++++
1 file changed, 55 insertions(+)
diff --git a/src/tools/cargo/tests/testsuite/registry.rs b/src/tools/cargo/tests/testsuite/registry.rs
index 60c52fd54625..d04dbbf35604 100644
--- a/src/tools/cargo/tests/testsuite/registry.rs
+++ b/src/tools/cargo/tests/testsuite/registry.rs
@@ -2941,3 +2941,58 @@ required by package `foo v0.1.0 ([ROOT]/foo)`
.with_status(101)
.run();
}
+
+#[cfg(unix)]
+#[cargo_test]
+fn set_mask_during_unpacking() {
+ use std::os::unix::fs::MetadataExt;
+
+ Package::new("bar", "1.0.0")
+ .file_with_mode("example.sh", 0o777, "#!/bin/sh")
+ .file_with_mode("src/lib.rs", 0o666, "")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("fetch")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v1.0.0 (registry `dummy-registry`)
+",
+ )
+ .run();
+ let src_file_path = |path: &str| {
+ glob::glob(
+ paths::home()
+ .join(".cargo/registry/src/*/bar-1.0.0/")
+ .join(path)
+ .to_str()
+ .unwrap(),
+ )
+ .unwrap()
+ .next()
+ .unwrap()
+ .unwrap()
+ };
+
+ // Assuming umask is `0o022`.
+ let metadata = fs::metadata(src_file_path("src/lib.rs")).unwrap();
+ assert_eq!(metadata.mode() & 0o777, 0o666);
+ let metadata = fs::metadata(src_file_path("example.sh")).unwrap();
+ assert_eq!(metadata.mode() & 0o777, 0o777);
+}
--
2.41.0
From 11fada38ef0249af79213188c8d6a57cecab5023 Mon Sep 17 00:00:00 2001
From: Weihang Lo <me@weihanglo.tw>
Date: Fri, 7 Jul 2023 14:50:59 +0100
Subject: [CVE-2023-38497 2/4] fix: respect `umask` when unpacking `.crate`
files
Without this, an attacker can upload globally writable files buried
in the `.crate` file. After a user downloaded and unpacked the file,
the attacker can then write malicous code to the downloaded sources.
---
Modified while backporting to use `lazy_static` instead of `OnceLock`.
Co-authored-by: Josh Stone <jistone@redhat.com>
---
Cargo.lock | 4 ++--
src/tools/cargo/Cargo.toml | 2 +-
.../cargo/src/cargo/sources/registry/mod.rs | 15 +++++++++++++++
src/tools/cargo/src/cargo/util/mod.rs | 19 +++++++++++++++++++
src/tools/cargo/tests/testsuite/registry.rs | 6 +++---
src/tools/cargo/tests/testsuite/vendor.rs | 5 +++--
6 files changed, 43 insertions(+), 8 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 50f732837865..7bcfacd1c5ea 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4807,9 +4807,9 @@ dependencies = [
[[package]]
name = "tar"
-version = "0.4.38"
+version = "0.4.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6"
+checksum = "ec96d2ffad078296368d46ff1cb309be1c23c513b4ab0e22a45de0185275ac96"
dependencies = [
"filetime",
"libc",
diff --git a/src/tools/cargo/Cargo.toml b/src/tools/cargo/Cargo.toml
index 4b4f328e189a..3f692a35c353 100644
--- a/src/tools/cargo/Cargo.toml
+++ b/src/tools/cargo/Cargo.toml
@@ -58,7 +58,7 @@ serde-value = "0.7.0"
sha1 = "0.10.5"
shell-escape = "0.1.4"
strip-ansi-escapes = "0.1.0"
-tar = { version = "0.4.38", default-features = false }
+tar = { version = "0.4.39", default-features = false }
tempfile = "3.0"
termcolor = "1.1"
toml_edit = { version = "0.15.0", features = ["serde", "easy", "perf"] }
diff --git a/src/tools/cargo/src/cargo/sources/registry/mod.rs b/src/tools/cargo/src/cargo/sources/registry/mod.rs
index cabd4f9c6038..905ba938cd1d 100644
--- a/src/tools/cargo/src/cargo/sources/registry/mod.rs
+++ b/src/tools/cargo/src/cargo/sources/registry/mod.rs
@@ -162,6 +162,7 @@ use std::borrow::Cow;
use std::collections::BTreeMap;
use std::collections::HashSet;
use std::fs::{File, OpenOptions};
+use std::io::Read;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::task::Poll;
@@ -620,6 +621,7 @@ impl<'cfg> RegistrySource<'cfg> {
let gz = GzDecoder::new(tarball);
let gz = LimitErrorReader::new(gz, max_unpack_size());
let mut tar = Archive::new(gz);
+ set_mask(&mut tar);
let prefix = unpack_dir.file_name().unwrap();
let parent = unpack_dir.parent().unwrap();
for entry in tar.entries()? {
@@ -872,3 +874,16 @@ mod tests {
assert_eq!(make_dep_prefix("aBcDe"), "aB/cD");
}
}
+
+/// Set the current [`umask`] value for the given tarball. No-op on non-Unix
+/// platforms.
+///
+/// On Windows, tar only looks at user permissions and tries to set the "read
+/// only" attribute, so no-op as well.
+///
+/// [`umask`]: https://man7.org/linux/man-pages/man2/umask.2.html
+#[allow(unused_variables)]
+fn set_mask<R: Read>(tar: &mut Archive<R>) {
+ #[cfg(unix)]
+ tar.set_mask(crate::util::get_umask());
+}
diff --git a/src/tools/cargo/src/cargo/util/mod.rs b/src/tools/cargo/src/cargo/util/mod.rs
index 9881332ecafc..d681fb766469 100644
--- a/src/tools/cargo/src/cargo/util/mod.rs
+++ b/src/tools/cargo/src/cargo/util/mod.rs
@@ -122,3 +122,22 @@ pub fn truncate_with_ellipsis(s: &str, max_width: usize) -> String {
}
prefix
}
+
+/// Get the current [`umask`] value.
+///
+/// [`umask`]: https://man7.org/linux/man-pages/man2/umask.2.html
+#[cfg(unix)]
+pub fn get_umask() -> u32 {
+ // SAFETY: Syscalls are unsafe. Calling `umask` twice is even unsafer for
+ // multithreading program, since it doesn't provide a way to retrive the
+ // value without modifications. We use a `lazy_static` here to ensure
+ // it only gets call once during the entire program lifetime.
+ lazy_static::lazy_static! {
+ static ref UMASK: libc::mode_t = unsafe {
+ let umask = libc::umask(0o022);
+ libc::umask(umask);
+ umask
+ };
+ }
+ *UMASK as u32 // it is u16 on macos
+}
diff --git a/src/tools/cargo/tests/testsuite/registry.rs b/src/tools/cargo/tests/testsuite/registry.rs
index d04dbbf35604..c18613f9d30c 100644
--- a/src/tools/cargo/tests/testsuite/registry.rs
+++ b/src/tools/cargo/tests/testsuite/registry.rs
@@ -2990,9 +2990,9 @@ fn set_mask_during_unpacking() {
.unwrap()
};
- // Assuming umask is `0o022`.
+ let umask = cargo::util::get_umask();
let metadata = fs::metadata(src_file_path("src/lib.rs")).unwrap();
- assert_eq!(metadata.mode() & 0o777, 0o666);
+ assert_eq!(metadata.mode() & 0o777, 0o666 & !umask);
let metadata = fs::metadata(src_file_path("example.sh")).unwrap();
- assert_eq!(metadata.mode() & 0o777, 0o777);
+ assert_eq!(metadata.mode() & 0o777, 0o777 & !umask);
}
diff --git a/src/tools/cargo/tests/testsuite/vendor.rs b/src/tools/cargo/tests/testsuite/vendor.rs
index 8e985e265a3d..db6f114cac69 100644
--- a/src/tools/cargo/tests/testsuite/vendor.rs
+++ b/src/tools/cargo/tests/testsuite/vendor.rs
@@ -890,10 +890,11 @@ fn vendor_preserves_permissions() {
p.cargo("vendor --respect-source-config").run();
+ let umask = cargo::util::get_umask();
let metadata = fs::metadata(p.root().join("vendor/bar/src/lib.rs")).unwrap();
- assert_eq!(metadata.mode() & 0o777, 0o644);
+ assert_eq!(metadata.mode() & 0o777, 0o644 & !umask);
let metadata = fs::metadata(p.root().join("vendor/bar/example.sh")).unwrap();
- assert_eq!(metadata.mode() & 0o777, 0o755);
+ assert_eq!(metadata.mode() & 0o777, 0o755 & !umask);
}
#[cargo_test]
--
2.41.0
From 86e9f660833ac2c60e335cc961498d8da546e8b4 Mon Sep 17 00:00:00 2001
From: Weihang Lo <me@weihanglo.tw>
Date: Fri, 7 Jul 2023 18:18:39 +0100
Subject: [CVE-2023-38497 3/4] fix: clear cache for old `.cargo-ok` format
In 1.71, `.cargo-ok` changed to contain a JSON `{ v: 1 }` to indicate
the version of it. A failure of parsing will result in a heavy-hammer
approach that unpacks the `.crate` file again. This is in response to a
security issue that the unpacking didn't respect umask on Unix systems.
---
.../cargo/src/cargo/sources/registry/mod.rs | 84 +++++++++++-
src/tools/cargo/tests/testsuite/registry.rs | 120 +++++++++++++++++-
2 files changed, 195 insertions(+), 9 deletions(-)
diff --git a/src/tools/cargo/src/cargo/sources/registry/mod.rs b/src/tools/cargo/src/cargo/sources/registry/mod.rs
index 905ba938cd1d..1bd98964066e 100644
--- a/src/tools/cargo/src/cargo/sources/registry/mod.rs
+++ b/src/tools/cargo/src/cargo/sources/registry/mod.rs
@@ -161,18 +161,21 @@
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::collections::HashSet;
+use std::fs;
use std::fs::{File, OpenOptions};
+use std::io;
use std::io::Read;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::task::Poll;
use anyhow::Context as _;
-use cargo_util::paths::exclude_from_backups_and_indexing;
+use cargo_util::paths::{self, exclude_from_backups_and_indexing};
use flate2::read::GzDecoder;
use log::debug;
use semver::Version;
use serde::Deserialize;
+use serde::Serialize;
use tar::Archive;
use crate::core::dependency::{DepKind, Dependency};
@@ -199,6 +202,14 @@ const LOWER_PREFIX_TEMPLATE: &str = "{lowerprefix}";
const CHECKSUM_TEMPLATE: &str = "{sha256-checksum}";
const MAX_UNPACK_SIZE: u64 = 512 * 1024 * 1024;
+/// The content inside `.cargo-ok`.
+/// See [`RegistrySource::unpack_package`] for more.
+#[derive(Deserialize, Serialize)]
+struct LockMetadata {
+ /// The version of `.cargo-ok` file
+ v: u32,
+}
+
/// A "source" for a local (see `local::LocalRegistry`) or remote (see
/// `remote::RemoteRegistry`) registry.
///
@@ -604,20 +615,77 @@ impl<'cfg> RegistrySource<'cfg> {
/// compiled.
///
/// No action is taken if the source looks like it's already unpacked.
+ ///
+ /// # History of interruption detection with `.cargo-ok` file
+ ///
+ /// Cargo has always included a `.cargo-ok` file ([`PACKAGE_SOURCE_LOCK`])
+ /// to detect if extraction was interrupted, but it was originally empty.
+ ///
+ /// In 1.34, Cargo was changed to create the `.cargo-ok` file before it
+ /// started extraction to implement fine-grained locking. After it was
+ /// finished extracting, it wrote two bytes to indicate it was complete.
+ /// It would use the length check to detect if it was possibly interrupted.
+ ///
+ /// In 1.36, Cargo changed to not use fine-grained locking, and instead used
+ /// a global lock. The use of `.cargo-ok` was no longer needed for locking
+ /// purposes, but was kept to detect when extraction was interrupted.
+ ///
+ /// In 1.49, Cargo changed to not create the `.cargo-ok` file before it
+ /// started extraction to deal with `.crate` files that inexplicably had
+ /// a `.cargo-ok` file in them.
+ ///
+ /// In 1.64, Cargo changed to detect `.crate` files with `.cargo-ok` files
+ /// in them in response to [CVE-2022-36113], which dealt with malicious
+ /// `.crate` files making `.cargo-ok` a symlink causing cargo to write "ok"
+ /// to any arbitrary file on the filesystem it has permission to.
+ ///
+ /// In 1.71, `.cargo-ok` changed to contain a JSON `{ v: 1 }` to indicate
+ /// the version of it. A failure of parsing will result in a heavy-hammer
+ /// approach that unpacks the `.crate` file again. This is in response to a
+ /// security issue that the unpacking didn't respect umask on Unix systems.
+ ///
+ /// This is all a long-winded way of explaining the circumstances that might
+ /// cause a directory to contain a `.cargo-ok` file that is empty or
+ /// otherwise corrupted. Either this was extracted by a version of Rust
+ /// before 1.34, in which case everything should be fine. However, an empty
+ /// file created by versions 1.36 to 1.49 indicates that the extraction was
+ /// interrupted and that we need to start again.
+ ///
+ /// Another possibility is that the filesystem is simply corrupted, in
+ /// which case deleting the directory might be the safe thing to do. That
+ /// is probably unlikely, though.
+ ///
+ /// To be safe, we deletes the directory and starts over again if an empty
+ /// `.cargo-ok` file is found.
+ ///
+ /// [CVE-2022-36113]: https://blog.rust-lang.org/2022/09/14/cargo-cves.html#arbitrary-file-corruption-cve-2022-36113
fn unpack_package(&self, pkg: PackageId, tarball: &File) -> CargoResult<PathBuf> {
// The `.cargo-ok` file is used to track if the source is already
// unpacked.
let package_dir = format!("{}-{}", pkg.name(), pkg.version());
let dst = self.src_path.join(&package_dir);
- dst.create_dir()?;
let path = dst.join(PACKAGE_SOURCE_LOCK);
let path = self.config.assert_package_cache_locked(&path);
let unpack_dir = path.parent().unwrap();
- if let Ok(meta) = path.metadata() {
- if meta.len() > 0 {
- return Ok(unpack_dir.to_path_buf());
- }
+ match fs::read_to_string(path) {
+ Ok(ok) => match serde_json::from_str::<LockMetadata>(&ok) {
+ Ok(lock_meta) if lock_meta.v == 1 => {
+ return Ok(unpack_dir.to_path_buf());
+ }
+ _ => {
+ if ok == "ok" {
+ log::debug!("old `ok` content found, clearing cache");
+ } else {
+ log::warn!("unrecognized .cargo-ok content, clearing cache: {ok}");
+ }
+ // See comment of `unpack_package` about why removing all stuff.
+ paths::remove_dir_all(dst.as_path_unlocked())?;
+ }
+ },
+ Err(e) if e.kind() == io::ErrorKind::NotFound => {}
+ Err(e) => anyhow::bail!("unable to read .cargo-ok file at {path:?}: {e}"),
}
+ dst.create_dir()?;
let gz = GzDecoder::new(tarball);
let gz = LimitErrorReader::new(gz, max_unpack_size());
let mut tar = Archive::new(gz);
@@ -675,7 +743,9 @@ impl<'cfg> RegistrySource<'cfg> {
.write(true)
.open(&path)
.with_context(|| format!("failed to open `{}`", path.display()))?;
- write!(ok, "ok")?;
+
+ let lock_meta = LockMetadata { v: 1 };
+ write!(ok, "{}", serde_json::to_string(&lock_meta).unwrap())?;
Ok(unpack_dir.to_path_buf())
}
diff --git a/src/tools/cargo/tests/testsuite/registry.rs b/src/tools/cargo/tests/testsuite/registry.rs
index c18613f9d30c..1d2e231363c4 100644
--- a/src/tools/cargo/tests/testsuite/registry.rs
+++ b/src/tools/cargo/tests/testsuite/registry.rs
@@ -2594,7 +2594,7 @@ fn package_lock_inside_package_is_overwritten() {
.join("bar-0.0.1")
.join(".cargo-ok");
- assert_eq!(ok.metadata().unwrap().len(), 2);
+ assert_eq!(ok.metadata().unwrap().len(), 7);
}
#[cargo_test]
@@ -2634,7 +2634,7 @@ fn package_lock_as_a_symlink_inside_package_is_overwritten() {
let librs = pkg_root.join("src/lib.rs");
// Is correctly overwritten and doesn't affect the file linked to
- assert_eq!(ok.metadata().unwrap().len(), 2);
+ assert_eq!(ok.metadata().unwrap().len(), 7);
assert_eq!(fs::read_to_string(librs).unwrap(), "pub fn f() {}");
}
@@ -2942,6 +2942,51 @@ required by package `foo v0.1.0 ([ROOT]/foo)`
.run();
}
+#[cargo_test]
+fn corrupted_ok_overwritten() {
+ // Checks what happens if .cargo-ok gets truncated, such as if the file is
+ // created, but the flush/close is interrupted.
+ Package::new("bar", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "1"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("fetch")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v1.0.0 (registry `dummy-registry`)
+",
+ )
+ .run();
+ let ok = glob::glob(
+ paths::home()
+ .join(".cargo/registry/src/*/bar-1.0.0/.cargo-ok")
+ .to_str()
+ .unwrap(),
+ )
+ .unwrap()
+ .next()
+ .unwrap()
+ .unwrap();
+ // Simulate cargo being interrupted, or filesystem corruption.
+ fs::write(&ok, "").unwrap();
+ assert_eq!(fs::read_to_string(&ok).unwrap(), "");
+ p.cargo("fetch").with_stderr("").run();
+ assert_eq!(fs::read_to_string(&ok).unwrap(), r#"{"v":1}"#);
+}
+
#[cfg(unix)]
#[cargo_test]
fn set_mask_during_unpacking() {
@@ -2996,3 +3041,74 @@ fn set_mask_during_unpacking() {
let metadata = fs::metadata(src_file_path("example.sh")).unwrap();
assert_eq!(metadata.mode() & 0o777, 0o777 & !umask);
}
+
+#[cargo_test]
+fn unpack_again_when_cargo_ok_is_unrecognized() {
+ Package::new("bar", "1.0.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("fetch")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v1.0.0 (registry `dummy-registry`)
+",
+ )
+ .run();
+
+ let src_file_path = |path: &str| {
+ glob::glob(
+ paths::home()
+ .join(".cargo/registry/src/*/bar-1.0.0/")
+ .join(path)
+ .to_str()
+ .unwrap(),
+ )
+ .unwrap()
+ .next()
+ .unwrap()
+ .unwrap()
+ };
+
+ // Change permissions to simulate the old behavior not respecting umask.
+ let lib_rs = src_file_path("src/lib.rs");
+ let cargo_ok = src_file_path(".cargo-ok");
+ let mut perms = fs::metadata(&lib_rs).unwrap().permissions();
+ assert!(!perms.readonly());
+ perms.set_readonly(true);
+ fs::set_permissions(&lib_rs, perms).unwrap();
+ let ok = fs::read_to_string(&cargo_ok).unwrap();
+ assert_eq!(&ok, r#"{"v":1}"#);
+
+ p.cargo("fetch").with_stderr("").run();
+
+ // Without changing `.cargo-ok`, a unpack won't be triggered.
+ let perms = fs::metadata(&lib_rs).unwrap().permissions();
+ assert!(perms.readonly());
+
+ // Write "ok" to simulate the old behavior and trigger the unpack again.
+ fs::write(&cargo_ok, "ok").unwrap();
+
+ p.cargo("fetch").with_stderr("").run();
+
+ // Permission has been restored and `.cargo-ok` is in the new format.
+ let perms = fs::metadata(lib_rs).unwrap().permissions();
+ assert!(!perms.readonly());
+ let ok = fs::read_to_string(&cargo_ok).unwrap();
+ assert_eq!(&ok, r#"{"v":1}"#);
+}
--
2.41.0
From ac01a45267da1a154e2a1f3010d7e80c247e03a9 Mon Sep 17 00:00:00 2001
From: Josh Stone <jistone@redhat.com>
Date: Mon, 7 Aug 2023 12:11:42 -0700
Subject: [CVE-2023-38497 4/4] Update vendored tar to 0.4.39
---
src/bootstrap/Cargo.lock | 4 +-
vendor/tar/.cargo-checksum.json | 2 +-
vendor/tar/Cargo.lock | 207 +++++++++++++++++++++-----------
vendor/tar/Cargo.toml | 18 ++-
vendor/tar/src/archive.rs | 29 +++++
vendor/tar/src/entry.rs | 180 ++++++++++++++++++++++++---
vendor/tar/tests/all.rs | 107 ++++++++++++++++-
vendor/tar/tests/entry.rs | 31 +++++
8 files changed, 481 insertions(+), 97 deletions(-)
diff --git a/src/bootstrap/Cargo.lock b/src/bootstrap/Cargo.lock
index baecca44cd98..5b0147b1d539 100644
--- a/src/bootstrap/Cargo.lock
+++ b/src/bootstrap/Cargo.lock
@@ -622,9 +622,9 @@ dependencies = [
[[package]]
name = "tar"
-version = "0.4.38"
+version = "0.4.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6"
+checksum = "ec96d2ffad078296368d46ff1cb309be1c23c513b4ab0e22a45de0185275ac96"
dependencies = [
"filetime",
"libc",
diff --git a/vendor/tar/.cargo-checksum.json b/vendor/tar/.cargo-checksum.json
index 508f784e82ae..792994f557ae 100644
--- a/vendor/tar/.cargo-checksum.json
+++ b/vendor/tar/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.lock":"9872bf9e41b9cadee45b688c9537030a993ca49a266fc7859029d8c74810d1d5","Cargo.toml":"8353c71aa4d394efa7aaeac3004d0a16fd0c7124b7bd57ea91ba87a7b2015f15","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397","README.md":"71079f1a0962c2cf288058f38d24735bddabd1427ac2dee72ec18cc5ae4bceed","examples/extract_file.rs":"dc487f6631d824175afc3ee33de99e80718a8ca3f9e57fddd7cac0a46c07d3ae","examples/list.rs":"36e412205eaffea8ab7f39be4173594b74e36acb369e091362b1975ee4a7a14b","examples/raw_list.rs":"0a735576ac354457d6d5a4d395d044fae99bf67a7c69960ca784a6f6a1743651","examples/write.rs":"419ac3e4155035e32b52cd8e6ae987a2d99cf82f60abbfb315c2a2c4f8e8fd19","src/archive.rs":"85a0091e02690c62379137988cd9b2689009536a0b941f1ab0581db26e9ebce6","src/builder.rs":"2914f394d44c133557532bf5765fe63e0def30ec0b447f8f2bc620e932a2036a","src/entry.rs":"705016636f7fdcad4fe20d7d2672be2b94cc53bb05e47628f5212b89e17a40fe","src/entry_type.rs":"0786688729a96b4a3135b28d40b95c3d4feaad66b9574c490cbea14814ab975f","src/error.rs":"a20813fbc52f1f2e3a79654f62de6001759f6504a06acee5b0819d4865398587","src/header.rs":"fb2b1fa943c19635826b3f2becfb82527be7d08fdac115af840da3ff06152908","src/lib.rs":"5468e413205c907c367c35d28a528389103d68fd6a5b5979bbedba7c9e6b6c99","src/pax.rs":"54002e31151f9c50e02a3da26b3cacd1d3c9a3902daee008ab76d112cf5a2430","tests/all.rs":"567a05d54e369d22efe40f3507a26e21f7878b95bd05c811250b2c350761791b","tests/entry.rs":"c1411ee09da9edb659b508867f0960e804966dfd33801f4a7afaefda331479dd","tests/header/mod.rs":"02b05639f63c39a47559650c7209817bb60282deb4f679d5b001ed936343d9de"},"package":"4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6"}
\ No newline at end of file
+{"files":{"Cargo.lock":"a074bb3491e0d988605218e794859ddb067bb624e793a053645fbbabadde83f6","Cargo.toml":"638559bf51f1b89359ece700138f774bc0a5c1513046e306d6109dc045799b84","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397","README.md":"71079f1a0962c2cf288058f38d24735bddabd1427ac2dee72ec18cc5ae4bceed","examples/extract_file.rs":"dc487f6631d824175afc3ee33de99e80718a8ca3f9e57fddd7cac0a46c07d3ae","examples/list.rs":"36e412205eaffea8ab7f39be4173594b74e36acb369e091362b1975ee4a7a14b","examples/raw_list.rs":"0a735576ac354457d6d5a4d395d044fae99bf67a7c69960ca784a6f6a1743651","examples/write.rs":"419ac3e4155035e32b52cd8e6ae987a2d99cf82f60abbfb315c2a2c4f8e8fd19","src/archive.rs":"9238c58f5a253034f490ede4c42de6e1b89986ee6db343920ee34b3dcfa5b238","src/builder.rs":"2914f394d44c133557532bf5765fe63e0def30ec0b447f8f2bc620e932a2036a","src/entry.rs":"0e4b0438cbc4cbec30a821ce8f23619fe9e53c17022a022de609d642e220193c","src/entry_type.rs":"0786688729a96b4a3135b28d40b95c3d4feaad66b9574c490cbea14814ab975f","src/error.rs":"a20813fbc52f1f2e3a79654f62de6001759f6504a06acee5b0819d4865398587","src/header.rs":"fb2b1fa943c19635826b3f2becfb82527be7d08fdac115af840da3ff06152908","src/lib.rs":"5468e413205c907c367c35d28a528389103d68fd6a5b5979bbedba7c9e6b6c99","src/pax.rs":"54002e31151f9c50e02a3da26b3cacd1d3c9a3902daee008ab76d112cf5a2430","tests/all.rs":"7a64869cd99a4642f2db71aa7d283633199e59b88ccca112cb467b0739e64e83","tests/entry.rs":"af12d84160e5459ebaee6ecdd68e0c811438c37c0cb881ad210e7f94132b9739","tests/header/mod.rs":"02b05639f63c39a47559650c7209817bb60282deb4f679d5b001ed936343d9de"},"package":"ec96d2ffad078296368d46ff1cb309be1c23c513b4ab0e22a45de0185275ac96"}
\ No newline at end of file
diff --git a/vendor/tar/Cargo.lock b/vendor/tar/Cargo.lock
index dd53b90b5883..f63a631ad2fe 100644
--- a/vendor/tar/Cargo.lock
+++ b/vendor/tar/Cargo.lock
@@ -2,12 +2,24 @@
# It is not intended for manual editing.
version = 3
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+[[package]]
+name = "cc"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
+
[[package]]
name = "cfg-if"
version = "1.0.0"
@@ -15,101 +27,120 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
-name = "filetime"
-version = "0.2.15"
+name = "errno"
+version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98"
+checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
dependencies = [
- "cfg-if",
+ "errno-dragonfly",
"libc",
- "redox_syscall",
- "winapi",
+ "windows-sys",
]
[[package]]
-name = "getrandom"
-version = "0.2.3"
+name = "errno-dragonfly"
+version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
+checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
- "cfg-if",
+ "cc",
"libc",
- "wasi",
]
[[package]]
-name = "libc"
-version = "0.2.103"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6"
-
-[[package]]
-name = "ppv-lite86"
-version = "0.2.10"
+name = "fastrand"
+version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
+checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
+dependencies = [
+ "instant",
+]
[[package]]
-name = "rand"
-version = "0.8.4"
+name = "filetime"
+version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
+checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153"
dependencies = [
+ "cfg-if",
"libc",
- "rand_chacha",
- "rand_core",
- "rand_hc",
+ "redox_syscall 0.2.16",
+ "windows-sys",
]
[[package]]
-name = "rand_chacha"
-version = "0.3.1"
+name = "hermit-abi"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
+
+[[package]]
+name = "instant"
+version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
- "ppv-lite86",
- "rand_core",
+ "cfg-if",
]
[[package]]
-name = "rand_core"
-version = "0.6.3"
+name = "io-lifetimes"
+version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
+checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
dependencies = [
- "getrandom",
+ "hermit-abi",
+ "libc",
+ "windows-sys",
]
[[package]]
-name = "rand_hc"
-version = "0.3.1"
+name = "libc"
+version = "0.2.147"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
+checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
- "rand_core",
+ "bitflags",
]
[[package]]
name = "redox_syscall"
-version = "0.2.10"
+version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
+checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
dependencies = [
"bitflags",
]
[[package]]
-name = "remove_dir_all"
-version = "0.5.3"
+name = "rustix"
+version = "0.37.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
+checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06"
dependencies = [
- "winapi",
+ "bitflags",
+ "errno",
+ "io-lifetimes",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
]
[[package]]
name = "tar"
-version = "0.4.38"
+version = "0.4.39"
dependencies = [
"filetime",
"libc",
@@ -119,51 +150,89 @@ dependencies = [
[[package]]
name = "tempfile"
-version = "3.2.0"
+version = "3.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
+checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6"
dependencies = [
+ "autocfg",
"cfg-if",
- "libc",
- "rand",
- "redox_syscall",
- "remove_dir_all",
- "winapi",
+ "fastrand",
+ "redox_syscall 0.3.5",
+ "rustix",
+ "windows-sys",
]
[[package]]
-name = "wasi"
-version = "0.10.2+wasi-snapshot-preview1"
+name = "windows-sys"
+version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets",
+]
[[package]]
-name = "winapi"
-version = "0.3.9"
+name = "windows-targets"
+version = "0.48.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f"
dependencies = [
- "winapi-i686-pc-windows-gnu",
- "winapi-x86_64-pc-windows-gnu",
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
]
[[package]]
-name = "winapi-i686-pc-windows-gnu"
-version = "0.4.0"
+name = "windows_aarch64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
[[package]]
-name = "winapi-x86_64-pc-windows-gnu"
-version = "0.4.0"
+name = "windows_x86_64_gnu"
+version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]]
name = "xattr"
-version = "0.2.2"
+version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c"
+checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc"
dependencies = [
"libc",
]
diff --git a/vendor/tar/Cargo.toml b/vendor/tar/Cargo.toml
index 23771b564918..6225b778f0d2 100644
--- a/vendor/tar/Cargo.toml
+++ b/vendor/tar/Cargo.toml
@@ -12,23 +12,35 @@
[package]
edition = "2018"
name = "tar"
-version = "0.4.38"
+version = "0.4.39"
authors = ["Alex Crichton <alex@alexcrichton.com>"]
exclude = ["tests/archives/*"]
-description = "A Rust implementation of a TAR file reader and writer. This library does not\ncurrently handle compression, but it is abstract over all I/O readers and\nwriters. Additionally, great lengths are taken to ensure that the entire\ncontents are never required to be entirely resident in memory all at once.\n"
+description = """
+A Rust implementation of a TAR file reader and writer. This library does not
+currently handle compression, but it is abstract over all I/O readers and
+writers. Additionally, great lengths are taken to ensure that the entire
+contents are never required to be entirely resident in memory all at once.
+"""
homepage = "https://github.com/alexcrichton/tar-rs"
documentation = "https://docs.rs/tar"
readme = "README.md"
-keywords = ["tar", "tarfile", "encoding"]
+keywords = [
+ "tar",
+ "tarfile",
+ "encoding",
+]
license = "MIT/Apache-2.0"
repository = "https://github.com/alexcrichton/tar-rs"
+
[dependencies.filetime]
version = "0.2.8"
+
[dev-dependencies.tempfile]
version = "3"
[features]
default = ["xattr"]
+
[target."cfg(unix)".dependencies.libc]
version = "0.2"
diff --git a/vendor/tar/src/archive.rs b/vendor/tar/src/archive.rs
index 1bed5124909e..c7a9d9803231 100644
--- a/vendor/tar/src/archive.rs
+++ b/vendor/tar/src/archive.rs
@@ -22,8 +22,10 @@ pub struct Archive<R: ?Sized + Read> {
pub struct ArchiveInner<R: ?Sized> {
pos: Cell<u64>,
+ mask: u32,
unpack_xattrs: bool,
preserve_permissions: bool,
+ preserve_ownerships: bool,
preserve_mtime: bool,
overwrite: bool,
ignore_zeros: bool,
@@ -52,8 +54,10 @@ impl<R: Read> Archive<R> {
pub fn new(obj: R) -> Archive<R> {
Archive {
inner: ArchiveInner {
+ mask: u32::MIN,
unpack_xattrs: false,
preserve_permissions: false,
+ preserve_ownerships: false,
preserve_mtime: true,
overwrite: true,
ignore_zeros: false,
@@ -106,6 +110,20 @@ impl<R: Read> Archive<R> {
me._unpack(dst.as_ref())
}
+ /// Set the mask of the permission bits when unpacking this entry.
+ ///
+ /// The mask will be inverted when applying against a mode, similar to how
+ /// `umask` works on Unix. In logical notation it looks like:
+ ///
+ /// ```text
+ /// new_mode = old_mode & (~mask)
+ /// ```
+ ///
+ /// The mask is 0 by default and is currently only implemented on Unix.
+ pub fn set_mask(&mut self, mask: u32) {
+ self.inner.mask = mask;
+ }
+
/// Indicate whether extended file attributes (xattrs on Unix) are preserved
/// when unpacking this archive.
///
@@ -126,6 +144,15 @@ impl<R: Read> Archive<R> {
self.inner.preserve_permissions = preserve;
}
+ /// Indicate whether numeric ownership ids (like uid and gid on Unix)
+ /// are preserved when unpacking this entry.
+ ///
+ /// This flag is disabled by default and is currently only implemented on
+ /// Unix.
+ pub fn set_preserve_ownerships(&mut self, preserve: bool) {
+ self.inner.preserve_ownerships = preserve;
+ }
+
/// Indicate whether files and symlinks should be overwritten on extraction.
pub fn set_overwrite(&mut self, overwrite: bool) {
self.inner.overwrite = overwrite;
@@ -304,10 +331,12 @@ impl<'a> EntriesFields<'a> {
long_pathname: None,
long_linkname: None,
pax_extensions: None,
+ mask: self.archive.inner.mask,
unpack_xattrs: self.archive.inner.unpack_xattrs,
preserve_permissions: self.archive.inner.preserve_permissions,
preserve_mtime: self.archive.inner.preserve_mtime,
overwrite: self.archive.inner.overwrite,
+ preserve_ownerships: self.archive.inner.preserve_ownerships,
};
// Store where the next entry is, rounding up by 512 bytes (the size of
diff --git a/vendor/tar/src/entry.rs b/vendor/tar/src/entry.rs
index 8f0b62acfb07..c81554fcbfff 100644
--- a/vendor/tar/src/entry.rs
+++ b/vendor/tar/src/entry.rs
@@ -31,6 +31,7 @@ pub struct EntryFields<'a> {
pub long_pathname: Option<Vec<u8>>,
pub long_linkname: Option<Vec<u8>>,
pub pax_extensions: Option<Vec<u8>>,
+ pub mask: u32,
pub header: Header,
pub size: u64,
pub header_pos: u64,
@@ -38,6 +39,7 @@ pub struct EntryFields<'a> {
pub data: Vec<EntryIo<'a>>,
pub unpack_xattrs: bool,
pub preserve_permissions: bool,
+ pub preserve_ownerships: bool,
pub preserve_mtime: bool,
pub overwrite: bool,
}
@@ -230,6 +232,20 @@ impl<'a, R: Read> Entry<'a, R> {
self.fields.unpack_in(dst.as_ref())
}
+ /// Set the mask of the permission bits when unpacking this entry.
+ ///
+ /// The mask will be inverted when applying against a mode, similar to how
+ /// `umask` works on Unix. In logical notation it looks like:
+ ///
+ /// ```text
+ /// new_mode = old_mode & (~mask)
+ /// ```
+ ///
+ /// The mask is 0 by default and is currently only implemented on Unix.
+ pub fn set_mask(&mut self, mask: u32) {
+ self.fields.mask = mask;
+ }
+
/// Indicate whether extended file attributes (xattrs on Unix) are preserved
/// when unpacking this entry.
///
@@ -444,13 +460,51 @@ impl<'a> EntryFields<'a> {
/// Returns access to the header of this entry in the archive.
fn unpack(&mut self, target_base: Option<&Path>, dst: &Path) -> io::Result<Unpacked> {
+ fn set_perms_ownerships(
+ dst: &Path,
+ f: Option<&mut std::fs::File>,
+ header: &Header,
+ mask: u32,
+ perms: bool,
+ ownerships: bool,
+ ) -> io::Result<()> {
+ // ownerships need to be set first to avoid stripping SUID bits in the permissions ...
+ if ownerships {
+ set_ownerships(dst, &f, header.uid()?, header.gid()?)?;
+ }
+ // ... then set permissions, SUID bits set here is kept
+ if let Ok(mode) = header.mode() {
+ set_perms(dst, f, mode, mask, perms)?;
+ }
+
+ Ok(())
+ }
+
+ fn get_mtime(header: &Header) -> Option<FileTime> {
+ header.mtime().ok().map(|mtime| {
+ // For some more information on this see the comments in
+ // `Header::fill_platform_from`, but the general idea is that
+ // we're trying to avoid 0-mtime files coming out of archives
+ // since some tools don't ingest them well. Perhaps one day
+ // when Cargo stops working with 0-mtime archives we can remove
+ // this.
+ let mtime = if mtime == 0 { 1 } else { mtime };
+ FileTime::from_unix_time(mtime as i64, 0)
+ })
+ }
+
let kind = self.header.entry_type();
if kind.is_dir() {
self.unpack_dir(dst)?;
- if let Ok(mode) = self.header.mode() {
- set_perms(dst, None, mode, self.preserve_permissions)?;
- }
+ set_perms_ownerships(
+ dst,
+ None,
+ &self.header,
+ self.mask,
+ self.preserve_permissions,
+ self.preserve_ownerships,
+ )?;
return Ok(Unpacked::__Nonexhaustive);
} else if kind.is_hard_link() || kind.is_symlink() {
let src = match self.link_name()? {
@@ -522,7 +576,14 @@ impl<'a> EntryFields<'a> {
),
)
})?;
- };
+ if self.preserve_mtime {
+ if let Some(mtime) = get_mtime(&self.header) {
+ filetime::set_symlink_file_times(dst, mtime, mtime).map_err(|e| {
+ TarError::new(format!("failed to set mtime for `{}`", dst.display()), e)
+ })?;
+ }
+ }
+ }
return Ok(Unpacked::__Nonexhaustive);
#[cfg(target_arch = "wasm32")]
@@ -553,9 +614,14 @@ impl<'a> EntryFields<'a> {
// Only applies to old headers.
if self.header.as_ustar().is_none() && self.path_bytes().ends_with(b"/") {
self.unpack_dir(dst)?;
- if let Ok(mode) = self.header.mode() {
- set_perms(dst, None, mode, self.preserve_permissions)?;
- }
+ set_perms_ownerships(
+ dst,
+ None,
+ &self.header,
+ self.mask,
+ self.preserve_permissions,
+ self.preserve_ownerships,
+ )?;
return Ok(Unpacked::__Nonexhaustive);
}
@@ -618,35 +684,105 @@ impl<'a> EntryFields<'a> {
})?;
if self.preserve_mtime {
- if let Ok(mtime) = self.header.mtime() {
- // For some more information on this see the comments in
- // `Header::fill_platform_from`, but the general idea is that
- // we're trying to avoid 0-mtime files coming out of archives
- // since some tools don't ingest them well. Perhaps one day
- // when Cargo stops working with 0-mtime archives we can remove
- // this.
- let mtime = if mtime == 0 { 1 } else { mtime };
- let mtime = FileTime::from_unix_time(mtime as i64, 0);
+ if let Some(mtime) = get_mtime(&self.header) {
filetime::set_file_handle_times(&f, Some(mtime), Some(mtime)).map_err(|e| {
TarError::new(format!("failed to set mtime for `{}`", dst.display()), e)
})?;
}
}
- if let Ok(mode) = self.header.mode() {
- set_perms(dst, Some(&mut f), mode, self.preserve_permissions)?;
- }
+ set_perms_ownerships(
+ dst,
+ Some(&mut f),
+ &self.header,
+ self.mask,
+ self.preserve_permissions,
+ self.preserve_ownerships,
+ )?;
if self.unpack_xattrs {
set_xattrs(self, dst)?;
}
return Ok(Unpacked::File(f));
+ fn set_ownerships(
+ dst: &Path,
+ f: &Option<&mut std::fs::File>,
+ uid: u64,
+ gid: u64,
+ ) -> Result<(), TarError> {
+ _set_ownerships(dst, f, uid, gid).map_err(|e| {
+ TarError::new(
+ format!(
+ "failed to set ownerships to uid={:?}, gid={:?} \
+ for `{}`",
+ uid,
+ gid,
+ dst.display()
+ ),
+ e,
+ )
+ })
+ }
+
+ #[cfg(unix)]
+ fn _set_ownerships(
+ dst: &Path,
+ f: &Option<&mut std::fs::File>,
+ uid: u64,
+ gid: u64,
+ ) -> io::Result<()> {
+ use std::convert::TryInto;
+ use std::os::unix::prelude::*;
+
+ let uid: libc::uid_t = uid.try_into().map_err(|_| {
+ io::Error::new(io::ErrorKind::Other, format!("UID {} is too large!", uid))
+ })?;
+ let gid: libc::gid_t = gid.try_into().map_err(|_| {
+ io::Error::new(io::ErrorKind::Other, format!("GID {} is too large!", gid))
+ })?;
+ match f {
+ Some(f) => unsafe {
+ let fd = f.as_raw_fd();
+ if libc::fchown(fd, uid, gid) != 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(())
+ }
+ },
+ None => unsafe {
+ let path = std::ffi::CString::new(dst.as_os_str().as_bytes()).map_err(|e| {
+ io::Error::new(
+ io::ErrorKind::Other,
+ format!("path contains null character: {:?}", e),
+ )
+ })?;
+ if libc::lchown(path.as_ptr(), uid, gid) != 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(())
+ }
+ },
+ }
+ }
+
+ // Windows does not support posix numeric ownership IDs
+ #[cfg(any(windows, target_arch = "wasm32"))]
+ fn _set_ownerships(
+ _: &Path,
+ _: &Option<&mut std::fs::File>,
+ _: u64,
+ _: u64,
+ ) -> io::Result<()> {
+ Ok(())
+ }
+
fn set_perms(
dst: &Path,
f: Option<&mut std::fs::File>,
mode: u32,
+ mask: u32,
preserve: bool,
) -> Result<(), TarError> {
- _set_perms(dst, f, mode, preserve).map_err(|e| {
+ _set_perms(dst, f, mode, mask, preserve).map_err(|e| {
TarError::new(
format!(
"failed to set permissions to {:o} \
@@ -664,11 +800,13 @@ impl<'a> EntryFields<'a> {
dst: &Path,
f: Option<&mut std::fs::File>,
mode: u32,
+ mask: u32,
preserve: bool,
) -> io::Result<()> {
use std::os::unix::prelude::*;
let mode = if preserve { mode } else { mode & 0o777 };
+ let mode = mode & !mask;
let perm = fs::Permissions::from_mode(mode as _);
match f {
Some(f) => f.set_permissions(perm),
@@ -681,6 +819,7 @@ impl<'a> EntryFields<'a> {
dst: &Path,
f: Option<&mut std::fs::File>,
mode: u32,
+ _mask: u32,
_preserve: bool,
) -> io::Result<()> {
if mode & 0o200 == 0o200 {
@@ -706,6 +845,7 @@ impl<'a> EntryFields<'a> {
dst: &Path,
f: Option<&mut std::fs::File>,
mode: u32,
+ mask: u32,
_preserve: bool,
) -> io::Result<()> {
Err(io::Error::new(io::ErrorKind::Other, "Not implemented"))
diff --git a/vendor/tar/tests/all.rs b/vendor/tar/tests/all.rs
index 11103bd6b80e..8c5359c56621 100644
--- a/vendor/tar/tests/all.rs
+++ b/vendor/tar/tests/all.rs
@@ -579,10 +579,21 @@ fn extracting_malicious_tarball() {
t!(a.append(&header, io::repeat(1).take(1)));
};
append("/tmp/abs_evil.txt");
- append("//tmp/abs_evil2.txt");
+ // std parse `//` as UNC path, see rust-lang/rust#100833
+ append(
+ #[cfg(not(windows))]
+ "//tmp/abs_evil2.txt",
+ #[cfg(windows)]
+ "C://tmp/abs_evil2.txt",
+ );
append("///tmp/abs_evil3.txt");
append("/./tmp/abs_evil4.txt");
- append("//./tmp/abs_evil5.txt");
+ append(
+ #[cfg(not(windows))]
+ "//./tmp/abs_evil5.txt",
+ #[cfg(windows)]
+ "C://./tmp/abs_evil5.txt",
+ );
append("///./tmp/abs_evil6.txt");
append("/../tmp/rel_evil.txt");
append("../rel_evil2.txt");
@@ -757,6 +768,40 @@ fn backslash_treated_well() {
assert!(fs::metadata(td.path().join("foo\\bar")).is_ok());
}
+#[test]
+#[cfg(unix)]
+fn set_mask() {
+ use ::std::os::unix::fs::PermissionsExt;
+ let mut ar = tar::Builder::new(Vec::new());
+
+ let mut header = tar::Header::new_gnu();
+ header.set_size(0);
+ header.set_entry_type(tar::EntryType::Regular);
+ t!(header.set_path("foo"));
+ header.set_mode(0o777);
+ header.set_cksum();
+ t!(ar.append(&header, &[][..]));
+
+ let mut header = tar::Header::new_gnu();
+ header.set_size(0);
+ header.set_entry_type(tar::EntryType::Regular);
+ t!(header.set_path("bar"));
+ header.set_mode(0o421);
+ header.set_cksum();
+ t!(ar.append(&header, &[][..]));
+
+ let td = t!(TempBuilder::new().prefix("tar-rs").tempdir());
+ let bytes = t!(ar.into_inner());
+ let mut ar = tar::Archive::new(&bytes[..]);
+ ar.set_mask(0o211);
+ t!(ar.unpack(td.path()));
+
+ let md = t!(fs::metadata(td.path().join("foo")));
+ assert_eq!(md.permissions().mode(), 0o100566);
+ let md = t!(fs::metadata(td.path().join("bar")));
+ assert_eq!(md.permissions().mode(), 0o100420);
+}
+
#[cfg(unix)]
#[test]
fn nul_bytes_in_path() {
@@ -792,6 +837,10 @@ fn unpack_links() {
let md = t!(fs::symlink_metadata(td.path().join("lnk")));
assert!(md.file_type().is_symlink());
+
+ let mtime = FileTime::from_last_modification_time(&md);
+ assert_eq!(mtime.unix_seconds(), 1448291033);
+
assert_eq!(
&*t!(fs::read_link(td.path().join("lnk"))),
Path::new("file")
@@ -1385,3 +1434,57 @@ fn header_size_overflow() {
err
);
}
+
+#[test]
+#[cfg(unix)]
+fn ownership_preserving() {
+ use std::os::unix::prelude::*;
+
+ let mut rdr = Vec::new();
+ let mut ar = Builder::new(&mut rdr);
+ let data: &[u8] = &[];
+ let mut header = Header::new_gnu();
+ // file 1 with uid = 580800000, gid = 580800000
+ header.set_gid(580800000);
+ header.set_uid(580800000);
+ t!(header.set_path("iamuid580800000"));
+ header.set_size(0);
+ header.set_cksum();
+ t!(ar.append(&header, data));
+ // file 2 with uid = 580800001, gid = 580800000
+ header.set_uid(580800001);
+ t!(header.set_path("iamuid580800001"));
+ header.set_cksum();
+ t!(ar.append(&header, data));
+ // file 3 with uid = 580800002, gid = 580800002
+ header.set_gid(580800002);
+ header.set_uid(580800002);
+ t!(header.set_path("iamuid580800002"));
+ header.set_cksum();
+ t!(ar.append(&header, data));
+ t!(ar.finish());
+
+ let rdr = Cursor::new(t!(ar.into_inner()));
+ let td = t!(TempBuilder::new().prefix("tar-rs").tempdir());
+ let mut ar = Archive::new(rdr);
+ ar.set_preserve_ownerships(true);
+
+ if unsafe { libc::getuid() } == 0 {
+ assert!(ar.unpack(td.path()).is_ok());
+ // validate against premade files
+ // iamuid580800001 has this ownership: 580800001:580800000
+ let meta = std::fs::metadata(td.path().join("iamuid580800000")).unwrap();
+ assert_eq!(meta.uid(), 580800000);
+ assert_eq!(meta.gid(), 580800000);
+ let meta = std::fs::metadata(td.path().join("iamuid580800001")).unwrap();
+ assert_eq!(meta.uid(), 580800001);
+ assert_eq!(meta.gid(), 580800000);
+ let meta = std::fs::metadata(td.path().join("iamuid580800002")).unwrap();
+ assert_eq!(meta.uid(), 580800002);
+ assert_eq!(meta.gid(), 580800002);
+ } else {
+ // it's not possible to unpack tar while preserving ownership
+ // without root permissions
+ assert!(ar.unpack(td.path()).is_err());
+ }
+}
diff --git a/vendor/tar/tests/entry.rs b/vendor/tar/tests/entry.rs
index fa8eeaee726d..62df663e8d18 100644
--- a/vendor/tar/tests/entry.rs
+++ b/vendor/tar/tests/entry.rs
@@ -180,6 +180,37 @@ fn directory_maintains_permissions() {
assert_eq!(md.permissions().mode(), 0o40777);
}
+#[test]
+#[cfg(unix)]
+fn set_entry_mask() {
+ use ::std::os::unix::fs::PermissionsExt;
+
+ let mut ar = tar::Builder::new(Vec::new());
+
+ let mut header = tar::Header::new_gnu();
+ header.set_size(0);
+ header.set_entry_type(tar::EntryType::Regular);
+ t!(header.set_path("foo"));
+ header.set_mode(0o777);
+ header.set_cksum();
+ t!(ar.append(&header, &[][..]));
+
+ let bytes = t!(ar.into_inner());
+ let mut ar = tar::Archive::new(&bytes[..]);
+ let td = t!(Builder::new().prefix("tar").tempdir());
+ let foo_path = td.path().join("foo");
+
+ let mut entries = t!(ar.entries());
+ let mut foo = t!(entries.next().unwrap());
+ foo.set_mask(0o027);
+ t!(foo.unpack(&foo_path));
+
+ let f = t!(File::open(foo_path));
+ let md = t!(f.metadata());
+ assert!(md.is_file());
+ assert_eq!(md.permissions().mode(), 0o100750);
+}
+
#[test]
#[cfg(not(windows))] // dangling symlinks have weird permissions
fn modify_link_just_created() {
--
2.41.0