Skip to content

Commit

Permalink
entry: convert hard link absolute paths to relative ones
Browse files Browse the repository at this point in the history
When unpacking a docker image, some hard links may be pointing to, say,
/bin/gunzip. Those were failing the validate_inside_dst() check,
preventing to unpack the image.

Fix this by re-using the same logic as we are already using for actual
files, removing the leading '/' from the link target path.

We do this only if the absolute link is not an ancestor of the target
dir as we want to support this use case according to the existing
absolute_hardlink() test.
  • Loading branch information
Guillaume Desmottes committed May 27, 2021
1 parent 7f2a355 commit 5125587
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 2 deletions.
13 changes: 13 additions & 0 deletions src/entry.rs
Expand Up @@ -467,6 +467,9 @@ impl<'a> EntryFields<'a> {
// the destination of this hard link is both present and
// inside our own directory. This is needed because we want
// to make sure to not overwrite anything outside the root.
// If the link points to an absolute path, assume it's relative
// to the target dir by removing the leading '/', as we already
// do for files, see unpack_in() comments.
//
// Note that this logic is only needed for hard links
// currently. With symlinks the `validate_inside_dst` which
Expand All @@ -475,6 +478,16 @@ impl<'a> EntryFields<'a> {
// links though they're canonicalized to their existing path
// so we need to validate at this time.
Some(ref p) => {
let mut src = src.to_path_buf();
if src.is_absolute() {
let dest_canon = p.canonicalize()?;
if !src.starts_with(&dest_canon) {
// Skip root component, making the target relative to the target dir
let components = src.components().skip(1);
src = components.collect();
}
}

let link_src = p.join(src);
self.validate_inside_dst(p, &link_src)?;
link_src
Expand Down
18 changes: 16 additions & 2 deletions tests/entry.rs
Expand Up @@ -44,6 +44,10 @@ fn absolute_symlink() {
#[test]
fn absolute_hardlink() {
let td = t!(Builder::new().prefix("tar").tempdir());
// on macos the tempdir is created in /var/folders/ which is actually
// /private/var/folders/. Canonicalize the path so the link target has
// the proper base.
let td_canon = td.path().canonicalize().unwrap();
let mut ar = tar::Builder::new(Vec::new());

let mut header = tar::Header::new_gnu();
Expand All @@ -58,16 +62,26 @@ fn absolute_hardlink() {
header.set_entry_type(tar::EntryType::Link);
t!(header.set_path("bar"));
// This absolute path under tempdir will be created at unpack time
t!(header.set_link_name(td.path().join("foo")));
t!(header.set_link_name(td_canon.join("foo")));
header.set_cksum();
t!(ar.append(&header, &[][..]));

let mut header = tar::Header::new_gnu();
header.set_size(0);
header.set_entry_type(tar::EntryType::Link);
t!(header.set_path("baz"));
// This absolute path on root will be converted when unpacking
t!(header.set_link_name("/foo"));
header.set_cksum();
t!(ar.append(&header, &[][..]));

let bytes = t!(ar.into_inner());
let mut ar = tar::Archive::new(&bytes[..]);

t!(ar.unpack(td.path()));
t!(ar.unpack(&td_canon));
t!(td.path().join("foo").metadata());
t!(td.path().join("bar").metadata());
t!(td.path().join("baz").metadata());
}

#[test]
Expand Down

0 comments on commit 5125587

Please sign in to comment.