1
1
mirror of https://github.com/mgree/ffs.git synced 2024-10-03 22:07:18 +03:00

impl --max-depth and --allow-symlink-escape. tests needed

This commit is contained in:
nad 2023-08-09 11:09:21 -04:00
parent f2aba26741
commit a486d7b1bb
4 changed files with 104 additions and 111 deletions

View File

@ -1,5 +1,5 @@
use std::fs;
use std::os::unix::fs::MetadataExt;
// use std::os::unix::fs::MetadataExt;
use std::collections::BTreeMap;
use std::collections::HashMap;
@ -28,12 +28,14 @@ use ::xattr;
pub struct Pack {
pub symlinks: HashMap<PathBuf, PathBuf>,
depth: u32,
}
impl Pack {
pub fn new() -> Self {
Self {
symlinks: HashMap::new(),
depth: 0,
}
}
@ -41,6 +43,14 @@ impl Pack {
where
V: Nodelike + std::fmt::Display + Default,
{
if config
.max_depth
.is_some_and(|max_depth| self.depth > max_depth)
{
self.depth -= 1;
return Ok(None);
}
// resolve symlink once and map the path to the linked file or
// to itself if it is not a symlink
if !self.symlinks.contains_key(&path) {
@ -64,87 +74,81 @@ impl Pack {
match &config.symlink {
// early return because we want to ignore this symlink,
// but we still add it to self.symlinks above
Symlink::NoFollow => return Ok(None),
Symlink::Follow(optional_links) => {
if optional_links.clone().is_some_and(|links| {
links
.iter()
.find(|link| {
// find by inode to avoid PathBuf comparison issue
// because of . or .. being unresolved.
link.symlink_metadata().unwrap().ino()
== path.symlink_metadata().unwrap().ino()
})
.is_some()
}) || optional_links.is_none()
{
let mut link_trail = Vec::new();
let mut link_follower = path.clone();
while link_follower.is_symlink() {
if link_trail.contains(&link_follower) {
error!("Symlink loop detected at {:?}.", link_follower);
std::process::exit(ERROR_STATUS_FUSE);
}
link_trail.push(link_follower.clone());
Symlink::NoFollow => {
self.depth -= 1;
return Ok(None);
}
Symlink::Follow => {
let mut link_trail = Vec::new();
let mut link_follower = path.clone();
while link_follower.is_symlink() {
if link_trail.contains(&link_follower) {
error!("Symlink loop detected at {:?}.", link_follower);
std::process::exit(ERROR_STATUS_FUSE);
}
link_trail.push(link_follower.clone());
if path_type.is_empty() {
// get the xattr of the first symlink that has it.
// this has the effect of inheriting xattrs from links down the
// chain.
match xattr::get(&link_follower, "user.type") {
Ok(Some(xattr)) if config.allow_xattr => path_type = xattr,
Ok(_) => (),
// TODO(nad) 2023-08-07: maybe unnecessary to check for ._ as
// symlink?
Err(_) => {
// Cannot call xattr::get on ._ file
warn!(
"._ files, like {:?}, prevent xattr calls. It will be encoded in base64.",
link_follower
);
path_type = b"bytes".to_vec()
}
};
}
// add the link to the mapping here if it wasn't already added above
if !self.symlinks.contains_key(&link_follower) {
let link = link_follower.read_link()?;
if link.is_absolute() {
self.symlinks.insert(link_follower.clone(), link);
} else {
self.symlinks.insert(
link_follower.clone(),
link_follower.clone().parent().unwrap().join(link),
if path_type.is_empty() {
// get the xattr of the first symlink that has it.
// this has the effect of inheriting xattrs from links down the
// chain.
match xattr::get(&link_follower, "user.type") {
Ok(Some(xattr)) if config.allow_xattr => path_type = xattr,
Ok(_) => (),
// TODO(nad) 2023-08-07: maybe unnecessary to check for ._ as
// symlink?
Err(_) => {
// Cannot call xattr::get on ._ file
warn!(
"._ files, like {:?}, prevent xattr calls. It will be encoded in base64.",
link_follower
);
path_type = b"bytes".to_vec()
}
};
}
// add the link to the mapping here if it wasn't already added above
if !self.symlinks.contains_key(&link_follower) {
let link = link_follower.read_link()?;
if link.is_absolute() {
self.symlinks.insert(link_follower.clone(), link);
} else {
self.symlinks.insert(
link_follower.clone(),
link_follower.clone().parent().unwrap().join(link),
);
}
link_follower = self.symlinks[&link_follower].clone();
}
link_follower = self.symlinks[&link_follower].clone();
}
if !link_follower.exists() {
error!("The symlink {:?} is broken.", path);
std::process::exit(ERROR_STATUS_FUSE);
}
if !link_follower.exists() {
error!("The symlink {:?} is broken.", path);
std::process::exit(ERROR_STATUS_FUSE);
}
let canonicalized = link_follower.canonicalize()?;
if path.starts_with(&canonicalized) {
error!(
"The symlink {:?} points to some ancestor directory: {:?}",
path, canonicalized
);
std::process::exit(ERROR_STATUS_FUSE);
}
} else {
// optional links exist but path inode doesn't match that of any
// of the specified paths.
// early return because we want to ignore this symlink
// but we still add it to self.symlinks above
// we reached the actual destination
let canonicalized = link_follower.canonicalize()?;
if path.starts_with(&canonicalized) {
error!(
"The symlink {:?} points to some ancestor directory: {:?}",
path, canonicalized
);
std::process::exit(ERROR_STATUS_FUSE);
}
if !config.allow_symlink_escape
&& !canonicalized.starts_with(config.mount.clone().unwrap())
{
warn!("The symlink {:?} points to some file outside of the directory being packed. \
Specify --allow-symlink-escape to allow pack to follow this symlink.", path);
self.depth -= 1;
return Ok(None);
}
}
}
}
// if the xattr isn't detected yet, either path is not a symlink or
// none of the symlinks on the chain have an xattr.
if path_type.is_empty() {
@ -214,6 +218,7 @@ impl Pack {
info!("type of {:?} is {}", path, path_type);
// return the value
match path_type {
"named" => {
let mut children = fs::read_dir(path.clone())?
@ -247,6 +252,7 @@ impl Pack {
name = child_name.to_string();
}
}
self.depth += 1;
let value = self.pack(child.clone(), &config)?;
match value {
Some(value) => {
@ -278,6 +284,7 @@ impl Pack {
warn!("skipping ignored file {:?}", child_name);
continue;
}
self.depth += 1;
let value = self.pack(child, &config)?;
match value {
Some(value) => {
@ -300,9 +307,13 @@ impl Pack {
if config.add_newlines && contents.ends_with('\n') {
contents.truncate(contents.len() - 1);
}
self.depth -= 1;
Ok(Some(V::from_string(t, contents, &config)))
}
Ok(_) | Err(_) => Ok(Some(V::from_bytes(contents, &config))),
Ok(_) | Err(_) => {
self.depth -= 1;
Ok(Some(V::from_bytes(contents, &config)))
}
}
} else {
error!(

View File

@ -286,23 +286,12 @@ pub fn pack() -> App<'static, 'static> {
.help("Never follow symbolic links. This is the default behaviour. `pack` will ignore all symbolic links.")
.short("P")
.overrides_with("FOLLOW_SYMLINKS")
.overrides_with("FOLLOW_SPECIFIED_SYMLINKS")
)
.arg(
Arg::with_name("FOLLOW_SYMLINKS")
.help("Follow all symlinks. For safety, you can also specify a --max-depth value.")
.short("L")
.overrides_with("NOFOLLOW_SYMLINKS")
.overrides_with("FOLLOW_SPECIFIED_SYMLINKS")
)
.arg(
Arg::with_name("FOLLOW_SPECIFIED_SYMLINKS")
.help("Follow all symlinks specified on the command line. For safety, you can also specify a --max-depth value.")
.short("H")
// .takes_value(true)
.min_values(1)
.overrides_with("NOFOLLOW_SYMLINKS")
.overrides_with("FOLLOW_SYMLINKS")
)
.arg(
Arg::with_name("MAXDEPTH")
@ -310,6 +299,11 @@ pub fn pack() -> App<'static, 'static> {
.long("max-depth")
.takes_value(true)
)
.arg(
Arg::with_name("ALLOW_SYMLINK_ESCAPE")
.help("Allows pack to follow symlinks outside of the directory being packed.")
.long("allow-symlink-escape")
)
.arg(
Arg::with_name("NOXATTR")
.help("Don't use extended attributes to track metadata (see `man xattr`)")

View File

@ -42,6 +42,7 @@ pub struct Config {
pub keep_macos_xattr_file: bool,
pub symlink: Symlink,
pub max_depth: Option<u32>,
pub allow_symlink_escape: bool,
pub munge: Munge,
pub read_only: bool,
pub input: Input,
@ -110,7 +111,7 @@ impl FromStr for Munge {
#[derive(Debug)]
pub enum Symlink {
NoFollow,
Follow(Option<Vec<PathBuf>>),
Follow,
}
impl Config {
@ -780,25 +781,12 @@ impl Config {
config.add_newlines = !args.is_present("EXACT");
config.read_only = args.is_present("READONLY");
config.allow_xattr = !args.is_present("NOXATTR");
config.allow_symlink_escape = args.is_present("ALLOW_SYMLINK_ESCAPE");
config.keep_macos_xattr_file = args.is_present("KEEPMACOSDOT");
config.pretty = args.is_present("PRETTY");
config.symlink = if args.is_present("FOLLOW_SYMLINKS") {
Symlink::Follow(None)
} else if args.is_present("FOLLOW_SPECIFIED_SYMLINKS") {
let cwd = std::env::current_dir().unwrap();
Symlink::Follow(Some(
args.values_of("FOLLOW_SPECIFIED_SYMLINKS")
.unwrap()
.map(|p| {
if PathBuf::from(p).is_absolute() {
PathBuf::from(p)
} else {
cwd.join(p)
}
})
.collect(),
))
Symlink::Follow
} else {
Symlink::NoFollow
};
@ -1015,6 +1003,7 @@ impl Default for Config {
keep_macos_xattr_file: false,
symlink: Symlink::NoFollow,
max_depth: None,
allow_symlink_escape: false,
munge: Munge::Rename,
read_only: false,
input: Input::Stdin,

View File

@ -96,9 +96,9 @@ printf '{"a":"a","b":"a","c":"a","d":"a","e":"a","tree":{"about":"tree about","r
pack -o "$OUT" -L -- "$MNT" || fail pack2
diff "$EXP" "$OUT" || fail "test0 follow"
printf '{"a":"a","d":"a","tree":{"about":"tree about","root":"tree root"}}' >"$EXP"
pack -o "$OUT" -H "$MNT"/d -- "$MNT" || fail pack3
diff "$EXP" "$OUT" || fail "test0 follow-specified"
# printf '{"a":"a","d":"a","tree":{"about":"tree about","root":"tree root"}}' >"$EXP"
# pack -o "$OUT" -H "$MNT"/d -- "$MNT" || fail pack3
# diff "$EXP" "$OUT" || fail "test0 follow-specified"
rm -r "$MNT"
mkdir "$MNT"
@ -141,9 +141,9 @@ printf '{"ascending":[4,4,4,4,4],"descending":[0,0,0,0,0]}' >"$EXP"
pack -o "$OUT" -L -- "$MNT" || fail pack5
diff "$EXP" "$OUT" || fail "test1 follow"
printf '{"ascending":[4,4],"descending":[0,0]}' >"$EXP"
pack -o "$OUT" -H "$MNT"/ascending/2 "$MNT"/descending/3 -- "$MNT" || fail pack6
diff "$EXP" "$OUT" || fail "test1 follow-specified"
# printf '{"ascending":[4,4],"descending":[0,0]}' >"$EXP"
# pack -o "$OUT" -H "$MNT"/ascending/2 "$MNT"/descending/3 -- "$MNT" || fail pack6
# diff "$EXP" "$OUT" || fail "test1 follow-specified"
rm -r "$MNT"
mkdir "$MNT"
@ -175,9 +175,9 @@ printf '{"path":{"to":{"other":{"file":{"data":null}},"some":{"link":{"abs":null
pack -o "$OUT" -L -- "$MNT" || fail pack8
diff "$EXP" "$OUT" || fail "test2 follow"
printf '{"path":{"to":{"other":{"file":{"data":null}},"some":{"link":{"abs":null}}}}}' >"$EXP"
pack -o "$OUT" -H "$MNT"/path/to/some/link/abs -- "$MNT" || fail pack9
diff "$EXP" "$OUT" || fail "test2 follow-specified"
# printf '{"path":{"to":{"other":{"file":{"data":null}},"some":{"link":{"abs":null}}}}}' >"$EXP"
# pack -o "$OUT" -H "$MNT"/path/to/some/link/abs -- "$MNT" || fail pack9
# diff "$EXP" "$OUT" || fail "test2 follow-specified"
rm -r "$MNT"
mkdir "$MNT"
@ -266,10 +266,9 @@ if [ "$RUNNER_OS" = "macOS" ] || [ "$(uname)" = "Darwin" ]; then
pack -o "$OUT" -L -- "$MNT" || fail pack15
diff "$EXP" "$OUT" || fail "test5 follow"
# TODO(nad) 2023-08-08: maybe only xattrs from followed symlinks are allowed.
printf '{"a":4,"b":4,"d":"4","f":"NAo="}' >"$EXP"
pack -o "$OUT" -H "$MNT"/b "$MNT"/d "$MNT"/f -- "$MNT" || fail pack16
diff "$EXP" "$OUT" || fail "test5 follow-specified"
# printf '{"a":4,"b":4,"d":"4","f":"NAo="}' >"$EXP"
# pack -o "$OUT" -H "$MNT"/b "$MNT"/d "$MNT"/f -- "$MNT" || fail pack16
# diff "$EXP" "$OUT" || fail "test5 follow-specified"
fi
rm "$EXP" "$OUT"