Skip to content

Commit f315a70

Browse files
committed
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.
1 parent ac6f044 commit f315a70

File tree

6 files changed

+46
-10
lines changed

6 files changed

+46
-10
lines changed

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ sha2 = "0.10.6"
8080
shell-escape = "0.1.4"
8181
snapbox = { version = "0.4.0", features = ["diff", "path"] }
8282
strip-ansi-escapes = "0.1.0"
83-
tar = { version = "0.4.38", default-features = false }
83+
tar = { version = "0.4.39", default-features = false }
8484
tempfile = "3.1.0"
8585
termcolor = "1.1.2"
8686
time = { version = "0.3", features = ["parsing", "formatting"] }

src/cargo/sources/registry/mod.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,9 @@ use std::borrow::Cow;
162162
use std::collections::BTreeMap;
163163
use std::collections::HashSet;
164164
use std::fs::{File, OpenOptions};
165-
use std::io::{self, Write};
165+
use std::io;
166+
use std::io::Read;
167+
use std::io::Write;
166168
use std::path::{Path, PathBuf};
167169
use std::task::{ready, Poll};
168170

@@ -698,7 +700,9 @@ impl<'cfg> RegistrySource<'cfg> {
698700
let size_limit = max_unpack_size(self.config, tarball.metadata()?.len());
699701
let gz = GzDecoder::new(tarball);
700702
let gz = LimitErrorReader::new(gz, size_limit);
701-
Archive::new(gz)
703+
let mut tar = Archive::new(gz);
704+
set_mask(&mut tar);
705+
tar
702706
};
703707
let prefix = unpack_dir.file_name().unwrap();
704708
let parent = unpack_dir.parent().unwrap();
@@ -1036,3 +1040,16 @@ mod tests {
10361040
assert_eq!(make_dep_prefix("aBcDe"), "aB/cD");
10371041
}
10381042
}
1043+
1044+
/// Set the current [`umask`] value for the given tarball. No-op on non-Unix
1045+
/// platforms.
1046+
///
1047+
/// On Windows, tar only looks at user permissions and tries to set the "read
1048+
/// only" attribute, so no-op as well.
1049+
///
1050+
/// [`umask`]: https://man7.org/linux/man-pages/man2/umask.2.html
1051+
#[allow(unused_variables)]
1052+
fn set_mask<R: Read>(tar: &mut Archive<R>) {
1053+
#[cfg(unix)]
1054+
tar.set_mask(crate::util::get_umask());
1055+
}

src/cargo/util/mod.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,24 @@ pub fn try_canonicalize<P: AsRef<Path>>(path: P) -> std::io::Result<PathBuf> {
207207
})
208208
}
209209

210+
/// Get the current [`umask`] value.
211+
///
212+
/// [`umask`]: https://man7.org/linux/man-pages/man2/umask.2.html
213+
#[cfg(unix)]
214+
pub fn get_umask() -> u32 {
215+
use std::sync::OnceLock;
216+
static UMASK: OnceLock<libc::mode_t> = OnceLock::new();
217+
// SAFETY: Syscalls are unsafe. Calling `umask` twice is even unsafer for
218+
// multithreading program, since it doesn't provide a way to retrive the
219+
// value without modifications. We use a static `OnceLock` here to ensure
220+
// it only gets call once during the entire program lifetime.
221+
*UMASK.get_or_init(|| unsafe {
222+
let umask = libc::umask(0o022);
223+
libc::umask(umask);
224+
umask
225+
}) as u32 // it is u16 on macos
226+
}
227+
210228
#[cfg(test)]
211229
mod test {
212230
use super::*;

tests/testsuite/registry.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3452,9 +3452,9 @@ fn set_mask_during_unpacking() {
34523452
.unwrap()
34533453
};
34543454

3455-
// Assuming umask is `0o022`.
3455+
let umask = cargo::util::get_umask();
34563456
let metadata = fs::metadata(src_file_path("src/lib.rs")).unwrap();
3457-
assert_eq!(metadata.mode() & 0o777, 0o666);
3457+
assert_eq!(metadata.mode() & 0o777, 0o666 & !umask);
34583458
let metadata = fs::metadata(src_file_path("example.sh")).unwrap();
3459-
assert_eq!(metadata.mode() & 0o777, 0o777);
3459+
assert_eq!(metadata.mode() & 0o777, 0o777 & !umask);
34603460
}

tests/testsuite/vendor.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,10 +1051,11 @@ fn vendor_preserves_permissions() {
10511051

10521052
p.cargo("vendor --respect-source-config").run();
10531053

1054+
let umask = cargo::util::get_umask();
10541055
let metadata = fs::metadata(p.root().join("vendor/bar/src/lib.rs")).unwrap();
1055-
assert_eq!(metadata.mode() & 0o777, 0o644);
1056+
assert_eq!(metadata.mode() & 0o777, 0o644 & !umask);
10561057
let metadata = fs::metadata(p.root().join("vendor/bar/example.sh")).unwrap();
1057-
assert_eq!(metadata.mode() & 0o777, 0o755);
1058+
assert_eq!(metadata.mode() & 0o777, 0o755 & !umask);
10581059
}
10591060

10601061
#[cargo_test]

0 commit comments

Comments
 (0)