public inbox for pbs-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [PATCH proxmox-backup v2] fix #5247: relative paths in exclude patterns.
@ 2026-03-30  8:11 Manuel Federanko
  2026-03-31 14:04 ` Wolfgang Bumiller
  0 siblings, 1 reply; 3+ messages in thread
From: Manuel Federanko @ 2026-03-30  8:11 UTC (permalink / raw)
  To: pbs-devel

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




^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH proxmox-backup v2] fix #5247: relative paths in exclude patterns.
  2026-03-30  8:11 [PATCH proxmox-backup v2] fix #5247: relative paths in exclude patterns Manuel Federanko
@ 2026-03-31 14:04 ` Wolfgang Bumiller
  2026-03-31 14:48   ` Manuel Federanko
  0 siblings, 1 reply; 3+ messages in thread
From: Wolfgang Bumiller @ 2026-03-31 14:04 UTC (permalink / raw)
  To: Manuel Federanko; +Cc: pbs-devel

On Mon, 30 Mar 2026 10:11:10 +0200, Manuel Federanko <m.federanko@proxmox.com> wrote:
> 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.

In the `.pxarexclude` case, logging may make sense as well IMO.

As for `--exclude`, I wonder we should just *bail* instead?

Although that might "break" some automated
otherwise-working-but-probably-too-big backups, so we can't... (although
for `foo/../bar` it's *really* tempting...)

>
>
> diff --git a/pbs-client/src/pxar/tools.rs b/pbs-client/src/pxar/tools.rs
> index 475c08ad3..7ad4009fe 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 {

(Note: the monomorphization from v1 was actually fine, but if the `_do`
variant is only used by this one function it could live *inside* here.)

> +    // 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());

Could just use

    .as_encoded_bytes()
    .ends_with(MAIN_SEPARATOR_STR.as_bytes())

-- 





^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH proxmox-backup v2] fix #5247: relative paths in exclude patterns.
  2026-03-31 14:04 ` Wolfgang Bumiller
@ 2026-03-31 14:48   ` Manuel Federanko
  0 siblings, 0 replies; 3+ messages in thread
From: Manuel Federanko @ 2026-03-31 14:48 UTC (permalink / raw)
  To: Wolfgang Bumiller; +Cc: pbs-devel

On 2026-03-31 4:04 PM, Wolfgang Bumiller wrote:
> On Mon, 30 Mar 2026 10:11:10 +0200, Manuel Federanko <m.federanko@proxmox.com> wrote:
>> 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.
>
> In the `.pxarexclude` case, logging may make sense as well IMO.

ack

> As for `--exclude`, I wonder we should just *bail* instead?
> 
> Although that might "break" some automated
> otherwise-working-but-probably-too-big backups, so we can't... (although
> for `foo/../bar` it's *really* tempting...)

I'm a bit undecided, maybe log a warning with a deprecation notice that
this will lead to an error in the future? Same goes for the cli option
now that I'm thinking about it.

>>
>>
>> diff --git a/pbs-client/src/pxar/tools.rs b/pbs-client/src/pxar/tools.rs
>> index 475c08ad3..7ad4009fe 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 {
> 
> (Note: the monomorphization from v1 was actually fine, but if the `_do`
> variant is only used by this one function it could live *inside* here.)
> 
>> +    // 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());
> 
> Could just use
> 
>     .as_encoded_bytes()
>     .ends_with(MAIN_SEPARATOR_STR.as_bytes())

ack, will change in v3 once warning/bailing has been decided




^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-03-31 14:47 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-03-30  8:11 [PATCH proxmox-backup v2] fix #5247: relative paths in exclude patterns Manuel Federanko
2026-03-31 14:04 ` Wolfgang Bumiller
2026-03-31 14:48   ` Manuel Federanko

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal