Tom Lane wrote: > Andres Freund <and...@2ndquadrant.com> writes: > > On 2013-08-19 14:28:28 -0400, Tom Lane wrote: > >> One possibility is to do the initial check somewhere shortly after > >> ChangeToDataDir(), and have the GUC check hook only attempt to make a > >> check in SIGHUP context. Unfortunately we aren't passing the context to > >> check hooks, only GucSource which isn't adequate for this. Not sure if we > >> want to go so far as to change the check-hook API at this point. We could > >> probably think of some other, klugy way to tell if it's initial startup. > > > Is it even actually safe to have stats_temp_directory PGC_SIGHUP after > > the per DB splitup? I haven't audited the code for it, but it seems > > somewhat likely that we would end up with some files in the old and some > > in the new directory? > > That's a good point. For the moment we could just change it to > PGC_POSTMASTER and eliminate this problem. Reducing it back to SIGHUP > would be a future feature, which is evidently less than trivial.
Here's a patchset for this. The first patch is the same as upthread, with the enum members renamed; the second is the actual pgstats change. (I considered the idea that checkDirectoryPermissions returned a bitmask of the failed checks, instead of just returning a code for the first check that fails. This might be useful if some caller wants to ignore certain problems. But at the moment I didn't see many other places where this could be used, so it's probably pointless ATM.) -- Álvaro Herrera http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Training & Services
*** a/src/backend/postmaster/postmaster.c --- b/src/backend/postmaster/postmaster.c *************** *** 1313,1385 **** getInstallationPaths(const char *argv0) static void checkDataDir(void) { char path[MAXPGPATH]; FILE *fp; - struct stat stat_buf; Assert(DataDir); ! if (stat(DataDir, &stat_buf) != 0) { ! if (errno == ENOENT) ereport(FATAL, (errcode_for_file_access(), errmsg("data directory \"%s\" does not exist", DataDir))); ! else ereport(FATAL, (errcode_for_file_access(), ! errmsg("could not read permissions of directory \"%s\": %m", ! DataDir))); } - /* eventual chdir would fail anyway, but let's test ... */ - if (!S_ISDIR(stat_buf.st_mode)) - ereport(FATAL, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("specified data directory \"%s\" is not a directory", - DataDir))); - - /* - * Check that the directory belongs to my userid; if not, reject. - * - * This check is an essential part of the interlock that prevents two - * postmasters from starting in the same directory (see CreateLockFile()). - * Do not remove or weaken it. - * - * XXX can we safely enable this check on Windows? - */ - #if !defined(WIN32) && !defined(__CYGWIN__) - if (stat_buf.st_uid != geteuid()) - ereport(FATAL, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("data directory \"%s\" has wrong ownership", - DataDir), - errhint("The server must be started by the user that owns the data directory."))); - #endif - - /* - * Check if the directory has group or world access. If so, reject. - * - * It would be possible to allow weaker constraints (for example, allow - * group access) but we cannot make a general assumption that that is - * okay; for example there are platforms where nearly all users - * customarily belong to the same group. Perhaps this test should be - * configurable. - * - * XXX temporarily suppress check when on Windows, because there may not - * be proper support for Unix-y file permissions. Need to think of a - * reasonable check to apply on Windows. - */ - #if !defined(WIN32) && !defined(__CYGWIN__) - if (stat_buf.st_mode & (S_IRWXG | S_IRWXO)) - ereport(FATAL, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("data directory \"%s\" has group or world access", - DataDir), - errdetail("Permissions should be u=rwx (0700)."))); - #endif - /* Look for PG_VERSION before looking for pg_control */ ValidatePgVersion(DataDir); --- 1313,1363 ---- static void checkDataDir(void) { + CheckDirErrcode err; char path[MAXPGPATH]; FILE *fp; Assert(DataDir); ! err = checkDirectoryPermissions(DataDir); ! switch (err) { ! case CKDIR_OK: ! break; ! case CKDIR_NOT_EXISTS: ereport(FATAL, (errcode_for_file_access(), errmsg("data directory \"%s\" does not exist", DataDir))); ! break; ! case CKDIR_CANT_READ_PERMS: ereport(FATAL, (errcode_for_file_access(), ! errmsg("could not read permissions of data directory \"%s\": %m", ! DataDir))); ! break; ! case CKDIR_NOT_DIR: ! ereport(FATAL, ! (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), ! errmsg("specified data directory \"%s\" is not a directory", ! DataDir))); ! break; ! case CKDIR_WRONG_OWNER: ! ereport(FATAL, ! (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), ! errmsg("data directory \"%s\" has wrong ownership", ! DataDir), ! errhint("The server must be started by the user that owns the data directory."))); ! break; ! case CKDIR_TOO_ACCESSIBLE: ! ereport(FATAL, ! (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), ! errmsg("data directory \"%s\" has group or world access", ! DataDir), ! errdetail("Permissions should be u=rwx (0700)."))); ! break; } /* Look for PG_VERSION before looking for pg_control */ ValidatePgVersion(DataDir); *** a/src/include/port.h --- b/src/include/port.h *************** *** 463,468 **** extern char *inet_net_ntop(int af, const void *src, int bits, --- 463,479 ---- /* port/pgcheckdir.c */ extern int pg_check_dir(const char *dir); + typedef enum + { + CKDIR_OK, + CKDIR_NOT_EXISTS, + CKDIR_CANT_READ_PERMS, + CKDIR_NOT_DIR, + CKDIR_WRONG_OWNER, + CKDIR_TOO_ACCESSIBLE + } CheckDirErrcode; + extern CheckDirErrcode checkDirectoryPermissions(char *directory); + /* port/pgmkdirp.c */ extern int pg_mkdir_p(char *path, int omode); *** a/src/port/pgcheckdir.c --- b/src/port/pgcheckdir.c *************** *** 14,19 **** --- 14,22 ---- #include "c.h" #include <dirent.h> + #include <sys/types.h> + #include <sys/stat.h> + #include <unistd.h> /* *************** *** 88,90 **** pg_check_dir(const char *dir) --- 91,147 ---- return result; } + + /* + * Verify permissions of a directory + */ + CheckDirErrcode + checkDirectoryPermissions(char *directory) + { + struct stat stat_buf; + + if (stat(directory, &stat_buf) != 0) + { + if (errno == ENOENT) + return CKDIR_NOT_EXISTS; + else + return CKDIR_CANT_READ_PERMS; + } + + if (!S_ISDIR(stat_buf.st_mode)) + return CKDIR_NOT_DIR; + + /* + * Check that the directory belongs to my userid; if not, reject. + * + * This check is an essential part of the interlock that prevents two + * postmasters from starting in the same directory (see CreateLockFile()). + * Do not remove or weaken it. + * + * XXX can we safely enable this check on Windows? + */ + #if !defined(WIN32) && !defined(__CYGWIN__) + if (stat_buf.st_uid != geteuid()) + return CKDIR_WRONG_OWNER; + #endif + + /* + * Check if the directory has group or world access. If so, reject. + * + * It would be possible to allow weaker constraints (for example, allow + * group access) but we cannot make a general assumption that that is + * okay; for example there are platforms where nearly all users + * customarily belong to the same group. Perhaps this test should be + * configurable. + * + * XXX temporarily suppress check when on Windows, because there may not + * be proper support for Unix-y file permissions. Need to think of a + * reasonable check to apply on Windows. + */ + #if !defined(WIN32) && !defined(__CYGWIN__) + if (stat_buf.st_mode & (S_IRWXG | S_IRWXO)) + return CKDIR_TOO_ACCESSIBLE; + #endif + + return CKDIR_OK; + }
*** a/doc/src/sgml/config.sgml --- b/doc/src/sgml/config.sgml *************** *** 4446,4453 **** COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; is <filename>pg_stat_tmp</filename>. Pointing this at a RAM-based file system will decrease physical I/O requirements and can lead to improved performance. ! This parameter can only be set in the <filename>postgresql.conf</> ! file or on the server command line. </para> </listitem> </varlistentry> --- 4446,4452 ---- is <filename>pg_stat_tmp</filename>. Pointing this at a RAM-based file system will decrease physical I/O requirements and can lead to improved performance. ! This parameter can only be set at server start. </para> </listitem> </varlistentry> *** a/src/backend/postmaster/pgstat.c --- b/src/backend/postmaster/pgstat.c *************** *** 554,559 **** startup_failed: --- 554,628 ---- } /* + * Verify the permissions of stats_temp_directory. + * + * For security reasons, we want this directory to be tightly controlled; + * both so that we don't interfere with other running instances, and so that + * others can't inject fake stats. + * + * The obvious way to do this is to set this function as a GUC check hook; but + * this doesn't work because the first time those hooks are run, we have not + * changed the current directory to the data directory yet; so the check fails + * when it's set to a relative path, as the default value is. We'd have to + * find a way to skip this check in the first run of check hooks. To avoid this + * problem, the stats_temp_directory setting is PGC_POSTMASTER for now, and we + * run the check separately in PostmasterMain, after changing directory, + * instead. + * + * If we were to overcome that problem, we should probably also consider having + * the stats collector write a full set of files in the new temp directory if + * the setting is changed on SIGHUP. + */ + bool + check_pgstat_temp_dir_perms(char *dir) + { + int elevel = FATAL; + CheckDirErrcode err; + + err = checkDirectoryPermissions(dir); + + switch (err) + { + case CKDIR_OK: + break; + case CKDIR_NOT_EXISTS: + ereport(elevel, + (errcode_for_file_access(), + errmsg("stats_temp_directory \"%s\" does not exist", + dir))); + break; + case CKDIR_CANT_READ_PERMS: + ereport(elevel, + (errcode_for_file_access(), + errmsg("could not read permissions of stats_temp_directory \"%s\": %m", + dir))); + break; + case CKDIR_NOT_DIR: + ereport(elevel, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("specified stats_temp_directory \"%s\" is not a directory", + dir))); + break; + case CKDIR_WRONG_OWNER: + ereport(elevel, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("stats_temp_directory \"%s\" has wrong ownership", + dir), + errhint("The server must be started by the user that owns the stats_temp_directory."))); + break; + case CKDIR_TOO_ACCESSIBLE: + ereport(elevel, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("stats_temp_directory \"%s\" has group or world access", + dir), + errdetail("Permissions should be u=rwx (0700)."))); + break; + } + + return err == CKDIR_OK; + } + + /* * subroutine for pgstat_reset_all */ static void *** a/src/backend/postmaster/postmaster.c --- b/src/backend/postmaster/postmaster.c *************** *** 829,834 **** PostmasterMain(int argc, char *argv[]) --- 829,836 ---- ExitPostmaster(1); } + (void) check_pgstat_temp_dir_perms(pgstat_stat_directory); + /* * Now that we are done processing the postmaster arguments, reset * getopt(3) library so that it will work correctly in subprocesses. *** a/src/backend/utils/misc/guc.c --- b/src/backend/utils/misc/guc.c *************** *** 3081,3087 **** static struct config_string ConfigureNamesString[] = }, { ! {"stats_temp_directory", PGC_SIGHUP, STATS_COLLECTOR, gettext_noop("Writes temporary statistics files to the specified directory."), NULL, GUC_SUPERUSER_ONLY --- 3081,3088 ---- }, { ! /* see check_pgstat_temp_dir_perms for why this is PGC_POSTMASTER */ ! {"stats_temp_directory", PGC_POSTMASTER, STATS_COLLECTOR, gettext_noop("Writes temporary statistics files to the specified directory."), NULL, GUC_SUPERUSER_ONLY *** a/src/include/pgstat.h --- b/src/include/pgstat.h *************** *** 750,755 **** extern void pgstat_init(void); --- 750,756 ---- extern int pgstat_start(void); extern void pgstat_reset_all(void); extern void allow_immediate_pgstat_restart(void); + extern bool check_pgstat_temp_dir_perms(char *dir); #ifdef EXEC_BACKEND extern void PgstatCollectorMain(int argc, char *argv[]) __attribute__((noreturn));
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers