diff options
author | Stephen Frost | 2018-04-07 21:45:39 +0000 |
---|---|---|
committer | Stephen Frost | 2018-04-07 21:45:39 +0000 |
commit | c37b3d08ca6873f9d4eaf24c72a90a550970cbb8 (patch) | |
tree | a92cd4f79d20c4d002bd1f41af8bfe9507d92636 /src/backend | |
parent | da9b580d89903fee871cf54845ffa2b26bda2e11 (diff) |
Allow group access on PGDATA
Allow the cluster to be optionally init'd with read access for the
group.
This means a relatively non-privileged user can perform a backup of the
cluster without requiring write privileges, which enhances security.
The mode of PGDATA is used to determine whether group permissions are
enabled for directory and file creates. This method was chosen as it's
simple and works well for the various utilities that write into PGDATA.
Changing the mode of PGDATA manually will not automatically change the
mode of all the files contained therein. If the user would like to
enable group access on an existing cluster then changing the mode of all
the existing files will be required. Note that pg_upgrade will
automatically change the mode of all migrated files if the new cluster
is init'd with the -g option.
Tests are included for the backend and all the utilities which operate
on the PG data directory to ensure that the correct mode is set based on
the data directory permissions.
Author: David Steele <david@pgmasters.net>
Reviewed-By: Michael Paquier, with discussion amongst many others.
Discussion: https://postgr.es/m/ad346fe6-b23e-59f1-ecb7-0e08390ad629%40pgmasters.net
Diffstat (limited to 'src/backend')
-rw-r--r-- | src/backend/bootstrap/bootstrap.c | 12 | ||||
-rw-r--r-- | src/backend/postmaster/postmaster.c | 87 | ||||
-rw-r--r-- | src/backend/tcop/postgres.c | 3 | ||||
-rw-r--r-- | src/backend/utils/init/globals.c | 9 | ||||
-rw-r--r-- | src/backend/utils/init/miscinit.c | 115 | ||||
-rw-r--r-- | src/backend/utils/misc/guc.c | 25 |
6 files changed, 160 insertions, 91 deletions
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index 644084d1c3b..59cd4b17f32 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -349,13 +349,15 @@ AuxiliaryProcessMain(int argc, char *argv[]) proc_exit(1); } - /* Validate we have been given a reasonable-looking DataDir */ - Assert(DataDir); - ValidatePgVersion(DataDir); - - /* Change into DataDir (if under postmaster, should be done already) */ + /* + * Validate we have been given a reasonable-looking DataDir and change + * into it (if under postmaster, should be done already). + */ if (!IsUnderPostmaster) + { + checkDataDir(); ChangeToDataDir(); + } /* If standalone, create lockfile for data directory */ if (!IsUnderPostmaster) diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 10afecffb37..ccf63e93097 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -391,7 +391,7 @@ static DNSServiceRef bonjour_sdref = NULL; static void CloseServerPorts(int status, Datum arg); static void unlink_external_pid_file(int status, Datum arg); static void getInstallationPaths(const char *argv0); -static void checkDataDir(void); +static void checkControlFile(void); static Port *ConnCreate(int serverFd); static void ConnFree(Port *port); static void reset_shared(int port); @@ -588,7 +588,12 @@ PostmasterMain(int argc, char *argv[]) IsPostmasterEnvironment = true; /* - * for security, no dir or file created can be group or other accessible + * We should not be creating any files or directories before we check the + * data directory (see checkDataDir()), but just in case set the umask to + * the most restrictive (owner-only) permissions. + * + * checkDataDir() will reset the umask based on the data directory + * permissions. */ umask(PG_MODE_MASK_OWNER); @@ -877,6 +882,9 @@ PostmasterMain(int argc, char *argv[]) /* Verify that DataDir looks reasonable */ checkDataDir(); + /* Check that pg_control exists */ + checkControlFile(); + /* And switch working directory into it */ ChangeToDataDir(); @@ -1469,82 +1477,17 @@ getInstallationPaths(const char *argv0) */ } - /* - * Validate the proposed data directory + * Check that pg_control exists in the correct location in the data directory. + * + * No attempt is made to validate the contents of pg_control here. This is + * just a sanity check to see if we are looking at a real data directory. */ static void -checkDataDir(void) +checkControlFile(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); snprintf(path, sizeof(path), "%s/global/pg_control", DataDir); diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 7bdecc32ec0..5095a4f6867 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -3731,8 +3731,7 @@ PostgresMain(int argc, char *argv[], * Validate we have been given a reasonable-looking DataDir (if under * postmaster, assume postmaster did this already). */ - Assert(DataDir); - ValidatePgVersion(DataDir); + checkDataDir(); /* Change into DataDir (if under postmaster, was done already) */ ChangeToDataDir(); diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c index c1f0441b081..0a3163398f3 100644 --- a/src/backend/utils/init/globals.c +++ b/src/backend/utils/init/globals.c @@ -16,8 +16,11 @@ * *------------------------------------------------------------------------- */ +#include <sys/stat.h> + #include "postgres.h" +#include "common/file_perm.h" #include "libpq/libpq-be.h" #include "libpq/pqcomm.h" #include "miscadmin.h" @@ -59,6 +62,12 @@ struct Latch *MyLatch; */ char *DataDir = NULL; +/* + * Mode of the data directory. The default is 0700 but may it be changed in + * checkDataDir() to 0750 if the data directory actually has that mode. + */ +int data_directory_mode = PG_DIR_MODE_OWNER; + char OutputFileName[MAXPGPATH]; /* debugging output file */ char my_exec_path[MAXPGPATH]; /* full path to my executable */ diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index f8f08f3f88b..03b28c3604a 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -89,6 +89,100 @@ SetDatabasePath(const char *path) } /* + * Validate the proposed data directory. + * + * Also initialize file and directory create modes and mode mask. + */ +void +checkDataDir(void) +{ + 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 correct permissions. If not, reject. + * + * Only two possible modes are allowed, 0700 and 0750. The latter mode + * indicates that group read/execute should be allowed on all newly + * created files and directories. + * + * 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 & PG_MODE_MASK_GROUP) + ereport(FATAL, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("data directory \"%s\" has invalid permissions", + DataDir), + errdetail("Permissions should be u=rwx (0700) or u=rwx,g=rx (0750)."))); +#endif + + /* + * Reset creation modes and mask based on the mode of the data directory. + * + * The mask was set earlier in startup to disallow group permissions on + * newly created files and directories. However, if group read/execute + * are present on the data directory then modify the create modes and mask + * to allow group read/execute on newly created files and directories and + * set the data_directory_mode GUC. + * + * Suppress when on Windows, because there may not be proper support for + * Unix-y file permissions. + */ +#if !defined(WIN32) && !defined(__CYGWIN__) + SetDataDirectoryCreatePerm(stat_buf.st_mode); + + umask(pg_mode_mask); + data_directory_mode = pg_dir_create_mode; +#endif + + /* Check for PG_VERSION */ + ValidatePgVersion(DataDir); +} + +/* * Set data directory, but make sure it's an absolute path. Use this, * never set DataDir directly. */ @@ -829,7 +923,7 @@ CreateLockFile(const char *filename, bool amPostmaster, /* * Try to create the lock file --- O_EXCL makes this atomic. * - * Think not to make the file protection weaker than 0600. See + * Think not to make the file protection weaker than 0600/0640. See * comments below. */ fd = open(filename, O_RDWR | O_CREAT | O_EXCL, pg_file_create_mode); @@ -899,17 +993,14 @@ CreateLockFile(const char *filename, bool amPostmaster, * implies that the existing process has a different userid than we * do, which means it cannot be a competing postmaster. A postmaster * cannot successfully attach to a data directory owned by a userid - * other than its own. (This is now checked directly in - * checkDataDir(), but has been true for a long time because of the - * restriction that the data directory isn't group- or - * world-accessible.) Also, since we create the lockfiles mode 600, - * we'd have failed above if the lockfile belonged to another userid - * --- which means that whatever process kill() is reporting about - * isn't the one that made the lockfile. (NOTE: this last - * consideration is the only one that keeps us from blowing away a - * Unix socket file belonging to an instance of Postgres being run by - * someone else, at least on machines where /tmp hasn't got a - * stickybit.) + * other than its own, as enforced in checkDataDir(). Also, since we + * create the lockfiles mode 0600/0640, we'd have failed above if the + * lockfile belonged to another userid --- which means that whatever + * process kill() is reporting about isn't the one that made the + * lockfile. (NOTE: this last consideration is the only one that + * keeps us from blowing away a Unix socket file belonging to an + * instance of Postgres being run by someone else, at least on + * machines where /tmp hasn't got a stickybit.) */ if (other_pid != my_pid && other_pid != my_p_pid && other_pid != my_gp_pid) diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 71c2b4eff16..8441837e0ff 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -194,6 +194,7 @@ static void assign_application_name(const char *newval, void *extra); static bool check_cluster_name(char **newval, void **extra, GucSource source); static const char *show_unix_socket_permissions(void); static const char *show_log_file_mode(void); +static const char *show_data_directory_mode(void); /* Private functions in guc-file.l that need to be called from guc.c */ static ConfigVariable *ProcessConfigFileInternal(GucContext context, @@ -2044,6 +2045,21 @@ static struct config_int ConfigureNamesInt[] = NULL, NULL, show_log_file_mode }, + + { + {"data_directory_mode", PGC_INTERNAL, PRESET_OPTIONS, + gettext_noop("Mode of the data directory."), + gettext_noop("The parameter value is a numeric mode specification " + "in the form accepted by the chmod and umask system " + "calls. (To use the customary octal format the number " + "must start with a 0 (zero).)"), + GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE + }, + &data_directory_mode, + 0700, 0000, 0777, + NULL, NULL, show_data_directory_mode + }, + { {"work_mem", PGC_USERSET, RESOURCES_MEM, gettext_noop("Sets the maximum memory to be used for query workspaces."), @@ -10744,4 +10760,13 @@ show_log_file_mode(void) return buf; } +static const char * +show_data_directory_mode(void) +{ + static char buf[12]; + + snprintf(buf, sizeof(buf), "%04o", data_directory_mode); + return buf; +} + #include "guc-file.c" |