Skip to content

Commit a9323d8

Browse files
authored
Merge pull request #431 from cbgbt/canonicalize
twoliter: disallow std::path::Path::canonicalize
2 parents 7eeedfa + 86f7a59 commit a9323d8

File tree

8 files changed

+128
-6
lines changed

8 files changed

+128
-6
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ buildsys-config = { version = "0.1", path = "tools/buildsys-config" }
5454
krane-static = { version = "0.1", path = "tools/krane" }
5555
oci-cli-wrapper = { version = "0.1", path = "tools/oci-cli-wrapper" }
5656
parse-datetime = { version = "0.1", path = "tools/parse-datetime" }
57+
path-absolutize = "3.1"
5758
pipesys = { version = "0.1", path = "tools/pipesys", lib = true, artifact = [ "bin:pipesys" ] }
5859
pubsys = { version = "0.1", path = "tools/pubsys", artifact = [ "bin:pubsys" ] }
5960
pubsys-config = { version = "0.1", path = "tools/pubsys-config" }
@@ -147,3 +148,6 @@ which = "6"
147148
[profile.dist]
148149
inherits = "release"
149150
lto = "thin"
151+
152+
[workspace.lints.clippy]
153+
disallowed-methods = "deny"

clippy.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[[disallowed-methods]]
2+
path = "std::path::Path::canonicalize"
3+
reason = "Twoliter usually does not want to resolve symlinks. Use path_absolutize instead"
4+
5+
[[disallowed-methods]]
6+
path = "std::fs::canonicalize"
7+
reason = "Twoliter usually does not want to resolve symlinks. Use path_absolutize instead"
8+
9+
[[disallowed-methods]]
10+
path = "tokio::fs::canonicalize"
11+
reason = "Twoliter usually does not want to resolve symlinks. Use path_absolutize instead"
12+

tests/integration-tests/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::path::PathBuf;
55
use std::process::Command;
66
use tempfile::TempDir;
77

8+
mod twoliter_build;
89
mod twoliter_update;
910

1011
pub const TWOLITER_PATH: &'static str = env!("CARGO_BIN_FILE_TWOLITER");
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
use super::{run_command, test_projects_dir, TWOLITER_PATH};
2+
use std::path::Path;
3+
use tempfile::TempDir;
4+
5+
#[test]
6+
#[ignore]
7+
fn test_workspace_symlinks_not_followed() {
8+
// Ensure a symlinked `Twoliter.toml` does not trick us into putting our build directory into
9+
// the symlink target's parent directory.
10+
11+
let target_kit = test_projects_dir().join("local-kit");
12+
let work_dir = copy_project_to_temp_dir(&target_kit);
13+
14+
let work_dir_path = work_dir.path();
15+
16+
let working_twoliter_toml = work_dir_path.join("Twoliter.toml");
17+
18+
// Replace Twoliter.toml with a symlink
19+
std::fs::remove_file(&working_twoliter_toml).unwrap();
20+
std::os::unix::fs::symlink(
21+
target_kit.join("Twoliter.toml"),
22+
work_dir_path.join("Twoliter.toml"),
23+
)
24+
.unwrap();
25+
26+
assert!(!work_dir_path.join("build").is_dir());
27+
28+
run_command(
29+
TWOLITER_PATH,
30+
[
31+
"update",
32+
"--project-path",
33+
working_twoliter_toml.to_str().unwrap(),
34+
],
35+
[],
36+
);
37+
run_command(
38+
TWOLITER_PATH,
39+
[
40+
"fetch",
41+
"--project-path",
42+
working_twoliter_toml.to_str().unwrap(),
43+
],
44+
[],
45+
);
46+
47+
assert!(work_dir_path.join("build").is_dir());
48+
}
49+
50+
pub(crate) fn copy_project_to_temp_dir(project: impl AsRef<Path>) -> TempDir {
51+
let temp_dir = TempDir::new().unwrap();
52+
copy_most_dirs_recursively(project, &temp_dir);
53+
temp_dir
54+
}
55+
56+
/// Copy dirs recursively except for some of the larger "ignoreable" dirs that may exist in the
57+
/// user's checkout.
58+
fn copy_most_dirs_recursively(src: impl AsRef<Path>, dst: impl AsRef<Path>) {
59+
let src = src.as_ref();
60+
let dst = dst.as_ref();
61+
for entry in std::fs::read_dir(src).unwrap() {
62+
std::fs::create_dir_all(&dst).unwrap();
63+
let entry = entry.unwrap();
64+
let file_type = entry.file_type().unwrap();
65+
if file_type.is_dir() {
66+
let name = entry.file_name().to_str().unwrap().to_string();
67+
if matches!(name.as_ref(), "target" | "build" | ".gomodcache" | ".cargo") {
68+
continue;
69+
}
70+
copy_most_dirs_recursively(&entry.path(), &dst.join(entry.file_name()));
71+
} else {
72+
std::fs::copy(entry.path(), dst.join(entry.file_name())).unwrap();
73+
}
74+
}
75+
}

twoliter/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ lazy_static.workspace = true
2727
log.workspace = true
2828
oci-cli-wrapper.workspace = true
2929
olpc-cjson.workspace = true
30+
path-absolutize.workspace = true
3031
semver = { workspace = true, features = ["serde"] }
3132
serde = { workspace = true, features = ["derive"] }
3233
serde_json.workspace = true
@@ -61,3 +62,6 @@ test-case.workspace = true
6162
default = ["integ-tests", "pubsys"]
6263
integ-tests = []
6364
pubsys = ["dep:pubsys"]
65+
66+
[lints]
67+
workspace = true

twoliter/src/common.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,18 +72,24 @@ pub(crate) async fn exec(cmd: &mut Command, quiet: bool) -> Result<Option<String
7272
#[allow(dead_code)]
7373
pub(crate) mod fs {
7474
use anyhow::{Context, Result};
75+
use path_absolutize::Absolutize;
7576
use std::fs::Metadata;
7677
use std::io::ErrorKind;
7778
use std::path::{Path, PathBuf};
7879
use tokio::fs;
7980
use tracing::instrument;
8081

82+
/// Canonicalizes the input path based on `cwd` without resolving symlinks or accessing the
83+
/// filesystem.
8184
#[instrument(level = "trace", skip(path), fields(path = %path.as_ref().display()))]
8285
pub(crate) async fn canonicalize(path: impl AsRef<Path>) -> Result<PathBuf> {
83-
fs::canonicalize(path.as_ref()).await.context(format!(
84-
"Unable to canonicalize '{}'",
85-
path.as_ref().display()
86-
))
86+
path.as_ref()
87+
.absolutize()
88+
.context(format!(
89+
"Unable to canonicalize '{}'",
90+
path.as_ref().display()
91+
))
92+
.map(|p| p.to_path_buf())
8793
}
8894

8995
#[instrument(

twoliter/src/project/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub(crate) use self::image::{Image, ProjectImage, ValidIdentifier, VendedArtifac
77
pub(crate) use self::vendor::ArtifactVendor;
88
use lock::LockedImage;
99
pub(crate) use lock::VerificationTagger;
10+
use path_absolutize::Absolutize;
1011

1112
use self::lock::{Lock, LockedSDK, Override};
1213
use crate::common::fs::{self, read_to_string};
@@ -108,7 +109,7 @@ impl Project<Unlocked> {
108109
dir.display()
109110
);
110111
let dir = dir
111-
.canonicalize()
112+
.absolutize()
112113
.context(format!("Unable to canonicalize '{}'", dir.display()))?;
113114
let filepath = dir.join("Twoliter.toml");
114115
if filepath.is_file() {

0 commit comments

Comments
 (0)