From b426bd48ee3f5a6890038421f81df7ed919d73dc Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Mon, 31 Jan 2022 13:53:38 -0500 Subject: [PATCH] Simplify coding around path_contains_parent_reference(). Given the existing stipulation that path_contains_parent_reference() must only be invoked on canonicalized paths, we can simplify things in the wake of commit c10f830c5. It is now only possible to see ".." at the start of a relative path. That means we can simplify path_contains_parent_reference() itself quite a bit, and it makes the two existing outside call sites dead code, since they'd already checked that the path is absolute. We could now fold path_contains_parent_reference() into its only remaining caller path_is_relative_and_below_cwd(). But it seems better to leave it as a separately callable function, in case any extensions are using it. Also document the pre-existing requirement for path_is_relative_and_below_cwd's input to be likewise canonicalized. Shenhao Wang and Tom Lane Discussion: https://postgr.es/m/OSBPR01MB4214FA221FFE046F11F2AD74F2D49@OSBPR01MB4214.jpnprd01.prod.outlook.com --- contrib/adminpack/adminpack.c | 6 ------ src/backend/utils/adt/genfile.c | 6 ------ src/port/path.c | 31 +++++++++++++------------------ 3 files changed, 13 insertions(+), 30 deletions(-) diff --git a/contrib/adminpack/adminpack.c b/contrib/adminpack/adminpack.c index 45e5ae31f6..d7d84d096f 100644 --- a/contrib/adminpack/adminpack.c +++ b/contrib/adminpack/adminpack.c @@ -88,12 +88,6 @@ convert_and_check_filename(text *arg) */ if (is_absolute_path(filename)) { - /* Disallow '/a/b/data/..' */ - if (path_contains_parent_reference(filename)) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("reference to parent directory (\"..\") not allowed"))); - /* Allow absolute paths if within DataDir */ if (!path_is_prefix_of_path(DataDir, filename)) ereport(ERROR, diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c index 542bbacaa2..fe6863d8b4 100644 --- a/src/backend/utils/adt/genfile.c +++ b/src/backend/utils/adt/genfile.c @@ -72,12 +72,6 @@ convert_and_check_filename(text *arg) */ if (is_absolute_path(filename)) { - /* Disallow '/a/b/data/..' */ - if (path_contains_parent_reference(filename)) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("reference to parent directory (\"..\") not allowed"))); - /* * Allow absolute paths if within DataDir or Log_directory, even * though Log_directory might be outside DataDir. diff --git a/src/port/path.c b/src/port/path.c index ea1d8f6ff5..05fe812f75 100644 --- a/src/port/path.c +++ b/src/port/path.c @@ -494,27 +494,21 @@ canonicalize_path(char *path) * Detect whether a path contains any parent-directory references ("..") * * The input *must* have been put through canonicalize_path previously. - * - * This is a bit tricky because we mustn't be fooled by "..a.." (legal) - * nor "C:.." (legal on Unix but not Windows). */ bool path_contains_parent_reference(const char *path) { - int path_len; - + /* + * Once canonicalized, an absolute path cannot contain any ".." at all, + * while a relative path could contain ".."(s) only at the start. So it + * is sufficient to check the start of the path, after skipping any + * Windows drive/network specifier. + */ path = skip_drive(path); /* C: shouldn't affect our conclusion */ - path_len = strlen(path); - - /* - * ".." could be the whole path; otherwise, if it's present it must be at - * the beginning, in the middle, or at the end. - */ - if (strcmp(path, "..") == 0 || - strncmp(path, "../", 3) == 0 || - strstr(path, "/../") != NULL || - (path_len >= 3 && strcmp(path + path_len - 3, "/..") == 0)) + if (path[0] == '.' && + path[1] == '.' && + (path[2] == '\0' || path[2] == '/')) return true; return false; @@ -522,10 +516,11 @@ path_contains_parent_reference(const char *path) /* * Detect whether a path is only in or below the current working directory. + * + * The input *must* have been put through canonicalize_path previously. + * * An absolute path that matches the current working directory should - * return false (we only want relative to the cwd). We don't allow - * "/../" even if that would keep us under the cwd (it is too hard to - * track that). + * return false (we only want relative to the cwd). */ bool path_is_relative_and_below_cwd(const char *path)