156 lines
4.7 KiB
Diff
156 lines
4.7 KiB
Diff
From 498829dca45ad207a23954fe2e5a30ed2ef4b363 Mon Sep 17 00:00:00 2001
|
|
From: Michael Paquier <michael@paquier.xyz>
|
|
Date: Mon, 11 May 2026 05:13:51 -0700
|
|
Subject: [PATCH] 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 <valerygubanov95@gmail.com>
|
|
Author: Michael Paquier <michael@paquier.xyz>
|
|
Reviewed-by: Amit Kapila <amit.kapila16@gmail.com>
|
|
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 a807086..faaa5ce 100644
|
|
--- a/src/bin/pg_rewind/file_ops.c
|
|
+++ b/src/bin/pg_rewind/file_ops.c
|
|
@@ -48,6 +48,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;
|
|
|
|
@@ -188,6 +191,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;
|
|
|
|
@@ -208,6 +214,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;
|
|
|
|
@@ -230,6 +239,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;
|
|
|
|
@@ -244,6 +257,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;
|
|
|
|
@@ -258,6 +275,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;
|
|
|
|
@@ -272,6 +292,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 db53dec..a257d8d 100644
|
|
--- a/src/include/port.h
|
|
+++ b/src/include/port.h
|
|
@@ -55,6 +55,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 d37a484..117f919 100644
|
|
--- a/src/port/path.c
|
|
+++ b/src/port/path.c
|
|
@@ -505,6 +505,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).
|
|
*
|
|
--
|
|
2.39.5 (Apple Git-154)
|
|
|