Skip to content

Commit a31c16f

Browse files
committed
Load safe.directories config and check it for config.
This loads safe.directories from the home config, and then checks the ownership of all other directories.
1 parent fe72b8b commit a31c16f

File tree

2 files changed

+151
-3
lines changed

2 files changed

+151
-3
lines changed

src/cargo/util/config/key.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,10 @@ impl ConfigKey {
7373

7474
/// Rewinds this `ConfigKey` back to the state it was at before the last
7575
/// `push` method being called.
76-
pub fn pop(&mut self) {
77-
let (_part, env) = self.parts.pop().unwrap();
76+
pub fn pop(&mut self) -> String {
77+
let (part, env) = self.parts.pop().unwrap();
7878
self.env.truncate(env);
79+
part
7980
}
8081

8182
/// Returns the corresponding environment variable key for this

src/cargo/util/config/mod.rs

Lines changed: 148 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ use crate::core::compiler::rustdoc::RustdocExternMap;
7070
use crate::core::shell::Verbosity;
7171
use crate::core::{features, CliUnstable, Shell, SourceId, Workspace};
7272
use crate::ops;
73-
use crate::util::errors::CargoResult;
73+
use crate::util::errors::{ownership_error, CargoResult};
7474
use crate::util::toml as cargo_toml;
7575
use crate::util::validate_package_name;
7676
use crate::util::{FileLock, Filesystem, IntoUrl, IntoUrlWithBase, Rustc};
@@ -186,6 +186,7 @@ pub struct Config {
186186
doc_extern_map: LazyCell<RustdocExternMap>,
187187
progress_config: ProgressConfig,
188188
env_config: LazyCell<EnvConfig>,
189+
safe_directories: LazyCell<HashSet<PathBuf>>,
189190
/// This should be false if:
190191
/// - this is an artifact of the rustc distribution process for "stable" or for "beta"
191192
/// - this is an `#[test]` that does not opt in with `enable_nightly_features`
@@ -284,6 +285,7 @@ impl Config {
284285
doc_extern_map: LazyCell::new(),
285286
progress_config: ProgressConfig::default(),
286287
env_config: LazyCell::new(),
288+
safe_directories: LazyCell::new(),
287289
nightly_features_allowed: matches!(&*features::channel(), "nightly" | "dev"),
288290
}
289291
}
@@ -1057,6 +1059,103 @@ impl Config {
10571059
}
10581060
}
10591061

1062+
/// Checks if the given path is owned by a different user.
1063+
fn check_safe_dir(&self, path: &Path, safe_directories: &HashSet<PathBuf>) -> CargoResult<()> {
1064+
if !self.safe_directories_enabled() {
1065+
return Ok(());
1066+
}
1067+
paths::validate_ownership(path, safe_directories).map_err(|e| {
1068+
match e.downcast_ref::<paths::OwnershipError>() {
1069+
Some(e) => {
1070+
let mut to_add = e.path.parent().unwrap();
1071+
if to_add.file_name().and_then(|f| f.to_str()) == Some(".cargo") {
1072+
to_add = to_add.parent().unwrap();
1073+
}
1074+
ownership_error(e, "config files", to_add, self)
1075+
}
1076+
None => e,
1077+
}
1078+
})
1079+
}
1080+
1081+
/// Returns whether or not the nightly-only safe.directories behavior is enabled.
1082+
pub fn safe_directories_enabled(&self) -> bool {
1083+
// Because the config is loaded before the CLI options are available
1084+
// (primarily to handle aliases), this can't be gated on a `-Z` flag.
1085+
// So for now, this is only enabled via an environment variable. This
1086+
// has some downsides (such as not supporting the "allow" list), but
1087+
// should be fine for a one-off exception.
1088+
self.env
1089+
.get("CARGO_UNSTABLE_SAFE_DIRECTORIES")
1090+
.map(|s| s.as_str())
1091+
== Some("true")
1092+
&& self.nightly_features_allowed
1093+
}
1094+
1095+
/// Returns the `safe.directories` config setting.
1096+
pub fn safe_directories(&self) -> CargoResult<&HashSet<PathBuf>> {
1097+
self.safe_directories.try_borrow_with(|| {
1098+
if !self.safe_directories_enabled() {
1099+
return Ok(HashSet::new());
1100+
}
1101+
let mut safe_directories: HashSet<PathBuf> = self
1102+
.get_list("safe.directories")?
1103+
.map(|dirs| {
1104+
dirs.val
1105+
.iter()
1106+
.map(|(s, def)| def.root(self).join(s))
1107+
.collect()
1108+
})
1109+
.unwrap_or_default();
1110+
self.extend_safe_directories_env(&mut safe_directories);
1111+
Ok(safe_directories)
1112+
})
1113+
}
1114+
1115+
fn extend_safe_directories_env(&self, safe_directories: &mut HashSet<PathBuf>) {
1116+
// Note: Unlike other Cargo environment variables, this does not
1117+
// assume paths relative to the current directory (only absolute paths
1118+
// are supported). This is intended as an extra layer of safety.
1119+
if let Some(dirs) = self.env.get("CARGO_SAFE_DIRECTORIES") {
1120+
safe_directories.extend(env::split_paths(dirs));
1121+
}
1122+
if let Some(dirs) = self.env.get("RUSTUP_SAFE_DIRECTORIES") {
1123+
safe_directories.extend(env::split_paths(dirs));
1124+
}
1125+
}
1126+
1127+
/// Loads the `safe.directories` setting directly from a `ConfigValue`
1128+
/// (which should be a Table of the root of the config file).
1129+
fn safe_directories_from_cv(
1130+
&self,
1131+
root: Option<&ConfigValue>,
1132+
) -> CargoResult<HashSet<PathBuf>> {
1133+
if !self.safe_directories_enabled() {
1134+
return Ok(HashSet::new());
1135+
}
1136+
let mut safe_directories: HashSet<PathBuf> = root
1137+
.map(|root| root.get("safe.directories"))
1138+
.transpose()?
1139+
.flatten()
1140+
.map(|cv| cv.list("safe.directories"))
1141+
.transpose()?
1142+
.map(|dirs| {
1143+
dirs.iter()
1144+
.map(|(dir, def)| {
1145+
if dir != "*" {
1146+
def.root(self).join(dir)
1147+
} else {
1148+
PathBuf::from(dir)
1149+
}
1150+
})
1151+
.collect()
1152+
})
1153+
.unwrap_or_default();
1154+
1155+
self.extend_safe_directories_env(&mut safe_directories);
1156+
Ok(safe_directories)
1157+
}
1158+
10601159
fn load_file(&self, path: &Path, includes: bool) -> CargoResult<ConfigValue> {
10611160
self._load_file(path, &mut HashSet::new(), includes)
10621161
}
@@ -1281,6 +1380,7 @@ impl Config {
12811380
.merge(tmp_table, true)
12821381
.with_context(|| format!("failed to merge --config argument `{arg}`"))?;
12831382
}
1383+
reject_cli_unsupported(&loaded_args)?;
12841384
Ok(loaded_args)
12851385
}
12861386

@@ -1370,6 +1470,7 @@ impl Config {
13701470
} else {
13711471
None
13721472
};
1473+
let safe_directories = self.safe_directories_from_cv(home_cv.as_ref())?;
13731474

13741475
let mut result = Vec::new();
13751476

@@ -1380,7 +1481,17 @@ impl Config {
13801481
continue;
13811482
}
13821483
if let Some(path) = self.get_file_path(&dot_cargo, "config", true)? {
1484+
self.check_safe_dir(&path, &safe_directories)?;
13831485
let cv = self._load_file(&path, &mut seen, includes)?;
1486+
if let Some(safe) = cv.get("safe.directories")? {
1487+
bail!(
1488+
"safe.directories may only be configured from Cargo's home directory\n\
1489+
Found `safe.directories` in {}\n\
1490+
Cargo's home directory is {}\n",
1491+
safe.definition(),
1492+
home.display()
1493+
);
1494+
}
13841495
result.push(cv);
13851496
}
13861497
}
@@ -1982,6 +2093,28 @@ impl ConfigValue {
19822093
self.definition()
19832094
)
19842095
}
2096+
2097+
/// Retrieve a `ConfigValue` using a dotted key notation.
2098+
///
2099+
/// This is similar to `Config::get`, but can be used directly on a root
2100+
/// `ConfigValue::Table`. This does *not* look at environment variables.
2101+
fn get(&self, key: &str) -> CargoResult<Option<&ConfigValue>> {
2102+
let mut key = ConfigKey::from_str(key);
2103+
let last = key.pop();
2104+
let (mut table, _def) = self.table("")?;
2105+
let mut key_so_far = ConfigKey::new();
2106+
for part in key.parts() {
2107+
key_so_far.push(part);
2108+
match table.get(part) {
2109+
Some(cv) => match cv {
2110+
CV::Table(t, _def) => table = t,
2111+
_ => cv.expected("table", &key_so_far.to_string())?,
2112+
},
2113+
None => return Ok(None),
2114+
}
2115+
}
2116+
Ok(table.get(&last))
2117+
}
19852118
}
19862119

19872120
pub fn homedir(cwd: &Path) -> Option<PathBuf> {
@@ -2473,3 +2606,17 @@ macro_rules! drop_eprint {
24732606
$crate::__shell_print!($config, err, false, $($arg)*)
24742607
);
24752608
}
2609+
2610+
/// Rejects config entries set on the CLI that are not supported.
2611+
fn reject_cli_unsupported(root: &CV) -> CargoResult<()> {
2612+
let (root, _) = root.table("")?;
2613+
// safe.directories cannot be set on the CLI because the config is loaded
2614+
// before the CLI is parsed (primarily to handle aliases).
2615+
if let Some(cv) = root.get("safe") {
2616+
let (safe, _) = cv.table("safe")?;
2617+
if safe.contains_key("directories") {
2618+
bail!("safe.directories cannot be set via the CLI");
2619+
}
2620+
}
2621+
Ok(())
2622+
}

0 commit comments

Comments
 (0)