mirror of
https://github.com/postgres/postgres.git
synced 2026-05-21 09:48:06 -04:00
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
This commit is contained in:
parent
137013f608
commit
0c83fe8e4c
5 changed files with 56 additions and 0 deletions
|
|
@ -215,6 +215,10 @@ bbstreamer_extractor_content(bbstreamer *streamer, bbstreamer_member *member,
|
|||
case BBSTREAMER_MEMBER_HEADER:
|
||||
Assert(mystreamer->file == NULL);
|
||||
|
||||
if (!path_is_safe_for_extraction(member->pathname))
|
||||
pg_fatal("tar member has unsafe path name: \"%s\"",
|
||||
member->pathname);
|
||||
|
||||
/* Prepend basepath. */
|
||||
snprintf(mystreamer->filename, sizeof(mystreamer->filename),
|
||||
"%s/%s", mystreamer->basepath, member->pathname);
|
||||
|
|
@ -233,6 +237,14 @@ bbstreamer_extractor_content(bbstreamer *streamer, bbstreamer_member *member,
|
|||
|
||||
if (mystreamer->link_map)
|
||||
linktarget = mystreamer->link_map(linktarget);
|
||||
|
||||
if (!is_absolute_path(linktarget) &&
|
||||
!path_is_safe_for_extraction(member->linktarget))
|
||||
{
|
||||
pg_fatal("link target has unsafe path name: \"%s\"",
|
||||
member->linktarget);
|
||||
}
|
||||
|
||||
extract_link(mystreamer->filename, linktarget);
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -295,6 +295,9 @@ bbstreamer_tar_header(bbstreamer_tar_parser *mystreamer)
|
|||
strlcpy(member->pathname, &buffer[0], MAXPGPATH);
|
||||
if (member->pathname[0] == '\0')
|
||||
pg_fatal("tar member has empty name");
|
||||
if (!path_is_safe_for_extraction(member->pathname))
|
||||
pg_fatal("tar member has unsafe path name: \"%s\"",
|
||||
member->pathname);
|
||||
member->size = read_tar_number(&buffer[124], 12);
|
||||
member->mode = read_tar_number(&buffer[100], 8);
|
||||
member->uid = read_tar_number(&buffer[108], 8);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -58,6 +58,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);
|
||||
|
|
|
|||
|
|
@ -626,6 +626,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).
|
||||
*
|
||||
|
|
|
|||
Loading…
Reference in a new issue