diff --git a/src/bin/pg_basebackup/bbstreamer_file.c b/src/bin/pg_basebackup/bbstreamer_file.c index 2da7d33b489..0ffe5f5389a 100644 --- a/src/bin/pg_basebackup/bbstreamer_file.c +++ b/src/bin/pg_basebackup/bbstreamer_file.c @@ -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 diff --git a/src/bin/pg_basebackup/bbstreamer_tar.c b/src/bin/pg_basebackup/bbstreamer_tar.c index d751ae91ea7..18d934f4a4c 100644 --- a/src/bin/pg_basebackup/bbstreamer_tar.c +++ b/src/bin/pg_basebackup/bbstreamer_tar.c @@ -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); diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c index 745435060cc..25f2fc15b32 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 3e277d7e308..d869bff2bce 100644 --- a/src/include/port.h +++ b/src/include/port.h @@ -58,6 +58,7 @@ extern void make_native_path(char *filename); 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 817f6b08348..58732b13418 100644 --- a/src/port/path.c +++ b/src/port/path.c @@ -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). *