Thread: Bug in canonicalize_path()

Bug in canonicalize_path()

From
Bruce Momjian
Date:
I found that in port/path.c::canonicalize_path, that if the path was
supplied as "/usr/local/bin/../.." we would return /usr/local/bin.  The
problem is then when we saw a trailing ".." we stripped it off and the
previous directory, but we never checked if the previous directory was
itself "..".

Patch applied to suppress trimming of ".." if ".." is above it.  I tried
coding something that would handle "../.." but is started to look too
messy and not worth the effort.

I don't see a need to backpatch this, but it could produce errors with
weird supplied paths.  Comments?

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073
? pg_config_paths.h
Index: path.c
===================================================================
RCS file: /cvsroot/pgsql/src/port/path.c,v
retrieving revision 1.51
diff -c -r1.51 path.c
*** path.c    26 Jan 2005 19:24:03 -0000    1.51
--- path.c    11 Aug 2005 03:52:06 -0000
***************
*** 284,290 ****

          if (len > 2 && strcmp(path + len - 2, "/.") == 0)
              trim_directory(path);
!         else if (len > 3 && strcmp(path + len - 3, "/..") == 0)
          {
              trim_directory(path);
              trim_directory(path);    /* remove directory above */
--- 284,293 ----

          if (len > 2 && strcmp(path + len - 2, "/.") == 0)
              trim_directory(path);
!         /* We can only deal with "/usr/local/..", not "/usr/local/../.." */
!         else if (len > 3 && strcmp(path + len - 3, "/..") == 0 &&
!                  (len != 5 || strcmp(path, "../..") != 0) &&
!                  (len < 6 || strcmp(path + len - 6, "/../..") != 0))
          {
              trim_directory(path);
              trim_directory(path);    /* remove directory above */

Re: Bug in canonicalize_path()

From
"William ZHANG"
Date:
What if we let the trailing "." or ".." as it is?

On Windows, GetFullPathName() can canonicalize a given path.

$ uname -a
MINGW32_NT-5.0 DEV 1.0.10(0.46/3/2) 2003-10-11 10:14 i686 unknown

$ cat path.c

#include <windows.h>

int
main(void)
{
        CHAR Buf[1024];

        GetFullPathName("C:\\Temp", 1024, Buf, NULL);
        printf("%s\n", Buf);
        GetFullPathName("C:\\Temp\\", 1024, Buf, NULL);
        printf("%s\n", Buf);
        GetFullPathName("C:\\Temp\\..\\", 1024, Buf, NULL);
        printf("%s\n", Buf);
        GetFullPathName("C:\\Temp\\..\\..", 1024, Buf, NULL);
        printf("%s\n", Buf);

        return 0;
}

$ gcc path.c

$ ./a.exe
C:\Temp
C:\Temp\    # trailing "\" is not removed.
C:\               #
C:\               #  C:\Temp\..\..

"Bruce Momjian" <pgman@candle.pha.pa.us> wrote
news:200508110356.j7B3uqR14136@candle.pha.pa.us...
> I found that in port/path.c::canonicalize_path, that if the path was
> supplied as "/usr/local/bin/../.." we would return /usr/local/bin.  The
> problem is then when we saw a trailing ".." we stripped it off and the
> previous directory, but we never checked if the previous directory was
> itself "..".
>
> Patch applied to suppress trimming of ".." if ".." is above it.  I tried
> coding something that would handle "../.." but is started to look too
> messy and not worth the effort.
>
> I don't see a need to backpatch this, but it could produce errors with
> weird supplied paths.  Comments?
>
> --
>   Bruce Momjian                        |  http://candle.pha.pa.us
>   pgman@candle.pha.pa.us               |  (610) 359-1001
>   +  If your life is a hard drive,     |  13 Roberts Road
>   +  Christ can be your backup.        |  Newtown Square, Pennsylvania
19073
>


----------------------------------------------------------------------------
----


> ? pg_config_paths.h
> Index: path.c
> ===================================================================
> RCS file: /cvsroot/pgsql/src/port/path.c,v
> retrieving revision 1.51
> diff -c -r1.51 path.c
> *** path.c 26 Jan 2005 19:24:03 -0000 1.51
> --- path.c 11 Aug 2005 03:52:06 -0000
> ***************
> *** 284,290 ****
>
>   if (len > 2 && strcmp(path + len - 2, "/.") == 0)
>   trim_directory(path);
> ! else if (len > 3 && strcmp(path + len - 3, "/..") == 0)
>   {
>   trim_directory(path);
>   trim_directory(path); /* remove directory above */
> --- 284,293 ----
>
>   if (len > 2 && strcmp(path + len - 2, "/.") == 0)
>   trim_directory(path);
> ! /* We can only deal with "/usr/local/..", not "/usr/local/../.." */
> ! else if (len > 3 && strcmp(path + len - 3, "/..") == 0 &&
> ! (len != 5 || strcmp(path, "../..") != 0) &&
> ! (len < 6 || strcmp(path + len - 6, "/../..") != 0))
>   {
>   trim_directory(path);
>   trim_directory(path); /* remove directory above */
>


----------------------------------------------------------------------------
----


>
> ---------------------------(end of broadcast)---------------------------
> TIP 1: if posting/reading through Usenet, please send an appropriate
>        subscribe-nomail command to majordomo@postgresql.org so that your
>        message can get through to the mailing list cleanly
>



Re: Bug in canonicalize_path()

From
Bruce Momjian
Date:
Yep, it is a bug on 8.0.X.  Are you suggesting a backpatch?  We can do
it.  Any objections?

---------------------------------------------------------------------------

William ZHANG wrote:
>
> What if we let the trailing "." or ".." as it is?
>
> On Windows, GetFullPathName() can canonicalize a given path.
>
> $ uname -a
> MINGW32_NT-5.0 DEV 1.0.10(0.46/3/2) 2003-10-11 10:14 i686 unknown
>
> $ cat path.c
>
> #include <windows.h>
>
> int
> main(void)
> {
>         CHAR Buf[1024];
>
>         GetFullPathName("C:\\Temp", 1024, Buf, NULL);
>         printf("%s\n", Buf);
>         GetFullPathName("C:\\Temp\\", 1024, Buf, NULL);
>         printf("%s\n", Buf);
>         GetFullPathName("C:\\Temp\\..\\", 1024, Buf, NULL);
>         printf("%s\n", Buf);
>         GetFullPathName("C:\\Temp\\..\\..", 1024, Buf, NULL);
>         printf("%s\n", Buf);
>
>         return 0;
> }
>
> $ gcc path.c
>
> $ ./a.exe
> C:\Temp
> C:\Temp\    # trailing "\" is not removed.
> C:\               #
> C:\               #  C:\Temp\..\..
>
> "Bruce Momjian" <pgman@candle.pha.pa.us> wrote
> news:200508110356.j7B3uqR14136@candle.pha.pa.us...
> > I found that in port/path.c::canonicalize_path, that if the path was
> > supplied as "/usr/local/bin/../.." we would return /usr/local/bin.  The
> > problem is then when we saw a trailing ".." we stripped it off and the
> > previous directory, but we never checked if the previous directory was
> > itself "..".
> >
> > Patch applied to suppress trimming of ".." if ".." is above it.  I tried
> > coding something that would handle "../.." but is started to look too
> > messy and not worth the effort.
> >
> > I don't see a need to backpatch this, but it could produce errors with
> > weird supplied paths.  Comments?
> >
> > --
> >   Bruce Momjian                        |  http://candle.pha.pa.us
> >   pgman@candle.pha.pa.us               |  (610) 359-1001
> >   +  If your life is a hard drive,     |  13 Roberts Road
> >   +  Christ can be your backup.        |  Newtown Square, Pennsylvania
> 19073
> >
>
>
> ----------------------------------------------------------------------------
> ----
>
>
> > ? pg_config_paths.h
> > Index: path.c
> > ===================================================================
> > RCS file: /cvsroot/pgsql/src/port/path.c,v
> > retrieving revision 1.51
> > diff -c -r1.51 path.c
> > *** path.c 26 Jan 2005 19:24:03 -0000 1.51
> > --- path.c 11 Aug 2005 03:52:06 -0000
> > ***************
> > *** 284,290 ****
> >
> >   if (len > 2 && strcmp(path + len - 2, "/.") == 0)
> >   trim_directory(path);
> > ! else if (len > 3 && strcmp(path + len - 3, "/..") == 0)
> >   {
> >   trim_directory(path);
> >   trim_directory(path); /* remove directory above */
> > --- 284,293 ----
> >
> >   if (len > 2 && strcmp(path + len - 2, "/.") == 0)
> >   trim_directory(path);
> > ! /* We can only deal with "/usr/local/..", not "/usr/local/../.." */
> > ! else if (len > 3 && strcmp(path + len - 3, "/..") == 0 &&
> > ! (len != 5 || strcmp(path, "../..") != 0) &&
> > ! (len < 6 || strcmp(path + len - 6, "/../..") != 0))
> >   {
> >   trim_directory(path);
> >   trim_directory(path); /* remove directory above */
> >
>
>
> ----------------------------------------------------------------------------
> ----
>
>
> >
> > ---------------------------(end of broadcast)---------------------------
> > TIP 1: if posting/reading through Usenet, please send an appropriate
> >        subscribe-nomail command to majordomo@postgresql.org so that your
> >        message can get through to the mailing list cleanly
> >
>
>
>
> ---------------------------(end of broadcast)---------------------------
> TIP 9: In versions below 8.0, the planner will ignore your desire to
>        choose an index scan if your joining column's datatypes do not
>        match
>

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: Bug in canonicalize_path()

From
Tom Lane
Date:
Bruce Momjian <pgman@candle.pha.pa.us> writes:
> Yep, it is a bug on 8.0.X.  Are you suggesting a backpatch?  We can do
> it.  Any objections?

I think he's suggesting that we depend on GetFullPathName(), which seems
a bit pointless considering we have to write the code anyway for other
platforms.

I didn't much like the patch as-is; we should think a little harder
about fixing the logic properly.

            regards, tom lane

Re: Bug in canonicalize_path()

From
Bruce Momjian
Date:
Tom Lane wrote:
> Bruce Momjian <pgman@candle.pha.pa.us> writes:
> > Yep, it is a bug on 8.0.X.  Are you suggesting a backpatch?  We can do
> > it.  Any objections?
>
> I think he's suggesting that we depend on GetFullPathName(), which seems
> a bit pointless considering we have to write the code anyway for other
> platforms.

Oh.

> I didn't much like the patch as-is; we should think a little harder
> about fixing the logic properly.

I thought about it.  Here is where we get stuck:

    usr/local/../../.. has to return  ..

Yuck.  If it an absolute path like /usr, you are fine.

In my first attempt, I counted the number of ".." groups, then went up
to remove each "..", and them remove a regular directory for each "..".
And then you have this case:

    /usr/local/../bin/../..

Here you hit the first ".." as you are going up.  It just seemed like a
lost cause.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: Bug in canonicalize_path()

From
"uniware"
Date:
----- Original Message ----- 
From: "Tom Lane" <tgl@sss.pgh.pa.us>
To: "Bruce Momjian" <pgman@candle.pha.pa.us>
Cc: "William ZHANG" <uniware@zedware.org>; <pgsql-patches@postgresql.org>
Sent: Friday, August 12, 2005 10:31 AM
Subject: Re: [PATCHES] Bug in canonicalize_path() 


> Bruce Momjian <pgman@candle.pha.pa.us> writes:
> > Yep, it is a bug on 8.0.X.  Are you suggesting a backpatch?  We can do
> > it.  Any objections?
> 
> I think he's suggesting that we depend on GetFullPathName(), which seems
> a bit pointless considering we have to write the code anyway for other
> platforms.
 
  If it must be fixed, I think we can learn sth. from GetFullPathName().
  Since we can not rely on this function on other platforms, we should
write it from scratch.

> I didn't much like the patch as-is; we should think a little harder
> about fixing the logic properly.
> 
> regards, tom lane

Re: Bug in canonicalize_path()

From
Tom Lane
Date:
I wrote:
> I didn't much like the patch as-is; we should think a little harder
> about fixing the logic properly.

Like, say, this.  (Very poorly tested but I think it's right.)

            regards, tom lane

*** src/port/path.c.orig    Thu Aug 11 21:39:22 2005
--- src/port/path.c    Thu Aug 11 22:46:05 2005
***************
*** 226,231 ****
--- 226,232 ----
  {
      char       *p, *to_p;
      bool        was_sep = false;
+     int            pending_strips;

  #ifdef WIN32
      /*
***************
*** 277,296 ****

      /*
       * Remove any trailing uses of "." and process ".." ourselves
       */
      for (;;)
      {
          int            len = strlen(path);

          if (len > 2 && strcmp(path + len - 2, "/.") == 0)
              trim_directory(path);
!         /* We can only deal with "/usr/local/..", not "/usr/local/../.." */
!         else if (len > 3 && strcmp(path + len - 3, "/..") == 0 &&
!                  (len != 5 || strcmp(path, "../..") != 0) &&
!                  (len < 6 || strcmp(path + len - 6, "/../..") != 0))
          {
              trim_directory(path);
!             trim_directory(path);    /* remove directory above */
          }
          else
              break;
--- 278,302 ----

      /*
       * Remove any trailing uses of "." and process ".." ourselves
+      *
+      * This is tricky because of the possibility of /foo/bar/baz/../..
       */
+     pending_strips = 0;
      for (;;)
      {
          int            len = strlen(path);

          if (len > 2 && strcmp(path + len - 2, "/.") == 0)
              trim_directory(path);
!         else if (len > 3 && strcmp(path + len - 3, "/..") == 0)
          {
              trim_directory(path);
!             pending_strips++;            /* must remove directory above */
!         }
!         else if (pending_strips > 0)
!         {
!             trim_directory(path);
!             pending_strips--;
          }
          else
              break;

Re: Bug in canonicalize_path()

From
Tom Lane
Date:
Bruce Momjian <pgman@candle.pha.pa.us> writes:
> And then you have this case:

>     /usr/local/../bin/../..

AFAICS, the patch I just proposed handles this.

If I recall the code properly, we do not have to make canonicalize_path
remove embedded "." or ".." --- that is, we do not have to simplify

    /usr/local/../bin

But we do have to get rid of every trailing "." or "..", else there are
failure cases when we replace the trailing component with an ordinary
file name.

            regards, tom lane

Re: Bug in canonicalize_path()

From
Bruce Momjian
Date:
Tom Lane wrote:
> Bruce Momjian <pgman@candle.pha.pa.us> writes:
> > And then you have this case:
>
> >     /usr/local/../bin/../..
>
> AFAICS, the patch I just proposed handles this.
>
> If I recall the code properly, we do not have to make canonicalize_path
> remove embedded "." or ".." --- that is, we do not have to simplify
>
>     /usr/local/../bin
>
> But we do have to get rid of every trailing "." or "..", else there are
> failure cases when we replace the trailing component with an ordinary
> file name.

But what about "usr/local/../../.."?  You loop is similar to what I
coded, but then when I realized I had to check for pending trims after I
run out of directories, and start appending them to create things like
../.., I gave up.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: Bug in canonicalize_path()

From
Tom Lane
Date:
Bruce Momjian <pgman@candle.pha.pa.us> writes:
> But what about "usr/local/../../.."?

What about it?  The case of /usr/local/../../.. is handled correctly,
and the case where it's an underspecified relative path doesn't seem
that interesting to me --- certainly that is not so important that we
should get the wrong answer on cases that *are* plausible.

Most of the uses of canonicalize_path are on paths that are required to
be absolute, anyway.

It wouldn't be too implausible to error out if pending_strips>0 after
exiting the loop.

            regards, tom lane

Re: Bug in canonicalize_path()

From
Bruce Momjian
Date:
Tom Lane wrote:
> Bruce Momjian <pgman@candle.pha.pa.us> writes:
> > But what about "usr/local/../../.."?
>
> What about it?  The case of /usr/local/../../.. is handled correctly,
> and the case where it's an underspecified relative path doesn't seem
> that interesting to me --- certainly that is not so important that we
> should get the wrong answer on cases that *are* plausible.
>
> Most of the uses of canonicalize_path are on paths that are required to
> be absolute, anyway.
>
> It wouldn't be too implausible to error out if pending_strips>0 after
> exiting the loop.

I figured it would be best to leave it alone if we can't process it, but
if you think it is more imporant to trim in cases like ../.., go ahead.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: Bug in canonicalize_path()

From
"William ZHANG"
Date:
I have test the following on Windows and Linux:

Windows:
    C:\>cd Winnt

    C:\WINNT>cd C:\Temp\..\..\

    C:\>

Linux:
    $ cd /usr/../../
    $ pwd
    /

We should handle this correctly. 

1 Single dot in the path can be removed safety. (except the first one. e.g. ./usr/local)
2 Every double dot may need a removal of the last part of the path. (except the first one. e.g. ../local)
   And if there are not enough part left, keep the last part as it is.

We can even make it easier by adding step 0: make sure path is an absolute path.

----- Original Message ----- 
From: "Bruce Momjian" <pgman@candle.pha.pa.us>
To: "Tom Lane" <tgl@sss.pgh.pa.us>
Cc: "William ZHANG" <uniware@zedware.org>; <pgsql-patches@postgresql.org>
Sent: Friday, August 12, 2005 11:15 AM
Subject: Re: [PATCHES] Bug in canonicalize_path()


> Tom Lane wrote:
> > Bruce Momjian <pgman@candle.pha.pa.us> writes:
> > > But what about "usr/local/../../.."?
> > 
> > What about it?  The case of /usr/local/../../.. is handled correctly,
> > and the case where it's an underspecified relative path doesn't seem
> > that interesting to me --- certainly that is not so important that we
> > should get the wrong answer on cases that *are* plausible.
> > 
> > Most of the uses of canonicalize_path are on paths that are required to
> > be absolute, anyway.
> > 
> > It wouldn't be too implausible to error out if pending_strips>0 after
> > exiting the loop.
> 
> I figured it would be best to leave it alone if we can't process it, but
> if you think it is more imporant to trim in cases like ../.., go ahead.
> 
> -- 
>   Bruce Momjian                        |  http://candle.pha.pa.us
>   pgman@candle.pha.pa.us               |  (610) 359-1001
>   +  If your life is a hard drive,     |  13 Roberts Road
>   +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: Bug in canonicalize_path()

From
Tom Lane
Date:
Bruce Momjian <pgman@candle.pha.pa.us> writes:
> I figured it would be best to leave it alone if we can't process it, but
> if you think it is more imporant to trim in cases like ../.., go ahead.

My recollection of this patch:

2004-08-09 16:20  tgl

    * src/: port/exec.c, port/path.c, bin/initdb/initdb.c:
    Path-mangling logic was failing to account for paths containing
    mentions of '.' or '..'.  Extend canonicalize_path() to trim off
    trailing occurrences of these things, and use it to fix up paths
    where needed (which I think is only after places where we trim the
    last path component, but maybe some others will turn up).  Fixes
    Josh's complaint that './initdb' does not work.

is that it's part of the API contract of canonicalize_path() that it
will not return something with trailing "." or "..".  It's not just
neatnik-ism to strip those, it's necessary to allow higher-level
path-mangling routines to reason about how to do what they need.
The entire concept of "trim the last directory" fails if you have
to account for "." or "..".

            regards, tom lane

Re: Bug in canonicalize_path()

From
Bruce Momjian
Date:
Tom Lane wrote:
> Bruce Momjian <pgman@candle.pha.pa.us> writes:
> > I figured it would be best to leave it alone if we can't process it, but
> > if you think it is more imporant to trim in cases like ../.., go ahead.
>
> My recollection of this patch:
>
> 2004-08-09 16:20  tgl
>
>     * src/: port/exec.c, port/path.c, bin/initdb/initdb.c:
>     Path-mangling logic was failing to account for paths containing
>     mentions of '.' or '..'.  Extend canonicalize_path() to trim off
>     trailing occurrences of these things, and use it to fix up paths
>     where needed (which I think is only after places where we trim the
>     last path component, but maybe some others will turn up).  Fixes
>     Josh's complaint that './initdb' does not work.
>
> is that it's part of the API contract of canonicalize_path() that it
> will not return something with trailing "." or "..".  It's not just
> neatnik-ism to strip those, it's necessary to allow higher-level
> path-mangling routines to reason about how to do what they need.
> The entire concept of "trim the last directory" fails if you have
> to account for "." or "..".

OK, new patch which I think handles all cases.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073
Index: src/port/path.c
===================================================================
RCS file: /cvsroot/pgsql/src/port/path.c,v
retrieving revision 1.54
diff -c -c -r1.54 path.c
*** src/port/path.c    12 Aug 2005 03:07:45 -0000    1.54
--- src/port/path.c    12 Aug 2005 04:47:59 -0000
***************
*** 226,231 ****
--- 226,232 ----
  {
      char       *p, *to_p;
      bool        was_sep = false;
+     int            pending_strips = 0;

  #ifdef WIN32
      /*
***************
*** 284,306 ****

          if (len > 2 && strcmp(path + len - 2, "/.") == 0)
              trim_directory(path);
!         /*
!          *    Process only a single trailing "..", and only if ".." does
!          *    not preceed it.
!          *    So, we only deal with "/usr/local/..", not with "/usr/local/../..".
!          *    We don't handle the even more complex cases, like
!          *    "usr/local/../../..".
!          */
!         else if (len > 3 && strcmp(path + len - 3, "/..") == 0 &&
!                  (len != 5 || strcmp(path, "../..") != 0) &&
!                  (len < 6 || strcmp(path + len - 6, "/../..") != 0))
          {
              trim_directory(path);
!             trim_directory(path);    /* remove directory above */
          }
          else
              break;
      }
  }


--- 285,311 ----

          if (len > 2 && strcmp(path + len - 2, "/.") == 0)
              trim_directory(path);
!         else if (len > 3 && strcmp(path + len - 3, "/..") == 0)
          {
              trim_directory(path);
!             pending_strips++;
!         }
!         /* for absolute paths, we just keep trimming */
!         else if (*path != '\0' && pending_strips > 0)
!         {
!             trim_directory(path);
!             pending_strips--;
          }
          else
              break;
      }
+
+     if (pending_strips > 0)
+     {
+         for (; pending_strips > 0; pending_strips--)
+             strcat(path, "../");
+         trim_trailing_separator(path);
+     }
  }



Re: Bug in canonicalize_path()

From
Tom Lane
Date:
Bruce Momjian <pgman@candle.pha.pa.us> writes:
> Tom Lane wrote:
>> ... it's part of the API contract of canonicalize_path() that it
>> will not return something with trailing "." or "..".

> OK, new patch which I think handles all cases.

> +     if (pending_strips > 0)
> +     {
> +         for (; pending_strips > 0; pending_strips--)
> +             strcat(path, "../");
> +         trim_trailing_separator(path);
> +     }

Uh, that hardly meets the API contract that I mentioned.  I think
we really have to throw an error if the path tries to ".." above
the starting point.  (Remember again that most of the uses of
this thing are dealing with absolute paths anyway, so this isn't
that big a deal.)

            regards, tom lane

Re: Bug in canonicalize_path()

From
Bruce Momjian
Date:
Tom Lane wrote:
> Bruce Momjian <pgman@candle.pha.pa.us> writes:
> > Tom Lane wrote:
> >> ... it's part of the API contract of canonicalize_path() that it
> >> will not return something with trailing "." or "..".
>
> > OK, new patch which I think handles all cases.
>
> > +     if (pending_strips > 0)
> > +     {
> > +         for (; pending_strips > 0; pending_strips--)
> > +             strcat(path, "../");
> > +         trim_trailing_separator(path);
> > +     }
>
> Uh, that hardly meets the API contract that I mentioned.  I think
> we really have to throw an error if the path tries to ".." above
> the starting point.  (Remember again that most of the uses of
> this thing are dealing with absolute paths anyway, so this isn't
> that big a deal.)

OK, so how do you want to error out?  exit()?  There are no ereport
calls in that file.  We can add them (using a *_srv.c file) or let it
return a boolean and check it at each call site.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: Bug in canonicalize_path()

From
Tom Lane
Date:
Bruce Momjian <pgman@candle.pha.pa.us> writes:
> Tom Lane wrote:
>> Uh, that hardly meets the API contract that I mentioned.  I think
>> we really have to throw an error if the path tries to ".." above
>> the starting point.

> OK, so how do you want to error out?  exit()?  There are no ereport
> calls in that file.

Yeah, we'll have to #ifdef FRONTEND it ... tedious ...

            regards, tom lane

Re: Bug in canonicalize_path()

From
Tom Lane
Date:
I wrote:
> Uh, that hardly meets the API contract that I mentioned.  I think
> we really have to throw an error if the path tries to ".." above
> the starting point.

After rereading all the callers of canonicalize_path, I've concluded
that none of them actually depend on not having a terminating ".."
as I thought.  There is a risk factor, which is that a lot of places
blindly trim the last component of a path --- but AFAICS, this is only
done with paths that are known to represent the name of a program,
so the last component wouldn't be ".." anyway.

So your last version of the patch seems like the way to go.  I'll
apply it along with changing path.c to support the parent-directory
test better.

            regards, tom lane

Re: Bug in canonicalize_path()

From
Bruce Momjian
Date:
Tom Lane wrote:
> I wrote:
> > Uh, that hardly meets the API contract that I mentioned.  I think
> > we really have to throw an error if the path tries to ".." above
> > the starting point.
>
> After rereading all the callers of canonicalize_path, I've concluded
> that none of them actually depend on not having a terminating ".."
> as I thought.  There is a risk factor, which is that a lot of places
> blindly trim the last component of a path --- but AFAICS, this is only
> done with paths that are known to represent the name of a program,
> so the last component wouldn't be ".." anyway.
>
> So your last version of the patch seems like the way to go.  I'll
> apply it along with changing path.c to support the parent-directory
> test better.

OK, here is a version that errors out that I just applied and reversed
out when I saw your email.  Feel free to use it or discard it.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073
Index: src/backend/postmaster/postmaster.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/postmaster/postmaster.c,v
retrieving revision 1.464
diff -c -c -r1.464 postmaster.c
*** src/backend/postmaster/postmaster.c    12 Aug 2005 18:23:53 -0000    1.464
--- src/backend/postmaster/postmaster.c    12 Aug 2005 19:40:44 -0000
***************
*** 377,384 ****
      char       *userDoption = NULL;
      int            i;

!     /* This will call exit() if strdup() fails. */
!     progname = get_progname(argv[0]);

      MyProcPid = PostmasterPid = getpid();

--- 377,387 ----
      char       *userDoption = NULL;
      int            i;

!     if ((progname = get_progname(argv[0])) == NULL)
!     {
!         printf(_("unable to allocate memory for program name \"%s\".\n"), progname);
!         ExitPostmaster(0);
!     }

      MyProcPid = PostmasterPid = getpid();

Index: src/port/Makefile
===================================================================
RCS file: /cvsroot/pgsql/src/port/Makefile,v
retrieving revision 1.25
diff -c -c -r1.25 Makefile
*** src/port/Makefile    20 Mar 2005 03:53:39 -0000    1.25
--- src/port/Makefile    12 Aug 2005 19:40:51 -0000
***************
*** 31,36 ****
--- 31,37 ----
  LIBOBJS_SRV := $(patsubst dirmod.o,dirmod_srv.o, $(LIBOBJS_SRV))
  LIBOBJS_SRV := $(patsubst exec.o,exec_srv.o, $(LIBOBJS_SRV))
  LIBOBJS_SRV := $(patsubst getaddrinfo.o,getaddrinfo_srv.o, $(LIBOBJS_SRV))
+ LIBOBJS_SRV := $(patsubst path.o,path_srv.o, $(LIBOBJS_SRV))
  LIBOBJS_SRV := $(patsubst thread.o,thread_srv.o, $(LIBOBJS_SRV))

  all: libpgport.a libpgport_srv.a
***************
*** 66,72 ****
  getaddrinfo_srv.o: getaddrinfo.c
      $(CC) $(CFLAGS) $(subst -DFRONTEND,, $(CPPFLAGS)) -c $< -o $@

! snprintf_srv.o: snprintf.c
      $(CC) $(CFLAGS) $(subst -DFRONTEND,, $(CPPFLAGS)) -c $< -o $@

  # No thread flags for server version
--- 67,73 ----
  getaddrinfo_srv.o: getaddrinfo.c
      $(CC) $(CFLAGS) $(subst -DFRONTEND,, $(CPPFLAGS)) -c $< -o $@

! path_srv.o: path.c
      $(CC) $(CFLAGS) $(subst -DFRONTEND,, $(CPPFLAGS)) -c $< -o $@

  # No thread flags for server version
Index: src/port/path.c
===================================================================
RCS file: /cvsroot/pgsql/src/port/path.c,v
retrieving revision 1.54
diff -c -c -r1.54 path.c
*** src/port/path.c    12 Aug 2005 03:07:45 -0000    1.54
--- src/port/path.c    12 Aug 2005 19:40:53 -0000
***************
*** 13,19 ****
   *-------------------------------------------------------------------------
   */

! #include "c.h"

  #include <ctype.h>
  #include <sys/stat.h>
--- 13,23 ----
   *-------------------------------------------------------------------------
   */

! #ifndef FRONTEND
! #include "postgres.h"
! #else
! #include "postgres_fe.h"
! #endif

  #include <ctype.h>
  #include <sys/stat.h>
***************
*** 226,231 ****
--- 230,236 ----
  {
      char       *p, *to_p;
      bool        was_sep = false;
+     int            pending_strips = 0;

  #ifdef WIN32
      /*
***************
*** 284,302 ****

          if (len > 2 && strcmp(path + len - 2, "/.") == 0)
              trim_directory(path);
!         /*
!          *    Process only a single trailing "..", and only if ".." does
!          *    not preceed it.
!          *    So, we only deal with "/usr/local/..", not with "/usr/local/../..".
!          *    We don't handle the even more complex cases, like
!          *    "usr/local/../../..".
!          */
!         else if (len > 3 && strcmp(path + len - 3, "/..") == 0 &&
!                  (len != 5 || strcmp(path, "../..") != 0) &&
!                  (len < 6 || strcmp(path + len - 6, "/../..") != 0))
          {
              trim_directory(path);
!             trim_directory(path);    /* remove directory above */
          }
          else
              break;
--- 289,326 ----

          if (len > 2 && strcmp(path + len - 2, "/.") == 0)
              trim_directory(path);
!         else if (len > 3 && strcmp(path + len - 3, "/..") == 0)
          {
              trim_directory(path);
!             pending_strips++;
!         }
!         else if (pending_strips > 0)
!         {
!             /*    If path is not "", we can keep trimming.  Even if path is
!              *    "/", we can keep trimming because trim_directory never removes
!              *    the leading separator, and the parent directory of "/" is "/".
!              */
!             if (*path != '\0')
!             {
!                 trim_directory(path);
!                 pending_strips--;
!             }
!             else
!             {
!                 /*
!                  *    If we still have pending_strips, it means the supplied path
!                  *    was exhausted and we still have more directories to move up.
!                  *    This means that the resulting path is only parents, like
!                  *    ".." or "../..".  If so, callers can not handle trailing "..",
!                  *    so we exit.
!                  */
! #ifndef FRONTEND
!                 elog(ERROR, "relative paths (\"..\") not supported");
! #else
!                 fprintf(stderr, _("relative paths (\"..\") not supported\n"));
!                 exit(1);
! #endif
!             }
          }
          else
              break;
***************
*** 305,312 ****


  /*
!  * Extracts the actual name of the program as called -
!  * stripped of .exe suffix if any
   */
  const char *
  get_progname(const char *argv0)
--- 329,338 ----


  /*
!  *    Extracts the actual name of the program as called -
!  *    stripped of .exe suffix if any.
!  *     The server calling this must check for NULL return
!  *    and report the error.
   */
  const char *
  get_progname(const char *argv0)
***************
*** 329,336 ****
          progname = strdup(nodir_name);
          if (progname == NULL)
          {
              fprintf(stderr, "%s: out of memory\n", nodir_name);
!             exit(1);    /* This could exit the postmaster */
          }
          progname[strlen(progname) - (sizeof(EXE) - 1)] = '\0';
          nodir_name = progname;
--- 355,370 ----
          progname = strdup(nodir_name);
          if (progname == NULL)
          {
+ #ifndef FRONTEND
+             /*
+              *    No elog() support in postmaster at this stage,
+              *    so return NULL and print error at the call.
+              */
+             return NULL;
+ #else
              fprintf(stderr, "%s: out of memory\n", nodir_name);
!             exit(1);
! #endif
          }
          progname[strlen(progname) - (sizeof(EXE) - 1)] = '\0';
          nodir_name = progname;

Re: Bug in canonicalize_path()

From
Tom Lane
Date:
Bruce Momjian <pgman@candle.pha.pa.us> writes:
> OK, here is a version that errors out that I just applied and reversed
> out when I saw your email.  Feel free to use it or discard it.

Sorry about that --- should have tried to mail you a bit sooner.

BTW, what's with the postmaster.c change?  Seems mistaken.

            regards, tom lane

Re: Bug in canonicalize_path()

From
Bruce Momjian
Date:
Tom Lane wrote:
> Bruce Momjian <pgman@candle.pha.pa.us> writes:
> > OK, here is a version that errors out that I just applied and reversed
> > out when I saw your email.  Feel free to use it or discard it.
>
> Sorry about that --- should have tried to mail you a bit sooner.
>
> BTW, what's with the postmaster.c change?  Seems mistaken.

Actually, it is a cleanup.  The original path.c::get_progname() used an
exit() call in WIN32 because it didn't have frontend/backend-specific
code.  With specific versions, I could return a NULL from get_progname
and print a relivant message in postmaster.c on failure.  I doubt it
would ever happen, but it was a cleanup.  It isn't significant enough to
justify making a server-specific version of path.c on its own, however.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: Bug in canonicalize_path()

From
Tom Lane
Date:
Bruce Momjian <pgman@candle.pha.pa.us> writes:
> In my first attempt, I counted the number of ".." groups, then went up
> to remove each "..", and them remove a regular directory for each "..".
> And then you have this case:

>     /usr/local/../bin/../..

> Here you hit the first ".." as you are going up.  It just seemed like a
> lost cause.

BTW, you were right: this is a *lot* harder than it looks at first
glance.  Here's what I ended up with:

    /*
     * Remove any trailing uses of "." and process ".." ourselves
     *
     * Note that "/../.." should reduce to just "/", while "../.." has to
     * be kept as-is.  In the latter case we put back mistakenly trimmed
     * ".." components below.  Also note that we want a Windows drive spec
     * to be visible to trim_directory(), but it's not part of the logic
     * that's looking at the name components; hence distinction between
     * path and spath.
     */
    spath = skip_drive(path);
    pending_strips = 0;
    for (;;)
    {
        int         len = strlen(spath);

        if (len >= 2 && strcmp(spath + len - 2, "/.") == 0)
            trim_directory(path);
        else if (strcmp(spath, ".") == 0)
        {
            /* Want to leave "." alone, but "./.." has to become ".." */
            if (pending_strips > 0)
                *spath = '\0';
            break;
        }
        else if ((len >= 3 && strcmp(spath + len - 3, "/..") == 0) ||
                 strcmp(spath, "..") == 0)
        {
            trim_directory(path);
            pending_strips++;
        }
        else if (pending_strips > 0 && *spath != '\0')
        {
            /* trim a regular directory name cancelled by ".." */
            trim_directory(path);
            pending_strips--;
            /* foo/.. should become ".", not empty */
            if (*spath == '\0')
                strcpy(spath, ".");
        }
        else
            break;
    }

    if (pending_strips > 0)
    {
        /*
         * We could only get here if path is now totally empty (other than
         * a possible drive specifier on Windows).
         * We have to put back one or more ".."'s that we took off.
         */
        while (--pending_strips > 0)
            strcat(path, "../");
        strcat(path, "..");
    }
}

            regards, tom lane