From: Manuel Federanko <m.federanko@proxmox.com>
To: pbs-devel@lists.proxmox.com
Subject: [PATCH proxmox-backup v2] fix #5247: relative paths in exclude patterns.
Date: Mon, 30 Mar 2026 10:11:10 +0200 [thread overview]
Message-ID: <20260330081110.8527-1-m.federanko@proxmox.com> (raw)
Patterns which start with ./ or for that matter contain /../ or similar
constructs will never match, since they get compared to sanitized paths
from directory traversal, sanitize those patterns too. Implement this
for both .pxarexclude files and the --exclude command line option.
Log a warning if a path was sanitized that was provided via --exclude.
Signed-off-by: Manuel Federanko <m.federanko@proxmox.com>
Fixes: https://bugzilla.proxmox.com/show_bug.cgi?id=5247
---
changes since v1:
- inline normalize_lexically helper
- bump log level from info to warn
pbs-client/src/pxar/create.rs | 7 ++++++-
pbs-client/src/pxar/tools.rs | 31 +++++++++++++++++++++++++++++++
proxmox-backup-client/src/main.rs | 16 ++++++++++++++--
3 files changed, 51 insertions(+), 3 deletions(-)
diff --git a/pbs-client/src/pxar/create.rs b/pbs-client/src/pxar/create.rs
index e42bcc87..1c878cb7 100644
--- a/pbs-client/src/pxar/create.rs
+++ b/pbs-client/src/pxar/create.rs
@@ -560,7 +560,12 @@ impl Archiver {
(line, MatchType::Exclude, false)
};
- match MatchEntry::parse_pattern(line, PatternFlag::PATH_NAME, mode) {
+ let line_normalized = crate::pxar::tools::normalize_lexically(OsStr::from_bytes(line));
+ match MatchEntry::parse_pattern(
+ line_normalized.as_path().as_os_str().as_bytes(),
+ PatternFlag::PATH_NAME,
+ mode,
+ ) {
Ok(pattern) => {
if anchored {
self.patterns.push(pattern.add_flags(MatchFlag::ANCHORED));
diff --git a/pbs-client/src/pxar/tools.rs b/pbs-client/src/pxar/tools.rs
index 475c08ad..7ad4009f 100644
--- a/pbs-client/src/pxar/tools.rs
+++ b/pbs-client/src/pxar/tools.rs
@@ -76,6 +76,37 @@ fn assert_single_path_component_do(path: &Path) -> Result<(), Error> {
Ok(())
}
+pub fn normalize_lexically<S: AsRef<OsStr> + ?Sized>(path: &S) -> PathBuf {
+ // FIXME: Once std::path::normalize_lexically is stabilized we can
+ // switch to that
+ use std::path::Component;
+
+ let path = Path::new(path);
+ let has_trailing_slash = path
+ .as_os_str()
+ .as_encoded_bytes()
+ .last()
+ .copied()
+ .is_some_and(|c: u8| c == std::path::MAIN_SEPARATOR_STR.bytes().last().unwrap());
+ let mut new = PathBuf::new();
+ let iter = path.components();
+ for component in iter {
+ match component {
+ Component::RootDir => new.push(Component::RootDir),
+ Component::Prefix(p) => new.push(Component::Prefix(p)),
+ Component::CurDir => continue,
+ Component::ParentDir => {
+ new.pop();
+ }
+ Component::Normal(n) => new.push(n),
+ };
+ }
+ if has_trailing_slash {
+ new.push("");
+ }
+ new
+}
+
#[rustfmt::skip]
fn symbolic_mode(c: u64, special: bool, special_x: u8, special_no_x: u8) -> [u8; 3] {
[
diff --git a/proxmox-backup-client/src/main.rs b/proxmox-backup-client/src/main.rs
index 2ada87bd..303b6ecd 100644
--- a/proxmox-backup-client/src/main.rs
+++ b/proxmox-backup-client/src/main.rs
@@ -826,9 +826,21 @@ async fn create_backup(
let entry = entry
.as_str()
.ok_or_else(|| format_err!("Invalid pattern string slice"))?;
+ let entry_normalized = pbs_client::pxar::tools::normalize_lexically(entry);
+ if entry_normalized.as_os_str() != entry {
+ log::warn!(
+ "Sanitized exclude pattern. Exclude patterns are relative to backup root, \
+ not the current working directory and should not contain '.' or '..' as path \
+ segments"
+ );
+ }
pattern_list.push(
- MatchEntry::parse_pattern(entry, PatternFlag::PATH_NAME, MatchType::Exclude)
- .map_err(|err| format_err!("invalid exclude pattern entry: {}", err))?,
+ MatchEntry::parse_pattern(
+ entry_normalized.as_os_str().as_encoded_bytes(),
+ PatternFlag::PATH_NAME,
+ MatchType::Exclude,
+ )
+ .map_err(|err| format_err!("invalid exclude pattern entry: {}", err))?,
);
}
--
2.47.3
next reply other threads:[~2026-03-30 8:11 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-30 8:11 Manuel Federanko [this message]
2026-03-31 14:04 ` Wolfgang Bumiller
2026-03-31 14:48 ` Manuel Federanko
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260330081110.8527-1-m.federanko@proxmox.com \
--to=m.federanko@proxmox.com \
--cc=pbs-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox