src/pxar/sequentail_decoder.rs: fix issue when restoring with glob pattern.

Partial extraction of an archive with a glob pattern, e.g. '**/*.conf' lead to
the unexpected behaviour of restoring all partially matched directories (in this
example all of them).

This patch fixes this unexpected behaviour by only restoring those directories
were the directory or one of its sub-items fully matched the pattern and should
therefore be restored.

To achive this behavoiur, directory metadata is pushed onto a stack and restored
on demand.

Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
This commit is contained in:
Christian Ebner 2019-08-01 16:23:48 +02:00 committed by Dietmar Maurer
parent 51ac99c314
commit beffac999f
1 changed files with 92 additions and 48 deletions

View File

@ -7,6 +7,7 @@ use endian_trait::Endian;
use super::format_definition::*; use super::format_definition::*;
use super::exclude_pattern::*; use super::exclude_pattern::*;
use super::dir_buffer::*;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -612,38 +613,27 @@ impl <'a, R: Read, F: Fn(&Path) -> Result<(), Error>> SequentialDecoder<'a, R, F
Ok(()) Ok(())
} }
fn restore_dir_sequential( fn restore_dir(
&mut self, &mut self,
base_path: &Path, base_path: &Path,
relative_path: &mut PathBuf, dirs: &mut PxarDirBuf,
full_path: &PathBuf, entry: CaFormatEntry,
parent_fd: Option<RawFd>,
entry: &CaFormatEntry,
filename: &OsStr, filename: &OsStr,
matched: MatchType,
match_pattern: &Vec<PxarExcludePattern>, match_pattern: &Vec<PxarExcludePattern>,
) -> Result<(), Error> { ) -> Result<(), Error> {
// By passing back Some(dir) we assure the fd stays open and in scope
let (fd, _dir) = if let Some(pfd) = parent_fd {
let dir = if filename.is_empty() {
nix::dir::Dir::openat(pfd, ".", OFlag::O_DIRECTORY, Mode::empty())?
} else {
dir_mkdirat(pfd, filename, !self.allow_existing_dirs)
.map_err(|err| format_err!("unable to open directory {:?} - {}", full_path, err))?
};
(Some(dir.as_raw_fd()), Some(dir))
} else {
(None, None)
};
let (mut head, attr) = self.read_attributes() let (mut head, attr) = self.read_attributes()
.map_err(|err| format_err!("Reading of directory attributes failed - {}", err))?; .map_err(|err| format_err!("Reading of directory attributes failed - {}", err))?;
let dir = PxarDir::new(filename, entry, attr);
dirs.push(dir);
if matched == MatchType::Include {
dirs.create_all_dirs(!self.allow_existing_dirs)?;
}
while head.htype == CA_FORMAT_FILENAME { while head.htype == CA_FORMAT_FILENAME {
let name = self.read_filename(head.size)?; let name = self.read_filename(head.size)?;
relative_path.push(&name); self.restore_dir_entry(base_path, dirs, &name, matched, match_pattern)?;
self.restore_sequential(base_path, relative_path, &name, fd, match_pattern)?;
relative_path.pop();
head = self.read_item()?; head = self.read_item()?;
} }
@ -651,17 +641,18 @@ impl <'a, R: Read, F: Fn(&Path) -> Result<(), Error>> SequentialDecoder<'a, R, F
bail!("got unknown header type inside directory entry {:016x}", head.htype); bail!("got unknown header type inside directory entry {:016x}", head.htype);
} }
//println!("Skip Goodbye");
if head.size < HEADER_SIZE { bail!("detected short goodbye table"); } if head.size < HEADER_SIZE { bail!("detected short goodbye table"); }
self.skip_bytes((head.size - HEADER_SIZE) as usize)?; self.skip_bytes((head.size - HEADER_SIZE) as usize)?;
// Only restore if we want to restore this part of the archive let last = dirs.pop()
if let Some(fd) = fd { .ok_or_else(|| format_err!("Tried to pop beyond dir root - this should not happen!"))?;
self.restore_ugid(&entry, fd)?; if let Some(d) = last.dir {
let fd = d.as_raw_fd();
self.restore_ugid(&last.entry, fd)?;
// fcaps have to be restored after restore_ugid as chown clears security.capability xattr, see CVE-2015-1350 // fcaps have to be restored after restore_ugid as chown clears security.capability xattr, see CVE-2015-1350
self.restore_attributes(fd, &attr, &entry)?; self.restore_attributes(fd, &last.attr, &last.entry)?;
self.restore_mode(&entry, fd)?; self.restore_mode(&last.entry, fd)?;
self.restore_mtime(&entry, fd)?; self.restore_mtime(&last.entry, fd)?;
} }
Ok(()) Ok(())
@ -670,33 +661,70 @@ impl <'a, R: Read, F: Fn(&Path) -> Result<(), Error>> SequentialDecoder<'a, R, F
/// Restore an archive into the specified directory. /// Restore an archive into the specified directory.
/// ///
/// The directory is created if it does not exist. /// The directory is created if it does not exist.
pub fn restore(&mut self, path: &Path, match_pattern: &Vec<PxarExcludePattern>) -> Result<(), Error> { pub fn restore(
&mut self,
path: &Path,
match_pattern: &Vec<PxarExcludePattern>
) -> Result<(), Error> {
let _ = std::fs::create_dir(path); let _ = std::fs::create_dir(path);
let dir = nix::dir::Dir::open(path, nix::fcntl::OFlag::O_DIRECTORY, nix::sys::stat::Mode::empty()) let dir = nix::dir::Dir::open(path, nix::fcntl::OFlag::O_DIRECTORY, nix::sys::stat::Mode::empty())
.map_err(|err| format_err!("unable to open target directory {:?} - {}", path, err))?; .map_err(|err| format_err!("unable to open target directory {:?} - {}", path, err))?;
let fd = Some(dir.as_raw_fd()); let fd = dir.as_raw_fd();
let mut dirs = PxarDirBuf::new(fd);
// An empty match pattern list indicates to restore the full archive.
let matched = if match_pattern.len() == 0 {
MatchType::Include
} else {
MatchType::None
};
let mut relative_path = PathBuf::new(); let header: CaFormatHeader = self.read_item()?;
self.restore_sequential(path, &mut relative_path, &OsString::new(), fd, match_pattern) check_ca_header::<CaFormatEntry>(&header, CA_FORMAT_ENTRY)?;
let entry: CaFormatEntry = self.read_item()?;
let (mut head, attr) = self.read_attributes()
.map_err(|err| format_err!("Reading of directory attributes failed - {}", err))?;
while head.htype == CA_FORMAT_FILENAME {
let name = self.read_filename(head.size)?;
self.restore_dir_entry(path, &mut dirs, &name, matched, match_pattern)?;
head = self.read_item()?;
}
if head.htype != CA_FORMAT_GOODBYE {
bail!("got unknown header type inside directory entry {:016x}", head.htype);
}
if head.size < HEADER_SIZE { bail!("detected short goodbye table"); }
self.skip_bytes((head.size - HEADER_SIZE) as usize)?;
self.restore_ugid(&entry, fd)?;
// fcaps have to be restored after restore_ugid as chown clears security.capability xattr, see CVE-2015-1350
self.restore_attributes(fd, &attr, &entry)?;
self.restore_mode(&entry, fd)?;
self.restore_mtime(&entry, fd)?;
Ok(())
} }
fn restore_sequential( fn restore_dir_entry(
&mut self, &mut self,
base_path: &Path, base_path: &Path,
relative_path: &mut PathBuf, dirs: &mut PxarDirBuf,
filename: &OsStr, // repeats path last relative_path component filename: &OsStr,
parent_fd: Option<RawFd>, parent_matched: MatchType,
match_pattern: &Vec<PxarExcludePattern>, match_pattern: &Vec<PxarExcludePattern>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let full_path = base_path.join(&relative_path); let relative_path = dirs.as_path_buf();
let full_path = base_path.join(&relative_path).join(filename);
let head: CaFormatHeader = self.read_item()?; let head: CaFormatHeader = self.read_item()?;
if head.htype == PXAR_FORMAT_HARDLINK { if head.htype == PXAR_FORMAT_HARDLINK {
let (target, _offset) = self.read_hardlink(head.size)?; let (target, _offset) = self.read_hardlink(head.size)?;
let target_path = base_path.join(&target); let target_path = base_path.join(&target);
if let Some(_) = parent_fd { if dirs.last_dir_fd().is_some() {
(self.callback)(&full_path)?; (self.callback)(&full_path)?;
hardlink(&target_path, &full_path)?; hardlink(&target_path, &full_path)?;
} }
@ -706,26 +734,42 @@ impl <'a, R: Read, F: Fn(&Path) -> Result<(), Error>> SequentialDecoder<'a, R, F
check_ca_header::<CaFormatEntry>(&head, CA_FORMAT_ENTRY)?; check_ca_header::<CaFormatEntry>(&head, CA_FORMAT_ENTRY)?;
let entry: CaFormatEntry = self.read_item()?; let entry: CaFormatEntry = self.read_item()?;
let mut fd = parent_fd;
let mut child_pattern = Vec::new(); let mut child_pattern = Vec::new();
// If parent was a match, then children should be assumed to match too
// This is especially the case when the full archive is restored and
// there are no match pattern.
let mut matched = parent_matched;
if match_pattern.len() > 0 { if match_pattern.len() > 0 {
if filename.is_empty() { match match_filename(filename, entry.mode as u32 & libc::S_IFMT == libc::S_IFDIR, match_pattern) {
child_pattern = match_pattern.clone(); (MatchType::Include, pattern) => {
} else { matched = MatchType::Include;
match match_filename(filename, entry.mode as u32 & libc::S_IFMT == libc::S_IFDIR, match_pattern) { child_pattern = pattern;
(MatchType::None, _) => fd = None, },
(MatchType::Exclude, _) => fd = None, (MatchType::None, _) => matched = MatchType::None,
(_, pattern) => child_pattern = pattern, (MatchType::Exclude, _) => matched = MatchType::Exclude,
} (MatchType::PartialExclude, pattern) => {
matched = MatchType::PartialExclude;
child_pattern = pattern;
},
(MatchType::PartialInclude, pattern) => {
matched = MatchType::PartialInclude;
child_pattern = pattern;
},
} }
} }
let fd = if matched == MatchType::Include {
Some(dirs.create_all_dirs(!self.allow_existing_dirs)?)
} else {
None
};
if fd.is_some() { if fd.is_some() {
(self.callback)(&full_path)?; (self.callback)(&full_path)?;
} }
match entry.mode as u32 & libc::S_IFMT { match entry.mode as u32 & libc::S_IFMT {
libc::S_IFDIR => self.restore_dir_sequential(base_path, relative_path, &full_path, fd, &entry, &filename, &child_pattern), libc::S_IFDIR => self.restore_dir(base_path, dirs, entry, &filename, matched, &child_pattern),
libc::S_IFLNK => self.restore_symlink(fd, &full_path, &entry, &filename), libc::S_IFLNK => self.restore_symlink(fd, &full_path, &entry, &filename),
libc::S_IFSOCK => self.restore_socket(fd, &entry, &filename), libc::S_IFSOCK => self.restore_socket(fd, &entry, &filename),
libc::S_IFIFO => self.restore_fifo(fd, &entry, &filename), libc::S_IFIFO => self.restore_fifo(fd, &entry, &filename),