use of getcwd(3)/chdir(2) during path resolution (exec.c) - Mailing list pgsql-hackers

From Petar Bogdanovic
Subject use of getcwd(3)/chdir(2) during path resolution (exec.c)
Date
Msg-id 20180303200437.GA10789@pintail.NGC068
Whole thread Raw
List pgsql-hackers
Hi,

When trying to deduct their absolute, symlink-free path from argv0,
pg_ctl, postgres (and probably others) use:

 a) getcwd(3) in find_my_exec in order to complement a relative path
    containing at least one directory separator

 b) getcwd(3) and chdir(2) in resolve_symlinks in order to prune
    symlinks from given absolute path

In both cases, the utility/daemon will fail (and exit) if the current
directory is either not readable or not searchable, even though said
utility/daemon has permission to access each component of its absolute
path in argv0.

That scenario is not uncommon:

    $ id
    uid=1000(petar)

    $ pwd
    /home/petar

    $ ls -lad .
    drwx------  5 petar  petar  512 Mar  2 23:41 .

    $ sudo -u pgsql /usr/bin/pg_ctl start (...)
    could not identify current directory: Permission denied
    could not identify current directory: Permission denied
    could not identify current directory: Permission denied
    The program "postgres" is needed by pg_ctl but was not found in the same directory as "pg_ctl".
    Check your installation.


There is an easy workaround for (a):  Only do getcwd if argv0 is not an
absolute path.  The variable cwd is not relevant for absolute paths.

(b) is not as straightforward;  resolve_symlinks only trusts getcwd to
provide symlink-free paths (see comment for rationale) so, during path
resolution, it will change the current directory one or more times and
finally, before returing, chdir back to the original wd.

The last step is, however, not possible if the original wd is not read-
and/or searchable.

In the patch below, path resolution is skipped if getcwd returns EACCES.
The other option would have been ignoring the initial getcwd / final
chdir and just keeping the most recent wd, which, at that point, could
be pretty much any component of the absolute path.  That seemed too
unpredictable.

This was tested with pgsql94 but that part of exec.c doesn't seem to
have changed in any more recent version.

Thanks,
Petar


--- src/common/exec.c.orig
+++ src/common/exec.c
@@ -103,45 +103,45 @@
 
 /*
  * find_my_exec -- find an absolute path to a valid executable
  *
  *    argv0 is the name passed on the command line
  *    retpath is the output area (must be of size MAXPGPATH)
  *    Returns 0 if OK, -1 if error.
  *
  * The reason we have to work so hard to find an absolute path is that
  * on some platforms we can't do dynamic loading unless we know the
  * executable's location.  Also, we need a full path not a relative
  * path because we will later change working directory.  Finally, we want
  * a true path not a symlink location, so that we can locate other files
  * that are part of our installation relative to the executable.
  */
 int
 find_my_exec(const char *argv0, char *retpath)
 {
     char        cwd[MAXPGPATH],
                 test_path[MAXPGPATH];
     char       *path;
 
-    if (!getcwd(cwd, MAXPGPATH))
+    if (!is_absolute_path(argv0) && !getcwd(cwd, MAXPGPATH))
     {
         log_error(_("could not identify current directory: %s"),
                   strerror(errno));
         return -1;
     }
 
     /*
      * If argv0 contains a separator, then PATH wasn't used.
      */
     if (first_dir_separator(argv0) != NULL)
     {
         if (is_absolute_path(argv0))
             StrNCpy(retpath, argv0, MAXPGPATH);
         else
             join_path_components(retpath, cwd, argv0);
         canonicalize_path(retpath);
 
         if (validate_exec(retpath) == 0)
             return resolve_symlinks(retpath);
 
         log_error(_("invalid binary \"%s\""), retpath);
         return -1;
@@ -219,44 +219,50 @@
 resolve_symlinks(char *path)
 {
 #ifdef HAVE_READLINK
     struct stat buf;
     char        orig_wd[MAXPGPATH],
                 link_buf[MAXPGPATH];
     char       *fname;
 
     /*
      * To resolve a symlink properly, we have to chdir into its directory and
      * then chdir to where the symlink points; otherwise we may fail to
      * resolve relative links correctly (consider cases involving mount
      * points, for example).  After following the final symlink, we use
      * getcwd() to figure out where the heck we're at.
      *
      * One might think we could skip all this if path doesn't point to a
      * symlink to start with, but that's wrong.  We also want to get rid of
      * any directory symlinks that are present in the given path. We expect
      * getcwd() to give us an accurate, symlink-free path.
      */
     if (!getcwd(orig_wd, MAXPGPATH))
     {
+        if (errno == EACCES)
+        {
+            log_error(_("access to current directory denied, "
+                    "skipping path resolution of \"%s\""), path);
+            return 0;
+        }
         log_error(_("could not identify current directory: %s"),
                   strerror(errno));
         return -1;
     }
 
     for (;;)
     {
         char       *lsep;
         int            rllen;
 
         lsep = last_dir_separator(path);
         if (lsep)
         {
             *lsep = '\0';
             if (chdir(path) == -1)
             {
                 log_error4(_("could not change directory to \"%s\": %s"), path, strerror(errno));
                 return -1;
             }
             fname = lsep + 1;
         }
         else


pgsql-hackers by date:

Previous
From: Andres Freund
Date:
Subject: Re: JIT compiling with LLVM v11
Next
From: Tomas Vondra
Date:
Subject: Re: autovacuum: change priority of the vacuumed tables