This is an automated email from the ASF dual-hosted git repository.

reshke pushed a commit to branch REL_2_STABLE
in repository https://gitbox.apache.org/repos/asf/cloudberry.git

commit c2a1b74db3fabdc1b7d16216f5deb55cefd18bae
Author: Michael Paquier <[email protected]>
AuthorDate: Mon May 11 05:13:51 2026 -0700

    Prevent path traversal in pg_basebackup and pg_rewind
    
    pg_rewind and pg_basebackup could be fed paths from rogue endpoints that
    could overwrite the contents of the client when received, achieving path
    traversal.
    
    There were two areas in the tree that were sensitive to this problem:
    - pg_basebackup, through the astreamer code, where no validation was
    performed before building an output path when streaming tar data.  This
    is an issue in v15 and newer versions.
    - pg_rewind file operations for paths received through libpq, for all
    the stable branches supported.
    
    In order to address this problem, this commit adds a helper function in
    path.c, that reuses path_is_relative_and_below_cwd() after applying
    canonicalize_path().  This can be used to validate the paths received
    from a connection point.  A path is considered invalid if any of the two
    following conditions is satisfied:
    - The path is absolute.
    - The path includes a direct parent-directory reference.
    
    Reported-by: XlabAI Team of Tencent Xuanwu Lab
    Reported-by: Valery Gubanov <[email protected]>
    Author: Michael Paquier <[email protected]>
    Reviewed-by: Amit Kapila <[email protected]>
    Backpatch-through: 14
    Security: CVE-2026-6475
---
 src/bin/pg_rewind/file_ops.c | 23 +++++++++++++++++++++++
 src/include/port.h           |  1 +
 src/port/path.c              | 17 +++++++++++++++++
 3 files changed, 41 insertions(+)

diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 40f22f4b958..f23ec8e1b4c 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -50,6 +50,9 @@ open_target_file(const char *path, bool trunc)
 {
        int                     mode;
 
+       if (!path_is_safe_for_extraction(path))
+               pg_fatal("target file path is unsafe for open: \"%s\"", path);
+
        if (dry_run)
                return;
 
@@ -202,6 +205,9 @@ remove_target_file(const char *path, bool missing_ok)
 {
        char            dstpath[MAXPGPATH];
 
+       if (!path_is_safe_for_extraction(path))
+               pg_fatal("target file path is unsafe for removal: \"%s\"", 
path);
+
        if (dry_run)
                return;
 
@@ -222,6 +228,9 @@ truncate_target_file(const char *path, off_t newsize)
        char            dstpath[MAXPGPATH];
        int                     fd;
 
+       if (!path_is_safe_for_extraction(path))
+               pg_fatal("target file path is unsafe for truncation: \"%s\"", 
path);
+
        if (dry_run)
                return;
 
@@ -244,6 +253,10 @@ create_target_dir(const char *path)
 {
        char            dstpath[MAXPGPATH];
 
+       if (!path_is_safe_for_extraction(path))
+               pg_fatal("target directory path is unsafe for directory 
creation: \"%s\"",
+                                path);
+
        if (dry_run)
                return;
 
@@ -258,6 +271,10 @@ remove_target_dir(const char *path)
 {
        char            dstpath[MAXPGPATH];
 
+       if (!path_is_safe_for_extraction(path))
+               pg_fatal("target directory path is unsafe for directory 
removal: \"%s\"",
+                                path);
+
        if (dry_run)
                return;
 
@@ -272,6 +289,9 @@ create_target_symlink(const char *path, const char *link)
 {
        char            dstpath[MAXPGPATH];
 
+       if (!path_is_safe_for_extraction(path))
+               pg_fatal("target symlink path is unsafe for creation: \"%s\"", 
path);
+
        if (dry_run)
                return;
 
@@ -286,6 +306,9 @@ remove_target_symlink(const char *path)
 {
        char            dstpath[MAXPGPATH];
 
+       if (!path_is_safe_for_extraction(path))
+               pg_fatal("target symlink path is unsafe for removal: \"%s\"", 
path);
+
        if (dry_run)
                return;
 
diff --git a/src/include/port.h b/src/include/port.h
index 4179df36ebc..837e403c9d4 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -54,6 +54,7 @@ extern void make_native_path(char *path);
 extern void cleanup_path(char *path);
 extern bool path_contains_parent_reference(const char *path);
 extern bool path_is_relative_and_below_cwd(const char *path);
+extern bool path_is_safe_for_extraction(const char *path);
 extern bool path_is_prefix_of_path(const char *path1, const char *path2);
 extern char *make_absolute_path(const char *path);
 extern const char *get_progname(const char *argv0);
diff --git a/src/port/path.c b/src/port/path.c
index c39d4688cd9..970087eafa2 100644
--- a/src/port/path.c
+++ b/src/port/path.c
@@ -428,6 +428,23 @@ path_is_relative_and_below_cwd(const char *path)
                return true;
 }
 
+/*
+ * Detect whether a path is safe for use during archive extraction.
+ *
+ * This applies canonicalize_path(), then it checks that the path does
+ * not contain any parent directory references.
+ */
+bool
+path_is_safe_for_extraction(const char *path)
+{
+       char            buf[MAXPGPATH];
+
+       strlcpy(buf, path, sizeof(buf));
+       canonicalize_path(buf);
+
+       return path_is_relative_and_below_cwd(buf);
+}
+
 /*
  * Detect whether path1 is a prefix of path2 (including equality).
  *


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to