PATCH: Configurable file mode mask
PostgreSQL currently requires the file mode mask (umask) to be 0077.
However, this precludes the possibility of a user in the postgres group
performing a backup (or whatever). Now that
pg_start_backup()/pg_stop_backup() privileges can be delegated to an
unprivileged user, it makes sense to also allow a (relatively)
unprivileged user to perform the backup at the file system level as well.
This patch introduces a new initdb param, -u/-file-mode-mask, and a new
GUC, file_mode_mask, to allow the default mode of files and directories
in the $PGDATA directory to be modified.
This obviously required mode changes in a number of places, so at the
same time the BasicOpenFile(), OpenTransientFile(), and
PathNameOpenFile() have been split into versions that either use the
default permissions or allow custom permissions. In the end there was
only one call to the custom permission version (be-fsstubs.c:505) for
all three variants.
The following three calls (at the least) need to be reviewed:
bin/pg_dump/pg_backup_directory.c:194
src/port/mkdtemp.c:190
bin/pg_basebackup.c:599:655:1399
And this call needs serious consideration:
bin/pg_rewind/file_ops.c:214
Besides that there should be tests to make sure the masks are working as
expected and these could be added to the initdb TAP tests, though no
mask tests exist at this time. Making sure all file operations produce
the correct modes would need to be placed in a new module, perhaps the
new backup tests proposed in [1]/messages/by-id/758e3fd1-45b4-5e28-75cd-e9e7f93a4c02@pgmasters.net.
Adam Brightwell developed the patch based on an initial concept by me
and Stephen Frost. I added the refactoring in fd.c and some additional
documentation.
This patch applies cleanly on 016c990 but may fare badly over time due
to the number of files modified.
--
-David
david@pgmasters.net
[1]: /messages/by-id/758e3fd1-45b4-5e28-75cd-e9e7f93a4c02@pgmasters.net
/messages/by-id/758e3fd1-45b4-5e28-75cd-e9e7f93a4c02@pgmasters.net
Attachments:
file-mode-mask-v1.patchtext/plain; charset=UTF-8; name=file-mode-mask-v1.patch; x-mac-creator=0; x-mac-type=0Download
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index 62dec87..98f8170 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -1858,8 +1858,7 @@ qtext_store(const char *query, int query_len,
*query_offset = off;
/* Now write the data into the successfully-reserved part of the file */
- fd = OpenTransientFile(PGSS_TEXT_FILE, O_RDWR | O_CREAT | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ fd = OpenTransientFile(PGSS_TEXT_FILE, O_RDWR | O_CREAT | PG_BINARY);
if (fd < 0)
goto error;
@@ -1923,7 +1922,7 @@ qtext_load_file(Size *buffer_size)
int fd;
struct stat stat;
- fd = OpenTransientFile(PGSS_TEXT_FILE, O_RDONLY | PG_BINARY, 0);
+ fd = OpenTransientFile(PGSS_TEXT_FILE, O_RDONLY | PG_BINARY);
if (fd < 0)
{
if (errno != ENOENT)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 1b390a2..2371878 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -812,6 +812,38 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-file-mode-mask" xreflabel="file_mode_mask">
+ <term><varname>file_mode_mask</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>file_mode_mask</> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Sets the file mode mask (umask) for the data directory. The parameter
+ value is expected to be a numeric mode specified in the format
+ accepted by the <function>chmod</function> and
+ <function>umask</function> system calls. (To use the customary octal
+ format the number must start with a <literal>0</literal> (zero).)
+ </para>
+
+ <para>
+ The default <literal>file_mode_mask</literal> is <literal>0077</literal>,
+ meaning that the only the database owner can read and write files in
+ the data directory. For example, setting the
+ <literal>file_mode_mask</literal> to <literal>0027</literal> would allow
+ any user in the same group as the database owner to read all database
+ files, which would be useful for producing a backup using a relatively
+ unprivileged user.
+ </para>
+
+ <para>
+ This parameter can only be set at server start.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-bonjour" xreflabel="bonjour">
<term><varname>bonjour</varname> (<type>boolean</type>)
<indexterm>
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index c7b283c..53a2acc 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -1010,8 +1010,7 @@ logical_rewrite_log_mapping(RewriteState state, TransactionId xid,
src->off = 0;
memcpy(src->path, path, sizeof(path));
src->vfd = PathNameOpenFile(path,
- O_CREAT | O_EXCL | O_WRONLY | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ O_CREAT | O_EXCL | O_WRONLY | PG_BINARY);
if (src->vfd < 0)
ereport(ERROR,
(errcode_for_file_access(),
@@ -1130,8 +1129,7 @@ heap_xlog_logical_rewrite(XLogReaderState *r)
xlrec->mapped_xid, XLogRecGetXid(r));
fd = OpenTransientFile(path,
- O_CREAT | O_WRONLY | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ O_CREAT | O_WRONLY | PG_BINARY);
if (fd < 0)
ereport(ERROR,
(errcode_for_file_access(),
@@ -1249,7 +1247,7 @@ CheckPointLogicalRewriteHeap(void)
}
else
{
- int fd = OpenTransientFile(path, O_RDONLY | PG_BINARY, 0);
+ int fd = OpenTransientFile(path, O_RDONLY | PG_BINARY);
/*
* The file cannot vanish due to concurrency since this function
diff --git a/src/backend/access/transam/slru.c b/src/backend/access/transam/slru.c
index a66ef5c..f385b79 100644
--- a/src/backend/access/transam/slru.c
+++ b/src/backend/access/transam/slru.c
@@ -594,7 +594,7 @@ SimpleLruDoesPhysicalPageExist(SlruCtl ctl, int pageno)
SlruFileName(ctl, path, segno);
- fd = OpenTransientFile(path, O_RDWR | PG_BINARY, S_IRUSR | S_IWUSR);
+ fd = OpenTransientFile(path, O_RDWR | PG_BINARY);
if (fd < 0)
{
/* expected: file doesn't exist */
@@ -649,7 +649,7 @@ SlruPhysicalReadPage(SlruCtl ctl, int pageno, int slotno)
* SlruPhysicalWritePage). Hence, if we are InRecovery, allow the case
* where the file doesn't exist, and return zeroes instead.
*/
- fd = OpenTransientFile(path, O_RDWR | PG_BINARY, S_IRUSR | S_IWUSR);
+ fd = OpenTransientFile(path, O_RDWR | PG_BINARY);
if (fd < 0)
{
if (errno != ENOENT || !InRecovery)
@@ -796,8 +796,7 @@ SlruPhysicalWritePage(SlruCtl ctl, int pageno, int slotno, SlruFlush fdata)
* don't use O_EXCL or O_TRUNC or anything like that.
*/
SlruFileName(ctl, path, segno);
- fd = OpenTransientFile(path, O_RDWR | O_CREAT | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ fd = OpenTransientFile(path, O_RDWR | O_CREAT | PG_BINARY);
if (fd < 0)
{
slru_errcause = SLRU_OPEN_FAILED;
diff --git a/src/backend/access/transam/timeline.c b/src/backend/access/transam/timeline.c
index 1fdc591..baa766a 100644
--- a/src/backend/access/transam/timeline.c
+++ b/src/backend/access/transam/timeline.c
@@ -306,8 +306,7 @@ writeTimeLineHistory(TimeLineID newTLI, TimeLineID parentTLI,
unlink(tmppath);
/* do not use get_sync_bit() here --- want to fsync only at end of fill */
- fd = OpenTransientFile(tmppath, O_RDWR | O_CREAT | O_EXCL,
- S_IRUSR | S_IWUSR);
+ fd = OpenTransientFile(tmppath, O_RDWR | O_CREAT | O_EXCL);
if (fd < 0)
ereport(ERROR,
(errcode_for_file_access(),
@@ -324,7 +323,7 @@ writeTimeLineHistory(TimeLineID newTLI, TimeLineID parentTLI,
else
TLHistoryFilePath(path, parentTLI);
- srcfd = OpenTransientFile(path, O_RDONLY, 0);
+ srcfd = OpenTransientFile(path, O_RDONLY);
if (srcfd < 0)
{
if (errno != ENOENT)
@@ -452,8 +451,7 @@ writeTimeLineHistoryFile(TimeLineID tli, char *content, int size)
unlink(tmppath);
/* do not use get_sync_bit() here --- want to fsync only at end of fill */
- fd = OpenTransientFile(tmppath, O_RDWR | O_CREAT | O_EXCL,
- S_IRUSR | S_IWUSR);
+ fd = OpenTransientFile(tmppath, O_RDWR | O_CREAT | O_EXCL);
if (fd < 0)
ereport(ERROR,
(errcode_for_file_access(),
diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c
index 0a8edb9..1f02dd1 100644
--- a/src/backend/access/transam/twophase.c
+++ b/src/backend/access/transam/twophase.c
@@ -1151,7 +1151,7 @@ ReadTwoPhaseFile(TransactionId xid, bool give_warnings)
TwoPhaseFilePath(path, xid);
- fd = OpenTransientFile(path, O_RDONLY | PG_BINARY, 0);
+ fd = OpenTransientFile(path, O_RDONLY | PG_BINARY);
if (fd < 0)
{
if (give_warnings)
@@ -1533,8 +1533,7 @@ RecreateTwoPhaseFile(TransactionId xid, void *content, int len)
TwoPhaseFilePath(path, xid);
fd = OpenTransientFile(path,
- O_CREAT | O_TRUNC | O_WRONLY | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ O_CREAT | O_TRUNC | O_WRONLY | PG_BINARY);
if (fd < 0)
ereport(ERROR,
(errcode_for_file_access(),
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 8973583..eed7dda 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -3156,8 +3156,7 @@ XLogFileInit(XLogSegNo logsegno, bool *use_existent, bool use_lock)
*/
if (*use_existent)
{
- fd = BasicOpenFile(path, O_RDWR | PG_BINARY | get_sync_bit(sync_method),
- S_IRUSR | S_IWUSR);
+ fd = BasicOpenFile(path, O_RDWR | PG_BINARY | get_sync_bit(sync_method));
if (fd < 0)
{
if (errno != ENOENT)
@@ -3182,8 +3181,7 @@ XLogFileInit(XLogSegNo logsegno, bool *use_existent, bool use_lock)
unlink(tmppath);
/* do not use get_sync_bit() here --- want to fsync only at end of fill */
- fd = BasicOpenFile(tmppath, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ fd = BasicOpenFile(tmppath, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
if (fd < 0)
ereport(ERROR,
(errcode_for_file_access(),
@@ -3275,8 +3273,7 @@ XLogFileInit(XLogSegNo logsegno, bool *use_existent, bool use_lock)
*use_existent = false;
/* Now open original target segment (might not be file I just made) */
- fd = BasicOpenFile(path, O_RDWR | PG_BINARY | get_sync_bit(sync_method),
- S_IRUSR | S_IWUSR);
+ fd = BasicOpenFile(path, O_RDWR | PG_BINARY | get_sync_bit(sync_method));
if (fd < 0)
ereport(ERROR,
(errcode_for_file_access(),
@@ -3317,7 +3314,7 @@ XLogFileCopy(XLogSegNo destsegno, TimeLineID srcTLI, XLogSegNo srcsegno,
* Open the source file
*/
XLogFilePath(path, srcTLI, srcsegno);
- srcfd = OpenTransientFile(path, O_RDONLY | PG_BINARY, 0);
+ srcfd = OpenTransientFile(path, O_RDONLY | PG_BINARY);
if (srcfd < 0)
ereport(ERROR,
(errcode_for_file_access(),
@@ -3331,8 +3328,7 @@ XLogFileCopy(XLogSegNo destsegno, TimeLineID srcTLI, XLogSegNo srcsegno,
unlink(tmppath);
/* do not use get_sync_bit() here --- want to fsync only at end of fill */
- fd = OpenTransientFile(tmppath, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ fd = OpenTransientFile(tmppath, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
if (fd < 0)
ereport(ERROR,
(errcode_for_file_access(),
@@ -3504,8 +3500,7 @@ XLogFileOpen(XLogSegNo segno)
XLogFilePath(path, ThisTimeLineID, segno);
- fd = BasicOpenFile(path, O_RDWR | PG_BINARY | get_sync_bit(sync_method),
- S_IRUSR | S_IWUSR);
+ fd = BasicOpenFile(path, O_RDWR | PG_BINARY | get_sync_bit(sync_method));
if (fd < 0)
ereport(PANIC,
(errcode_for_file_access(),
@@ -3571,7 +3566,7 @@ XLogFileRead(XLogSegNo segno, int emode, TimeLineID tli,
snprintf(path, MAXPGPATH, XLOGDIR "/%s", xlogfname);
}
- fd = BasicOpenFile(path, O_RDONLY | PG_BINARY, 0);
+ fd = BasicOpenFile(path, O_RDONLY | PG_BINARY);
if (fd >= 0)
{
/* Success! */
@@ -4065,7 +4060,7 @@ ValidateXLOGDirectoryStructure(void)
{
ereport(LOG,
(errmsg("creating missing WAL directory \"%s\"", path)));
- if (mkdir(path, S_IRWXU) < 0)
+ if (mkdir(path, PG_DEFAULT_DIR_MODE) < 0)
ereport(FATAL,
(errmsg("could not create missing directory \"%s\": %m",
path)));
@@ -4404,8 +4399,7 @@ WriteControlFile(void)
memcpy(buffer, ControlFile, sizeof(ControlFileData));
fd = BasicOpenFile(XLOG_CONTROL_FILE,
- O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
if (fd < 0)
ereport(PANIC,
(errcode_for_file_access(),
@@ -4444,8 +4438,7 @@ ReadControlFile(void)
* Read data...
*/
fd = BasicOpenFile(XLOG_CONTROL_FILE,
- O_RDWR | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ O_RDWR | PG_BINARY);
if (fd < 0)
ereport(PANIC,
(errcode_for_file_access(),
@@ -4624,8 +4617,7 @@ UpdateControlFile(void)
FIN_CRC32C(ControlFile->crc);
fd = BasicOpenFile(XLOG_CONTROL_FILE,
- O_RDWR | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ O_RDWR | PG_BINARY);
if (fd < 0)
ereport(PANIC,
(errcode_for_file_access(),
diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c
index 8b99b78..96ac73f 100644
--- a/src/backend/access/transam/xlogutils.c
+++ b/src/backend/access/transam/xlogutils.c
@@ -687,7 +687,7 @@ XLogRead(char *buf, TimeLineID tli, XLogRecPtr startptr, Size count)
XLogFilePath(path, tli, sendSegNo);
- sendFile = BasicOpenFile(path, O_RDONLY | PG_BINARY, 0);
+ sendFile = BasicOpenFile(path, O_RDONLY | PG_BINARY);
if (sendFile < 0)
{
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 11ee536..578b46b 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -428,7 +428,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
/* Check for existing file of same name */
rpath = relpath(rnode, MAIN_FORKNUM);
- fd = BasicOpenFile(rpath, O_RDONLY | PG_BINARY, 0);
+ fd = BasicOpenFile(rpath, O_RDONLY | PG_BINARY);
if (fd >= 0)
{
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index f9c2620..312ac07 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -151,7 +151,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
else
{
/* Directory creation failed? */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (mkdir(dir, PG_DEFAULT_DIR_MODE) < 0)
{
char *parentdir;
@@ -173,7 +173,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
get_parent_directory(parentdir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (mkdir(parentdir, PG_DEFAULT_DIR_MODE) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -184,7 +184,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
parentdir = pstrdup(dir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (mkdir(parentdir, PG_DEFAULT_DIR_MODE) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -192,7 +192,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
pfree(parentdir);
/* Create database directory */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (mkdir(dir, PG_DEFAULT_DIR_MODE) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -610,7 +610,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* The creation of the version directory prevents more than one tablespace
* in a single location.
*/
- if (mkdir(location_with_version_dir, S_IRWXU) < 0)
+ if (mkdir(location_with_version_dir, PG_DEFAULT_DIR_MODE) < 0)
{
if (errno == EEXIST)
ereport(ERROR,
diff --git a/src/backend/libpq/be-fsstubs.c b/src/backend/libpq/be-fsstubs.c
index f537aff..c0c8d94 100644
--- a/src/backend/libpq/be-fsstubs.c
+++ b/src/backend/libpq/be-fsstubs.c
@@ -462,7 +462,7 @@ lo_import_internal(text *filename, Oid lobjOid)
* open the file to be read in
*/
text_to_cstring_buffer(filename, fnamebuf, sizeof(fnamebuf));
- fd = OpenTransientFile(fnamebuf, O_RDONLY | PG_BINARY, S_IRWXU);
+ fd = OpenTransientFile(fnamebuf, O_RDONLY | PG_BINARY);
if (fd < 0)
ereport(ERROR,
(errcode_for_file_access(),
@@ -538,8 +538,8 @@ be_lo_export(PG_FUNCTION_ARGS)
*/
text_to_cstring_buffer(filename, fnamebuf, sizeof(fnamebuf));
oumask = umask(S_IWGRP | S_IWOTH);
- fd = OpenTransientFile(fnamebuf, O_CREAT | O_WRONLY | O_TRUNC | PG_BINARY,
- S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ fd = OpenTransientFilePerm(fnamebuf, O_CREAT | O_WRONLY | O_TRUNC | PG_BINARY,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
umask(oumask);
if (fd < 0)
ereport(ERROR,
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 271c492..bfb6c7d 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -579,9 +579,10 @@ PostmasterMain(int argc, char *argv[])
IsPostmasterEnvironment = true;
/*
- * for security, no dir or file created can be group or other accessible
+ * Initially set most restrictive umask in case any files are
+ * accidentally created before file_mode_mask is loaded from GUC.
*/
- umask(S_IRWXG | S_IRWXO);
+ umask(file_mode_mask);
/*
* Initialize random(3) so we don't get the same values in every run.
@@ -854,6 +855,9 @@ PostmasterMain(int argc, char *argv[])
ExitPostmaster(0);
}
+ /* Reset the file mode creation mask now that GUC has been loaded. */
+ umask(file_mode_mask);
+
/* Verify that DataDir looks reasonable */
checkDataDir();
@@ -1489,25 +1493,20 @@ checkDataDir(void)
#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.
+ * Check if the directory has the correct permissions. If not, then reject.
*
* 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))
+ if (stat_buf.st_mode & file_mode_mask)
ereport(FATAL,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("data directory \"%s\" has group or world access",
+ errmsg("data directory \"%s\" has incorrect permissions",
DataDir),
- errdetail("Permissions should be u=rwx (0700).")));
+ errdetail("Permissions should be (%04o).",
+ (PG_DEFAULT_DIR_MODE & ~file_mode_mask))));
#endif
/* Look for PG_VERSION before looking for pg_control */
@@ -4393,7 +4392,7 @@ internal_forkexec(int argc, char *argv[], Port *port)
* As in OpenTemporaryFileInTablespace, try to make the temp-file
* directory
*/
- mkdir(PG_TEMP_FILES_DIR, S_IRWXU);
+ mkdir(PG_TEMP_FILES_DIR, PG_DEFAULT_DIR_MODE);
fp = AllocateFile(tmpfilename, PG_BINARY_W);
if (!fp)
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index 13a0301..684944d 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -41,6 +41,7 @@
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
#include "storage/dsm.h"
+#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pg_shmem.h"
@@ -320,7 +321,7 @@ SysLoggerMain(int argc, char *argv[])
/*
* Also, create new directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ mkdir(Log_directory, PG_DEFAULT_DIR_MODE);
}
if (strcmp(Log_filename, currentLogFilename) != 0)
{
@@ -555,7 +556,7 @@ SysLogger_Start(void)
/*
* Create log directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ mkdir(Log_directory, PG_DEFAULT_DIR_MODE);
/*
* The initial logfile is created right in the postmaster, to verify that
diff --git a/src/backend/replication/logical/origin.c b/src/backend/replication/logical/origin.c
index bf84c68..51d54ab 100644
--- a/src/backend/replication/logical/origin.c
+++ b/src/backend/replication/logical/origin.c
@@ -527,8 +527,7 @@ CheckPointReplicationOrigin(void)
* CheckpointLock.
*/
tmpfd = OpenTransientFile((char *) tmppath,
- O_CREAT | O_EXCL | O_WRONLY | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ O_CREAT | O_EXCL | O_WRONLY | PG_BINARY);
if (tmpfd < 0)
ereport(PANIC,
(errcode_for_file_access(),
@@ -637,7 +636,7 @@ StartupReplicationOrigin(void)
elog(DEBUG2, "starting up replication origin progress state");
- fd = OpenTransientFile((char *) path, O_RDONLY | PG_BINARY, 0);
+ fd = OpenTransientFile((char *) path, O_RDONLY | PG_BINARY);
/*
* might have had max_replication_slots == 0 last run, or we just brought
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index 8aac670..af7edea 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -2103,8 +2103,7 @@ ReorderBufferSerializeTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
/* open segment, create it if necessary */
fd = OpenTransientFile(path,
- O_CREAT | O_WRONLY | O_APPEND | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ O_CREAT | O_WRONLY | O_APPEND | PG_BINARY);
if (fd < 0)
ereport(ERROR,
@@ -2345,7 +2344,7 @@ ReorderBufferRestoreChanges(ReorderBuffer *rb, ReorderBufferTXN *txn,
NameStr(MyReplicationSlot->data.name), txn->xid,
(uint32) (recptr >> 32), (uint32) recptr);
- *fd = OpenTransientFile(path, O_RDONLY | PG_BINARY, 0);
+ *fd = OpenTransientFile(path, O_RDONLY | PG_BINARY);
if (*fd < 0 && errno == ENOENT)
{
*fd = -1;
@@ -3030,7 +3029,7 @@ ApplyLogicalMappingFile(HTAB *tuplecid_data, Oid relid, const char *fname)
LogicalRewriteMappingData map;
sprintf(path, "pg_logical/mappings/%s", fname);
- fd = OpenTransientFile(path, O_RDONLY | PG_BINARY, 0);
+ fd = OpenTransientFile(path, O_RDONLY | PG_BINARY);
if (fd < 0)
ereport(ERROR,
(errcode_for_file_access(),
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index 6f19cdc..961b0f9 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -1574,8 +1574,7 @@ SnapBuildSerialize(SnapBuild *builder, XLogRecPtr lsn)
/* we have valid data now, open tempfile and write it there */
fd = OpenTransientFile(tmppath,
- O_CREAT | O_EXCL | O_WRONLY | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ O_CREAT | O_EXCL | O_WRONLY | PG_BINARY);
if (fd < 0)
ereport(ERROR,
(errmsg("could not open file \"%s\": %m", path)));
@@ -1655,7 +1654,7 @@ SnapBuildRestore(SnapBuild *builder, XLogRecPtr lsn)
sprintf(path, "pg_logical/snapshots/%X-%X.snap",
(uint32) (lsn >> 32), (uint32) lsn);
- fd = OpenTransientFile(path, O_RDONLY | PG_BINARY, 0);
+ fd = OpenTransientFile(path, O_RDONLY | PG_BINARY);
if (fd < 0 && errno == ENOENT)
return false;
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 10d69d0..7e8b50e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -1011,7 +1011,7 @@ CreateSlotOnDisk(ReplicationSlot *slot)
rmtree(tmppath, true);
/* Create and fsync the temporary slot directory. */
- if (mkdir(tmppath, S_IRWXU) < 0)
+ if (mkdir(tmppath, PG_DEFAULT_DIR_MODE) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -1072,9 +1072,7 @@ SaveSlotToPath(ReplicationSlot *slot, const char *dir, int elevel)
sprintf(tmppath, "%s/state.tmp", dir);
sprintf(path, "%s/state", dir);
- fd = OpenTransientFile(tmppath,
- O_CREAT | O_EXCL | O_WRONLY | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ fd = OpenTransientFile(tmppath, O_CREAT | O_EXCL | O_WRONLY | PG_BINARY);
if (fd < 0)
{
ereport(elevel,
@@ -1187,7 +1185,7 @@ RestoreSlotFromDisk(const char *name)
elog(DEBUG1, "restoring replication slot from \"%s\"", path);
- fd = OpenTransientFile(path, O_RDWR | PG_BINARY, 0);
+ fd = OpenTransientFile(path, O_RDWR | PG_BINARY);
/*
* We do not need to handle this as we are rename()ing the directory into
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 9cf9eb0..e1af7a3 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -437,7 +437,7 @@ SendTimeLineHistory(TimeLineHistoryCmd *cmd)
pq_sendint(&buf, len, 4); /* col1 len */
pq_sendbytes(&buf, histfname, len);
- fd = OpenTransientFile(path, O_RDONLY | PG_BINARY, 0666);
+ fd = OpenTransientFile(path, O_RDONLY | PG_BINARY);
if (fd < 0)
ereport(ERROR,
(errcode_for_file_access(),
@@ -2031,7 +2031,7 @@ retry:
XLogFilePath(path, curFileTimeLine, sendSegNo);
- sendFile = BasicOpenFile(path, O_RDONLY | PG_BINARY, 0);
+ sendFile = BasicOpenFile(path, O_RDONLY | PG_BINARY);
if (sendFile < 0)
{
/*
diff --git a/src/backend/storage/file/copydir.c b/src/backend/storage/file/copydir.c
index 101da47..0e078f5 100644
--- a/src/backend/storage/file/copydir.c
+++ b/src/backend/storage/file/copydir.c
@@ -41,7 +41,7 @@ copydir(char *fromdir, char *todir, bool recurse)
char fromfile[MAXPGPATH];
char tofile[MAXPGPATH];
- if (mkdir(todir, S_IRWXU) != 0)
+ if (mkdir(todir, PG_DEFAULT_DIR_MODE) != 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m", todir)));
@@ -148,14 +148,13 @@ copy_file(char *fromfile, char *tofile)
/*
* Open the files
*/
- srcfd = OpenTransientFile(fromfile, O_RDONLY | PG_BINARY, 0);
+ srcfd = OpenTransientFile(fromfile, O_RDONLY | PG_BINARY);
if (srcfd < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not open file \"%s\": %m", fromfile)));
- dstfd = OpenTransientFile(tofile, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ dstfd = OpenTransientFile(tofile, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
if (dstfd < 0)
ereport(ERROR,
(errcode_for_file_access(),
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index fd02fc0..46caf90 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -138,6 +138,8 @@ int max_files_per_process = 1000;
*/
int max_safe_fds = 32; /* default if not changed */
+/* The file mode creation mask for creating new files and directories. */
+int file_mode_mask = PG_DEFAULT_MODE_MASK;
/* Debugging.... */
@@ -604,7 +606,7 @@ durable_rename(const char *oldfile, const char *newfile, int elevel)
if (fsync_fname_ext(oldfile, false, false, elevel) != 0)
return -1;
- fd = OpenTransientFile((char *) newfile, PG_BINARY | O_RDWR, 0);
+ fd = OpenTransientFile((char *) newfile, PG_BINARY | O_RDWR);
if (fd < 0)
{
if (errno != ENOENT)
@@ -896,7 +898,7 @@ set_max_safe_fds(void)
* this module wouldn't have any open files to close at that point anyway.
*/
int
-BasicOpenFile(FileName fileName, int fileFlags, int fileMode)
+BasicOpenFilePerm(FileName fileName, int fileFlags, int fileMode)
{
int fd;
@@ -922,6 +924,12 @@ tryAgain:
return -1; /* failure */
}
+int
+BasicOpenFile(FileName fileName, int fileFlags)
+{
+ return BasicOpenFilePerm(fileName, fileFlags, PG_DEFAULT_FILE_MODE);
+}
+
#if defined(FDDEBUG)
static void
@@ -1047,8 +1055,8 @@ LruInsert(File file)
* overall system file table being full. So, be prepared to release
* another FD if necessary...
*/
- vfdP->fd = BasicOpenFile(vfdP->fileName, vfdP->fileFlags,
- vfdP->fileMode);
+ vfdP->fd = BasicOpenFilePerm(vfdP->fileName, vfdP->fileFlags,
+ vfdP->fileMode);
if (vfdP->fd < 0)
{
DO_DB(elog(LOG, "re-open failed: %m"));
@@ -1263,13 +1271,19 @@ FileInvalidate(File file)
* (which should always be $PGDATA when this code is running).
*/
File
-PathNameOpenFile(FileName fileName, int fileFlags, int fileMode)
+PathNameOpenFile(FileName fileName, int fileFlags)
+{
+ return PathNameOpenFilePerm(fileName, fileFlags, PG_DEFAULT_FILE_MODE);
+}
+
+File
+PathNameOpenFilePerm(FileName fileName, int fileFlags, int fileMode)
{
char *fnamecopy;
File file;
Vfd *vfdP;
- DO_DB(elog(LOG, "PathNameOpenFile: %s %x %o",
+ DO_DB(elog(LOG, "PathNameOpenFilePerm: %s %x %o",
fileName, fileFlags, fileMode));
/*
@@ -1287,7 +1301,7 @@ PathNameOpenFile(FileName fileName, int fileFlags, int fileMode)
/* Close excess kernel FDs. */
ReleaseLruFiles();
- vfdP->fd = BasicOpenFile(fileName, fileFlags, fileMode);
+ vfdP->fd = BasicOpenFilePerm(fileName, fileFlags, fileMode);
if (vfdP->fd < 0)
{
@@ -1424,8 +1438,7 @@ OpenTemporaryFileInTablespace(Oid tblspcOid, bool rejectError)
* temp file that can be reused.
*/
file = PathNameOpenFile(tempfilepath,
- O_RDWR | O_CREAT | O_TRUNC | PG_BINARY,
- 0600);
+ O_RDWR | O_CREAT | O_TRUNC | PG_BINARY);
if (file <= 0)
{
/*
@@ -1436,11 +1449,10 @@ OpenTemporaryFileInTablespace(Oid tblspcOid, bool rejectError)
* just did the same thing. If it doesn't work then we'll bomb out on
* the second create attempt, instead.
*/
- mkdir(tempdirpath, S_IRWXU);
+ mkdir(tempdirpath, PG_DEFAULT_DIR_MODE);
file = PathNameOpenFile(tempfilepath,
- O_RDWR | O_CREAT | O_TRUNC | PG_BINARY,
- 0600);
+ O_RDWR | O_CREAT | O_TRUNC | PG_BINARY);
if (file <= 0 && rejectError)
elog(ERROR, "could not create temporary file \"%s\": %m",
tempfilepath);
@@ -2090,7 +2102,7 @@ TryAgain:
* Like AllocateFile, but returns an unbuffered fd like open(2)
*/
int
-OpenTransientFile(FileName fileName, int fileFlags, int fileMode)
+OpenTransientFilePerm(FileName fileName, int fileFlags, int fileMode)
{
int fd;
@@ -2107,7 +2119,7 @@ OpenTransientFile(FileName fileName, int fileFlags, int fileMode)
/* Close excess kernel FDs. */
ReleaseLruFiles();
- fd = BasicOpenFile(fileName, fileFlags, fileMode);
+ fd = BasicOpenFilePerm(fileName, fileFlags, fileMode);
if (fd >= 0)
{
@@ -2124,6 +2136,12 @@ OpenTransientFile(FileName fileName, int fileFlags, int fileMode)
return -1; /* failure */
}
+int
+OpenTransientFile(FileName fileName, int fileFlags)
+{
+ return OpenTransientFilePerm(fileName, fileFlags, PG_DEFAULT_FILE_MODE);
+}
+
/*
* Routines that want to initiate a pipe stream should use OpenPipeStream
* rather than plain popen(). This lets fd.c deal with freeing FDs if
@@ -3030,7 +3048,7 @@ pre_sync_fname(const char *fname, bool isdir, int elevel)
if (isdir)
return;
- fd = OpenTransientFile((char *) fname, O_RDONLY | PG_BINARY, 0);
+ fd = OpenTransientFile((char *) fname, O_RDONLY | PG_BINARY);
if (fd < 0)
{
@@ -3090,7 +3108,7 @@ fsync_fname_ext(const char *fname, bool isdir, bool ignore_perm, int elevel)
else
flags |= O_RDONLY;
- fd = OpenTransientFile((char *) fname, flags, 0);
+ fd = OpenTransientFile((char *) fname, flags);
/*
* Some OSs don't allow us to open directories at all (Windows returns
diff --git a/src/backend/storage/ipc/dsm_impl.c b/src/backend/storage/ipc/dsm_impl.c
index b2c9cdc..80ee4fa 100644
--- a/src/backend/storage/ipc/dsm_impl.c
+++ b/src/backend/storage/ipc/dsm_impl.c
@@ -283,7 +283,7 @@ dsm_impl_posix(dsm_op op, dsm_handle handle, Size request_size,
* returning.
*/
flags = O_RDWR | (op == DSM_OP_CREATE ? O_CREAT | O_EXCL : 0);
- if ((fd = shm_open(name, flags, 0600)) == -1)
+ if ((fd = shm_open(name, flags, PG_DEFAULT_FILE_MODE)) == -1)
{
if (errno != EEXIST)
ereport(elevel,
@@ -834,7 +834,7 @@ dsm_impl_mmap(dsm_op op, dsm_handle handle, Size request_size,
/* Create new segment or open an existing one for attach or resize. */
flags = O_RDWR | (op == DSM_OP_CREATE ? O_CREAT | O_EXCL : 0);
- if ((fd = OpenTransientFile(name, flags, 0600)) == -1)
+ if ((fd = OpenTransientFile(name, flags)) == -1)
{
if (errno != EEXIST)
ereport(elevel,
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index ffd91f5..dfc5b75 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -28,6 +28,7 @@
#include "postmaster/autovacuum.h"
#endif
#include "storage/dsm.h"
+#include "storage/fd.h"
#include "storage/ipc.h"
#include "tcop/tcopprot.h"
@@ -132,8 +133,8 @@ proc_exit(int code)
else
snprintf(gprofDirName, 32, "gprof/%d", (int) getpid());
- mkdir("gprof", S_IRWXU | S_IRWXG | S_IRWXO);
- mkdir(gprofDirName, S_IRWXU | S_IRWXG | S_IRWXO);
+ mkdir("gprof", PG_DEFAULT_DIR_MODE);
+ mkdir(gprofDirName, PG_DEFAULT_DIR_MODE);
chdir(gprofDirName);
}
#endif
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index 6c17b54..cf951a2 100644
--- a/src/backend/storage/smgr/md.c
+++ b/src/backend/storage/smgr/md.c
@@ -303,7 +303,7 @@ mdcreate(SMgrRelation reln, ForkNumber forkNum, bool isRedo)
path = relpath(reln->smgr_rnode, forkNum);
- fd = PathNameOpenFile(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY, 0600);
+ fd = PathNameOpenFile(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
if (fd < 0)
{
@@ -316,7 +316,7 @@ mdcreate(SMgrRelation reln, ForkNumber forkNum, bool isRedo)
* already, even if isRedo is not set. (See also mdopen)
*/
if (isRedo || IsBootstrapProcessingMode())
- fd = PathNameOpenFile(path, O_RDWR | PG_BINARY, 0600);
+ fd = PathNameOpenFile(path, O_RDWR | PG_BINARY);
if (fd < 0)
{
/* be sure to report the error reported by create, not open */
@@ -429,7 +429,7 @@ mdunlinkfork(RelFileNodeBackend rnode, ForkNumber forkNum, bool isRedo)
/* truncate(2) would be easier here, but Windows hasn't got it */
int fd;
- fd = OpenTransientFile(path, O_RDWR | PG_BINARY, 0);
+ fd = OpenTransientFile(path, O_RDWR | PG_BINARY);
if (fd >= 0)
{
int save_errno;
@@ -582,7 +582,7 @@ mdopen(SMgrRelation reln, ForkNumber forknum, int behavior)
path = relpath(reln->smgr_rnode, forknum);
- fd = PathNameOpenFile(path, O_RDWR | PG_BINARY, 0600);
+ fd = PathNameOpenFile(path, O_RDWR | PG_BINARY);
if (fd < 0)
{
@@ -593,7 +593,7 @@ mdopen(SMgrRelation reln, ForkNumber forknum, int behavior)
* substitute for mdcreate() in bootstrap mode only. (See mdcreate)
*/
if (IsBootstrapProcessingMode())
- fd = PathNameOpenFile(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY, 0600);
+ fd = PathNameOpenFile(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
if (fd < 0)
{
if ((behavior & EXTENSION_RETURN_NULL) &&
@@ -1779,7 +1779,7 @@ _mdfd_openseg(SMgrRelation reln, ForkNumber forknum, BlockNumber segno,
fullpath = _mdfd_segpath(reln, forknum, segno);
/* open the file */
- fd = PathNameOpenFile(fullpath, O_RDWR | PG_BINARY | oflags, 0600);
+ fd = PathNameOpenFile(fullpath, O_RDWR | PG_BINARY | oflags);
pfree(fullpath);
diff --git a/src/backend/utils/cache/relmapper.c b/src/backend/utils/cache/relmapper.c
index c9d6e44..fe68b5b 100644
--- a/src/backend/utils/cache/relmapper.c
+++ b/src/backend/utils/cache/relmapper.c
@@ -643,8 +643,7 @@ load_relmap_file(bool shared)
}
/* Read data ... */
- fd = OpenTransientFile(mapfilename,
- O_RDONLY | PG_BINARY, S_IRUSR | S_IWUSR);
+ fd = OpenTransientFile(mapfilename, O_RDONLY | PG_BINARY);
if (fd < 0)
ereport(FATAL,
(errcode_for_file_access(),
@@ -742,9 +741,7 @@ write_relmap_file(bool shared, RelMapFile *newmap,
realmap = &local_map;
}
- fd = OpenTransientFile(mapfilename,
- O_WRONLY | O_CREAT | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ fd = OpenTransientFile(mapfilename, O_WRONLY | O_CREAT | PG_BINARY);
if (fd < 0)
ereport(ERROR,
(errcode_for_file_access(),
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 0707f66..bb3e471 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -190,6 +190,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_file_mode_mask(void);
/* Private functions in guc-file.l that need to be called from guc.c */
static ConfigVariable *ProcessConfigFileInternal(GucContext context,
@@ -2841,6 +2842,18 @@ static struct config_int ConfigureNamesInt[] =
4096, 64, MAX_KILOBYTES,
NULL, NULL, NULL
},
+ {
+ {"file_mode_mask", PGC_POSTMASTER, UNGROUPED,
+ gettext_noop("Sets the file mode creation mask for the backend process."),
+ gettext_noop("The parameter value is expected to be 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).)")
+ },
+ &file_mode_mask,
+ PG_DEFAULT_MODE_MASK, 0000, 0777,
+ NULL, NULL, show_file_mode_mask
+ },
/* End-of-list marker */
{
@@ -7186,8 +7199,7 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
* truncate and reuse it.
*/
Tmpfd = BasicOpenFile(AutoConfTmpFileName,
- O_CREAT | O_RDWR | O_TRUNC,
- S_IRUSR | S_IWUSR);
+ O_CREAT | O_RDWR | O_TRUNC);
if (Tmpfd < 0)
ereport(ERROR,
(errcode_for_file_access(),
@@ -10444,6 +10456,15 @@ show_unix_socket_permissions(void)
}
static const char *
+show_file_mode_mask(void)
+{
+ static char buf[8];
+
+ snprintf(buf, sizeof(buf), "%04o", file_mode_mask);
+ return buf;
+}
+
+static const char *
show_log_file_mode(void)
{
static char buf[8];
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 157d775..81fd9da 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -49,6 +49,7 @@
#external_pid_file = '' # write an extra PID file
# (change requires restart)
+#file_mode_mask = 0077
#------------------------------------------------------------------------------
# CONNECTIONS AND AUTHENTICATION
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 1ed0d20..283ec09 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -110,6 +110,15 @@ static const char *const auth_methods_local[] = {
NULL
};
+/* File mode to use with chmod on files. */
+#define PG_DEFAULT_FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)
+
+/* File mode to use with chmod on directories. */
+#define PG_DEFAULT_DIR_MODE (S_IRWXU | S_IRWXG | S_IRWXO)
+
+/* Default file mode creation mask. */
+#define PG_DEFAULT_MODE_MASK (S_IRWXG | S_IRWXO)
+
/*
* these values are passed in by makefile defines
*/
@@ -139,6 +148,7 @@ static bool sync_only = false;
static bool show_setting = false;
static bool data_checksums = false;
static char *xlog_dir = "";
+static mode_t file_mode_mask = PG_DEFAULT_MODE_MASK;
/* internal vars */
@@ -1095,6 +1105,11 @@ setup_config(void)
conflines = replace_token(conflines, "#dynamic_shared_memory_type = posix",
repltok);
+ snprintf(repltok, sizeof(repltok), "file_mode_mask = %04o",
+ file_mode_mask);
+ conflines = replace_token(conflines, "#file_mode_mask = 0077",
+ repltok);
+
#if DEFAULT_BACKEND_FLUSH_AFTER > 0
snprintf(repltok, sizeof(repltok), "#backend_flush_after = %dkB",
DEFAULT_BACKEND_FLUSH_AFTER * (BLCKSZ / 1024));
@@ -1131,7 +1146,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, (PG_DEFAULT_FILE_MODE & ~file_mode_mask)) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1151,7 +1166,7 @@ setup_config(void)
sprintf(path, "%s/postgresql.auto.conf", pg_data);
writefile(path, autoconflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, (PG_DEFAULT_FILE_MODE & ~file_mode_mask)) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1235,7 +1250,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_hba.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, (PG_DEFAULT_FILE_MODE & ~file_mode_mask)) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1251,7 +1266,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_ident.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, (PG_DEFAULT_FILE_MODE & ~file_mode_mask)) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -2243,6 +2258,7 @@ usage(const char *progname)
printf(_(" --auth-local=METHOD default authentication method for local-socket connections\n"));
printf(_(" [-D, --pgdata=]DATADIR location for this database cluster\n"));
printf(_(" -E, --encoding=ENCODING set default encoding for new databases\n"));
+ printf(_(" -u, --file-mode-mask=MASK set default file mode creation mask (umask)\n"));
printf(_(" --locale=LOCALE set default locale for new databases\n"));
printf(_(" --lc-collate=, --lc-ctype=, --lc-messages=LOCALE\n"
" --lc-monetary=, --lc-numeric=, --lc-time=LOCALE\n"
@@ -2624,7 +2640,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (pg_mkdir_p(pg_data, S_IRWXU) != 0)
+ if (pg_mkdir_p(pg_data, PG_DEFAULT_DIR_MODE) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2642,7 +2658,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (chmod(pg_data, S_IRWXU) != 0)
+ if (chmod(pg_data, (PG_DEFAULT_DIR_MODE & ~file_mode_mask)) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2710,7 +2726,8 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (pg_mkdir_p(xlog_dir, S_IRWXU) != 0)
+ printf("MASK: %04o\n", file_mode_mask);
+ if (pg_mkdir_p(xlog_dir, PG_DEFAULT_DIR_MODE) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2728,7 +2745,8 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (chmod(xlog_dir, S_IRWXU) != 0)
+ printf("MASK: %04o\n", file_mode_mask);
+ if (chmod(xlog_dir, (PG_DEFAULT_DIR_MODE & ~file_mode_mask)) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2778,7 +2796,7 @@ create_xlog_or_symlink(void)
else
{
/* Without -X option, just make the subdirectory normally */
- if (mkdir(subdirloc, S_IRWXU) < 0)
+ if (mkdir(subdirloc, PG_DEFAULT_DIR_MODE) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, subdirloc, strerror(errno));
@@ -2814,7 +2832,9 @@ initialize_data_directory(void)
setup_signals();
- umask(S_IRWXG | S_IRWXO);
+ /* Set the file mode creation mask. */
+ umask(file_mode_mask);
+ printf(_("Initializing with file mode creation mask: %04o\n"), file_mode_mask);
create_data_directory();
@@ -2834,7 +2854,7 @@ initialize_data_directory(void)
* The parent directory already exists, so we only need mkdir() not
* pg_mkdir_p() here, which avoids some failure modes; cf bug #13853.
*/
- if (mkdir(path, S_IRWXU) < 0)
+ if (mkdir(path, PG_DEFAULT_DIR_MODE) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -2942,6 +2962,7 @@ main(int argc, char *argv[])
{"sync-only", no_argument, NULL, 'S'},
{"waldir", required_argument, NULL, 'X'},
{"data-checksums", no_argument, NULL, 'k'},
+ {"file-mode-mask", required_argument, NULL, 'u'},
{NULL, 0, NULL, 0}
};
@@ -2983,7 +3004,7 @@ main(int argc, char *argv[])
/* process command-line options */
- while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:", long_options, &option_index)) != -1)
+ while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:u:", long_options, &option_index)) != -1)
{
switch (c)
{
@@ -3074,6 +3095,9 @@ main(int argc, char *argv[])
case 'X':
xlog_dir = pg_strdup(optarg);
break;
+ case 'u':
+ file_mode_mask = strtoul(optarg, NULL, 8);
+ break;
default:
/* getopt_long already emitted a complaint */
fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 1a43a2c..6660b7b 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -22,7 +22,7 @@
* Use them for all file activity...
*
* File fd;
- * fd = PathNameOpenFile("foo", O_RDONLY, 0600);
+ * fd = PathNameOpenFile("foo", O_RDONLY);
*
* AllocateFile();
* FreeFile();
@@ -59,13 +59,21 @@ extern int max_files_per_process;
*/
extern int max_safe_fds;
+/* The file mode creation mask for creating new files and directories. */
+extern int file_mode_mask;
+
+/* Define default modes and masks. */
+#define PG_DEFAULT_FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)
+#define PG_DEFAULT_DIR_MODE (S_IRWXU | S_IRWXG | S_IRWXO)
+#define PG_DEFAULT_MODE_MASK (S_IRWXG | S_IRWXO)
/*
* prototypes for functions in fd.c
*/
/* Operations on virtual Files --- equivalent to Unix kernel file ops */
-extern File PathNameOpenFile(FileName fileName, int fileFlags, int fileMode);
+extern File PathNameOpenFilePerm(FileName fileName, int fileFlags, int fileMode);
+extern File PathNameOpenFile(FileName fileName, int fileFlags);
extern File OpenTemporaryFile(bool interXact);
extern void FileClose(File file);
extern int FilePrefetch(File file, off_t offset, int amount);
@@ -94,11 +102,13 @@ extern struct dirent *ReadDir(DIR *dir, const char *dirname);
extern int FreeDir(DIR *dir);
/* Operations to allow use of a plain kernel FD, with automatic cleanup */
-extern int OpenTransientFile(FileName fileName, int fileFlags, int fileMode);
+extern int OpenTransientFilePerm(FileName fileName, int fileFlags, int fileMode);
+extern int OpenTransientFile(FileName fileName, int fileFlags);
extern int CloseTransientFile(int fd);
/* If you've really really gotta have a plain kernel FD, use this */
-extern int BasicOpenFile(FileName fileName, int fileFlags, int fileMode);
+extern int BasicOpenFilePerm(FileName fileName, int fileFlags, int fileMode);
+extern int BasicOpenFile(FileName fileName, int fileFlags);
/* Miscellaneous support routines */
extern void InitFileAccess(void);
On 1 March 2017 at 01:58, David Steele <david@pgmasters.net> wrote:
PostgreSQL currently requires the file mode mask (umask) to be 0077.
However, this precludes the possibility of a user in the postgres group
performing a backup (or whatever). Now that
pg_start_backup()/pg_stop_backup() privileges can be delegated to an
unprivileged user, it makes sense to also allow a (relatively)
unprivileged user to perform the backup at the file system level as well.
+1
This patch introduces a new initdb param, -u/-file-mode-mask, and a new
GUC, file_mode_mask,
Why both initdb and at server start? Seems like initdb is OK, or in pg_control.
to allow the default mode of files and directories
in the $PGDATA directory to be modified.
Are you saying if this is changed all files/directories will be
changed to the new mode?
It seems like it would be annoying to have some files in one mode,
some in another.
--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Simon Riggs <simon@2ndquadrant.com> writes:
On 1 March 2017 at 01:58, David Steele <david@pgmasters.net> wrote:
PostgreSQL currently requires the file mode mask (umask) to be 0077.
However, this precludes the possibility of a user in the postgres group
performing a backup (or whatever). Now that
pg_start_backup()/pg_stop_backup() privileges can be delegated to an
unprivileged user, it makes sense to also allow a (relatively)
unprivileged user to perform the backup at the file system level as well.
+1
I'd ask what is the point, considering that we don't view "cp -a" as a
supported backup technique in the first place.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Mar 6, 2017 at 7:38 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Simon Riggs <simon@2ndquadrant.com> writes:
On 1 March 2017 at 01:58, David Steele <david@pgmasters.net> wrote:
PostgreSQL currently requires the file mode mask (umask) to be 0077.
However, this precludes the possibility of a user in the postgres group
performing a backup (or whatever). Now that
pg_start_backup()/pg_stop_backup() privileges can be delegated to an
unprivileged user, it makes sense to also allow a (relatively)
unprivileged user to perform the backup at the file system level as well.+1
I'd ask what is the point, considering that we don't view "cp -a" as a
supported backup technique in the first place.
/me is confused.
Surely the idea is that you'd like an unprivileged database user to
run pg_start_backup(), an operating system user that can read but not
write the database files to copy them, and then the unprivileged to
then run pg_stop_backup(). I have no opinion on the patch, but I
support the goal. As I said on the surprisingly-controversial thread
about ripping out hard-coded superuser checks, reducing the level of
privilege which someone must have in order to perform a necessary
operation leads to better security. An exclusive backup taken via the
filesystem (probably not via cp, but say via tar or cpio) inevitably
requires the backup user to be able to read the entire cluster
directory, but it doesn't inherently require the backup user to be
able to write the cluster directory.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Greetings,
* Simon Riggs (simon@2ndquadrant.com) wrote:
On 1 March 2017 at 01:58, David Steele <david@pgmasters.net> wrote:
PostgreSQL currently requires the file mode mask (umask) to be 0077.
However, this precludes the possibility of a user in the postgres group
performing a backup (or whatever). Now that
pg_start_backup()/pg_stop_backup() privileges can be delegated to an
unprivileged user, it makes sense to also allow a (relatively)
unprivileged user to perform the backup at the file system level as well.+1
This patch introduces a new initdb param, -u/-file-mode-mask, and a new
GUC, file_mode_mask,Why both initdb and at server start? Seems like initdb is OK, or in pg_control.
One could imagine someone wishing to change their mind regarding the
permissions after initdb, and for existing systems who may wish to move
to allowing group-read in an environment where that can be safely done
but don't wish to re-initdb.
to allow the default mode of files and directories
in the $PGDATA directory to be modified.Are you saying if this is changed all files/directories will be
changed to the new mode?
No, new files will be created with the new mode and existing files will
be allowed to have the mode set. Changing all of the existing files
didn't seem like something we should be trying to do at server start.
It seems like it would be annoying to have some files in one mode,
some in another.
It's not intended for that to happen, but it is possible for it to. The
alternative is to try and forcibly change all files at server start time
to match what is configured but that didn't seem like a great idea.
Thanks!
Stephen
Tom,
* Tom Lane (tgl@sss.pgh.pa.us) wrote:
Simon Riggs <simon@2ndquadrant.com> writes:
On 1 March 2017 at 01:58, David Steele <david@pgmasters.net> wrote:
PostgreSQL currently requires the file mode mask (umask) to be 0077.
However, this precludes the possibility of a user in the postgres group
performing a backup (or whatever). Now that
pg_start_backup()/pg_stop_backup() privileges can be delegated to an
unprivileged user, it makes sense to also allow a (relatively)
unprivileged user to perform the backup at the file system level as well.+1
I'd ask what is the point, considering that we don't view "cp -a" as a
supported backup technique in the first place.
The point is to allow backups to be performed as a user who only has
read-only access to the files and is a non-superuser in the database.
With the changes to allow GRANT'ing of the pg_start/stop_backup and
related functions and these changes to allow the files to be group
readable, that will be possible using a tool such as pgbackrest, not
just with a "cp -a".
Thanks!
Stephen
On 3/6/17 8:50 AM, Stephen Frost wrote:
* Simon Riggs (simon@2ndquadrant.com) wrote:
to allow the default mode of files and directories
in the $PGDATA directory to be modified.Are you saying if this is changed all files/directories will be
changed to the new mode?No, new files will be created with the new mode and existing files will
be allowed to have the mode set. Changing all of the existing files
didn't seem like something we should be trying to do at server start.It seems like it would be annoying to have some files in one mode,
some in another.It's not intended for that to happen, but it is possible for it to. The
alternative is to try and forcibly change all files at server start time
to match what is configured but that didn't seem like a great idea.
Agreed. It would definitely affect server start time, perhaps
significantly.
I have added a note to the docs that a change in file_mode_mask does not
automatically change the mode of existing files on disk.
This patch applies cleanly on 6f3a13f.
--
-David
david@pgmasters.net
Attachments:
file-mode-mask-v2.patchtext/plain; charset=UTF-8; name=file-mode-mask-v2.patch; x-mac-creator=0; x-mac-type=0Download
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index 62dec87..98f8170 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -1858,8 +1858,7 @@ qtext_store(const char *query, int query_len,
*query_offset = off;
/* Now write the data into the successfully-reserved part of the file */
- fd = OpenTransientFile(PGSS_TEXT_FILE, O_RDWR | O_CREAT | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ fd = OpenTransientFile(PGSS_TEXT_FILE, O_RDWR | O_CREAT | PG_BINARY);
if (fd < 0)
goto error;
@@ -1923,7 +1922,7 @@ qtext_load_file(Size *buffer_size)
int fd;
struct stat stat;
- fd = OpenTransientFile(PGSS_TEXT_FILE, O_RDONLY | PG_BINARY, 0);
+ fd = OpenTransientFile(PGSS_TEXT_FILE, O_RDONLY | PG_BINARY);
if (fd < 0)
{
if (errno != ENOENT)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cd82c04..6c84082 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -812,6 +812,44 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-file-mode-mask" xreflabel="file_mode_mask">
+ <term><varname>file_mode_mask</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>file_mode_mask</> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Sets the file mode mask (umask) for the data directory. The parameter
+ value is expected to be a numeric mode specified in the format
+ accepted by the <function>chmod</function> and
+ <function>umask</function> system calls. (To use the customary octal
+ format the number must start with a <literal>0</literal> (zero).)
+ </para>
+
+ <para>
+ The default <literal>file_mode_mask</literal> is <literal>0077</literal>,
+ meaning that the only the database owner can read and write files in
+ the data directory. For example, setting the
+ <literal>file_mode_mask</literal> to <literal>0027</literal> would allow
+ any user in the same group as the database owner to read all database
+ files, which would be useful for producing a backup using a relatively
+ unprivileged user.
+ </para>
+
+ <para>
+ Note that changing this parameter does not automatically change the
+ mode of existing files in the cluster. This must be done manually by
+ an administator.
+ </para>
+
+ <para>
+ This parameter can only be set at server start.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-bonjour" xreflabel="bonjour">
<term><varname>bonjour</varname> (<type>boolean</type>)
<indexterm>
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 1aaa490..bd1f849 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -296,6 +296,25 @@ PostgreSQL documentation
</varlistentry>
<varlistentry>
+ <term><option>-u <replaceable class="parameter">mask</replaceable></option></term>
+ <term><option>--file-mode-mask=<replaceable class="parameter">mask</replaceable></option></term>
+ <listitem>
+ <para>
+ Sets the file mode mask (umask) for the data directory. The parameter
+ value is expected to be a numeric mode specified in the format
+ accepted by the <function>chmod</function> and
+ <function>umask</function> system calls. (To use the customary octal
+ format the number must start with a <literal>0</literal> (zero).)
+ </para>
+
+ <para>
+ Specifying this parameter will automatically set
+ <xref linkend="guc-file-mode-mask"> in <filename>postgresql.conf</>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><option>-W</option></term>
<term><option>--pwprompt</option></term>
<listitem>
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index c7b283c..53a2acc 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -1010,8 +1010,7 @@ logical_rewrite_log_mapping(RewriteState state, TransactionId xid,
src->off = 0;
memcpy(src->path, path, sizeof(path));
src->vfd = PathNameOpenFile(path,
- O_CREAT | O_EXCL | O_WRONLY | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ O_CREAT | O_EXCL | O_WRONLY | PG_BINARY);
if (src->vfd < 0)
ereport(ERROR,
(errcode_for_file_access(),
@@ -1130,8 +1129,7 @@ heap_xlog_logical_rewrite(XLogReaderState *r)
xlrec->mapped_xid, XLogRecGetXid(r));
fd = OpenTransientFile(path,
- O_CREAT | O_WRONLY | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ O_CREAT | O_WRONLY | PG_BINARY);
if (fd < 0)
ereport(ERROR,
(errcode_for_file_access(),
@@ -1249,7 +1247,7 @@ CheckPointLogicalRewriteHeap(void)
}
else
{
- int fd = OpenTransientFile(path, O_RDONLY | PG_BINARY, 0);
+ int fd = OpenTransientFile(path, O_RDONLY | PG_BINARY);
/*
* The file cannot vanish due to concurrency since this function
diff --git a/src/backend/access/transam/slru.c b/src/backend/access/transam/slru.c
index a66ef5c..f385b79 100644
--- a/src/backend/access/transam/slru.c
+++ b/src/backend/access/transam/slru.c
@@ -594,7 +594,7 @@ SimpleLruDoesPhysicalPageExist(SlruCtl ctl, int pageno)
SlruFileName(ctl, path, segno);
- fd = OpenTransientFile(path, O_RDWR | PG_BINARY, S_IRUSR | S_IWUSR);
+ fd = OpenTransientFile(path, O_RDWR | PG_BINARY);
if (fd < 0)
{
/* expected: file doesn't exist */
@@ -649,7 +649,7 @@ SlruPhysicalReadPage(SlruCtl ctl, int pageno, int slotno)
* SlruPhysicalWritePage). Hence, if we are InRecovery, allow the case
* where the file doesn't exist, and return zeroes instead.
*/
- fd = OpenTransientFile(path, O_RDWR | PG_BINARY, S_IRUSR | S_IWUSR);
+ fd = OpenTransientFile(path, O_RDWR | PG_BINARY);
if (fd < 0)
{
if (errno != ENOENT || !InRecovery)
@@ -796,8 +796,7 @@ SlruPhysicalWritePage(SlruCtl ctl, int pageno, int slotno, SlruFlush fdata)
* don't use O_EXCL or O_TRUNC or anything like that.
*/
SlruFileName(ctl, path, segno);
- fd = OpenTransientFile(path, O_RDWR | O_CREAT | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ fd = OpenTransientFile(path, O_RDWR | O_CREAT | PG_BINARY);
if (fd < 0)
{
slru_errcause = SLRU_OPEN_FAILED;
diff --git a/src/backend/access/transam/timeline.c b/src/backend/access/transam/timeline.c
index 1fdc591..baa766a 100644
--- a/src/backend/access/transam/timeline.c
+++ b/src/backend/access/transam/timeline.c
@@ -306,8 +306,7 @@ writeTimeLineHistory(TimeLineID newTLI, TimeLineID parentTLI,
unlink(tmppath);
/* do not use get_sync_bit() here --- want to fsync only at end of fill */
- fd = OpenTransientFile(tmppath, O_RDWR | O_CREAT | O_EXCL,
- S_IRUSR | S_IWUSR);
+ fd = OpenTransientFile(tmppath, O_RDWR | O_CREAT | O_EXCL);
if (fd < 0)
ereport(ERROR,
(errcode_for_file_access(),
@@ -324,7 +323,7 @@ writeTimeLineHistory(TimeLineID newTLI, TimeLineID parentTLI,
else
TLHistoryFilePath(path, parentTLI);
- srcfd = OpenTransientFile(path, O_RDONLY, 0);
+ srcfd = OpenTransientFile(path, O_RDONLY);
if (srcfd < 0)
{
if (errno != ENOENT)
@@ -452,8 +451,7 @@ writeTimeLineHistoryFile(TimeLineID tli, char *content, int size)
unlink(tmppath);
/* do not use get_sync_bit() here --- want to fsync only at end of fill */
- fd = OpenTransientFile(tmppath, O_RDWR | O_CREAT | O_EXCL,
- S_IRUSR | S_IWUSR);
+ fd = OpenTransientFile(tmppath, O_RDWR | O_CREAT | O_EXCL);
if (fd < 0)
ereport(ERROR,
(errcode_for_file_access(),
diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c
index 0a8edb9..1f02dd1 100644
--- a/src/backend/access/transam/twophase.c
+++ b/src/backend/access/transam/twophase.c
@@ -1151,7 +1151,7 @@ ReadTwoPhaseFile(TransactionId xid, bool give_warnings)
TwoPhaseFilePath(path, xid);
- fd = OpenTransientFile(path, O_RDONLY | PG_BINARY, 0);
+ fd = OpenTransientFile(path, O_RDONLY | PG_BINARY);
if (fd < 0)
{
if (give_warnings)
@@ -1533,8 +1533,7 @@ RecreateTwoPhaseFile(TransactionId xid, void *content, int len)
TwoPhaseFilePath(path, xid);
fd = OpenTransientFile(path,
- O_CREAT | O_TRUNC | O_WRONLY | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ O_CREAT | O_TRUNC | O_WRONLY | PG_BINARY);
if (fd < 0)
ereport(ERROR,
(errcode_for_file_access(),
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 8973583..eed7dda 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -3156,8 +3156,7 @@ XLogFileInit(XLogSegNo logsegno, bool *use_existent, bool use_lock)
*/
if (*use_existent)
{
- fd = BasicOpenFile(path, O_RDWR | PG_BINARY | get_sync_bit(sync_method),
- S_IRUSR | S_IWUSR);
+ fd = BasicOpenFile(path, O_RDWR | PG_BINARY | get_sync_bit(sync_method));
if (fd < 0)
{
if (errno != ENOENT)
@@ -3182,8 +3181,7 @@ XLogFileInit(XLogSegNo logsegno, bool *use_existent, bool use_lock)
unlink(tmppath);
/* do not use get_sync_bit() here --- want to fsync only at end of fill */
- fd = BasicOpenFile(tmppath, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ fd = BasicOpenFile(tmppath, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
if (fd < 0)
ereport(ERROR,
(errcode_for_file_access(),
@@ -3275,8 +3273,7 @@ XLogFileInit(XLogSegNo logsegno, bool *use_existent, bool use_lock)
*use_existent = false;
/* Now open original target segment (might not be file I just made) */
- fd = BasicOpenFile(path, O_RDWR | PG_BINARY | get_sync_bit(sync_method),
- S_IRUSR | S_IWUSR);
+ fd = BasicOpenFile(path, O_RDWR | PG_BINARY | get_sync_bit(sync_method));
if (fd < 0)
ereport(ERROR,
(errcode_for_file_access(),
@@ -3317,7 +3314,7 @@ XLogFileCopy(XLogSegNo destsegno, TimeLineID srcTLI, XLogSegNo srcsegno,
* Open the source file
*/
XLogFilePath(path, srcTLI, srcsegno);
- srcfd = OpenTransientFile(path, O_RDONLY | PG_BINARY, 0);
+ srcfd = OpenTransientFile(path, O_RDONLY | PG_BINARY);
if (srcfd < 0)
ereport(ERROR,
(errcode_for_file_access(),
@@ -3331,8 +3328,7 @@ XLogFileCopy(XLogSegNo destsegno, TimeLineID srcTLI, XLogSegNo srcsegno,
unlink(tmppath);
/* do not use get_sync_bit() here --- want to fsync only at end of fill */
- fd = OpenTransientFile(tmppath, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ fd = OpenTransientFile(tmppath, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
if (fd < 0)
ereport(ERROR,
(errcode_for_file_access(),
@@ -3504,8 +3500,7 @@ XLogFileOpen(XLogSegNo segno)
XLogFilePath(path, ThisTimeLineID, segno);
- fd = BasicOpenFile(path, O_RDWR | PG_BINARY | get_sync_bit(sync_method),
- S_IRUSR | S_IWUSR);
+ fd = BasicOpenFile(path, O_RDWR | PG_BINARY | get_sync_bit(sync_method));
if (fd < 0)
ereport(PANIC,
(errcode_for_file_access(),
@@ -3571,7 +3566,7 @@ XLogFileRead(XLogSegNo segno, int emode, TimeLineID tli,
snprintf(path, MAXPGPATH, XLOGDIR "/%s", xlogfname);
}
- fd = BasicOpenFile(path, O_RDONLY | PG_BINARY, 0);
+ fd = BasicOpenFile(path, O_RDONLY | PG_BINARY);
if (fd >= 0)
{
/* Success! */
@@ -4065,7 +4060,7 @@ ValidateXLOGDirectoryStructure(void)
{
ereport(LOG,
(errmsg("creating missing WAL directory \"%s\"", path)));
- if (mkdir(path, S_IRWXU) < 0)
+ if (mkdir(path, PG_DEFAULT_DIR_MODE) < 0)
ereport(FATAL,
(errmsg("could not create missing directory \"%s\": %m",
path)));
@@ -4404,8 +4399,7 @@ WriteControlFile(void)
memcpy(buffer, ControlFile, sizeof(ControlFileData));
fd = BasicOpenFile(XLOG_CONTROL_FILE,
- O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
if (fd < 0)
ereport(PANIC,
(errcode_for_file_access(),
@@ -4444,8 +4438,7 @@ ReadControlFile(void)
* Read data...
*/
fd = BasicOpenFile(XLOG_CONTROL_FILE,
- O_RDWR | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ O_RDWR | PG_BINARY);
if (fd < 0)
ereport(PANIC,
(errcode_for_file_access(),
@@ -4624,8 +4617,7 @@ UpdateControlFile(void)
FIN_CRC32C(ControlFile->crc);
fd = BasicOpenFile(XLOG_CONTROL_FILE,
- O_RDWR | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ O_RDWR | PG_BINARY);
if (fd < 0)
ereport(PANIC,
(errcode_for_file_access(),
diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c
index 8b99b78..96ac73f 100644
--- a/src/backend/access/transam/xlogutils.c
+++ b/src/backend/access/transam/xlogutils.c
@@ -687,7 +687,7 @@ XLogRead(char *buf, TimeLineID tli, XLogRecPtr startptr, Size count)
XLogFilePath(path, tli, sendSegNo);
- sendFile = BasicOpenFile(path, O_RDONLY | PG_BINARY, 0);
+ sendFile = BasicOpenFile(path, O_RDONLY | PG_BINARY);
if (sendFile < 0)
{
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 11ee536..578b46b 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -428,7 +428,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
/* Check for existing file of same name */
rpath = relpath(rnode, MAIN_FORKNUM);
- fd = BasicOpenFile(rpath, O_RDONLY | PG_BINARY, 0);
+ fd = BasicOpenFile(rpath, O_RDONLY | PG_BINARY);
if (fd >= 0)
{
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index f9c2620..312ac07 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -151,7 +151,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
else
{
/* Directory creation failed? */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (mkdir(dir, PG_DEFAULT_DIR_MODE) < 0)
{
char *parentdir;
@@ -173,7 +173,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
get_parent_directory(parentdir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (mkdir(parentdir, PG_DEFAULT_DIR_MODE) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -184,7 +184,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
parentdir = pstrdup(dir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (mkdir(parentdir, PG_DEFAULT_DIR_MODE) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -192,7 +192,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
pfree(parentdir);
/* Create database directory */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (mkdir(dir, PG_DEFAULT_DIR_MODE) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -610,7 +610,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* The creation of the version directory prevents more than one tablespace
* in a single location.
*/
- if (mkdir(location_with_version_dir, S_IRWXU) < 0)
+ if (mkdir(location_with_version_dir, PG_DEFAULT_DIR_MODE) < 0)
{
if (errno == EEXIST)
ereport(ERROR,
diff --git a/src/backend/libpq/be-fsstubs.c b/src/backend/libpq/be-fsstubs.c
index f537aff..c0c8d94 100644
--- a/src/backend/libpq/be-fsstubs.c
+++ b/src/backend/libpq/be-fsstubs.c
@@ -462,7 +462,7 @@ lo_import_internal(text *filename, Oid lobjOid)
* open the file to be read in
*/
text_to_cstring_buffer(filename, fnamebuf, sizeof(fnamebuf));
- fd = OpenTransientFile(fnamebuf, O_RDONLY | PG_BINARY, S_IRWXU);
+ fd = OpenTransientFile(fnamebuf, O_RDONLY | PG_BINARY);
if (fd < 0)
ereport(ERROR,
(errcode_for_file_access(),
@@ -538,8 +538,8 @@ be_lo_export(PG_FUNCTION_ARGS)
*/
text_to_cstring_buffer(filename, fnamebuf, sizeof(fnamebuf));
oumask = umask(S_IWGRP | S_IWOTH);
- fd = OpenTransientFile(fnamebuf, O_CREAT | O_WRONLY | O_TRUNC | PG_BINARY,
- S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ fd = OpenTransientFilePerm(fnamebuf, O_CREAT | O_WRONLY | O_TRUNC | PG_BINARY,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
umask(oumask);
if (fd < 0)
ereport(ERROR,
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 6831342..9fbd2a9 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -579,9 +579,10 @@ PostmasterMain(int argc, char *argv[])
IsPostmasterEnvironment = true;
/*
- * for security, no dir or file created can be group or other accessible
+ * Initially set most restrictive umask in case any files are
+ * accidentally created before file_mode_mask is loaded from GUC.
*/
- umask(S_IRWXG | S_IRWXO);
+ umask(file_mode_mask);
/*
* Initialize random(3) so we don't get the same values in every run.
@@ -854,6 +855,9 @@ PostmasterMain(int argc, char *argv[])
ExitPostmaster(0);
}
+ /* Reset the file mode creation mask now that GUC has been loaded. */
+ umask(file_mode_mask);
+
/* Verify that DataDir looks reasonable */
checkDataDir();
@@ -1496,25 +1500,20 @@ checkDataDir(void)
#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.
+ * Check if the directory has the correct permissions. If not, then reject.
*
* 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))
+ if (stat_buf.st_mode & file_mode_mask)
ereport(FATAL,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("data directory \"%s\" has group or world access",
+ errmsg("data directory \"%s\" has incorrect permissions",
DataDir),
- errdetail("Permissions should be u=rwx (0700).")));
+ errdetail("Permissions should be (%04o).",
+ (PG_DEFAULT_DIR_MODE & ~file_mode_mask))));
#endif
/* Look for PG_VERSION before looking for pg_control */
@@ -4400,7 +4399,7 @@ internal_forkexec(int argc, char *argv[], Port *port)
* As in OpenTemporaryFileInTablespace, try to make the temp-file
* directory
*/
- mkdir(PG_TEMP_FILES_DIR, S_IRWXU);
+ mkdir(PG_TEMP_FILES_DIR, PG_DEFAULT_DIR_MODE);
fp = AllocateFile(tmpfilename, PG_BINARY_W);
if (!fp)
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index aaefdae..f7b1cb5 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -41,6 +41,7 @@
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
#include "storage/dsm.h"
+#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pg_shmem.h"
@@ -322,7 +323,7 @@ SysLoggerMain(int argc, char *argv[])
/*
* Also, create new directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ mkdir(Log_directory, PG_DEFAULT_DIR_MODE);
}
if (strcmp(Log_filename, currentLogFilename) != 0)
{
@@ -564,7 +565,7 @@ SysLogger_Start(void)
/*
* Create log directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ mkdir(Log_directory, PG_DEFAULT_DIR_MODE);
/*
* The initial logfile is created right in the postmaster, to verify that
diff --git a/src/backend/replication/logical/origin.c b/src/backend/replication/logical/origin.c
index bf84c68..51d54ab 100644
--- a/src/backend/replication/logical/origin.c
+++ b/src/backend/replication/logical/origin.c
@@ -527,8 +527,7 @@ CheckPointReplicationOrigin(void)
* CheckpointLock.
*/
tmpfd = OpenTransientFile((char *) tmppath,
- O_CREAT | O_EXCL | O_WRONLY | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ O_CREAT | O_EXCL | O_WRONLY | PG_BINARY);
if (tmpfd < 0)
ereport(PANIC,
(errcode_for_file_access(),
@@ -637,7 +636,7 @@ StartupReplicationOrigin(void)
elog(DEBUG2, "starting up replication origin progress state");
- fd = OpenTransientFile((char *) path, O_RDONLY | PG_BINARY, 0);
+ fd = OpenTransientFile((char *) path, O_RDONLY | PG_BINARY);
/*
* might have had max_replication_slots == 0 last run, or we just brought
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index 8aac670..af7edea 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -2103,8 +2103,7 @@ ReorderBufferSerializeTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
/* open segment, create it if necessary */
fd = OpenTransientFile(path,
- O_CREAT | O_WRONLY | O_APPEND | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ O_CREAT | O_WRONLY | O_APPEND | PG_BINARY);
if (fd < 0)
ereport(ERROR,
@@ -2345,7 +2344,7 @@ ReorderBufferRestoreChanges(ReorderBuffer *rb, ReorderBufferTXN *txn,
NameStr(MyReplicationSlot->data.name), txn->xid,
(uint32) (recptr >> 32), (uint32) recptr);
- *fd = OpenTransientFile(path, O_RDONLY | PG_BINARY, 0);
+ *fd = OpenTransientFile(path, O_RDONLY | PG_BINARY);
if (*fd < 0 && errno == ENOENT)
{
*fd = -1;
@@ -3030,7 +3029,7 @@ ApplyLogicalMappingFile(HTAB *tuplecid_data, Oid relid, const char *fname)
LogicalRewriteMappingData map;
sprintf(path, "pg_logical/mappings/%s", fname);
- fd = OpenTransientFile(path, O_RDONLY | PG_BINARY, 0);
+ fd = OpenTransientFile(path, O_RDONLY | PG_BINARY);
if (fd < 0)
ereport(ERROR,
(errcode_for_file_access(),
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index 6f19cdc..961b0f9 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -1574,8 +1574,7 @@ SnapBuildSerialize(SnapBuild *builder, XLogRecPtr lsn)
/* we have valid data now, open tempfile and write it there */
fd = OpenTransientFile(tmppath,
- O_CREAT | O_EXCL | O_WRONLY | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ O_CREAT | O_EXCL | O_WRONLY | PG_BINARY);
if (fd < 0)
ereport(ERROR,
(errmsg("could not open file \"%s\": %m", path)));
@@ -1655,7 +1654,7 @@ SnapBuildRestore(SnapBuild *builder, XLogRecPtr lsn)
sprintf(path, "pg_logical/snapshots/%X-%X.snap",
(uint32) (lsn >> 32), (uint32) lsn);
- fd = OpenTransientFile(path, O_RDONLY | PG_BINARY, 0);
+ fd = OpenTransientFile(path, O_RDONLY | PG_BINARY);
if (fd < 0 && errno == ENOENT)
return false;
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 10d69d0..7e8b50e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -1011,7 +1011,7 @@ CreateSlotOnDisk(ReplicationSlot *slot)
rmtree(tmppath, true);
/* Create and fsync the temporary slot directory. */
- if (mkdir(tmppath, S_IRWXU) < 0)
+ if (mkdir(tmppath, PG_DEFAULT_DIR_MODE) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -1072,9 +1072,7 @@ SaveSlotToPath(ReplicationSlot *slot, const char *dir, int elevel)
sprintf(tmppath, "%s/state.tmp", dir);
sprintf(path, "%s/state", dir);
- fd = OpenTransientFile(tmppath,
- O_CREAT | O_EXCL | O_WRONLY | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ fd = OpenTransientFile(tmppath, O_CREAT | O_EXCL | O_WRONLY | PG_BINARY);
if (fd < 0)
{
ereport(elevel,
@@ -1187,7 +1185,7 @@ RestoreSlotFromDisk(const char *name)
elog(DEBUG1, "restoring replication slot from \"%s\"", path);
- fd = OpenTransientFile(path, O_RDWR | PG_BINARY, 0);
+ fd = OpenTransientFile(path, O_RDWR | PG_BINARY);
/*
* We do not need to handle this as we are rename()ing the directory into
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 9cf9eb0..e1af7a3 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -437,7 +437,7 @@ SendTimeLineHistory(TimeLineHistoryCmd *cmd)
pq_sendint(&buf, len, 4); /* col1 len */
pq_sendbytes(&buf, histfname, len);
- fd = OpenTransientFile(path, O_RDONLY | PG_BINARY, 0666);
+ fd = OpenTransientFile(path, O_RDONLY | PG_BINARY);
if (fd < 0)
ereport(ERROR,
(errcode_for_file_access(),
@@ -2031,7 +2031,7 @@ retry:
XLogFilePath(path, curFileTimeLine, sendSegNo);
- sendFile = BasicOpenFile(path, O_RDONLY | PG_BINARY, 0);
+ sendFile = BasicOpenFile(path, O_RDONLY | PG_BINARY);
if (sendFile < 0)
{
/*
diff --git a/src/backend/storage/file/copydir.c b/src/backend/storage/file/copydir.c
index 101da47..0e078f5 100644
--- a/src/backend/storage/file/copydir.c
+++ b/src/backend/storage/file/copydir.c
@@ -41,7 +41,7 @@ copydir(char *fromdir, char *todir, bool recurse)
char fromfile[MAXPGPATH];
char tofile[MAXPGPATH];
- if (mkdir(todir, S_IRWXU) != 0)
+ if (mkdir(todir, PG_DEFAULT_DIR_MODE) != 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m", todir)));
@@ -148,14 +148,13 @@ copy_file(char *fromfile, char *tofile)
/*
* Open the files
*/
- srcfd = OpenTransientFile(fromfile, O_RDONLY | PG_BINARY, 0);
+ srcfd = OpenTransientFile(fromfile, O_RDONLY | PG_BINARY);
if (srcfd < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not open file \"%s\": %m", fromfile)));
- dstfd = OpenTransientFile(tofile, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ dstfd = OpenTransientFile(tofile, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
if (dstfd < 0)
ereport(ERROR,
(errcode_for_file_access(),
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index fd02fc0..46caf90 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -138,6 +138,8 @@ int max_files_per_process = 1000;
*/
int max_safe_fds = 32; /* default if not changed */
+/* The file mode creation mask for creating new files and directories. */
+int file_mode_mask = PG_DEFAULT_MODE_MASK;
/* Debugging.... */
@@ -604,7 +606,7 @@ durable_rename(const char *oldfile, const char *newfile, int elevel)
if (fsync_fname_ext(oldfile, false, false, elevel) != 0)
return -1;
- fd = OpenTransientFile((char *) newfile, PG_BINARY | O_RDWR, 0);
+ fd = OpenTransientFile((char *) newfile, PG_BINARY | O_RDWR);
if (fd < 0)
{
if (errno != ENOENT)
@@ -896,7 +898,7 @@ set_max_safe_fds(void)
* this module wouldn't have any open files to close at that point anyway.
*/
int
-BasicOpenFile(FileName fileName, int fileFlags, int fileMode)
+BasicOpenFilePerm(FileName fileName, int fileFlags, int fileMode)
{
int fd;
@@ -922,6 +924,12 @@ tryAgain:
return -1; /* failure */
}
+int
+BasicOpenFile(FileName fileName, int fileFlags)
+{
+ return BasicOpenFilePerm(fileName, fileFlags, PG_DEFAULT_FILE_MODE);
+}
+
#if defined(FDDEBUG)
static void
@@ -1047,8 +1055,8 @@ LruInsert(File file)
* overall system file table being full. So, be prepared to release
* another FD if necessary...
*/
- vfdP->fd = BasicOpenFile(vfdP->fileName, vfdP->fileFlags,
- vfdP->fileMode);
+ vfdP->fd = BasicOpenFilePerm(vfdP->fileName, vfdP->fileFlags,
+ vfdP->fileMode);
if (vfdP->fd < 0)
{
DO_DB(elog(LOG, "re-open failed: %m"));
@@ -1263,13 +1271,19 @@ FileInvalidate(File file)
* (which should always be $PGDATA when this code is running).
*/
File
-PathNameOpenFile(FileName fileName, int fileFlags, int fileMode)
+PathNameOpenFile(FileName fileName, int fileFlags)
+{
+ return PathNameOpenFilePerm(fileName, fileFlags, PG_DEFAULT_FILE_MODE);
+}
+
+File
+PathNameOpenFilePerm(FileName fileName, int fileFlags, int fileMode)
{
char *fnamecopy;
File file;
Vfd *vfdP;
- DO_DB(elog(LOG, "PathNameOpenFile: %s %x %o",
+ DO_DB(elog(LOG, "PathNameOpenFilePerm: %s %x %o",
fileName, fileFlags, fileMode));
/*
@@ -1287,7 +1301,7 @@ PathNameOpenFile(FileName fileName, int fileFlags, int fileMode)
/* Close excess kernel FDs. */
ReleaseLruFiles();
- vfdP->fd = BasicOpenFile(fileName, fileFlags, fileMode);
+ vfdP->fd = BasicOpenFilePerm(fileName, fileFlags, fileMode);
if (vfdP->fd < 0)
{
@@ -1424,8 +1438,7 @@ OpenTemporaryFileInTablespace(Oid tblspcOid, bool rejectError)
* temp file that can be reused.
*/
file = PathNameOpenFile(tempfilepath,
- O_RDWR | O_CREAT | O_TRUNC | PG_BINARY,
- 0600);
+ O_RDWR | O_CREAT | O_TRUNC | PG_BINARY);
if (file <= 0)
{
/*
@@ -1436,11 +1449,10 @@ OpenTemporaryFileInTablespace(Oid tblspcOid, bool rejectError)
* just did the same thing. If it doesn't work then we'll bomb out on
* the second create attempt, instead.
*/
- mkdir(tempdirpath, S_IRWXU);
+ mkdir(tempdirpath, PG_DEFAULT_DIR_MODE);
file = PathNameOpenFile(tempfilepath,
- O_RDWR | O_CREAT | O_TRUNC | PG_BINARY,
- 0600);
+ O_RDWR | O_CREAT | O_TRUNC | PG_BINARY);
if (file <= 0 && rejectError)
elog(ERROR, "could not create temporary file \"%s\": %m",
tempfilepath);
@@ -2090,7 +2102,7 @@ TryAgain:
* Like AllocateFile, but returns an unbuffered fd like open(2)
*/
int
-OpenTransientFile(FileName fileName, int fileFlags, int fileMode)
+OpenTransientFilePerm(FileName fileName, int fileFlags, int fileMode)
{
int fd;
@@ -2107,7 +2119,7 @@ OpenTransientFile(FileName fileName, int fileFlags, int fileMode)
/* Close excess kernel FDs. */
ReleaseLruFiles();
- fd = BasicOpenFile(fileName, fileFlags, fileMode);
+ fd = BasicOpenFilePerm(fileName, fileFlags, fileMode);
if (fd >= 0)
{
@@ -2124,6 +2136,12 @@ OpenTransientFile(FileName fileName, int fileFlags, int fileMode)
return -1; /* failure */
}
+int
+OpenTransientFile(FileName fileName, int fileFlags)
+{
+ return OpenTransientFilePerm(fileName, fileFlags, PG_DEFAULT_FILE_MODE);
+}
+
/*
* Routines that want to initiate a pipe stream should use OpenPipeStream
* rather than plain popen(). This lets fd.c deal with freeing FDs if
@@ -3030,7 +3048,7 @@ pre_sync_fname(const char *fname, bool isdir, int elevel)
if (isdir)
return;
- fd = OpenTransientFile((char *) fname, O_RDONLY | PG_BINARY, 0);
+ fd = OpenTransientFile((char *) fname, O_RDONLY | PG_BINARY);
if (fd < 0)
{
@@ -3090,7 +3108,7 @@ fsync_fname_ext(const char *fname, bool isdir, bool ignore_perm, int elevel)
else
flags |= O_RDONLY;
- fd = OpenTransientFile((char *) fname, flags, 0);
+ fd = OpenTransientFile((char *) fname, flags);
/*
* Some OSs don't allow us to open directories at all (Windows returns
diff --git a/src/backend/storage/ipc/dsm_impl.c b/src/backend/storage/ipc/dsm_impl.c
index b2c9cdc..80ee4fa 100644
--- a/src/backend/storage/ipc/dsm_impl.c
+++ b/src/backend/storage/ipc/dsm_impl.c
@@ -283,7 +283,7 @@ dsm_impl_posix(dsm_op op, dsm_handle handle, Size request_size,
* returning.
*/
flags = O_RDWR | (op == DSM_OP_CREATE ? O_CREAT | O_EXCL : 0);
- if ((fd = shm_open(name, flags, 0600)) == -1)
+ if ((fd = shm_open(name, flags, PG_DEFAULT_FILE_MODE)) == -1)
{
if (errno != EEXIST)
ereport(elevel,
@@ -834,7 +834,7 @@ dsm_impl_mmap(dsm_op op, dsm_handle handle, Size request_size,
/* Create new segment or open an existing one for attach or resize. */
flags = O_RDWR | (op == DSM_OP_CREATE ? O_CREAT | O_EXCL : 0);
- if ((fd = OpenTransientFile(name, flags, 0600)) == -1)
+ if ((fd = OpenTransientFile(name, flags)) == -1)
{
if (errno != EEXIST)
ereport(elevel,
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index ffd91f5..dfc5b75 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -28,6 +28,7 @@
#include "postmaster/autovacuum.h"
#endif
#include "storage/dsm.h"
+#include "storage/fd.h"
#include "storage/ipc.h"
#include "tcop/tcopprot.h"
@@ -132,8 +133,8 @@ proc_exit(int code)
else
snprintf(gprofDirName, 32, "gprof/%d", (int) getpid());
- mkdir("gprof", S_IRWXU | S_IRWXG | S_IRWXO);
- mkdir(gprofDirName, S_IRWXU | S_IRWXG | S_IRWXO);
+ mkdir("gprof", PG_DEFAULT_DIR_MODE);
+ mkdir(gprofDirName, PG_DEFAULT_DIR_MODE);
chdir(gprofDirName);
}
#endif
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index 6c17b54..cf951a2 100644
--- a/src/backend/storage/smgr/md.c
+++ b/src/backend/storage/smgr/md.c
@@ -303,7 +303,7 @@ mdcreate(SMgrRelation reln, ForkNumber forkNum, bool isRedo)
path = relpath(reln->smgr_rnode, forkNum);
- fd = PathNameOpenFile(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY, 0600);
+ fd = PathNameOpenFile(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
if (fd < 0)
{
@@ -316,7 +316,7 @@ mdcreate(SMgrRelation reln, ForkNumber forkNum, bool isRedo)
* already, even if isRedo is not set. (See also mdopen)
*/
if (isRedo || IsBootstrapProcessingMode())
- fd = PathNameOpenFile(path, O_RDWR | PG_BINARY, 0600);
+ fd = PathNameOpenFile(path, O_RDWR | PG_BINARY);
if (fd < 0)
{
/* be sure to report the error reported by create, not open */
@@ -429,7 +429,7 @@ mdunlinkfork(RelFileNodeBackend rnode, ForkNumber forkNum, bool isRedo)
/* truncate(2) would be easier here, but Windows hasn't got it */
int fd;
- fd = OpenTransientFile(path, O_RDWR | PG_BINARY, 0);
+ fd = OpenTransientFile(path, O_RDWR | PG_BINARY);
if (fd >= 0)
{
int save_errno;
@@ -582,7 +582,7 @@ mdopen(SMgrRelation reln, ForkNumber forknum, int behavior)
path = relpath(reln->smgr_rnode, forknum);
- fd = PathNameOpenFile(path, O_RDWR | PG_BINARY, 0600);
+ fd = PathNameOpenFile(path, O_RDWR | PG_BINARY);
if (fd < 0)
{
@@ -593,7 +593,7 @@ mdopen(SMgrRelation reln, ForkNumber forknum, int behavior)
* substitute for mdcreate() in bootstrap mode only. (See mdcreate)
*/
if (IsBootstrapProcessingMode())
- fd = PathNameOpenFile(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY, 0600);
+ fd = PathNameOpenFile(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
if (fd < 0)
{
if ((behavior & EXTENSION_RETURN_NULL) &&
@@ -1779,7 +1779,7 @@ _mdfd_openseg(SMgrRelation reln, ForkNumber forknum, BlockNumber segno,
fullpath = _mdfd_segpath(reln, forknum, segno);
/* open the file */
- fd = PathNameOpenFile(fullpath, O_RDWR | PG_BINARY | oflags, 0600);
+ fd = PathNameOpenFile(fullpath, O_RDWR | PG_BINARY | oflags);
pfree(fullpath);
diff --git a/src/backend/utils/cache/relmapper.c b/src/backend/utils/cache/relmapper.c
index c9d6e44..fe68b5b 100644
--- a/src/backend/utils/cache/relmapper.c
+++ b/src/backend/utils/cache/relmapper.c
@@ -643,8 +643,7 @@ load_relmap_file(bool shared)
}
/* Read data ... */
- fd = OpenTransientFile(mapfilename,
- O_RDONLY | PG_BINARY, S_IRUSR | S_IWUSR);
+ fd = OpenTransientFile(mapfilename, O_RDONLY | PG_BINARY);
if (fd < 0)
ereport(FATAL,
(errcode_for_file_access(),
@@ -742,9 +741,7 @@ write_relmap_file(bool shared, RelMapFile *newmap,
realmap = &local_map;
}
- fd = OpenTransientFile(mapfilename,
- O_WRONLY | O_CREAT | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ fd = OpenTransientFile(mapfilename, O_WRONLY | O_CREAT | PG_BINARY);
if (fd < 0)
ereport(ERROR,
(errcode_for_file_access(),
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 0707f66..bb3e471 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -190,6 +190,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_file_mode_mask(void);
/* Private functions in guc-file.l that need to be called from guc.c */
static ConfigVariable *ProcessConfigFileInternal(GucContext context,
@@ -2841,6 +2842,18 @@ static struct config_int ConfigureNamesInt[] =
4096, 64, MAX_KILOBYTES,
NULL, NULL, NULL
},
+ {
+ {"file_mode_mask", PGC_POSTMASTER, UNGROUPED,
+ gettext_noop("Sets the file mode creation mask for the backend process."),
+ gettext_noop("The parameter value is expected to be 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).)")
+ },
+ &file_mode_mask,
+ PG_DEFAULT_MODE_MASK, 0000, 0777,
+ NULL, NULL, show_file_mode_mask
+ },
/* End-of-list marker */
{
@@ -7186,8 +7199,7 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
* truncate and reuse it.
*/
Tmpfd = BasicOpenFile(AutoConfTmpFileName,
- O_CREAT | O_RDWR | O_TRUNC,
- S_IRUSR | S_IWUSR);
+ O_CREAT | O_RDWR | O_TRUNC);
if (Tmpfd < 0)
ereport(ERROR,
(errcode_for_file_access(),
@@ -10444,6 +10456,15 @@ show_unix_socket_permissions(void)
}
static const char *
+show_file_mode_mask(void)
+{
+ static char buf[8];
+
+ snprintf(buf, sizeof(buf), "%04o", file_mode_mask);
+ return buf;
+}
+
+static const char *
show_log_file_mode(void)
{
static char buf[8];
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 157d775..81fd9da 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -49,6 +49,7 @@
#external_pid_file = '' # write an extra PID file
# (change requires restart)
+#file_mode_mask = 0077
#------------------------------------------------------------------------------
# CONNECTIONS AND AUTHENTICATION
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 1ed0d20..283ec09 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -110,6 +110,15 @@ static const char *const auth_methods_local[] = {
NULL
};
+/* File mode to use with chmod on files. */
+#define PG_DEFAULT_FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)
+
+/* File mode to use with chmod on directories. */
+#define PG_DEFAULT_DIR_MODE (S_IRWXU | S_IRWXG | S_IRWXO)
+
+/* Default file mode creation mask. */
+#define PG_DEFAULT_MODE_MASK (S_IRWXG | S_IRWXO)
+
/*
* these values are passed in by makefile defines
*/
@@ -139,6 +148,7 @@ static bool sync_only = false;
static bool show_setting = false;
static bool data_checksums = false;
static char *xlog_dir = "";
+static mode_t file_mode_mask = PG_DEFAULT_MODE_MASK;
/* internal vars */
@@ -1095,6 +1105,11 @@ setup_config(void)
conflines = replace_token(conflines, "#dynamic_shared_memory_type = posix",
repltok);
+ snprintf(repltok, sizeof(repltok), "file_mode_mask = %04o",
+ file_mode_mask);
+ conflines = replace_token(conflines, "#file_mode_mask = 0077",
+ repltok);
+
#if DEFAULT_BACKEND_FLUSH_AFTER > 0
snprintf(repltok, sizeof(repltok), "#backend_flush_after = %dkB",
DEFAULT_BACKEND_FLUSH_AFTER * (BLCKSZ / 1024));
@@ -1131,7 +1146,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, (PG_DEFAULT_FILE_MODE & ~file_mode_mask)) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1151,7 +1166,7 @@ setup_config(void)
sprintf(path, "%s/postgresql.auto.conf", pg_data);
writefile(path, autoconflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, (PG_DEFAULT_FILE_MODE & ~file_mode_mask)) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1235,7 +1250,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_hba.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, (PG_DEFAULT_FILE_MODE & ~file_mode_mask)) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1251,7 +1266,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_ident.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, (PG_DEFAULT_FILE_MODE & ~file_mode_mask)) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -2243,6 +2258,7 @@ usage(const char *progname)
printf(_(" --auth-local=METHOD default authentication method for local-socket connections\n"));
printf(_(" [-D, --pgdata=]DATADIR location for this database cluster\n"));
printf(_(" -E, --encoding=ENCODING set default encoding for new databases\n"));
+ printf(_(" -u, --file-mode-mask=MASK set default file mode creation mask (umask)\n"));
printf(_(" --locale=LOCALE set default locale for new databases\n"));
printf(_(" --lc-collate=, --lc-ctype=, --lc-messages=LOCALE\n"
" --lc-monetary=, --lc-numeric=, --lc-time=LOCALE\n"
@@ -2624,7 +2640,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (pg_mkdir_p(pg_data, S_IRWXU) != 0)
+ if (pg_mkdir_p(pg_data, PG_DEFAULT_DIR_MODE) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2642,7 +2658,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (chmod(pg_data, S_IRWXU) != 0)
+ if (chmod(pg_data, (PG_DEFAULT_DIR_MODE & ~file_mode_mask)) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2710,7 +2726,8 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (pg_mkdir_p(xlog_dir, S_IRWXU) != 0)
+ printf("MASK: %04o\n", file_mode_mask);
+ if (pg_mkdir_p(xlog_dir, PG_DEFAULT_DIR_MODE) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2728,7 +2745,8 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (chmod(xlog_dir, S_IRWXU) != 0)
+ printf("MASK: %04o\n", file_mode_mask);
+ if (chmod(xlog_dir, (PG_DEFAULT_DIR_MODE & ~file_mode_mask)) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2778,7 +2796,7 @@ create_xlog_or_symlink(void)
else
{
/* Without -X option, just make the subdirectory normally */
- if (mkdir(subdirloc, S_IRWXU) < 0)
+ if (mkdir(subdirloc, PG_DEFAULT_DIR_MODE) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, subdirloc, strerror(errno));
@@ -2814,7 +2832,9 @@ initialize_data_directory(void)
setup_signals();
- umask(S_IRWXG | S_IRWXO);
+ /* Set the file mode creation mask. */
+ umask(file_mode_mask);
+ printf(_("Initializing with file mode creation mask: %04o\n"), file_mode_mask);
create_data_directory();
@@ -2834,7 +2854,7 @@ initialize_data_directory(void)
* The parent directory already exists, so we only need mkdir() not
* pg_mkdir_p() here, which avoids some failure modes; cf bug #13853.
*/
- if (mkdir(path, S_IRWXU) < 0)
+ if (mkdir(path, PG_DEFAULT_DIR_MODE) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -2942,6 +2962,7 @@ main(int argc, char *argv[])
{"sync-only", no_argument, NULL, 'S'},
{"waldir", required_argument, NULL, 'X'},
{"data-checksums", no_argument, NULL, 'k'},
+ {"file-mode-mask", required_argument, NULL, 'u'},
{NULL, 0, NULL, 0}
};
@@ -2983,7 +3004,7 @@ main(int argc, char *argv[])
/* process command-line options */
- while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:", long_options, &option_index)) != -1)
+ while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:u:", long_options, &option_index)) != -1)
{
switch (c)
{
@@ -3074,6 +3095,9 @@ main(int argc, char *argv[])
case 'X':
xlog_dir = pg_strdup(optarg);
break;
+ case 'u':
+ file_mode_mask = strtoul(optarg, NULL, 8);
+ break;
default:
/* getopt_long already emitted a complaint */
fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 1a43a2c..6660b7b 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -22,7 +22,7 @@
* Use them for all file activity...
*
* File fd;
- * fd = PathNameOpenFile("foo", O_RDONLY, 0600);
+ * fd = PathNameOpenFile("foo", O_RDONLY);
*
* AllocateFile();
* FreeFile();
@@ -59,13 +59,21 @@ extern int max_files_per_process;
*/
extern int max_safe_fds;
+/* The file mode creation mask for creating new files and directories. */
+extern int file_mode_mask;
+
+/* Define default modes and masks. */
+#define PG_DEFAULT_FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)
+#define PG_DEFAULT_DIR_MODE (S_IRWXU | S_IRWXG | S_IRWXO)
+#define PG_DEFAULT_MODE_MASK (S_IRWXG | S_IRWXO)
/*
* prototypes for functions in fd.c
*/
/* Operations on virtual Files --- equivalent to Unix kernel file ops */
-extern File PathNameOpenFile(FileName fileName, int fileFlags, int fileMode);
+extern File PathNameOpenFilePerm(FileName fileName, int fileFlags, int fileMode);
+extern File PathNameOpenFile(FileName fileName, int fileFlags);
extern File OpenTemporaryFile(bool interXact);
extern void FileClose(File file);
extern int FilePrefetch(File file, off_t offset, int amount);
@@ -94,11 +102,13 @@ extern struct dirent *ReadDir(DIR *dir, const char *dirname);
extern int FreeDir(DIR *dir);
/* Operations to allow use of a plain kernel FD, with automatic cleanup */
-extern int OpenTransientFile(FileName fileName, int fileFlags, int fileMode);
+extern int OpenTransientFilePerm(FileName fileName, int fileFlags, int fileMode);
+extern int OpenTransientFile(FileName fileName, int fileFlags);
extern int CloseTransientFile(int fd);
/* If you've really really gotta have a plain kernel FD, use this */
-extern int BasicOpenFile(FileName fileName, int fileFlags, int fileMode);
+extern int BasicOpenFilePerm(FileName fileName, int fileFlags, int fileMode);
+extern int BasicOpenFile(FileName fileName, int fileFlags);
/* Miscellaneous support routines */
extern void InitFileAccess(void);
On 3/6/17 8:17 AM, Robert Haas wrote:
On Mon, Mar 6, 2017 at 7:38 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Simon Riggs <simon@2ndquadrant.com> writes:
On 1 March 2017 at 01:58, David Steele <david@pgmasters.net> wrote:
PostgreSQL currently requires the file mode mask (umask) to be 0077.
However, this precludes the possibility of a user in the postgres group
performing a backup (or whatever). Now that
pg_start_backup()/pg_stop_backup() privileges can be delegated to an
unprivileged user, it makes sense to also allow a (relatively)
unprivileged user to perform the backup at the file system level as well.+1
I'd ask what is the point, considering that we don't view "cp -a" as a
supported backup technique in the first place./me is confused.
Surely the idea is that you'd like an unprivileged database user to
run pg_start_backup(), an operating system user that can read but not
write the database files to copy them, and then the unprivileged to
then run pg_stop_backup(). I have no opinion on the patch, but I
support the goal. As I said on the surprisingly-controversial thread
about ripping out hard-coded superuser checks, reducing the level of
privilege which someone must have in order to perform a necessary
operation leads to better security. An exclusive backup taken via the
filesystem (probably not via cp, but say via tar or cpio) inevitably
requires the backup user to be able to read the entire cluster
directory, but it doesn't inherently require the backup user to be
able to write the cluster directory.
Limiting privileges also serves to guard against any bugs in tools that
run directly against $PGDATA and do not require write privileges.
--
-David
david@pgmasters.net
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 2/28/17 20:58, David Steele wrote:
This patch introduces a new initdb param, -u/-file-mode-mask, and a new
GUC, file_mode_mask, to allow the default mode of files and directories
in the $PGDATA directory to be modified.
The postmaster.pid file appears not to observe the configured mask.
There ought to be a test, perhaps under src/bin/initdb/, to check for
that kind of thing.
There is no documentation update for initdb.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
From: pgsql-hackers-owner@postgresql.org
[mailto:pgsql-hackers-owner@postgresql.org] On Behalf Of David Steele
PostgreSQL currently requires the file mode mask (umask) to be 0077.
However, this precludes the possibility of a user in the postgres group
performing a backup (or whatever). Now that
pg_start_backup()/pg_stop_backup() privileges can be delegated to an
unprivileged user, it makes sense to also allow a (relatively) unprivileged
user to perform the backup at the file system level as well.
I'd like to help review this. First, let me give some questions and comments.
1.What's the concrete use case of this feature? Do you intend to extend the concept of multiple DBAs to the full range of administration of a single database instance, or just multiple OS users for database backup?
If you think that multiple OS user support is desirable to reduce the administration burdon on a single person, then isn't the automated backup sufficient (such as with cron)?
2.Backup should always be considered with recovery. If you allow another OS user to back up the database, can you allow him to recover the database as well?
For example, assume the PostgreSQL user account (the OS user who does initdb and pg_ctl start/stop) is dba1, and dba2 backs up the database using tar or cpio.
When dba2 restores the backup, the owner of the database cluster becomes dba2. If the file permission only allows one user to write the file, then dba1 can't start the instance.
3.The default location of the SSL key file is $PGDATA, so the permission of the key file is likely to become 0640. But the current postgres requires it to be 0600. See src/backend/libpq/be-secure-openssl.c.
4.I've seen a few users to place .pgpass file in $PGDATA and set the environment variable PGPASSFILE to point to it. They expect it to be back up with other database files. So I'm afraid the permission of .pgpass file also becomes 0640 some time. However, the current code requires it to be 0600. See src/interface/libpq/fe-connect.c.
5.I think some explanation about the concept of multiple OS users is necessary, such as here:
16.1. Short Version
https://www.postgresql.org/docs/devel/static/install-short.html
18.2. Creating a Database Cluster
https://www.postgresql.org/docs/devel/static/creating-cluster.html
[FYI]
Oracle instructs the user, during the software installation, to put "umask 022" in ~/.bashrc or so.
MySQL's files in the data directory appears to be 0640.
Regards
Takayuki Tsunakawa
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Peter,
* Peter Eisentraut (peter.eisentraut@2ndquadrant.com) wrote:
On 2/28/17 20:58, David Steele wrote:
This patch introduces a new initdb param, -u/-file-mode-mask, and a new
GUC, file_mode_mask, to allow the default mode of files and directories
in the $PGDATA directory to be modified.The postmaster.pid file appears not to observe the configured mask.
Good point, it should.
There ought to be a test, perhaps under src/bin/initdb/, to check for
that kind of thing.
Good idea.
There is no documentation update for initdb.
Right, that needs to be fixed.
Thanks!
Stephen
Greetings,
* Tsunakawa, Takayuki (tsunakawa.takay@jp.fujitsu.com) wrote:
From: pgsql-hackers-owner@postgresql.org
[mailto:pgsql-hackers-owner@postgresql.org] On Behalf Of David Steele
PostgreSQL currently requires the file mode mask (umask) to be 0077.
However, this precludes the possibility of a user in the postgres group
performing a backup (or whatever). Now that
pg_start_backup()/pg_stop_backup() privileges can be delegated to an
unprivileged user, it makes sense to also allow a (relatively) unprivileged
user to perform the backup at the file system level as well.I'd like to help review this. First, let me give some questions and comments.
Great!
1.What's the concrete use case of this feature? Do you intend to extend the concept of multiple DBAs to the full range of administration of a single database instance, or just multiple OS users for database backup?
This is to allow a non-postgres user to perform a backup of the
database. Perhaps this could be leveraged for other administration
functions, but it's not clear how off-hand to me and the backup use-case
is the reason for adding this.
If you think that multiple OS user support is desirable to reduce the administration burdon on a single person, then isn't the automated backup sufficient (such as with cron)?
I'm not quite sure what the question here is, but it is desirable to
minimize the amount of access any process requires to only that which is
required to perform its duties. In the case of backup, only read access
to the data directory and access to connect to PG and run certain
functions is required. The ability to run those functions as a
non-superuser was added in 9.6, this continues the work to minimize what
a backup user needs to perform a backup of the system by allowing a user
to have only read-only access to the data directory.
There are multiple reasons why matching the privileges a process has to
only that which is required is good practice. Minimizing impact to
ongoing operations from a compromise of the backup user and reducing the
risk that bugs in backup software could disrupt operations are two of
those.
2.Backup should always be considered with recovery. If you allow another OS user to back up the database, can you allow him to recover the database as well?
That would not be our recommended approach, but it would be possible to
do. Our recommended solution is for the backup user to only be able to
perform the backup. In this use-case, the restore would be run by the
OS user who owns the database (eg: postgres), who would have read-only
access to the backup repository.
To be clear, this is not hypothetical, pgBackrest supports these
configurations and has been tested with this approach. As noted, there
are a few additional items that need to be addressed which weren't
covered in the initial testing (on Debian-based systems, the pid file
and SSL key aren't in the data directory, so they didn't pose a problem,
but they should be addressed so that this can work on other
distributions).
For example, assume the PostgreSQL user account (the OS user who does initdb and pg_ctl start/stop) is dba1, and dba2 backs up the database using tar or cpio.
When dba2 restores the backup, the owner of the database cluster becomes dba2. If the file permission only allows one user to write the file, then dba1 can't start the instance.
If the uesr chose to configure the system with both dba1 and dba2 having
write access to the data directory, performing a restore as dba2 would
be possible and the backup utility could be sure to remove all existing
files and restore them from the backup, ensuring that all of the files
would be owned by a single user (dba2 in this case). Of course, one
would then need to adjust the startup process to run as dba2 instead.
With group write access to all of the files and directories, it may be
possible to actually run with the dba1 user even though the files are
owned by dba2, but we would not recommend it as the result would be a
mix of files owned by one dba or the other. This is not the use-case
this is being developed for.
3.The default location of the SSL key file is $PGDATA, so the permission of the key file is likely to become 0640. But the current postgres requires it to be 0600. See src/backend/libpq/be-secure-openssl.c.
Yes, that needs to be addressed. There was discussion on another thread
that it would be useful to support the SSL key file having group read
access, but since this patch is handling the other files it seems like
it would make sense to do that change here also.
4.I've seen a few users to place .pgpass file in $PGDATA and set the environment variable PGPASSFILE to point to it. They expect it to be back up with other database files. So I'm afraid the permission of .pgpass file also becomes 0640 some time. However, the current code requires it to be 0600. See src/interface/libpq/fe-connect.c.
This is not a configuration which we would recommend (generally
speaking, it's not really a good idea to drop random files into
$PGDATA). That said, there was discussion on another thread about
supporting this with an explicit client-side option to allow it. That
would be independent from this patch, I believe.
5.I think some explanation about the concept of multiple OS users is necessary, such as here:
16.1. Short Version
https://www.postgresql.org/docs/devel/static/install-short.html18.2. Creating a Database Cluster
https://www.postgresql.org/docs/devel/static/creating-cluster.html
I agree that we should update the documention for this, including those.
[FYI]
Oracle instructs the user, during the software installation, to put "umask 022" in ~/.bashrc or so.
MySQL's files in the data directory appears to be 0640.
When it comes to MySQL, at least, this may be distribution dependent.
In any case, it's good to see that we are not the only ones doing this.
Thanks!
Stephen
On 3/10/17 8:12 AM, Stephen Frost wrote:
Peter,
* Peter Eisentraut (peter.eisentraut@2ndquadrant.com) wrote:
On 2/28/17 20:58, David Steele wrote:
This patch introduces a new initdb param, -u/-file-mode-mask, and a new
GUC, file_mode_mask, to allow the default mode of files and directories
in the $PGDATA directory to be modified.The postmaster.pid file appears not to observe the configured mask.
Good point, it should.
Leaving the mask on this file as-is was intentional. At miscinit.c:829:
/* Think not to make the file protection weaker than 0600. See comments
below. */
At miscinit.c:893:
/* We can treat the EPERM-error case as okay because that error 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.) */
I can't see why this explanation does not continue to hold even if
permissions for other files are changed. For the use cases I envision,
I don't think being able to read/manipulate postmaster.pid is important,
only to detect that it is present.
There ought to be a test, perhaps under src/bin/initdb/, to check for
that kind of thing.Good idea.
Agreed, will add to next patch.
There is no documentation update for initdb.
The --file-mode-mask option was added to the option list, but you are
probably referring to a paragraph under description. Will add to the
next patch.
--
-David
david@pgmasters.net
On 3/10/17 8:34 AM, Stephen Frost wrote:
Greetings,
* Tsunakawa, Takayuki (tsunakawa.takay@jp.fujitsu.com) wrote:
From: pgsql-hackers-owner@postgresql.org
[mailto:pgsql-hackers-owner@postgresql.org] On Behalf Of David Steele
PostgreSQL currently requires the file mode mask (umask) to be 0077.
However, this precludes the possibility of a user in the postgres group
performing a backup (or whatever). Now that
pg_start_backup()/pg_stop_backup() privileges can be delegated to an
unprivileged user, it makes sense to also allow a (relatively) unprivileged
user to perform the backup at the file system level as well.I'd like to help review this. First, let me give some questions and comments.
Much appreciated!
3.The default location of the SSL key file is $PGDATA, so the permission of the key file is likely to become 0640. But the current postgres requires it to be 0600. See src/backend/libpq/be-secure-openssl.c.
Yes, that needs to be addressed. There was discussion on another thread
that it would be useful to support the SSL key file having group read
access, but since this patch is handling the other files it seems like
it would make sense to do that change here also.
Perhaps, but since these files are not setup by initdb I'm not sure if
we should be handling their permissions. This seems to be a
distro-specific issue.
It seems to me that it would be best to advise in the docs that these
files should be relocated if they won't be readable by the backup user.
In any event, I'm not convinced that backing up server private keys is a
good idea.
5.I think some explanation about the concept of multiple OS users is necessary, such as here:
16.1. Short Version
https://www.postgresql.org/docs/devel/static/install-short.html18.2. Creating a Database Cluster
https://www.postgresql.org/docs/devel/static/creating-cluster.htmlI agree that we should update the documention for this, including those.
We'll add that to the next patch.
Thanks,
--
-David
david@pgmasters.net
David Steele <david@pgmasters.net> writes:
At miscinit.c:893:
/* We can treat the EPERM-error case as okay because that error 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.) */
TBH, the fact that we're relying on 0600 mode for considerations such
as these makes me tremendously afraid of this whole patch. I think that
the claimed advantages are not anywhere near worth the risk that somebody
is going to destroy their database because we weakened some aspect of the
protection against starting multiple postmasters in a database directory.
At the very least, I'd want to see much closer analysis of the safety
issues than I've seen so far in this thread. And since the proposed
patch falsifies the above-quoted comment (and probably others), why
hasn't it updated it?
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
... oh, and now that I've actually looked at the patch, I think it's
a seriously bad idea to proceed by removing the mode parameter to
PathNameOpenFile et al. That's basically doubling down on an assumption
that there are NO places in the backend, and never will be any, in which
we want to create files with nondefault permissions. That assumption
seems broken on its face. It also makes the patch exceedingly invasive
for extensions.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi Tom,
On 3/13/17 1:13 PM, Tom Lane wrote:
... oh, and now that I've actually looked at the patch, I think it's
a seriously bad idea to proceed by removing the mode parameter to
PathNameOpenFile et al. That's basically doubling down on an assumption
that there are NO places in the backend, and never will be any, in which
we want to create files with nondefault permissions. That assumption
seems broken on its face. It also makes the patch exceedingly invasive
for extensions.
I think it's a bad idea to have the same parameters copied over and over
throughout the code with slight variations (e.g. 0600 vs S_IRUSR |
S_IWUSR) but the same intent.
In all cases there is another version of the function (added by this
patch) that accepts a mode parameter. In practice this was only needed
in one place, be_lo_export(). I think this makes a pretty good argument
for standardization/simplification in other areas.
Thanks,
--
-David
david@pgmasters.net
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi Tom,
On 3/13/17 1:03 PM, Tom Lane wrote:
David Steele <david@pgmasters.net> writes:
At miscinit.c:893:
/* We can treat the EPERM-error case as okay because that error 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.) */TBH, the fact that we're relying on 0600 mode for considerations such
as these makes me tremendously afraid of this whole patch. I think that
the claimed advantages are not anywhere near worth the risk that somebody
is going to destroy their database because we weakened some aspect of the
protection against starting multiple postmasters in a database directory.
I don't see a risk if the user uses umask 0027 which is the example
given in the docs. I'm happy to change this example to a strong
recommendation.
At the very least, I'd want to see much closer analysis of the safety
issues than I've seen so far in this thread.
I think it's clear that there would be safety risks with unwise umask
choices. I also think the example/recommended umask is safe.
Running external processes as the postgres user carries serious risks as
well. Not only with regards to data access but the danger of corruption
due to bugs. If a process does not require write access to do its job
then why take that risk?
To (hopefully) address your concerns, I'll perform an analysis of
starting multiple postmasters with a variety of umask choices and report
the outcomes here.
And since the proposed
patch falsifies the above-quoted comment (and probably others), why
hasn't it updated it?
That was an oversight on my part. I'll update it in the next patch.
Thanks,
--
-David
david@pgmasters.net
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
David Steele <david@pgmasters.net> writes:
On 3/13/17 1:03 PM, Tom Lane wrote:
TBH, the fact that we're relying on 0600 mode for considerations such
as these makes me tremendously afraid of this whole patch. I think that
the claimed advantages are not anywhere near worth the risk that somebody
is going to destroy their database because we weakened some aspect of the
protection against starting multiple postmasters in a database directory.
I don't see a risk if the user uses umask 0027 which is the example
given in the docs. I'm happy to change this example to a strong
recommendation.
I do not want a "strong recommendation". I want "we don't let you
break this". We don't let you run the postmaster as root either,
even though there have been repeated requests to remove that safety
check.
It might be all right if we forcibly or'd 027 into whatever umask
the user tries to provide; not sure. The existing safety analysis,
such as the cited comment, has all been based on the assumption of
file mode 600 not mode 640. I'm not certain that we always attempt
to open-for-writing files that we expect to be exclusively accessible
by the postmaster.
Anyway, given that we do that analysis, I'd rather not expose this
as a "here, set the umask you want" variable. I think a bool saying
"allow group access" (translating to exactly two supported umasks,
027 and 077) would be simpler from the user's standpoint and much
easier to reason about. I don't see the value in having to think
about what happens if the user supplies a mask like 037 or 067.
I also don't especially want to have to analyze cases like "what
happens if user initdb'd with mask X but then changes the GUC and
restarts the postmaster?". Maybe the right thing is to not expose
this as a GUC at all, but drive it off the permissions observed on
the data directory at postmaster start. Viz:
* $PGDATA has permissions 0700: adopt umask 077
* $PGDATA has permissions 0750: adopt umask 027
* anything else: fail
That way, a "chmod -R" would be the necessary and sufficient procedure
for switching from one case to the other.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 3/13/17 2:16 PM, Tom Lane wrote:
David Steele <david@pgmasters.net> writes:
On 3/13/17 1:03 PM, Tom Lane wrote:
TBH, the fact that we're relying on 0600 mode for considerations such
as these makes me tremendously afraid of this whole patch. I think that
the claimed advantages are not anywhere near worth the risk that somebody
is going to destroy their database because we weakened some aspect of the
protection against starting multiple postmasters in a database directory.I don't see a risk if the user uses umask 0027 which is the example
given in the docs. I'm happy to change this example to a strong
recommendation.
<...>
Anyway, given that we do that analysis, I'd rather not expose this
as a "here, set the umask you want" variable. I think a bool saying
"allow group access" (translating to exactly two supported umasks,
027 and 077) would be simpler from the user's standpoint and much
easier to reason about. I don't see the value in having to think
about what happens if the user supplies a mask like 037 or 067.
We debated a flag vs a umask and came down on the side of flexibility.
I'm perfectly happy with using a flag instead.
I also don't especially want to have to analyze cases like "what
happens if user initdb'd with mask X but then changes the GUC and
restarts the postmaster?". Maybe the right thing is to not expose
this as a GUC at all, but drive it off the permissions observed on
the data directory at postmaster start. Viz:* $PGDATA has permissions 0700: adopt umask 077
* $PGDATA has permissions 0750: adopt umask 027
* anything else: fail
How about a GUC, allow_group_access, that when set will enforce
permissions and set the umask accordingly, and when not set will follow
$PGDATA as proposed above?
Not much we can do for Windows, though. I think it would have to WARN
if the GUC is set and then continue as usual.
That way, a "chmod -R" would be the necessary and sufficient procedure
for switching from one case to the other.
I'm OK with that if you think it's the best course, but perhaps the GUC
would be better because it can detect accidental permission changes.
--
-David
david@pgmasters.net
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
David Steele <david@pgmasters.net> writes:
On 3/13/17 2:16 PM, Tom Lane wrote:
I also don't especially want to have to analyze cases like "what
happens if user initdb'd with mask X but then changes the GUC and
restarts the postmaster?". Maybe the right thing is to not expose
this as a GUC at all, but drive it off the permissions observed on
the data directory at postmaster start. Viz:* $PGDATA has permissions 0700: adopt umask 077
* $PGDATA has permissions 0750: adopt umask 027
* anything else: fail
How about a GUC, allow_group_access, that when set will enforce
permissions and set the umask accordingly, and when not set will follow
$PGDATA as proposed above?
Seems overcomplicated ...
Not much we can do for Windows, though. I think it would have to WARN
if the GUC is set and then continue as usual.
Yeah, the Windows port has been weak in this area all along. I don't
think it's incumbent on you to make it better.
That way, a "chmod -R" would be the necessary and sufficient procedure
for switching from one case to the other.
I'm OK with that if you think it's the best course, but perhaps the GUC
would be better because it can detect accidental permission changes.
If we're only checking file permissions at postmaster start, I think it's
illusory to suppose that we're offering very much protection against
accidental changes. A chmod applied while the postmaster is running
could still break things, and we'd not notice till the next restart.
But it might be worth thinking about whether we want to encourage people
to do manual chmod's at all; that's fairly easy to get wrong, particularly
given the difference in X bits that should be applied to files and
directories. Another approach that could be worth considering is
a PGC_POSTMASTER GUC with just two states (group access or not) and
make it the postmaster's responsibility to do the equivalent of chmod -R
to make the file tree match the GUC. I think we do a tree scan anyway
for other purposes, so correcting any wrong file permissions might not
be much added work in the normal case.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
From: David Steele [mailto:david@pgmasters.net]
3.The default location of the SSL key file is $PGDATA, so the permission
of the key file is likely to become 0640. But the current postgres requires
it to be 0600. See src/backend/libpq/be-secure-openssl.c.Yes, that needs to be addressed. There was discussion on another
thread that it would be useful to support the SSL key file having
group read access, but since this patch is handling the other files it
seems like it would make sense to do that change here also.Perhaps, but since these files are not setup by initdb I'm not sure if we
should be handling their permissions. This seems to be a distro-specific
issue.It seems to me that it would be best to advise in the docs that these files
should be relocated if they won't be readable by the backup user.
In any event, I'm not convinced that backing up server private keys is a
good idea.
Maybe so, but it's convenient to be able to store the key and certificate files in $PGDATA and back them up together. If the database backup were stolen through the compromised backup OS user, then the malicious person can read the data anyway regardless of whether the key file is there or not. That's because the key is not for encrypting data at rest.
Related to this, please see:
https://www.postgresql.org/docs/devel/static/ssl-tcp.html
"On Unix systems, the permissions on server.key must disallow any access to world or group; achieve this by the command chmod 0600 server.key. Alternatively, the file can be owned by root and have group read access (that is, 0640 permissions). That setup is intended for installations where certificate and key files are managed by the operating system. The user under which the PostgreSQL server runs should then be made a member of the group that has access to those certificate and key files."
In the latter case, the file owner is root and the permission is 0640. At first I was a bit confused and misunderstood that the PostgreSQL user account and the backup OS user needs to belong to the same OS group. But that's not the case. The group of the key file can be, for example, "ssl_cert", the PostgreSQL user account belongs to the OS group "ssl_cert" and "dba", and the backup OS user only belongs to "backup." This can prevent the backup OS user from reading the key file. I think it would be better to have some explanation with examples in the above section.
Regards
Takayuki Tsunakawa
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 3/14/17 4:23 AM, Tsunakawa, Takayuki wrote:
From: David Steele [mailto:david@pgmasters.net]
3.The default location of the SSL key file is $PGDATA, so the permission
of the key file is likely to become 0640. But the current postgres requires
it to be 0600. See src/backend/libpq/be-secure-openssl.c.Yes, that needs to be addressed. There was discussion on another
thread that it would be useful to support the SSL key file having
group read access, but since this patch is handling the other files it
seems like it would make sense to do that change here also.Perhaps, but since these files are not setup by initdb I'm not sure if we
should be handling their permissions. This seems to be a distro-specific
issue.It seems to me that it would be best to advise in the docs that these files
should be relocated if they won't be readable by the backup user.
In any event, I'm not convinced that backing up server private keys is a
good idea.Maybe so, but it's convenient to be able to store the key and certificate files in $PGDATA and back them up together. If the database backup were stolen through the compromised backup OS user, then the malicious person can read the data anyway regardless of whether the key file is there or not. That's because the key is not for encrypting data at rest.
Sure, but having the private key may allow them to get new data from the
server as well as the data from the backup.
To be clear, the default for this patch is to leave permissions exactly
as they are now. It also provides alternatives that may or not be
useful in all cases.
Related to this, please see:
https://www.postgresql.org/docs/devel/static/ssl-tcp.html
"On Unix systems, the permissions on server.key must disallow any access to world or group; achieve this by the command chmod 0600 server.key. Alternatively, the file can be owned by root and have group read access (that is, 0640 permissions). That setup is intended for installations where certificate and key files are managed by the operating system. The user under which the PostgreSQL server runs should then be made a member of the group that has access to those certificate and key files."
In the latter case, the file owner is root and the permission is 0640. At first I was a bit confused and misunderstood that the PostgreSQL user account and the backup OS user needs to belong to the same OS group. But that's not the case. The group of the key file can be, for example, "ssl_cert", the PostgreSQL user account belongs to the OS group "ssl_cert" and "dba", and the backup OS user only belongs to "backup." This can prevent the backup OS user from reading the key file. I think it would be better to have some explanation with examples in the above section.
If the backup user is in the same group as the postgres user and in the
ssl_cert group then backups of the certs would be possible using group
reads. Restores will be a little tricky, though, because of the need to
set ownership to root. The restore would need to be run as root or the
permissions fixed up after the restore completes.
--
-David
david@pgmasters.net
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 3/13/17 3:03 PM, Tom Lane wrote:
David Steele <david@pgmasters.net> writes:
On 3/13/17 2:16 PM, Tom Lane wrote:
I also don't especially want to have to analyze cases like "what
happens if user initdb'd with mask X but then changes the GUC and
restarts the postmaster?". Maybe the right thing is to not expose
this as a GUC at all, but drive it off the permissions observed on
the data directory at postmaster start. Viz:* $PGDATA has permissions 0700: adopt umask 077
* $PGDATA has permissions 0750: adopt umask 027
* anything else: fail
How about a GUC, allow_group_access, that when set will enforce
permissions and set the umask accordingly, and when not set will follow
$PGDATA as proposed above?Seems overcomplicated ...
Yeah. It wouldn't be a lot of fun to document, that's for sure.
That way, a "chmod -R" would be the necessary and sufficient procedure
for switching from one case to the other.I'm OK with that if you think it's the best course, but perhaps the GUC
would be better because it can detect accidental permission changes.If we're only checking file permissions at postmaster start, I think it's
illusory to suppose that we're offering very much protection against
accidental changes. A chmod applied while the postmaster is running
could still break things, and we'd not notice till the next restart.
Fair enough.
But it might be worth thinking about whether we want to encourage people
to do manual chmod's at all; that's fairly easy to get wrong, particularly
given the difference in X bits that should be applied to files and
directories. Another approach that could be worth considering is
a PGC_POSTMASTER GUC with just two states (group access or not) and
make it the postmaster's responsibility to do the equivalent of chmod -R
to make the file tree match the GUC. I think we do a tree scan anyway
for other purposes, so correcting any wrong file permissions might not
be much added work in the normal case.
The majority of scanning is done in recovery (to find and remove
unlogged tables) and I'm not sure we would want to add that overhead to
normal startup.
--
-David
david@pgmasters.net
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
From: pgsql-hackers-owner@postgresql.org
[mailto:pgsql-hackers-owner@postgresql.org] On Behalf Of David Steele
Sure, but having the private key may allow them to get new data from the
server as well as the data from the backup.
You are right. My rough intent was that the data is stolen anyway. So, I thought it might not be so bad to expect to be able to back up the SSL key file in $PGDATA together with the database. If it's bad, then the default value of ssl_key_file (=$PGDATA/ssl.key) should be disallowed.
https://www.postgresql.org/docs/devel/static/ssl-tcp.html
"On Unix systems, the permissions on server.key must disallow any access
to world or group; achieve this by the command chmod 0600 server.key.
Alternatively, the file can be owned by root and have group read access
(that is, 0640 permissions). That setup is intended for installations where
certificate and key files are managed by the operating system. The user
under which the PostgreSQL server runs should then be made a member of the
group that has access to those certificate and key files."In the latter case, the file owner is root and the permission is 0640.
At first I was a bit confused and misunderstood that the PostgreSQL user
account and the backup OS user needs to belong to the same OS group. But
that's not the case. The group of the key file can be, for example,
"ssl_cert", the PostgreSQL user account belongs to the OS group "ssl_cert"
and "dba", and the backup OS user only belongs to "backup." This can prevent
the backup OS user from reading the key file. I think it would be better
to have some explanation with examples in the above section.If the backup user is in the same group as the postgres user and in the
ssl_cert group then backups of the certs would be possible using group reads.
Restores will be a little tricky, though, because of the need to set
ownership to root. The restore would need to be run as root or the
permissions fixed up after the restore completes.
Yes, but I thought, from the following message, that you do not recommend that the backup user be able to read the SSL key file. So, I proposed to describe the example configuration to achieve that -- postgres user in dba and ssl_cert group, and a separate backup user in only dba group.
It seems to me that it would be best to advise in the docs that these
files should be relocated if they won't be readable by the backup user.
In any event, I'm not convinced that backing up server private keys
is a good idea.
To be clear, the default for this patch is to leave permissions exactly
as they are now. It also provides alternatives that may or not be useful
in all cases.
So you think there are configurations that may be useful or not, don't you? Adding a new parameter could possibly complicate what users have to consider. Maximal flexibility could lead to insecure misuse. So I think it would be better to describe secure and practical configuration examples in the SSL section and/or the backup chapter. The configuration factor includes whether the backup user is different from the postgres user, where the SSL key file is placed, the owner of the SSL key file, whether the backup user can read the SSL key file.
Regards
Takayuki Tsunakawa
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
From: pgsql-hackers-owner@postgresql.org
[mailto:pgsql-hackers-owner@postgresql.org] On Behalf Of David Steele
But it might be worth thinking about whether we want to encourage
people to do manual chmod's at all; that's fairly easy to get wrong,
particularly given the difference in X bits that should be applied to
files and directories. Another approach that could be worth
considering is a PGC_POSTMASTER GUC with just two states (group access
or not) and make it the postmaster's responsibility to do the
equivalent of chmod -R to make the file tree match the GUC. I think
we do a tree scan anyway for other purposes, so correcting any wrong
file permissions might not be much added work in the normal case.The majority of scanning is done in recovery (to find and remove unlogged
tables) and I'm not sure we would want to add that overhead to normal startup.
I'm on David's side, too. I don't postmaster to always scan all files at startup.
On the other hand, just doing "chmod -R $PGDATA" is not enough, because chmod doesn't follow the symbolic links. Symbolic links are used for pg_tblspc/* and pg_wal at least. FYI, MySQL's manual describes the pithole like this:
https://dev.mysql.com/doc/refman/8.0/en/changing-mysql-user.html
----------------------------------------
2. Change the database directories and files so that user_name has privileges to read and write files in them (you might need to do this as the Unix root user):
shell> chown -R user_name /path/to/mysql/datadir
If you do not do this, the server will not be able to access databases or tables when it runs as user_name.
If directories or files within the MySQL data directory are symbolic links, chown -R might not follow symbolic links for you. If it does not, you will also need to follow those links and change the directories and files they point to.
----------------------------------------
I think we also need to describe the procedure carefully. That said, it would be best to make users aware of a configuration alternative (group access) with enough documentation when they first build the database or upgrade the database cluster. Just describing the alternative only in initdb reference page would result in being unaware of the better configuration, like --data-checksum.
Regards
Takayuki Tsunakawa
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 3/15/17 1:56 AM, Tsunakawa, Takayuki wrote:
From: pgsql-hackers-owner@postgresql.org
[mailto:pgsql-hackers-owner@postgresql.org] On Behalf Of David Steele
Sure, but having the private key may allow them to get new data from the
server as well as the data from the backup.You are right. My rough intent was that the data is stolen anyway. So, I thought it might not be so bad to expect to be able to back up the SSL key file in $PGDATA together with the database. If it's bad, then the default value of ssl_key_file (=$PGDATA/ssl.key) should be disallowed.
I think it really depends on the situation and the user needs to make an
evaluation of the security risks for themselves.
If the backup user is in the same group as the postgres user and in the
ssl_cert group then backups of the certs would be possible using group reads.
Restores will be a little tricky, though, because of the need to set
ownership to root. The restore would need to be run as root or the
permissions fixed up after the restore completes.Yes, but I thought, from the following message, that you do not recommend that the backup user be able to read the SSL key file. So, I proposed to describe the example configuration to achieve that -- postgres user in dba and ssl_cert group, and a separate backup user in only dba group.
That would work as long as the key was not in $PGDATA. Most database
backup software does not have a --ignore option because of the obvious
dangers.
It seems to me that it would be best to advise in the docs that these
files should be relocated if they won't be readable by the backup user.
In any event, I'm not convinced that backing up server private keys
is a good idea.To be clear, the default for this patch is to leave permissions exactly
as they are now. It also provides alternatives that may or not be useful
in all cases.So you think there are configurations that may be useful or not, don't you? Adding a new parameter could possibly complicate what users have to consider. Maximal flexibility could lead to insecure misuse. So I think it would be better to describe secure and practical configuration examples in the SSL section and/or the backup chapter. The configuration factor includes whether the backup user is different from the postgres user, where the SSL key file is placed, the owner of the SSL key file, whether the backup user can read the SSL key file.
I think we are more or less on the same page, and you'd like to see
documentation that shows examples of secure configurations when group
access is allow. I agree and I'm working on documentation for all the
sections you recommended.
Thanks,
--
-David
david@pgmasters.net
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 3/15/17 3:00 AM, Tsunakawa, Takayuki wrote:
From: pgsql-hackers-owner@postgresql.org
[mailto:pgsql-hackers-owner@postgresql.org] On Behalf Of David Steele
But it might be worth thinking about whether we want to encourage
people to do manual chmod's at all; that's fairly easy to get wrong,
particularly given the difference in X bits that should be applied to
files and directories. Another approach that could be worth
considering is a PGC_POSTMASTER GUC with just two states (group access
or not) and make it the postmaster's responsibility to do the
equivalent of chmod -R to make the file tree match the GUC. I think
we do a tree scan anyway for other purposes, so correcting any wrong
file permissions might not be much added work in the normal case.The majority of scanning is done in recovery (to find and remove unlogged
tables) and I'm not sure we would want to add that overhead to normal startup.I'm on David's side, too. I don't postmaster to always scan all files at startup.
On the other hand, just doing "chmod -R $PGDATA" is not enough, because chmod doesn't follow the symbolic links. Symbolic links are used for pg_tblspc/* and pg_wal at least. FYI, MySQL's manual describes the pithole like this:
Good point - I think we'll need to add that to the docs as well.
I think we also need to describe the procedure carefully. That said, it would be best to make users aware of a configuration alternative (group access) with enough documentation when they first build the database or upgrade the database cluster. Just describing the alternative only in initdb reference page would result in being unaware of the better configuration, like --data-checksum.
I'm working on a new approach incorporating everybody's suggestions and
enhanced documentation. It should be ready on Monday.
--
-David
david@pgmasters.net
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Mar 15, 2017 at 3:00 AM, Tsunakawa, Takayuki
<tsunakawa.takay@jp.fujitsu.com> wrote:
I'm on David's side, too. I don't postmaster to always scan all files at startup.
+1. Even just doing it during crash recovery, it can take a
regrettably long time on machines with tons of relations and a very
slow disk. I've been sort of thinking that we should add some logging
there so that users know what's happening when that code goes into the
tank - I've seen that come up 3 or 4 times now, and I'm getting tired
of telling people to run strace to find out.
I think Tom's concerns about people doing insecure stuff are
excessive. People can do insecure stuff no matter what we do, and
trying to prevent them often leads to them doing even-more-insecure
stuff. That having been aid, I do wonder whether the idea of allowing
group read privileges specifically might be a better concept than a
umask, though, because (1) it's not obvious that there's a real use
case for anything other than group read privileges, so why not support
exactly that to avoid user confusion and (2) umask is a pretty
specific concept that may not apply on every platform. For example,
AFS has an ACL list instead of using the traditional UNIX permission
bits, and I'm not sure Windows has the umask concept exactly either.
So wording what we're trying to do a bit more generically might be
smart.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 3/18/17 3:57 PM, Robert Haas wrote:
On Wed, Mar 15, 2017 at 3:00 AM, Tsunakawa, Takayuki
<tsunakawa.takay@jp.fujitsu.com> wrote:I'm on David's side, too. I don't postmaster to always scan all files at startup.
+1. Even just doing it during crash recovery, it can take a
regrettably long time on machines with tons of relations and a very
slow disk. I've been sort of thinking that we should add some logging
there so that users know what's happening when that code goes into the
tank - I've seen that come up 3 or 4 times now, and I'm getting tired
of telling people to run strace to find out.
Most of this time is spent scanning for unlogged tables. We were
working on a patch to put unlogged tables (except for the init fork) in
a pg_unlogged directory created for each tablespace, just like
pgsql_temp. This would make cleanup a lot faster and make it easier for
backup software to skip relations that can't be restored and only take
up space.
Unfortunately the patch was not ready for the last CF and maybe would
not have been a good candidate anyway due to complexity. It's still on
our radar, though.
I think Tom's concerns about people doing insecure stuff are
excessive. People can do insecure stuff no matter what we do, and
trying to prevent them often leads to them doing even-more-insecure
stuff. That having been aid, I do wonder whether the idea of allowing
group read privileges specifically might be a better concept than a
umask, though, because (1) it's not obvious that there's a real use
case for anything other than group read privileges, so why not support
exactly that to avoid user confusion and (2) umask is a pretty
specific concept that may not apply on every platform. For example,
AFS has an ACL list instead of using the traditional UNIX permission
bits, and I'm not sure Windows has the umask concept exactly either.
So wording what we're trying to do a bit more generically might be
smart.
We took Tom's advice to heart and this is the direction the patch is
currently going in. Even the GUC may be too much as there are number of
tools that write into PGDATA but don't read postgresql.conf. It looks
like using the permissions of PGDATA may be the best way to go.
In any case, the changes required have enlarged the size and scope of
the patch considerably and we are not confident that it will be done in
time to commit for v10.
I have marked this submission "Returned with Feedback".
--
-David
david@pgmasters.net
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 3/21/17 2:02 PM, David Steele wrote:
On 3/18/17 3:57 PM, Robert Haas wrote:
I think Tom's concerns about people doing insecure stuff are
excessive. People can do insecure stuff no matter what we do, and
trying to prevent them often leads to them doing even-more-insecure
stuff. That having been aid, I do wonder whether the idea of allowing
group read privileges specifically might be a better concept than a
umask, though, because (1) it's not obvious that there's a real use
case for anything other than group read privileges, so why not support
exactly that to avoid user confusion and (2) umask is a pretty
specific concept that may not apply on every platform. For example,
AFS has an ACL list instead of using the traditional UNIX permission
bits, and I'm not sure Windows has the umask concept exactly either.
So wording what we're trying to do a bit more generically might be
smart.We took Tom's advice to heart and this is the direction the patch is
currently going in. Even the GUC may be too much as there are number of
tools that write into PGDATA but don't read postgresql.conf. It looks
like using the permissions of PGDATA may be the best way to go.In any case, the changes required have enlarged the size and scope of
the patch considerably and we are not confident that it will be done in
time to commit for v10.I have marked this submission "Returned with Feedback".
Attached is a new patch set that should address various concerns raised
in this thread.
1) group-access-v3-01-mkdir.patch
Abstracts all mkdir calls in the backend into a MakeDirectory() function
implemented in fd.c. This did not get committed in September as part of
0c5803b450e but I still think it has value. However, I have kept it
separate to reduce noise in the second patch. The mkdir() calls could
also be modified to use PG_DIR_MODE_DEFAULT with equivalent results.
2) group-access-v3-02-group.patch
This is a "GUC-less" implementation of group read access that depends on
the mode of the $PGDATA directory to determine which mode to use for
subsequent writes. The initdb option is preserved to allow group access
to be enabled when the cluster is initialized.
Only two modes are allowed (700, 750) and the error message on startup
is hard-coded to address translation concerns.
I'll add this to the 2018-01 CF.
Thanks!
--
-David
david@pgmasters.net
Attachments:
group-access-v3-01-mkdir.patchtext/plain; charset=UTF-8; name=group-access-v3-01-mkdir.patch; x-mac-creator=0; x-mac-type=0Download
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 3e9a12dacd..cc6e46f7c4 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4086,7 +4086,7 @@ ValidateXLOGDirectoryStructure(void)
{
ereport(LOG,
(errmsg("creating missing WAL directory \"%s\"", path)));
- if (mkdir(path, S_IRWXU) < 0)
+ if (MakeDirectory(path) < 0)
ereport(FATAL,
(errmsg("could not create missing directory \"%s\": %m",
path)));
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index d574e4dd00..a0cafedd9b 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -151,7 +151,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
else
{
/* Directory creation failed? */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakeDirectory(dir) < 0)
{
char *parentdir;
@@ -173,7 +173,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
get_parent_directory(parentdir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectory(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -184,7 +184,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
parentdir = pstrdup(dir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectory(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -192,7 +192,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
pfree(parentdir);
/* Create database directory */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakeDirectory(dir) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -279,7 +279,7 @@ CreateTableSpace(CreateTableSpaceStmt *stmt)
/*
* Check that location isn't too long. Remember that we're going to append
* 'PG_XXX/<dboid>/<relid>_<fork>.<nnn>'. FYI, we never actually
- * reference the whole path here, but mkdir() uses the first two parts.
+ * reference the whole path here, but MakeDirectory() uses the first two parts.
*/
if (strlen(location) + 1 + strlen(TABLESPACE_VERSION_DIRECTORY) + 1 +
OIDCHARS + 1 + OIDCHARS + 1 + FORKNAMECHARS + 1 + OIDCHARS > MAXPGPATH)
@@ -574,7 +574,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* Attempt to coerce target directory to safe permissions. If this fails,
* it doesn't exist or has the wrong owner.
*/
- if (chmod(location, S_IRWXU) != 0)
+ if (chmod(location, PG_DIR_MODE_DEFAULT) != 0)
{
if (errno == ENOENT)
ereport(ERROR,
@@ -599,7 +599,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
if (stat(location_with_version_dir, &st) == 0 && S_ISDIR(st.st_mode))
{
if (!rmtree(location_with_version_dir, true))
- /* If this failed, mkdir() below is going to error. */
+ /* If this failed, MakeDirectory() below is going to error. */
ereport(WARNING,
(errmsg("some useless files may be left behind in old database directory \"%s\"",
location_with_version_dir)));
@@ -610,7 +610,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* The creation of the version directory prevents more than one tablespace
* in a single location.
*/
- if (mkdir(location_with_version_dir, S_IRWXU) < 0)
+ if (MakeDirectory(location_with_version_dir) < 0)
{
if (errno == EEXIST)
ereport(ERROR,
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 17c7f7e78f..0049778dfd 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -589,7 +589,7 @@ PostmasterMain(int argc, char *argv[])
/*
* for security, no dir or file created can be group or other accessible
*/
- umask(S_IRWXG | S_IRWXO);
+ umask(PG_MODE_MASK_DEFAULT);
/*
* Initialize random(3) so we don't get the same values in every run.
@@ -4495,7 +4495,7 @@ internal_forkexec(int argc, char *argv[], Port *port)
* As in OpenTemporaryFileInTablespace, try to make the temp-file
* directory
*/
- mkdir(PG_TEMP_FILES_DIR, S_IRWXU);
+ MakeDirectory(PG_TEMP_FILES_DIR);
fp = AllocateFile(tmpfilename, PG_BINARY_W);
if (!fp)
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index aeb117796d..ffb7540355 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -41,6 +41,7 @@
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
#include "storage/dsm.h"
+#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pg_shmem.h"
@@ -322,7 +323,7 @@ SysLoggerMain(int argc, char *argv[])
/*
* Also, create new directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ MakeDirectory(Log_directory);
}
if (strcmp(Log_filename, currentLogFilename) != 0)
{
@@ -564,7 +565,7 @@ SysLogger_Start(void)
/*
* Create log directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ MakeDirectory(Log_directory);
/*
* The initial logfile is created right in the postmaster, to verify that
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 0d27b6f39e..6d10699c2c 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -1166,13 +1166,13 @@ CreateSlotOnDisk(ReplicationSlot *slot)
* It's just barely possible that some previous effort to create or drop a
* slot with this name left a temp directory lying around. If that seems
* to be the case, try to remove it. If the rmtree() fails, we'll error
- * out at the mkdir() below, so we don't bother checking success.
+ * out at the MakeDirectory() below, so we don't bother checking success.
*/
if (stat(tmppath, &st) == 0 && S_ISDIR(st.st_mode))
rmtree(tmppath, true);
/* Create and fsync the temporary slot directory. */
- if (mkdir(tmppath, S_IRWXU) < 0)
+ if (MakeDirectory(tmppath) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
diff --git a/src/backend/storage/file/copydir.c b/src/backend/storage/file/copydir.c
index d169e9c8bb..784400a285 100644
--- a/src/backend/storage/file/copydir.c
+++ b/src/backend/storage/file/copydir.c
@@ -41,7 +41,7 @@ copydir(char *fromdir, char *todir, bool recurse)
char fromfile[MAXPGPATH * 2];
char tofile[MAXPGPATH * 2];
- if (mkdir(todir, S_IRWXU) != 0)
+ if (MakeDirectory(todir) != 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m", todir)));
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index f449ee5c51..342401b8dd 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -1434,7 +1434,7 @@ PathNameOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode)
void
PathNameCreateTemporaryDir(const char *basedir, const char *directory)
{
- if (mkdir(directory, S_IRWXU) < 0)
+ if (MakeDirectory(directory) < 0)
{
if (errno == EEXIST)
return;
@@ -1444,14 +1444,14 @@ PathNameCreateTemporaryDir(const char *basedir, const char *directory)
* EEXIST to close a race against another process following the same
* algorithm.
*/
- if (mkdir(basedir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectory(basedir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary directory \"%s\": %m",
basedir)));
/* Try again. */
- if (mkdir(directory, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectory(directory) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary subdirectory \"%s\": %m",
@@ -1601,11 +1601,11 @@ OpenTemporaryFileInTablespace(Oid tblspcOid, bool rejectError)
* We might need to create the tablespace's tempfile directory, if no
* one has yet done so.
*
- * Don't check for error from mkdir; it could fail if someone else
+ * Don't check error from MakeDirectory; it could fail if someone else
* just did the same thing. If it doesn't work then we'll bomb out on
* the second create attempt, instead.
*/
- mkdir(tempdirpath, S_IRWXU);
+ MakeDirectory(tempdirpath);
file = PathNameOpenFile(tempfilepath,
O_RDWR | O_CREAT | O_TRUNC | PG_BINARY);
@@ -3533,3 +3533,22 @@ fsync_parent_path(const char *fname, int elevel)
return 0;
}
+
+/*
+ * Create a directory with MakeDirectoryPerm() and pass PG_DIR_MODE_DEFAULT to
+ * the directoryMode parameter.
+ */
+int
+MakeDirectory(const char *directoryName)
+{
+ return MakeDirectoryPerm(directoryName, PG_DIR_MODE_DEFAULT);
+}
+
+/*
+ * Create a directory with the specified mode.
+ */
+int
+MakeDirectoryPerm(const char *directoryName, mode_t directoryMode)
+{
+ return mkdir(directoryName, directoryMode);
+}
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index dfb47e7c39..f3c9d9f2d3 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -132,8 +132,8 @@ proc_exit(int code)
else
snprintf(gprofDirName, 32, "gprof/%d", (int) getpid());
- mkdir("gprof", S_IRWXU | S_IRWXG | S_IRWXO);
- mkdir(gprofDirName, S_IRWXU | S_IRWXG | S_IRWXO);
+ MakeDirectoryPerm("gprof", S_IRWXU | S_IRWXG | S_IRWXO);
+ MakeDirectoryPerm(gprofDirName, S_IRWXU | S_IRWXG | S_IRWXO);
chdir(gprofDirName);
}
#endif
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index dc2eb35f06..0e50556abe 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -41,6 +41,12 @@
#include <dirent.h>
+/*
+ * Default mode for created directories, unless something else is specified
+ * using the MakeDirectoryPerm() function variant.
+ */
+#define PG_DIR_MODE_DEFAULT (S_IRWXU)
+#define PG_MODE_MASK_DEFAULT (S_IRWXG | S_IRWXO)
/*
* FileSeek uses the standard UNIX lseek(2) flags.
@@ -111,6 +117,10 @@ extern int CloseTransientFile(int fd);
extern int BasicOpenFile(const char *fileName, int fileFlags);
extern int BasicOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode);
+ /* Operations to make directories */
+extern int MakeDirectory(const char *directoryName);
+extern int MakeDirectoryPerm(const char *directoryName, mode_t directoryMode);
+
/* Miscellaneous support routines */
extern void InitFileAccess(void);
extern void set_max_safe_fds(void);
group-access-v3-02-group.patchtext/plain; charset=UTF-8; name=group-access-v3-02-group.patch; x-mac-creator=0; x-mac-type=0Download
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index 9d8e69056f..a9912eb418 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -1095,7 +1095,11 @@ SELECT pg_stop_backup();
and <filename>postmaster.opts</filename>, which record information
about the running <application>postmaster</application>, not about the
<application>postmaster</application> which will eventually use this backup.
- (These files can confuse <application>pg_ctl</application>.)
+ (These files can confuse <application>pg_ctl</application>.) If group read
+ access is enabled on the data directory and an unprivileged user in the
+ <productname>PostgreSQL</productname> group is performing the backup, then
+ <filename>postmaster.pid</filename> will not be readable and must be
+ excluded.
</para>
<para>
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 585665f161..5f57b933b9 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -77,6 +77,14 @@ PostgreSQL documentation
</para>
<para>
+ For security reasons the new cluster created by <command>initdb</command>
+ will only be accessible by the cluster owner by default. The
+ <option>--allow-group-access</option> option allows any user in the same
+ group as the cluster owner to read files in the cluster. This is useful
+ for performing backups as a non-privileged user.
+ </para>
+
+ <para>
<command>initdb</command> initializes the database cluster's default
locale and character set encoding. The character set encoding,
collation order (<literal>LC_COLLATE</literal>) and character set classes
@@ -302,6 +310,17 @@ PostgreSQL documentation
</varlistentry>
<varlistentry>
+ <term><option>-g</option></term>
+ <term><option>--allow-group-access</option></term>
+ <listitem>
+ <para>
+ Allows users in the same group as the cluster owner to read all cluster
+ files created by <command>initdb</command>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><option>-W</option></term>
<term><option>--pwprompt</option></term>
<listitem>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index a2ebd3e21c..8f602dc705 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -137,7 +137,10 @@ postgres$ <userinput>initdb -D /usr/local/pgsql/data</userinput>
database, it is essential that it be secured from unauthorized
access. <command>initdb</command> therefore revokes access
permissions from everyone but the
- <productname>PostgreSQL</productname> user.
+ <productname>PostgreSQL</productname> user, and optionally, group.
+ Group access, when enabled, is read-only. This allows an unprivileged
+ user in the <productname>PostgreSQL</productname> group to take a backup of
+ the cluster data or perform other operations that only require read access.
</para>
<para>
@@ -2237,6 +2240,15 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
+ If the data directory allows group read access then certificate files may
+ need to be located outside of the data directory in order to conform to the
+ security requirements outlined above. Generally, group access is enabled
+ to allow an unprivileged user to backup the database, and in that case the
+ backup software will not be able to read the certificate files and will
+ likely error.
+ </para>
+
+ <para>
If the private key is protected with a passphrase, the
server will prompt for the passphrase and will not start until it has
been entered.
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 0049778dfd..f9be0cff2a 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -1524,25 +1524,30 @@ checkDataDir(void)
#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.
+ * Check if the directory has correct permissions. If not, reject.
*
* 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))
+ if (stat_buf.st_mode & PG_MODE_MASK_ALLOW_GROUP)
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).")));
+ errmsg("data directory \"%s\" has invalid permissions",
+ DataDir),
+ errdetail("Permissions should be u=rwx (0700) or u=rwx,g=rx (0750).")));
+#endif
+
+ /*
+ * Reset the file mode creation mask based on the mode of the data
+ * directory.
+ *
+ * Suppress when on Windows, because there may not be proper support
+ * for Unix-y file permissions.
+ */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+ umask(~(stat_buf.st_mode & PG_DIR_MODE_DEFAULT));
#endif
/* Look for PG_VERSION before looking for pg_control */
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index 342401b8dd..52a4a31ce1 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -125,12 +125,6 @@
#define FD_MINFREE 10
/*
- * Default mode for created files, unless something else is specified using
- * the *Perm() function variants.
- */
-#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR)
-
-/*
* A number of platforms allow individual processes to open many more files
* than they can really support when *many* processes do the same thing.
* This GUC parameter lets the DBA limit max_safe_fds to something less than
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index ddc850db1c..afc94c7f94 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -113,6 +113,12 @@ static const char *const auth_methods_local[] = {
NULL
};
+/* Default file mode creation mask */
+#define PG_MODE_MASK_DEFAULT (S_IRWXG | S_IRWXO)
+
+/* File mode mask that allows group access */
+#define PG_MODE_MASK_ALLOW_GROUP (S_IWGRP | S_IRWXO)
+
/*
* these values are passed in by makefile defines
*/
@@ -144,7 +150,13 @@ static bool data_checksums = false;
static char *xlog_dir = NULL;
static char *str_wal_segment_size_mb = NULL;
static int wal_segment_size_mb;
+static mode_t file_mode_mask = PG_MODE_MASK_DEFAULT;
+
+/* File mode to use with chmod on files */
+#define PG_FILE_MODE ((S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) & ~file_mode_mask)
+/* File mode to use with chmod on directories */
+#define PG_DIR_MODE ((S_IRWXU | S_IRWXG | S_IRWXO) & ~file_mode_mask)
/* internal vars */
static const char *progname;
@@ -1170,7 +1182,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, PG_FILE_MODE) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1190,7 +1202,7 @@ setup_config(void)
sprintf(path, "%s/postgresql.auto.conf", pg_data);
writefile(path, autoconflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, PG_FILE_MODE) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1277,7 +1289,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_hba.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, PG_FILE_MODE) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1293,7 +1305,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_ident.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, PG_FILE_MODE) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -2311,6 +2323,7 @@ usage(const char *progname)
printf(_(" --auth-local=METHOD default authentication method for local-socket connections\n"));
printf(_(" [-D, --pgdata=]DATADIR location for this database cluster\n"));
printf(_(" -E, --encoding=ENCODING set default encoding for new databases\n"));
+ printf(_(" -g, --allow-group-access set file mode creation mask to 0027\n"));
printf(_(" --locale=LOCALE set default locale for new databases\n"));
printf(_(" --lc-collate=, --lc-ctype=, --lc-messages=LOCALE\n"
" --lc-monetary=, --lc-numeric=, --lc-time=LOCALE\n"
@@ -2692,7 +2705,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (pg_mkdir_p(pg_data, S_IRWXU) != 0)
+ if (pg_mkdir_p(pg_data, PG_DIR_MODE) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2710,7 +2723,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (chmod(pg_data, S_IRWXU) != 0)
+ if (chmod(pg_data, PG_DIR_MODE) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2778,7 +2791,8 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (pg_mkdir_p(xlog_dir, S_IRWXU) != 0)
+ printf("MASK: %04o\n", file_mode_mask);
+ if (pg_mkdir_p(xlog_dir, PG_DIR_MODE) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2796,7 +2810,8 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (chmod(xlog_dir, S_IRWXU) != 0)
+ printf("MASK: %04o\n", file_mode_mask);
+ if (chmod(xlog_dir, PG_DIR_MODE) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2846,7 +2861,7 @@ create_xlog_or_symlink(void)
else
{
/* Without -X option, just make the subdirectory normally */
- if (mkdir(subdirloc, S_IRWXU) < 0)
+ if (mkdir(subdirloc, PG_DIR_MODE) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, subdirloc, strerror(errno));
@@ -2882,7 +2897,9 @@ initialize_data_directory(void)
setup_signals();
- umask(S_IRWXG | S_IRWXO);
+ /* Set the file mode creation mask. */
+ umask(file_mode_mask);
+ printf(_("Initializing with file mode creation mask: %04o\n"), file_mode_mask);
create_data_directory();
@@ -2902,7 +2919,7 @@ initialize_data_directory(void)
* The parent directory already exists, so we only need mkdir() not
* pg_mkdir_p() here, which avoids some failure modes; cf bug #13853.
*/
- if (mkdir(path, S_IRWXU) < 0)
+ if (mkdir(path, PG_DIR_MODE) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -3016,6 +3033,7 @@ main(int argc, char *argv[])
{"waldir", required_argument, NULL, 'X'},
{"wal-segsize", required_argument, NULL, 12},
{"data-checksums", no_argument, NULL, 'k'},
+ {"allow-group-access", no_argument, NULL, 'g'},
{NULL, 0, NULL, 0}
};
@@ -3057,7 +3075,7 @@ main(int argc, char *argv[])
/* process command-line options */
- while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:", long_options, &option_index)) != -1)
+ while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:g", long_options, &option_index)) != -1)
{
switch (c)
{
@@ -3150,6 +3168,8 @@ main(int argc, char *argv[])
break;
case 12:
str_wal_segment_size_mb = pg_strdup(optarg);
+ case 'g':
+ file_mode_mask = PG_MODE_MASK_ALLOW_GROUP;
break;
default:
/* getopt_long already emitted a complaint */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 0e50556abe..15a81fea3d 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -42,13 +42,6 @@
#include <dirent.h>
/*
- * Default mode for created directories, unless something else is specified
- * using the MakeDirectoryPerm() function variant.
- */
-#define PG_DIR_MODE_DEFAULT (S_IRWXU)
-#define PG_MODE_MASK_DEFAULT (S_IRWXG | S_IRWXO)
-
-/*
* FileSeek uses the standard UNIX lseek(2) flags.
*/
@@ -63,6 +56,21 @@ extern PGDLLIMPORT int max_files_per_process;
*/
extern int max_safe_fds;
+/* Define default mode mask and a mask that allows group read. */
+#define PG_MODE_MASK_DEFAULT (S_IRWXG | S_IRWXO)
+#define PG_MODE_MASK_ALLOW_GROUP (S_IWGRP | S_IRWXO)
+
+/*
+ * Default mode for created files, unless something else is specified using
+ * the *Perm() function variants.
+ */
+#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR | S_IRGRP)
+
+/*
+ * Default mode for created directories, unless something else is specified using
+ * the MakeDirectoryPerm() function.
+ */
+#define PG_DIR_MODE_DEFAULT (S_IRWXU | S_IRGRP | S_IXGRP)
/*
* prototypes for functions in fd.c
On Thu, Dec 28, 2017 at 2:36 PM, David Steele <david@pgmasters.net> wrote:
Attached is a new patch set that should address various concerns raised in
this thread.1) group-access-v3-01-mkdir.patch
Abstracts all mkdir calls in the backend into a MakeDirectory() function
implemented in fd.c. This did not get committed in September as part of
0c5803b450e but I still think it has value. However, I have kept it
separate to reduce noise in the second patch. The mkdir() calls could also
be modified to use PG_DIR_MODE_DEFAULT with equivalent results.
+/*
+ * Default mode for created directories, unless something else is specified
+ * using the MakeDirectoryPerm() function variant.
+ */
+#define PG_DIR_MODE_DEFAULT (S_IRWXU)
+#define PG_MODE_MASK_DEFAULT (S_IRWXG | S_IRWXO)
There's only one comment here, but there are two constants that do
different things.
Also, this hunk gets removed again by 02, which is probably OK, but 02
adds the replacement stuff in a different part of the file, which
seems not as good.
I think MakeDirectory() is a good wrapper, but isn't
MakeDirectoryPerm() sort of silly?
2) group-access-v3-02-group.patch
This is a "GUC-less" implementation of group read access that depends on the
mode of the $PGDATA directory to determine which mode to use for subsequent
writes. The initdb option is preserved to allow group access to be enabled
when the cluster is initialized.Only two modes are allowed (700, 750) and the error message on startup is
hard-coded to address translation concerns.
+ umask(~(stat_buf.st_mode & PG_DIR_MODE_DEFAULT));
Hmm, I wonder if this is going to be 100% portable. Maybe some
obscure platform won't like an argument with all the high bits set.
Also, why should we break what's now one #if into two? That seems
like it's mildly more complicated for no gain.
+ (These files can confuse <application>pg_ctl</application>.) If group read
+ access is enabled on the data directory and an unprivileged user in the
+ <productname>PostgreSQL</productname> group is performing the backup, then
+ <filename>postmaster.pid</filename> will not be readable and must be
+ excluded.
Is that actually the behavior we want?
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Hi Robert,
Thanks for looking at the patches.
On 12/31/17 1:27 PM, Robert Haas wrote:
On Thu, Dec 28, 2017 at 2:36 PM, David Steele <david@pgmasters.net> wrote:
Attached is a new patch set that should address various concerns raised in
this thread.1) group-access-v3-01-mkdir.patch
Abstracts all mkdir calls in the backend into a MakeDirectory() function
implemented in fd.c. This did not get committed in September as part of
0c5803b450e but I still think it has value. However, I have kept it
separate to reduce noise in the second patch. The mkdir() calls could also
be modified to use PG_DIR_MODE_DEFAULT with equivalent results.+/* + * Default mode for created directories, unless something else is specified + * using the MakeDirectoryPerm() function variant. + */ +#define PG_DIR_MODE_DEFAULT (S_IRWXU) +#define PG_MODE_MASK_DEFAULT (S_IRWXG | S_IRWXO)There's only one comment here, but there are two constants that do
different things.
Looks like a copy pasto - I didn't mean PG_MODE_MASK_DEFAULT to be in
patch 01 at all. Removed.
Also, this hunk gets removed again by 02, which is probably OK, but 02
adds the replacement stuff in a different part of the file, which
seems not as good.
Agreed. I have moved that.
I think MakeDirectory() is a good wrapper, but isn't
MakeDirectoryPerm() sort of silly?
There's one place in the backend (storage/ipc/ipc.c) that sets
non-default directory permissions. This function is intended to support
that and any extensions that need to set custom perms.
2) group-access-v3-02-group.patch
This is a "GUC-less" implementation of group read access that depends on the
mode of the $PGDATA directory to determine which mode to use for subsequent
writes. The initdb option is preserved to allow group access to be enabled
when the cluster is initialized.Only two modes are allowed (700, 750) and the error message on startup is
hard-coded to address translation concerns.+ umask(~(stat_buf.st_mode & PG_DIR_MODE_DEFAULT));
Hmm, I wonder if this is going to be 100% portable. Maybe some
obscure platform won't like an argument with all the high bits set.
Sure - I have masked this with 0777 to clear any high bits. Sound OK?
Also, why should we break what's now one #if into two? That seems
like it's mildly more complicated for no gain.
There were already two #ifdef blocks so I added a third. I think we
should leave it as three or combine them all into one. There are no
runtime performance implications so I'm agnostic.
+ (These files can confuse <application>pg_ctl</application>.) If group read + access is enabled on the data directory and an unprivileged user in the + <productname>PostgreSQL</productname> group is performing the backup, then + <filename>postmaster.pid</filename> will not be readable and must be + excluded.Is that actually the behavior we want?
I think it is. Comments in the code are pretty specific about not
changing the permissions on postmaster.pid and I don't see any reason to
mess with it. Copying postmaster.pid as part of a backup is not a good
idea anyway.
New patches attached.
Thanks!
--
-David
david@pgmasters.net
Attachments:
group-access-v4-01-mkdir.patchtext/plain; charset=UTF-8; name=group-access-v4-01-mkdir.patch; x-mac-creator=0; x-mac-type=0Download
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 3e9a12dacd..cc6e46f7c4 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4086,7 +4086,7 @@ ValidateXLOGDirectoryStructure(void)
{
ereport(LOG,
(errmsg("creating missing WAL directory \"%s\"", path)));
- if (mkdir(path, S_IRWXU) < 0)
+ if (MakeDirectory(path) < 0)
ereport(FATAL,
(errmsg("could not create missing directory \"%s\": %m",
path)));
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index d574e4dd00..a0cafedd9b 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -151,7 +151,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
else
{
/* Directory creation failed? */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakeDirectory(dir) < 0)
{
char *parentdir;
@@ -173,7 +173,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
get_parent_directory(parentdir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectory(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -184,7 +184,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
parentdir = pstrdup(dir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectory(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -192,7 +192,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
pfree(parentdir);
/* Create database directory */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakeDirectory(dir) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -279,7 +279,7 @@ CreateTableSpace(CreateTableSpaceStmt *stmt)
/*
* Check that location isn't too long. Remember that we're going to append
* 'PG_XXX/<dboid>/<relid>_<fork>.<nnn>'. FYI, we never actually
- * reference the whole path here, but mkdir() uses the first two parts.
+ * reference the whole path here, but MakeDirectory() uses the first two parts.
*/
if (strlen(location) + 1 + strlen(TABLESPACE_VERSION_DIRECTORY) + 1 +
OIDCHARS + 1 + OIDCHARS + 1 + FORKNAMECHARS + 1 + OIDCHARS > MAXPGPATH)
@@ -574,7 +574,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* Attempt to coerce target directory to safe permissions. If this fails,
* it doesn't exist or has the wrong owner.
*/
- if (chmod(location, S_IRWXU) != 0)
+ if (chmod(location, PG_DIR_MODE_DEFAULT) != 0)
{
if (errno == ENOENT)
ereport(ERROR,
@@ -599,7 +599,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
if (stat(location_with_version_dir, &st) == 0 && S_ISDIR(st.st_mode))
{
if (!rmtree(location_with_version_dir, true))
- /* If this failed, mkdir() below is going to error. */
+ /* If this failed, MakeDirectory() below is going to error. */
ereport(WARNING,
(errmsg("some useless files may be left behind in old database directory \"%s\"",
location_with_version_dir)));
@@ -610,7 +610,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* The creation of the version directory prevents more than one tablespace
* in a single location.
*/
- if (mkdir(location_with_version_dir, S_IRWXU) < 0)
+ if (MakeDirectory(location_with_version_dir) < 0)
{
if (errno == EEXIST)
ereport(ERROR,
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 17c7f7e78f..aeda54f00b 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -4495,7 +4495,7 @@ internal_forkexec(int argc, char *argv[], Port *port)
* As in OpenTemporaryFileInTablespace, try to make the temp-file
* directory
*/
- mkdir(PG_TEMP_FILES_DIR, S_IRWXU);
+ MakeDirectory(PG_TEMP_FILES_DIR);
fp = AllocateFile(tmpfilename, PG_BINARY_W);
if (!fp)
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index aeb117796d..ffb7540355 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -41,6 +41,7 @@
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
#include "storage/dsm.h"
+#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pg_shmem.h"
@@ -322,7 +323,7 @@ SysLoggerMain(int argc, char *argv[])
/*
* Also, create new directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ MakeDirectory(Log_directory);
}
if (strcmp(Log_filename, currentLogFilename) != 0)
{
@@ -564,7 +565,7 @@ SysLogger_Start(void)
/*
* Create log directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ MakeDirectory(Log_directory);
/*
* The initial logfile is created right in the postmaster, to verify that
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 0d27b6f39e..6d10699c2c 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -1166,13 +1166,13 @@ CreateSlotOnDisk(ReplicationSlot *slot)
* It's just barely possible that some previous effort to create or drop a
* slot with this name left a temp directory lying around. If that seems
* to be the case, try to remove it. If the rmtree() fails, we'll error
- * out at the mkdir() below, so we don't bother checking success.
+ * out at the MakeDirectory() below, so we don't bother checking success.
*/
if (stat(tmppath, &st) == 0 && S_ISDIR(st.st_mode))
rmtree(tmppath, true);
/* Create and fsync the temporary slot directory. */
- if (mkdir(tmppath, S_IRWXU) < 0)
+ if (MakeDirectory(tmppath) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
diff --git a/src/backend/storage/file/copydir.c b/src/backend/storage/file/copydir.c
index d169e9c8bb..784400a285 100644
--- a/src/backend/storage/file/copydir.c
+++ b/src/backend/storage/file/copydir.c
@@ -41,7 +41,7 @@ copydir(char *fromdir, char *todir, bool recurse)
char fromfile[MAXPGPATH * 2];
char tofile[MAXPGPATH * 2];
- if (mkdir(todir, S_IRWXU) != 0)
+ if (MakeDirectory(todir) != 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m", todir)));
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index f449ee5c51..342401b8dd 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -1434,7 +1434,7 @@ PathNameOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode)
void
PathNameCreateTemporaryDir(const char *basedir, const char *directory)
{
- if (mkdir(directory, S_IRWXU) < 0)
+ if (MakeDirectory(directory) < 0)
{
if (errno == EEXIST)
return;
@@ -1444,14 +1444,14 @@ PathNameCreateTemporaryDir(const char *basedir, const char *directory)
* EEXIST to close a race against another process following the same
* algorithm.
*/
- if (mkdir(basedir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectory(basedir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary directory \"%s\": %m",
basedir)));
/* Try again. */
- if (mkdir(directory, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectory(directory) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary subdirectory \"%s\": %m",
@@ -1601,11 +1601,11 @@ OpenTemporaryFileInTablespace(Oid tblspcOid, bool rejectError)
* We might need to create the tablespace's tempfile directory, if no
* one has yet done so.
*
- * Don't check for error from mkdir; it could fail if someone else
+ * Don't check error from MakeDirectory; it could fail if someone else
* just did the same thing. If it doesn't work then we'll bomb out on
* the second create attempt, instead.
*/
- mkdir(tempdirpath, S_IRWXU);
+ MakeDirectory(tempdirpath);
file = PathNameOpenFile(tempfilepath,
O_RDWR | O_CREAT | O_TRUNC | PG_BINARY);
@@ -3533,3 +3533,22 @@ fsync_parent_path(const char *fname, int elevel)
return 0;
}
+
+/*
+ * Create a directory with MakeDirectoryPerm() and pass PG_DIR_MODE_DEFAULT to
+ * the directoryMode parameter.
+ */
+int
+MakeDirectory(const char *directoryName)
+{
+ return MakeDirectoryPerm(directoryName, PG_DIR_MODE_DEFAULT);
+}
+
+/*
+ * Create a directory with the specified mode.
+ */
+int
+MakeDirectoryPerm(const char *directoryName, mode_t directoryMode)
+{
+ return mkdir(directoryName, directoryMode);
+}
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index dfb47e7c39..f3c9d9f2d3 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -132,8 +132,8 @@ proc_exit(int code)
else
snprintf(gprofDirName, 32, "gprof/%d", (int) getpid());
- mkdir("gprof", S_IRWXU | S_IRWXG | S_IRWXO);
- mkdir(gprofDirName, S_IRWXU | S_IRWXG | S_IRWXO);
+ MakeDirectoryPerm("gprof", S_IRWXU | S_IRWXG | S_IRWXO);
+ MakeDirectoryPerm(gprofDirName, S_IRWXU | S_IRWXG | S_IRWXO);
chdir(gprofDirName);
}
#endif
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index dc2eb35f06..bc2d7bc063 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -57,6 +57,11 @@ extern PGDLLIMPORT int max_files_per_process;
*/
extern int max_safe_fds;
+/*
+ * Default mode for created directories, unless something else is specified
+ * using the MakeDirectoryPerm() function variant.
+ */
+#define PG_DIR_MODE_DEFAULT (S_IRWXU)
/*
* prototypes for functions in fd.c
@@ -111,6 +116,10 @@ extern int CloseTransientFile(int fd);
extern int BasicOpenFile(const char *fileName, int fileFlags);
extern int BasicOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode);
+ /* Operations to make directories */
+extern int MakeDirectory(const char *directoryName);
+extern int MakeDirectoryPerm(const char *directoryName, mode_t directoryMode);
+
/* Miscellaneous support routines */
extern void InitFileAccess(void);
extern void set_max_safe_fds(void);
group-access-v4-02-group.patchtext/plain; charset=UTF-8; name=group-access-v4-02-group.patch; x-mac-creator=0; x-mac-type=0Download
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index 9d8e69056f..a9912eb418 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -1095,7 +1095,11 @@ SELECT pg_stop_backup();
and <filename>postmaster.opts</filename>, which record information
about the running <application>postmaster</application>, not about the
<application>postmaster</application> which will eventually use this backup.
- (These files can confuse <application>pg_ctl</application>.)
+ (These files can confuse <application>pg_ctl</application>.) If group read
+ access is enabled on the data directory and an unprivileged user in the
+ <productname>PostgreSQL</productname> group is performing the backup, then
+ <filename>postmaster.pid</filename> will not be readable and must be
+ excluded.
</para>
<para>
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 585665f161..5f57b933b9 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -77,6 +77,14 @@ PostgreSQL documentation
</para>
<para>
+ For security reasons the new cluster created by <command>initdb</command>
+ will only be accessible by the cluster owner by default. The
+ <option>--allow-group-access</option> option allows any user in the same
+ group as the cluster owner to read files in the cluster. This is useful
+ for performing backups as a non-privileged user.
+ </para>
+
+ <para>
<command>initdb</command> initializes the database cluster's default
locale and character set encoding. The character set encoding,
collation order (<literal>LC_COLLATE</literal>) and character set classes
@@ -302,6 +310,17 @@ PostgreSQL documentation
</varlistentry>
<varlistentry>
+ <term><option>-g</option></term>
+ <term><option>--allow-group-access</option></term>
+ <listitem>
+ <para>
+ Allows users in the same group as the cluster owner to read all cluster
+ files created by <command>initdb</command>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><option>-W</option></term>
<term><option>--pwprompt</option></term>
<listitem>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index a2ebd3e21c..8f602dc705 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -137,7 +137,10 @@ postgres$ <userinput>initdb -D /usr/local/pgsql/data</userinput>
database, it is essential that it be secured from unauthorized
access. <command>initdb</command> therefore revokes access
permissions from everyone but the
- <productname>PostgreSQL</productname> user.
+ <productname>PostgreSQL</productname> user, and optionally, group.
+ Group access, when enabled, is read-only. This allows an unprivileged
+ user in the <productname>PostgreSQL</productname> group to take a backup of
+ the cluster data or perform other operations that only require read access.
</para>
<para>
@@ -2237,6 +2240,15 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
+ If the data directory allows group read access then certificate files may
+ need to be located outside of the data directory in order to conform to the
+ security requirements outlined above. Generally, group access is enabled
+ to allow an unprivileged user to backup the database, and in that case the
+ backup software will not be able to read the certificate files and will
+ likely error.
+ </para>
+
+ <para>
If the private key is protected with a passphrase, the
server will prompt for the passphrase and will not start until it has
been entered.
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index aeda54f00b..b780b70e53 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -587,7 +587,8 @@ PostmasterMain(int argc, char *argv[])
IsPostmasterEnvironment = true;
/*
- * for security, no dir or file created can be group or other accessible
+ * By default, no dir or file created can be group or other accessible. This
+ * may be modified later depending in the permissions of the data directory.
*/
umask(S_IRWXG | S_IRWXO);
@@ -1524,25 +1525,30 @@ checkDataDir(void)
#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.
+ * Check if the directory has correct permissions. If not, reject.
*
* 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))
+ if (stat_buf.st_mode & PG_MODE_MASK_ALLOW_GROUP)
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).")));
+ errmsg("data directory \"%s\" has invalid permissions",
+ DataDir),
+ errdetail("Permissions should be u=rwx (0700) or u=rwx,g=rx (0750).")));
+#endif
+
+ /*
+ * Reset the file mode creation mask based on the mode of the data
+ * directory.
+ *
+ * Suppress when on Windows, because there may not be proper support
+ * for Unix-y file permissions.
+ */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+ umask(~(stat_buf.st_mode & PG_DIR_MODE_DEFAULT) & 0777);
#endif
/* Look for PG_VERSION before looking for pg_control */
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index 342401b8dd..52a4a31ce1 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -125,12 +125,6 @@
#define FD_MINFREE 10
/*
- * Default mode for created files, unless something else is specified using
- * the *Perm() function variants.
- */
-#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR)
-
-/*
* A number of platforms allow individual processes to open many more files
* than they can really support when *many* processes do the same thing.
* This GUC parameter lets the DBA limit max_safe_fds to something less than
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index ddc850db1c..afc94c7f94 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -113,6 +113,12 @@ static const char *const auth_methods_local[] = {
NULL
};
+/* Default file mode creation mask */
+#define PG_MODE_MASK_DEFAULT (S_IRWXG | S_IRWXO)
+
+/* File mode mask that allows group access */
+#define PG_MODE_MASK_ALLOW_GROUP (S_IWGRP | S_IRWXO)
+
/*
* these values are passed in by makefile defines
*/
@@ -144,7 +150,13 @@ static bool data_checksums = false;
static char *xlog_dir = NULL;
static char *str_wal_segment_size_mb = NULL;
static int wal_segment_size_mb;
+static mode_t file_mode_mask = PG_MODE_MASK_DEFAULT;
+
+/* File mode to use with chmod on files */
+#define PG_FILE_MODE ((S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) & ~file_mode_mask)
+/* File mode to use with chmod on directories */
+#define PG_DIR_MODE ((S_IRWXU | S_IRWXG | S_IRWXO) & ~file_mode_mask)
/* internal vars */
static const char *progname;
@@ -1170,7 +1182,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, PG_FILE_MODE) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1190,7 +1202,7 @@ setup_config(void)
sprintf(path, "%s/postgresql.auto.conf", pg_data);
writefile(path, autoconflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, PG_FILE_MODE) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1277,7 +1289,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_hba.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, PG_FILE_MODE) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1293,7 +1305,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_ident.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, PG_FILE_MODE) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -2311,6 +2323,7 @@ usage(const char *progname)
printf(_(" --auth-local=METHOD default authentication method for local-socket connections\n"));
printf(_(" [-D, --pgdata=]DATADIR location for this database cluster\n"));
printf(_(" -E, --encoding=ENCODING set default encoding for new databases\n"));
+ printf(_(" -g, --allow-group-access set file mode creation mask to 0027\n"));
printf(_(" --locale=LOCALE set default locale for new databases\n"));
printf(_(" --lc-collate=, --lc-ctype=, --lc-messages=LOCALE\n"
" --lc-monetary=, --lc-numeric=, --lc-time=LOCALE\n"
@@ -2692,7 +2705,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (pg_mkdir_p(pg_data, S_IRWXU) != 0)
+ if (pg_mkdir_p(pg_data, PG_DIR_MODE) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2710,7 +2723,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (chmod(pg_data, S_IRWXU) != 0)
+ if (chmod(pg_data, PG_DIR_MODE) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2778,7 +2791,8 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (pg_mkdir_p(xlog_dir, S_IRWXU) != 0)
+ printf("MASK: %04o\n", file_mode_mask);
+ if (pg_mkdir_p(xlog_dir, PG_DIR_MODE) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2796,7 +2810,8 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (chmod(xlog_dir, S_IRWXU) != 0)
+ printf("MASK: %04o\n", file_mode_mask);
+ if (chmod(xlog_dir, PG_DIR_MODE) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2846,7 +2861,7 @@ create_xlog_or_symlink(void)
else
{
/* Without -X option, just make the subdirectory normally */
- if (mkdir(subdirloc, S_IRWXU) < 0)
+ if (mkdir(subdirloc, PG_DIR_MODE) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, subdirloc, strerror(errno));
@@ -2882,7 +2897,9 @@ initialize_data_directory(void)
setup_signals();
- umask(S_IRWXG | S_IRWXO);
+ /* Set the file mode creation mask. */
+ umask(file_mode_mask);
+ printf(_("Initializing with file mode creation mask: %04o\n"), file_mode_mask);
create_data_directory();
@@ -2902,7 +2919,7 @@ initialize_data_directory(void)
* The parent directory already exists, so we only need mkdir() not
* pg_mkdir_p() here, which avoids some failure modes; cf bug #13853.
*/
- if (mkdir(path, S_IRWXU) < 0)
+ if (mkdir(path, PG_DIR_MODE) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -3016,6 +3033,7 @@ main(int argc, char *argv[])
{"waldir", required_argument, NULL, 'X'},
{"wal-segsize", required_argument, NULL, 12},
{"data-checksums", no_argument, NULL, 'k'},
+ {"allow-group-access", no_argument, NULL, 'g'},
{NULL, 0, NULL, 0}
};
@@ -3057,7 +3075,7 @@ main(int argc, char *argv[])
/* process command-line options */
- while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:", long_options, &option_index)) != -1)
+ while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:g", long_options, &option_index)) != -1)
{
switch (c)
{
@@ -3150,6 +3168,8 @@ main(int argc, char *argv[])
break;
case 12:
str_wal_segment_size_mb = pg_strdup(optarg);
+ case 'g':
+ file_mode_mask = PG_MODE_MASK_ALLOW_GROUP;
break;
default:
/* getopt_long already emitted a complaint */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index bc2d7bc063..5258584cd3 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -58,10 +58,28 @@ extern PGDLLIMPORT int max_files_per_process;
extern int max_safe_fds;
/*
+ * Default mode mask for data directory permissions that does not allow
+ * group execute/read.
+ */
+#define PG_MODE_MASK_DEFAULT (S_IRWXG | S_IRWXO)
+
+/*
+ * Optional mode mask for data directory permissions that allows group
+ * read/execute.
+ */
+#define PG_MODE_MASK_ALLOW_GROUP (S_IWGRP | S_IRWXO)
+
+/*
+ * Default mode for created files, unless something else is specified using
+ * the *Perm() function variants.
+ */
+#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR | S_IRGRP)
+
+/*
* Default mode for created directories, unless something else is specified
* using the MakeDirectoryPerm() function variant.
*/
-#define PG_DIR_MODE_DEFAULT (S_IRWXU)
+#define PG_DIR_MODE_DEFAULT (S_IRWXU | S_IRGRP | S_IXGRP)
/*
* prototypes for functions in fd.c
On Tue, Jan 2, 2018 at 11:43 AM, David Steele <david@pgmasters.net> wrote:
I think MakeDirectory() is a good wrapper, but isn't
MakeDirectoryPerm() sort of silly?
There's one place in the backend (storage/ipc/ipc.c) that sets non-default
directory permissions. This function is intended to support that and any
extensions that need to set custom perms.
Yeah, but all it does is call mkdir(), which could just as well be
called directly. I think there's a pointer to a wrapper when it does
something for you -- supply an argument, log something, handle
portability concerns -- but this wrapper does exactly nothing.
+ umask(~(stat_buf.st_mode & PG_DIR_MODE_DEFAULT));
Hmm, I wonder if this is going to be 100% portable. Maybe some
obscure platform won't like an argument with all the high bits set.Sure - I have masked this with 0777 to clear any high bits. Sound OK?
Seems a little strange to spell it that way when we're using constants
everywhere else. How about writing it like this:
umask(PG_DIR_MODE_DEFAULT & ~stat_buf.st_mode);
I think that reads as "clear all bits from PG_DIR_MODE_DEFAULT that
are not set in stat_buf.st_mode", which sounds like what we want.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On 1/3/18 08:11, Robert Haas wrote:
On Tue, Jan 2, 2018 at 11:43 AM, David Steele <david@pgmasters.net> wrote:
I think MakeDirectory() is a good wrapper, but isn't
MakeDirectoryPerm() sort of silly?
There's one place in the backend (storage/ipc/ipc.c) that sets non-default
directory permissions. This function is intended to support that and any
extensions that need to set custom perms.Yeah, but all it does is call mkdir(), which could just as well be
called directly. I think there's a pointer to a wrapper when it does
something for you -- supply an argument, log something, handle
portability concerns -- but this wrapper does exactly nothing.
Yeah, I didn't like this aspect when this patch was originally
submitted. We want to keep the code legible for future new
contributors. Having these generic-sounding but specific-in-purpose
wrapper functions can be pretty confusing. Let's use mkdir() when it's
the appropriate function, and let's figure out a different name for
"make a data directory subdirectory in a secure and robust way".
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On 1/8/18 8:58 PM, Peter Eisentraut wrote:
On 1/3/18 08:11, Robert Haas wrote:
On Tue, Jan 2, 2018 at 11:43 AM, David Steele <david@pgmasters.net> wrote:
I think MakeDirectory() is a good wrapper, but isn't
MakeDirectoryPerm() sort of silly?
There's one place in the backend (storage/ipc/ipc.c) that sets non-default
directory permissions. This function is intended to support that and any
extensions that need to set custom perms.Yeah, but all it does is call mkdir(), which could just as well be
called directly. I think there's a pointer to a wrapper when it does
something for you -- supply an argument, log something, handle
portability concerns -- but this wrapper does exactly nothing.Yeah, I didn't like this aspect when this patch was originally
submitted. We want to keep the code legible for future new
contributors. Having these generic-sounding but specific-in-purpose
wrapper functions can be pretty confusing. Let's use mkdir() when it's
the appropriate function, and let's figure out a different name for
"make a data directory subdirectory in a secure and robust way".
I think there is value to keeping the function names symmetric to the
FileOpen()/FileOpenPerm() variants but I'm clearly in the minority so
there's no point in pursuing it.
How about MakeDirectoryDefaultPerm()? That's what I'll go with if I
don't hear any other ideas. The single call to MakeDirectoryPerm() will
be reverted to mkdir() and I'll remove the function.
Thanks,
--
-David
david@pgmasters.net
David,
* David Steele (david@pgmasters.net) wrote:
On 1/8/18 8:58 PM, Peter Eisentraut wrote:
On 1/3/18 08:11, Robert Haas wrote:
On Tue, Jan 2, 2018 at 11:43 AM, David Steele <david@pgmasters.net> wrote:
I think MakeDirectory() is a good wrapper, but isn't
MakeDirectoryPerm() sort of silly?
There's one place in the backend (storage/ipc/ipc.c) that sets non-default
directory permissions. This function is intended to support that and any
extensions that need to set custom perms.Yeah, but all it does is call mkdir(), which could just as well be
called directly. I think there's a pointer to a wrapper when it does
something for you -- supply an argument, log something, handle
portability concerns -- but this wrapper does exactly nothing.Yeah, I didn't like this aspect when this patch was originally
submitted. We want to keep the code legible for future new
contributors. Having these generic-sounding but specific-in-purpose
wrapper functions can be pretty confusing. Let's use mkdir() when it's
the appropriate function, and let's figure out a different name for
"make a data directory subdirectory in a secure and robust way".I think there is value to keeping the function names symmetric to the
FileOpen()/FileOpenPerm() variants but I'm clearly in the minority so
there's no point in pursuing it.How about MakeDirectoryDefaultPerm()? That's what I'll go with if I
don't hear any other ideas. The single call to MakeDirectoryPerm() will
be reverted to mkdir() and I'll remove the function.
I would be sure to include a good comment/explanation about why mkdir()
is being used there and not MakeDirectoryDefaultPerm() (unless one
exists already), just to avoid later hackers thinking that mkdir() is
the way to go in general when, in most cases, MakeDirectoryDefaultPerm()
should be used.
Thanks!
Stephen
David Steele wrote:
On 1/8/18 8:58 PM, Peter Eisentraut wrote:
Yeah, I didn't like this aspect when this patch was originally
submitted. We want to keep the code legible for future new
contributors. Having these generic-sounding but specific-in-purpose
wrapper functions can be pretty confusing. Let's use mkdir() when it's
the appropriate function, and let's figure out a different name for
"make a data directory subdirectory in a secure and robust way".
How about MakeDirectoryDefaultPerm()? That's what I'll go with if I
don't hear any other ideas. The single call to MakeDirectoryPerm() will
be reverted to mkdir() and I'll remove the function.
I'd go with MakeDirectory, documenting exactly what it does and why, and
be done with it. If your new function satisfies future users, great; if
not, it can be patched (or not) once we know exactly what these callers
need.
You know, YAGNI.
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On 1/10/18 12:37, David Steele wrote:
How about MakeDirectoryDefaultPerm()? That's what I'll go with if I
don't hear any other ideas. The single call to MakeDirectoryPerm() will
be reverted to mkdir() and I'll remove the function.
Works for me.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Wed, Jan 10, 2018 at 03:19:46PM -0300, Alvaro Herrera wrote:
David Steele wrote:
On 1/8/18 8:58 PM, Peter Eisentraut wrote:
Yeah, I didn't like this aspect when this patch was originally
submitted. We want to keep the code legible for future new
contributors. Having these generic-sounding but specific-in-purpose
wrapper functions can be pretty confusing. Let's use mkdir() when it's
the appropriate function, and let's figure out a different name for
"make a data directory subdirectory in a secure and robust way".How about MakeDirectoryDefaultPerm()? That's what I'll go with if I
don't hear any other ideas. The single call to MakeDirectoryPerm() will
be reverted to mkdir() and I'll remove the function.I'd go with MakeDirectory, documenting exactly what it does and why, and
be done with it. If your new function satisfies future users, great; if
not, it can be patched (or not) once we know exactly what these callers
need.
After going through the thread, I would vote for making things simple by
just using MakeDirectory() and document precisely what it does. Anyway,
there is as well the approach of using MakeDirectoryDefaultPerm(), so
I'll be fine with the decision you make, David. The patchis moved to
"waiting on author".
--
Michael
On 1/19/18 3:08 AM, Michael Paquier wrote:
On Wed, Jan 10, 2018 at 03:19:46PM -0300, Alvaro Herrera wrote:
David Steele wrote:
On 1/8/18 8:58 PM, Peter Eisentraut wrote:
Yeah, I didn't like this aspect when this patch was originally
submitted. We want to keep the code legible for future new
contributors. Having these generic-sounding but specific-in-purpose
wrapper functions can be pretty confusing. Let's use mkdir() when it's
the appropriate function, and let's figure out a different name for
"make a data directory subdirectory in a secure and robust way".How about MakeDirectoryDefaultPerm()? That's what I'll go with if I
don't hear any other ideas. The single call to MakeDirectoryPerm() will
be reverted to mkdir() and I'll remove the function.I'd go with MakeDirectory, documenting exactly what it does and why, and
be done with it. If your new function satisfies future users, great; if
not, it can be patched (or not) once we know exactly what these callers
need.After going through the thread, I would vote for making things simple by
just using MakeDirectory() and document precisely what it does. Anyway,
there is as well the approach of using MakeDirectoryDefaultPerm(), so
I'll be fine with the decision you make, David. The patchis moved to
"waiting on author".
I ended up with MakeDirectoryDefaultPerm(), but I'm flexible.
Attached is a new patch set that also adds the following to patch 02.
1) Move permission constants to a new file in common so they can be
shared with the front end.
2) Update all client programs that write to PGDATA: initdb, pg_ctl,
pg_resetwal, pg_rewind, pg_upgrade.
3) Add tests for initdb, pg_ctl, and pg_rewind.
I have yet to add tests for pg_rewindwal and pg_upgrade. pg_rewindwal
doesn't *have* any tests as far as I can tell and pg_upgrade has tests
in a shell script -- it's not clear how I would extend it or reuse the
Perl code for perm testing.
Does anyone have suggestions on tests for pg_resetwal and pg_upgrade?
Should I start from scratch?
Thanks,
--
-David
david@pgmasters.net
Attachments:
group-access-v5-01-mkdir.patchtext/plain; charset=UTF-8; name=group-access-v5-01-mkdir.patch; x-mac-creator=0; x-mac-type=0Download
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index e42b828edf..9814ac6d3e 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4086,7 +4086,7 @@ ValidateXLOGDirectoryStructure(void)
{
ereport(LOG,
(errmsg("creating missing WAL directory \"%s\"", path)));
- if (mkdir(path, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(path) < 0)
ereport(FATAL,
(errmsg("could not create missing directory \"%s\": %m",
path)));
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 8cb834c271..4d836aaeaf 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -151,7 +151,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
else
{
/* Directory creation failed? */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(dir) < 0)
{
char *parentdir;
@@ -173,7 +173,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
get_parent_directory(parentdir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -184,7 +184,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
parentdir = pstrdup(dir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -192,7 +192,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
pfree(parentdir);
/* Create database directory */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(dir) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -279,7 +279,8 @@ CreateTableSpace(CreateTableSpaceStmt *stmt)
/*
* Check that location isn't too long. Remember that we're going to append
* 'PG_XXX/<dboid>/<relid>_<fork>.<nnn>'. FYI, we never actually
- * reference the whole path here, but mkdir() uses the first two parts.
+ * reference the whole path here, but MakeDirectoryDefaultPerm() uses the first
+ * two parts.
*/
if (strlen(location) + 1 + strlen(TABLESPACE_VERSION_DIRECTORY) + 1 +
OIDCHARS + 1 + OIDCHARS + 1 + FORKNAMECHARS + 1 + OIDCHARS > MAXPGPATH)
@@ -574,7 +575,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* Attempt to coerce target directory to safe permissions. If this fails,
* it doesn't exist or has the wrong owner.
*/
- if (chmod(location, S_IRWXU) != 0)
+ if (chmod(location, PG_DIR_MODE_DEFAULT) != 0)
{
if (errno == ENOENT)
ereport(ERROR,
@@ -599,7 +600,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
if (stat(location_with_version_dir, &st) == 0 && S_ISDIR(st.st_mode))
{
if (!rmtree(location_with_version_dir, true))
- /* If this failed, mkdir() below is going to error. */
+ /* If this failed, MakeDirectoryDefaultPerm() below is going to error. */
ereport(WARNING,
(errmsg("some useless files may be left behind in old database directory \"%s\"",
location_with_version_dir)));
@@ -610,7 +611,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* The creation of the version directory prevents more than one tablespace
* in a single location.
*/
- if (mkdir(location_with_version_dir, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(location_with_version_dir) < 0)
{
if (errno == EEXIST)
ereport(ERROR,
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index f3ddf828bb..e13a2ae8c4 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -4495,7 +4495,7 @@ internal_forkexec(int argc, char *argv[], Port *port)
* As in OpenTemporaryFileInTablespace, try to make the temp-file
* directory
*/
- mkdir(PG_TEMP_FILES_DIR, S_IRWXU);
+ MakeDirectoryDefaultPerm(PG_TEMP_FILES_DIR);
fp = AllocateFile(tmpfilename, PG_BINARY_W);
if (!fp)
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index f70eea37df..ae23941f59 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -41,6 +41,7 @@
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
#include "storage/dsm.h"
+#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pg_shmem.h"
@@ -322,7 +323,7 @@ SysLoggerMain(int argc, char *argv[])
/*
* Also, create new directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ MakeDirectoryDefaultPerm(Log_directory);
}
if (strcmp(Log_filename, currentLogFilename) != 0)
{
@@ -564,7 +565,7 @@ SysLogger_Start(void)
/*
* Create log directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ MakeDirectoryDefaultPerm(Log_directory);
/*
* The initial logfile is created right in the postmaster, to verify that
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index fc9ef22b0b..3a98360b50 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -1166,13 +1166,14 @@ CreateSlotOnDisk(ReplicationSlot *slot)
* It's just barely possible that some previous effort to create or drop a
* slot with this name left a temp directory lying around. If that seems
* to be the case, try to remove it. If the rmtree() fails, we'll error
- * out at the mkdir() below, so we don't bother checking success.
+ * out at the MakeDirectoryDefaultPerm() below, so we don't bother checking
+ * success.
*/
if (stat(tmppath, &st) == 0 && S_ISDIR(st.st_mode))
rmtree(tmppath, true);
/* Create and fsync the temporary slot directory. */
- if (mkdir(tmppath, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(tmppath) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
diff --git a/src/backend/storage/file/copydir.c b/src/backend/storage/file/copydir.c
index ca6342db0d..ff980dc1f5 100644
--- a/src/backend/storage/file/copydir.c
+++ b/src/backend/storage/file/copydir.c
@@ -41,7 +41,7 @@ copydir(char *fromdir, char *todir, bool recurse)
char fromfile[MAXPGPATH * 2];
char tofile[MAXPGPATH * 2];
- if (mkdir(todir, S_IRWXU) != 0)
+ if (MakeDirectoryDefaultPerm(todir) != 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m", todir)));
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index 71516a9a5a..9aeca8a04d 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -1435,7 +1435,7 @@ PathNameOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode)
void
PathNameCreateTemporaryDir(const char *basedir, const char *directory)
{
- if (mkdir(directory, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(directory) < 0)
{
if (errno == EEXIST)
return;
@@ -1445,14 +1445,14 @@ PathNameCreateTemporaryDir(const char *basedir, const char *directory)
* EEXIST to close a race against another process following the same
* algorithm.
*/
- if (mkdir(basedir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(basedir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary directory \"%s\": %m",
basedir)));
/* Try again. */
- if (mkdir(directory, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(directory) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary subdirectory \"%s\": %m",
@@ -1602,11 +1602,11 @@ OpenTemporaryFileInTablespace(Oid tblspcOid, bool rejectError)
* We might need to create the tablespace's tempfile directory, if no
* one has yet done so.
*
- * Don't check for error from mkdir; it could fail if someone else
- * just did the same thing. If it doesn't work then we'll bomb out on
+ * Don't check error from MakeDirectoryDefaultPerm; it could fail if someone
+ * else just did the same thing. If it doesn't work then we'll bomb out on
* the second create attempt, instead.
*/
- mkdir(tempdirpath, S_IRWXU);
+ MakeDirectoryDefaultPerm(tempdirpath);
file = PathNameOpenFile(tempfilepath,
O_RDWR | O_CREAT | O_TRUNC | PG_BINARY);
@@ -3545,3 +3545,16 @@ fsync_parent_path(const char *fname, int elevel)
return 0;
}
+
+/*
+ * Create a directory using PG_DIR_MODE_DEFAULT for permissions
+ *
+ * Directories in PGDATA should normally have specific permissions -- when
+ * using this function the caller does not need to know what they should be.
+ * For permissions other than the default use mkdir() directly.
+ */
+int
+MakeDirectoryDefaultPerm(const char *directoryName)
+{
+ return mkdir(directoryName, PG_DIR_MODE_DEFAULT);
+}
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index 2de35efbd4..687df55444 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -132,6 +132,10 @@ proc_exit(int code)
else
snprintf(gprofDirName, 32, "gprof/%d", (int) getpid());
+ /*
+ * Use mkdir() instead of MakeDirectoryDefaultPerm() here because non-default
+ * permissions are required.
+ */
mkdir("gprof", S_IRWXU | S_IRWXG | S_IRWXO);
mkdir(gprofDirName, S_IRWXU | S_IRWXG | S_IRWXO);
chdir(gprofDirName);
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index db5ca16679..909d7202e9 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -57,6 +57,10 @@ extern PGDLLIMPORT int max_files_per_process;
*/
extern int max_safe_fds;
+/*
+ * Default mode for directories created with MakeDirectoryDefaultPerm().
+ */
+#define PG_DIR_MODE_DEFAULT (S_IRWXU)
/*
* prototypes for functions in fd.c
@@ -111,6 +115,9 @@ extern int CloseTransientFile(int fd);
extern int BasicOpenFile(const char *fileName, int fileFlags);
extern int BasicOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode);
+ /* Make a directory with default permissions */
+extern int MakeDirectoryDefaultPerm(const char *directoryName);
+
/* Miscellaneous support routines */
extern void InitFileAccess(void);
extern void set_max_safe_fds(void);
group-access-v5-02-group.patchtext/plain; charset=UTF-8; name=group-access-v5-02-group.patch; x-mac-creator=0; x-mac-type=0Download
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index 9d8e69056f..a9912eb418 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -1095,7 +1095,11 @@ SELECT pg_stop_backup();
and <filename>postmaster.opts</filename>, which record information
about the running <application>postmaster</application>, not about the
<application>postmaster</application> which will eventually use this backup.
- (These files can confuse <application>pg_ctl</application>.)
+ (These files can confuse <application>pg_ctl</application>.) If group read
+ access is enabled on the data directory and an unprivileged user in the
+ <productname>PostgreSQL</productname> group is performing the backup, then
+ <filename>postmaster.pid</filename> will not be readable and must be
+ excluded.
</para>
<para>
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 585665f161..5f57b933b9 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -76,6 +76,14 @@ PostgreSQL documentation
to do so.)
</para>
+ <para>
+ For security reasons the new cluster created by <command>initdb</command>
+ will only be accessible by the cluster owner by default. The
+ <option>--allow-group-access</option> option allows any user in the same
+ group as the cluster owner to read files in the cluster. This is useful
+ for performing backups as a non-privileged user.
+ </para>
+
<para>
<command>initdb</command> initializes the database cluster's default
locale and character set encoding. The character set encoding,
@@ -301,6 +309,17 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-g</option></term>
+ <term><option>--allow-group-access</option></term>
+ <listitem>
+ <para>
+ Allows users in the same group as the cluster owner to read all cluster
+ files created by <command>initdb</command>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-W</option></term>
<term><option>--pwprompt</option></term>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index a2ebd3e21c..8f602dc705 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -137,7 +137,10 @@ postgres$ <userinput>initdb -D /usr/local/pgsql/data</userinput>
database, it is essential that it be secured from unauthorized
access. <command>initdb</command> therefore revokes access
permissions from everyone but the
- <productname>PostgreSQL</productname> user.
+ <productname>PostgreSQL</productname> user, and optionally, group.
+ Group access, when enabled, is read-only. This allows an unprivileged
+ user in the <productname>PostgreSQL</productname> group to take a backup of
+ the cluster data or perform other operations that only require read access.
</para>
<para>
@@ -2236,6 +2239,15 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
member of the group that has access to those certificate and key files.
</para>
+ <para>
+ If the data directory allows group read access then certificate files may
+ need to be located outside of the data directory in order to conform to the
+ security requirements outlined above. Generally, group access is enabled
+ to allow an unprivileged user to backup the database, and in that case the
+ backup software will not be able to read the certificate files and will
+ likely error.
+ </para>
+
<para>
If the private key is protected with a passphrase, the
server will prompt for the passphrase and will not start until it has
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 4d836aaeaf..e0b67d7689 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -68,6 +68,7 @@
#include "commands/seclabel.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
+#include "common/file_perm.h"
#include "miscadmin.h"
#include "postmaster/bgwriter.h"
#include "storage/fd.h"
@@ -566,6 +567,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
char *linkloc;
char *location_with_version_dir;
struct stat st;
+ mode_t current_umask; /* Current umask value */
linkloc = psprintf("pg_tblspc/%u", tablespaceoid);
location_with_version_dir = psprintf("%s/%s", location,
@@ -575,7 +577,10 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* Attempt to coerce target directory to safe permissions. If this fails,
* it doesn't exist or has the wrong owner.
*/
- if (chmod(location, PG_DIR_MODE_DEFAULT) != 0)
+ current_umask = umask(0);
+ umask(current_umask);
+
+ if (chmod(location, PG_DIR_MODE_DEFAULT & ~current_umask) != 0)
{
if (errno == ENOENT)
ereport(ERROR,
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index e13a2ae8c4..65f528147a 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -97,6 +97,7 @@
#include "access/xlog.h"
#include "bootstrap/bootstrap.h"
#include "catalog/pg_control.h"
+#include "common/file_perm.h"
#include "common/ip.h"
#include "lib/ilist.h"
#include "libpq/auth.h"
@@ -587,7 +588,8 @@ PostmasterMain(int argc, char *argv[])
IsPostmasterEnvironment = true;
/*
- * for security, no dir or file created can be group or other accessible
+ * By default, no dir or file created can be group or other accessible. This
+ * may be modified later depending in the permissions of the data directory.
*/
umask(S_IRWXG | S_IRWXO);
@@ -1524,25 +1526,30 @@ checkDataDir(void)
#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.
+ * Check if the directory has correct permissions. If not, reject.
*
* 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))
+ if (stat_buf.st_mode & PG_MODE_MASK_ALLOW_GROUP)
ereport(FATAL,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("data directory \"%s\" has group or world access",
+ errmsg("data directory \"%s\" has invalid permissions",
DataDir),
- errdetail("Permissions should be u=rwx (0700).")));
+ errdetail("Permissions should be u=rwx (0700) or u=rwx,g=rx (0750).")));
+#endif
+
+ /*
+ * Reset the file mode creation mask based on the mode of the data
+ * directory.
+ *
+ * Suppress when on Windows, because there may not be proper support
+ * for Unix-y file permissions.
+ */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+ umask(PG_MODE_MASK_DEFAULT & ~stat_buf.st_mode);
#endif
/* Look for PG_VERSION before looking for pg_control */
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index 9aeca8a04d..21c2456e63 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -84,6 +84,7 @@
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "catalog/pg_tablespace.h"
+#include "common/file_perm.h"
#include "pgstat.h"
#include "portability/mem.h"
#include "storage/fd.h"
@@ -124,12 +125,6 @@
*/
#define FD_MINFREE 10
-/*
- * Default mode for created files, unless something else is specified using
- * the *Perm() function variants.
- */
-#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR)
-
/*
* A number of platforms allow individual processes to open many more files
* than they can really support when *many* processes do the same thing.
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 2efd3b75b1..cdbf4d52d8 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -64,6 +64,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_class.h"
#include "catalog/pg_collation.h"
+#include "common/file_perm.h"
#include "common/file_utils.h"
#include "common/restricted_token.h"
#include "common/username.h"
@@ -144,6 +145,7 @@ static bool data_checksums = false;
static char *xlog_dir = NULL;
static char *str_wal_segment_size_mb = NULL;
static int wal_segment_size_mb;
+static mode_t file_mode_mask = PG_MODE_MASK_DEFAULT;
/* internal vars */
@@ -1170,12 +1172,6 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
- {
- fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
- progname, path, strerror(errno));
- exit_nicely();
- }
/*
* create the automatic configuration file to store the configuration
@@ -1190,12 +1186,6 @@ setup_config(void)
sprintf(path, "%s/postgresql.auto.conf", pg_data);
writefile(path, autoconflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
- {
- fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
- progname, path, strerror(errno));
- exit_nicely();
- }
free(conflines);
@@ -1277,12 +1267,6 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_hba.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
- {
- fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
- progname, path, strerror(errno));
- exit_nicely();
- }
free(conflines);
@@ -1293,12 +1277,6 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_ident.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
- {
- fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
- progname, path, strerror(errno));
- exit_nicely();
- }
free(conflines);
@@ -2311,6 +2289,7 @@ usage(const char *progname)
printf(_(" --auth-local=METHOD default authentication method for local-socket connections\n"));
printf(_(" [-D, --pgdata=]DATADIR location for this database cluster\n"));
printf(_(" -E, --encoding=ENCODING set default encoding for new databases\n"));
+ printf(_(" -g, --allow-group-access allow group read/execute on data directory\n"));
printf(_(" --locale=LOCALE set default locale for new databases\n"));
printf(_(" --lc-collate=, --lc-ctype=, --lc-messages=LOCALE\n"
" --lc-monetary=, --lc-numeric=, --lc-time=LOCALE\n"
@@ -2692,7 +2671,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (pg_mkdir_p(pg_data, S_IRWXU) != 0)
+ if (pg_mkdir_p(pg_data, PG_DIR_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2710,7 +2689,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (chmod(pg_data, S_IRWXU) != 0)
+ if (chmod(pg_data, PG_DIR_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2778,7 +2757,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (pg_mkdir_p(xlog_dir, S_IRWXU) != 0)
+ if (pg_mkdir_p(xlog_dir, PG_DIR_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2796,7 +2775,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (chmod(xlog_dir, S_IRWXU) != 0)
+ if (chmod(xlog_dir, PG_DIR_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2846,7 +2825,7 @@ create_xlog_or_symlink(void)
else
{
/* Without -X option, just make the subdirectory normally */
- if (mkdir(subdirloc, S_IRWXU) < 0)
+ if (mkdir(subdirloc, PG_DIR_MODE_DEFAULT) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, subdirloc, strerror(errno));
@@ -2882,8 +2861,6 @@ initialize_data_directory(void)
setup_signals();
- umask(S_IRWXG | S_IRWXO);
-
create_data_directory();
create_xlog_or_symlink();
@@ -2902,7 +2879,7 @@ initialize_data_directory(void)
* The parent directory already exists, so we only need mkdir() not
* pg_mkdir_p() here, which avoids some failure modes; cf bug #13853.
*/
- if (mkdir(path, S_IRWXU) < 0)
+ if (mkdir(path, PG_DIR_MODE_DEFAULT) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -3016,6 +2993,7 @@ main(int argc, char *argv[])
{"waldir", required_argument, NULL, 'X'},
{"wal-segsize", required_argument, NULL, 12},
{"data-checksums", no_argument, NULL, 'k'},
+ {"allow-group-access", no_argument, NULL, 'g'},
{NULL, 0, NULL, 0}
};
@@ -3057,7 +3035,7 @@ main(int argc, char *argv[])
/* process command-line options */
- while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:", long_options, &option_index)) != -1)
+ while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:g", long_options, &option_index)) != -1)
{
switch (c)
{
@@ -3150,6 +3128,8 @@ main(int argc, char *argv[])
break;
case 12:
str_wal_segment_size_mb = pg_strdup(optarg);
+ case 'g':
+ file_mode_mask = PG_MODE_MASK_ALLOW_GROUP;
break;
default:
/* getopt_long already emitted a complaint */
@@ -3159,6 +3139,8 @@ main(int argc, char *argv[])
}
}
+ /* Set the file mode creation mask */
+ umask(file_mode_mask);
/*
* Non-option argument specifies data directory as long as it wasn't
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index c0cfa6e92c..f767f07ed5 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -4,9 +4,11 @@
use strict;
use warnings;
+use Fcntl ':mode';
+use File::stat qw{lstat};
use PostgresNode;
use TestLib;
-use Test::More tests => 15;
+use Test::More tests => 18;
my $tempdir = TestLib::tempdir;
my $xlogdir = "$tempdir/pgxlog";
@@ -45,6 +47,17 @@ mkdir $datadir;
command_ok([ 'initdb', '-N', '-T', 'german', '-X', $xlogdir, $datadir ],
'successful creation');
+
+ ok(check_pg_data_perm($datadir, 0));
}
command_ok([ 'initdb', '-S', $datadir ], 'sync only');
command_fails([ 'initdb', $datadir ], 'existing data directory');
+
+# Init a new db with group access
+my $datadir_group = "$tempdir/data_group";
+
+command_ok(
+ [ 'initdb', '-g', $datadir_group ],
+ 'successful creation with group access');
+
+ok(check_pg_data_perm($datadir_group, 1));
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index 62c72c3fcf..56cf0b1d9b 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -25,6 +25,7 @@
#include "catalog/pg_control.h"
#include "common/controldata_utils.h"
+#include "common/file_perm.h"
#include "getopt_long.h"
#include "utils/pidfile.h"
@@ -2107,7 +2108,8 @@ main(int argc, char **argv)
*/
argv0 = argv[0];
- umask(S_IRWXG | S_IRWXO);
+ /* Set restrictive mode mask until PGDATA permissions are checked */
+ umask(PG_MODE_MASK_DEFAULT);
/* support --help and --version even if invoked as root */
if (argc > 1)
@@ -2342,6 +2344,9 @@ main(int argc, char **argv)
snprintf(version_file, MAXPGPATH, "%s/PG_VERSION", pg_data);
snprintf(pid_file, MAXPGPATH, "%s/postmaster.pid", pg_data);
snprintf(backup_file, MAXPGPATH, "%s/backup_label", pg_data);
+
+ /* Set mask based on PGDATA permissions */
+ umask(DataDirectoryMask(pg_data));
}
switch (ctl_command)
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl
index 5da4746cb4..b04f54e734 100644
--- a/src/bin/pg_ctl/t/001_start_stop.pl
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -2,9 +2,11 @@ use strict;
use warnings;
use Config;
+use Fcntl ':mode';
+use File::stat qw{lstat};
use PostgresNode;
use TestLib;
-use Test::More tests => 19;
+use Test::More tests => 25;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -57,10 +59,37 @@ command_ok([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ], 'pg_ctl stop');
command_fails([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ],
'second pg_ctl stop fails');
+# Log file for first perm test
+my $logFileName = "$tempdir/data/perm-test-600.log";
+
command_ok(
- [ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
+ [ 'pg_ctl', 'restart', '-D', "$tempdir/data", '-l', $logFileName ],
'pg_ctl restart with server not running');
-command_ok([ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
+
+# Log file should exist and have no group permissions
+ok(-f $logFileName);
+ok(check_pg_data_perm("$tempdir/data", 0));
+
+# Log file for second perm test
+$logFileName = "$tempdir/data/perm-test-640.log";
+
+# Change the data dir mode so log file will be created with group read
+# privileges on the next start
+system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
+
+command_ok(
+ [ 'chmod', "-R", 'g+rX', "$tempdir/data" ],
+ 'add group perms to PGDATA');
+
+command_ok(
+ [ 'pg_ctl', 'start', '-D', "$tempdir/data", '-l', $logFileName ],
+ 'start server to check group permissions');
+
+ok(-f $logFileName);
+ok(check_pg_data_perm("$tempdir/data", 1));
+
+command_ok(
+ [ 'pg_ctl', 'restart', '-D', "$tempdir/data", '-l', $logFileName ],
'pg_ctl restart with server running');
system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index a132cf2e9f..6a4dc502c4 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -52,6 +52,7 @@
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
#include "common/fe_memutils.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "storage/large_object.h"
#include "pg_getopt.h"
@@ -327,6 +328,9 @@ main(int argc, char *argv[])
exit(1);
}
+ /* Set mask based on PGDATA permissions */
+ umask(DataDirectoryMask(DataDir));
+
/* Check that data directory matches our server version */
CheckDataVersion();
@@ -919,7 +923,7 @@ RewriteControlFile(void)
fd = open(XLOG_CONTROL_FILE,
O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ PG_FILE_MODE_DEFAULT);
if (fd < 0)
{
fprintf(stderr, _("%s: could not create pg_control file: %s\n"),
@@ -1201,7 +1205,7 @@ WriteEmptyXLOG(void)
unlink(path);
fd = open(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ PG_FILE_MODE_DEFAULT);
if (fd < 0)
{
fprintf(stderr, _("%s: could not open file \"%s\": %s\n"),
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index 42fd577f21..5b310f208b 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -115,10 +115,12 @@ sub check_query
sub setup_cluster
{
my $extra_name = shift;
+ my $group_access = shift;
# Initialize master, data checksums are mandatory
$node_master = get_new_node('master' . ($extra_name ? "_${extra_name}" : ''));
- $node_master->init(allows_streaming => 1);
+ $node_master->init(allows_streaming => 1,
+ has_group_access => defined($group_access) ? $group_access : 0);
# Set wal_keep_segments to prevent WAL segment recycling after enforced
# checkpoints in the tests.
$node_master->append_conf('postgresql.conf', qq(
@@ -236,6 +238,9 @@ sub run_pg_rewind
"$tmp_folder/master-postgresql.conf.tmp",
"$master_pgdata/postgresql.conf");
+ chmod($node_master->group_access() ? 0640 : 0600, "$master_pgdata/postgresql.conf")
+ or die("unable to set permissions for $master_pgdata/postgresql.conf");
+
# Plug-in rewound node to the now-promoted standby node
my $port_standby = $node_standby->port;
$node_master->append_conf(
@@ -254,6 +259,9 @@ recovery_target_timeline='latest'
# Clean up after the test. Stop both servers, if they're still running.
sub clean_rewind_test
{
+ ok (check_pg_data_perm(
+ $node_master->data_dir(), $node_master->group_access()));
+
$node_master->teardown_node if defined $node_master;
$node_standby->teardown_node if defined $node_standby;
}
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 705383d184..d51914e901 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -18,6 +18,7 @@
#include <fcntl.h>
#include <unistd.h>
+#include "common/file_perm.h"
#include "file_ops.h"
#include "filemap.h"
#include "logging.h"
@@ -58,7 +59,7 @@ open_target_file(const char *path, bool trunc)
mode = O_WRONLY | O_CREAT | PG_BINARY;
if (trunc)
mode |= O_TRUNC;
- dstfd = open(dstpath, mode, 0600);
+ dstfd = open(dstpath, mode, PG_FILE_MODE_DEFAULT);
if (dstfd < 0)
pg_fatal("could not open target file \"%s\": %s\n",
dstpath, strerror(errno));
@@ -190,7 +191,7 @@ truncate_target_file(const char *path, off_t newsize)
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
- fd = open(dstpath, O_WRONLY, 0);
+ fd = open(dstpath, O_WRONLY, PG_FILE_MODE_DEFAULT);
if (fd < 0)
pg_fatal("could not open file \"%s\" for truncation: %s\n",
dstpath, strerror(errno));
@@ -211,7 +212,7 @@ create_target_dir(const char *path)
return;
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
- if (mkdir(dstpath, S_IRWXU) != 0)
+ if (mkdir(dstpath, PG_DIR_MODE_DEFAULT) != 0)
pg_fatal("could not create directory \"%s\": %s\n",
dstpath, strerror(errno));
}
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 72ab2f8d5e..11153e5f2f 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -24,6 +24,7 @@
#include "access/xlog_internal.h"
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "getopt_long.h"
#include "storage/bufpage.h"
@@ -185,6 +186,9 @@ main(int argc, char **argv)
exit(1);
}
+ /* Set mask based on PGDATA permissions */
+ umask(DataDirectoryMask(datadir_target));
+
/*
* Don't allow pg_rewind to be run as root, to avoid overwriting the
* ownership of files in the data directory. We need only check for root
diff --git a/src/bin/pg_rewind/t/001_basic.pl b/src/bin/pg_rewind/t/001_basic.pl
index 736f34eae3..205bdd77ef 100644
--- a/src/bin/pg_rewind/t/001_basic.pl
+++ b/src/bin/pg_rewind/t/001_basic.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 8;
+use Test::More tests => 10;
use RewindTest;
@@ -9,7 +9,7 @@ sub run_test
{
my $test_mode = shift;
- RewindTest::setup_cluster($test_mode);
+ RewindTest::setup_cluster($test_mode, 1);
RewindTest::start_master();
# Create a test table and insert a row in master.
diff --git a/src/bin/pg_rewind/t/002_databases.pl b/src/bin/pg_rewind/t/002_databases.pl
index 37cdd712f3..0e0140b96f 100644
--- a/src/bin/pg_rewind/t/002_databases.pl
+++ b/src/bin/pg_rewind/t/002_databases.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 6;
use RewindTest;
diff --git a/src/bin/pg_rewind/t/003_extrafiles.pl b/src/bin/pg_rewind/t/003_extrafiles.pl
index 2433a4aab6..28933da343 100644
--- a/src/bin/pg_rewind/t/003_extrafiles.pl
+++ b/src/bin/pg_rewind/t/003_extrafiles.pl
@@ -3,7 +3,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 6;
use File::Find;
@@ -14,6 +14,8 @@ sub run_test
{
my $test_mode = shift;
+ umask(0077);
+
RewindTest::setup_cluster($test_mode);
RewindTest::start_master();
diff --git a/src/bin/pg_rewind/t/004_pg_xlog_symlink.pl b/src/bin/pg_rewind/t/004_pg_xlog_symlink.pl
index feadaa6a0f..70dd061915 100644
--- a/src/bin/pg_rewind/t/004_pg_xlog_symlink.pl
+++ b/src/bin/pg_rewind/t/004_pg_xlog_symlink.pl
@@ -14,7 +14,7 @@ if ($windows_os)
}
else
{
- plan tests => 4;
+ plan tests => 6;
}
use RewindTest;
diff --git a/src/bin/pg_rewind/t/005_same_timeline.pl b/src/bin/pg_rewind/t/005_same_timeline.pl
index 0e334ee191..2fbca4ad7c 100644
--- a/src/bin/pg_rewind/t/005_same_timeline.pl
+++ b/src/bin/pg_rewind/t/005_same_timeline.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 1;
+use Test::More tests => 2;
use RewindTest;
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 8d4f254f9f..44d63f451f 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -442,7 +442,7 @@ create_script_for_cluster_analyze(char **analyze_script_file_name)
*analyze_script_file_name = psprintf("%sanalyze_new_cluster.%s",
SCRIPT_PREFIX, SCRIPT_EXT);
- if ((script = fopen_priv(*analyze_script_file_name, "w")) == NULL)
+ if ((script = fopen(*analyze_script_file_name, "w")) == NULL)
pg_fatal("could not open file \"%s\": %s\n",
*analyze_script_file_name, strerror(errno));
@@ -570,7 +570,7 @@ create_script_for_old_cluster_deletion(char **deletion_script_file_name)
prep_status("Creating script to delete old cluster");
- if ((script = fopen_priv(*deletion_script_file_name, "w")) == NULL)
+ if ((script = fopen(*deletion_script_file_name, "w")) == NULL)
pg_fatal("could not open file \"%s\": %s\n",
*deletion_script_file_name, strerror(errno));
@@ -834,7 +834,7 @@ check_for_isn_and_int8_passing_mismatch(ClusterInfo *cluster)
for (rowno = 0; rowno < ntups; rowno++)
{
found = true;
- if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL)
+ if (script == NULL && (script = fopen(output_path, "w")) == NULL)
pg_fatal("could not open file \"%s\": %s\n",
output_path, strerror(errno));
if (!db_used)
@@ -937,7 +937,7 @@ check_for_reg_data_type_usage(ClusterInfo *cluster)
for (rowno = 0; rowno < ntups; rowno++)
{
found = true;
- if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL)
+ if (script == NULL && (script = fopen(output_path, "w")) == NULL)
pg_fatal("could not open file \"%s\": %s\n",
output_path, strerror(errno));
if (!db_used)
@@ -1028,7 +1028,7 @@ check_for_jsonb_9_4_usage(ClusterInfo *cluster)
for (rowno = 0; rowno < ntups; rowno++)
{
found = true;
- if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL)
+ if (script == NULL && (script = fopen(output_path, "w")) == NULL)
pg_fatal("could not open file \"%s\": %s\n",
output_path, strerror(errno));
if (!db_used)
diff --git a/src/bin/pg_upgrade/dump.c b/src/bin/pg_upgrade/dump.c
index 8a662e9865..def22c6521 100644
--- a/src/bin/pg_upgrade/dump.c
+++ b/src/bin/pg_upgrade/dump.c
@@ -18,7 +18,6 @@ void
generate_old_dump(void)
{
int dbnum;
- mode_t old_umask;
prep_status("Creating dump of global objects");
@@ -33,13 +32,6 @@ generate_old_dump(void)
prep_status("Creating dump of database schemas\n");
- /*
- * Set umask for this function, all functions it calls, and all
- * subprocesses/threads it creates. We can't use fopen_priv() as Windows
- * uses threads and umask is process-global.
- */
- old_umask = umask(S_IRWXG | S_IRWXO);
-
/* create per-db dump files */
for (dbnum = 0; dbnum < old_cluster.dbarr.ndbs; dbnum++)
{
@@ -74,8 +66,6 @@ generate_old_dump(void)
while (reap_child(true) == true)
;
- umask(old_umask);
-
end_progress_output();
check_ok();
}
diff --git a/src/bin/pg_upgrade/file.c b/src/bin/pg_upgrade/file.c
index f88e3d558f..595d43ee31 100644
--- a/src/bin/pg_upgrade/file.c
+++ b/src/bin/pg_upgrade/file.c
@@ -10,6 +10,7 @@
#include "postgres_fe.h"
#include "access/visibilitymap.h"
+#include "common/file_perm.h"
#include "pg_upgrade.h"
#include "storage/bufpage.h"
#include "storage/checksum.h"
@@ -44,7 +45,7 @@ copyFile(const char *src, const char *dst,
schemaName, relName, src, strerror(errno));
if ((dest_fd = open(dst, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR)) < 0)
+ PG_FILE_MODE_DEFAULT)) < 0)
pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
schemaName, relName, dst, strerror(errno));
@@ -151,7 +152,7 @@ rewriteVisibilityMap(const char *fromfile, const char *tofile,
schemaName, relName, fromfile, strerror(errno));
if ((dst_fd = open(tofile, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR)) < 0)
+ PG_FILE_MODE_DEFAULT)) < 0)
pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
schemaName, relName, tofile, strerror(errno));
@@ -314,18 +315,3 @@ win32_pghardlink(const char *src, const char *dst)
return 0;
}
#endif
-
-
-/* fopen() file with no group/other permissions */
-FILE *
-fopen_priv(const char *path, const char *mode)
-{
- mode_t old_umask = umask(S_IRWXG | S_IRWXO);
- FILE *fp;
-
- fp = fopen(path, mode);
-
- umask(old_umask); /* we assume this can't change errno */
-
- return fp;
-}
diff --git a/src/bin/pg_upgrade/function.c b/src/bin/pg_upgrade/function.c
index d61fa38c92..70d8cf2902 100644
--- a/src/bin/pg_upgrade/function.c
+++ b/src/bin/pg_upgrade/function.c
@@ -249,7 +249,7 @@ check_loadable_libraries(void)
{
found = true;
- if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL)
+ if (script == NULL && (script = fopen(output_path, "w")) == NULL)
pg_fatal("could not open file \"%s\": %s\n",
output_path, strerror(errno));
fprintf(script, _("could not load library \"%s\": %s"),
diff --git a/src/bin/pg_upgrade/option.c b/src/bin/pg_upgrade/option.c
index 9dbc9225a6..ef84f44672 100644
--- a/src/bin/pg_upgrade/option.c
+++ b/src/bin/pg_upgrade/option.c
@@ -97,7 +97,7 @@ parseCommandLine(int argc, char *argv[])
if (os_user_effective_id == 0)
pg_fatal("%s: cannot be run as root\n", os_info.progname);
- if ((log_opts.internal = fopen_priv(INTERNAL_LOG_FILE, "a")) == NULL)
+ if ((log_opts.internal = fopen(INTERNAL_LOG_FILE, "a")) == NULL)
pg_fatal("could not write to log file \"%s\"\n", INTERNAL_LOG_FILE);
while ((option = getopt_long(argc, argv, "d:D:b:B:cj:ko:O:p:P:rU:v",
@@ -213,7 +213,7 @@ parseCommandLine(int argc, char *argv[])
/* label start of upgrade in logfiles */
for (filename = output_files; *filename != NULL; filename++)
{
- if ((fp = fopen_priv(*filename, "a")) == NULL)
+ if ((fp = fopen(*filename, "a")) == NULL)
pg_fatal("could not write to log file \"%s\"\n", *filename);
/* Start with newline because we might be appending to a file. */
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 872621489f..a1088259af 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -38,6 +38,7 @@
#include "pg_upgrade.h"
#include "catalog/pg_class.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "fe_utils/string_utils.h"
@@ -95,6 +96,9 @@ main(int argc, char **argv)
check_cluster_compatibility(live_check);
+ /* Set mask based on PGDATA permissions */
+ umask(DataDirectoryMask(new_cluster.pgdata));
+
check_and_dump_old_cluster(live_check);
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 67f874b4f1..b8a1a18e36 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -374,7 +374,6 @@ void linkFile(const char *src, const char *dst,
void rewriteVisibilityMap(const char *fromfile, const char *tofile,
const char *schemaName, const char *relName);
void check_hard_link(void);
-FILE *fopen_priv(const char *path, const char *mode);
/* function.c */
diff --git a/src/bin/pg_upgrade/version.c b/src/bin/pg_upgrade/version.c
index 76e9d65537..205f772fc5 100644
--- a/src/bin/pg_upgrade/version.c
+++ b/src/bin/pg_upgrade/version.c
@@ -53,7 +53,7 @@ new_9_0_populate_pg_largeobject_metadata(ClusterInfo *cluster, bool check_mode)
{
PQExpBufferData connectbuf;
- if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL)
+ if (script == NULL && (script = fopen(output_path, "w")) == NULL)
pg_fatal("could not open file \"%s\": %s\n", output_path,
strerror(errno));
@@ -152,7 +152,7 @@ old_9_3_check_for_line_data_type_usage(ClusterInfo *cluster)
for (rowno = 0; rowno < ntups; rowno++)
{
found = true;
- if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL)
+ if (script == NULL && (script = fopen(output_path, "w")) == NULL)
pg_fatal("could not open file \"%s\": %s\n", output_path,
strerror(errno));
if (!db_used)
@@ -253,7 +253,7 @@ old_9_6_check_for_unknown_data_type_usage(ClusterInfo *cluster)
for (rowno = 0; rowno < ntups; rowno++)
{
found = true;
- if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL)
+ if (script == NULL && (script = fopen(output_path, "w")) == NULL)
pg_fatal("could not open file \"%s\": %s\n", output_path,
strerror(errno));
if (!db_used)
@@ -335,7 +335,7 @@ old_9_6_invalidate_hash_indexes(ClusterInfo *cluster, bool check_mode)
found = true;
if (!check_mode)
{
- if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL)
+ if (script == NULL && (script = fopen(output_path, "w")) == NULL)
pg_fatal("could not open file \"%s\": %s\n", output_path,
strerror(errno));
if (!db_used)
diff --git a/src/common/Makefile b/src/common/Makefile
index 80e78d72fe..504375bef4 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -51,7 +51,7 @@ else
OBJS_COMMON += sha2.o
endif
-OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o file_utils.o restricted_token.o
+OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o file_perm.o file_utils.o restricted_token.o
OBJS_SRV = $(OBJS_COMMON:%.o=%_srv.o)
diff --git a/src/common/file_perm.c b/src/common/file_perm.c
new file mode 100644
index 0000000000..2be76913cd
--- /dev/null
+++ b/src/common/file_perm.c
@@ -0,0 +1,50 @@
+/*-------------------------------------------------------------------------
+ *
+ * File and directory permission routines
+ *
+ * Group read/execute may optional be enabled on PGDATA so any frontend tools
+ * That write into PGDATA must know what mask to set and the permissions to
+ * use for creating files and directories.
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/file_perm.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres_fe.h"
+
+#include <sys/stat.h>
+
+#include "common/file_perm.h"
+
+
+/*
+ * Determine the mask to use when writing to PGDATA.
+ *
+ * Errors are not handled here and should be checked by the frontend
+ * application.
+ */
+#ifdef FRONTEND
+
+mode_t
+DataDirectoryMask(const char *dataDir)
+{
+ struct stat statBuf;
+
+ /*
+ * If an error occurs getting the mode then return the more restrictive mask.
+ * It is the reponsibility of the frontend application to generate an error.
+ */
+ if (stat(dataDir, &statBuf) != 0)
+ return PG_MODE_MASK_DEFAULT;
+
+ /*
+ * Construct the mask that the caller should pass to umask().
+ */
+ return PG_MODE_MASK_DEFAULT & ~statBuf.st_mode;
+}
+
+#endif /* FRONTEND */
diff --git a/src/include/common/file_perm.h b/src/include/common/file_perm.h
new file mode 100644
index 0000000000..b94fdaf08a
--- /dev/null
+++ b/src/include/common/file_perm.h
@@ -0,0 +1,55 @@
+/*-------------------------------------------------------------------------
+ *
+ * File and directory permission constants and routines
+ *
+ * Group read/execute may optional be enabled on PGDATA so any frontend tools
+ * That write into PGDATA must know what mask to set and the permissions to
+ * use for creating files and directories.
+ *
+ * This module is located in common so the backend can use the constants.
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/file_perm.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FILE_PERM_H
+#define FILE_PERM_H
+
+/*
+ * Default mode mask for data directory permissions that does not allow
+ * group execute/read.
+ */
+#define PG_MODE_MASK_DEFAULT (S_IRWXG | S_IRWXO)
+
+/*
+ * Optional mode mask for data directory permissions that allows group
+ * read/execute.
+ */
+#define PG_MODE_MASK_ALLOW_GROUP (S_IWGRP | S_IRWXO)
+
+/*
+ * Default mode for created files, unless something else is specified using
+ * the *Perm() function variants.
+ */
+#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR | S_IRGRP)
+
+/*
+ * Default mode for directories created with MakeDirectoryDefaultPerm().
+ */
+#define PG_DIR_MODE_DEFAULT (S_IRWXU | S_IRGRP | S_IXGRP)
+
+#ifdef FRONTEND
+
+/*
+ * Determine the mask to use when writing to PGDATA
+ */
+mode_t DataDirectoryMask(const char *dataDir);
+
+#endif /* FRONTEND */
+
+
+#endif /* FILE_PERM_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 909d7202e9..09c3533251 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -57,10 +57,6 @@ extern PGDLLIMPORT int max_files_per_process;
*/
extern int max_safe_fds;
-/*
- * Default mode for directories created with MakeDirectoryDefaultPerm().
- */
-#define PG_DIR_MODE_DEFAULT (S_IRWXU)
/*
* prototypes for functions in fd.c
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 1d5ac4ee35..6d02377294 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -268,6 +268,20 @@ sub connstr
=pod
+=item $node->group_access()
+
+Does the data dir allow group access?
+
+=cut
+
+sub group_access
+{
+ my ($self) = @_;
+ return $self->{_group_access};
+}
+
+=pod
+
=item $node->data_dir()
Returns the path to the data directory. postgresql.conf and pg_hba.conf are
@@ -405,10 +419,16 @@ sub init
$params{allows_streaming} = 0 unless defined $params{allows_streaming};
$params{has_archiving} = 0 unless defined $params{has_archiving};
+ $params{has_group_access} = 0 unless defined $params{has_group_access};
mkdir $self->backup_dir;
mkdir $self->archive_dir;
+ if ($params{has_group_access})
+ {
+ push(@{$params{extra}}, '-g');
+ }
+
TestLib::system_or_bail('initdb', '-D', $pgdata, '-A', 'trust', '-N',
@{ $params{extra} });
TestLib::system_or_bail($ENV{PG_REGRESS}, '--config-auth', $pgdata);
@@ -459,8 +479,12 @@ sub init
}
close $conf;
+ chmod($params{has_group_access} ? 0640 : 0600, "$pgdata/postgresql.conf")
+ or die("unable to set permissions for $pgdata/postgresql.conf");
+
$self->set_replication_conf if $params{allows_streaming};
$self->enable_archiving if $params{has_archiving};
+ $self->{_group_access} = 1 if $params{has_group_access};
}
=pod
@@ -483,6 +507,9 @@ sub append_conf
my $conffile = $self->data_dir . '/' . $filename;
TestLib::append_to_file($conffile, $str . "\n");
+
+ chmod($self->group_access() ? 0640 : 0600, $conffile)
+ or die("unable to set permissions for $conffile");
}
=pod
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index fdd427608b..c4da3140c1 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -12,8 +12,10 @@ use warnings;
use Config;
use Exporter 'import';
+use Fcntl qw(:mode);
use File::Basename;
use File::Spec;
+use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
use SimpleTee;
@@ -26,6 +28,7 @@ our @EXPORT = qw(
slurp_dir
slurp_file
append_to_file
+ check_pg_data_perm
check_pg_config
system_or_bail
system_log
@@ -222,6 +225,96 @@ sub append_to_file
close $fh;
}
+# Ensure all permissions in the pg_data directory are correct. When allow_group
+# is true then permissions should be dir = 0750, file = 0640. When allow_group
+# is false then permissions should be dir = 0700, file = 0600.
+sub check_pg_data_perm
+{
+ my ($dir, $allow_group, $level) = @_;
+
+ # Expected permission
+ my $expected_file_perm = $allow_group ? 0640 : 0600;
+ my $expected_dir_perm = $allow_group ? 0750 : 0700;
+
+ # First level is 0
+ $level = defined($level) ? $level : 0;
+
+ # Get all entries in the dir
+ opendir(my $dir_handle, $dir)
+ or die("unable to open $dir");
+
+ my @dir_entry = grep(!/^(\.\.)$/i, readdir($dir_handle));
+ close($dir_handle);
+
+ @dir_entry != 0
+ or die("unable to read $dir");
+
+ # Check each entry
+ foreach my $entry (@dir_entry)
+ {
+ my $entry_path = $entry eq '.' ? $dir : "$dir/$entry";
+ my $entry_stat = stat($entry_path);
+
+ defined($entry_stat)
+ or die("unable to stat $entry_path");
+
+ my $entry_mode = S_IMODE($entry_stat->mode);
+
+ # Is this a file?
+ if (S_ISREG($entry_stat->mode))
+ {
+ # postmaster.pid file must be 600
+ if ($level == 0 && $entry eq 'postmaster.pid')
+ {
+ if ($entry_mode != 0600)
+ {
+ print(*STDERR, "$entry_path mode must be 0600\n");
+ return 0;
+ }
+ }
+ else
+ {
+ if ($entry_mode != $expected_file_perm)
+ {
+ print(*STDERR,
+ sprintf("$entry_path mode must be %04o\n",
+ $expected_file_perm));
+ return 0;
+ }
+ }
+ }
+ # Else a directory?
+ elsif (S_ISDIR($entry_stat->mode))
+ {
+ # Only need to check the dir once
+ if ($entry eq '.')
+ {
+ if ($entry_mode != $expected_dir_perm)
+ {
+ print(*STDERR,
+ sprintf("$entry_path mode must be %04o\n",
+ $expected_dir_perm));
+ return 0;
+ }
+ }
+ else
+ {
+ if (!check_pg_data_perm($entry_path, $allow_group, $level + 1))
+ {
+ return 0;
+ }
+ }
+ }
+ # Else something we can't handle
+ else
+ {
+ die "unknown file type for $entry_path";
+ }
+ }
+
+ return 1;
+}
+
# Check presence of a given regexp within pg_config.h for the installation
# where tests are running, returning a match status result depending on
# that.
On 1/19/18 14:07, David Steele wrote:
I have yet to add tests for pg_rewindwal and pg_upgrade. pg_rewindwal
doesn't *have* any tests as far as I can tell and pg_upgrade has tests
in a shell script -- it's not clear how I would extend it or reuse the
Perl code for perm testing.Does anyone have suggestions on tests for pg_resetwal and pg_upgrade?
Should I start from scratch?
A test suite for pg_resetwal would be nice.
TAP tests for pg_upgrade will create problems with the build farm.
There was a recent thread about that.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Peter Eisentraut wrote:
On 1/19/18 14:07, David Steele wrote:
I have yet to add tests for pg_rewindwal and pg_upgrade. pg_rewindwal
doesn't *have* any tests as far as I can tell and pg_upgrade has tests
in a shell script -- it's not clear how I would extend it or reuse the
Perl code for perm testing.Does anyone have suggestions on tests for pg_resetwal and pg_upgrade?
Should I start from scratch?A test suite for pg_resetwal would be nice.
TAP tests for pg_upgrade will create problems with the build farm.
There was a recent thread about that.
Is this about this commit?
commit 58ffe141eb37c3f027acd25c1fc6b36513bf9380
Author: Peter Eisentraut <peter_e@gmx.net>
AuthorDate: Fri Sep 22 16:34:46 2017 -0400
CommitDate: Fri Sep 22 16:46:56 2017 -0400
Revert "Add basic TAP test setup for pg_upgrade"
This reverts commit f41e56c76e39f02bef7ba002c9de03d62b76de4d.
The build farm client would run the pg_upgrade tests twice, once as part
of the existing pg_upgrade check run and once as part of picking up all
TAP tests by looking for "t" directories. Since the pg_upgrade tests
are pretty slow, we will need a better solution or possibly a build farm
client change before we can proceed with this.
If the only problem is that buildfarm would run tests twice, then I
think we should just press forward with this regardless of that: it
seems a chicken-and-egg problem, because buildfarm cannot be upgraded to
use some new test method if the method doesn't exist yet. As a
solution, let's just live with some run duplication for a period until
the machines are upgraded to a future buildfarm client code.
If there are other problems, let's see what they are so that we can fix
them.
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Fri, Jan 19, 2018 at 06:54:23PM -0300, Alvaro Herrera wrote:
Peter Eisentraut wrote:
If the only problem is that buildfarm would run tests twice, then I
think we should just press forward with this regardless of that: it
seems a chicken-and-egg problem, because buildfarm cannot be upgraded to
use some new test method if the method doesn't exist yet. As a
solution, let's just live with some run duplication for a period until
the machines are upgraded to a future buildfarm client code.If there are other problems, let's see what they are so that we can fix
them.
The main complain I received about this move of the pg_upgrade tests to
be a TAP one is that they don't support easily cross-major version
upgrades, removing some abilities to what a developer or the builfarm
can actually do. Making this possible would require first some
refactoring of PostgresNode.pm so as a node is aware of the binary paths
it uses to be able to manipulate multiple instances with different
install paths at the same time.
--
Michael
On 1/19/18 4:43 PM, Peter Eisentraut wrote:
On 1/19/18 14:07, David Steele wrote:
I have yet to add tests for pg_rewindwal and pg_upgrade. pg_rewindwal
doesn't *have* any tests as far as I can tell and pg_upgrade has tests
in a shell script -- it's not clear how I would extend it or reuse the
Perl code for perm testing.Does anyone have suggestions on tests for pg_resetwal and pg_upgrade?
Should I start from scratch?A test suite for pg_resetwal would be nice.
It sure would! I'll put together a basic test suite then add perms
testing to it.
--
-David
david@pgmasters.net
On 1/20/18 5:47 PM, Michael Paquier wrote:
On Fri, Jan 19, 2018 at 06:54:23PM -0300, Alvaro Herrera wrote:
Peter Eisentraut wrote:
If the only problem is that buildfarm would run tests twice, then I
think we should just press forward with this regardless of that: it
seems a chicken-and-egg problem, because buildfarm cannot be upgraded to
use some new test method if the method doesn't exist yet. As a
solution, let's just live with some run duplication for a period until
the machines are upgraded to a future buildfarm client code.
It also appears that the TAP tests don't have the infrastructure to be
able to run pg_ugrade properly, per below.
The main complain I received about this move of the pg_upgrade tests to
be a TAP one is that they don't support easily cross-major version
upgrades, removing some abilities to what a developer or the builfarm
can actually do.
Unless I read it wrong the buildfarm is not doing cross-version
upgrades, but a developer/user can do so manually using the same script?
Making this possible would require first some
refactoring of PostgresNode.pm so as a node is aware of the binary paths
it uses to be able to manipulate multiple instances with different
install paths at the same time.
Any idea of what the LOE would be for that?
Thanks,
--
-David
david@pgmasters.net
David Steele <david@pgmasters.net> writes:
Unless I read it wrong the buildfarm is not doing cross-version
upgrades, but a developer/user can do so manually using the same script?
The buildfarm isn't doing that *by default*, but Andrew has at least
one critter configured to do so (crake I think). It found a bug just
a couple days ago too, so losing that capability isn't going to sell.
regards, tom lane
On 1/23/18 9:26 AM, Tom Lane wrote:
David Steele <david@pgmasters.net> writes:
Unless I read it wrong the buildfarm is not doing cross-version
upgrades, but a developer/user can do so manually using the same script?The buildfarm isn't doing that *by default*, but Andrew has at least
one critter configured to do so (crake I think). It found a bug just
a couple days ago too, so losing that capability isn't going to sell.
Thanks for the clarification, Tom.
What if I update pg_upgrade/test.sh to optionally allow group
permissions and we configure an animal to test it if it gets committed?
It's not ideal, I know, but it would get the permissions patch over the
line and is consistent with how we currently test pg_upgrade.
Regards,
--
-David
david@pgmasters.net
On 1/19/18 16:54, Alvaro Herrera wrote:
If the only problem is that buildfarm would run tests twice, then I
think we should just press forward with this regardless of that: it
seems a chicken-and-egg problem, because buildfarm cannot be upgraded to
use some new test method if the method doesn't exist yet. As a
solution, let's just live with some run duplication for a period until
the machines are upgraded to a future buildfarm client code.
Well, people protested against that approach.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Peter Eisentraut <peter.eisentraut@2ndquadrant.com> writes:
On 1/19/18 16:54, Alvaro Herrera wrote:
If the only problem is that buildfarm would run tests twice, then I
think we should just press forward with this regardless of that: it
seems a chicken-and-egg problem, because buildfarm cannot be upgraded to
use some new test method if the method doesn't exist yet. As a
solution, let's just live with some run duplication for a period until
the machines are upgraded to a future buildfarm client code.
Well, people protested against that approach.
I think I was one of the complainers, but my objection was pretty
straightforward. I want the buildfarm script update to be available
more or less contemporaneously with the change going into git.
Some owners might not care if their machines are doing lots of
extra work, but for those that do, we shouldn't leave them stuck
until a buildfarm update appears in some indefinite future.
In short: get Dunstan into the loop. Don't push this till he's
signed off on new buildfarm code.
regards, tom lane
On 1/23/18 09:33, David Steele wrote:
What if I update pg_upgrade/test.sh to optionally allow group
permissions and we configure an animal to test it if it gets committed?It's not ideal, I know, but it would get the permissions patch over the
line and is consistent with how we currently test pg_upgrade.
Basically, what you'd need is some way to pass some options to the
initdb invocations in the pg_upgrade test script, right? That would
seem reasonable, and also useful for testing upgrading with other initdb
options.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Tue, Jan 23, 2018 at 09:18:51AM -0500, David Steele wrote:
On 1/20/18 5:47 PM, Michael Paquier wrote:
Making this possible would require first some
refactoring of PostgresNode.pm so as a node is aware of the binary paths
it uses to be able to manipulate multiple instances with different
install paths at the same time.Any idea of what the LOE would be for that?
What does LOE means?
--
Michael
On 1/23/18 9:22 PM, Michael Paquier wrote:
On Tue, Jan 23, 2018 at 09:18:51AM -0500, David Steele wrote:
On 1/20/18 5:47 PM, Michael Paquier wrote:
Making this possible would require first some
refactoring of PostgresNode.pm so as a node is aware of the binary paths
it uses to be able to manipulate multiple instances with different
install paths at the same time.Any idea of what the LOE would be for that?
What does LOE means?
Sorry - it means "level of effort". I was trying to get an idea if it
is something that could be available in the PG11 development cycle, or
if I should be looking at other ways to get the testing for this patch
completed.
--
-David
david@pgmasters.net
On Tue, Jan 23, 2018 at 10:48:07PM -0500, David Steele wrote:
Sorry - it means "level of effort". I was trying to get an idea if it
is something that could be available in the PG11 development cycle, or
if I should be looking at other ways to get the testing for this patch
completed.
I don't think that it would be more than a couple of hours digging
things out, but that may be optimistic. What only needs to be done is
extending get_new_node with an optional argument to define the bin
directory of a PostgresNode and register the path. If the binary
directory is undefined, just rely on PATH and call pg_config --bindir to
get the information, then register it. What we need to be careful at is
that all the binary calls of PostgresNode.pm need to be changed to use
the binary path, which is not really complicated, but it can be easy to
miss some.
There is one small issue with TestLib::check_pg_config but I think that
we can live with the existing version relying on PATH. Once this
refactoring is done, you need the patch set I sent previously here with
some tiny modifications:
/messages/by-id/CAB7nPqRJXz0sEuUL36eBsF7iZtOQGMJoJPGFWxHLuS6TYPxf5w@mail.gmail.com
Those modifications involve just looking at the environment variables
specified in pg_upgrade's TESTING and change the node binaries if those
are defined. It is also necessary to tweak probin's paths for the dump
consistency checks, which is something that my last patch set was not
doing.
It is tiring to see this topic come up again and again, so you know
what, let's bite the bullet. I commit myself into coding this thing with
a patch set for the next commit fest. the only thing I want to be sure
about is if folks on this list are fine with the plan of this email.
--
Michael
On 1/19/18 4:43 PM, Peter Eisentraut wrote:
On 1/19/18 14:07, David Steele wrote:
I have yet to add tests for pg_rewindwal and pg_upgrade. pg_rewindwal
doesn't *have* any tests as far as I can tell and pg_upgrade has tests
in a shell script -- it's not clear how I would extend it or reuse the
Perl code for perm testing.Does anyone have suggestions on tests for pg_resetwal and pg_upgrade?
Should I start from scratch?A test suite for pg_resetwal would be nice.
Agreed.
TAP tests for pg_upgrade will create problems with the build farm.
There was a recent thread about that.
OK, that being the case I have piggy-backed on the current pg_upgrade
tests in the same way that --wal-segsize did.
There are now three patches:
1) 01-pgresetwal-test
Adds a *very* basic test suite for pg_resetwal. I was able to make this
utility core dump (floating point errors) pretty easily with empty or
malformed pg_control files so I focused on WAL reset functionality plus
the basic help/command tests that every utility has.
2) 02-mkdir
Converts mkdir() calls to MakeDirectoryDefaultPerm() if the original
call used default permissions.
3) 03-group
Allow group access on PGDATA. This includes backend changes, utility
changes (initdb, pg_ctl, pg_upgrade, etc.) and tests for each utility.
Regards,
--
-David
david@pgmasters.net
Attachments:
group-access-v6-01-pgresetwal-test.patchtext/plain; charset=UTF-8; name=group-access-v6-01-pgresetwal-test.patch; x-mac-creator=0; x-mac-type=0Download
diff --git a/src/bin/pg_resetwal/Makefile b/src/bin/pg_resetwal/Makefile
index 5ab7ad33e0..13c9ca6279 100644
--- a/src/bin/pg_resetwal/Makefile
+++ b/src/bin/pg_resetwal/Makefile
@@ -33,3 +33,9 @@ uninstall:
clean distclean maintainer-clean:
rm -f pg_resetwal$(X) $(OBJS)
+
+check:
+ $(prove_check)
+
+installcheck:
+ $(prove_installcheck)
diff --git a/src/bin/pg_resetwal/t/001_basic.pl b/src/bin/pg_resetwal/t/001_basic.pl
new file mode 100644
index 0000000000..234bd70303
--- /dev/null
+++ b/src/bin/pg_resetwal/t/001_basic.pl
@@ -0,0 +1,53 @@
+use strict;
+use warnings;
+
+use Config;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 13;
+
+my $tempdir = TestLib::tempdir;
+my $tempdir_short = TestLib::tempdir_short;
+
+program_help_ok('pg_resetwal');
+program_version_ok('pg_resetwal');
+program_options_handling_ok('pg_resetwal');
+
+# Initialize node without replication settings
+my $node = get_new_node('main');
+my $pgdata = $node->data_dir;
+my $pgwal = "$pgdata/pg_wal";
+$node->init;
+$node->start;
+
+# Remove WAL from pg_wal and make sure it gets rebuilt
+$node->stop;
+
+unlink("$pgwal/000000010000000000000001") == 1
+ or die("unable to remove 000000010000000000000001");
+
+is_deeply(
+ [sort(slurp_dir($pgwal))], [sort(qw(. .. archive_status))], 'no WAL');
+
+$node->command_ok(['pg_resetwal', '-D', $pgdata], 'recreate pg_wal');
+
+is_deeply(
+ [sort(slurp_dir($pgwal))],
+ [sort(qw(. .. archive_status 000000010000000000000002))],
+ 'WAL recreated');
+
+$node->start;
+
+# Reset to specific WAL segment
+$node->stop;
+
+$node->command_ok(
+ ['pg_resetwal', '-l', '000000070000000700000007', '-D', $pgdata],
+ 'set to specific WAL');
+
+is_deeply(
+ [sort(slurp_dir($pgwal))],
+ [sort(qw(. .. archive_status 000000070000000700000007))],
+ 'WAL recreated');
+
+$node->start;
group-access-v6-02-mkdir.patchtext/plain; charset=UTF-8; name=group-access-v6-02-mkdir.patch; x-mac-creator=0; x-mac-type=0Download
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index e42b828edf..9814ac6d3e 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4086,7 +4086,7 @@ ValidateXLOGDirectoryStructure(void)
{
ereport(LOG,
(errmsg("creating missing WAL directory \"%s\"", path)));
- if (mkdir(path, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(path) < 0)
ereport(FATAL,
(errmsg("could not create missing directory \"%s\": %m",
path)));
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 5c450caa4e..746b8c121b 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -151,7 +151,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
else
{
/* Directory creation failed? */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(dir) < 0)
{
char *parentdir;
@@ -173,7 +173,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
get_parent_directory(parentdir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -184,7 +184,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
parentdir = pstrdup(dir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -192,7 +192,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
pfree(parentdir);
/* Create database directory */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(dir) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -279,7 +279,8 @@ CreateTableSpace(CreateTableSpaceStmt *stmt)
/*
* Check that location isn't too long. Remember that we're going to append
* 'PG_XXX/<dboid>/<relid>_<fork>.<nnn>'. FYI, we never actually
- * reference the whole path here, but mkdir() uses the first two parts.
+ * reference the whole path here, but MakeDirectoryDefaultPerm() uses the first
+ * two parts.
*/
if (strlen(location) + 1 + strlen(TABLESPACE_VERSION_DIRECTORY) + 1 +
OIDCHARS + 1 + OIDCHARS + 1 + FORKNAMECHARS + 1 + OIDCHARS > MAXPGPATH)
@@ -574,7 +575,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* Attempt to coerce target directory to safe permissions. If this fails,
* it doesn't exist or has the wrong owner.
*/
- if (chmod(location, S_IRWXU) != 0)
+ if (chmod(location, PG_DIR_MODE_DEFAULT) != 0)
{
if (errno == ENOENT)
ereport(ERROR,
@@ -599,7 +600,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
if (stat(location_with_version_dir, &st) == 0 && S_ISDIR(st.st_mode))
{
if (!rmtree(location_with_version_dir, true))
- /* If this failed, mkdir() below is going to error. */
+ /* If this failed, MakeDirectoryDefaultPerm() below is going to error. */
ereport(WARNING,
(errmsg("some useless files may be left behind in old database directory \"%s\"",
location_with_version_dir)));
@@ -610,7 +611,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* The creation of the version directory prevents more than one tablespace
* in a single location.
*/
- if (mkdir(location_with_version_dir, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(location_with_version_dir) < 0)
{
if (errno == EEXIST)
ereport(ERROR,
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index f3ddf828bb..e13a2ae8c4 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -4495,7 +4495,7 @@ internal_forkexec(int argc, char *argv[], Port *port)
* As in OpenTemporaryFileInTablespace, try to make the temp-file
* directory
*/
- mkdir(PG_TEMP_FILES_DIR, S_IRWXU);
+ MakeDirectoryDefaultPerm(PG_TEMP_FILES_DIR);
fp = AllocateFile(tmpfilename, PG_BINARY_W);
if (!fp)
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index f70eea37df..ae23941f59 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -41,6 +41,7 @@
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
#include "storage/dsm.h"
+#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pg_shmem.h"
@@ -322,7 +323,7 @@ SysLoggerMain(int argc, char *argv[])
/*
* Also, create new directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ MakeDirectoryDefaultPerm(Log_directory);
}
if (strcmp(Log_filename, currentLogFilename) != 0)
{
@@ -564,7 +565,7 @@ SysLogger_Start(void)
/*
* Create log directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ MakeDirectoryDefaultPerm(Log_directory);
/*
* The initial logfile is created right in the postmaster, to verify that
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index fc9ef22b0b..3a98360b50 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -1166,13 +1166,14 @@ CreateSlotOnDisk(ReplicationSlot *slot)
* It's just barely possible that some previous effort to create or drop a
* slot with this name left a temp directory lying around. If that seems
* to be the case, try to remove it. If the rmtree() fails, we'll error
- * out at the mkdir() below, so we don't bother checking success.
+ * out at the MakeDirectoryDefaultPerm() below, so we don't bother checking
+ * success.
*/
if (stat(tmppath, &st) == 0 && S_ISDIR(st.st_mode))
rmtree(tmppath, true);
/* Create and fsync the temporary slot directory. */
- if (mkdir(tmppath, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(tmppath) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
diff --git a/src/backend/storage/file/copydir.c b/src/backend/storage/file/copydir.c
index ca6342db0d..ff980dc1f5 100644
--- a/src/backend/storage/file/copydir.c
+++ b/src/backend/storage/file/copydir.c
@@ -41,7 +41,7 @@ copydir(char *fromdir, char *todir, bool recurse)
char fromfile[MAXPGPATH * 2];
char tofile[MAXPGPATH * 2];
- if (mkdir(todir, S_IRWXU) != 0)
+ if (MakeDirectoryDefaultPerm(todir) != 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m", todir)));
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index 71516a9a5a..9aeca8a04d 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -1435,7 +1435,7 @@ PathNameOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode)
void
PathNameCreateTemporaryDir(const char *basedir, const char *directory)
{
- if (mkdir(directory, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(directory) < 0)
{
if (errno == EEXIST)
return;
@@ -1445,14 +1445,14 @@ PathNameCreateTemporaryDir(const char *basedir, const char *directory)
* EEXIST to close a race against another process following the same
* algorithm.
*/
- if (mkdir(basedir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(basedir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary directory \"%s\": %m",
basedir)));
/* Try again. */
- if (mkdir(directory, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(directory) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary subdirectory \"%s\": %m",
@@ -1602,11 +1602,11 @@ OpenTemporaryFileInTablespace(Oid tblspcOid, bool rejectError)
* We might need to create the tablespace's tempfile directory, if no
* one has yet done so.
*
- * Don't check for error from mkdir; it could fail if someone else
- * just did the same thing. If it doesn't work then we'll bomb out on
+ * Don't check error from MakeDirectoryDefaultPerm; it could fail if someone
+ * else just did the same thing. If it doesn't work then we'll bomb out on
* the second create attempt, instead.
*/
- mkdir(tempdirpath, S_IRWXU);
+ MakeDirectoryDefaultPerm(tempdirpath);
file = PathNameOpenFile(tempfilepath,
O_RDWR | O_CREAT | O_TRUNC | PG_BINARY);
@@ -3545,3 +3545,16 @@ fsync_parent_path(const char *fname, int elevel)
return 0;
}
+
+/*
+ * Create a directory using PG_DIR_MODE_DEFAULT for permissions
+ *
+ * Directories in PGDATA should normally have specific permissions -- when
+ * using this function the caller does not need to know what they should be.
+ * For permissions other than the default use mkdir() directly.
+ */
+int
+MakeDirectoryDefaultPerm(const char *directoryName)
+{
+ return mkdir(directoryName, PG_DIR_MODE_DEFAULT);
+}
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index 2de35efbd4..687df55444 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -132,6 +132,10 @@ proc_exit(int code)
else
snprintf(gprofDirName, 32, "gprof/%d", (int) getpid());
+ /*
+ * Use mkdir() instead of MakeDirectoryDefaultPerm() here because non-default
+ * permissions are required.
+ */
mkdir("gprof", S_IRWXU | S_IRWXG | S_IRWXO);
mkdir(gprofDirName, S_IRWXU | S_IRWXG | S_IRWXO);
chdir(gprofDirName);
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index db5ca16679..909d7202e9 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -57,6 +57,10 @@ extern PGDLLIMPORT int max_files_per_process;
*/
extern int max_safe_fds;
+/*
+ * Default mode for directories created with MakeDirectoryDefaultPerm().
+ */
+#define PG_DIR_MODE_DEFAULT (S_IRWXU)
/*
* prototypes for functions in fd.c
@@ -111,6 +115,9 @@ extern int CloseTransientFile(int fd);
extern int BasicOpenFile(const char *fileName, int fileFlags);
extern int BasicOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode);
+ /* Make a directory with default permissions */
+extern int MakeDirectoryDefaultPerm(const char *directoryName);
+
/* Miscellaneous support routines */
extern void InitFileAccess(void);
extern void set_max_safe_fds(void);
group-access-v6-03-group.patchtext/plain; charset=UTF-8; name=group-access-v6-03-group.patch; x-mac-creator=0; x-mac-type=0Download
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index 9d8e69056f..a9912eb418 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -1095,7 +1095,11 @@ SELECT pg_stop_backup();
and <filename>postmaster.opts</filename>, which record information
about the running <application>postmaster</application>, not about the
<application>postmaster</application> which will eventually use this backup.
- (These files can confuse <application>pg_ctl</application>.)
+ (These files can confuse <application>pg_ctl</application>.) If group read
+ access is enabled on the data directory and an unprivileged user in the
+ <productname>PostgreSQL</productname> group is performing the backup, then
+ <filename>postmaster.pid</filename> will not be readable and must be
+ excluded.
</para>
<para>
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 585665f161..5f57b933b9 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -76,6 +76,14 @@ PostgreSQL documentation
to do so.)
</para>
+ <para>
+ For security reasons the new cluster created by <command>initdb</command>
+ will only be accessible by the cluster owner by default. The
+ <option>--allow-group-access</option> option allows any user in the same
+ group as the cluster owner to read files in the cluster. This is useful
+ for performing backups as a non-privileged user.
+ </para>
+
<para>
<command>initdb</command> initializes the database cluster's default
locale and character set encoding. The character set encoding,
@@ -301,6 +309,17 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-g</option></term>
+ <term><option>--allow-group-access</option></term>
+ <listitem>
+ <para>
+ Allows users in the same group as the cluster owner to read all cluster
+ files created by <command>initdb</command>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-W</option></term>
<term><option>--pwprompt</option></term>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index d162acb2e8..1c6fbf2920 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -137,7 +137,10 @@ postgres$ <userinput>initdb -D /usr/local/pgsql/data</userinput>
database, it is essential that it be secured from unauthorized
access. <command>initdb</command> therefore revokes access
permissions from everyone but the
- <productname>PostgreSQL</productname> user.
+ <productname>PostgreSQL</productname> user, and optionally, group.
+ Group access, when enabled, is read-only. This allows an unprivileged
+ user in the <productname>PostgreSQL</productname> group to take a backup of
+ the cluster data or perform other operations that only require read access.
</para>
<para>
@@ -2236,6 +2239,15 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
member of the group that has access to those certificate and key files.
</para>
+ <para>
+ If the data directory allows group read access then certificate files may
+ need to be located outside of the data directory in order to conform to the
+ security requirements outlined above. Generally, group access is enabled
+ to allow an unprivileged user to backup the database, and in that case the
+ backup software will not be able to read the certificate files and will
+ likely error.
+ </para>
+
<para>
If the private key is protected with a passphrase, the
server will prompt for the passphrase and will not start until it has
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 746b8c121b..c4ee987446 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -68,6 +68,7 @@
#include "commands/seclabel.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
+#include "common/file_perm.h"
#include "miscadmin.h"
#include "postmaster/bgwriter.h"
#include "storage/fd.h"
@@ -566,6 +567,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
char *linkloc;
char *location_with_version_dir;
struct stat st;
+ mode_t current_umask; /* Current umask value */
linkloc = psprintf("pg_tblspc/%u", tablespaceoid);
location_with_version_dir = psprintf("%s/%s", location,
@@ -575,7 +577,10 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* Attempt to coerce target directory to safe permissions. If this fails,
* it doesn't exist or has the wrong owner.
*/
- if (chmod(location, PG_DIR_MODE_DEFAULT) != 0)
+ current_umask = umask(0);
+ umask(current_umask);
+
+ if (chmod(location, PG_DIR_MODE_DEFAULT & ~current_umask) != 0)
{
if (errno == ENOENT)
ereport(ERROR,
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index e13a2ae8c4..65f528147a 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -97,6 +97,7 @@
#include "access/xlog.h"
#include "bootstrap/bootstrap.h"
#include "catalog/pg_control.h"
+#include "common/file_perm.h"
#include "common/ip.h"
#include "lib/ilist.h"
#include "libpq/auth.h"
@@ -587,7 +588,8 @@ PostmasterMain(int argc, char *argv[])
IsPostmasterEnvironment = true;
/*
- * for security, no dir or file created can be group or other accessible
+ * By default, no dir or file created can be group or other accessible. This
+ * may be modified later depending in the permissions of the data directory.
*/
umask(S_IRWXG | S_IRWXO);
@@ -1524,25 +1526,30 @@ checkDataDir(void)
#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.
+ * Check if the directory has correct permissions. If not, reject.
*
* 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))
+ if (stat_buf.st_mode & PG_MODE_MASK_ALLOW_GROUP)
ereport(FATAL,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("data directory \"%s\" has group or world access",
+ errmsg("data directory \"%s\" has invalid permissions",
DataDir),
- errdetail("Permissions should be u=rwx (0700).")));
+ errdetail("Permissions should be u=rwx (0700) or u=rwx,g=rx (0750).")));
+#endif
+
+ /*
+ * Reset the file mode creation mask based on the mode of the data
+ * directory.
+ *
+ * Suppress when on Windows, because there may not be proper support
+ * for Unix-y file permissions.
+ */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+ umask(PG_MODE_MASK_DEFAULT & ~stat_buf.st_mode);
#endif
/* Look for PG_VERSION before looking for pg_control */
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index 9aeca8a04d..21c2456e63 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -84,6 +84,7 @@
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "catalog/pg_tablespace.h"
+#include "common/file_perm.h"
#include "pgstat.h"
#include "portability/mem.h"
#include "storage/fd.h"
@@ -124,12 +125,6 @@
*/
#define FD_MINFREE 10
-/*
- * Default mode for created files, unless something else is specified using
- * the *Perm() function variants.
- */
-#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR)
-
/*
* A number of platforms allow individual processes to open many more files
* than they can really support when *many* processes do the same thing.
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 2efd3b75b1..56356d75c5 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -64,6 +64,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_class.h"
#include "catalog/pg_collation.h"
+#include "common/file_perm.h"
#include "common/file_utils.h"
#include "common/restricted_token.h"
#include "common/username.h"
@@ -144,6 +145,7 @@ static bool data_checksums = false;
static char *xlog_dir = NULL;
static char *str_wal_segment_size_mb = NULL;
static int wal_segment_size_mb;
+static mode_t file_mode_mask = PG_MODE_MASK_DEFAULT;
/* internal vars */
@@ -1170,12 +1172,6 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
- {
- fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
- progname, path, strerror(errno));
- exit_nicely();
- }
/*
* create the automatic configuration file to store the configuration
@@ -1190,12 +1186,6 @@ setup_config(void)
sprintf(path, "%s/postgresql.auto.conf", pg_data);
writefile(path, autoconflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
- {
- fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
- progname, path, strerror(errno));
- exit_nicely();
- }
free(conflines);
@@ -1277,12 +1267,6 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_hba.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
- {
- fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
- progname, path, strerror(errno));
- exit_nicely();
- }
free(conflines);
@@ -1293,12 +1277,6 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_ident.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
- {
- fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
- progname, path, strerror(errno));
- exit_nicely();
- }
free(conflines);
@@ -2311,6 +2289,7 @@ usage(const char *progname)
printf(_(" --auth-local=METHOD default authentication method for local-socket connections\n"));
printf(_(" [-D, --pgdata=]DATADIR location for this database cluster\n"));
printf(_(" -E, --encoding=ENCODING set default encoding for new databases\n"));
+ printf(_(" -g, --allow-group-access allow group read/execute on data directory\n"));
printf(_(" --locale=LOCALE set default locale for new databases\n"));
printf(_(" --lc-collate=, --lc-ctype=, --lc-messages=LOCALE\n"
" --lc-monetary=, --lc-numeric=, --lc-time=LOCALE\n"
@@ -2692,7 +2671,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (pg_mkdir_p(pg_data, S_IRWXU) != 0)
+ if (pg_mkdir_p(pg_data, PG_DIR_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2710,7 +2689,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (chmod(pg_data, S_IRWXU) != 0)
+ if (chmod(pg_data, PG_DIR_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2778,7 +2757,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (pg_mkdir_p(xlog_dir, S_IRWXU) != 0)
+ if (pg_mkdir_p(xlog_dir, PG_DIR_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2796,7 +2775,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (chmod(xlog_dir, S_IRWXU) != 0)
+ if (chmod(xlog_dir, PG_DIR_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2846,7 +2825,7 @@ create_xlog_or_symlink(void)
else
{
/* Without -X option, just make the subdirectory normally */
- if (mkdir(subdirloc, S_IRWXU) < 0)
+ if (mkdir(subdirloc, PG_DIR_MODE_DEFAULT) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, subdirloc, strerror(errno));
@@ -2882,8 +2861,6 @@ initialize_data_directory(void)
setup_signals();
- umask(S_IRWXG | S_IRWXO);
-
create_data_directory();
create_xlog_or_symlink();
@@ -2902,7 +2879,7 @@ initialize_data_directory(void)
* The parent directory already exists, so we only need mkdir() not
* pg_mkdir_p() here, which avoids some failure modes; cf bug #13853.
*/
- if (mkdir(path, S_IRWXU) < 0)
+ if (mkdir(path, PG_DIR_MODE_DEFAULT) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -3016,6 +2993,7 @@ main(int argc, char *argv[])
{"waldir", required_argument, NULL, 'X'},
{"wal-segsize", required_argument, NULL, 12},
{"data-checksums", no_argument, NULL, 'k'},
+ {"allow-group-access", no_argument, NULL, 'g'},
{NULL, 0, NULL, 0}
};
@@ -3057,7 +3035,7 @@ main(int argc, char *argv[])
/* process command-line options */
- while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:", long_options, &option_index)) != -1)
+ while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:g", long_options, &option_index)) != -1)
{
switch (c)
{
@@ -3151,6 +3129,9 @@ main(int argc, char *argv[])
case 12:
str_wal_segment_size_mb = pg_strdup(optarg);
break;
+ case 'g':
+ file_mode_mask = PG_MODE_MASK_ALLOW_GROUP;
+ break;
default:
/* getopt_long already emitted a complaint */
fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
@@ -3159,6 +3140,8 @@ main(int argc, char *argv[])
}
}
+ /* Set the file mode creation mask */
+ umask(file_mode_mask);
/*
* Non-option argument specifies data directory as long as it wasn't
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index c0cfa6e92c..f767f07ed5 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -4,9 +4,11 @@
use strict;
use warnings;
+use Fcntl ':mode';
+use File::stat qw{lstat};
use PostgresNode;
use TestLib;
-use Test::More tests => 15;
+use Test::More tests => 18;
my $tempdir = TestLib::tempdir;
my $xlogdir = "$tempdir/pgxlog";
@@ -45,6 +47,17 @@ mkdir $datadir;
command_ok([ 'initdb', '-N', '-T', 'german', '-X', $xlogdir, $datadir ],
'successful creation');
+
+ ok(check_pg_data_perm($datadir, 0));
}
command_ok([ 'initdb', '-S', $datadir ], 'sync only');
command_fails([ 'initdb', $datadir ], 'existing data directory');
+
+# Init a new db with group access
+my $datadir_group = "$tempdir/data_group";
+
+command_ok(
+ [ 'initdb', '-g', $datadir_group ],
+ 'successful creation with group access');
+
+ok(check_pg_data_perm($datadir_group, 1));
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index 9bc830b085..bb70e625fd 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -25,6 +25,7 @@
#include "catalog/pg_control.h"
#include "common/controldata_utils.h"
+#include "common/file_perm.h"
#include "getopt_long.h"
#include "utils/pidfile.h"
@@ -2170,7 +2171,8 @@ main(int argc, char **argv)
*/
argv0 = argv[0];
- umask(S_IRWXG | S_IRWXO);
+ /* Set restrictive mode mask until PGDATA permissions are checked */
+ umask(PG_MODE_MASK_DEFAULT);
/* support --help and --version even if invoked as root */
if (argc > 1)
@@ -2405,6 +2407,9 @@ main(int argc, char **argv)
snprintf(version_file, MAXPGPATH, "%s/PG_VERSION", pg_data);
snprintf(pid_file, MAXPGPATH, "%s/postmaster.pid", pg_data);
snprintf(backup_file, MAXPGPATH, "%s/backup_label", pg_data);
+
+ /* Set mask based on PGDATA permissions */
+ umask(DataDirectoryMask(pg_data));
}
switch (ctl_command)
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl
index 5da4746cb4..b04f54e734 100644
--- a/src/bin/pg_ctl/t/001_start_stop.pl
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -2,9 +2,11 @@ use strict;
use warnings;
use Config;
+use Fcntl ':mode';
+use File::stat qw{lstat};
use PostgresNode;
use TestLib;
-use Test::More tests => 19;
+use Test::More tests => 25;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -57,10 +59,37 @@ command_ok([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ], 'pg_ctl stop');
command_fails([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ],
'second pg_ctl stop fails');
+# Log file for first perm test
+my $logFileName = "$tempdir/data/perm-test-600.log";
+
command_ok(
- [ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
+ [ 'pg_ctl', 'restart', '-D', "$tempdir/data", '-l', $logFileName ],
'pg_ctl restart with server not running');
-command_ok([ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
+
+# Log file should exist and have no group permissions
+ok(-f $logFileName);
+ok(check_pg_data_perm("$tempdir/data", 0));
+
+# Log file for second perm test
+$logFileName = "$tempdir/data/perm-test-640.log";
+
+# Change the data dir mode so log file will be created with group read
+# privileges on the next start
+system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
+
+command_ok(
+ [ 'chmod', "-R", 'g+rX', "$tempdir/data" ],
+ 'add group perms to PGDATA');
+
+command_ok(
+ [ 'pg_ctl', 'start', '-D', "$tempdir/data", '-l', $logFileName ],
+ 'start server to check group permissions');
+
+ok(-f $logFileName);
+ok(check_pg_data_perm("$tempdir/data", 1));
+
+command_ok(
+ [ 'pg_ctl', 'restart', '-D', "$tempdir/data", '-l', $logFileName ],
'pg_ctl restart with server running');
system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index a132cf2e9f..6a4dc502c4 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -52,6 +52,7 @@
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
#include "common/fe_memutils.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "storage/large_object.h"
#include "pg_getopt.h"
@@ -327,6 +328,9 @@ main(int argc, char *argv[])
exit(1);
}
+ /* Set mask based on PGDATA permissions */
+ umask(DataDirectoryMask(DataDir));
+
/* Check that data directory matches our server version */
CheckDataVersion();
@@ -919,7 +923,7 @@ RewriteControlFile(void)
fd = open(XLOG_CONTROL_FILE,
O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ PG_FILE_MODE_DEFAULT);
if (fd < 0)
{
fprintf(stderr, _("%s: could not create pg_control file: %s\n"),
@@ -1201,7 +1205,7 @@ WriteEmptyXLOG(void)
unlink(path);
fd = open(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ PG_FILE_MODE_DEFAULT);
if (fd < 0)
{
fprintf(stderr, _("%s: could not open file \"%s\": %s\n"),
diff --git a/src/bin/pg_resetwal/t/001_basic.pl b/src/bin/pg_resetwal/t/001_basic.pl
index 234bd70303..184e2ea3a8 100644
--- a/src/bin/pg_resetwal/t/001_basic.pl
+++ b/src/bin/pg_resetwal/t/001_basic.pl
@@ -4,7 +4,7 @@ use warnings;
use Config;
use PostgresNode;
use TestLib;
-use Test::More tests => 13;
+use Test::More tests => 16;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -36,11 +36,18 @@ is_deeply(
[sort(qw(. .. archive_status 000000010000000000000002))],
'WAL recreated');
+ok(check_pg_data_perm($pgdata, 0), 'check PGDATA permissions');
+
$node->start;
-# Reset to specific WAL segment
+# Reset to specific WAL segment. Also enable group access to make sure files
+# and directories are created with group permissions.
$node->stop;
+command_ok(
+ ['chmod', "-R", 'g+rX', "$pgdata"],
+ 'add group perms to PGDATA');
+
$node->command_ok(
['pg_resetwal', '-l', '000000070000000700000007', '-D', $pgdata],
'set to specific WAL');
@@ -50,4 +57,6 @@ is_deeply(
[sort(qw(. .. archive_status 000000070000000700000007))],
'WAL recreated');
+ok(check_pg_data_perm($pgdata, 1), 'check PGDATA permissions');
+
$node->start;
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index 42fd577f21..5b310f208b 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -115,10 +115,12 @@ sub check_query
sub setup_cluster
{
my $extra_name = shift;
+ my $group_access = shift;
# Initialize master, data checksums are mandatory
$node_master = get_new_node('master' . ($extra_name ? "_${extra_name}" : ''));
- $node_master->init(allows_streaming => 1);
+ $node_master->init(allows_streaming => 1,
+ has_group_access => defined($group_access) ? $group_access : 0);
# Set wal_keep_segments to prevent WAL segment recycling after enforced
# checkpoints in the tests.
$node_master->append_conf('postgresql.conf', qq(
@@ -236,6 +238,9 @@ sub run_pg_rewind
"$tmp_folder/master-postgresql.conf.tmp",
"$master_pgdata/postgresql.conf");
+ chmod($node_master->group_access() ? 0640 : 0600, "$master_pgdata/postgresql.conf")
+ or die("unable to set permissions for $master_pgdata/postgresql.conf");
+
# Plug-in rewound node to the now-promoted standby node
my $port_standby = $node_standby->port;
$node_master->append_conf(
@@ -254,6 +259,9 @@ recovery_target_timeline='latest'
# Clean up after the test. Stop both servers, if they're still running.
sub clean_rewind_test
{
+ ok (check_pg_data_perm(
+ $node_master->data_dir(), $node_master->group_access()));
+
$node_master->teardown_node if defined $node_master;
$node_standby->teardown_node if defined $node_standby;
}
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 705383d184..d51914e901 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -18,6 +18,7 @@
#include <fcntl.h>
#include <unistd.h>
+#include "common/file_perm.h"
#include "file_ops.h"
#include "filemap.h"
#include "logging.h"
@@ -58,7 +59,7 @@ open_target_file(const char *path, bool trunc)
mode = O_WRONLY | O_CREAT | PG_BINARY;
if (trunc)
mode |= O_TRUNC;
- dstfd = open(dstpath, mode, 0600);
+ dstfd = open(dstpath, mode, PG_FILE_MODE_DEFAULT);
if (dstfd < 0)
pg_fatal("could not open target file \"%s\": %s\n",
dstpath, strerror(errno));
@@ -190,7 +191,7 @@ truncate_target_file(const char *path, off_t newsize)
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
- fd = open(dstpath, O_WRONLY, 0);
+ fd = open(dstpath, O_WRONLY, PG_FILE_MODE_DEFAULT);
if (fd < 0)
pg_fatal("could not open file \"%s\" for truncation: %s\n",
dstpath, strerror(errno));
@@ -211,7 +212,7 @@ create_target_dir(const char *path)
return;
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
- if (mkdir(dstpath, S_IRWXU) != 0)
+ if (mkdir(dstpath, PG_DIR_MODE_DEFAULT) != 0)
pg_fatal("could not create directory \"%s\": %s\n",
dstpath, strerror(errno));
}
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 72ab2f8d5e..11153e5f2f 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -24,6 +24,7 @@
#include "access/xlog_internal.h"
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "getopt_long.h"
#include "storage/bufpage.h"
@@ -185,6 +186,9 @@ main(int argc, char **argv)
exit(1);
}
+ /* Set mask based on PGDATA permissions */
+ umask(DataDirectoryMask(datadir_target));
+
/*
* Don't allow pg_rewind to be run as root, to avoid overwriting the
* ownership of files in the data directory. We need only check for root
diff --git a/src/bin/pg_rewind/t/001_basic.pl b/src/bin/pg_rewind/t/001_basic.pl
index 736f34eae3..205bdd77ef 100644
--- a/src/bin/pg_rewind/t/001_basic.pl
+++ b/src/bin/pg_rewind/t/001_basic.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 8;
+use Test::More tests => 10;
use RewindTest;
@@ -9,7 +9,7 @@ sub run_test
{
my $test_mode = shift;
- RewindTest::setup_cluster($test_mode);
+ RewindTest::setup_cluster($test_mode, 1);
RewindTest::start_master();
# Create a test table and insert a row in master.
diff --git a/src/bin/pg_rewind/t/002_databases.pl b/src/bin/pg_rewind/t/002_databases.pl
index 37cdd712f3..0e0140b96f 100644
--- a/src/bin/pg_rewind/t/002_databases.pl
+++ b/src/bin/pg_rewind/t/002_databases.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 6;
use RewindTest;
diff --git a/src/bin/pg_rewind/t/003_extrafiles.pl b/src/bin/pg_rewind/t/003_extrafiles.pl
index 2433a4aab6..28933da343 100644
--- a/src/bin/pg_rewind/t/003_extrafiles.pl
+++ b/src/bin/pg_rewind/t/003_extrafiles.pl
@@ -3,7 +3,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 6;
use File::Find;
@@ -14,6 +14,8 @@ sub run_test
{
my $test_mode = shift;
+ umask(0077);
+
RewindTest::setup_cluster($test_mode);
RewindTest::start_master();
diff --git a/src/bin/pg_rewind/t/004_pg_xlog_symlink.pl b/src/bin/pg_rewind/t/004_pg_xlog_symlink.pl
index feadaa6a0f..70dd061915 100644
--- a/src/bin/pg_rewind/t/004_pg_xlog_symlink.pl
+++ b/src/bin/pg_rewind/t/004_pg_xlog_symlink.pl
@@ -14,7 +14,7 @@ if ($windows_os)
}
else
{
- plan tests => 4;
+ plan tests => 6;
}
use RewindTest;
diff --git a/src/bin/pg_rewind/t/005_same_timeline.pl b/src/bin/pg_rewind/t/005_same_timeline.pl
index 0e334ee191..2fbca4ad7c 100644
--- a/src/bin/pg_rewind/t/005_same_timeline.pl
+++ b/src/bin/pg_rewind/t/005_same_timeline.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 1;
+use Test::More tests => 2;
use RewindTest;
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 8d4f254f9f..44d63f451f 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -442,7 +442,7 @@ create_script_for_cluster_analyze(char **analyze_script_file_name)
*analyze_script_file_name = psprintf("%sanalyze_new_cluster.%s",
SCRIPT_PREFIX, SCRIPT_EXT);
- if ((script = fopen_priv(*analyze_script_file_name, "w")) == NULL)
+ if ((script = fopen(*analyze_script_file_name, "w")) == NULL)
pg_fatal("could not open file \"%s\": %s\n",
*analyze_script_file_name, strerror(errno));
@@ -570,7 +570,7 @@ create_script_for_old_cluster_deletion(char **deletion_script_file_name)
prep_status("Creating script to delete old cluster");
- if ((script = fopen_priv(*deletion_script_file_name, "w")) == NULL)
+ if ((script = fopen(*deletion_script_file_name, "w")) == NULL)
pg_fatal("could not open file \"%s\": %s\n",
*deletion_script_file_name, strerror(errno));
@@ -834,7 +834,7 @@ check_for_isn_and_int8_passing_mismatch(ClusterInfo *cluster)
for (rowno = 0; rowno < ntups; rowno++)
{
found = true;
- if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL)
+ if (script == NULL && (script = fopen(output_path, "w")) == NULL)
pg_fatal("could not open file \"%s\": %s\n",
output_path, strerror(errno));
if (!db_used)
@@ -937,7 +937,7 @@ check_for_reg_data_type_usage(ClusterInfo *cluster)
for (rowno = 0; rowno < ntups; rowno++)
{
found = true;
- if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL)
+ if (script == NULL && (script = fopen(output_path, "w")) == NULL)
pg_fatal("could not open file \"%s\": %s\n",
output_path, strerror(errno));
if (!db_used)
@@ -1028,7 +1028,7 @@ check_for_jsonb_9_4_usage(ClusterInfo *cluster)
for (rowno = 0; rowno < ntups; rowno++)
{
found = true;
- if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL)
+ if (script == NULL && (script = fopen(output_path, "w")) == NULL)
pg_fatal("could not open file \"%s\": %s\n",
output_path, strerror(errno));
if (!db_used)
diff --git a/src/bin/pg_upgrade/dump.c b/src/bin/pg_upgrade/dump.c
index 8a662e9865..def22c6521 100644
--- a/src/bin/pg_upgrade/dump.c
+++ b/src/bin/pg_upgrade/dump.c
@@ -18,7 +18,6 @@ void
generate_old_dump(void)
{
int dbnum;
- mode_t old_umask;
prep_status("Creating dump of global objects");
@@ -33,13 +32,6 @@ generate_old_dump(void)
prep_status("Creating dump of database schemas\n");
- /*
- * Set umask for this function, all functions it calls, and all
- * subprocesses/threads it creates. We can't use fopen_priv() as Windows
- * uses threads and umask is process-global.
- */
- old_umask = umask(S_IRWXG | S_IRWXO);
-
/* create per-db dump files */
for (dbnum = 0; dbnum < old_cluster.dbarr.ndbs; dbnum++)
{
@@ -74,8 +66,6 @@ generate_old_dump(void)
while (reap_child(true) == true)
;
- umask(old_umask);
-
end_progress_output();
check_ok();
}
diff --git a/src/bin/pg_upgrade/file.c b/src/bin/pg_upgrade/file.c
index f88e3d558f..595d43ee31 100644
--- a/src/bin/pg_upgrade/file.c
+++ b/src/bin/pg_upgrade/file.c
@@ -10,6 +10,7 @@
#include "postgres_fe.h"
#include "access/visibilitymap.h"
+#include "common/file_perm.h"
#include "pg_upgrade.h"
#include "storage/bufpage.h"
#include "storage/checksum.h"
@@ -44,7 +45,7 @@ copyFile(const char *src, const char *dst,
schemaName, relName, src, strerror(errno));
if ((dest_fd = open(dst, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR)) < 0)
+ PG_FILE_MODE_DEFAULT)) < 0)
pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
schemaName, relName, dst, strerror(errno));
@@ -151,7 +152,7 @@ rewriteVisibilityMap(const char *fromfile, const char *tofile,
schemaName, relName, fromfile, strerror(errno));
if ((dst_fd = open(tofile, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR)) < 0)
+ PG_FILE_MODE_DEFAULT)) < 0)
pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
schemaName, relName, tofile, strerror(errno));
@@ -314,18 +315,3 @@ win32_pghardlink(const char *src, const char *dst)
return 0;
}
#endif
-
-
-/* fopen() file with no group/other permissions */
-FILE *
-fopen_priv(const char *path, const char *mode)
-{
- mode_t old_umask = umask(S_IRWXG | S_IRWXO);
- FILE *fp;
-
- fp = fopen(path, mode);
-
- umask(old_umask); /* we assume this can't change errno */
-
- return fp;
-}
diff --git a/src/bin/pg_upgrade/function.c b/src/bin/pg_upgrade/function.c
index d61fa38c92..70d8cf2902 100644
--- a/src/bin/pg_upgrade/function.c
+++ b/src/bin/pg_upgrade/function.c
@@ -249,7 +249,7 @@ check_loadable_libraries(void)
{
found = true;
- if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL)
+ if (script == NULL && (script = fopen(output_path, "w")) == NULL)
pg_fatal("could not open file \"%s\": %s\n",
output_path, strerror(errno));
fprintf(script, _("could not load library \"%s\": %s"),
diff --git a/src/bin/pg_upgrade/option.c b/src/bin/pg_upgrade/option.c
index 9dbc9225a6..ef84f44672 100644
--- a/src/bin/pg_upgrade/option.c
+++ b/src/bin/pg_upgrade/option.c
@@ -97,7 +97,7 @@ parseCommandLine(int argc, char *argv[])
if (os_user_effective_id == 0)
pg_fatal("%s: cannot be run as root\n", os_info.progname);
- if ((log_opts.internal = fopen_priv(INTERNAL_LOG_FILE, "a")) == NULL)
+ if ((log_opts.internal = fopen(INTERNAL_LOG_FILE, "a")) == NULL)
pg_fatal("could not write to log file \"%s\"\n", INTERNAL_LOG_FILE);
while ((option = getopt_long(argc, argv, "d:D:b:B:cj:ko:O:p:P:rU:v",
@@ -213,7 +213,7 @@ parseCommandLine(int argc, char *argv[])
/* label start of upgrade in logfiles */
for (filename = output_files; *filename != NULL; filename++)
{
- if ((fp = fopen_priv(*filename, "a")) == NULL)
+ if ((fp = fopen(*filename, "a")) == NULL)
pg_fatal("could not write to log file \"%s\"\n", *filename);
/* Start with newline because we might be appending to a file. */
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index a67e484a85..98e8977190 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -38,6 +38,7 @@
#include "pg_upgrade.h"
#include "catalog/pg_class.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "fe_utils/string_utils.h"
@@ -95,6 +96,9 @@ main(int argc, char **argv)
check_cluster_compatibility(live_check);
+ /* Set mask based on PGDATA permissions */
+ umask(DataDirectoryMask(new_cluster.pgdata));
+
check_and_dump_old_cluster(live_check);
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 67f874b4f1..b8a1a18e36 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -374,7 +374,6 @@ void linkFile(const char *src, const char *dst,
void rewriteVisibilityMap(const char *fromfile, const char *tofile,
const char *schemaName, const char *relName);
void check_hard_link(void);
-FILE *fopen_priv(const char *path, const char *mode);
/* function.c */
diff --git a/src/bin/pg_upgrade/test.sh b/src/bin/pg_upgrade/test.sh
index 39983abea1..24631a91c6 100644
--- a/src/bin/pg_upgrade/test.sh
+++ b/src/bin/pg_upgrade/test.sh
@@ -20,9 +20,9 @@ unset MAKELEVEL
# Run a given "initdb" binary and overlay the regression testing
# authentication configuration.
standard_initdb() {
- # To increase coverage of non-standard segment size without
- # increase test runtime, run these tests with a lower setting.
- "$1" -N --wal-segsize 1
+ # To increase coverage of non-standard segment size and group access
+ # without increasing test runtime, run these tests with a custom setting.
+ "$1" -N --wal-segsize 1 -g
if [ -n "$TEMP_CONFIG" -a -r "$TEMP_CONFIG" ]
then
cat "$TEMP_CONFIG" >> "$PGDATA/postgresql.conf"
@@ -230,6 +230,17 @@ standard_initdb 'initdb'
pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "${PGDATA}" -b "$oldbindir" -B "$bindir" -p "$PGPORT" -P "$PGPORT"
+# make sure all directories and files have group permissions
+if [ $(find ${PGDATA} -type f ! -perm 640 | wc -l) -ne 0 ]; then
+ echo "files in PGDATA with permission != 640";
+ exit 1;
+fi
+
+if [ $(find ${PGDATA} -type d ! -perm 750 | wc -l) -ne 0 ]; then
+ echo "directories in PGDATA with permission != 750";
+ exit 1;
+fi
+
pg_ctl start -l "$logdir/postmaster2.log" -o "$POSTMASTER_OPTS" -w
case $testhost in
diff --git a/src/bin/pg_upgrade/version.c b/src/bin/pg_upgrade/version.c
index 76e9d65537..205f772fc5 100644
--- a/src/bin/pg_upgrade/version.c
+++ b/src/bin/pg_upgrade/version.c
@@ -53,7 +53,7 @@ new_9_0_populate_pg_largeobject_metadata(ClusterInfo *cluster, bool check_mode)
{
PQExpBufferData connectbuf;
- if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL)
+ if (script == NULL && (script = fopen(output_path, "w")) == NULL)
pg_fatal("could not open file \"%s\": %s\n", output_path,
strerror(errno));
@@ -152,7 +152,7 @@ old_9_3_check_for_line_data_type_usage(ClusterInfo *cluster)
for (rowno = 0; rowno < ntups; rowno++)
{
found = true;
- if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL)
+ if (script == NULL && (script = fopen(output_path, "w")) == NULL)
pg_fatal("could not open file \"%s\": %s\n", output_path,
strerror(errno));
if (!db_used)
@@ -253,7 +253,7 @@ old_9_6_check_for_unknown_data_type_usage(ClusterInfo *cluster)
for (rowno = 0; rowno < ntups; rowno++)
{
found = true;
- if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL)
+ if (script == NULL && (script = fopen(output_path, "w")) == NULL)
pg_fatal("could not open file \"%s\": %s\n", output_path,
strerror(errno));
if (!db_used)
@@ -335,7 +335,7 @@ old_9_6_invalidate_hash_indexes(ClusterInfo *cluster, bool check_mode)
found = true;
if (!check_mode)
{
- if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL)
+ if (script == NULL && (script = fopen(output_path, "w")) == NULL)
pg_fatal("could not open file \"%s\": %s\n", output_path,
strerror(errno));
if (!db_used)
diff --git a/src/common/Makefile b/src/common/Makefile
index 80e78d72fe..504375bef4 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -51,7 +51,7 @@ else
OBJS_COMMON += sha2.o
endif
-OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o file_utils.o restricted_token.o
+OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o file_perm.o file_utils.o restricted_token.o
OBJS_SRV = $(OBJS_COMMON:%.o=%_srv.o)
diff --git a/src/common/file_perm.c b/src/common/file_perm.c
new file mode 100644
index 0000000000..2be76913cd
--- /dev/null
+++ b/src/common/file_perm.c
@@ -0,0 +1,50 @@
+/*-------------------------------------------------------------------------
+ *
+ * File and directory permission routines
+ *
+ * Group read/execute may optional be enabled on PGDATA so any frontend tools
+ * That write into PGDATA must know what mask to set and the permissions to
+ * use for creating files and directories.
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/file_perm.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres_fe.h"
+
+#include <sys/stat.h>
+
+#include "common/file_perm.h"
+
+
+/*
+ * Determine the mask to use when writing to PGDATA.
+ *
+ * Errors are not handled here and should be checked by the frontend
+ * application.
+ */
+#ifdef FRONTEND
+
+mode_t
+DataDirectoryMask(const char *dataDir)
+{
+ struct stat statBuf;
+
+ /*
+ * If an error occurs getting the mode then return the more restrictive mask.
+ * It is the reponsibility of the frontend application to generate an error.
+ */
+ if (stat(dataDir, &statBuf) != 0)
+ return PG_MODE_MASK_DEFAULT;
+
+ /*
+ * Construct the mask that the caller should pass to umask().
+ */
+ return PG_MODE_MASK_DEFAULT & ~statBuf.st_mode;
+}
+
+#endif /* FRONTEND */
diff --git a/src/include/common/file_perm.h b/src/include/common/file_perm.h
new file mode 100644
index 0000000000..b94fdaf08a
--- /dev/null
+++ b/src/include/common/file_perm.h
@@ -0,0 +1,55 @@
+/*-------------------------------------------------------------------------
+ *
+ * File and directory permission constants and routines
+ *
+ * Group read/execute may optional be enabled on PGDATA so any frontend tools
+ * That write into PGDATA must know what mask to set and the permissions to
+ * use for creating files and directories.
+ *
+ * This module is located in common so the backend can use the constants.
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/file_perm.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FILE_PERM_H
+#define FILE_PERM_H
+
+/*
+ * Default mode mask for data directory permissions that does not allow
+ * group execute/read.
+ */
+#define PG_MODE_MASK_DEFAULT (S_IRWXG | S_IRWXO)
+
+/*
+ * Optional mode mask for data directory permissions that allows group
+ * read/execute.
+ */
+#define PG_MODE_MASK_ALLOW_GROUP (S_IWGRP | S_IRWXO)
+
+/*
+ * Default mode for created files, unless something else is specified using
+ * the *Perm() function variants.
+ */
+#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR | S_IRGRP)
+
+/*
+ * Default mode for directories created with MakeDirectoryDefaultPerm().
+ */
+#define PG_DIR_MODE_DEFAULT (S_IRWXU | S_IRGRP | S_IXGRP)
+
+#ifdef FRONTEND
+
+/*
+ * Determine the mask to use when writing to PGDATA
+ */
+mode_t DataDirectoryMask(const char *dataDir);
+
+#endif /* FRONTEND */
+
+
+#endif /* FILE_PERM_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 909d7202e9..09c3533251 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -57,10 +57,6 @@ extern PGDLLIMPORT int max_files_per_process;
*/
extern int max_safe_fds;
-/*
- * Default mode for directories created with MakeDirectoryDefaultPerm().
- */
-#define PG_DIR_MODE_DEFAULT (S_IRWXU)
/*
* prototypes for functions in fd.c
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 1d5ac4ee35..6d02377294 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -268,6 +268,20 @@ sub connstr
=pod
+=item $node->group_access()
+
+Does the data dir allow group access?
+
+=cut
+
+sub group_access
+{
+ my ($self) = @_;
+ return $self->{_group_access};
+}
+
+=pod
+
=item $node->data_dir()
Returns the path to the data directory. postgresql.conf and pg_hba.conf are
@@ -405,10 +419,16 @@ sub init
$params{allows_streaming} = 0 unless defined $params{allows_streaming};
$params{has_archiving} = 0 unless defined $params{has_archiving};
+ $params{has_group_access} = 0 unless defined $params{has_group_access};
mkdir $self->backup_dir;
mkdir $self->archive_dir;
+ if ($params{has_group_access})
+ {
+ push(@{$params{extra}}, '-g');
+ }
+
TestLib::system_or_bail('initdb', '-D', $pgdata, '-A', 'trust', '-N',
@{ $params{extra} });
TestLib::system_or_bail($ENV{PG_REGRESS}, '--config-auth', $pgdata);
@@ -459,8 +479,12 @@ sub init
}
close $conf;
+ chmod($params{has_group_access} ? 0640 : 0600, "$pgdata/postgresql.conf")
+ or die("unable to set permissions for $pgdata/postgresql.conf");
+
$self->set_replication_conf if $params{allows_streaming};
$self->enable_archiving if $params{has_archiving};
+ $self->{_group_access} = 1 if $params{has_group_access};
}
=pod
@@ -483,6 +507,9 @@ sub append_conf
my $conffile = $self->data_dir . '/' . $filename;
TestLib::append_to_file($conffile, $str . "\n");
+
+ chmod($self->group_access() ? 0640 : 0600, $conffile)
+ or die("unable to set permissions for $conffile");
}
=pod
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index fdd427608b..c4da3140c1 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -12,8 +12,10 @@ use warnings;
use Config;
use Exporter 'import';
+use Fcntl qw(:mode);
use File::Basename;
use File::Spec;
+use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
use SimpleTee;
@@ -26,6 +28,7 @@ our @EXPORT = qw(
slurp_dir
slurp_file
append_to_file
+ check_pg_data_perm
check_pg_config
system_or_bail
system_log
@@ -222,6 +225,96 @@ sub append_to_file
close $fh;
}
+# Ensure all permissions in the pg_data directory are correct. When allow_group
+# is true then permissions should be dir = 0750, file = 0640. When allow_group
+# is false then permissions should be dir = 0700, file = 0600.
+sub check_pg_data_perm
+{
+ my ($dir, $allow_group, $level) = @_;
+
+ # Expected permission
+ my $expected_file_perm = $allow_group ? 0640 : 0600;
+ my $expected_dir_perm = $allow_group ? 0750 : 0700;
+
+ # First level is 0
+ $level = defined($level) ? $level : 0;
+
+ # Get all entries in the dir
+ opendir(my $dir_handle, $dir)
+ or die("unable to open $dir");
+
+ my @dir_entry = grep(!/^(\.\.)$/i, readdir($dir_handle));
+ close($dir_handle);
+
+ @dir_entry != 0
+ or die("unable to read $dir");
+
+ # Check each entry
+ foreach my $entry (@dir_entry)
+ {
+ my $entry_path = $entry eq '.' ? $dir : "$dir/$entry";
+ my $entry_stat = stat($entry_path);
+
+ defined($entry_stat)
+ or die("unable to stat $entry_path");
+
+ my $entry_mode = S_IMODE($entry_stat->mode);
+
+ # Is this a file?
+ if (S_ISREG($entry_stat->mode))
+ {
+ # postmaster.pid file must be 600
+ if ($level == 0 && $entry eq 'postmaster.pid')
+ {
+ if ($entry_mode != 0600)
+ {
+ print(*STDERR, "$entry_path mode must be 0600\n");
+ return 0;
+ }
+ }
+ else
+ {
+ if ($entry_mode != $expected_file_perm)
+ {
+ print(*STDERR,
+ sprintf("$entry_path mode must be %04o\n",
+ $expected_file_perm));
+ return 0;
+ }
+ }
+ }
+ # Else a directory?
+ elsif (S_ISDIR($entry_stat->mode))
+ {
+ # Only need to check the dir once
+ if ($entry eq '.')
+ {
+ if ($entry_mode != $expected_dir_perm)
+ {
+ print(*STDERR,
+ sprintf("$entry_path mode must be %04o\n",
+ $expected_dir_perm));
+ return 0;
+ }
+ }
+ else
+ {
+ if (!check_pg_data_perm($entry_path, $allow_group, $level + 1))
+ {
+ return 0;
+ }
+ }
+ }
+ # Else something we can't handle
+ else
+ {
+ die "unknown file type for $entry_path";
+ }
+ }
+
+ return 1;
+}
+
# Check presence of a given regexp within pg_config.h for the installation
# where tests are running, returning a match status result depending on
# that.
On Mon, Jan 29, 2018 at 04:29:08PM -0500, David Steele wrote:
OK, that being the case I have piggy-backed on the current pg_upgrade
tests in the same way that --wal-segsize did.There are now three patches:
1) 01-pgresetwal-test
Adds a *very* basic test suite for pg_resetwal. I was able to make this
utility core dump (floating point errors) pretty easily with empty or
malformed pg_control files so I focused on WAL reset functionality plus
the basic help/command tests that every utility has.
A little is better than nothing, so that's an improvement, thanks!
+# Remove WAL from pg_wal and make sure it gets rebuilt
+$node->stop;
+
+unlink("$pgwal/000000010000000000000001") == 1
+ or die("unable to remove 000000010000000000000001");
+
+is_deeply(
+ [sort(slurp_dir($pgwal))], [sort(qw(. .. archive_status))], 'no WAL');
This is_deeply test serves little purpose. The segment gets removed
directly so I would just remove it.
Another test that could be easily included is with -o, then create a
table and check for its OID value. Also for -x, then just use
txid_current to check the value consistency. For -c, with
track_commit_timestamp set to on, you can set a couple of values and
then check for pg_last_committed_xact() which would be NULL if you use
an XID older than the minimum set for example. All those are simple
enough so they could easily be added in the first version of this test
suite.
You need to update src/bin/pg_resetxlog/.gitignore to include
tmp_check/.
2) 02-mkdir
Converts mkdir() calls to MakeDirectoryDefaultPerm() if the original
call used default permissions.
So the only call not converted to that API is in ipc.c... OK, this one
is pretty simple so nothing really to say about it, the real meat comes
with patch 3. I would rather see with a good eye if this patch
introduces file_perm.h which includes both PG_FILE_MODE_DEFAULT and
PG_DIR_MODE_DEFAULT, and have the frontends use those values. This
would make the switch to 0003 a bit easier if you look at pg_resetwal's
diffs for example.
3) 03-group
Allow group access on PGDATA. This includes backend changes, utility
changes (initdb, pg_ctl, pg_upgrade, etc.) and tests for each utility.
+++ b/src/include/common/file_perm.h
+ *
+ * This module is located in common so the backend can use the constants.
+ *
This is the case of more or less all the content in src/common/, except
for the things which are frontend-only, so this comment could just be
removed.
+command_ok(
+ ['chmod', "-R", 'g+rX', "$pgdata"],
+ 'add group perms to PGDATA');
That would blow up on Windows. You should replace that by perl's chmod
and look at its return result for sanity checks, likely with ($cnt != 0).
And let's not use icacls please...
+ if ($params{has_group_access})
+ {
+ push(@{$params{extra}}, '-g');
+ }
No need to introduce a new parameter here, please use directly extra =>
[ '-g' ] in the routines calling PostgresNode::init.
The efforts put in the tests are good.
Patch 0003 needs a very careful lookup... We don't want to introduce any
kind of race conditions here. I am not familiar enough with the
proposed patch but the patch is giving me some chills.
You may want to run pgindent once on your patch set.
--
Michael
Hi Michael,
Thanks for having a look at the patches.
On 1/30/18 3:01 AM, Michael Paquier wrote:
On Mon, Jan 29, 2018 at 04:29:08PM -0500, David Steele wrote:
Adds a *very* basic test suite for pg_resetwal. I was able to make this
utility core dump (floating point errors) pretty easily with empty or
malformed pg_control files so I focused on WAL reset functionality plus
the basic help/command tests that every utility has.A little is better than nothing, so that's an improvement, thanks!
+# Remove WAL from pg_wal and make sure it gets rebuilt +$node->stop; + +unlink("$pgwal/000000010000000000000001") == 1 + or die("unable to remove 000000010000000000000001"); + +is_deeply( + [sort(slurp_dir($pgwal))], [sort(qw(. .. archive_status))], 'no WAL'); This is_deeply test serves little purpose. The segment gets removed directly so I would just remove it.
The purpose of this test to be ensure nothing else is in the directory.
As the tests get more complex I think keeping track of the state will be
important. In other words, this is really an assertion that the current
test state is correct, not that the prior command succeeded.
Another test that could be easily included is with -o, then create a
table and check for its OID value. Also for -x, then just use
txid_current to check the value consistency. For -c, with
track_commit_timestamp set to on, you can set a couple of values and
then check for pg_last_committed_xact() which would be NULL if you use
an XID older than the minimum set for example. All those are simple
enough so they could easily be added in the first version of this test
suite.
I would prefer to leave this for another patch.
You need to update src/bin/pg_resetxlog/.gitignore to include
tmp_check/.
Added.
2) 02-mkdir
Converts mkdir() calls to MakeDirectoryDefaultPerm() if the original
call used default permissions.So the only call not converted to that API is in ipc.c... OK, this one
is pretty simple so nothing really to say about it, the real meat comes
with patch 3. I would rather see with a good eye if this patch
introduces file_perm.h which includes both PG_FILE_MODE_DEFAULT and
PG_DIR_MODE_DEFAULT, and have the frontends use those values. This
would make the switch to 0003 a bit easier if you look at pg_resetwal's
diffs for example.
Agreed. I thought about this originally but could not come up with a
good split. I spent some time on it and came up with something that I
think works (and would make sense to commit separately).
3) 03-group
Allow group access on PGDATA. This includes backend changes, utility
changes (initdb, pg_ctl, pg_upgrade, etc.) and tests for each utility.+++ b/src/include/common/file_perm.h + * + * This module is located in common so the backend can use the constants. + * This is the case of more or less all the content in src/common/, except for the things which are frontend-only, so this comment could just be removed.
Removed.
+command_ok( + ['chmod', "-R", 'g+rX', "$pgdata"], + 'add group perms to PGDATA');That would blow up on Windows. You should replace that by perl's chmod
and look at its return result for sanity checks, likely with ($cnt != 0).
And let's not use icacls please...
Fixed with a new function, add_pg_data_group_perm().
+ if ($params{has_group_access}) + { + push(@{$params{extra}}, '-g'); + } No need to introduce a new parameter here, please use directly extra => [ '-g' ] in the routines calling PostgresNode::init.
Other code needs to know if group access is enabled on the node (see
RewindTest.pm) so it's not just a matter of passing the option.
The efforts put in the tests are good.
Thanks!
New patches are attached. The end result is almost identical to v6 but
code was moved from 03 to 02 as per Michael's suggestion.
1) 01-pgresetwal-test
Adds a very basic test suite for pg_resetwal.
2) 02-file-perm
Adds a new header (file_perm.h) and makes all front-end utilities use
the new constants for setting umask. Converts mkdir() calls in the
backend to MakeDirectoryDefaultPerm() if the original
call used default permissions. Adds tests to make sure permissions in
PGDATA are set correctly by the front-end tools.
3) 03-group
Allow group access on PGDATA. This includes backend changes, utility
changes (initdb, pg_ctl, pg_upgrade, etc.) and tests for each utility.
Thanks!
--
-David
david@pgmasters.net
Attachments:
group-access-v7-01-pgresetwal-test.patchtext/plain; charset=UTF-8; name=group-access-v7-01-pgresetwal-test.patch; x-mac-creator=0; x-mac-type=0Download
diff --git a/src/bin/pg_resetwal/.gitignore b/src/bin/pg_resetwal/.gitignore
index 236abb4323..a950255fd7 100644
--- a/src/bin/pg_resetwal/.gitignore
+++ b/src/bin/pg_resetwal/.gitignore
@@ -1 +1,2 @@
/pg_resetwal
+/tmp_check
diff --git a/src/bin/pg_resetwal/Makefile b/src/bin/pg_resetwal/Makefile
index 5ab7ad33e0..13c9ca6279 100644
--- a/src/bin/pg_resetwal/Makefile
+++ b/src/bin/pg_resetwal/Makefile
@@ -33,3 +33,9 @@ uninstall:
clean distclean maintainer-clean:
rm -f pg_resetwal$(X) $(OBJS)
+
+check:
+ $(prove_check)
+
+installcheck:
+ $(prove_installcheck)
diff --git a/src/bin/pg_resetwal/t/001_basic.pl b/src/bin/pg_resetwal/t/001_basic.pl
new file mode 100644
index 0000000000..234bd70303
--- /dev/null
+++ b/src/bin/pg_resetwal/t/001_basic.pl
@@ -0,0 +1,53 @@
+use strict;
+use warnings;
+
+use Config;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 13;
+
+my $tempdir = TestLib::tempdir;
+my $tempdir_short = TestLib::tempdir_short;
+
+program_help_ok('pg_resetwal');
+program_version_ok('pg_resetwal');
+program_options_handling_ok('pg_resetwal');
+
+# Initialize node without replication settings
+my $node = get_new_node('main');
+my $pgdata = $node->data_dir;
+my $pgwal = "$pgdata/pg_wal";
+$node->init;
+$node->start;
+
+# Remove WAL from pg_wal and make sure it gets rebuilt
+$node->stop;
+
+unlink("$pgwal/000000010000000000000001") == 1
+ or die("unable to remove 000000010000000000000001");
+
+is_deeply(
+ [sort(slurp_dir($pgwal))], [sort(qw(. .. archive_status))], 'no WAL');
+
+$node->command_ok(['pg_resetwal', '-D', $pgdata], 'recreate pg_wal');
+
+is_deeply(
+ [sort(slurp_dir($pgwal))],
+ [sort(qw(. .. archive_status 000000010000000000000002))],
+ 'WAL recreated');
+
+$node->start;
+
+# Reset to specific WAL segment
+$node->stop;
+
+$node->command_ok(
+ ['pg_resetwal', '-l', '000000070000000700000007', '-D', $pgdata],
+ 'set to specific WAL');
+
+is_deeply(
+ [sort(slurp_dir($pgwal))],
+ [sort(qw(. .. archive_status 000000070000000700000007))],
+ 'WAL recreated');
+
+$node->start;
group-access-v7-02-file-perm.patchtext/plain; charset=UTF-8; name=group-access-v7-02-file-perm.patch; x-mac-creator=0; x-mac-type=0Download
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 47a6c4d895..bf07bbd746 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4086,7 +4086,7 @@ ValidateXLOGDirectoryStructure(void)
{
ereport(LOG,
(errmsg("creating missing WAL directory \"%s\"", path)));
- if (mkdir(path, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(path) < 0)
ereport(FATAL,
(errmsg("could not create missing directory \"%s\": %m",
path)));
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 5c450caa4e..c4ee987446 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -68,6 +68,7 @@
#include "commands/seclabel.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
+#include "common/file_perm.h"
#include "miscadmin.h"
#include "postmaster/bgwriter.h"
#include "storage/fd.h"
@@ -151,7 +152,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
else
{
/* Directory creation failed? */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(dir) < 0)
{
char *parentdir;
@@ -173,7 +174,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
get_parent_directory(parentdir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -184,7 +185,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
parentdir = pstrdup(dir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -192,7 +193,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
pfree(parentdir);
/* Create database directory */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(dir) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -279,7 +280,8 @@ CreateTableSpace(CreateTableSpaceStmt *stmt)
/*
* Check that location isn't too long. Remember that we're going to append
* 'PG_XXX/<dboid>/<relid>_<fork>.<nnn>'. FYI, we never actually
- * reference the whole path here, but mkdir() uses the first two parts.
+ * reference the whole path here, but MakeDirectoryDefaultPerm() uses the first
+ * two parts.
*/
if (strlen(location) + 1 + strlen(TABLESPACE_VERSION_DIRECTORY) + 1 +
OIDCHARS + 1 + OIDCHARS + 1 + FORKNAMECHARS + 1 + OIDCHARS > MAXPGPATH)
@@ -565,6 +567,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
char *linkloc;
char *location_with_version_dir;
struct stat st;
+ mode_t current_umask; /* Current umask value */
linkloc = psprintf("pg_tblspc/%u", tablespaceoid);
location_with_version_dir = psprintf("%s/%s", location,
@@ -574,7 +577,10 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* Attempt to coerce target directory to safe permissions. If this fails,
* it doesn't exist or has the wrong owner.
*/
- if (chmod(location, S_IRWXU) != 0)
+ current_umask = umask(0);
+ umask(current_umask);
+
+ if (chmod(location, PG_DIR_MODE_DEFAULT & ~current_umask) != 0)
{
if (errno == ENOENT)
ereport(ERROR,
@@ -599,7 +605,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
if (stat(location_with_version_dir, &st) == 0 && S_ISDIR(st.st_mode))
{
if (!rmtree(location_with_version_dir, true))
- /* If this failed, mkdir() below is going to error. */
+ /* If this failed, MakeDirectoryDefaultPerm() below is going to error. */
ereport(WARNING,
(errmsg("some useless files may be left behind in old database directory \"%s\"",
location_with_version_dir)));
@@ -610,7 +616,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* The creation of the version directory prevents more than one tablespace
* in a single location.
*/
- if (mkdir(location_with_version_dir, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(location_with_version_dir) < 0)
{
if (errno == EEXIST)
ereport(ERROR,
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index f3ddf828bb..e13a2ae8c4 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -4495,7 +4495,7 @@ internal_forkexec(int argc, char *argv[], Port *port)
* As in OpenTemporaryFileInTablespace, try to make the temp-file
* directory
*/
- mkdir(PG_TEMP_FILES_DIR, S_IRWXU);
+ MakeDirectoryDefaultPerm(PG_TEMP_FILES_DIR);
fp = AllocateFile(tmpfilename, PG_BINARY_W);
if (!fp)
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index f70eea37df..ae23941f59 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -41,6 +41,7 @@
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
#include "storage/dsm.h"
+#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pg_shmem.h"
@@ -322,7 +323,7 @@ SysLoggerMain(int argc, char *argv[])
/*
* Also, create new directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ MakeDirectoryDefaultPerm(Log_directory);
}
if (strcmp(Log_filename, currentLogFilename) != 0)
{
@@ -564,7 +565,7 @@ SysLogger_Start(void)
/*
* Create log directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ MakeDirectoryDefaultPerm(Log_directory);
/*
* The initial logfile is created right in the postmaster, to verify that
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index fc9ef22b0b..3a98360b50 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -1166,13 +1166,14 @@ CreateSlotOnDisk(ReplicationSlot *slot)
* It's just barely possible that some previous effort to create or drop a
* slot with this name left a temp directory lying around. If that seems
* to be the case, try to remove it. If the rmtree() fails, we'll error
- * out at the mkdir() below, so we don't bother checking success.
+ * out at the MakeDirectoryDefaultPerm() below, so we don't bother checking
+ * success.
*/
if (stat(tmppath, &st) == 0 && S_ISDIR(st.st_mode))
rmtree(tmppath, true);
/* Create and fsync the temporary slot directory. */
- if (mkdir(tmppath, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(tmppath) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
diff --git a/src/backend/storage/file/copydir.c b/src/backend/storage/file/copydir.c
index ca6342db0d..ff980dc1f5 100644
--- a/src/backend/storage/file/copydir.c
+++ b/src/backend/storage/file/copydir.c
@@ -41,7 +41,7 @@ copydir(char *fromdir, char *todir, bool recurse)
char fromfile[MAXPGPATH * 2];
char tofile[MAXPGPATH * 2];
- if (mkdir(todir, S_IRWXU) != 0)
+ if (MakeDirectoryDefaultPerm(todir) != 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m", todir)));
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index 2a18e94ff4..1491665375 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -84,6 +84,7 @@
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "catalog/pg_tablespace.h"
+#include "common/file_perm.h"
#include "pgstat.h"
#include "portability/mem.h"
#include "storage/fd.h"
@@ -124,12 +125,6 @@
*/
#define FD_MINFREE 10
-/*
- * Default mode for created files, unless something else is specified using
- * the *Perm() function variants.
- */
-#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR)
-
/*
* A number of platforms allow individual processes to open many more files
* than they can really support when *many* processes do the same thing.
@@ -1435,7 +1430,7 @@ PathNameOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode)
void
PathNameCreateTemporaryDir(const char *basedir, const char *directory)
{
- if (mkdir(directory, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(directory) < 0)
{
if (errno == EEXIST)
return;
@@ -1445,14 +1440,14 @@ PathNameCreateTemporaryDir(const char *basedir, const char *directory)
* EEXIST to close a race against another process following the same
* algorithm.
*/
- if (mkdir(basedir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(basedir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary directory \"%s\": %m",
basedir)));
/* Try again. */
- if (mkdir(directory, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(directory) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary subdirectory \"%s\": %m",
@@ -1602,11 +1597,11 @@ OpenTemporaryFileInTablespace(Oid tblspcOid, bool rejectError)
* We might need to create the tablespace's tempfile directory, if no
* one has yet done so.
*
- * Don't check for error from mkdir; it could fail if someone else
- * just did the same thing. If it doesn't work then we'll bomb out on
+ * Don't check error from MakeDirectoryDefaultPerm; it could fail if someone
+ * else just did the same thing. If it doesn't work then we'll bomb out on
* the second create attempt, instead.
*/
- mkdir(tempdirpath, S_IRWXU);
+ MakeDirectoryDefaultPerm(tempdirpath);
file = PathNameOpenFile(tempfilepath,
O_RDWR | O_CREAT | O_TRUNC | PG_BINARY);
@@ -3555,3 +3550,16 @@ fsync_parent_path(const char *fname, int elevel)
return 0;
}
+
+/*
+ * Create a directory using PG_DIR_MODE_DEFAULT for permissions
+ *
+ * Directories in PGDATA should normally have specific permissions -- when
+ * using this function the caller does not need to know what they should be.
+ * For permissions other than the default use mkdir() directly.
+ */
+int
+MakeDirectoryDefaultPerm(const char *directoryName)
+{
+ return mkdir(directoryName, PG_DIR_MODE_DEFAULT);
+}
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index 726db7b7f1..466613e4af 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -137,6 +137,10 @@ proc_exit(int code)
else
snprintf(gprofDirName, 32, "gprof/%d", (int) getpid());
+ /*
+ * Use mkdir() instead of MakeDirectoryDefaultPerm() here because non-default
+ * permissions are required.
+ */
mkdir("gprof", S_IRWXU | S_IRWXG | S_IRWXO);
mkdir(gprofDirName, S_IRWXU | S_IRWXG | S_IRWXO);
chdir(gprofDirName);
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 2efd3b75b1..4ef06c2349 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -64,6 +64,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_class.h"
#include "catalog/pg_collation.h"
+#include "common/file_perm.h"
#include "common/file_utils.h"
#include "common/restricted_token.h"
#include "common/username.h"
@@ -144,6 +145,7 @@ static bool data_checksums = false;
static char *xlog_dir = NULL;
static char *str_wal_segment_size_mb = NULL;
static int wal_segment_size_mb;
+static mode_t file_mode_mask = PG_MODE_MASK_DEFAULT;
/* internal vars */
@@ -1170,12 +1172,6 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
- {
- fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
- progname, path, strerror(errno));
- exit_nicely();
- }
/*
* create the automatic configuration file to store the configuration
@@ -1190,12 +1186,6 @@ setup_config(void)
sprintf(path, "%s/postgresql.auto.conf", pg_data);
writefile(path, autoconflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
- {
- fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
- progname, path, strerror(errno));
- exit_nicely();
- }
free(conflines);
@@ -1277,12 +1267,6 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_hba.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
- {
- fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
- progname, path, strerror(errno));
- exit_nicely();
- }
free(conflines);
@@ -1293,12 +1277,6 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_ident.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
- {
- fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
- progname, path, strerror(errno));
- exit_nicely();
- }
free(conflines);
@@ -2692,7 +2670,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (pg_mkdir_p(pg_data, S_IRWXU) != 0)
+ if (pg_mkdir_p(pg_data, PG_DIR_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2710,7 +2688,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (chmod(pg_data, S_IRWXU) != 0)
+ if (chmod(pg_data, PG_DIR_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2778,7 +2756,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (pg_mkdir_p(xlog_dir, S_IRWXU) != 0)
+ if (pg_mkdir_p(xlog_dir, PG_DIR_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2796,7 +2774,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (chmod(xlog_dir, S_IRWXU) != 0)
+ if (chmod(xlog_dir, PG_DIR_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2846,7 +2824,7 @@ create_xlog_or_symlink(void)
else
{
/* Without -X option, just make the subdirectory normally */
- if (mkdir(subdirloc, S_IRWXU) < 0)
+ if (mkdir(subdirloc, PG_DIR_MODE_DEFAULT) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, subdirloc, strerror(errno));
@@ -2882,8 +2860,6 @@ initialize_data_directory(void)
setup_signals();
- umask(S_IRWXG | S_IRWXO);
-
create_data_directory();
create_xlog_or_symlink();
@@ -2902,7 +2878,7 @@ initialize_data_directory(void)
* The parent directory already exists, so we only need mkdir() not
* pg_mkdir_p() here, which avoids some failure modes; cf bug #13853.
*/
- if (mkdir(path, S_IRWXU) < 0)
+ if (mkdir(path, PG_DIR_MODE_DEFAULT) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -3159,6 +3135,8 @@ main(int argc, char *argv[])
}
}
+ /* Set the file mode creation mask */
+ umask(file_mode_mask);
/*
* Non-option argument specifies data directory as long as it wasn't
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index c0cfa6e92c..561608f1d8 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 15;
+use Test::More tests => 16;
my $tempdir = TestLib::tempdir;
my $xlogdir = "$tempdir/pgxlog";
@@ -45,6 +45,8 @@ mkdir $datadir;
command_ok([ 'initdb', '-N', '-T', 'german', '-X', $xlogdir, $datadir ],
'successful creation');
+
+ ok(check_pg_data_perm($datadir, 0));
}
command_ok([ 'initdb', '-S', $datadir ], 'sync only');
command_fails([ 'initdb', $datadir ], 'existing data directory');
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index 9bc830b085..1eef7179cc 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -25,6 +25,7 @@
#include "catalog/pg_control.h"
#include "common/controldata_utils.h"
+#include "common/file_perm.h"
#include "getopt_long.h"
#include "utils/pidfile.h"
@@ -2170,7 +2171,7 @@ main(int argc, char **argv)
*/
argv0 = argv[0];
- umask(S_IRWXG | S_IRWXO);
+ umask(PG_MODE_MASK_DEFAULT);
/* support --help and --version even if invoked as root */
if (argc > 1)
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl
index 5da4746cb4..909bf4d465 100644
--- a/src/bin/pg_ctl/t/001_start_stop.pl
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -4,7 +4,7 @@ use warnings;
use Config;
use PostgresNode;
use TestLib;
-use Test::More tests => 19;
+use Test::More tests => 20;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -57,10 +57,15 @@ command_ok([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ], 'pg_ctl stop');
command_fails([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ],
'second pg_ctl stop fails');
+# Log file for first perm test
+my $logFileName = "$tempdir/data/perm-test-600.log";
+
command_ok(
- [ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
+ [ 'pg_ctl', 'restart', '-D', "$tempdir/data", '-l', $logFileName ],
'pg_ctl restart with server not running');
-command_ok([ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
- 'pg_ctl restart with server running');
+
+# Log file should exist and have no group permissions
+ok(-f $logFileName);
+ok(check_pg_data_perm("$tempdir/data", 0));
system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index a132cf2e9f..47abd41fc3 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -52,6 +52,7 @@
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
#include "common/fe_memutils.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "storage/large_object.h"
#include "pg_getopt.h"
@@ -327,6 +328,9 @@ main(int argc, char *argv[])
exit(1);
}
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_DEFAULT);
+
/* Check that data directory matches our server version */
CheckDataVersion();
@@ -919,7 +923,7 @@ RewriteControlFile(void)
fd = open(XLOG_CONTROL_FILE,
O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ PG_FILE_MODE_DEFAULT);
if (fd < 0)
{
fprintf(stderr, _("%s: could not create pg_control file: %s\n"),
@@ -1201,7 +1205,7 @@ WriteEmptyXLOG(void)
unlink(path);
fd = open(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ PG_FILE_MODE_DEFAULT);
if (fd < 0)
{
fprintf(stderr, _("%s: could not open file \"%s\": %s\n"),
diff --git a/src/bin/pg_resetwal/t/001_basic.pl b/src/bin/pg_resetwal/t/001_basic.pl
index 234bd70303..db0c2a630a 100644
--- a/src/bin/pg_resetwal/t/001_basic.pl
+++ b/src/bin/pg_resetwal/t/001_basic.pl
@@ -4,7 +4,7 @@ use warnings;
use Config;
use PostgresNode;
use TestLib;
-use Test::More tests => 13;
+use Test::More tests => 14;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -36,6 +36,8 @@ is_deeply(
[sort(qw(. .. archive_status 000000010000000000000002))],
'WAL recreated');
+ok(check_pg_data_perm($pgdata, 0), 'check PGDATA permissions');
+
$node->start;
# Reset to specific WAL segment
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index 00b5b42dd7..b18a7f954c 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -237,6 +237,9 @@ sub run_pg_rewind
"$tmp_folder/master-postgresql.conf.tmp",
"$master_pgdata/postgresql.conf");
+ chmod(0600, "$master_pgdata/postgresql.conf")
+ or die("unable to set permissions for $master_pgdata/postgresql.conf");
+
# Plug-in rewound node to the now-promoted standby node
my $port_standby = $node_standby->port;
$node_master->append_conf(
@@ -255,6 +258,8 @@ recovery_target_timeline='latest'
# Clean up after the test. Stop both servers, if they're still running.
sub clean_rewind_test
{
+ ok (check_pg_data_perm($node_master->data_dir(), 0));
+
$node_master->teardown_node if defined $node_master;
$node_standby->teardown_node if defined $node_standby;
}
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 705383d184..d51914e901 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -18,6 +18,7 @@
#include <fcntl.h>
#include <unistd.h>
+#include "common/file_perm.h"
#include "file_ops.h"
#include "filemap.h"
#include "logging.h"
@@ -58,7 +59,7 @@ open_target_file(const char *path, bool trunc)
mode = O_WRONLY | O_CREAT | PG_BINARY;
if (trunc)
mode |= O_TRUNC;
- dstfd = open(dstpath, mode, 0600);
+ dstfd = open(dstpath, mode, PG_FILE_MODE_DEFAULT);
if (dstfd < 0)
pg_fatal("could not open target file \"%s\": %s\n",
dstpath, strerror(errno));
@@ -190,7 +191,7 @@ truncate_target_file(const char *path, off_t newsize)
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
- fd = open(dstpath, O_WRONLY, 0);
+ fd = open(dstpath, O_WRONLY, PG_FILE_MODE_DEFAULT);
if (fd < 0)
pg_fatal("could not open file \"%s\" for truncation: %s\n",
dstpath, strerror(errno));
@@ -211,7 +212,7 @@ create_target_dir(const char *path)
return;
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
- if (mkdir(dstpath, S_IRWXU) != 0)
+ if (mkdir(dstpath, PG_DIR_MODE_DEFAULT) != 0)
pg_fatal("could not create directory \"%s\": %s\n",
dstpath, strerror(errno));
}
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 72ab2f8d5e..3d179e2c7d 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -24,6 +24,7 @@
#include "access/xlog_internal.h"
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "getopt_long.h"
#include "storage/bufpage.h"
@@ -185,6 +186,9 @@ main(int argc, char **argv)
exit(1);
}
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_DEFAULT);
+
/*
* Don't allow pg_rewind to be run as root, to avoid overwriting the
* ownership of files in the data directory. We need only check for root
diff --git a/src/bin/pg_rewind/t/001_basic.pl b/src/bin/pg_rewind/t/001_basic.pl
index 736f34eae3..e7fc200fc0 100644
--- a/src/bin/pg_rewind/t/001_basic.pl
+++ b/src/bin/pg_rewind/t/001_basic.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 8;
+use Test::More tests => 10;
use RewindTest;
diff --git a/src/bin/pg_rewind/t/002_databases.pl b/src/bin/pg_rewind/t/002_databases.pl
index 37cdd712f3..0e0140b96f 100644
--- a/src/bin/pg_rewind/t/002_databases.pl
+++ b/src/bin/pg_rewind/t/002_databases.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 6;
use RewindTest;
diff --git a/src/bin/pg_rewind/t/003_extrafiles.pl b/src/bin/pg_rewind/t/003_extrafiles.pl
index 2433a4aab6..28933da343 100644
--- a/src/bin/pg_rewind/t/003_extrafiles.pl
+++ b/src/bin/pg_rewind/t/003_extrafiles.pl
@@ -3,7 +3,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 6;
use File::Find;
@@ -14,6 +14,8 @@ sub run_test
{
my $test_mode = shift;
+ umask(0077);
+
RewindTest::setup_cluster($test_mode);
RewindTest::start_master();
diff --git a/src/bin/pg_rewind/t/004_pg_xlog_symlink.pl b/src/bin/pg_rewind/t/004_pg_xlog_symlink.pl
index feadaa6a0f..70dd061915 100644
--- a/src/bin/pg_rewind/t/004_pg_xlog_symlink.pl
+++ b/src/bin/pg_rewind/t/004_pg_xlog_symlink.pl
@@ -14,7 +14,7 @@ if ($windows_os)
}
else
{
- plan tests => 4;
+ plan tests => 6;
}
use RewindTest;
diff --git a/src/bin/pg_rewind/t/005_same_timeline.pl b/src/bin/pg_rewind/t/005_same_timeline.pl
index 0e334ee191..2fbca4ad7c 100644
--- a/src/bin/pg_rewind/t/005_same_timeline.pl
+++ b/src/bin/pg_rewind/t/005_same_timeline.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 1;
+use Test::More tests => 2;
use RewindTest;
diff --git a/src/bin/pg_upgrade/file.c b/src/bin/pg_upgrade/file.c
index f38bfacf02..595d43ee31 100644
--- a/src/bin/pg_upgrade/file.c
+++ b/src/bin/pg_upgrade/file.c
@@ -10,6 +10,7 @@
#include "postgres_fe.h"
#include "access/visibilitymap.h"
+#include "common/file_perm.h"
#include "pg_upgrade.h"
#include "storage/bufpage.h"
#include "storage/checksum.h"
@@ -44,7 +45,7 @@ copyFile(const char *src, const char *dst,
schemaName, relName, src, strerror(errno));
if ((dest_fd = open(dst, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR)) < 0)
+ PG_FILE_MODE_DEFAULT)) < 0)
pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
schemaName, relName, dst, strerror(errno));
@@ -151,7 +152,7 @@ rewriteVisibilityMap(const char *fromfile, const char *tofile,
schemaName, relName, fromfile, strerror(errno));
if ((dst_fd = open(tofile, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR)) < 0)
+ PG_FILE_MODE_DEFAULT)) < 0)
pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
schemaName, relName, tofile, strerror(errno));
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index d12412799f..59df6fd88f 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -38,6 +38,7 @@
#include "pg_upgrade.h"
#include "catalog/pg_class.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "fe_utils/string_utils.h"
@@ -79,7 +80,7 @@ main(int argc, char **argv)
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_upgrade"));
/* Ensure that all files created by pg_upgrade are non-world-readable */
- umask(S_IRWXG | S_IRWXO);
+ umask(PG_MODE_MASK_DEFAULT);
parseCommandLine(argc, argv);
diff --git a/src/bin/pg_upgrade/test.sh b/src/bin/pg_upgrade/test.sh
index 39983abea1..4e8db4aaf4 100644
--- a/src/bin/pg_upgrade/test.sh
+++ b/src/bin/pg_upgrade/test.sh
@@ -230,6 +230,17 @@ standard_initdb 'initdb'
pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "${PGDATA}" -b "$oldbindir" -B "$bindir" -p "$PGPORT" -P "$PGPORT"
+# make sure all directories and files have group permissions
+if [ $(find ${PGDATA} -type f ! -perm 600 | wc -l) -ne 0 ]; then
+ echo "files in PGDATA with permission != 600";
+ exit 1;
+fi
+
+if [ $(find ${PGDATA} -type d ! -perm 700 | wc -l) -ne 0 ]; then
+ echo "directories in PGDATA with permission != 700";
+ exit 1;
+fi
+
pg_ctl start -l "$logdir/postmaster2.log" -o "$POSTMASTER_OPTS" -w
case $testhost in
diff --git a/src/bin/pg_upgrade/version.c b/src/bin/pg_upgrade/version.c
index 76e9d65537..205f772fc5 100644
--- a/src/bin/pg_upgrade/version.c
+++ b/src/bin/pg_upgrade/version.c
@@ -53,7 +53,7 @@ new_9_0_populate_pg_largeobject_metadata(ClusterInfo *cluster, bool check_mode)
{
PQExpBufferData connectbuf;
- if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL)
+ if (script == NULL && (script = fopen(output_path, "w")) == NULL)
pg_fatal("could not open file \"%s\": %s\n", output_path,
strerror(errno));
@@ -152,7 +152,7 @@ old_9_3_check_for_line_data_type_usage(ClusterInfo *cluster)
for (rowno = 0; rowno < ntups; rowno++)
{
found = true;
- if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL)
+ if (script == NULL && (script = fopen(output_path, "w")) == NULL)
pg_fatal("could not open file \"%s\": %s\n", output_path,
strerror(errno));
if (!db_used)
@@ -253,7 +253,7 @@ old_9_6_check_for_unknown_data_type_usage(ClusterInfo *cluster)
for (rowno = 0; rowno < ntups; rowno++)
{
found = true;
- if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL)
+ if (script == NULL && (script = fopen(output_path, "w")) == NULL)
pg_fatal("could not open file \"%s\": %s\n", output_path,
strerror(errno));
if (!db_used)
@@ -335,7 +335,7 @@ old_9_6_invalidate_hash_indexes(ClusterInfo *cluster, bool check_mode)
found = true;
if (!check_mode)
{
- if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL)
+ if (script == NULL && (script = fopen(output_path, "w")) == NULL)
pg_fatal("could not open file \"%s\": %s\n", output_path,
strerror(errno));
if (!db_used)
diff --git a/src/include/common/file_perm.h b/src/include/common/file_perm.h
new file mode 100644
index 0000000000..5b823cbd39
--- /dev/null
+++ b/src/include/common/file_perm.h
@@ -0,0 +1,32 @@
+/*-------------------------------------------------------------------------
+ *
+ * File and directory permission constants
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/file_perm.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FILE_PERM_H
+#define FILE_PERM_H
+
+/*
+ * Default mode mask for data directory permissions that does not allow
+ * group execute/read.
+ */
+#define PG_MODE_MASK_DEFAULT (S_IRWXG | S_IRWXO)
+
+/*
+ * Default mode for created files.
+ */
+#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR | S_IRGRP)
+
+/*
+ * Default mode for directories.
+ */
+#define PG_DIR_MODE_DEFAULT (S_IRWXU | S_IRGRP | S_IXGRP)
+
+#endif /* FILE_PERM_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 4244e7b1fd..33c636471b 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -112,6 +112,9 @@ extern int CloseTransientFile(int fd);
extern int BasicOpenFile(const char *fileName, int fileFlags);
extern int BasicOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode);
+ /* Make a directory with default permissions */
+extern int MakeDirectoryDefaultPerm(const char *directoryName);
+
/* Miscellaneous support routines */
extern void InitFileAccess(void);
extern void set_max_safe_fds(void);
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 80188315f1..76e571b98c 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -484,6 +484,9 @@ sub append_conf
my $conffile = $self->data_dir . '/' . $filename;
TestLib::append_to_file($conffile, $str . "\n");
+
+ chmod(0600, $conffile)
+ or die("unable to set permissions for $conffile");
}
=pod
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index fdd427608b..acccecc5de 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -12,8 +12,11 @@ use warnings;
use Config;
use Exporter 'import';
+use Fcntl qw(:mode);
use File::Basename;
+use File::Find;
use File::Spec;
+use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
use SimpleTee;
@@ -26,6 +29,7 @@ our @EXPORT = qw(
slurp_dir
slurp_file
append_to_file
+ check_pg_data_perm
check_pg_config
system_or_bail
system_log
@@ -222,6 +226,83 @@ sub append_to_file
close $fh;
}
+# Ensure all permissions in the pg_data directory are correct. When allow_group
+# is true then permissions should be dir = 0750, file = 0640. When allow_group
+# is false then permissions should be dir = 0700, file = 0600.
+sub check_pg_data_perm
+{
+ my ($pgdata, $allow_group) = @_;
+
+ # Result defaults to true
+ my $result = 1;
+
+ # Expected permission
+ my $expected_file_perm = $allow_group ? 0640 : 0600;
+ my $expected_dir_perm = $allow_group ? 0750 : 0700;
+
+ find
+ (
+ sub
+ {
+ my $file_stat = stat($File::Find::name);
+
+ defined($file_stat)
+ or die("unable to stat $File::Find::name");
+
+ my $file_mode = S_IMODE($file_stat->mode);
+
+ # Is this a file?
+ if (S_ISREG($file_stat->mode))
+ {
+ # postmaster.pid file must be 600
+ if ($File::Find::name =~ /\/postmaster\.pid$/)
+ {
+ if ($file_mode != 0600)
+ {
+ print(*STDERR, "$File::Find::name mode must be 0600\n");
+
+ $result = 0;
+ return
+ }
+ }
+ else
+ {
+ if ($file_mode != $expected_file_perm)
+ {
+ print(*STDERR,
+ sprintf("$File::Find::name mode must be %04o\n",
+ $expected_file_perm));
+
+ $result = 0;
+ return
+ }
+ }
+ }
+ # Else a directory?
+ elsif (S_ISDIR($file_stat->mode))
+ {
+ if ($file_mode != $expected_dir_perm)
+ {
+ print(*STDERR,
+ sprintf("$File::Find::name mode must be %04o\n",
+ $expected_dir_perm));
+
+ $result = 0;
+ return
+ }
+ }
+ # Else something we can't handle
+ else
+ {
+ die "unknown file type for $File::Find::name";
+ }
+ },
+ $pgdata
+ );
+
+ return $result;
+}
+
# Check presence of a given regexp within pg_config.h for the installation
# where tests are running, returning a match status result depending on
# that.
group-access-v7-03-group.patchtext/plain; charset=UTF-8; name=group-access-v7-03-group.patch; x-mac-creator=0; x-mac-type=0Download
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index 9d8e69056f..a9912eb418 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -1095,7 +1095,11 @@ SELECT pg_stop_backup();
and <filename>postmaster.opts</filename>, which record information
about the running <application>postmaster</application>, not about the
<application>postmaster</application> which will eventually use this backup.
- (These files can confuse <application>pg_ctl</application>.)
+ (These files can confuse <application>pg_ctl</application>.) If group read
+ access is enabled on the data directory and an unprivileged user in the
+ <productname>PostgreSQL</productname> group is performing the backup, then
+ <filename>postmaster.pid</filename> will not be readable and must be
+ excluded.
</para>
<para>
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 585665f161..5f57b933b9 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -76,6 +76,14 @@ PostgreSQL documentation
to do so.)
</para>
+ <para>
+ For security reasons the new cluster created by <command>initdb</command>
+ will only be accessible by the cluster owner by default. The
+ <option>--allow-group-access</option> option allows any user in the same
+ group as the cluster owner to read files in the cluster. This is useful
+ for performing backups as a non-privileged user.
+ </para>
+
<para>
<command>initdb</command> initializes the database cluster's default
locale and character set encoding. The character set encoding,
@@ -301,6 +309,17 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-g</option></term>
+ <term><option>--allow-group-access</option></term>
+ <listitem>
+ <para>
+ Allows users in the same group as the cluster owner to read all cluster
+ files created by <command>initdb</command>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-W</option></term>
<term><option>--pwprompt</option></term>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 71f02300c2..8d8a48aad4 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -137,7 +137,10 @@ postgres$ <userinput>initdb -D /usr/local/pgsql/data</userinput>
database, it is essential that it be secured from unauthorized
access. <command>initdb</command> therefore revokes access
permissions from everyone but the
- <productname>PostgreSQL</productname> user.
+ <productname>PostgreSQL</productname> user, and optionally, group.
+ Group access, when enabled, is read-only. This allows an unprivileged
+ user in the <productname>PostgreSQL</productname> group to take a backup of
+ the cluster data or perform other operations that only require read access.
</para>
<para>
@@ -2220,6 +2223,15 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
member of the group that has access to those certificate and key files.
</para>
+ <para>
+ If the data directory allows group read access then certificate files may
+ need to be located outside of the data directory in order to conform to the
+ security requirements outlined above. Generally, group access is enabled
+ to allow an unprivileged user to backup the database, and in that case the
+ backup software will not be able to read the certificate files and will
+ likely error.
+ </para>
+
<para>
If the private key is protected with a passphrase, the
server will prompt for the passphrase and will not start until it has
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index e13a2ae8c4..65f528147a 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -97,6 +97,7 @@
#include "access/xlog.h"
#include "bootstrap/bootstrap.h"
#include "catalog/pg_control.h"
+#include "common/file_perm.h"
#include "common/ip.h"
#include "lib/ilist.h"
#include "libpq/auth.h"
@@ -587,7 +588,8 @@ PostmasterMain(int argc, char *argv[])
IsPostmasterEnvironment = true;
/*
- * for security, no dir or file created can be group or other accessible
+ * By default, no dir or file created can be group or other accessible. This
+ * may be modified later depending in the permissions of the data directory.
*/
umask(S_IRWXG | S_IRWXO);
@@ -1524,25 +1526,30 @@ checkDataDir(void)
#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.
+ * Check if the directory has correct permissions. If not, reject.
*
* 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))
+ if (stat_buf.st_mode & PG_MODE_MASK_ALLOW_GROUP)
ereport(FATAL,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("data directory \"%s\" has group or world access",
+ errmsg("data directory \"%s\" has invalid permissions",
DataDir),
- errdetail("Permissions should be u=rwx (0700).")));
+ errdetail("Permissions should be u=rwx (0700) or u=rwx,g=rx (0750).")));
+#endif
+
+ /*
+ * Reset the file mode creation mask based on the mode of the data
+ * directory.
+ *
+ * Suppress when on Windows, because there may not be proper support
+ * for Unix-y file permissions.
+ */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+ umask(PG_MODE_MASK_DEFAULT & ~stat_buf.st_mode);
#endif
/* Look for PG_VERSION before looking for pg_control */
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 4ef06c2349..56356d75c5 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -2289,6 +2289,7 @@ usage(const char *progname)
printf(_(" --auth-local=METHOD default authentication method for local-socket connections\n"));
printf(_(" [-D, --pgdata=]DATADIR location for this database cluster\n"));
printf(_(" -E, --encoding=ENCODING set default encoding for new databases\n"));
+ printf(_(" -g, --allow-group-access allow group read/execute on data directory\n"));
printf(_(" --locale=LOCALE set default locale for new databases\n"));
printf(_(" --lc-collate=, --lc-ctype=, --lc-messages=LOCALE\n"
" --lc-monetary=, --lc-numeric=, --lc-time=LOCALE\n"
@@ -2992,6 +2993,7 @@ main(int argc, char *argv[])
{"waldir", required_argument, NULL, 'X'},
{"wal-segsize", required_argument, NULL, 12},
{"data-checksums", no_argument, NULL, 'k'},
+ {"allow-group-access", no_argument, NULL, 'g'},
{NULL, 0, NULL, 0}
};
@@ -3033,7 +3035,7 @@ main(int argc, char *argv[])
/* process command-line options */
- while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:", long_options, &option_index)) != -1)
+ while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:g", long_options, &option_index)) != -1)
{
switch (c)
{
@@ -3127,6 +3129,9 @@ main(int argc, char *argv[])
case 12:
str_wal_segment_size_mb = pg_strdup(optarg);
break;
+ case 'g':
+ file_mode_mask = PG_MODE_MASK_ALLOW_GROUP;
+ break;
default:
/* getopt_long already emitted a complaint */
fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index 561608f1d8..f767f07ed5 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -4,9 +4,11 @@
use strict;
use warnings;
+use Fcntl ':mode';
+use File::stat qw{lstat};
use PostgresNode;
use TestLib;
-use Test::More tests => 16;
+use Test::More tests => 18;
my $tempdir = TestLib::tempdir;
my $xlogdir = "$tempdir/pgxlog";
@@ -50,3 +52,12 @@ mkdir $datadir;
}
command_ok([ 'initdb', '-S', $datadir ], 'sync only');
command_fails([ 'initdb', $datadir ], 'existing data directory');
+
+# Init a new db with group access
+my $datadir_group = "$tempdir/data_group";
+
+command_ok(
+ [ 'initdb', '-g', $datadir_group ],
+ 'successful creation with group access');
+
+ok(check_pg_data_perm($datadir_group, 1));
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index 1eef7179cc..bb70e625fd 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -2171,6 +2171,7 @@ main(int argc, char **argv)
*/
argv0 = argv[0];
+ /* Set restrictive mode mask until PGDATA permissions are checked */
umask(PG_MODE_MASK_DEFAULT);
/* support --help and --version even if invoked as root */
@@ -2406,6 +2407,9 @@ main(int argc, char **argv)
snprintf(version_file, MAXPGPATH, "%s/PG_VERSION", pg_data);
snprintf(pid_file, MAXPGPATH, "%s/postmaster.pid", pg_data);
snprintf(backup_file, MAXPGPATH, "%s/backup_label", pg_data);
+
+ /* Set mask based on PGDATA permissions */
+ umask(DataDirectoryMask(pg_data));
}
switch (ctl_command)
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl
index 909bf4d465..f8facc3f00 100644
--- a/src/bin/pg_ctl/t/001_start_stop.pl
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -2,9 +2,11 @@ use strict;
use warnings;
use Config;
+use Fcntl ':mode';
+use File::stat qw{lstat};
use PostgresNode;
use TestLib;
-use Test::More tests => 20;
+use Test::More tests => 24;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -68,4 +70,24 @@ command_ok(
ok(-f $logFileName);
ok(check_pg_data_perm("$tempdir/data", 0));
+# Log file for second perm test
+$logFileName = "$tempdir/data/perm-test-640.log";
+
+# Change the data dir mode so log file will be created with group read
+# privileges on the next start
+system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
+
+add_pg_data_group_perm("$tempdir/data");
+
+command_ok(
+ [ 'pg_ctl', 'start', '-D', "$tempdir/data", '-l', $logFileName ],
+ 'start server to check group permissions');
+
+ok(-f $logFileName);
+ok(check_pg_data_perm("$tempdir/data", 1));
+
+command_ok(
+ [ 'pg_ctl', 'restart', '-D', "$tempdir/data", '-l', $logFileName ],
+ 'pg_ctl restart with server running');
+
system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 47abd41fc3..6a4dc502c4 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -328,8 +328,8 @@ main(int argc, char *argv[])
exit(1);
}
- /* Set dir/file mode mask */
- umask(PG_MODE_MASK_DEFAULT);
+ /* Set mask based on PGDATA permissions */
+ umask(DataDirectoryMask(DataDir));
/* Check that data directory matches our server version */
CheckDataVersion();
diff --git a/src/bin/pg_resetwal/t/001_basic.pl b/src/bin/pg_resetwal/t/001_basic.pl
index db0c2a630a..4a53358f58 100644
--- a/src/bin/pg_resetwal/t/001_basic.pl
+++ b/src/bin/pg_resetwal/t/001_basic.pl
@@ -4,7 +4,7 @@ use warnings;
use Config;
use PostgresNode;
use TestLib;
-use Test::More tests => 14;
+use Test::More tests => 15;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -40,9 +40,12 @@ ok(check_pg_data_perm($pgdata, 0), 'check PGDATA permissions');
$node->start;
-# Reset to specific WAL segment
+# Reset to specific WAL segment. Also enable group access to make sure files
+# and directories are created with group permissions.
$node->stop;
+add_pg_data_group_perm($pgdata);
+
$node->command_ok(
['pg_resetwal', '-l', '000000070000000700000007', '-D', $pgdata],
'set to specific WAL');
@@ -52,4 +55,6 @@ is_deeply(
[sort(qw(. .. archive_status 000000070000000700000007))],
'WAL recreated');
+ok(check_pg_data_perm($pgdata, 1), 'check PGDATA permissions');
+
$node->start;
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index b18a7f954c..10d44fd259 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -116,10 +116,12 @@ sub check_query
sub setup_cluster
{
my $extra_name = shift;
+ my $group_access = shift;
# Initialize master, data checksums are mandatory
$node_master = get_new_node('master' . ($extra_name ? "_${extra_name}" : ''));
- $node_master->init(allows_streaming => 1);
+ $node_master->init(allows_streaming => 1,
+ has_group_access => defined($group_access) ? $group_access : 0);
# Set wal_keep_segments to prevent WAL segment recycling after enforced
# checkpoints in the tests.
$node_master->append_conf('postgresql.conf', qq(
@@ -237,7 +239,7 @@ sub run_pg_rewind
"$tmp_folder/master-postgresql.conf.tmp",
"$master_pgdata/postgresql.conf");
- chmod(0600, "$master_pgdata/postgresql.conf")
+ chmod($node_master->group_access() ? 0640 : 0600, "$master_pgdata/postgresql.conf")
or die("unable to set permissions for $master_pgdata/postgresql.conf");
# Plug-in rewound node to the now-promoted standby node
@@ -258,7 +260,8 @@ recovery_target_timeline='latest'
# Clean up after the test. Stop both servers, if they're still running.
sub clean_rewind_test
{
- ok (check_pg_data_perm($node_master->data_dir(), 0));
+ ok (check_pg_data_perm(
+ $node_master->data_dir(), $node_master->group_access()));
$node_master->teardown_node if defined $node_master;
$node_standby->teardown_node if defined $node_standby;
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 3d179e2c7d..11153e5f2f 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -186,8 +186,8 @@ main(int argc, char **argv)
exit(1);
}
- /* Set dir/file mode mask */
- umask(PG_MODE_MASK_DEFAULT);
+ /* Set mask based on PGDATA permissions */
+ umask(DataDirectoryMask(datadir_target));
/*
* Don't allow pg_rewind to be run as root, to avoid overwriting the
diff --git a/src/bin/pg_rewind/t/001_basic.pl b/src/bin/pg_rewind/t/001_basic.pl
index e7fc200fc0..205bdd77ef 100644
--- a/src/bin/pg_rewind/t/001_basic.pl
+++ b/src/bin/pg_rewind/t/001_basic.pl
@@ -9,7 +9,7 @@ sub run_test
{
my $test_mode = shift;
- RewindTest::setup_cluster($test_mode);
+ RewindTest::setup_cluster($test_mode, 1);
RewindTest::start_master();
# Create a test table and insert a row in master.
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 59df6fd88f..54f54fb0a4 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -79,7 +79,7 @@ main(int argc, char **argv)
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_upgrade"));
- /* Ensure that all files created by pg_upgrade are non-world-readable */
+ /* Set default restrictive mask until new cluster permissions are read */
umask(PG_MODE_MASK_DEFAULT);
parseCommandLine(argc, argv);
@@ -100,6 +100,9 @@ main(int argc, char **argv)
check_cluster_compatibility(live_check);
+ /* Set mask based on new PGDATA permissions */
+ umask(DataDirectoryMask(new_cluster.pgdata));
+
check_and_dump_old_cluster(live_check);
diff --git a/src/bin/pg_upgrade/test.sh b/src/bin/pg_upgrade/test.sh
index 4e8db4aaf4..24631a91c6 100644
--- a/src/bin/pg_upgrade/test.sh
+++ b/src/bin/pg_upgrade/test.sh
@@ -20,9 +20,9 @@ unset MAKELEVEL
# Run a given "initdb" binary and overlay the regression testing
# authentication configuration.
standard_initdb() {
- # To increase coverage of non-standard segment size without
- # increase test runtime, run these tests with a lower setting.
- "$1" -N --wal-segsize 1
+ # To increase coverage of non-standard segment size and group access
+ # without increasing test runtime, run these tests with a custom setting.
+ "$1" -N --wal-segsize 1 -g
if [ -n "$TEMP_CONFIG" -a -r "$TEMP_CONFIG" ]
then
cat "$TEMP_CONFIG" >> "$PGDATA/postgresql.conf"
@@ -231,13 +231,13 @@ standard_initdb 'initdb'
pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "${PGDATA}" -b "$oldbindir" -B "$bindir" -p "$PGPORT" -P "$PGPORT"
# make sure all directories and files have group permissions
-if [ $(find ${PGDATA} -type f ! -perm 600 | wc -l) -ne 0 ]; then
- echo "files in PGDATA with permission != 600";
+if [ $(find ${PGDATA} -type f ! -perm 640 | wc -l) -ne 0 ]; then
+ echo "files in PGDATA with permission != 640";
exit 1;
fi
-if [ $(find ${PGDATA} -type d ! -perm 700 | wc -l) -ne 0 ]; then
- echo "directories in PGDATA with permission != 700";
+if [ $(find ${PGDATA} -type d ! -perm 750 | wc -l) -ne 0 ]; then
+ echo "directories in PGDATA with permission != 750";
exit 1;
fi
diff --git a/src/common/Makefile b/src/common/Makefile
index 80e78d72fe..504375bef4 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -51,7 +51,7 @@ else
OBJS_COMMON += sha2.o
endif
-OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o file_utils.o restricted_token.o
+OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o file_perm.o file_utils.o restricted_token.o
OBJS_SRV = $(OBJS_COMMON:%.o=%_srv.o)
diff --git a/src/common/file_perm.c b/src/common/file_perm.c
new file mode 100644
index 0000000000..2be76913cd
--- /dev/null
+++ b/src/common/file_perm.c
@@ -0,0 +1,50 @@
+/*-------------------------------------------------------------------------
+ *
+ * File and directory permission routines
+ *
+ * Group read/execute may optional be enabled on PGDATA so any frontend tools
+ * That write into PGDATA must know what mask to set and the permissions to
+ * use for creating files and directories.
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/file_perm.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres_fe.h"
+
+#include <sys/stat.h>
+
+#include "common/file_perm.h"
+
+
+/*
+ * Determine the mask to use when writing to PGDATA.
+ *
+ * Errors are not handled here and should be checked by the frontend
+ * application.
+ */
+#ifdef FRONTEND
+
+mode_t
+DataDirectoryMask(const char *dataDir)
+{
+ struct stat statBuf;
+
+ /*
+ * If an error occurs getting the mode then return the more restrictive mask.
+ * It is the reponsibility of the frontend application to generate an error.
+ */
+ if (stat(dataDir, &statBuf) != 0)
+ return PG_MODE_MASK_DEFAULT;
+
+ /*
+ * Construct the mask that the caller should pass to umask().
+ */
+ return PG_MODE_MASK_DEFAULT & ~statBuf.st_mode;
+}
+
+#endif /* FRONTEND */
diff --git a/src/include/common/file_perm.h b/src/include/common/file_perm.h
index 5b823cbd39..35dcbe6108 100644
--- a/src/include/common/file_perm.h
+++ b/src/include/common/file_perm.h
@@ -1,6 +1,10 @@
/*-------------------------------------------------------------------------
*
- * File and directory permission constants
+ * File and directory permission constants and routines
+ *
+ * Group read/execute may optional be enabled on PGDATA so any frontend tools
+ * That write into PGDATA must know what mask to set and the permissions to
+ * use for creating files and directories.
*
*
* Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
@@ -20,13 +24,30 @@
#define PG_MODE_MASK_DEFAULT (S_IRWXG | S_IRWXO)
/*
- * Default mode for created files.
+ * Optional mode mask for data directory permissions that allows group
+ * read/execute.
+ */
+#define PG_MODE_MASK_ALLOW_GROUP (S_IWGRP | S_IRWXO)
+
+/*
+ * Default mode for created files, unless something else is specified using
+ * the *Perm() function variants.
*/
#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR | S_IRGRP)
/*
- * Default mode for directories.
+ * Default mode for directories created with MakeDirectoryDefaultPerm().
*/
#define PG_DIR_MODE_DEFAULT (S_IRWXU | S_IRGRP | S_IXGRP)
+#ifdef FRONTEND
+
+/*
+ * Determine the mask to use when writing to PGDATA
+ */
+mode_t DataDirectoryMask(const char *dataDir);
+
+#endif /* FRONTEND */
+
+
#endif /* FILE_PERM_H */
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 76e571b98c..8e9c6c11f0 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -269,6 +269,20 @@ sub connstr
=pod
+=item $node->group_access()
+
+Does the data dir allow group access?
+
+=cut
+
+sub group_access
+{
+ my ($self) = @_;
+ return $self->{_group_access};
+}
+
+=pod
+
=item $node->data_dir()
Returns the path to the data directory. postgresql.conf and pg_hba.conf are
@@ -406,10 +420,16 @@ sub init
$params{allows_streaming} = 0 unless defined $params{allows_streaming};
$params{has_archiving} = 0 unless defined $params{has_archiving};
+ $params{has_group_access} = 0 unless defined $params{has_group_access};
mkdir $self->backup_dir;
mkdir $self->archive_dir;
+ if ($params{has_group_access})
+ {
+ push(@{$params{extra}}, '-g');
+ }
+
TestLib::system_or_bail('initdb', '-D', $pgdata, '-A', 'trust', '-N',
@{ $params{extra} });
TestLib::system_or_bail($ENV{PG_REGRESS}, '--config-auth', $pgdata);
@@ -460,8 +480,12 @@ sub init
}
close $conf;
+ chmod($params{has_group_access} ? 0640 : 0600, "$pgdata/postgresql.conf")
+ or die("unable to set permissions for $pgdata/postgresql.conf");
+
$self->set_replication_conf if $params{allows_streaming};
$self->enable_archiving if $params{has_archiving};
+ $self->{_group_access} = 1 if $params{has_group_access};
}
=pod
@@ -485,7 +509,7 @@ sub append_conf
TestLib::append_to_file($conffile, $str . "\n");
- chmod(0600, $conffile)
+ chmod($self->group_access() ? 0640 : 0600, $conffile)
or die("unable to set permissions for $conffile");
}
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index acccecc5de..3d404eefac 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -30,6 +30,7 @@ our @EXPORT = qw(
slurp_file
append_to_file
check_pg_data_perm
+ add_pg_data_group_perm
check_pg_config
system_or_bail
system_log
@@ -303,6 +304,29 @@ sub check_pg_data_perm
return $result;
}
+# Add group permissions to a PGDATA directory
+sub add_pg_data_group_perm
+{
+ my ($pgdata) = @_;
+
+ find
+ (
+ sub
+ {
+ my $file_stat = stat($File::Find::name);
+
+ defined($file_stat)
+ or die("unable to stat $file_stat");
+
+ chmod(S_IMODE($file_stat->mode) |
+ (S_ISDIR($file_stat->mode) ? 0050 : 0040),
+ $File::Find::name)
+ or die "unable to chmod $File::Find::name";
+ },
+ $pgdata
+ );
+}
+
# Check presence of a given regexp within pg_config.h for the installation
# where tests are running, returning a match status result depending on
# that.
On Tue, Feb 27, 2018 at 03:52:32PM -0500, David Steele wrote:
On 1/30/18 3:01 AM, Michael Paquier wrote:
The purpose of this test to be ensure nothing else is in the directory.
As the tests get more complex I think keeping track of the state will be
important. In other words, this is really an assertion that the current
test state is correct, not that the prior command succeeded.
Good point. Objection removed.
Another test that could be easily included is with -o, then create a
table and check for its OID value. Also for -x, then just use
txid_current to check the value consistency. For -c, with
track_commit_timestamp set to on, you can set a couple of values and
then check for pg_last_committed_xact() which would be NULL if you use
an XID older than the minimum set for example. All those are simple
enough so they could easily be added in the first version of this test
suite.I would prefer to leave this for another patch.
OK, no problems with that either.
2) 02-mkdir
Converts mkdir() calls to MakeDirectoryDefaultPerm() if the original
call used default permissions.So the only call not converted to that API is in ipc.c... OK, this one
is pretty simple so nothing really to say about it, the real meat comes
with patch 3. I would rather see with a good eye if this patch
introduces file_perm.h which includes both PG_FILE_MODE_DEFAULT and
PG_DIR_MODE_DEFAULT, and have the frontends use those values. This
would make the switch to 0003 a bit easier if you look at pg_resetwal's
diffs for example.Agreed. I thought about this originally but could not come up with a
good split. I spent some time on it and came up with something that I
think works (and would make sense to commit separately).
OK, thanks.
+command_ok( + ['chmod', "-R", 'g+rX', "$pgdata"], + 'add group perms to PGDATA');That would blow up on Windows. You should replace that by perl's chmod
and look at its return result for sanity checks, likely with ($cnt != 0).
And let's not use icacls please...Fixed with a new function, add_pg_data_group_perm().
That's basically a recursive chmod, so chmod_recursive is more adapted?
I could imagine that this is useful as well for removing group
permissions, so the new mode could be specified as an argument.
+ if ($params{has_group_access}) + { + push(@{$params{extra}}, '-g'); + } No need to introduce a new parameter here, please use directly extra => [ '-g' ] in the routines calling PostgresNode::init.Other code needs to know if group access is enabled on the node (see
RewindTest.pm) so it's not just a matter of passing the option.
I don't quite understand here. I have no objection into extending
setup_cluster() with a group_access argument so as the tests run both
the group access and without it, but I'd rather keep the low-level API
clean of anything fancy if we can use the facility which already
exists.
New patches are attached. The end result is almost identical to v6 but
code was moved from 03 to 02 as per Michael's suggestion.
Thanks for the updated versions.
1) 01-pgresetwal-test
Adds a very basic test suite for pg_resetwal.
Okay. This one looks fine to me. It could be passed down to a
committer.
2) 02-file-perm
Adds a new header (file_perm.h) and makes all front-end utilities use
the new constants for setting umask. Converts mkdir() calls in the
backend to MakeDirectoryDefaultPerm() if the original
call used default permissions. Adds tests to make sure permissions in
PGDATA are set correctly by the front-end tools.
I had a more simple (complicated?) thing in mind for the split, which
would actually cause this patch to be split into two more:
1) Introduce file_perm.h and replace all the existing values for modes
and masks to what the header introduces. This allows to check if the
new header is not forgotten anywhere, especially for the frontends.
2) Introduce MakeDirectoryDefaultPerm, which switches the backend calls
of mkdir() to the new API.
3) Add the grouping facilities, which updates the default masks and
modes of file_perm.h.
Grouping 1) and 2) together as you do makes sense as well I guess, as in
my proposal the mkdir calls in the backend would be touched twice, still
things get more incremental.
+/*
+ * Default mode for created files.
+ */
+#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR | S_IRGRP)
+
+/*
+ * Default mode for directories.
+ */
+#define PG_DIR_MODE_DEFAULT (S_IRWXU | S_IRGRP | S_IXGRP)
For the first cut, this should not use S_IRGRP in the first declaration,
and (S_IXGRP | S_IXGRP) in the second declaration.
- if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL)
+ if (script == NULL && (script = fopen(output_path, "w")) == NULL)
Why are those calls in pg_upgrade changed?
TestLib.pm does not need the group-related extensions, right?
check_pg_data_perm() looks useful even without $allow_group even if the
grouping facility is reverted at the end. S_ISLNK should be considered
as well for tablespaces or symlinks of pg_wal? We have such cases in
the regression tests.
- if (chmod(pg_data, S_IRWXU) != 0)
+ if (chmod(pg_data, PG_DIR_MODE_DEFAULT & ~file_mode_mask) != 0)
[...]
- if (chmod(xlog_dir, S_IRWXU) != 0)
+ if (chmod(xlog_dir, PG_DIR_MODE_DEFAULT & ~file_mode_mask) != 0)
Those seems incorrect, as they consider the default value only.
snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
- {
- fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
- progname, path, strerror(errno));
- exit_nicely();
- }
Hm. Why are those removed?
setup_signals();
- umask(S_IRWXG | S_IRWXO);
-
create_data_directory();
This should not be moved around.
3) 03-group
Allow group access on PGDATA. This includes backend changes, utility
changes (initdb, pg_ctl, pg_upgrade, etc.) and tests for each utility.
I have not looked much in depth into this one, but I spotted two issues
on a quick read:
+ * Errors are not handled here and should be checked by the frontend
+ * application.
+ */
+#ifdef FRONTEND
What you need to do for frontend-only files in src/common/ is to define
that at the top and not bother about FRONTEND cflags at all:
#ifndef FRONTEND
#error "This file is not expected to be compiled for backend code"
#endif
src/tools/msvc/Mkvcbuild.pm also needs to be updated with this new file,
or you will break MSVC build.
--
Michael
Hi,
On 2018-02-27 15:52:32 -0500, David Steele wrote:
Thanks for having a look at the patches.
I'd personally appreciate if you'd add commit messages to the individual
patches in a series. A brief explanation why something is done is good
enough. E.g. here it's far from obvious why something is done with
pg_resetwal.
I'd suggest just using git format-patch -v to format series.
Independent of that, I'm not entirely clear on what the status of this
patch is, without rereading the thread. Could you summarize?
- Andres
Hi Andres,
On 3/1/18 9:04 PM, Andres Freund wrote:
On 2018-02-27 15:52:32 -0500, David Steele wrote:
Thanks for having a look at the patches.
I'd personally appreciate if you'd add commit messages to the individual
patches in a series. A brief explanation why something is done is good
enough.I'd suggest just using git format-patch -v to format series.
Sure, I've been meaning to switch over to this format and just haven't
gotten around to it yet. I do recognize the value, however, and will do
it going forward.
Independent of that, I'm not entirely clear on what the status of this
patch is, without rereading the thread. Could you summarize?
Good question. This patch has been around for a year and has gone
though a number of iterations.
In summary, we started with a more rigid design that required a GUC to
enable group privs (actually, at first it was any mode you wanted).
Through feedback from Tom and others we realized that the front-end
tools could not read the GUCs and it would probably be best based on the
perms of PGDATA, as long as they were 700 or 750.
We decided the patch was too big, so broke it up a bit and got some of
the infrastructure committed in 2017-09 [1]https://commitfest.postgresql.org/14/1262/.
In 2018-01 we brought in a unified patch set that built on the ideas
from 2017-03. During that CF we taught all the front-end tools to check
PGDATA for permissions and wrote a lot of tests.
The current incarnation is a refinement with input from Michael and a
different split in the patches.
E.g. here it's far from obvious why something is done with
pg_resetwal.
[reordered by me for clarity]
Any front-end tool that writes into PGDATA is affected by this patch
because mode must be set correctly.
pg_resetwal had no tests, so I wrote a basic test suite to base the
group permissions tests on. I included the basic test suite as a
separate patch because I felt it should be committed even if the main
feature wasn't. It's far from a comprehensive test suite, but certainly
better than what we have currently.
Does that clear things up?
--
-David
david@pgmasters.net
On Thu, Mar 01, 2018 at 09:32:58PM -0500, David Steele wrote:
On 3/1/18 9:04 PM, Andres Freund wrote:
I'd suggest just using git format-patch -v to format series.
Sure, I've been meaning to switch over to this format and just haven't
gotten around to it yet. I do recognize the value, however, and will do it
going forward.
Simple patches can bypass on that, but once you have at least three
patches this helps a lot with reviews. Explaining as well in the email
sending the patches what each patch actually does, even roughly helps in
focusing on one element or the other.
E.g. here it's far from obvious why something is done with
pg_resetwal.[reordered by me for clarity]
Any front-end tool that writes into PGDATA is affected by this patch because
mode must be set correctly.pg_resetwal had no tests, so I wrote a basic test suite to base the group
permissions tests on. I included the basic test suite as a separate patch
because I felt it should be committed even if the main feature wasn't. It's
far from a comprehensive test suite, but certainly better than what we have
currently.
Based on my recent lookup at code level for this feature, the patch for
pg_resetwal (which could have been discussed on its own thread as well),
would be fine for commit. The thing could be extended a bit more but
there is nothing opposing even a basic test suite to be in. Then you
have a set of refactoring patches, which still need some work. And
finally there is a rather invasive patch on top of the whole thing. The
refactoring work shows much more value only after the main feature is
in, still I think that unifying the default permissions allowed for
files and directories, as well as mkdir() calls has some value in
itself to think it as an mergeable, independent, change. I think that
it would be hard to get the whole patch set into the tree by the end of
the CF though, but cutting the refactoring pieces would be doable. At
least it would provide some base for integration in v12. And the
refactoring patch has some pieces that would be helpful for TAP tests as
well.
--
Michael
On 2/28/18 2:28 AM, Michael Paquier wrote:
On Tue, Feb 27, 2018 at 03:52:32PM -0500, David Steele wrote:
On 1/30/18 3:01 AM, Michael Paquier wrote:
+command_ok( + ['chmod', "-R", 'g+rX', "$pgdata"], + 'add group perms to PGDATA');That would blow up on Windows. You should replace that by perl's chmod
and look at its return result for sanity checks, likely with ($cnt != 0).
And let's not use icacls please...Fixed with a new function, add_pg_data_group_perm().
That's basically a recursive chmod, so chmod_recursive is more adapted?
I could imagine that this is useful as well for removing group
permissions, so the new mode could be specified as an argument.
The required package (File::chmod::Recursive) for chmod_recursive is not
in use anywhere else and was not installed when I installed build
dependencies.
I'm not sure what the protocol for introducing a new Perl module is? I
couldn't find packages for the major OSes. Are we OK with using CPAN?
+ if ($params{has_group_access}) + { + push(@{$params{extra}}, '-g'); + } No need to introduce a new parameter here, please use directly extra => [ '-g' ] in the routines calling PostgresNode::init.Other code needs to know if group access is enabled on the node (see
RewindTest.pm) so it's not just a matter of passing the option.I don't quite understand here. I have no objection into extending
setup_cluster() with a group_access argument so as the tests run both
the group access and without it, but I'd rather keep the low-level API
clean of anything fancy if we can use the facility which already
exists.
I'm not sure what you mean by, "facility that already exists". I think
I implemented this the same as the allows_streaming and has_archiving
flags. The only difference is that this option must be set at initdb
time rather than in postgresql.conf.
Could you be more specific?
New patches are attached. The end result is almost identical to v6 but
code was moved from 03 to 02 as per Michael's suggestion.Thanks for the updated versions.
1) 01-pgresetwal-test
Adds a very basic test suite for pg_resetwal.
Okay. This one looks fine to me. It could be passed down to a
committer.
Agreed.
2) 02-file-perm
Adds a new header (file_perm.h) and makes all front-end utilities use
the new constants for setting umask. Converts mkdir() calls in the
backend to MakeDirectoryDefaultPerm() if the original
call used default permissions. Adds tests to make sure permissions in
PGDATA are set correctly by the front-end tools.I had a more simple (complicated?) thing in mind for the split, which
would actually cause this patch to be split into two more:
1) Introduce file_perm.h and replace all the existing values for modes
and masks to what the header introduces. This allows to check if the
new header is not forgotten anywhere, especially for the frontends.
2) Introduce MakeDirectoryDefaultPerm, which switches the backend calls
of mkdir() to the new API.
3) Add the grouping facilities, which updates the default masks and
modes of file_perm.h.Grouping 1) and 2) together as you do makes sense as well I guess, as in
my proposal the mkdir calls in the backend would be touched twice, still
things get more incremental.
I think I prefer grouping 1 and 2. It produces less churn, as you say,
and I don't think MakeDirectoryDefaultPerm really needs its own patch.
Based on comments so far, nobody has an objection to it.
In theory, the first two patches could be applied just for refactoring
without adding group permissions at all. There are a lot of new tests
to make sure permissions are set correctly so it seems like a win right off.
+/* + * Default mode for created files. + */ +#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR | S_IRGRP) + +/* + * Default mode for directories. + */ +#define PG_DIR_MODE_DEFAULT (S_IRWXU | S_IRGRP | S_IXGRP) For the first cut, this should not use S_IRGRP in the first declaration, and (S_IXGRP | S_IXGRP) in the second declaration.
Done.
- if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL) + if (script == NULL && (script = fopen(output_path, "w")) == NULL) Why are those calls in pg_upgrade changed?
Done.
Those should have been removed already. Originally I had removed
fopen_priv() because it didn't serve a purpose anymore. In the
meantime, somebody else made it a macro to fopen(). I decided my patch
would be less noisy if I reverted to fopen_priv() but I missed a few places.
TestLib.pm does not need the group-related extensions, right?
Done.
check_pg_data_perm() looks useful even without $allow_group even if the
grouping facility is reverted at the end. S_ISLNK should be considered
as well for tablespaces or symlinks of pg_wal? We have such cases in
the regression tests.
Hmmm, the link is just pointing to a directory whose permissions have
been changed. Why do we need to worry about the link?
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
- {
- fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
- progname, path, strerror(errno));
- exit_nicely();
- }
Hm. Why are those removed?
When I started working on this, pg_upgrade did not set umask and instead
did chmod for each dir created. umask() has since been added (even
before my patch) and so these are now noops. Seemed easier to remove
them than to change them all.
setup_signals();
- umask(S_IRWXG | S_IRWXO);
-
create_data_directory();
This should not be moved around.
Hmmm - I moved it much earlier in the process which seems like a good
thing. Consider if there was a option to fixup permissions, like there
is to fsync. Isn't it best to set the mode as soon as possible to
prevent code from being inserted before it?
What you need to do for frontend-only files in src/common/ is to define
that at the top and not bother about FRONTEND cflags at all:
#ifndef FRONTEND
#error "This file is not expected to be compiled for backend code"
#endif
Fixed.
src/tools/msvc/Mkvcbuild.pm also needs to be updated with this new file,
or you will break MSVC build.
Fixed.
New patches attached.
--
-David
david@pgmasters.net
Attachments:
group-access-v8-01-pgresetwal-test.patchtext/plain; charset=UTF-8; name=group-access-v8-01-pgresetwal-test.patch; x-mac-creator=0; x-mac-type=0Download
From 2caecfef0cb0b029c439786fe462d69b5caaf67b Mon Sep 17 00:00:00 2001
From: David Steele <david@pgmasters.net>
Date: Mon, 5 Mar 2018 14:33:10 -0500
Subject: [PATCH 1/3] pg_resetwal tests.
Adds a very basic test suite for pg_resetwal.
---
src/bin/pg_resetwal/.gitignore | 1 +
src/bin/pg_resetwal/Makefile | 6 +++++
src/bin/pg_resetwal/t/001_basic.pl | 53 ++++++++++++++++++++++++++++++++++++++
3 files changed, 60 insertions(+)
create mode 100644 src/bin/pg_resetwal/t/001_basic.pl
diff --git a/src/bin/pg_resetwal/.gitignore b/src/bin/pg_resetwal/.gitignore
index 236abb4323..a950255fd7 100644
--- a/src/bin/pg_resetwal/.gitignore
+++ b/src/bin/pg_resetwal/.gitignore
@@ -1 +1,2 @@
/pg_resetwal
+/tmp_check
diff --git a/src/bin/pg_resetwal/Makefile b/src/bin/pg_resetwal/Makefile
index 5ab7ad33e0..13c9ca6279 100644
--- a/src/bin/pg_resetwal/Makefile
+++ b/src/bin/pg_resetwal/Makefile
@@ -33,3 +33,9 @@ uninstall:
clean distclean maintainer-clean:
rm -f pg_resetwal$(X) $(OBJS)
+
+check:
+ $(prove_check)
+
+installcheck:
+ $(prove_installcheck)
diff --git a/src/bin/pg_resetwal/t/001_basic.pl b/src/bin/pg_resetwal/t/001_basic.pl
new file mode 100644
index 0000000000..234bd70303
--- /dev/null
+++ b/src/bin/pg_resetwal/t/001_basic.pl
@@ -0,0 +1,53 @@
+use strict;
+use warnings;
+
+use Config;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 13;
+
+my $tempdir = TestLib::tempdir;
+my $tempdir_short = TestLib::tempdir_short;
+
+program_help_ok('pg_resetwal');
+program_version_ok('pg_resetwal');
+program_options_handling_ok('pg_resetwal');
+
+# Initialize node without replication settings
+my $node = get_new_node('main');
+my $pgdata = $node->data_dir;
+my $pgwal = "$pgdata/pg_wal";
+$node->init;
+$node->start;
+
+# Remove WAL from pg_wal and make sure it gets rebuilt
+$node->stop;
+
+unlink("$pgwal/000000010000000000000001") == 1
+ or die("unable to remove 000000010000000000000001");
+
+is_deeply(
+ [sort(slurp_dir($pgwal))], [sort(qw(. .. archive_status))], 'no WAL');
+
+$node->command_ok(['pg_resetwal', '-D', $pgdata], 'recreate pg_wal');
+
+is_deeply(
+ [sort(slurp_dir($pgwal))],
+ [sort(qw(. .. archive_status 000000010000000000000002))],
+ 'WAL recreated');
+
+$node->start;
+
+# Reset to specific WAL segment
+$node->stop;
+
+$node->command_ok(
+ ['pg_resetwal', '-l', '000000070000000700000007', '-D', $pgdata],
+ 'set to specific WAL');
+
+is_deeply(
+ [sort(slurp_dir($pgwal))],
+ [sort(qw(. .. archive_status 000000070000000700000007))],
+ 'WAL recreated');
+
+$node->start;
--
2.14.3 (Apple Git-98)
group-access-v8-02-file-perm.patchtext/plain; charset=UTF-8; name=group-access-v8-02-file-perm.patch; x-mac-creator=0; x-mac-type=0Download
From 9c1bdd25ea92fca5ded4a2889283cf8f47a28a0f Mon Sep 17 00:00:00 2001
From: David Steele <david@pgmasters.net>
Date: Mon, 5 Mar 2018 14:36:08 -0500
Subject: [PATCH 2/3] Refactor file permissions in backend/frontend
Adds a new header (file_perm.h) and makes all front-end utilities use
the new constants for setting umask. Converts mkdir() calls in the
backend to MakeDirectoryDefaultPerm() if the original
call used default permissions. Adds tests to make sure permissions in
PGDATA are set correctly by the front-end tools.
---
src/backend/access/transam/xlog.c | 2 +-
src/backend/commands/tablespace.c | 22 +++++---
src/backend/postmaster/postmaster.c | 2 +-
src/backend/postmaster/syslogger.c | 5 +-
src/backend/replication/slot.c | 5 +-
src/backend/storage/file/copydir.c | 2 +-
src/backend/storage/file/fd.c | 32 +++++++-----
src/backend/storage/ipc/ipc.c | 4 ++
src/bin/initdb/initdb.c | 42 ++++------------
src/bin/initdb/t/001_initdb.pl | 4 +-
src/bin/pg_ctl/pg_ctl.c | 3 +-
src/bin/pg_ctl/t/001_start_stop.pl | 13 +++--
src/bin/pg_resetwal/pg_resetwal.c | 8 ++-
src/bin/pg_resetwal/t/001_basic.pl | 4 +-
src/bin/pg_rewind/RewindTest.pm | 5 ++
src/bin/pg_rewind/file_ops.c | 7 +--
src/bin/pg_rewind/pg_rewind.c | 4 ++
src/bin/pg_rewind/t/001_basic.pl | 2 +-
src/bin/pg_rewind/t/002_databases.pl | 2 +-
src/bin/pg_rewind/t/003_extrafiles.pl | 4 +-
src/bin/pg_rewind/t/004_pg_xlog_symlink.pl | 2 +-
src/bin/pg_rewind/t/005_same_timeline.pl | 2 +-
src/bin/pg_upgrade/file.c | 5 +-
src/bin/pg_upgrade/pg_upgrade.c | 3 +-
src/bin/pg_upgrade/test.sh | 11 ++++
src/include/common/file_perm.h | 32 ++++++++++++
src/include/storage/fd.h | 3 ++
src/test/perl/PostgresNode.pm | 3 ++
src/test/perl/TestLib.pm | 80 ++++++++++++++++++++++++++++++
29 files changed, 234 insertions(+), 79 deletions(-)
create mode 100644 src/include/common/file_perm.h
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 47a6c4d895..bf07bbd746 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4086,7 +4086,7 @@ ValidateXLOGDirectoryStructure(void)
{
ereport(LOG,
(errmsg("creating missing WAL directory \"%s\"", path)));
- if (mkdir(path, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(path) < 0)
ereport(FATAL,
(errmsg("could not create missing directory \"%s\": %m",
path)));
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 5c450caa4e..c4ee987446 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -68,6 +68,7 @@
#include "commands/seclabel.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
+#include "common/file_perm.h"
#include "miscadmin.h"
#include "postmaster/bgwriter.h"
#include "storage/fd.h"
@@ -151,7 +152,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
else
{
/* Directory creation failed? */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(dir) < 0)
{
char *parentdir;
@@ -173,7 +174,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
get_parent_directory(parentdir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -184,7 +185,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
parentdir = pstrdup(dir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -192,7 +193,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
pfree(parentdir);
/* Create database directory */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(dir) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -279,7 +280,8 @@ CreateTableSpace(CreateTableSpaceStmt *stmt)
/*
* Check that location isn't too long. Remember that we're going to append
* 'PG_XXX/<dboid>/<relid>_<fork>.<nnn>'. FYI, we never actually
- * reference the whole path here, but mkdir() uses the first two parts.
+ * reference the whole path here, but MakeDirectoryDefaultPerm() uses the first
+ * two parts.
*/
if (strlen(location) + 1 + strlen(TABLESPACE_VERSION_DIRECTORY) + 1 +
OIDCHARS + 1 + OIDCHARS + 1 + FORKNAMECHARS + 1 + OIDCHARS > MAXPGPATH)
@@ -565,6 +567,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
char *linkloc;
char *location_with_version_dir;
struct stat st;
+ mode_t current_umask; /* Current umask value */
linkloc = psprintf("pg_tblspc/%u", tablespaceoid);
location_with_version_dir = psprintf("%s/%s", location,
@@ -574,7 +577,10 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* Attempt to coerce target directory to safe permissions. If this fails,
* it doesn't exist or has the wrong owner.
*/
- if (chmod(location, S_IRWXU) != 0)
+ current_umask = umask(0);
+ umask(current_umask);
+
+ if (chmod(location, PG_DIR_MODE_DEFAULT & ~current_umask) != 0)
{
if (errno == ENOENT)
ereport(ERROR,
@@ -599,7 +605,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
if (stat(location_with_version_dir, &st) == 0 && S_ISDIR(st.st_mode))
{
if (!rmtree(location_with_version_dir, true))
- /* If this failed, mkdir() below is going to error. */
+ /* If this failed, MakeDirectoryDefaultPerm() below is going to error. */
ereport(WARNING,
(errmsg("some useless files may be left behind in old database directory \"%s\"",
location_with_version_dir)));
@@ -610,7 +616,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* The creation of the version directory prevents more than one tablespace
* in a single location.
*/
- if (mkdir(location_with_version_dir, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(location_with_version_dir) < 0)
{
if (errno == EEXIST)
ereport(ERROR,
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index f3ddf828bb..e13a2ae8c4 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -4495,7 +4495,7 @@ internal_forkexec(int argc, char *argv[], Port *port)
* As in OpenTemporaryFileInTablespace, try to make the temp-file
* directory
*/
- mkdir(PG_TEMP_FILES_DIR, S_IRWXU);
+ MakeDirectoryDefaultPerm(PG_TEMP_FILES_DIR);
fp = AllocateFile(tmpfilename, PG_BINARY_W);
if (!fp)
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index f70eea37df..ae23941f59 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -41,6 +41,7 @@
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
#include "storage/dsm.h"
+#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pg_shmem.h"
@@ -322,7 +323,7 @@ SysLoggerMain(int argc, char *argv[])
/*
* Also, create new directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ MakeDirectoryDefaultPerm(Log_directory);
}
if (strcmp(Log_filename, currentLogFilename) != 0)
{
@@ -564,7 +565,7 @@ SysLogger_Start(void)
/*
* Create log directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ MakeDirectoryDefaultPerm(Log_directory);
/*
* The initial logfile is created right in the postmaster, to verify that
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index fc9ef22b0b..3a98360b50 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -1166,13 +1166,14 @@ CreateSlotOnDisk(ReplicationSlot *slot)
* It's just barely possible that some previous effort to create or drop a
* slot with this name left a temp directory lying around. If that seems
* to be the case, try to remove it. If the rmtree() fails, we'll error
- * out at the mkdir() below, so we don't bother checking success.
+ * out at the MakeDirectoryDefaultPerm() below, so we don't bother checking
+ * success.
*/
if (stat(tmppath, &st) == 0 && S_ISDIR(st.st_mode))
rmtree(tmppath, true);
/* Create and fsync the temporary slot directory. */
- if (mkdir(tmppath, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(tmppath) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
diff --git a/src/backend/storage/file/copydir.c b/src/backend/storage/file/copydir.c
index ca6342db0d..ff980dc1f5 100644
--- a/src/backend/storage/file/copydir.c
+++ b/src/backend/storage/file/copydir.c
@@ -41,7 +41,7 @@ copydir(char *fromdir, char *todir, bool recurse)
char fromfile[MAXPGPATH * 2];
char tofile[MAXPGPATH * 2];
- if (mkdir(todir, S_IRWXU) != 0)
+ if (MakeDirectoryDefaultPerm(todir) != 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m", todir)));
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index 2a18e94ff4..1491665375 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -84,6 +84,7 @@
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "catalog/pg_tablespace.h"
+#include "common/file_perm.h"
#include "pgstat.h"
#include "portability/mem.h"
#include "storage/fd.h"
@@ -124,12 +125,6 @@
*/
#define FD_MINFREE 10
-/*
- * Default mode for created files, unless something else is specified using
- * the *Perm() function variants.
- */
-#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR)
-
/*
* A number of platforms allow individual processes to open many more files
* than they can really support when *many* processes do the same thing.
@@ -1435,7 +1430,7 @@ PathNameOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode)
void
PathNameCreateTemporaryDir(const char *basedir, const char *directory)
{
- if (mkdir(directory, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(directory) < 0)
{
if (errno == EEXIST)
return;
@@ -1445,14 +1440,14 @@ PathNameCreateTemporaryDir(const char *basedir, const char *directory)
* EEXIST to close a race against another process following the same
* algorithm.
*/
- if (mkdir(basedir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(basedir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary directory \"%s\": %m",
basedir)));
/* Try again. */
- if (mkdir(directory, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(directory) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary subdirectory \"%s\": %m",
@@ -1602,11 +1597,11 @@ OpenTemporaryFileInTablespace(Oid tblspcOid, bool rejectError)
* We might need to create the tablespace's tempfile directory, if no
* one has yet done so.
*
- * Don't check for error from mkdir; it could fail if someone else
- * just did the same thing. If it doesn't work then we'll bomb out on
+ * Don't check error from MakeDirectoryDefaultPerm; it could fail if someone
+ * else just did the same thing. If it doesn't work then we'll bomb out on
* the second create attempt, instead.
*/
- mkdir(tempdirpath, S_IRWXU);
+ MakeDirectoryDefaultPerm(tempdirpath);
file = PathNameOpenFile(tempfilepath,
O_RDWR | O_CREAT | O_TRUNC | PG_BINARY);
@@ -3555,3 +3550,16 @@ fsync_parent_path(const char *fname, int elevel)
return 0;
}
+
+/*
+ * Create a directory using PG_DIR_MODE_DEFAULT for permissions
+ *
+ * Directories in PGDATA should normally have specific permissions -- when
+ * using this function the caller does not need to know what they should be.
+ * For permissions other than the default use mkdir() directly.
+ */
+int
+MakeDirectoryDefaultPerm(const char *directoryName)
+{
+ return mkdir(directoryName, PG_DIR_MODE_DEFAULT);
+}
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index 726db7b7f1..466613e4af 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -137,6 +137,10 @@ proc_exit(int code)
else
snprintf(gprofDirName, 32, "gprof/%d", (int) getpid());
+ /*
+ * Use mkdir() instead of MakeDirectoryDefaultPerm() here because non-default
+ * permissions are required.
+ */
mkdir("gprof", S_IRWXU | S_IRWXG | S_IRWXO);
mkdir(gprofDirName, S_IRWXU | S_IRWXG | S_IRWXO);
chdir(gprofDirName);
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 2efd3b75b1..4ef06c2349 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -64,6 +64,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_class.h"
#include "catalog/pg_collation.h"
+#include "common/file_perm.h"
#include "common/file_utils.h"
#include "common/restricted_token.h"
#include "common/username.h"
@@ -144,6 +145,7 @@ static bool data_checksums = false;
static char *xlog_dir = NULL;
static char *str_wal_segment_size_mb = NULL;
static int wal_segment_size_mb;
+static mode_t file_mode_mask = PG_MODE_MASK_DEFAULT;
/* internal vars */
@@ -1170,12 +1172,6 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
- {
- fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
- progname, path, strerror(errno));
- exit_nicely();
- }
/*
* create the automatic configuration file to store the configuration
@@ -1190,12 +1186,6 @@ setup_config(void)
sprintf(path, "%s/postgresql.auto.conf", pg_data);
writefile(path, autoconflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
- {
- fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
- progname, path, strerror(errno));
- exit_nicely();
- }
free(conflines);
@@ -1277,12 +1267,6 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_hba.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
- {
- fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
- progname, path, strerror(errno));
- exit_nicely();
- }
free(conflines);
@@ -1293,12 +1277,6 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_ident.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
- {
- fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
- progname, path, strerror(errno));
- exit_nicely();
- }
free(conflines);
@@ -2692,7 +2670,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (pg_mkdir_p(pg_data, S_IRWXU) != 0)
+ if (pg_mkdir_p(pg_data, PG_DIR_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2710,7 +2688,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (chmod(pg_data, S_IRWXU) != 0)
+ if (chmod(pg_data, PG_DIR_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2778,7 +2756,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (pg_mkdir_p(xlog_dir, S_IRWXU) != 0)
+ if (pg_mkdir_p(xlog_dir, PG_DIR_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2796,7 +2774,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (chmod(xlog_dir, S_IRWXU) != 0)
+ if (chmod(xlog_dir, PG_DIR_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2846,7 +2824,7 @@ create_xlog_or_symlink(void)
else
{
/* Without -X option, just make the subdirectory normally */
- if (mkdir(subdirloc, S_IRWXU) < 0)
+ if (mkdir(subdirloc, PG_DIR_MODE_DEFAULT) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, subdirloc, strerror(errno));
@@ -2882,8 +2860,6 @@ initialize_data_directory(void)
setup_signals();
- umask(S_IRWXG | S_IRWXO);
-
create_data_directory();
create_xlog_or_symlink();
@@ -2902,7 +2878,7 @@ initialize_data_directory(void)
* The parent directory already exists, so we only need mkdir() not
* pg_mkdir_p() here, which avoids some failure modes; cf bug #13853.
*/
- if (mkdir(path, S_IRWXU) < 0)
+ if (mkdir(path, PG_DIR_MODE_DEFAULT) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -3159,6 +3135,8 @@ main(int argc, char *argv[])
}
}
+ /* Set the file mode creation mask */
+ umask(file_mode_mask);
/*
* Non-option argument specifies data directory as long as it wasn't
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index c0cfa6e92c..561608f1d8 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 15;
+use Test::More tests => 16;
my $tempdir = TestLib::tempdir;
my $xlogdir = "$tempdir/pgxlog";
@@ -45,6 +45,8 @@ mkdir $datadir;
command_ok([ 'initdb', '-N', '-T', 'german', '-X', $xlogdir, $datadir ],
'successful creation');
+
+ ok(check_pg_data_perm($datadir, 0));
}
command_ok([ 'initdb', '-S', $datadir ], 'sync only');
command_fails([ 'initdb', $datadir ], 'existing data directory');
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index 9bc830b085..1eef7179cc 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -25,6 +25,7 @@
#include "catalog/pg_control.h"
#include "common/controldata_utils.h"
+#include "common/file_perm.h"
#include "getopt_long.h"
#include "utils/pidfile.h"
@@ -2170,7 +2171,7 @@ main(int argc, char **argv)
*/
argv0 = argv[0];
- umask(S_IRWXG | S_IRWXO);
+ umask(PG_MODE_MASK_DEFAULT);
/* support --help and --version even if invoked as root */
if (argc > 1)
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl
index 5da4746cb4..909bf4d465 100644
--- a/src/bin/pg_ctl/t/001_start_stop.pl
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -4,7 +4,7 @@ use warnings;
use Config;
use PostgresNode;
use TestLib;
-use Test::More tests => 19;
+use Test::More tests => 20;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -57,10 +57,15 @@ command_ok([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ], 'pg_ctl stop');
command_fails([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ],
'second pg_ctl stop fails');
+# Log file for first perm test
+my $logFileName = "$tempdir/data/perm-test-600.log";
+
command_ok(
- [ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
+ [ 'pg_ctl', 'restart', '-D', "$tempdir/data", '-l', $logFileName ],
'pg_ctl restart with server not running');
-command_ok([ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
- 'pg_ctl restart with server running');
+
+# Log file should exist and have no group permissions
+ok(-f $logFileName);
+ok(check_pg_data_perm("$tempdir/data", 0));
system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index a132cf2e9f..47abd41fc3 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -52,6 +52,7 @@
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
#include "common/fe_memutils.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "storage/large_object.h"
#include "pg_getopt.h"
@@ -327,6 +328,9 @@ main(int argc, char *argv[])
exit(1);
}
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_DEFAULT);
+
/* Check that data directory matches our server version */
CheckDataVersion();
@@ -919,7 +923,7 @@ RewriteControlFile(void)
fd = open(XLOG_CONTROL_FILE,
O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ PG_FILE_MODE_DEFAULT);
if (fd < 0)
{
fprintf(stderr, _("%s: could not create pg_control file: %s\n"),
@@ -1201,7 +1205,7 @@ WriteEmptyXLOG(void)
unlink(path);
fd = open(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ PG_FILE_MODE_DEFAULT);
if (fd < 0)
{
fprintf(stderr, _("%s: could not open file \"%s\": %s\n"),
diff --git a/src/bin/pg_resetwal/t/001_basic.pl b/src/bin/pg_resetwal/t/001_basic.pl
index 234bd70303..db0c2a630a 100644
--- a/src/bin/pg_resetwal/t/001_basic.pl
+++ b/src/bin/pg_resetwal/t/001_basic.pl
@@ -4,7 +4,7 @@ use warnings;
use Config;
use PostgresNode;
use TestLib;
-use Test::More tests => 13;
+use Test::More tests => 14;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -36,6 +36,8 @@ is_deeply(
[sort(qw(. .. archive_status 000000010000000000000002))],
'WAL recreated');
+ok(check_pg_data_perm($pgdata, 0), 'check PGDATA permissions');
+
$node->start;
# Reset to specific WAL segment
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index 00b5b42dd7..b18a7f954c 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -237,6 +237,9 @@ sub run_pg_rewind
"$tmp_folder/master-postgresql.conf.tmp",
"$master_pgdata/postgresql.conf");
+ chmod(0600, "$master_pgdata/postgresql.conf")
+ or die("unable to set permissions for $master_pgdata/postgresql.conf");
+
# Plug-in rewound node to the now-promoted standby node
my $port_standby = $node_standby->port;
$node_master->append_conf(
@@ -255,6 +258,8 @@ recovery_target_timeline='latest'
# Clean up after the test. Stop both servers, if they're still running.
sub clean_rewind_test
{
+ ok (check_pg_data_perm($node_master->data_dir(), 0));
+
$node_master->teardown_node if defined $node_master;
$node_standby->teardown_node if defined $node_standby;
}
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 705383d184..d51914e901 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -18,6 +18,7 @@
#include <fcntl.h>
#include <unistd.h>
+#include "common/file_perm.h"
#include "file_ops.h"
#include "filemap.h"
#include "logging.h"
@@ -58,7 +59,7 @@ open_target_file(const char *path, bool trunc)
mode = O_WRONLY | O_CREAT | PG_BINARY;
if (trunc)
mode |= O_TRUNC;
- dstfd = open(dstpath, mode, 0600);
+ dstfd = open(dstpath, mode, PG_FILE_MODE_DEFAULT);
if (dstfd < 0)
pg_fatal("could not open target file \"%s\": %s\n",
dstpath, strerror(errno));
@@ -190,7 +191,7 @@ truncate_target_file(const char *path, off_t newsize)
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
- fd = open(dstpath, O_WRONLY, 0);
+ fd = open(dstpath, O_WRONLY, PG_FILE_MODE_DEFAULT);
if (fd < 0)
pg_fatal("could not open file \"%s\" for truncation: %s\n",
dstpath, strerror(errno));
@@ -211,7 +212,7 @@ create_target_dir(const char *path)
return;
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
- if (mkdir(dstpath, S_IRWXU) != 0)
+ if (mkdir(dstpath, PG_DIR_MODE_DEFAULT) != 0)
pg_fatal("could not create directory \"%s\": %s\n",
dstpath, strerror(errno));
}
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 72ab2f8d5e..3d179e2c7d 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -24,6 +24,7 @@
#include "access/xlog_internal.h"
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "getopt_long.h"
#include "storage/bufpage.h"
@@ -185,6 +186,9 @@ main(int argc, char **argv)
exit(1);
}
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_DEFAULT);
+
/*
* Don't allow pg_rewind to be run as root, to avoid overwriting the
* ownership of files in the data directory. We need only check for root
diff --git a/src/bin/pg_rewind/t/001_basic.pl b/src/bin/pg_rewind/t/001_basic.pl
index 736f34eae3..e7fc200fc0 100644
--- a/src/bin/pg_rewind/t/001_basic.pl
+++ b/src/bin/pg_rewind/t/001_basic.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 8;
+use Test::More tests => 10;
use RewindTest;
diff --git a/src/bin/pg_rewind/t/002_databases.pl b/src/bin/pg_rewind/t/002_databases.pl
index 37cdd712f3..0e0140b96f 100644
--- a/src/bin/pg_rewind/t/002_databases.pl
+++ b/src/bin/pg_rewind/t/002_databases.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 6;
use RewindTest;
diff --git a/src/bin/pg_rewind/t/003_extrafiles.pl b/src/bin/pg_rewind/t/003_extrafiles.pl
index 2433a4aab6..28933da343 100644
--- a/src/bin/pg_rewind/t/003_extrafiles.pl
+++ b/src/bin/pg_rewind/t/003_extrafiles.pl
@@ -3,7 +3,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 6;
use File::Find;
@@ -14,6 +14,8 @@ sub run_test
{
my $test_mode = shift;
+ umask(0077);
+
RewindTest::setup_cluster($test_mode);
RewindTest::start_master();
diff --git a/src/bin/pg_rewind/t/004_pg_xlog_symlink.pl b/src/bin/pg_rewind/t/004_pg_xlog_symlink.pl
index feadaa6a0f..70dd061915 100644
--- a/src/bin/pg_rewind/t/004_pg_xlog_symlink.pl
+++ b/src/bin/pg_rewind/t/004_pg_xlog_symlink.pl
@@ -14,7 +14,7 @@ if ($windows_os)
}
else
{
- plan tests => 4;
+ plan tests => 6;
}
use RewindTest;
diff --git a/src/bin/pg_rewind/t/005_same_timeline.pl b/src/bin/pg_rewind/t/005_same_timeline.pl
index 0e334ee191..2fbca4ad7c 100644
--- a/src/bin/pg_rewind/t/005_same_timeline.pl
+++ b/src/bin/pg_rewind/t/005_same_timeline.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 1;
+use Test::More tests => 2;
use RewindTest;
diff --git a/src/bin/pg_upgrade/file.c b/src/bin/pg_upgrade/file.c
index f38bfacf02..595d43ee31 100644
--- a/src/bin/pg_upgrade/file.c
+++ b/src/bin/pg_upgrade/file.c
@@ -10,6 +10,7 @@
#include "postgres_fe.h"
#include "access/visibilitymap.h"
+#include "common/file_perm.h"
#include "pg_upgrade.h"
#include "storage/bufpage.h"
#include "storage/checksum.h"
@@ -44,7 +45,7 @@ copyFile(const char *src, const char *dst,
schemaName, relName, src, strerror(errno));
if ((dest_fd = open(dst, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR)) < 0)
+ PG_FILE_MODE_DEFAULT)) < 0)
pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
schemaName, relName, dst, strerror(errno));
@@ -151,7 +152,7 @@ rewriteVisibilityMap(const char *fromfile, const char *tofile,
schemaName, relName, fromfile, strerror(errno));
if ((dst_fd = open(tofile, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR)) < 0)
+ PG_FILE_MODE_DEFAULT)) < 0)
pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
schemaName, relName, tofile, strerror(errno));
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index d12412799f..59df6fd88f 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -38,6 +38,7 @@
#include "pg_upgrade.h"
#include "catalog/pg_class.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "fe_utils/string_utils.h"
@@ -79,7 +80,7 @@ main(int argc, char **argv)
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_upgrade"));
/* Ensure that all files created by pg_upgrade are non-world-readable */
- umask(S_IRWXG | S_IRWXO);
+ umask(PG_MODE_MASK_DEFAULT);
parseCommandLine(argc, argv);
diff --git a/src/bin/pg_upgrade/test.sh b/src/bin/pg_upgrade/test.sh
index 39983abea1..4e8db4aaf4 100644
--- a/src/bin/pg_upgrade/test.sh
+++ b/src/bin/pg_upgrade/test.sh
@@ -230,6 +230,17 @@ standard_initdb 'initdb'
pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "${PGDATA}" -b "$oldbindir" -B "$bindir" -p "$PGPORT" -P "$PGPORT"
+# make sure all directories and files have group permissions
+if [ $(find ${PGDATA} -type f ! -perm 600 | wc -l) -ne 0 ]; then
+ echo "files in PGDATA with permission != 600";
+ exit 1;
+fi
+
+if [ $(find ${PGDATA} -type d ! -perm 700 | wc -l) -ne 0 ]; then
+ echo "directories in PGDATA with permission != 700";
+ exit 1;
+fi
+
pg_ctl start -l "$logdir/postmaster2.log" -o "$POSTMASTER_OPTS" -w
case $testhost in
diff --git a/src/include/common/file_perm.h b/src/include/common/file_perm.h
new file mode 100644
index 0000000000..3c6da0e000
--- /dev/null
+++ b/src/include/common/file_perm.h
@@ -0,0 +1,32 @@
+/*-------------------------------------------------------------------------
+ *
+ * File and directory permission constants
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/file_perm.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FILE_PERM_H
+#define FILE_PERM_H
+
+/*
+ * Default mode mask for data directory permissions that does not allow
+ * group execute/read.
+ */
+#define PG_MODE_MASK_DEFAULT (S_IRWXG | S_IRWXO)
+
+/*
+ * Default mode for created files.
+ */
+#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR)
+
+/*
+ * Default mode for directories.
+ */
+#define PG_DIR_MODE_DEFAULT S_IRWXU
+
+#endif /* FILE_PERM_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 4244e7b1fd..33c636471b 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -112,6 +112,9 @@ extern int CloseTransientFile(int fd);
extern int BasicOpenFile(const char *fileName, int fileFlags);
extern int BasicOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode);
+ /* Make a directory with default permissions */
+extern int MakeDirectoryDefaultPerm(const char *directoryName);
+
/* Miscellaneous support routines */
extern void InitFileAccess(void);
extern void set_max_safe_fds(void);
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 80188315f1..76e571b98c 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -484,6 +484,9 @@ sub append_conf
my $conffile = $self->data_dir . '/' . $filename;
TestLib::append_to_file($conffile, $str . "\n");
+
+ chmod(0600, $conffile)
+ or die("unable to set permissions for $conffile");
}
=pod
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index fdd427608b..21f4a9c55e 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -12,8 +12,11 @@ use warnings;
use Config;
use Exporter 'import';
+use Fcntl qw(:mode);
use File::Basename;
+use File::Find;
use File::Spec;
+use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
use SimpleTee;
@@ -26,6 +29,7 @@ our @EXPORT = qw(
slurp_dir
slurp_file
append_to_file
+ check_pg_data_perm
check_pg_config
system_or_bail
system_log
@@ -222,6 +226,82 @@ sub append_to_file
close $fh;
}
+# Ensure all permissions in the pg_data directory are correct. Permissions
+# should be dir = 0700, file = 0600.
+sub check_pg_data_perm
+{
+ my ($pgdata) = @_;
+
+ # Result defaults to true
+ my $result = 1;
+
+ # Expected permission
+ my $expected_file_perm = 0600;
+ my $expected_dir_perm = 0700;
+
+ find
+ (
+ sub
+ {
+ my $file_stat = stat($File::Find::name);
+
+ defined($file_stat)
+ or die("unable to stat $File::Find::name");
+
+ my $file_mode = S_IMODE($file_stat->mode);
+
+ # Is this a file?
+ if (S_ISREG($file_stat->mode))
+ {
+ # postmaster.pid file must be 600
+ if ($File::Find::name =~ /\/postmaster\.pid$/)
+ {
+ if ($file_mode != 0600)
+ {
+ print(*STDERR, "$File::Find::name mode must be 0600\n");
+
+ $result = 0;
+ return
+ }
+ }
+ else
+ {
+ if ($file_mode != $expected_file_perm)
+ {
+ print(*STDERR,
+ sprintf("$File::Find::name mode must be %04o\n",
+ $expected_file_perm));
+
+ $result = 0;
+ return
+ }
+ }
+ }
+ # Else a directory?
+ elsif (S_ISDIR($file_stat->mode))
+ {
+ if ($file_mode != $expected_dir_perm)
+ {
+ print(*STDERR,
+ sprintf("$File::Find::name mode must be %04o\n",
+ $expected_dir_perm));
+
+ $result = 0;
+ return
+ }
+ }
+ # Else something we can't handle
+ else
+ {
+ die "unknown file type for $File::Find::name";
+ }
+ },
+ $pgdata
+ );
+
+ return $result;
+}
+
# Check presence of a given regexp within pg_config.h for the installation
# where tests are running, returning a match status result depending on
# that.
--
2.14.3 (Apple Git-98)
group-access-v8-03-group.patchtext/plain; charset=UTF-8; name=group-access-v8-03-group.patch; x-mac-creator=0; x-mac-type=0Download
From d92d3e7d3778506b58b2bc99bdb81423439d651f Mon Sep 17 00:00:00 2001
From: David Steele <david@pgmasters.net>
Date: Mon, 5 Mar 2018 14:38:18 -0500
Subject: [PATCH 3/3] Allow group access on PGDATA.
This includes backend changes, utility changes (initdb, pg_ctl, pg_upgrade, etc.) and tests for each utility.
---
doc/src/sgml/backup.sgml | 6 ++++-
doc/src/sgml/ref/initdb.sgml | 19 +++++++++++++++
doc/src/sgml/runtime.sgml | 14 ++++++++++-
src/backend/postmaster/postmaster.c | 29 +++++++++++++---------
src/bin/initdb/initdb.c | 7 +++++-
src/bin/initdb/t/001_initdb.pl | 13 +++++++++-
src/bin/pg_ctl/pg_ctl.c | 4 ++++
src/bin/pg_ctl/t/001_start_stop.pl | 24 ++++++++++++++++++-
src/bin/pg_resetwal/pg_resetwal.c | 4 ++--
src/bin/pg_resetwal/t/001_basic.pl | 9 +++++--
src/bin/pg_rewind/RewindTest.pm | 9 ++++---
src/bin/pg_rewind/pg_rewind.c | 4 ++--
src/bin/pg_rewind/t/001_basic.pl | 2 +-
src/bin/pg_upgrade/pg_upgrade.c | 5 +++-
src/bin/pg_upgrade/test.sh | 14 +++++------
src/common/Makefile | 2 +-
src/common/file_perm.c | 48 +++++++++++++++++++++++++++++++++++++
src/include/common/file_perm.h | 31 ++++++++++++++++++++----
src/test/perl/PostgresNode.pm | 26 +++++++++++++++++++-
src/test/perl/TestLib.pm | 35 +++++++++++++++++++++++----
src/tools/msvc/Mkvcbuild.pm | 2 +-
21 files changed, 260 insertions(+), 47 deletions(-)
create mode 100644 src/common/file_perm.c
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index 9d8e69056f..a9912eb418 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -1095,7 +1095,11 @@ SELECT pg_stop_backup();
and <filename>postmaster.opts</filename>, which record information
about the running <application>postmaster</application>, not about the
<application>postmaster</application> which will eventually use this backup.
- (These files can confuse <application>pg_ctl</application>.)
+ (These files can confuse <application>pg_ctl</application>.) If group read
+ access is enabled on the data directory and an unprivileged user in the
+ <productname>PostgreSQL</productname> group is performing the backup, then
+ <filename>postmaster.pid</filename> will not be readable and must be
+ excluded.
</para>
<para>
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 585665f161..5f57b933b9 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -76,6 +76,14 @@ PostgreSQL documentation
to do so.)
</para>
+ <para>
+ For security reasons the new cluster created by <command>initdb</command>
+ will only be accessible by the cluster owner by default. The
+ <option>--allow-group-access</option> option allows any user in the same
+ group as the cluster owner to read files in the cluster. This is useful
+ for performing backups as a non-privileged user.
+ </para>
+
<para>
<command>initdb</command> initializes the database cluster's default
locale and character set encoding. The character set encoding,
@@ -301,6 +309,17 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-g</option></term>
+ <term><option>--allow-group-access</option></term>
+ <listitem>
+ <para>
+ Allows users in the same group as the cluster owner to read all cluster
+ files created by <command>initdb</command>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-W</option></term>
<term><option>--pwprompt</option></term>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 71f02300c2..8d8a48aad4 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -137,7 +137,10 @@ postgres$ <userinput>initdb -D /usr/local/pgsql/data</userinput>
database, it is essential that it be secured from unauthorized
access. <command>initdb</command> therefore revokes access
permissions from everyone but the
- <productname>PostgreSQL</productname> user.
+ <productname>PostgreSQL</productname> user, and optionally, group.
+ Group access, when enabled, is read-only. This allows an unprivileged
+ user in the <productname>PostgreSQL</productname> group to take a backup of
+ the cluster data or perform other operations that only require read access.
</para>
<para>
@@ -2220,6 +2223,15 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
member of the group that has access to those certificate and key files.
</para>
+ <para>
+ If the data directory allows group read access then certificate files may
+ need to be located outside of the data directory in order to conform to the
+ security requirements outlined above. Generally, group access is enabled
+ to allow an unprivileged user to backup the database, and in that case the
+ backup software will not be able to read the certificate files and will
+ likely error.
+ </para>
+
<para>
If the private key is protected with a passphrase, the
server will prompt for the passphrase and will not start until it has
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index e13a2ae8c4..65f528147a 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -97,6 +97,7 @@
#include "access/xlog.h"
#include "bootstrap/bootstrap.h"
#include "catalog/pg_control.h"
+#include "common/file_perm.h"
#include "common/ip.h"
#include "lib/ilist.h"
#include "libpq/auth.h"
@@ -587,7 +588,8 @@ PostmasterMain(int argc, char *argv[])
IsPostmasterEnvironment = true;
/*
- * for security, no dir or file created can be group or other accessible
+ * By default, no dir or file created can be group or other accessible. This
+ * may be modified later depending in the permissions of the data directory.
*/
umask(S_IRWXG | S_IRWXO);
@@ -1524,25 +1526,30 @@ checkDataDir(void)
#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.
+ * Check if the directory has correct permissions. If not, reject.
*
* 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))
+ if (stat_buf.st_mode & PG_MODE_MASK_ALLOW_GROUP)
ereport(FATAL,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("data directory \"%s\" has group or world access",
+ errmsg("data directory \"%s\" has invalid permissions",
DataDir),
- errdetail("Permissions should be u=rwx (0700).")));
+ errdetail("Permissions should be u=rwx (0700) or u=rwx,g=rx (0750).")));
+#endif
+
+ /*
+ * Reset the file mode creation mask based on the mode of the data
+ * directory.
+ *
+ * Suppress when on Windows, because there may not be proper support
+ * for Unix-y file permissions.
+ */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+ umask(PG_MODE_MASK_DEFAULT & ~stat_buf.st_mode);
#endif
/* Look for PG_VERSION before looking for pg_control */
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 4ef06c2349..56356d75c5 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -2289,6 +2289,7 @@ usage(const char *progname)
printf(_(" --auth-local=METHOD default authentication method for local-socket connections\n"));
printf(_(" [-D, --pgdata=]DATADIR location for this database cluster\n"));
printf(_(" -E, --encoding=ENCODING set default encoding for new databases\n"));
+ printf(_(" -g, --allow-group-access allow group read/execute on data directory\n"));
printf(_(" --locale=LOCALE set default locale for new databases\n"));
printf(_(" --lc-collate=, --lc-ctype=, --lc-messages=LOCALE\n"
" --lc-monetary=, --lc-numeric=, --lc-time=LOCALE\n"
@@ -2992,6 +2993,7 @@ main(int argc, char *argv[])
{"waldir", required_argument, NULL, 'X'},
{"wal-segsize", required_argument, NULL, 12},
{"data-checksums", no_argument, NULL, 'k'},
+ {"allow-group-access", no_argument, NULL, 'g'},
{NULL, 0, NULL, 0}
};
@@ -3033,7 +3035,7 @@ main(int argc, char *argv[])
/* process command-line options */
- while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:", long_options, &option_index)) != -1)
+ while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:g", long_options, &option_index)) != -1)
{
switch (c)
{
@@ -3127,6 +3129,9 @@ main(int argc, char *argv[])
case 12:
str_wal_segment_size_mb = pg_strdup(optarg);
break;
+ case 'g':
+ file_mode_mask = PG_MODE_MASK_ALLOW_GROUP;
+ break;
default:
/* getopt_long already emitted a complaint */
fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index 561608f1d8..f767f07ed5 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -4,9 +4,11 @@
use strict;
use warnings;
+use Fcntl ':mode';
+use File::stat qw{lstat};
use PostgresNode;
use TestLib;
-use Test::More tests => 16;
+use Test::More tests => 18;
my $tempdir = TestLib::tempdir;
my $xlogdir = "$tempdir/pgxlog";
@@ -50,3 +52,12 @@ mkdir $datadir;
}
command_ok([ 'initdb', '-S', $datadir ], 'sync only');
command_fails([ 'initdb', $datadir ], 'existing data directory');
+
+# Init a new db with group access
+my $datadir_group = "$tempdir/data_group";
+
+command_ok(
+ [ 'initdb', '-g', $datadir_group ],
+ 'successful creation with group access');
+
+ok(check_pg_data_perm($datadir_group, 1));
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index 1eef7179cc..bb70e625fd 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -2171,6 +2171,7 @@ main(int argc, char **argv)
*/
argv0 = argv[0];
+ /* Set restrictive mode mask until PGDATA permissions are checked */
umask(PG_MODE_MASK_DEFAULT);
/* support --help and --version even if invoked as root */
@@ -2406,6 +2407,9 @@ main(int argc, char **argv)
snprintf(version_file, MAXPGPATH, "%s/PG_VERSION", pg_data);
snprintf(pid_file, MAXPGPATH, "%s/postmaster.pid", pg_data);
snprintf(backup_file, MAXPGPATH, "%s/backup_label", pg_data);
+
+ /* Set mask based on PGDATA permissions */
+ umask(DataDirectoryMask(pg_data));
}
switch (ctl_command)
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl
index 909bf4d465..f8facc3f00 100644
--- a/src/bin/pg_ctl/t/001_start_stop.pl
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -2,9 +2,11 @@ use strict;
use warnings;
use Config;
+use Fcntl ':mode';
+use File::stat qw{lstat};
use PostgresNode;
use TestLib;
-use Test::More tests => 20;
+use Test::More tests => 24;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -68,4 +70,24 @@ command_ok(
ok(-f $logFileName);
ok(check_pg_data_perm("$tempdir/data", 0));
+# Log file for second perm test
+$logFileName = "$tempdir/data/perm-test-640.log";
+
+# Change the data dir mode so log file will be created with group read
+# privileges on the next start
+system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
+
+add_pg_data_group_perm("$tempdir/data");
+
+command_ok(
+ [ 'pg_ctl', 'start', '-D', "$tempdir/data", '-l', $logFileName ],
+ 'start server to check group permissions');
+
+ok(-f $logFileName);
+ok(check_pg_data_perm("$tempdir/data", 1));
+
+command_ok(
+ [ 'pg_ctl', 'restart', '-D', "$tempdir/data", '-l', $logFileName ],
+ 'pg_ctl restart with server running');
+
system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 47abd41fc3..6a4dc502c4 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -328,8 +328,8 @@ main(int argc, char *argv[])
exit(1);
}
- /* Set dir/file mode mask */
- umask(PG_MODE_MASK_DEFAULT);
+ /* Set mask based on PGDATA permissions */
+ umask(DataDirectoryMask(DataDir));
/* Check that data directory matches our server version */
CheckDataVersion();
diff --git a/src/bin/pg_resetwal/t/001_basic.pl b/src/bin/pg_resetwal/t/001_basic.pl
index db0c2a630a..4a53358f58 100644
--- a/src/bin/pg_resetwal/t/001_basic.pl
+++ b/src/bin/pg_resetwal/t/001_basic.pl
@@ -4,7 +4,7 @@ use warnings;
use Config;
use PostgresNode;
use TestLib;
-use Test::More tests => 14;
+use Test::More tests => 15;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -40,9 +40,12 @@ ok(check_pg_data_perm($pgdata, 0), 'check PGDATA permissions');
$node->start;
-# Reset to specific WAL segment
+# Reset to specific WAL segment. Also enable group access to make sure files
+# and directories are created with group permissions.
$node->stop;
+add_pg_data_group_perm($pgdata);
+
$node->command_ok(
['pg_resetwal', '-l', '000000070000000700000007', '-D', $pgdata],
'set to specific WAL');
@@ -52,4 +55,6 @@ is_deeply(
[sort(qw(. .. archive_status 000000070000000700000007))],
'WAL recreated');
+ok(check_pg_data_perm($pgdata, 1), 'check PGDATA permissions');
+
$node->start;
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index b18a7f954c..10d44fd259 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -116,10 +116,12 @@ sub check_query
sub setup_cluster
{
my $extra_name = shift;
+ my $group_access = shift;
# Initialize master, data checksums are mandatory
$node_master = get_new_node('master' . ($extra_name ? "_${extra_name}" : ''));
- $node_master->init(allows_streaming => 1);
+ $node_master->init(allows_streaming => 1,
+ has_group_access => defined($group_access) ? $group_access : 0);
# Set wal_keep_segments to prevent WAL segment recycling after enforced
# checkpoints in the tests.
$node_master->append_conf('postgresql.conf', qq(
@@ -237,7 +239,7 @@ sub run_pg_rewind
"$tmp_folder/master-postgresql.conf.tmp",
"$master_pgdata/postgresql.conf");
- chmod(0600, "$master_pgdata/postgresql.conf")
+ chmod($node_master->group_access() ? 0640 : 0600, "$master_pgdata/postgresql.conf")
or die("unable to set permissions for $master_pgdata/postgresql.conf");
# Plug-in rewound node to the now-promoted standby node
@@ -258,7 +260,8 @@ recovery_target_timeline='latest'
# Clean up after the test. Stop both servers, if they're still running.
sub clean_rewind_test
{
- ok (check_pg_data_perm($node_master->data_dir(), 0));
+ ok (check_pg_data_perm(
+ $node_master->data_dir(), $node_master->group_access()));
$node_master->teardown_node if defined $node_master;
$node_standby->teardown_node if defined $node_standby;
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 3d179e2c7d..11153e5f2f 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -186,8 +186,8 @@ main(int argc, char **argv)
exit(1);
}
- /* Set dir/file mode mask */
- umask(PG_MODE_MASK_DEFAULT);
+ /* Set mask based on PGDATA permissions */
+ umask(DataDirectoryMask(datadir_target));
/*
* Don't allow pg_rewind to be run as root, to avoid overwriting the
diff --git a/src/bin/pg_rewind/t/001_basic.pl b/src/bin/pg_rewind/t/001_basic.pl
index e7fc200fc0..205bdd77ef 100644
--- a/src/bin/pg_rewind/t/001_basic.pl
+++ b/src/bin/pg_rewind/t/001_basic.pl
@@ -9,7 +9,7 @@ sub run_test
{
my $test_mode = shift;
- RewindTest::setup_cluster($test_mode);
+ RewindTest::setup_cluster($test_mode, 1);
RewindTest::start_master();
# Create a test table and insert a row in master.
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 59df6fd88f..54f54fb0a4 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -79,7 +79,7 @@ main(int argc, char **argv)
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_upgrade"));
- /* Ensure that all files created by pg_upgrade are non-world-readable */
+ /* Set default restrictive mask until new cluster permissions are read */
umask(PG_MODE_MASK_DEFAULT);
parseCommandLine(argc, argv);
@@ -100,6 +100,9 @@ main(int argc, char **argv)
check_cluster_compatibility(live_check);
+ /* Set mask based on new PGDATA permissions */
+ umask(DataDirectoryMask(new_cluster.pgdata));
+
check_and_dump_old_cluster(live_check);
diff --git a/src/bin/pg_upgrade/test.sh b/src/bin/pg_upgrade/test.sh
index 4e8db4aaf4..24631a91c6 100644
--- a/src/bin/pg_upgrade/test.sh
+++ b/src/bin/pg_upgrade/test.sh
@@ -20,9 +20,9 @@ unset MAKELEVEL
# Run a given "initdb" binary and overlay the regression testing
# authentication configuration.
standard_initdb() {
- # To increase coverage of non-standard segment size without
- # increase test runtime, run these tests with a lower setting.
- "$1" -N --wal-segsize 1
+ # To increase coverage of non-standard segment size and group access
+ # without increasing test runtime, run these tests with a custom setting.
+ "$1" -N --wal-segsize 1 -g
if [ -n "$TEMP_CONFIG" -a -r "$TEMP_CONFIG" ]
then
cat "$TEMP_CONFIG" >> "$PGDATA/postgresql.conf"
@@ -231,13 +231,13 @@ standard_initdb 'initdb'
pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "${PGDATA}" -b "$oldbindir" -B "$bindir" -p "$PGPORT" -P "$PGPORT"
# make sure all directories and files have group permissions
-if [ $(find ${PGDATA} -type f ! -perm 600 | wc -l) -ne 0 ]; then
- echo "files in PGDATA with permission != 600";
+if [ $(find ${PGDATA} -type f ! -perm 640 | wc -l) -ne 0 ]; then
+ echo "files in PGDATA with permission != 640";
exit 1;
fi
-if [ $(find ${PGDATA} -type d ! -perm 700 | wc -l) -ne 0 ]; then
- echo "directories in PGDATA with permission != 700";
+if [ $(find ${PGDATA} -type d ! -perm 750 | wc -l) -ne 0 ]; then
+ echo "directories in PGDATA with permission != 750";
exit 1;
fi
diff --git a/src/common/Makefile b/src/common/Makefile
index 80e78d72fe..504375bef4 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -51,7 +51,7 @@ else
OBJS_COMMON += sha2.o
endif
-OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o file_utils.o restricted_token.o
+OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o file_perm.o file_utils.o restricted_token.o
OBJS_SRV = $(OBJS_COMMON:%.o=%_srv.o)
diff --git a/src/common/file_perm.c b/src/common/file_perm.c
new file mode 100644
index 0000000000..f01d43725e
--- /dev/null
+++ b/src/common/file_perm.c
@@ -0,0 +1,48 @@
+/*-------------------------------------------------------------------------
+ *
+ * File and directory permission routines
+ *
+ * Group read/execute may optional be enabled on PGDATA so any frontend tools
+ * That write into PGDATA must know what mask to set and the permissions to
+ * use for creating files and directories.
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/file_perm.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#error "This file is not expected to be compiled for backend code"
+#endif
+
+#include <sys/stat.h>
+
+#include "common/file_perm.h"
+
+/*
+ * Determine the mask to use when writing to PGDATA.
+ *
+ * Errors are not handled here and should be checked by the frontend
+ * application.
+ */
+mode_t
+DataDirectoryMask(const char *dataDir)
+{
+ struct stat statBuf;
+
+ /*
+ * If an error occurs getting the mode then return the more restrictive mask.
+ * It is the reponsibility of the frontend application to generate an error.
+ */
+ if (stat(dataDir, &statBuf) != 0)
+ return PG_MODE_MASK_DEFAULT;
+
+ /*
+ * Construct the mask that the caller should pass to umask().
+ */
+ return PG_MODE_MASK_DEFAULT & ~statBuf.st_mode;
+}
diff --git a/src/include/common/file_perm.h b/src/include/common/file_perm.h
index 3c6da0e000..35dcbe6108 100644
--- a/src/include/common/file_perm.h
+++ b/src/include/common/file_perm.h
@@ -1,6 +1,10 @@
/*-------------------------------------------------------------------------
*
- * File and directory permission constants
+ * File and directory permission constants and routines
+ *
+ * Group read/execute may optional be enabled on PGDATA so any frontend tools
+ * That write into PGDATA must know what mask to set and the permissions to
+ * use for creating files and directories.
*
*
* Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
@@ -20,13 +24,30 @@
#define PG_MODE_MASK_DEFAULT (S_IRWXG | S_IRWXO)
/*
- * Default mode for created files.
+ * Optional mode mask for data directory permissions that allows group
+ * read/execute.
+ */
+#define PG_MODE_MASK_ALLOW_GROUP (S_IWGRP | S_IRWXO)
+
+/*
+ * Default mode for created files, unless something else is specified using
+ * the *Perm() function variants.
+ */
+#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR | S_IRGRP)
+
+/*
+ * Default mode for directories created with MakeDirectoryDefaultPerm().
*/
-#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR)
+#define PG_DIR_MODE_DEFAULT (S_IRWXU | S_IRGRP | S_IXGRP)
+
+#ifdef FRONTEND
/*
- * Default mode for directories.
+ * Determine the mask to use when writing to PGDATA
*/
-#define PG_DIR_MODE_DEFAULT S_IRWXU
+mode_t DataDirectoryMask(const char *dataDir);
+
+#endif /* FRONTEND */
+
#endif /* FILE_PERM_H */
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 76e571b98c..8e9c6c11f0 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -269,6 +269,20 @@ sub connstr
=pod
+=item $node->group_access()
+
+Does the data dir allow group access?
+
+=cut
+
+sub group_access
+{
+ my ($self) = @_;
+ return $self->{_group_access};
+}
+
+=pod
+
=item $node->data_dir()
Returns the path to the data directory. postgresql.conf and pg_hba.conf are
@@ -406,10 +420,16 @@ sub init
$params{allows_streaming} = 0 unless defined $params{allows_streaming};
$params{has_archiving} = 0 unless defined $params{has_archiving};
+ $params{has_group_access} = 0 unless defined $params{has_group_access};
mkdir $self->backup_dir;
mkdir $self->archive_dir;
+ if ($params{has_group_access})
+ {
+ push(@{$params{extra}}, '-g');
+ }
+
TestLib::system_or_bail('initdb', '-D', $pgdata, '-A', 'trust', '-N',
@{ $params{extra} });
TestLib::system_or_bail($ENV{PG_REGRESS}, '--config-auth', $pgdata);
@@ -460,8 +480,12 @@ sub init
}
close $conf;
+ chmod($params{has_group_access} ? 0640 : 0600, "$pgdata/postgresql.conf")
+ or die("unable to set permissions for $pgdata/postgresql.conf");
+
$self->set_replication_conf if $params{allows_streaming};
$self->enable_archiving if $params{has_archiving};
+ $self->{_group_access} = 1 if $params{has_group_access};
}
=pod
@@ -485,7 +509,7 @@ sub append_conf
TestLib::append_to_file($conffile, $str . "\n");
- chmod(0600, $conffile)
+ chmod($self->group_access() ? 0640 : 0600, $conffile)
or die("unable to set permissions for $conffile");
}
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index 21f4a9c55e..3d404eefac 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -30,6 +30,7 @@ our @EXPORT = qw(
slurp_file
append_to_file
check_pg_data_perm
+ add_pg_data_group_perm
check_pg_config
system_or_bail
system_log
@@ -226,18 +227,19 @@ sub append_to_file
close $fh;
}
-# Ensure all permissions in the pg_data directory are correct. Permissions
-# should be dir = 0700, file = 0600.
+# Ensure all permissions in the pg_data directory are correct. When allow_group
+# is true then permissions should be dir = 0750, file = 0640. When allow_group
+# is false then permissions should be dir = 0700, file = 0600.
sub check_pg_data_perm
{
- my ($pgdata) = @_;
+ my ($pgdata, $allow_group) = @_;
# Result defaults to true
my $result = 1;
# Expected permission
- my $expected_file_perm = 0600;
- my $expected_dir_perm = 0700;
+ my $expected_file_perm = $allow_group ? 0640 : 0600;
+ my $expected_dir_perm = $allow_group ? 0750 : 0700;
find
(
@@ -302,6 +304,29 @@ sub check_pg_data_perm
return $result;
}
+# Add group permissions to a PGDATA directory
+sub add_pg_data_group_perm
+{
+ my ($pgdata) = @_;
+
+ find
+ (
+ sub
+ {
+ my $file_stat = stat($File::Find::name);
+
+ defined($file_stat)
+ or die("unable to stat $file_stat");
+
+ chmod(S_IMODE($file_stat->mode) |
+ (S_ISDIR($file_stat->mode) ? 0050 : 0040),
+ $File::Find::name)
+ or die "unable to chmod $File::Find::name";
+ },
+ $pgdata
+ );
+}
+
# Check presence of a given regexp within pg_config.h for the installation
# where tests are running, returning a match status result depending on
# that.
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 72976f44d8..78d4934ec6 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -125,7 +125,7 @@ sub mkvcbuild
}
our @pgcommonfrontendfiles = (
- @pgcommonallfiles, qw(fe_memutils.c file_utils.c
+ @pgcommonallfiles, qw(fe_memutils.c file_perm.c file_utils.c
restricted_token.c));
our @pgcommonbkndfiles = @pgcommonallfiles;
--
2.14.3 (Apple Git-98)
On 3/1/18 11:18 PM, Michael Paquier wrote:
Based on my recent lookup at code level for this feature, the patch for
pg_resetwal (which could have been discussed on its own thread as well),
would be fine for commit. The thing could be extended a bit more but
there is nothing opposing even a basic test suite to be in.
There are no core changes, so it doesn't seem like the tests can hurt
anything.
Then you
have a set of refactoring patches, which still need some work.
New patches posted today, hopefully those address most of your concerns.
And
finally there is a rather invasive patch on top of the whole thing.
I'm not sure if I would call it invasive since it's an optional feature
that is off by default. Honestly, I think the refactor in 02 is more
likely to cause problems even if the goal there is *not* to change the
behavior.
The
refactoring work shows much more value only after the main feature is
in, still I think that unifying the default permissions allowed for
files and directories, as well as mkdir() calls has some value in
itself to think it as an mergeable, independent, change.
I agree.
I think that
it would be hard to get the whole patch set into the tree by the end of
the CF though
I hope it does make it, it's a pretty big win for security.
but cutting the refactoring pieces would be doable. At
least it would provide some base for integration in v12. And the
refactoring patch has some pieces that would be helpful for TAP tests as
well.
I've gone pretty big on tests in this patch because I recognize it is a
pretty fundamental behavior change.
Thanks,
--
-David
david@pgmasters.net
David Steele <david@pgmasters.net> writes:
On 2/28/18 2:28 AM, Michael Paquier wrote:
That's basically a recursive chmod, so chmod_recursive is more adapted?
I could imagine that this is useful as well for removing group
permissions, so the new mode could be specified as an argument.
The required package (File::chmod::Recursive) for chmod_recursive is not
in use anywhere else and was not installed when I installed build
dependencies.
I'm not sure what the protocol for introducing a new Perl module is? I
couldn't find packages for the major OSes. Are we OK with using CPAN?
I don't think that's cool. Anything that's not part of a standard Perl
installation is a bit of a lift already, and if it's not packaged by
major distros then it's really a problem for many people. (Yeah, they
may know what CPAN is, but they might have local policy issues about
installing directly from there.)
regards, tom lane
Hi Tom,
On 3/5/18 5:11 PM, Tom Lane wrote:
David Steele <david@pgmasters.net> writes:
On 2/28/18 2:28 AM, Michael Paquier wrote:
That's basically a recursive chmod, so chmod_recursive is more adapted?
I could imagine that this is useful as well for removing group
permissions, so the new mode could be specified as an argument.The required package (File::chmod::Recursive) for chmod_recursive is not
in use anywhere else and was not installed when I installed build
dependencies.I'm not sure what the protocol for introducing a new Perl module is? I
couldn't find packages for the major OSes. Are we OK with using CPAN?I don't think that's cool. Anything that's not part of a standard Perl
installation is a bit of a lift already, and if it's not packaged by
major distros then it's really a problem for many people. (Yeah, they
may know what CPAN is, but they might have local policy issues about
installing directly from there.)
This is my view as well. I don't think CPAN should ever be on a
production box, mostly because of all the other tools that need to be
installed to make it work.
It's a little different here, because these packages are only used for
testing/development.
Of course, maybe I have this wrong and Michael will point out my error.
If not, I'm happy to rework the function (about 15 lines) to be more
generic.
--
-David
david@pgmasters.net
Tom, all,
* Tom Lane (tgl@sss.pgh.pa.us) wrote:
David Steele <david@pgmasters.net> writes:
On 2/28/18 2:28 AM, Michael Paquier wrote:
That's basically a recursive chmod, so chmod_recursive is more adapted?
I could imagine that this is useful as well for removing group
permissions, so the new mode could be specified as an argument.The required package (File::chmod::Recursive) for chmod_recursive is not
in use anywhere else and was not installed when I installed build
dependencies.I'm not sure what the protocol for introducing a new Perl module is? I
couldn't find packages for the major OSes. Are we OK with using CPAN?I don't think that's cool. Anything that's not part of a standard Perl
installation is a bit of a lift already, and if it's not packaged by
major distros then it's really a problem for many people. (Yeah, they
may know what CPAN is, but they might have local policy issues about
installing directly from there.)
Agreed.
Thanks!
Stephen
David Steele <david@pgmasters.net> writes:
On 3/5/18 5:11 PM, Tom Lane wrote:
David Steele <david@pgmasters.net> writes:
I'm not sure what the protocol for introducing a new Perl module is? I
couldn't find packages for the major OSes. Are we OK with using CPAN?
I don't think that's cool. Anything that's not part of a standard Perl
installation is a bit of a lift already, and if it's not packaged by
major distros then it's really a problem for many people. (Yeah, they
may know what CPAN is, but they might have local policy issues about
installing directly from there.)
It's a little different here, because these packages are only used for
testing/development.
True, but if we want this test to be part of either check-world or the
buildfarm run, that's still a lot of people we're asking to install
the extra module. If we're talking about saving just a few dozen
lines of code, it ain't worth it.
regards, tom lane
On 3/5/18 5:51 PM, Tom Lane wrote:
David Steele <david@pgmasters.net> writes:
On 3/5/18 5:11 PM, Tom Lane wrote:
David Steele <david@pgmasters.net> writes:
I'm not sure what the protocol for introducing a new Perl module is? I
couldn't find packages for the major OSes. Are we OK with using CPAN?I don't think that's cool. Anything that's not part of a standard Perl
installation is a bit of a lift already, and if it's not packaged by
major distros then it's really a problem for many people. (Yeah, they
may know what CPAN is, but they might have local policy issues about
installing directly from there.)It's a little different here, because these packages are only used for
testing/development.True, but if we want this test to be part of either check-world or the
buildfarm run, that's still a lot of people we're asking to install
the extra module. If we're talking about saving just a few dozen
lines of code, it ain't worth it.
+1.
--
-David
david@pgmasters.net
On Mon, Mar 05, 2018 at 05:11:29PM -0500, Tom Lane wrote:
David Steele <david@pgmasters.net> writes:
On 2/28/18 2:28 AM, Michael Paquier wrote:
That's basically a recursive chmod, so chmod_recursive is more adapted?
I could imagine that this is useful as well for removing group
permissions, so the new mode could be specified as an argument.The required package (File::chmod::Recursive) for chmod_recursive is not
in use anywhere else and was not installed when I installed build
dependencies.
Woah. I didn't even know that chmod_recursive existed and was part of a
module. What I commented about here was to rename to a more generic
name the routine you are implementing so as other tests could use it.
I'm not sure what the protocol for introducing a new Perl module is? I
couldn't find packages for the major OSes. Are we OK with using CPAN?I don't think that's cool. Anything that's not part of a standard Perl
installation is a bit of a lift already, and if it's not packaged by
major distros then it's really a problem for many people. (Yeah, they
may know what CPAN is, but they might have local policy issues about
installing directly from there.)
Yes, that's not cool. I am not pushing in this direction. Sorry for
creating confusion with fuzzy wording.
--
Michael
On 3/5/18 8:03 PM, Michael Paquier wrote:
On Mon, Mar 05, 2018 at 05:11:29PM -0500, Tom Lane wrote:
David Steele <david@pgmasters.net> writes:
On 2/28/18 2:28 AM, Michael Paquier wrote:
That's basically a recursive chmod, so chmod_recursive is more adapted?
I could imagine that this is useful as well for removing group
permissions, so the new mode could be specified as an argument.The required package (File::chmod::Recursive) for chmod_recursive is not
in use anywhere else and was not installed when I installed build
dependencies.Woah. I didn't even know that chmod_recursive existed and was part of a
module. What I commented about here was to rename to a more generic
name the routine you are implementing so as other tests could use it.
OK, that is pretty funny. I thought you were directing me to a Perl
function I hadn't heard of (but did exist).
I'm not sure what the protocol for introducing a new Perl module is? I
couldn't find packages for the major OSes. Are we OK with using CPAN?I don't think that's cool. Anything that's not part of a standard Perl
installation is a bit of a lift already, and if it's not packaged by
major distros then it's really a problem for many people. (Yeah, they
may know what CPAN is, but they might have local policy issues about
installing directly from there.)Yes, that's not cool. I am not pushing in this direction. Sorry for
creating confusion with fuzzy wording.
No worries, I'll just make it more generic as suggested.
--
-David
david@pgmasters.net
On Mon, Mar 05, 2018 at 03:07:20PM -0500, David Steele wrote:
On 2/28/18 2:28 AM, Michael Paquier wrote:
On Tue, Feb 27, 2018 at 03:52:32PM -0500, David Steele wrote:
I don't quite understand here. I have no objection into extending
setup_cluster() with a group_access argument so as the tests run both
the group access and without it, but I'd rather keep the low-level API
clean of anything fancy if we can use the facility which already
exists.I'm not sure what you mean by, "facility that already exists". I think
I implemented this the same as the allows_streaming and has_archiving
flags. The only difference is that this option must be set at initdb
time rather than in postgresql.conf.Could you be more specific?
Let's remove has_group_access from PostgresNode::init and instead use
existing parameter called "extra". In your patch, this setup:
has_group_access => 1
is equivalent to that:
extra => [ -g ]
You can also guess the value of has_group_access by parsing the
arguments from the array "extra".
I think I prefer grouping 1 and 2. It produces less churn, as you say,
and I don't think MakeDirectoryDefaultPerm really needs its own patch.
Based on comments so far, nobody has an objection to it.In theory, the first two patches could be applied just for refactoring
without adding group permissions at all. There are a lot of new tests
to make sure permissions are set correctly so it seems like a win
right off.
Okay, that's fine for me.
check_pg_data_perm() looks useful even without $allow_group even if the
grouping facility is reverted at the end. S_ISLNK should be considered
as well for tablespaces or symlinks of pg_wal? We have such cases in
the regression tests.Hmmm, the link is just pointing to a directory whose permissions have
been changed. Why do we need to worry about the link?
Oh, perhaps I misread your code here, but this ignores symlinks, for
example take an instance where pg_wal is symlinked, we'd likely want to
make sure that at least archive_status is using a correct set of
permissions, no? There is a "follow" option in File::Find which could
help.
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
- {
- fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
- progname, path, strerror(errno));
- exit_nicely();
- }
Hm. Why are those removed?When I started working on this, pg_upgrade did not set umask and instead
did chmod for each dir created. umask() has since been added (even
before my patch) and so these are now noops. Seemed easier to remove
them than to change them all.setup_signals();
- umask(S_IRWXG | S_IRWXO);
-
create_data_directory();
This should not be moved around.Hmmm - I moved it much earlier in the process which seems like a good
thing. Consider if there was a option to fixup permissions, like there
is to fsync. Isn't it best to set the mode as soon as possible to
prevent code from being inserted before it?
Those two are separate issues. Could you begin a new thread on the
matter? This will attract more attention.
The indentation in RewindTest.pm is a bit wrong.
- if (chmod(location, S_IRWXU) != 0)
+ current_umask = umask(0);
+ umask(current_umask);
[...]
- if (chmod(pg_data, S_IRWXU) != 0)
+ if (chmod(pg_data, PG_DIR_MODE_DEFAULT & ~file_mode_mask) != 0)
Such modifications should be part of patch 3, not 2, where the group
features really apply.
my $test_mode = shift;
+ umask(0077);
+
RewindTest::setup_cluster($test_mode);
What's that for? A comment would be welcome.
+# make sure all directories and files have group permissions
+if [ $(find ${PGDATA} -type f ! -perm 600 | wc -l) -ne 0 ]; then
+ echo "files in PGDATA with permission != 600";
+ exit 1;
+fi
Perhaps on hold if we are able to move pg_upgrade tests to a TAP
suite... We'll see what happens.
Perhaps the tests should be cut into a separate patch? Those are not
directly related to the refactoring done in patch 2.
Patch 2 begins to look fine for me. I still need to look at patch 3 in
more details.
--
Michael
On 3/5/18 10:46 PM, Michael Paquier wrote:
On Mon, Mar 05, 2018 at 03:07:20PM -0500, David Steele wrote:
On 2/28/18 2:28 AM, Michael Paquier wrote:
On Tue, Feb 27, 2018 at 03:52:32PM -0500, David Steele wrote:
I don't quite understand here. I have no objection into extending
setup_cluster() with a group_access argument so as the tests run both
the group access and without it, but I'd rather keep the low-level API
clean of anything fancy if we can use the facility which already
exists.I'm not sure what you mean by, "facility that already exists". I think
I implemented this the same as the allows_streaming and has_archiving
flags. The only difference is that this option must be set at initdb
time rather than in postgresql.conf.Could you be more specific?
Let's remove has_group_access from PostgresNode::init and instead use
existing parameter called "extra". In your patch, this setup:
has_group_access => 1
is equivalent to that:
extra => [ -g ]
You can also guess the value of has_group_access by parsing the
arguments from the array "extra".
OK. extra is used to set -g and the group_access() function checks
pgdata directly since this can change after the cluster is created.
check_pg_data_perm() looks useful even without $allow_group even if the
grouping facility is reverted at the end. S_ISLNK should be considered
as well for tablespaces or symlinks of pg_wal? We have such cases in
the regression tests.Hmmm, the link is just pointing to a directory whose permissions have
been changed. Why do we need to worry about the link?Oh, perhaps I misread your code here, but this ignores symlinks, for
example take an instance where pg_wal is symlinked, we'd likely want to
make sure that at least archive_status is using a correct set of
permissions, no? There is a "follow" option in File::Find which could
help.
Ah, I see what you mean now. Fixed using follow_fast. This variant
claims to be faster and it doesn't matter if files are occasionally
checked twice.
setup_signals();
- umask(S_IRWXG | S_IRWXO);
-
create_data_directory();
This should not be moved around.Hmmm - I moved it much earlier in the process which seems like a good
thing. Consider if there was a option to fixup permissions, like there
is to fsync. Isn't it best to set the mode as soon as possible to
prevent code from being inserted before it?Those two are separate issues. Could you begin a new thread on the
matter? This will attract more attention.
OK, I'll move it back for now, and make a note to raise the position as
a separate issue. I'd rather not do it in this fest, though.
The indentation in RewindTest.pm is a bit wrong.
Fixed.
- if (chmod(location, S_IRWXU) != 0) + current_umask = umask(0); + umask(current_umask); [...] - if (chmod(pg_data, S_IRWXU) != 0) + if (chmod(pg_data, PG_DIR_MODE_DEFAULT & ~file_mode_mask) != 0) Such modifications should be part of patch 3, not 2, where the group features really apply.
The goal of patch 2 is to refactor the way permissions are set by
replacing locally hard-coded values with centralized values, so I think
this makes sense here.
my $test_mode = shift;
+ umask(0077);
Added. (Ensure that all files are created with default data dir
permissions).
RewindTest::setup_cluster($test_mode);
What's that for? A comment would be welcome.
Added. (Used to differentiate clusters).
Perhaps the tests should be cut into a separate patch?
I prefer tests to be in the same patch as the code they test.
Those are not
directly related to the refactoring done in patch 2.
The point of the tests is to be sure there are no regressions in the
refactor so they seem directly related to me. Also, the tests
themselves were not to good about keeping permissions consistent.
Patch 2 begins to look fine for me.
I also made chmod_recursive() generic.
New patches are attached.
Thanks!
--
-David
david@pgmasters.net
Attachments:
group-access-v9-01-pgresetwal-test.patchtext/plain; charset=UTF-8; name=group-access-v9-01-pgresetwal-test.patch; x-mac-creator=0; x-mac-type=0Download
From 2caecfef0cb0b029c439786fe462d69b5caaf67b Mon Sep 17 00:00:00 2001
From: David Steele <david@pgmasters.net>
Date: Mon, 5 Mar 2018 14:33:10 -0500
Subject: [PATCH 1/3] pg_resetwal tests.
Adds a very basic test suite for pg_resetwal.
---
src/bin/pg_resetwal/.gitignore | 1 +
src/bin/pg_resetwal/Makefile | 6 +++++
src/bin/pg_resetwal/t/001_basic.pl | 53 ++++++++++++++++++++++++++++++++++++++
3 files changed, 60 insertions(+)
create mode 100644 src/bin/pg_resetwal/t/001_basic.pl
diff --git a/src/bin/pg_resetwal/.gitignore b/src/bin/pg_resetwal/.gitignore
index 236abb4323..a950255fd7 100644
--- a/src/bin/pg_resetwal/.gitignore
+++ b/src/bin/pg_resetwal/.gitignore
@@ -1 +1,2 @@
/pg_resetwal
+/tmp_check
diff --git a/src/bin/pg_resetwal/Makefile b/src/bin/pg_resetwal/Makefile
index 5ab7ad33e0..13c9ca6279 100644
--- a/src/bin/pg_resetwal/Makefile
+++ b/src/bin/pg_resetwal/Makefile
@@ -33,3 +33,9 @@ uninstall:
clean distclean maintainer-clean:
rm -f pg_resetwal$(X) $(OBJS)
+
+check:
+ $(prove_check)
+
+installcheck:
+ $(prove_installcheck)
diff --git a/src/bin/pg_resetwal/t/001_basic.pl b/src/bin/pg_resetwal/t/001_basic.pl
new file mode 100644
index 0000000000..234bd70303
--- /dev/null
+++ b/src/bin/pg_resetwal/t/001_basic.pl
@@ -0,0 +1,53 @@
+use strict;
+use warnings;
+
+use Config;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 13;
+
+my $tempdir = TestLib::tempdir;
+my $tempdir_short = TestLib::tempdir_short;
+
+program_help_ok('pg_resetwal');
+program_version_ok('pg_resetwal');
+program_options_handling_ok('pg_resetwal');
+
+# Initialize node without replication settings
+my $node = get_new_node('main');
+my $pgdata = $node->data_dir;
+my $pgwal = "$pgdata/pg_wal";
+$node->init;
+$node->start;
+
+# Remove WAL from pg_wal and make sure it gets rebuilt
+$node->stop;
+
+unlink("$pgwal/000000010000000000000001") == 1
+ or die("unable to remove 000000010000000000000001");
+
+is_deeply(
+ [sort(slurp_dir($pgwal))], [sort(qw(. .. archive_status))], 'no WAL');
+
+$node->command_ok(['pg_resetwal', '-D', $pgdata], 'recreate pg_wal');
+
+is_deeply(
+ [sort(slurp_dir($pgwal))],
+ [sort(qw(. .. archive_status 000000010000000000000002))],
+ 'WAL recreated');
+
+$node->start;
+
+# Reset to specific WAL segment
+$node->stop;
+
+$node->command_ok(
+ ['pg_resetwal', '-l', '000000070000000700000007', '-D', $pgdata],
+ 'set to specific WAL');
+
+is_deeply(
+ [sort(slurp_dir($pgwal))],
+ [sort(qw(. .. archive_status 000000070000000700000007))],
+ 'WAL recreated');
+
+$node->start;
--
2.14.3 (Apple Git-98)
group-access-v9-02-file-perm.patchtext/plain; charset=UTF-8; name=group-access-v9-02-file-perm.patch; x-mac-creator=0; x-mac-type=0Download
From bff3a74ef82df71cc3b0811ee39582a27a0577c0 Mon Sep 17 00:00:00 2001
From: David Steele <david@pgmasters.net>
Date: Tue, 6 Mar 2018 13:03:32 -0500
Subject: [PATCH 2/3] Refactor file permissions in backend/frontend
Adds a new header (file_perm.h) and makes all front-end utilities use
the new constants for setting umask. Converts mkdir() calls in the
backend to MakeDirectoryDefaultPerm() if the original
call used default permissions. Adds tests to make sure permissions in
PGDATA are set correctly by the front-end tools.
---
src/backend/access/transam/xlog.c | 2 +-
src/backend/commands/tablespace.c | 22 +++++---
src/backend/postmaster/postmaster.c | 2 +-
src/backend/postmaster/syslogger.c | 5 +-
src/backend/replication/slot.c | 5 +-
src/backend/storage/file/copydir.c | 2 +-
src/backend/storage/file/fd.c | 32 +++++++-----
src/backend/storage/ipc/ipc.c | 4 ++
src/bin/initdb/initdb.c | 41 ++++-----------
src/bin/initdb/t/001_initdb.pl | 4 +-
src/bin/pg_ctl/pg_ctl.c | 3 +-
src/bin/pg_ctl/t/001_start_stop.pl | 13 +++--
src/bin/pg_resetwal/pg_resetwal.c | 8 ++-
src/bin/pg_resetwal/t/001_basic.pl | 4 +-
src/bin/pg_rewind/RewindTest.pm | 5 ++
src/bin/pg_rewind/file_ops.c | 7 +--
src/bin/pg_rewind/pg_rewind.c | 4 ++
src/bin/pg_rewind/t/001_basic.pl | 2 +-
src/bin/pg_rewind/t/002_databases.pl | 2 +-
src/bin/pg_rewind/t/003_extrafiles.pl | 5 +-
src/bin/pg_rewind/t/004_pg_xlog_symlink.pl | 2 +-
src/bin/pg_rewind/t/005_same_timeline.pl | 2 +-
src/bin/pg_upgrade/file.c | 5 +-
src/bin/pg_upgrade/pg_upgrade.c | 3 +-
src/bin/pg_upgrade/test.sh | 11 ++++
src/include/common/file_perm.h | 32 ++++++++++++
src/include/storage/fd.h | 3 ++
src/test/perl/PostgresNode.pm | 3 ++
src/test/perl/TestLib.pm | 82 ++++++++++++++++++++++++++++++
29 files changed, 237 insertions(+), 78 deletions(-)
create mode 100644 src/include/common/file_perm.h
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 47a6c4d895..bf07bbd746 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4086,7 +4086,7 @@ ValidateXLOGDirectoryStructure(void)
{
ereport(LOG,
(errmsg("creating missing WAL directory \"%s\"", path)));
- if (mkdir(path, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(path) < 0)
ereport(FATAL,
(errmsg("could not create missing directory \"%s\": %m",
path)));
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 5c450caa4e..c4ee987446 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -68,6 +68,7 @@
#include "commands/seclabel.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
+#include "common/file_perm.h"
#include "miscadmin.h"
#include "postmaster/bgwriter.h"
#include "storage/fd.h"
@@ -151,7 +152,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
else
{
/* Directory creation failed? */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(dir) < 0)
{
char *parentdir;
@@ -173,7 +174,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
get_parent_directory(parentdir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -184,7 +185,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
parentdir = pstrdup(dir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -192,7 +193,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
pfree(parentdir);
/* Create database directory */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(dir) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -279,7 +280,8 @@ CreateTableSpace(CreateTableSpaceStmt *stmt)
/*
* Check that location isn't too long. Remember that we're going to append
* 'PG_XXX/<dboid>/<relid>_<fork>.<nnn>'. FYI, we never actually
- * reference the whole path here, but mkdir() uses the first two parts.
+ * reference the whole path here, but MakeDirectoryDefaultPerm() uses the first
+ * two parts.
*/
if (strlen(location) + 1 + strlen(TABLESPACE_VERSION_DIRECTORY) + 1 +
OIDCHARS + 1 + OIDCHARS + 1 + FORKNAMECHARS + 1 + OIDCHARS > MAXPGPATH)
@@ -565,6 +567,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
char *linkloc;
char *location_with_version_dir;
struct stat st;
+ mode_t current_umask; /* Current umask value */
linkloc = psprintf("pg_tblspc/%u", tablespaceoid);
location_with_version_dir = psprintf("%s/%s", location,
@@ -574,7 +577,10 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* Attempt to coerce target directory to safe permissions. If this fails,
* it doesn't exist or has the wrong owner.
*/
- if (chmod(location, S_IRWXU) != 0)
+ current_umask = umask(0);
+ umask(current_umask);
+
+ if (chmod(location, PG_DIR_MODE_DEFAULT & ~current_umask) != 0)
{
if (errno == ENOENT)
ereport(ERROR,
@@ -599,7 +605,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
if (stat(location_with_version_dir, &st) == 0 && S_ISDIR(st.st_mode))
{
if (!rmtree(location_with_version_dir, true))
- /* If this failed, mkdir() below is going to error. */
+ /* If this failed, MakeDirectoryDefaultPerm() below is going to error. */
ereport(WARNING,
(errmsg("some useless files may be left behind in old database directory \"%s\"",
location_with_version_dir)));
@@ -610,7 +616,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* The creation of the version directory prevents more than one tablespace
* in a single location.
*/
- if (mkdir(location_with_version_dir, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(location_with_version_dir) < 0)
{
if (errno == EEXIST)
ereport(ERROR,
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index f3ddf828bb..e13a2ae8c4 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -4495,7 +4495,7 @@ internal_forkexec(int argc, char *argv[], Port *port)
* As in OpenTemporaryFileInTablespace, try to make the temp-file
* directory
*/
- mkdir(PG_TEMP_FILES_DIR, S_IRWXU);
+ MakeDirectoryDefaultPerm(PG_TEMP_FILES_DIR);
fp = AllocateFile(tmpfilename, PG_BINARY_W);
if (!fp)
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index f70eea37df..ae23941f59 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -41,6 +41,7 @@
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
#include "storage/dsm.h"
+#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pg_shmem.h"
@@ -322,7 +323,7 @@ SysLoggerMain(int argc, char *argv[])
/*
* Also, create new directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ MakeDirectoryDefaultPerm(Log_directory);
}
if (strcmp(Log_filename, currentLogFilename) != 0)
{
@@ -564,7 +565,7 @@ SysLogger_Start(void)
/*
* Create log directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ MakeDirectoryDefaultPerm(Log_directory);
/*
* The initial logfile is created right in the postmaster, to verify that
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index fc9ef22b0b..3a98360b50 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -1166,13 +1166,14 @@ CreateSlotOnDisk(ReplicationSlot *slot)
* It's just barely possible that some previous effort to create or drop a
* slot with this name left a temp directory lying around. If that seems
* to be the case, try to remove it. If the rmtree() fails, we'll error
- * out at the mkdir() below, so we don't bother checking success.
+ * out at the MakeDirectoryDefaultPerm() below, so we don't bother checking
+ * success.
*/
if (stat(tmppath, &st) == 0 && S_ISDIR(st.st_mode))
rmtree(tmppath, true);
/* Create and fsync the temporary slot directory. */
- if (mkdir(tmppath, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(tmppath) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
diff --git a/src/backend/storage/file/copydir.c b/src/backend/storage/file/copydir.c
index ca6342db0d..ff980dc1f5 100644
--- a/src/backend/storage/file/copydir.c
+++ b/src/backend/storage/file/copydir.c
@@ -41,7 +41,7 @@ copydir(char *fromdir, char *todir, bool recurse)
char fromfile[MAXPGPATH * 2];
char tofile[MAXPGPATH * 2];
- if (mkdir(todir, S_IRWXU) != 0)
+ if (MakeDirectoryDefaultPerm(todir) != 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m", todir)));
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index 2a18e94ff4..1491665375 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -84,6 +84,7 @@
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "catalog/pg_tablespace.h"
+#include "common/file_perm.h"
#include "pgstat.h"
#include "portability/mem.h"
#include "storage/fd.h"
@@ -124,12 +125,6 @@
*/
#define FD_MINFREE 10
-/*
- * Default mode for created files, unless something else is specified using
- * the *Perm() function variants.
- */
-#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR)
-
/*
* A number of platforms allow individual processes to open many more files
* than they can really support when *many* processes do the same thing.
@@ -1435,7 +1430,7 @@ PathNameOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode)
void
PathNameCreateTemporaryDir(const char *basedir, const char *directory)
{
- if (mkdir(directory, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(directory) < 0)
{
if (errno == EEXIST)
return;
@@ -1445,14 +1440,14 @@ PathNameCreateTemporaryDir(const char *basedir, const char *directory)
* EEXIST to close a race against another process following the same
* algorithm.
*/
- if (mkdir(basedir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(basedir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary directory \"%s\": %m",
basedir)));
/* Try again. */
- if (mkdir(directory, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(directory) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary subdirectory \"%s\": %m",
@@ -1602,11 +1597,11 @@ OpenTemporaryFileInTablespace(Oid tblspcOid, bool rejectError)
* We might need to create the tablespace's tempfile directory, if no
* one has yet done so.
*
- * Don't check for error from mkdir; it could fail if someone else
- * just did the same thing. If it doesn't work then we'll bomb out on
+ * Don't check error from MakeDirectoryDefaultPerm; it could fail if someone
+ * else just did the same thing. If it doesn't work then we'll bomb out on
* the second create attempt, instead.
*/
- mkdir(tempdirpath, S_IRWXU);
+ MakeDirectoryDefaultPerm(tempdirpath);
file = PathNameOpenFile(tempfilepath,
O_RDWR | O_CREAT | O_TRUNC | PG_BINARY);
@@ -3555,3 +3550,16 @@ fsync_parent_path(const char *fname, int elevel)
return 0;
}
+
+/*
+ * Create a directory using PG_DIR_MODE_DEFAULT for permissions
+ *
+ * Directories in PGDATA should normally have specific permissions -- when
+ * using this function the caller does not need to know what they should be.
+ * For permissions other than the default use mkdir() directly.
+ */
+int
+MakeDirectoryDefaultPerm(const char *directoryName)
+{
+ return mkdir(directoryName, PG_DIR_MODE_DEFAULT);
+}
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index 726db7b7f1..466613e4af 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -137,6 +137,10 @@ proc_exit(int code)
else
snprintf(gprofDirName, 32, "gprof/%d", (int) getpid());
+ /*
+ * Use mkdir() instead of MakeDirectoryDefaultPerm() here because non-default
+ * permissions are required.
+ */
mkdir("gprof", S_IRWXU | S_IRWXG | S_IRWXO);
mkdir(gprofDirName, S_IRWXU | S_IRWXG | S_IRWXO);
chdir(gprofDirName);
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 2efd3b75b1..f8420af2a9 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -64,6 +64,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_class.h"
#include "catalog/pg_collation.h"
+#include "common/file_perm.h"
#include "common/file_utils.h"
#include "common/restricted_token.h"
#include "common/username.h"
@@ -144,6 +145,7 @@ static bool data_checksums = false;
static char *xlog_dir = NULL;
static char *str_wal_segment_size_mb = NULL;
static int wal_segment_size_mb;
+static mode_t file_mode_mask = PG_MODE_MASK_DEFAULT;
/* internal vars */
@@ -1170,12 +1172,6 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
- {
- fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
- progname, path, strerror(errno));
- exit_nicely();
- }
/*
* create the automatic configuration file to store the configuration
@@ -1190,12 +1186,6 @@ setup_config(void)
sprintf(path, "%s/postgresql.auto.conf", pg_data);
writefile(path, autoconflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
- {
- fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
- progname, path, strerror(errno));
- exit_nicely();
- }
free(conflines);
@@ -1277,12 +1267,6 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_hba.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
- {
- fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
- progname, path, strerror(errno));
- exit_nicely();
- }
free(conflines);
@@ -1293,12 +1277,6 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_ident.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
- {
- fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
- progname, path, strerror(errno));
- exit_nicely();
- }
free(conflines);
@@ -2692,7 +2670,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (pg_mkdir_p(pg_data, S_IRWXU) != 0)
+ if (pg_mkdir_p(pg_data, PG_DIR_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2710,7 +2688,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (chmod(pg_data, S_IRWXU) != 0)
+ if (chmod(pg_data, PG_DIR_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2778,7 +2756,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (pg_mkdir_p(xlog_dir, S_IRWXU) != 0)
+ if (pg_mkdir_p(xlog_dir, PG_DIR_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2796,7 +2774,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (chmod(xlog_dir, S_IRWXU) != 0)
+ if (chmod(xlog_dir, PG_DIR_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2846,7 +2824,7 @@ create_xlog_or_symlink(void)
else
{
/* Without -X option, just make the subdirectory normally */
- if (mkdir(subdirloc, S_IRWXU) < 0)
+ if (mkdir(subdirloc, PG_DIR_MODE_DEFAULT) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, subdirloc, strerror(errno));
@@ -2882,7 +2860,8 @@ initialize_data_directory(void)
setup_signals();
- umask(S_IRWXG | S_IRWXO);
+ /* Set the file mode creation mask */
+ umask(file_mode_mask);
create_data_directory();
@@ -2902,7 +2881,7 @@ initialize_data_directory(void)
* The parent directory already exists, so we only need mkdir() not
* pg_mkdir_p() here, which avoids some failure modes; cf bug #13853.
*/
- if (mkdir(path, S_IRWXU) < 0)
+ if (mkdir(path, PG_DIR_MODE_DEFAULT) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, path, strerror(errno));
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index c0cfa6e92c..561608f1d8 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 15;
+use Test::More tests => 16;
my $tempdir = TestLib::tempdir;
my $xlogdir = "$tempdir/pgxlog";
@@ -45,6 +45,8 @@ mkdir $datadir;
command_ok([ 'initdb', '-N', '-T', 'german', '-X', $xlogdir, $datadir ],
'successful creation');
+
+ ok(check_pg_data_perm($datadir, 0));
}
command_ok([ 'initdb', '-S', $datadir ], 'sync only');
command_fails([ 'initdb', $datadir ], 'existing data directory');
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index 9bc830b085..1eef7179cc 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -25,6 +25,7 @@
#include "catalog/pg_control.h"
#include "common/controldata_utils.h"
+#include "common/file_perm.h"
#include "getopt_long.h"
#include "utils/pidfile.h"
@@ -2170,7 +2171,7 @@ main(int argc, char **argv)
*/
argv0 = argv[0];
- umask(S_IRWXG | S_IRWXO);
+ umask(PG_MODE_MASK_DEFAULT);
/* support --help and --version even if invoked as root */
if (argc > 1)
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl
index 5da4746cb4..909bf4d465 100644
--- a/src/bin/pg_ctl/t/001_start_stop.pl
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -4,7 +4,7 @@ use warnings;
use Config;
use PostgresNode;
use TestLib;
-use Test::More tests => 19;
+use Test::More tests => 20;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -57,10 +57,15 @@ command_ok([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ], 'pg_ctl stop');
command_fails([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ],
'second pg_ctl stop fails');
+# Log file for first perm test
+my $logFileName = "$tempdir/data/perm-test-600.log";
+
command_ok(
- [ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
+ [ 'pg_ctl', 'restart', '-D', "$tempdir/data", '-l', $logFileName ],
'pg_ctl restart with server not running');
-command_ok([ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
- 'pg_ctl restart with server running');
+
+# Log file should exist and have no group permissions
+ok(-f $logFileName);
+ok(check_pg_data_perm("$tempdir/data", 0));
system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index a132cf2e9f..47abd41fc3 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -52,6 +52,7 @@
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
#include "common/fe_memutils.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "storage/large_object.h"
#include "pg_getopt.h"
@@ -327,6 +328,9 @@ main(int argc, char *argv[])
exit(1);
}
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_DEFAULT);
+
/* Check that data directory matches our server version */
CheckDataVersion();
@@ -919,7 +923,7 @@ RewriteControlFile(void)
fd = open(XLOG_CONTROL_FILE,
O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ PG_FILE_MODE_DEFAULT);
if (fd < 0)
{
fprintf(stderr, _("%s: could not create pg_control file: %s\n"),
@@ -1201,7 +1205,7 @@ WriteEmptyXLOG(void)
unlink(path);
fd = open(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ PG_FILE_MODE_DEFAULT);
if (fd < 0)
{
fprintf(stderr, _("%s: could not open file \"%s\": %s\n"),
diff --git a/src/bin/pg_resetwal/t/001_basic.pl b/src/bin/pg_resetwal/t/001_basic.pl
index 234bd70303..db0c2a630a 100644
--- a/src/bin/pg_resetwal/t/001_basic.pl
+++ b/src/bin/pg_resetwal/t/001_basic.pl
@@ -4,7 +4,7 @@ use warnings;
use Config;
use PostgresNode;
use TestLib;
-use Test::More tests => 13;
+use Test::More tests => 14;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -36,6 +36,8 @@ is_deeply(
[sort(qw(. .. archive_status 000000010000000000000002))],
'WAL recreated');
+ok(check_pg_data_perm($pgdata, 0), 'check PGDATA permissions');
+
$node->start;
# Reset to specific WAL segment
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index 00b5b42dd7..cbf4910ffc 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -237,6 +237,9 @@ sub run_pg_rewind
"$tmp_folder/master-postgresql.conf.tmp",
"$master_pgdata/postgresql.conf");
+ chmod(0600, "$master_pgdata/postgresql.conf")
+ or die("unable to set permissions for $master_pgdata/postgresql.conf");
+
# Plug-in rewound node to the now-promoted standby node
my $port_standby = $node_standby->port;
$node_master->append_conf(
@@ -255,6 +258,8 @@ recovery_target_timeline='latest'
# Clean up after the test. Stop both servers, if they're still running.
sub clean_rewind_test
{
+ ok (check_pg_data_perm($node_master->data_dir(), 0));
+
$node_master->teardown_node if defined $node_master;
$node_standby->teardown_node if defined $node_standby;
}
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 705383d184..d51914e901 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -18,6 +18,7 @@
#include <fcntl.h>
#include <unistd.h>
+#include "common/file_perm.h"
#include "file_ops.h"
#include "filemap.h"
#include "logging.h"
@@ -58,7 +59,7 @@ open_target_file(const char *path, bool trunc)
mode = O_WRONLY | O_CREAT | PG_BINARY;
if (trunc)
mode |= O_TRUNC;
- dstfd = open(dstpath, mode, 0600);
+ dstfd = open(dstpath, mode, PG_FILE_MODE_DEFAULT);
if (dstfd < 0)
pg_fatal("could not open target file \"%s\": %s\n",
dstpath, strerror(errno));
@@ -190,7 +191,7 @@ truncate_target_file(const char *path, off_t newsize)
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
- fd = open(dstpath, O_WRONLY, 0);
+ fd = open(dstpath, O_WRONLY, PG_FILE_MODE_DEFAULT);
if (fd < 0)
pg_fatal("could not open file \"%s\" for truncation: %s\n",
dstpath, strerror(errno));
@@ -211,7 +212,7 @@ create_target_dir(const char *path)
return;
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
- if (mkdir(dstpath, S_IRWXU) != 0)
+ if (mkdir(dstpath, PG_DIR_MODE_DEFAULT) != 0)
pg_fatal("could not create directory \"%s\": %s\n",
dstpath, strerror(errno));
}
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 72ab2f8d5e..3d179e2c7d 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -24,6 +24,7 @@
#include "access/xlog_internal.h"
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "getopt_long.h"
#include "storage/bufpage.h"
@@ -185,6 +186,9 @@ main(int argc, char **argv)
exit(1);
}
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_DEFAULT);
+
/*
* Don't allow pg_rewind to be run as root, to avoid overwriting the
* ownership of files in the data directory. We need only check for root
diff --git a/src/bin/pg_rewind/t/001_basic.pl b/src/bin/pg_rewind/t/001_basic.pl
index 736f34eae3..e7fc200fc0 100644
--- a/src/bin/pg_rewind/t/001_basic.pl
+++ b/src/bin/pg_rewind/t/001_basic.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 8;
+use Test::More tests => 10;
use RewindTest;
diff --git a/src/bin/pg_rewind/t/002_databases.pl b/src/bin/pg_rewind/t/002_databases.pl
index 37cdd712f3..0e0140b96f 100644
--- a/src/bin/pg_rewind/t/002_databases.pl
+++ b/src/bin/pg_rewind/t/002_databases.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 6;
use RewindTest;
diff --git a/src/bin/pg_rewind/t/003_extrafiles.pl b/src/bin/pg_rewind/t/003_extrafiles.pl
index 2433a4aab6..e8e8465c01 100644
--- a/src/bin/pg_rewind/t/003_extrafiles.pl
+++ b/src/bin/pg_rewind/t/003_extrafiles.pl
@@ -3,7 +3,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 6;
use File::Find;
@@ -14,6 +14,9 @@ sub run_test
{
my $test_mode = shift;
+ # Ensure that all files are created with default data dir permissions
+ umask(0077);
+
RewindTest::setup_cluster($test_mode);
RewindTest::start_master();
diff --git a/src/bin/pg_rewind/t/004_pg_xlog_symlink.pl b/src/bin/pg_rewind/t/004_pg_xlog_symlink.pl
index feadaa6a0f..70dd061915 100644
--- a/src/bin/pg_rewind/t/004_pg_xlog_symlink.pl
+++ b/src/bin/pg_rewind/t/004_pg_xlog_symlink.pl
@@ -14,7 +14,7 @@ if ($windows_os)
}
else
{
- plan tests => 4;
+ plan tests => 6;
}
use RewindTest;
diff --git a/src/bin/pg_rewind/t/005_same_timeline.pl b/src/bin/pg_rewind/t/005_same_timeline.pl
index 0e334ee191..2fbca4ad7c 100644
--- a/src/bin/pg_rewind/t/005_same_timeline.pl
+++ b/src/bin/pg_rewind/t/005_same_timeline.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 1;
+use Test::More tests => 2;
use RewindTest;
diff --git a/src/bin/pg_upgrade/file.c b/src/bin/pg_upgrade/file.c
index f38bfacf02..595d43ee31 100644
--- a/src/bin/pg_upgrade/file.c
+++ b/src/bin/pg_upgrade/file.c
@@ -10,6 +10,7 @@
#include "postgres_fe.h"
#include "access/visibilitymap.h"
+#include "common/file_perm.h"
#include "pg_upgrade.h"
#include "storage/bufpage.h"
#include "storage/checksum.h"
@@ -44,7 +45,7 @@ copyFile(const char *src, const char *dst,
schemaName, relName, src, strerror(errno));
if ((dest_fd = open(dst, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR)) < 0)
+ PG_FILE_MODE_DEFAULT)) < 0)
pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
schemaName, relName, dst, strerror(errno));
@@ -151,7 +152,7 @@ rewriteVisibilityMap(const char *fromfile, const char *tofile,
schemaName, relName, fromfile, strerror(errno));
if ((dst_fd = open(tofile, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR)) < 0)
+ PG_FILE_MODE_DEFAULT)) < 0)
pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
schemaName, relName, tofile, strerror(errno));
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index d12412799f..59df6fd88f 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -38,6 +38,7 @@
#include "pg_upgrade.h"
#include "catalog/pg_class.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "fe_utils/string_utils.h"
@@ -79,7 +80,7 @@ main(int argc, char **argv)
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_upgrade"));
/* Ensure that all files created by pg_upgrade are non-world-readable */
- umask(S_IRWXG | S_IRWXO);
+ umask(PG_MODE_MASK_DEFAULT);
parseCommandLine(argc, argv);
diff --git a/src/bin/pg_upgrade/test.sh b/src/bin/pg_upgrade/test.sh
index 39983abea1..4e8db4aaf4 100644
--- a/src/bin/pg_upgrade/test.sh
+++ b/src/bin/pg_upgrade/test.sh
@@ -230,6 +230,17 @@ standard_initdb 'initdb'
pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "${PGDATA}" -b "$oldbindir" -B "$bindir" -p "$PGPORT" -P "$PGPORT"
+# make sure all directories and files have group permissions
+if [ $(find ${PGDATA} -type f ! -perm 600 | wc -l) -ne 0 ]; then
+ echo "files in PGDATA with permission != 600";
+ exit 1;
+fi
+
+if [ $(find ${PGDATA} -type d ! -perm 700 | wc -l) -ne 0 ]; then
+ echo "directories in PGDATA with permission != 700";
+ exit 1;
+fi
+
pg_ctl start -l "$logdir/postmaster2.log" -o "$POSTMASTER_OPTS" -w
case $testhost in
diff --git a/src/include/common/file_perm.h b/src/include/common/file_perm.h
new file mode 100644
index 0000000000..3c6da0e000
--- /dev/null
+++ b/src/include/common/file_perm.h
@@ -0,0 +1,32 @@
+/*-------------------------------------------------------------------------
+ *
+ * File and directory permission constants
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/file_perm.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FILE_PERM_H
+#define FILE_PERM_H
+
+/*
+ * Default mode mask for data directory permissions that does not allow
+ * group execute/read.
+ */
+#define PG_MODE_MASK_DEFAULT (S_IRWXG | S_IRWXO)
+
+/*
+ * Default mode for created files.
+ */
+#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR)
+
+/*
+ * Default mode for directories.
+ */
+#define PG_DIR_MODE_DEFAULT S_IRWXU
+
+#endif /* FILE_PERM_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 4244e7b1fd..33c636471b 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -112,6 +112,9 @@ extern int CloseTransientFile(int fd);
extern int BasicOpenFile(const char *fileName, int fileFlags);
extern int BasicOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode);
+ /* Make a directory with default permissions */
+extern int MakeDirectoryDefaultPerm(const char *directoryName);
+
/* Miscellaneous support routines */
extern void InitFileAccess(void);
extern void set_max_safe_fds(void);
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 80188315f1..76e571b98c 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -484,6 +484,9 @@ sub append_conf
my $conffile = $self->data_dir . '/' . $filename;
TestLib::append_to_file($conffile, $str . "\n");
+
+ chmod(0600, $conffile)
+ or die("unable to set permissions for $conffile");
}
=pod
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index fdd427608b..4a1f50cc1f 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -12,8 +12,11 @@ use warnings;
use Config;
use Exporter 'import';
+use Fcntl qw(:mode);
use File::Basename;
+use File::Find;
use File::Spec;
+use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
use SimpleTee;
@@ -26,6 +29,7 @@ our @EXPORT = qw(
slurp_dir
slurp_file
append_to_file
+ check_pg_data_perm
check_pg_config
system_or_bail
system_log
@@ -222,6 +226,84 @@ sub append_to_file
close $fh;
}
+# Ensure all permissions in the pg_data directory are correct. Permissions
+# should be dir = 0700, file = 0600.
+sub check_pg_data_perm
+{
+ my ($pgdata) = @_;
+
+ # Result defaults to true
+ my $result = 1;
+
+ # Expected permission
+ my $expected_file_perm = 0600;
+ my $expected_dir_perm = 0700;
+
+ find
+ (
+ {follow_fast => 1,
+ wanted =>
+ sub
+ {
+ my $file_stat = stat($File::Find::name);
+
+ defined($file_stat)
+ or die("unable to stat $File::Find::name");
+
+ my $file_mode = S_IMODE($file_stat->mode);
+
+ # Is this a file?
+ if (S_ISREG($file_stat->mode))
+ {
+ # postmaster.pid file must be 600
+ if ($File::Find::name =~ /\/postmaster\.pid$/)
+ {
+ if ($file_mode != 0600)
+ {
+ print(*STDERR, "$File::Find::name mode must be 0600\n");
+
+ $result = 0;
+ return
+ }
+ }
+ else
+ {
+ if ($file_mode != $expected_file_perm)
+ {
+ print(*STDERR,
+ sprintf("$File::Find::name mode must be %04o\n",
+ $expected_file_perm));
+
+ $result = 0;
+ return
+ }
+ }
+ }
+ # Else a directory?
+ elsif (S_ISDIR($file_stat->mode))
+ {
+ if ($file_mode != $expected_dir_perm)
+ {
+ print(*STDERR,
+ sprintf("$File::Find::name mode must be %04o\n",
+ $expected_dir_perm));
+
+ $result = 0;
+ return
+ }
+ }
+ # Else something we can't handle
+ else
+ {
+ die "unknown file type for $File::Find::name";
+ }
+ }},
+ $pgdata
+ );
+
+ return $result;
+}
+
# Check presence of a given regexp within pg_config.h for the installation
# where tests are running, returning a match status result depending on
# that.
--
2.14.3 (Apple Git-98)
group-access-v9-03-group.patchtext/plain; charset=UTF-8; name=group-access-v9-03-group.patch; x-mac-creator=0; x-mac-type=0Download
From 961863ff1e0588f4d31b6add8c2d9d076d4274f6 Mon Sep 17 00:00:00 2001
From: David Steele <david@pgmasters.net>
Date: Tue, 6 Mar 2018 13:09:00 -0500
Subject: [PATCH 3/3] Allow group access on PGDATA.
This includes backend changes, utility changes (initdb, pg_ctl, pg_upgrade, etc.) and tests for each utility.
---
doc/src/sgml/backup.sgml | 6 ++++-
doc/src/sgml/ref/initdb.sgml | 19 +++++++++++++++
doc/src/sgml/runtime.sgml | 14 ++++++++++-
src/backend/postmaster/postmaster.c | 29 +++++++++++++---------
src/bin/initdb/initdb.c | 7 +++++-
src/bin/initdb/t/001_initdb.pl | 13 +++++++++-
src/bin/pg_ctl/pg_ctl.c | 4 ++++
src/bin/pg_ctl/t/001_start_stop.pl | 24 ++++++++++++++++++-
src/bin/pg_resetwal/pg_resetwal.c | 4 ++--
src/bin/pg_resetwal/t/001_basic.pl | 9 +++++--
src/bin/pg_rewind/RewindTest.pm | 12 ++++++----
src/bin/pg_rewind/pg_rewind.c | 4 ++--
src/bin/pg_rewind/t/001_basic.pl | 2 +-
src/bin/pg_upgrade/pg_upgrade.c | 5 +++-
src/bin/pg_upgrade/test.sh | 14 +++++------
src/common/Makefile | 2 +-
src/common/file_perm.c | 48 +++++++++++++++++++++++++++++++++++++
src/include/common/file_perm.h | 31 ++++++++++++++++++++----
src/test/perl/PostgresNode.pm | 27 ++++++++++++++++++++-
src/test/perl/TestLib.pm | 36 ++++++++++++++++++++++++----
src/tools/msvc/Mkvcbuild.pm | 2 +-
21 files changed, 264 insertions(+), 48 deletions(-)
create mode 100644 src/common/file_perm.c
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index 9d8e69056f..a9912eb418 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -1095,7 +1095,11 @@ SELECT pg_stop_backup();
and <filename>postmaster.opts</filename>, which record information
about the running <application>postmaster</application>, not about the
<application>postmaster</application> which will eventually use this backup.
- (These files can confuse <application>pg_ctl</application>.)
+ (These files can confuse <application>pg_ctl</application>.) If group read
+ access is enabled on the data directory and an unprivileged user in the
+ <productname>PostgreSQL</productname> group is performing the backup, then
+ <filename>postmaster.pid</filename> will not be readable and must be
+ excluded.
</para>
<para>
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 585665f161..5f57b933b9 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -76,6 +76,14 @@ PostgreSQL documentation
to do so.)
</para>
+ <para>
+ For security reasons the new cluster created by <command>initdb</command>
+ will only be accessible by the cluster owner by default. The
+ <option>--allow-group-access</option> option allows any user in the same
+ group as the cluster owner to read files in the cluster. This is useful
+ for performing backups as a non-privileged user.
+ </para>
+
<para>
<command>initdb</command> initializes the database cluster's default
locale and character set encoding. The character set encoding,
@@ -301,6 +309,17 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-g</option></term>
+ <term><option>--allow-group-access</option></term>
+ <listitem>
+ <para>
+ Allows users in the same group as the cluster owner to read all cluster
+ files created by <command>initdb</command>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-W</option></term>
<term><option>--pwprompt</option></term>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 71f02300c2..8d8a48aad4 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -137,7 +137,10 @@ postgres$ <userinput>initdb -D /usr/local/pgsql/data</userinput>
database, it is essential that it be secured from unauthorized
access. <command>initdb</command> therefore revokes access
permissions from everyone but the
- <productname>PostgreSQL</productname> user.
+ <productname>PostgreSQL</productname> user, and optionally, group.
+ Group access, when enabled, is read-only. This allows an unprivileged
+ user in the <productname>PostgreSQL</productname> group to take a backup of
+ the cluster data or perform other operations that only require read access.
</para>
<para>
@@ -2220,6 +2223,15 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
member of the group that has access to those certificate and key files.
</para>
+ <para>
+ If the data directory allows group read access then certificate files may
+ need to be located outside of the data directory in order to conform to the
+ security requirements outlined above. Generally, group access is enabled
+ to allow an unprivileged user to backup the database, and in that case the
+ backup software will not be able to read the certificate files and will
+ likely error.
+ </para>
+
<para>
If the private key is protected with a passphrase, the
server will prompt for the passphrase and will not start until it has
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index e13a2ae8c4..65f528147a 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -97,6 +97,7 @@
#include "access/xlog.h"
#include "bootstrap/bootstrap.h"
#include "catalog/pg_control.h"
+#include "common/file_perm.h"
#include "common/ip.h"
#include "lib/ilist.h"
#include "libpq/auth.h"
@@ -587,7 +588,8 @@ PostmasterMain(int argc, char *argv[])
IsPostmasterEnvironment = true;
/*
- * for security, no dir or file created can be group or other accessible
+ * By default, no dir or file created can be group or other accessible. This
+ * may be modified later depending in the permissions of the data directory.
*/
umask(S_IRWXG | S_IRWXO);
@@ -1524,25 +1526,30 @@ checkDataDir(void)
#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.
+ * Check if the directory has correct permissions. If not, reject.
*
* 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))
+ if (stat_buf.st_mode & PG_MODE_MASK_ALLOW_GROUP)
ereport(FATAL,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("data directory \"%s\" has group or world access",
+ errmsg("data directory \"%s\" has invalid permissions",
DataDir),
- errdetail("Permissions should be u=rwx (0700).")));
+ errdetail("Permissions should be u=rwx (0700) or u=rwx,g=rx (0750).")));
+#endif
+
+ /*
+ * Reset the file mode creation mask based on the mode of the data
+ * directory.
+ *
+ * Suppress when on Windows, because there may not be proper support
+ * for Unix-y file permissions.
+ */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+ umask(PG_MODE_MASK_DEFAULT & ~stat_buf.st_mode);
#endif
/* Look for PG_VERSION before looking for pg_control */
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index f8420af2a9..ff4acdb072 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -2289,6 +2289,7 @@ usage(const char *progname)
printf(_(" --auth-local=METHOD default authentication method for local-socket connections\n"));
printf(_(" [-D, --pgdata=]DATADIR location for this database cluster\n"));
printf(_(" -E, --encoding=ENCODING set default encoding for new databases\n"));
+ printf(_(" -g, --allow-group-access allow group read/execute on data directory\n"));
printf(_(" --locale=LOCALE set default locale for new databases\n"));
printf(_(" --lc-collate=, --lc-ctype=, --lc-messages=LOCALE\n"
" --lc-monetary=, --lc-numeric=, --lc-time=LOCALE\n"
@@ -2995,6 +2996,7 @@ main(int argc, char *argv[])
{"waldir", required_argument, NULL, 'X'},
{"wal-segsize", required_argument, NULL, 12},
{"data-checksums", no_argument, NULL, 'k'},
+ {"allow-group-access", no_argument, NULL, 'g'},
{NULL, 0, NULL, 0}
};
@@ -3036,7 +3038,7 @@ main(int argc, char *argv[])
/* process command-line options */
- while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:", long_options, &option_index)) != -1)
+ while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:g", long_options, &option_index)) != -1)
{
switch (c)
{
@@ -3130,6 +3132,9 @@ main(int argc, char *argv[])
case 12:
str_wal_segment_size_mb = pg_strdup(optarg);
break;
+ case 'g':
+ file_mode_mask = PG_MODE_MASK_ALLOW_GROUP;
+ break;
default:
/* getopt_long already emitted a complaint */
fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index 561608f1d8..f767f07ed5 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -4,9 +4,11 @@
use strict;
use warnings;
+use Fcntl ':mode';
+use File::stat qw{lstat};
use PostgresNode;
use TestLib;
-use Test::More tests => 16;
+use Test::More tests => 18;
my $tempdir = TestLib::tempdir;
my $xlogdir = "$tempdir/pgxlog";
@@ -50,3 +52,12 @@ mkdir $datadir;
}
command_ok([ 'initdb', '-S', $datadir ], 'sync only');
command_fails([ 'initdb', $datadir ], 'existing data directory');
+
+# Init a new db with group access
+my $datadir_group = "$tempdir/data_group";
+
+command_ok(
+ [ 'initdb', '-g', $datadir_group ],
+ 'successful creation with group access');
+
+ok(check_pg_data_perm($datadir_group, 1));
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index 1eef7179cc..bb70e625fd 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -2171,6 +2171,7 @@ main(int argc, char **argv)
*/
argv0 = argv[0];
+ /* Set restrictive mode mask until PGDATA permissions are checked */
umask(PG_MODE_MASK_DEFAULT);
/* support --help and --version even if invoked as root */
@@ -2406,6 +2407,9 @@ main(int argc, char **argv)
snprintf(version_file, MAXPGPATH, "%s/PG_VERSION", pg_data);
snprintf(pid_file, MAXPGPATH, "%s/postmaster.pid", pg_data);
snprintf(backup_file, MAXPGPATH, "%s/backup_label", pg_data);
+
+ /* Set mask based on PGDATA permissions */
+ umask(DataDirectoryMask(pg_data));
}
switch (ctl_command)
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl
index 909bf4d465..f950be3366 100644
--- a/src/bin/pg_ctl/t/001_start_stop.pl
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -2,9 +2,11 @@ use strict;
use warnings;
use Config;
+use Fcntl ':mode';
+use File::stat qw{lstat};
use PostgresNode;
use TestLib;
-use Test::More tests => 20;
+use Test::More tests => 24;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -68,4 +70,24 @@ command_ok(
ok(-f $logFileName);
ok(check_pg_data_perm("$tempdir/data", 0));
+# Log file for second perm test
+$logFileName = "$tempdir/data/perm-test-640.log";
+
+# Change the data dir mode so log file will be created with group read
+# privileges on the next start
+system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
+
+chmod_recursive("$tempdir/data", 0750, 0640);
+
+command_ok(
+ [ 'pg_ctl', 'start', '-D', "$tempdir/data", '-l', $logFileName ],
+ 'start server to check group permissions');
+
+ok(-f $logFileName);
+ok(check_pg_data_perm("$tempdir/data", 1));
+
+command_ok(
+ [ 'pg_ctl', 'restart', '-D', "$tempdir/data", '-l', $logFileName ],
+ 'pg_ctl restart with server running');
+
system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 47abd41fc3..6a4dc502c4 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -328,8 +328,8 @@ main(int argc, char *argv[])
exit(1);
}
- /* Set dir/file mode mask */
- umask(PG_MODE_MASK_DEFAULT);
+ /* Set mask based on PGDATA permissions */
+ umask(DataDirectoryMask(DataDir));
/* Check that data directory matches our server version */
CheckDataVersion();
diff --git a/src/bin/pg_resetwal/t/001_basic.pl b/src/bin/pg_resetwal/t/001_basic.pl
index db0c2a630a..391717084a 100644
--- a/src/bin/pg_resetwal/t/001_basic.pl
+++ b/src/bin/pg_resetwal/t/001_basic.pl
@@ -4,7 +4,7 @@ use warnings;
use Config;
use PostgresNode;
use TestLib;
-use Test::More tests => 14;
+use Test::More tests => 15;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -40,9 +40,12 @@ ok(check_pg_data_perm($pgdata, 0), 'check PGDATA permissions');
$node->start;
-# Reset to specific WAL segment
+# Reset to specific WAL segment. Also enable group access to make sure files
+# and directories are created with group permissions.
$node->stop;
+chmod_recursive($pgdata, 0750, 0640);
+
$node->command_ok(
['pg_resetwal', '-l', '000000070000000700000007', '-D', $pgdata],
'set to specific WAL');
@@ -52,4 +55,6 @@ is_deeply(
[sort(qw(. .. archive_status 000000070000000700000007))],
'WAL recreated');
+ok(check_pg_data_perm($pgdata, 1), 'check PGDATA permissions');
+
$node->start;
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index cbf4910ffc..abc74e487a 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -115,11 +115,13 @@ sub check_query
sub setup_cluster
{
- my $extra_name = shift;
+ my $extra_name = shift; # Used to differentiate clusters
+ my $group_access = shift; # Should group access be enabled on PGDATA?
# Initialize master, data checksums are mandatory
$node_master = get_new_node('master' . ($extra_name ? "_${extra_name}" : ''));
- $node_master->init(allows_streaming => 1);
+ $node_master->init(
+ allows_streaming => 1, extra => $group_access ? ['-g'] : undef);
# Set wal_keep_segments to prevent WAL segment recycling after enforced
# checkpoints in the tests.
$node_master->append_conf('postgresql.conf', qq(
@@ -237,7 +239,8 @@ sub run_pg_rewind
"$tmp_folder/master-postgresql.conf.tmp",
"$master_pgdata/postgresql.conf");
- chmod(0600, "$master_pgdata/postgresql.conf")
+ chmod($node_master->group_access() ? 0640 : 0600,
+ "$master_pgdata/postgresql.conf")
or die("unable to set permissions for $master_pgdata/postgresql.conf");
# Plug-in rewound node to the now-promoted standby node
@@ -258,7 +261,8 @@ recovery_target_timeline='latest'
# Clean up after the test. Stop both servers, if they're still running.
sub clean_rewind_test
{
- ok (check_pg_data_perm($node_master->data_dir(), 0));
+ ok (check_pg_data_perm(
+ $node_master->data_dir(), $node_master->group_access()));
$node_master->teardown_node if defined $node_master;
$node_standby->teardown_node if defined $node_standby;
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 3d179e2c7d..11153e5f2f 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -186,8 +186,8 @@ main(int argc, char **argv)
exit(1);
}
- /* Set dir/file mode mask */
- umask(PG_MODE_MASK_DEFAULT);
+ /* Set mask based on PGDATA permissions */
+ umask(DataDirectoryMask(datadir_target));
/*
* Don't allow pg_rewind to be run as root, to avoid overwriting the
diff --git a/src/bin/pg_rewind/t/001_basic.pl b/src/bin/pg_rewind/t/001_basic.pl
index e7fc200fc0..205bdd77ef 100644
--- a/src/bin/pg_rewind/t/001_basic.pl
+++ b/src/bin/pg_rewind/t/001_basic.pl
@@ -9,7 +9,7 @@ sub run_test
{
my $test_mode = shift;
- RewindTest::setup_cluster($test_mode);
+ RewindTest::setup_cluster($test_mode, 1);
RewindTest::start_master();
# Create a test table and insert a row in master.
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 59df6fd88f..54f54fb0a4 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -79,7 +79,7 @@ main(int argc, char **argv)
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_upgrade"));
- /* Ensure that all files created by pg_upgrade are non-world-readable */
+ /* Set default restrictive mask until new cluster permissions are read */
umask(PG_MODE_MASK_DEFAULT);
parseCommandLine(argc, argv);
@@ -100,6 +100,9 @@ main(int argc, char **argv)
check_cluster_compatibility(live_check);
+ /* Set mask based on new PGDATA permissions */
+ umask(DataDirectoryMask(new_cluster.pgdata));
+
check_and_dump_old_cluster(live_check);
diff --git a/src/bin/pg_upgrade/test.sh b/src/bin/pg_upgrade/test.sh
index 4e8db4aaf4..24631a91c6 100644
--- a/src/bin/pg_upgrade/test.sh
+++ b/src/bin/pg_upgrade/test.sh
@@ -20,9 +20,9 @@ unset MAKELEVEL
# Run a given "initdb" binary and overlay the regression testing
# authentication configuration.
standard_initdb() {
- # To increase coverage of non-standard segment size without
- # increase test runtime, run these tests with a lower setting.
- "$1" -N --wal-segsize 1
+ # To increase coverage of non-standard segment size and group access
+ # without increasing test runtime, run these tests with a custom setting.
+ "$1" -N --wal-segsize 1 -g
if [ -n "$TEMP_CONFIG" -a -r "$TEMP_CONFIG" ]
then
cat "$TEMP_CONFIG" >> "$PGDATA/postgresql.conf"
@@ -231,13 +231,13 @@ standard_initdb 'initdb'
pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "${PGDATA}" -b "$oldbindir" -B "$bindir" -p "$PGPORT" -P "$PGPORT"
# make sure all directories and files have group permissions
-if [ $(find ${PGDATA} -type f ! -perm 600 | wc -l) -ne 0 ]; then
- echo "files in PGDATA with permission != 600";
+if [ $(find ${PGDATA} -type f ! -perm 640 | wc -l) -ne 0 ]; then
+ echo "files in PGDATA with permission != 640";
exit 1;
fi
-if [ $(find ${PGDATA} -type d ! -perm 700 | wc -l) -ne 0 ]; then
- echo "directories in PGDATA with permission != 700";
+if [ $(find ${PGDATA} -type d ! -perm 750 | wc -l) -ne 0 ]; then
+ echo "directories in PGDATA with permission != 750";
exit 1;
fi
diff --git a/src/common/Makefile b/src/common/Makefile
index 80e78d72fe..504375bef4 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -51,7 +51,7 @@ else
OBJS_COMMON += sha2.o
endif
-OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o file_utils.o restricted_token.o
+OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o file_perm.o file_utils.o restricted_token.o
OBJS_SRV = $(OBJS_COMMON:%.o=%_srv.o)
diff --git a/src/common/file_perm.c b/src/common/file_perm.c
new file mode 100644
index 0000000000..f01d43725e
--- /dev/null
+++ b/src/common/file_perm.c
@@ -0,0 +1,48 @@
+/*-------------------------------------------------------------------------
+ *
+ * File and directory permission routines
+ *
+ * Group read/execute may optional be enabled on PGDATA so any frontend tools
+ * That write into PGDATA must know what mask to set and the permissions to
+ * use for creating files and directories.
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/file_perm.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#error "This file is not expected to be compiled for backend code"
+#endif
+
+#include <sys/stat.h>
+
+#include "common/file_perm.h"
+
+/*
+ * Determine the mask to use when writing to PGDATA.
+ *
+ * Errors are not handled here and should be checked by the frontend
+ * application.
+ */
+mode_t
+DataDirectoryMask(const char *dataDir)
+{
+ struct stat statBuf;
+
+ /*
+ * If an error occurs getting the mode then return the more restrictive mask.
+ * It is the reponsibility of the frontend application to generate an error.
+ */
+ if (stat(dataDir, &statBuf) != 0)
+ return PG_MODE_MASK_DEFAULT;
+
+ /*
+ * Construct the mask that the caller should pass to umask().
+ */
+ return PG_MODE_MASK_DEFAULT & ~statBuf.st_mode;
+}
diff --git a/src/include/common/file_perm.h b/src/include/common/file_perm.h
index 3c6da0e000..35dcbe6108 100644
--- a/src/include/common/file_perm.h
+++ b/src/include/common/file_perm.h
@@ -1,6 +1,10 @@
/*-------------------------------------------------------------------------
*
- * File and directory permission constants
+ * File and directory permission constants and routines
+ *
+ * Group read/execute may optional be enabled on PGDATA so any frontend tools
+ * That write into PGDATA must know what mask to set and the permissions to
+ * use for creating files and directories.
*
*
* Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
@@ -20,13 +24,30 @@
#define PG_MODE_MASK_DEFAULT (S_IRWXG | S_IRWXO)
/*
- * Default mode for created files.
+ * Optional mode mask for data directory permissions that allows group
+ * read/execute.
+ */
+#define PG_MODE_MASK_ALLOW_GROUP (S_IWGRP | S_IRWXO)
+
+/*
+ * Default mode for created files, unless something else is specified using
+ * the *Perm() function variants.
+ */
+#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR | S_IRGRP)
+
+/*
+ * Default mode for directories created with MakeDirectoryDefaultPerm().
*/
-#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR)
+#define PG_DIR_MODE_DEFAULT (S_IRWXU | S_IRGRP | S_IXGRP)
+
+#ifdef FRONTEND
/*
- * Default mode for directories.
+ * Determine the mask to use when writing to PGDATA
*/
-#define PG_DIR_MODE_DEFAULT S_IRWXU
+mode_t DataDirectoryMask(const char *dataDir);
+
+#endif /* FRONTEND */
+
#endif /* FILE_PERM_H */
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 76e571b98c..1bd91524d7 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -86,9 +86,11 @@ use Carp;
use Config;
use Cwd;
use Exporter 'import';
+use Fcntl qw(:mode);
use File::Basename;
use File::Path qw(rmtree);
use File::Spec;
+use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
use RecursiveCopy;
@@ -269,6 +271,26 @@ sub connstr
=pod
+=item $node->group_access()
+
+Does the data dir allow group access?
+
+=cut
+
+sub group_access
+{
+ my ($self) = @_;
+
+ my $dir_stat = stat($self->data_dir);
+
+ defined($dir_stat)
+ or die('unable to stat ' . $self->data_dir);
+
+ return (S_IMODE($dir_stat->mode) == 0750);
+}
+
+=pod
+
=item $node->data_dir()
Returns the path to the data directory. postgresql.conf and pg_hba.conf are
@@ -460,6 +482,9 @@ sub init
}
close $conf;
+ chmod($self->group_access ? 0640 : 0600, "$pgdata/postgresql.conf")
+ or die("unable to set permissions for $pgdata/postgresql.conf");
+
$self->set_replication_conf if $params{allows_streaming};
$self->enable_archiving if $params{has_archiving};
}
@@ -485,7 +510,7 @@ sub append_conf
TestLib::append_to_file($conffile, $str . "\n");
- chmod(0600, $conffile)
+ chmod($self->group_access() ? 0640 : 0600, $conffile)
or die("unable to set permissions for $conffile");
}
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index 4a1f50cc1f..ff436dcdc0 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -30,6 +30,7 @@ our @EXPORT = qw(
slurp_file
append_to_file
check_pg_data_perm
+ chmod_recursive
check_pg_config
system_or_bail
system_log
@@ -226,18 +227,19 @@ sub append_to_file
close $fh;
}
-# Ensure all permissions in the pg_data directory are correct. Permissions
-# should be dir = 0700, file = 0600.
+# Ensure all permissions in the pg_data directory are correct. When allow_group
+# is true then permissions should be dir = 0750, file = 0640. When allow_group
+# is false then permissions should be dir = 0700, file = 0600.
sub check_pg_data_perm
{
- my ($pgdata) = @_;
+ my ($pgdata, $allow_group) = @_;
# Result defaults to true
my $result = 1;
# Expected permission
- my $expected_file_perm = 0600;
- my $expected_dir_perm = 0700;
+ my $expected_file_perm = $allow_group ? 0640 : 0600;
+ my $expected_dir_perm = $allow_group ? 0750 : 0700;
find
(
@@ -304,6 +306,30 @@ sub check_pg_data_perm
return $result;
}
+# Change permissions recursively on a directory
+sub chmod_recursive
+{
+ my ($dir, $dir_mode, $file_mode) = @_;
+
+ find
+ (
+ {follow_fast => 1,
+ wanted =>
+ sub
+ {
+ my $file_stat = stat($File::Find::name);
+
+ if (defined($file_stat))
+ {
+ chmod(S_ISDIR($file_stat->mode) ? $dir_mode : $file_mode,
+ $File::Find::name)
+ or die "unable to chmod $File::Find::name";
+ }
+ }},
+ $dir
+ );
+}
+
# Check presence of a given regexp within pg_config.h for the installation
# where tests are running, returning a match status result depending on
# that.
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 72976f44d8..78d4934ec6 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -125,7 +125,7 @@ sub mkvcbuild
}
our @pgcommonfrontendfiles = (
- @pgcommonallfiles, qw(fe_memutils.c file_utils.c
+ @pgcommonallfiles, qw(fe_memutils.c file_perm.c file_utils.c
restricted_token.c));
our @pgcommonbkndfiles = @pgcommonallfiles;
--
2.14.3 (Apple Git-98)
On Tue, Mar 06, 2018 at 01:32:49PM -0500, David Steele wrote:
On 3/5/18 10:46 PM, Michael Paquier wrote:
Ah, I see what you mean now. Fixed using follow_fast. This variant
claims to be faster and it doesn't matter if files are occasionally
checked twice.
Fine for me. I can see this option present in perl 5.8.8 as well, so we
should be good.
Those two are separate issues. Could you begin a new thread on the
matter? This will attract more attention.OK, I'll move it back for now, and make a note to raise the position as
a separate issue. I'd rather not do it in this fest, though.
Seems like you forgot to re-add the chmod calls in initdb.c.
- if (chmod(location, S_IRWXU) != 0) + current_umask = umask(0); + umask(current_umask); [...] - if (chmod(pg_data, S_IRWXU) != 0) + if (chmod(pg_data, PG_DIR_MODE_DEFAULT & ~file_mode_mask) != 0) Such modifications should be part of patch 3, not 2, where the group features really apply.The goal of patch 2 is to refactor the way permissions are set by
replacing locally hard-coded values with centralized values, so I think
this makes sense here.
Hm. I would have left that out in this first version, now I am fine to
defer that to a committer's opinion as well.
my $test_mode = shift;
+ umask(0077);
Added. (Ensure that all files are created with default data dir
permissions).
+ # Ensure that all files are created with default data dir permissions
+ umask(0077);
I have stared at this comment two minutes to finally understand that
this refers to the extra files which are part of this test. It may be
better to be a bit more precise here. I thought first that this
referred as well to setup_cluster calls...
Patch 2 begins to look fine for me.
I also made chmod_recursive() generic.
Likely this name is not worth worrying with conflicts in CPAN stuff ;)
+# Ensure all permissions in the pg_data directory are
correct. Permissions
+# should be dir = 0700, file = 0600.
+sub check_pg_data_perm
+{
check_permission_recursive() would be a more adapted name. Stuff in
TestLib.pm is not necessarily linked to data folders.
sub clean_rewind_test
{
+ ok (check_pg_data_perm($node_master->data_dir(), 0));
+
$node_master->teardown_node if defined $node_master;
I have also a pending patch for pg_rewind which adds read-only files in
the data folders of a new test, so this would cause this part to blow
up. Testing that for all the test sets does not bring additional value
as well, and doing it at cleanup phase is also confusing. So could you
move that check into only one test's script? You could remove the umask
call in 003_extrafiles.pl as well this way, and reduce the patch diffs.
+ if ($file_mode != 0600)
+ {
+ print(*STDERR, "$File::Find::name mode must be 0600\n");
+
+ $result = 0;
+ return
+ }
0600 should be replaced by $expected_file_perm, and isn't a ';' missing
for each return "clause" (how does this even work..)?
Patch 2 is getting close for a committer lookup I think, still need to
look at patch 3 in details.. And from patch 3:
# Expected permission
- my $expected_file_perm = 0600;
- my $expected_dir_perm = 0700;
+ my $expected_file_perm = $allow_group ? 0640 : 0600;
+ my $expected_dir_perm = $allow_group ? 0750 : 0700;
Or $expected_file_perm and $expected_dir_perm could just be used as
arguments.
--
Michael
On 3/6/18 10:04 PM, Michael Paquier wrote:
On Tue, Mar 06, 2018 at 01:32:49PM -0500, David Steele wrote:
On 3/5/18 10:46 PM, Michael Paquier wrote:
Those two are separate issues. Could you begin a new thread on the
matter? This will attract more attention.OK, I'll move it back for now, and make a note to raise the position as
a separate issue. I'd rather not do it in this fest, though.Seems like you forgot to re-add the chmod calls in initdb.c.
Hmmm, I thought we were talking about moving the position of umask().
I don't think the chmod() calls are needed because umask() is being set.
The tests show that the config files have the proper permissions after
inidb, so this just looks like redundant code to me.
But, I'm not going to argue if you think they should go back. It would
make the patch less noisy.
- if (chmod(location, S_IRWXU) != 0) + current_umask = umask(0); + umask(current_umask); [...] - if (chmod(pg_data, S_IRWXU) != 0) + if (chmod(pg_data, PG_DIR_MODE_DEFAULT & ~file_mode_mask) != 0) Such modifications should be part of patch 3, not 2, where the group features really apply.The goal of patch 2 is to refactor the way permissions are set by
replacing locally hard-coded values with centralized values, so I think
this makes sense here.Hm. I would have left that out in this first version, now I am fine to
defer that to a committer's opinion as well.
OK - I really do think it belongs here. A committer may not agree, of
course.
my $test_mode = shift;
+ umask(0077);
Added. (Ensure that all files are created with default data dir
permissions).+ # Ensure that all files are created with default data dir permissions + umask(0077); I have stared at this comment two minutes to finally understand that this refers to the extra files which are part of this test. It may be better to be a bit more precise here. I thought first that this referred as well to setup_cluster calls...
Updated to, "Ensure that all files created in this module for testing
are set with default data dir permissions."
+# Ensure all permissions in the pg_data directory are correct. Permissions +# should be dir = 0700, file = 0600. +sub check_pg_data_perm +{ check_permission_recursive() would be a more adapted name. Stuff in TestLib.pm is not necessarily linked to data folders.
When we add group permissions we need to have special logic around
postmaster.pid. This should be 0600 even if the rest of the files are 0640.
I can certainly remove that special logic in 02 and make this function
more generic, but then I think we would still have to add
check_pg_data_perm() in patch 03.
Another way to do this would be to make the function generic and
stipulate that the postmaster must be shut down before running the
function. We could check postmaster.pid permissions as a separate test.
What do you think?
sub clean_rewind_test { + ok (check_pg_data_perm($node_master->data_dir(), 0)); + $node_master->teardown_node if defined $node_master; I have also a pending patch for pg_rewind which adds read-only files in the data folders of a new test, so this would cause this part to blow up. Testing that for all the test sets does not bring additional value as well, and doing it at cleanup phase is also confusing. So could you move that check into only one test's script? You could remove the umask call in 003_extrafiles.pl as well this way, and reduce the patch diffs.
I think I would rather have a way to skip the permission test rather
than disable it for most of the tests. pg_rewind does more writes into
PGDATA that anything other than a backend. Good coverage seems like a plus.
+ if ($file_mode != 0600) + { + print(*STDERR, "$File::Find::name mode must be 0600\n"); + + $result = 0; + return + } 0600 should be replaced by $expected_file_perm, and isn't a ';' missing for each return "clause" (how does this even work..)?
Well, 0600 is a special case -- see above. As for the missing
semi-colon, well that's Perl for you. Fixed.
Patch 2 is getting close for a committer lookup I think, still need to look at patch 3 in details.. And from patch 3: # Expected permission - my $expected_file_perm = 0600; - my $expected_dir_perm = 0700; + my $expected_file_perm = $allow_group ? 0640 : 0600; + my $expected_dir_perm = $allow_group ? 0750 : 0700; Or $expected_file_perm and $expected_dir_perm could just be used as arguments.
This gets back to the check_pg_data_perm() discussion above.
I'll hold off on another set of patches until I hear back from you.
There were only trivial changes as noted above.
Thanks,
--
-David
david@pgmasters.net
On Wed, Mar 07, 2018 at 10:56:32AM -0500, David Steele wrote:
On 3/6/18 10:04 PM, Michael Paquier wrote:
Seems like you forgot to re-add the chmod calls in initdb.c.
Hmmm, I thought we were talking about moving the position of umask().
I don't think the chmod() calls are needed because umask() is being set.
The tests show that the config files have the proper permissions after
inidb, so this just looks like redundant code to me.
Let's discuss that on a separate thread then, there could be something
we are missing, but I agree that those should not be needed. Looking at
the code history, those calls have been around since the beginning of
PG-times.
Another way to do this would be to make the function generic and
stipulate that the postmaster must be shut down before running the
function. We could check postmaster.pid permissions as a separate
test.
Yeah, that looks like a sensitive approach as this could be run
post-initdb or after taking a backup. This way we would avoid other
similar behaviors in the future... Still postmaster.pid is an
exception.
sub clean_rewind_test { + ok (check_pg_data_perm($node_master->data_dir(), 0)); + $node_master->teardown_node if defined $node_master; I have also a pending patch for pg_rewind which adds read-only files in the data folders of a new test, so this would cause this part to blow up. Testing that for all the test sets does not bring additional value as well, and doing it at cleanup phase is also confusing. So could you move that check into only one test's script? You could remove the umask call in 003_extrafiles.pl as well this way, and reduce the patch diffs.I think I would rather have a way to skip the permission test rather
than disable it for most of the tests. pg_rewind does more writes into
PGDATA that anything other than a backend. Good coverage seems like a
plus.
All the tests basically run the same scenario, wiht minimal variations,
so let's do the test once in the test touching the highest amount of
files and call it good.
Patch 2 is getting close for a committer lookup I think, still need to look at patch 3 in details.. And from patch 3: # Expected permission - my $expected_file_perm = 0600; - my $expected_dir_perm = 0700; + my $expected_file_perm = $allow_group ? 0640 : 0600; + my $expected_dir_perm = $allow_group ? 0750 : 0700; Or $expected_file_perm and $expected_dir_perm could just be used as arguments.This gets back to the check_pg_data_perm() discussion above.
I'll hold off on another set of patches until I hear back from you.
There were only trivial changes as noted above.
OK, let's keep things simple here and assume that the grouping extension
is not available yet. So no extra parameters should be needed.
I have begun to read through patch 3.
- if (stat_buf.st_mode & (S_IRWXG | S_IRWXO))
+ if (stat_buf.st_mode & PG_MODE_MASK_ALLOW_GROUP)
ereport(FATAL,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("data directory \"%s\" has group or world access",
+ errmsg("data directory \"%s\" has invalid permissions",
DataDir),
- errdetail("Permissions should be u=rwx (0700).")));
+ errdetail("Permissions should be u=rwx (0700) or u=rwx,g=rx (0750).")));
Hm. This relaxes the checks and concerns me a lot. What if users want
to keep strict permission all the time and rely on the existing check to
be sure that this gets never changed post-initdb? For such users, we
may want to track if cluster has been initialized with group access, in
which case tracking that in the control file would be more adapted.
Then the startup checks should use this configuration. Another idea
would be a startup option. So, I cannot believe that all users would
like to see such checks relaxed. Some of my users would surely complain
about such sanity checks relaxed unconditionally, so making this
configurable would be fine, and the current approach is not acceptable
in my opinion.
Patch 2 introduces some standards regarding file permissions as those
are copied all over the code tree, which is definitely good in my
opinion. I am rather reserved about patch 3 per what I am seeing now.
Looking in-depth at the security-related requirements would take more
time than I have now.
--
Michael
Hi Michael,
On 3/7/18 8:51 PM, Michael Paquier wrote:
On Wed, Mar 07, 2018 at 10:56:32AM -0500, David Steele wrote:
On 3/6/18 10:04 PM, Michael Paquier wrote:
Seems like you forgot to re-add the chmod calls in initdb.c.
Hmmm, I thought we were talking about moving the position of umask().
I don't think the chmod() calls are needed because umask() is being set.
The tests show that the config files have the proper permissions after
inidb, so this just looks like redundant code to me.Let's discuss that on a separate thread then, there could be something
we are missing, but I agree that those should not be needed. Looking at
the code history, those calls have been around since the beginning of
PG-times.
Done.
Another way to do this would be to make the function generic and
stipulate that the postmaster must be shut down before running the
function. We could check postmaster.pid permissions as a separate
test.Yeah, that looks like a sensitive approach as this could be run
post-initdb or after taking a backup. This way we would avoid other
similar behaviors in the future... Still postmaster.pid is an
exception.
Done.
sub clean_rewind_test { + ok (check_pg_data_perm($node_master->data_dir(), 0)); + $node_master->teardown_node if defined $node_master; I have also a pending patch for pg_rewind which adds read-only files in the data folders of a new test, so this would cause this part to blow up. Testing that for all the test sets does not bring additional value as well, and doing it at cleanup phase is also confusing. So could you move that check into only one test's script? You could remove the umask call in 003_extrafiles.pl as well this way, and reduce the patch diffs.I think I would rather have a way to skip the permission test rather
than disable it for most of the tests. pg_rewind does more writes into
PGDATA that anything other than a backend. Good coverage seems like a
plus.All the tests basically run the same scenario, with minimal variations,
so let's do the test once in the test touching the highest amount of
files and call it good.
OK, test 001 is used to check default mode and 002 is used to check
group mode. The rest are left as-is. Does that work for you?
I have begun to read through patch 3.
- if (stat_buf.st_mode & (S_IRWXG | S_IRWXO)) + if (stat_buf.st_mode & PG_MODE_MASK_ALLOW_GROUP) ereport(FATAL, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("data directory \"%s\" has group or world access", + errmsg("data directory \"%s\" has invalid permissions", DataDir), - errdetail("Permissions should be u=rwx (0700)."))); + errdetail("Permissions should be u=rwx (0700) or u=rwx,g=rx (0750)."))); Hm. This relaxes the checks and concerns me a lot. What if users want to keep strict permission all the time and rely on the existing check to be sure that this gets never changed post-initdb? For such users, we may want to track if cluster has been initialized with group access, in which case tracking that in the control file would be more adapted. Then the startup checks should use this configuration. Another idea would be a startup option. So, I cannot believe that all users would like to see such checks relaxed. Some of my users would surely complain about such sanity checks relaxed unconditionally, so making this configurable would be fine, and the current approach is not acceptable in my opinion.
How about a GUC that enforces one mode or the other on startup? Default
would be 700. The GUC can be set automatically by initdb based on the
-g option. We had this GUC originally, but since the front-end tools
can't read it we abandoned it. Seems like it would be good as an
enforcing mechanism, though.
Thanks,
--
-David
david@pgmasters.net
Attachments:
group-access-v10-01-pgresetwal-test.patchtext/plain; charset=UTF-8; name=group-access-v10-01-pgresetwal-test.patch; x-mac-creator=0; x-mac-type=0Download
From 19d827b9d9c0285556e977d3962619deb84c3c0e Mon Sep 17 00:00:00 2001
From: David Steele <david@pgmasters.net>
Date: Mon, 5 Mar 2018 14:33:10 -0500
Subject: [PATCH 1/3] pg_resetwal tests.
Adds a very basic test suite for pg_resetwal.
---
src/bin/pg_resetwal/.gitignore | 1 +
src/bin/pg_resetwal/Makefile | 6 +++++
src/bin/pg_resetwal/t/001_basic.pl | 53 ++++++++++++++++++++++++++++++++++++++
3 files changed, 60 insertions(+)
create mode 100644 src/bin/pg_resetwal/t/001_basic.pl
diff --git a/src/bin/pg_resetwal/.gitignore b/src/bin/pg_resetwal/.gitignore
index 236abb4323..a950255fd7 100644
--- a/src/bin/pg_resetwal/.gitignore
+++ b/src/bin/pg_resetwal/.gitignore
@@ -1 +1,2 @@
/pg_resetwal
+/tmp_check
diff --git a/src/bin/pg_resetwal/Makefile b/src/bin/pg_resetwal/Makefile
index 5ab7ad33e0..13c9ca6279 100644
--- a/src/bin/pg_resetwal/Makefile
+++ b/src/bin/pg_resetwal/Makefile
@@ -33,3 +33,9 @@ uninstall:
clean distclean maintainer-clean:
rm -f pg_resetwal$(X) $(OBJS)
+
+check:
+ $(prove_check)
+
+installcheck:
+ $(prove_installcheck)
diff --git a/src/bin/pg_resetwal/t/001_basic.pl b/src/bin/pg_resetwal/t/001_basic.pl
new file mode 100644
index 0000000000..234bd70303
--- /dev/null
+++ b/src/bin/pg_resetwal/t/001_basic.pl
@@ -0,0 +1,53 @@
+use strict;
+use warnings;
+
+use Config;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 13;
+
+my $tempdir = TestLib::tempdir;
+my $tempdir_short = TestLib::tempdir_short;
+
+program_help_ok('pg_resetwal');
+program_version_ok('pg_resetwal');
+program_options_handling_ok('pg_resetwal');
+
+# Initialize node without replication settings
+my $node = get_new_node('main');
+my $pgdata = $node->data_dir;
+my $pgwal = "$pgdata/pg_wal";
+$node->init;
+$node->start;
+
+# Remove WAL from pg_wal and make sure it gets rebuilt
+$node->stop;
+
+unlink("$pgwal/000000010000000000000001") == 1
+ or die("unable to remove 000000010000000000000001");
+
+is_deeply(
+ [sort(slurp_dir($pgwal))], [sort(qw(. .. archive_status))], 'no WAL');
+
+$node->command_ok(['pg_resetwal', '-D', $pgdata], 'recreate pg_wal');
+
+is_deeply(
+ [sort(slurp_dir($pgwal))],
+ [sort(qw(. .. archive_status 000000010000000000000002))],
+ 'WAL recreated');
+
+$node->start;
+
+# Reset to specific WAL segment
+$node->stop;
+
+$node->command_ok(
+ ['pg_resetwal', '-l', '000000070000000700000007', '-D', $pgdata],
+ 'set to specific WAL');
+
+is_deeply(
+ [sort(slurp_dir($pgwal))],
+ [sort(qw(. .. archive_status 000000070000000700000007))],
+ 'WAL recreated');
+
+$node->start;
--
2.14.3 (Apple Git-98)
group-access-v10-02-file-perm.patchtext/plain; charset=UTF-8; name=group-access-v10-02-file-perm.patch; x-mac-creator=0; x-mac-type=0Download
From aa244efad465533225442b817f303ce81a76dd79 Mon Sep 17 00:00:00 2001
From: David Steele <david@pgmasters.net>
Date: Fri, 9 Mar 2018 13:44:36 -0500
Subject: [PATCH 2/3] Refactor file permissions in backend/frontend
Adds a new header (file_perm.h) and makes all front-end utilities use
the new constants for setting umask. Converts mkdir() calls in the
backend to MakeDirectoryDefaultPerm() if the original
call used default permissions. Adds tests to make sure permissions in
PGDATA are set correctly by the front-end tools.
---
src/backend/access/transam/xlog.c | 2 +-
src/backend/commands/tablespace.c | 22 +++++++----
src/backend/postmaster/postmaster.c | 2 +-
src/backend/postmaster/syslogger.c | 5 ++-
src/backend/replication/slot.c | 5 ++-
src/backend/storage/file/copydir.c | 2 +-
src/backend/storage/file/fd.c | 32 ++++++++++------
src/backend/storage/ipc/ipc.c | 4 ++
src/bin/initdb/initdb.c | 24 ++++++------
src/bin/initdb/t/001_initdb.pl | 4 +-
src/bin/pg_ctl/pg_ctl.c | 3 +-
src/bin/pg_ctl/t/001_start_stop.pl | 13 +++++--
src/bin/pg_resetwal/pg_resetwal.c | 8 +++-
src/bin/pg_resetwal/t/001_basic.pl | 4 +-
src/bin/pg_rewind/RewindTest.pm | 3 ++
src/bin/pg_rewind/file_ops.c | 7 ++--
src/bin/pg_rewind/pg_rewind.c | 4 ++
src/bin/pg_rewind/t/001_basic.pl | 3 +-
src/bin/pg_upgrade/file.c | 5 ++-
src/bin/pg_upgrade/pg_upgrade.c | 3 +-
src/bin/pg_upgrade/test.sh | 11 ++++++
src/include/common/file_perm.h | 32 ++++++++++++++++
src/include/storage/fd.h | 3 ++
src/test/perl/PostgresNode.pm | 3 ++
src/test/perl/TestLib.pm | 73 +++++++++++++++++++++++++++++++++++++
25 files changed, 223 insertions(+), 54 deletions(-)
create mode 100644 src/include/common/file_perm.h
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 47a6c4d895..bf07bbd746 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4086,7 +4086,7 @@ ValidateXLOGDirectoryStructure(void)
{
ereport(LOG,
(errmsg("creating missing WAL directory \"%s\"", path)));
- if (mkdir(path, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(path) < 0)
ereport(FATAL,
(errmsg("could not create missing directory \"%s\": %m",
path)));
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 5c450caa4e..c4ee987446 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -68,6 +68,7 @@
#include "commands/seclabel.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
+#include "common/file_perm.h"
#include "miscadmin.h"
#include "postmaster/bgwriter.h"
#include "storage/fd.h"
@@ -151,7 +152,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
else
{
/* Directory creation failed? */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(dir) < 0)
{
char *parentdir;
@@ -173,7 +174,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
get_parent_directory(parentdir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -184,7 +185,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
parentdir = pstrdup(dir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -192,7 +193,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
pfree(parentdir);
/* Create database directory */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(dir) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -279,7 +280,8 @@ CreateTableSpace(CreateTableSpaceStmt *stmt)
/*
* Check that location isn't too long. Remember that we're going to append
* 'PG_XXX/<dboid>/<relid>_<fork>.<nnn>'. FYI, we never actually
- * reference the whole path here, but mkdir() uses the first two parts.
+ * reference the whole path here, but MakeDirectoryDefaultPerm() uses the first
+ * two parts.
*/
if (strlen(location) + 1 + strlen(TABLESPACE_VERSION_DIRECTORY) + 1 +
OIDCHARS + 1 + OIDCHARS + 1 + FORKNAMECHARS + 1 + OIDCHARS > MAXPGPATH)
@@ -565,6 +567,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
char *linkloc;
char *location_with_version_dir;
struct stat st;
+ mode_t current_umask; /* Current umask value */
linkloc = psprintf("pg_tblspc/%u", tablespaceoid);
location_with_version_dir = psprintf("%s/%s", location,
@@ -574,7 +577,10 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* Attempt to coerce target directory to safe permissions. If this fails,
* it doesn't exist or has the wrong owner.
*/
- if (chmod(location, S_IRWXU) != 0)
+ current_umask = umask(0);
+ umask(current_umask);
+
+ if (chmod(location, PG_DIR_MODE_DEFAULT & ~current_umask) != 0)
{
if (errno == ENOENT)
ereport(ERROR,
@@ -599,7 +605,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
if (stat(location_with_version_dir, &st) == 0 && S_ISDIR(st.st_mode))
{
if (!rmtree(location_with_version_dir, true))
- /* If this failed, mkdir() below is going to error. */
+ /* If this failed, MakeDirectoryDefaultPerm() below is going to error. */
ereport(WARNING,
(errmsg("some useless files may be left behind in old database directory \"%s\"",
location_with_version_dir)));
@@ -610,7 +616,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* The creation of the version directory prevents more than one tablespace
* in a single location.
*/
- if (mkdir(location_with_version_dir, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(location_with_version_dir) < 0)
{
if (errno == EEXIST)
ereport(ERROR,
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 660f3185e6..956960fd4b 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -4492,7 +4492,7 @@ internal_forkexec(int argc, char *argv[], Port *port)
* As in OpenTemporaryFileInTablespace, try to make the temp-file
* directory
*/
- mkdir(PG_TEMP_FILES_DIR, S_IRWXU);
+ MakeDirectoryDefaultPerm(PG_TEMP_FILES_DIR);
fp = AllocateFile(tmpfilename, PG_BINARY_W);
if (!fp)
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index f70eea37df..ae23941f59 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -41,6 +41,7 @@
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
#include "storage/dsm.h"
+#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pg_shmem.h"
@@ -322,7 +323,7 @@ SysLoggerMain(int argc, char *argv[])
/*
* Also, create new directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ MakeDirectoryDefaultPerm(Log_directory);
}
if (strcmp(Log_filename, currentLogFilename) != 0)
{
@@ -564,7 +565,7 @@ SysLogger_Start(void)
/*
* Create log directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ MakeDirectoryDefaultPerm(Log_directory);
/*
* The initial logfile is created right in the postmaster, to verify that
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index fc9ef22b0b..3a98360b50 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -1166,13 +1166,14 @@ CreateSlotOnDisk(ReplicationSlot *slot)
* It's just barely possible that some previous effort to create or drop a
* slot with this name left a temp directory lying around. If that seems
* to be the case, try to remove it. If the rmtree() fails, we'll error
- * out at the mkdir() below, so we don't bother checking success.
+ * out at the MakeDirectoryDefaultPerm() below, so we don't bother checking
+ * success.
*/
if (stat(tmppath, &st) == 0 && S_ISDIR(st.st_mode))
rmtree(tmppath, true);
/* Create and fsync the temporary slot directory. */
- if (mkdir(tmppath, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(tmppath) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
diff --git a/src/backend/storage/file/copydir.c b/src/backend/storage/file/copydir.c
index ca6342db0d..ff980dc1f5 100644
--- a/src/backend/storage/file/copydir.c
+++ b/src/backend/storage/file/copydir.c
@@ -41,7 +41,7 @@ copydir(char *fromdir, char *todir, bool recurse)
char fromfile[MAXPGPATH * 2];
char tofile[MAXPGPATH * 2];
- if (mkdir(todir, S_IRWXU) != 0)
+ if (MakeDirectoryDefaultPerm(todir) != 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m", todir)));
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index 2a18e94ff4..1491665375 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -84,6 +84,7 @@
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "catalog/pg_tablespace.h"
+#include "common/file_perm.h"
#include "pgstat.h"
#include "portability/mem.h"
#include "storage/fd.h"
@@ -124,12 +125,6 @@
*/
#define FD_MINFREE 10
-/*
- * Default mode for created files, unless something else is specified using
- * the *Perm() function variants.
- */
-#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR)
-
/*
* A number of platforms allow individual processes to open many more files
* than they can really support when *many* processes do the same thing.
@@ -1435,7 +1430,7 @@ PathNameOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode)
void
PathNameCreateTemporaryDir(const char *basedir, const char *directory)
{
- if (mkdir(directory, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(directory) < 0)
{
if (errno == EEXIST)
return;
@@ -1445,14 +1440,14 @@ PathNameCreateTemporaryDir(const char *basedir, const char *directory)
* EEXIST to close a race against another process following the same
* algorithm.
*/
- if (mkdir(basedir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(basedir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary directory \"%s\": %m",
basedir)));
/* Try again. */
- if (mkdir(directory, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(directory) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary subdirectory \"%s\": %m",
@@ -1602,11 +1597,11 @@ OpenTemporaryFileInTablespace(Oid tblspcOid, bool rejectError)
* We might need to create the tablespace's tempfile directory, if no
* one has yet done so.
*
- * Don't check for error from mkdir; it could fail if someone else
- * just did the same thing. If it doesn't work then we'll bomb out on
+ * Don't check error from MakeDirectoryDefaultPerm; it could fail if someone
+ * else just did the same thing. If it doesn't work then we'll bomb out on
* the second create attempt, instead.
*/
- mkdir(tempdirpath, S_IRWXU);
+ MakeDirectoryDefaultPerm(tempdirpath);
file = PathNameOpenFile(tempfilepath,
O_RDWR | O_CREAT | O_TRUNC | PG_BINARY);
@@ -3555,3 +3550,16 @@ fsync_parent_path(const char *fname, int elevel)
return 0;
}
+
+/*
+ * Create a directory using PG_DIR_MODE_DEFAULT for permissions
+ *
+ * Directories in PGDATA should normally have specific permissions -- when
+ * using this function the caller does not need to know what they should be.
+ * For permissions other than the default use mkdir() directly.
+ */
+int
+MakeDirectoryDefaultPerm(const char *directoryName)
+{
+ return mkdir(directoryName, PG_DIR_MODE_DEFAULT);
+}
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index 726db7b7f1..466613e4af 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -137,6 +137,10 @@ proc_exit(int code)
else
snprintf(gprofDirName, 32, "gprof/%d", (int) getpid());
+ /*
+ * Use mkdir() instead of MakeDirectoryDefaultPerm() here because non-default
+ * permissions are required.
+ */
mkdir("gprof", S_IRWXU | S_IRWXG | S_IRWXO);
mkdir(gprofDirName, S_IRWXU | S_IRWXG | S_IRWXO);
chdir(gprofDirName);
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 1f7f2aaa54..278e56bf95 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -64,6 +64,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_class.h"
#include "catalog/pg_collation.h"
+#include "common/file_perm.h"
#include "common/file_utils.h"
#include "common/restricted_token.h"
#include "common/username.h"
@@ -1170,7 +1171,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1190,7 +1191,7 @@ setup_config(void)
sprintf(path, "%s/postgresql.auto.conf", pg_data);
writefile(path, autoconflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1277,7 +1278,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_hba.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1293,7 +1294,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_ident.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -2692,7 +2693,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (pg_mkdir_p(pg_data, S_IRWXU) != 0)
+ if (pg_mkdir_p(pg_data, PG_DIR_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2710,7 +2711,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (chmod(pg_data, S_IRWXU) != 0)
+ if (chmod(pg_data, PG_DIR_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2778,7 +2779,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (pg_mkdir_p(xlog_dir, S_IRWXU) != 0)
+ if (pg_mkdir_p(xlog_dir, PG_DIR_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2796,7 +2797,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (chmod(xlog_dir, S_IRWXU) != 0)
+ if (chmod(xlog_dir, PG_DIR_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2846,7 +2847,7 @@ create_xlog_or_symlink(void)
else
{
/* Without -X option, just make the subdirectory normally */
- if (mkdir(subdirloc, S_IRWXU) < 0)
+ if (mkdir(subdirloc, PG_DIR_MODE_DEFAULT) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, subdirloc, strerror(errno));
@@ -2882,7 +2883,8 @@ initialize_data_directory(void)
setup_signals();
- umask(S_IRWXG | S_IRWXO);
+ /* Set the file mode creation mask */
+ umask(PG_MODE_MASK_DEFAULT);
create_data_directory();
@@ -2902,7 +2904,7 @@ initialize_data_directory(void)
* The parent directory already exists, so we only need mkdir() not
* pg_mkdir_p() here, which avoids some failure modes; cf bug #13853.
*/
- if (mkdir(path, S_IRWXU) < 0)
+ if (mkdir(path, PG_DIR_MODE_DEFAULT) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, path, strerror(errno));
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index c0cfa6e92c..3331560445 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 15;
+use Test::More tests => 16;
my $tempdir = TestLib::tempdir;
my $xlogdir = "$tempdir/pgxlog";
@@ -45,6 +45,8 @@ mkdir $datadir;
command_ok([ 'initdb', '-N', '-T', 'german', '-X', $xlogdir, $datadir ],
'successful creation');
+
+ ok(check_mode_recursive($datadir, 0700, 0600));
}
command_ok([ 'initdb', '-S', $datadir ], 'sync only');
command_fails([ 'initdb', $datadir ], 'existing data directory');
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index 9bc830b085..1eef7179cc 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -25,6 +25,7 @@
#include "catalog/pg_control.h"
#include "common/controldata_utils.h"
+#include "common/file_perm.h"
#include "getopt_long.h"
#include "utils/pidfile.h"
@@ -2170,7 +2171,7 @@ main(int argc, char **argv)
*/
argv0 = argv[0];
- umask(S_IRWXG | S_IRWXO);
+ umask(PG_MODE_MASK_DEFAULT);
/* support --help and --version even if invoked as root */
if (argc > 1)
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl
index 5da4746cb4..3d82abe696 100644
--- a/src/bin/pg_ctl/t/001_start_stop.pl
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -4,7 +4,7 @@ use warnings;
use Config;
use PostgresNode;
use TestLib;
-use Test::More tests => 19;
+use Test::More tests => 20;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -57,10 +57,15 @@ command_ok([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ], 'pg_ctl stop');
command_fails([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ],
'second pg_ctl stop fails');
+# Log file for first perm test
+my $logFileName = "$tempdir/data/perm-test-600.log";
+
command_ok(
- [ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
+ [ 'pg_ctl', 'restart', '-D', "$tempdir/data", '-l', $logFileName ],
'pg_ctl restart with server not running');
-command_ok([ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
- 'pg_ctl restart with server running');
+
+# Log file should exist and have no group permissions
+ok(-f $logFileName);
+ok(check_mode_recursive("$tempdir/data", 0700, 0600));
system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index a132cf2e9f..47abd41fc3 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -52,6 +52,7 @@
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
#include "common/fe_memutils.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "storage/large_object.h"
#include "pg_getopt.h"
@@ -327,6 +328,9 @@ main(int argc, char *argv[])
exit(1);
}
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_DEFAULT);
+
/* Check that data directory matches our server version */
CheckDataVersion();
@@ -919,7 +923,7 @@ RewriteControlFile(void)
fd = open(XLOG_CONTROL_FILE,
O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ PG_FILE_MODE_DEFAULT);
if (fd < 0)
{
fprintf(stderr, _("%s: could not create pg_control file: %s\n"),
@@ -1201,7 +1205,7 @@ WriteEmptyXLOG(void)
unlink(path);
fd = open(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ PG_FILE_MODE_DEFAULT);
if (fd < 0)
{
fprintf(stderr, _("%s: could not open file \"%s\": %s\n"),
diff --git a/src/bin/pg_resetwal/t/001_basic.pl b/src/bin/pg_resetwal/t/001_basic.pl
index 234bd70303..8f3f2990f3 100644
--- a/src/bin/pg_resetwal/t/001_basic.pl
+++ b/src/bin/pg_resetwal/t/001_basic.pl
@@ -4,7 +4,7 @@ use warnings;
use Config;
use PostgresNode;
use TestLib;
-use Test::More tests => 13;
+use Test::More tests => 14;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -36,6 +36,8 @@ is_deeply(
[sort(qw(. .. archive_status 000000010000000000000002))],
'WAL recreated');
+ok(check_mode_recursive($pgdata, 0700, 0600), 'check PGDATA permissions');
+
$node->start;
# Reset to specific WAL segment
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index 00b5b42dd7..1751de1a35 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -237,6 +237,9 @@ sub run_pg_rewind
"$tmp_folder/master-postgresql.conf.tmp",
"$master_pgdata/postgresql.conf");
+ chmod(0600, "$master_pgdata/postgresql.conf")
+ or die("unable to set permissions for $master_pgdata/postgresql.conf");
+
# Plug-in rewound node to the now-promoted standby node
my $port_standby = $node_standby->port;
$node_master->append_conf(
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 705383d184..d51914e901 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -18,6 +18,7 @@
#include <fcntl.h>
#include <unistd.h>
+#include "common/file_perm.h"
#include "file_ops.h"
#include "filemap.h"
#include "logging.h"
@@ -58,7 +59,7 @@ open_target_file(const char *path, bool trunc)
mode = O_WRONLY | O_CREAT | PG_BINARY;
if (trunc)
mode |= O_TRUNC;
- dstfd = open(dstpath, mode, 0600);
+ dstfd = open(dstpath, mode, PG_FILE_MODE_DEFAULT);
if (dstfd < 0)
pg_fatal("could not open target file \"%s\": %s\n",
dstpath, strerror(errno));
@@ -190,7 +191,7 @@ truncate_target_file(const char *path, off_t newsize)
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
- fd = open(dstpath, O_WRONLY, 0);
+ fd = open(dstpath, O_WRONLY, PG_FILE_MODE_DEFAULT);
if (fd < 0)
pg_fatal("could not open file \"%s\" for truncation: %s\n",
dstpath, strerror(errno));
@@ -211,7 +212,7 @@ create_target_dir(const char *path)
return;
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
- if (mkdir(dstpath, S_IRWXU) != 0)
+ if (mkdir(dstpath, PG_DIR_MODE_DEFAULT) != 0)
pg_fatal("could not create directory \"%s\": %s\n",
dstpath, strerror(errno));
}
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 72ab2f8d5e..3d179e2c7d 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -24,6 +24,7 @@
#include "access/xlog_internal.h"
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "getopt_long.h"
#include "storage/bufpage.h"
@@ -185,6 +186,9 @@ main(int argc, char **argv)
exit(1);
}
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_DEFAULT);
+
/*
* Don't allow pg_rewind to be run as root, to avoid overwriting the
* ownership of files in the data directory. We need only check for root
diff --git a/src/bin/pg_rewind/t/001_basic.pl b/src/bin/pg_rewind/t/001_basic.pl
index 736f34eae3..559dc53602 100644
--- a/src/bin/pg_rewind/t/001_basic.pl
+++ b/src/bin/pg_rewind/t/001_basic.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 8;
+use Test::More tests => 10;
use RewindTest;
@@ -86,6 +86,7 @@ in master, before promotion
),
'tail-copy');
+ ok (check_mode_recursive($node_master->data_dir(), 0700, 0600));
RewindTest::clean_rewind_test();
}
diff --git a/src/bin/pg_upgrade/file.c b/src/bin/pg_upgrade/file.c
index f38bfacf02..595d43ee31 100644
--- a/src/bin/pg_upgrade/file.c
+++ b/src/bin/pg_upgrade/file.c
@@ -10,6 +10,7 @@
#include "postgres_fe.h"
#include "access/visibilitymap.h"
+#include "common/file_perm.h"
#include "pg_upgrade.h"
#include "storage/bufpage.h"
#include "storage/checksum.h"
@@ -44,7 +45,7 @@ copyFile(const char *src, const char *dst,
schemaName, relName, src, strerror(errno));
if ((dest_fd = open(dst, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR)) < 0)
+ PG_FILE_MODE_DEFAULT)) < 0)
pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
schemaName, relName, dst, strerror(errno));
@@ -151,7 +152,7 @@ rewriteVisibilityMap(const char *fromfile, const char *tofile,
schemaName, relName, fromfile, strerror(errno));
if ((dst_fd = open(tofile, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR)) < 0)
+ PG_FILE_MODE_DEFAULT)) < 0)
pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
schemaName, relName, tofile, strerror(errno));
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index d12412799f..59df6fd88f 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -38,6 +38,7 @@
#include "pg_upgrade.h"
#include "catalog/pg_class.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "fe_utils/string_utils.h"
@@ -79,7 +80,7 @@ main(int argc, char **argv)
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_upgrade"));
/* Ensure that all files created by pg_upgrade are non-world-readable */
- umask(S_IRWXG | S_IRWXO);
+ umask(PG_MODE_MASK_DEFAULT);
parseCommandLine(argc, argv);
diff --git a/src/bin/pg_upgrade/test.sh b/src/bin/pg_upgrade/test.sh
index 39983abea1..4e8db4aaf4 100644
--- a/src/bin/pg_upgrade/test.sh
+++ b/src/bin/pg_upgrade/test.sh
@@ -230,6 +230,17 @@ standard_initdb 'initdb'
pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "${PGDATA}" -b "$oldbindir" -B "$bindir" -p "$PGPORT" -P "$PGPORT"
+# make sure all directories and files have group permissions
+if [ $(find ${PGDATA} -type f ! -perm 600 | wc -l) -ne 0 ]; then
+ echo "files in PGDATA with permission != 600";
+ exit 1;
+fi
+
+if [ $(find ${PGDATA} -type d ! -perm 700 | wc -l) -ne 0 ]; then
+ echo "directories in PGDATA with permission != 700";
+ exit 1;
+fi
+
pg_ctl start -l "$logdir/postmaster2.log" -o "$POSTMASTER_OPTS" -w
case $testhost in
diff --git a/src/include/common/file_perm.h b/src/include/common/file_perm.h
new file mode 100644
index 0000000000..3c6da0e000
--- /dev/null
+++ b/src/include/common/file_perm.h
@@ -0,0 +1,32 @@
+/*-------------------------------------------------------------------------
+ *
+ * File and directory permission constants
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/file_perm.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FILE_PERM_H
+#define FILE_PERM_H
+
+/*
+ * Default mode mask for data directory permissions that does not allow
+ * group execute/read.
+ */
+#define PG_MODE_MASK_DEFAULT (S_IRWXG | S_IRWXO)
+
+/*
+ * Default mode for created files.
+ */
+#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR)
+
+/*
+ * Default mode for directories.
+ */
+#define PG_DIR_MODE_DEFAULT S_IRWXU
+
+#endif /* FILE_PERM_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 4244e7b1fd..33c636471b 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -112,6 +112,9 @@ extern int CloseTransientFile(int fd);
extern int BasicOpenFile(const char *fileName, int fileFlags);
extern int BasicOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode);
+ /* Make a directory with default permissions */
+extern int MakeDirectoryDefaultPerm(const char *directoryName);
+
/* Miscellaneous support routines */
extern void InitFileAccess(void);
extern void set_max_safe_fds(void);
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 80188315f1..76e571b98c 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -484,6 +484,9 @@ sub append_conf
my $conffile = $self->data_dir . '/' . $filename;
TestLib::append_to_file($conffile, $str . "\n");
+
+ chmod(0600, $conffile)
+ or die("unable to set permissions for $conffile");
}
=pod
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index 7d017d49bd..9de8dc9041 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -12,8 +12,11 @@ use warnings;
use Config;
use Exporter 'import';
+use Fcntl qw(:mode);
use File::Basename;
+use File::Find;
use File::Spec;
+use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
use SimpleTee;
@@ -26,6 +29,7 @@ our @EXPORT = qw(
slurp_dir
slurp_file
append_to_file
+ check_mode_recursive
check_pg_config
system_or_bail
system_log
@@ -222,6 +226,75 @@ sub append_to_file
close $fh;
}
+# Check that all file/dir modes in a directory match the expected values,
+# ignoring the mode of any specified files.
+sub check_mode_recursive
+{
+ my ($dir, $expected_dir_mode, $expected_file_mode, $ignore_list) = @_;
+
+ # Result defaults to true
+ my $result = 1;
+
+ find
+ (
+ {follow_fast => 1,
+ wanted =>
+ sub
+ {
+ my $file_stat = stat($File::Find::name);
+
+ # Is file in the ignore list?
+ foreach my $ignore ($ignore_list ? @{$ignore_list} : [])
+ {
+ if ("$dir/$ignore" eq $File::Find::name)
+ {
+ return;
+ }
+ }
+
+ defined($file_stat)
+ or die("unable to stat $File::Find::name");
+
+ my $file_mode = S_IMODE($file_stat->mode);
+
+ # Is this a file?
+ if (S_ISREG($file_stat->mode))
+ {
+ if ($file_mode != $expected_file_mode)
+ {
+ print(*STDERR,
+ sprintf("$File::Find::name mode must be %04o\n",
+ $expected_file_mode));
+
+ $result = 0;
+ return;
+ }
+ }
+ # Else a directory?
+ elsif (S_ISDIR($file_stat->mode))
+ {
+ if ($file_mode != $expected_dir_mode)
+ {
+ print(*STDERR,
+ sprintf("$File::Find::name mode must be %04o\n",
+ $expected_dir_mode));
+
+ $result = 0;
+ return;
+ }
+ }
+ # Else something we can't handle
+ else
+ {
+ die "unknown file type for $File::Find::name";
+ }
+ }},
+ $dir
+ );
+
+ return $result;
+}
+
# Check presence of a given regexp within pg_config.h for the installation
# where tests are running, returning a match status result depending on
# that.
--
2.14.3 (Apple Git-98)
group-access-v10-03-group.patchtext/plain; charset=UTF-8; name=group-access-v10-03-group.patch; x-mac-creator=0; x-mac-type=0Download
From 2c3f01a060e217aed284c5bed5b9377a198e0817 Mon Sep 17 00:00:00 2001
From: David Steele <david@pgmasters.net>
Date: Fri, 9 Mar 2018 13:45:50 -0500
Subject: [PATCH 3/3] Allow group access on PGDATA.
This includes backend changes, utility changes (initdb, pg_ctl, pg_upgrade, etc.) and tests for each utility.
---
doc/src/sgml/backup.sgml | 6 ++++-
doc/src/sgml/ref/initdb.sgml | 19 ++++++++++++++
doc/src/sgml/runtime.sgml | 14 ++++++++++-
src/backend/postmaster/postmaster.c | 29 +++++++++++++---------
src/bin/initdb/initdb.c | 22 +++++++++++------
src/bin/initdb/t/001_initdb.pl | 13 +++++++++-
src/bin/pg_ctl/pg_ctl.c | 4 +++
src/bin/pg_ctl/t/001_start_stop.pl | 24 +++++++++++++++++-
src/bin/pg_resetwal/pg_resetwal.c | 4 +--
src/bin/pg_resetwal/t/001_basic.pl | 9 +++++--
src/bin/pg_rewind/RewindTest.pm | 9 ++++---
src/bin/pg_rewind/pg_rewind.c | 4 +--
src/bin/pg_rewind/t/002_databases.pl | 6 +++--
src/bin/pg_upgrade/pg_upgrade.c | 5 +++-
src/bin/pg_upgrade/test.sh | 14 +++++------
src/common/Makefile | 2 +-
src/common/file_perm.c | 48 ++++++++++++++++++++++++++++++++++++
src/include/common/file_perm.h | 31 +++++++++++++++++++----
src/test/perl/PostgresNode.pm | 27 +++++++++++++++++++-
src/test/perl/TestLib.pm | 25 +++++++++++++++++++
src/tools/msvc/Mkvcbuild.pm | 2 +-
21 files changed, 267 insertions(+), 50 deletions(-)
create mode 100644 src/common/file_perm.c
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index 9d8e69056f..a9912eb418 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -1095,7 +1095,11 @@ SELECT pg_stop_backup();
and <filename>postmaster.opts</filename>, which record information
about the running <application>postmaster</application>, not about the
<application>postmaster</application> which will eventually use this backup.
- (These files can confuse <application>pg_ctl</application>.)
+ (These files can confuse <application>pg_ctl</application>.) If group read
+ access is enabled on the data directory and an unprivileged user in the
+ <productname>PostgreSQL</productname> group is performing the backup, then
+ <filename>postmaster.pid</filename> will not be readable and must be
+ excluded.
</para>
<para>
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 585665f161..5f57b933b9 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -76,6 +76,14 @@ PostgreSQL documentation
to do so.)
</para>
+ <para>
+ For security reasons the new cluster created by <command>initdb</command>
+ will only be accessible by the cluster owner by default. The
+ <option>--allow-group-access</option> option allows any user in the same
+ group as the cluster owner to read files in the cluster. This is useful
+ for performing backups as a non-privileged user.
+ </para>
+
<para>
<command>initdb</command> initializes the database cluster's default
locale and character set encoding. The character set encoding,
@@ -301,6 +309,17 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-g</option></term>
+ <term><option>--allow-group-access</option></term>
+ <listitem>
+ <para>
+ Allows users in the same group as the cluster owner to read all cluster
+ files created by <command>initdb</command>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-W</option></term>
<term><option>--pwprompt</option></term>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 587b430527..40ce3d9813 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -137,7 +137,10 @@ postgres$ <userinput>initdb -D /usr/local/pgsql/data</userinput>
database, it is essential that it be secured from unauthorized
access. <command>initdb</command> therefore revokes access
permissions from everyone but the
- <productname>PostgreSQL</productname> user.
+ <productname>PostgreSQL</productname> user, and optionally, group.
+ Group access, when enabled, is read-only. This allows an unprivileged
+ user in the <productname>PostgreSQL</productname> group to take a backup of
+ the cluster data or perform other operations that only require read access.
</para>
<para>
@@ -2194,6 +2197,15 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
member of the group that has access to those certificate and key files.
</para>
+ <para>
+ If the data directory allows group read access then certificate files may
+ need to be located outside of the data directory in order to conform to the
+ security requirements outlined above. Generally, group access is enabled
+ to allow an unprivileged user to backup the database, and in that case the
+ backup software will not be able to read the certificate files and will
+ likely error.
+ </para>
+
<para>
If the private key is protected with a passphrase, the
server will prompt for the passphrase and will not start until it has
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 956960fd4b..9ce5535327 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -97,6 +97,7 @@
#include "access/xlog.h"
#include "bootstrap/bootstrap.h"
#include "catalog/pg_control.h"
+#include "common/file_perm.h"
#include "common/ip.h"
#include "lib/ilist.h"
#include "libpq/auth.h"
@@ -587,7 +588,8 @@ PostmasterMain(int argc, char *argv[])
IsPostmasterEnvironment = true;
/*
- * for security, no dir or file created can be group or other accessible
+ * By default, no dir or file created can be group or other accessible. This
+ * may be modified later depending in the permissions of the data directory.
*/
umask(S_IRWXG | S_IRWXO);
@@ -1521,25 +1523,30 @@ checkDataDir(void)
#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.
+ * Check if the directory has correct permissions. If not, reject.
*
* 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))
+ if (stat_buf.st_mode & PG_MODE_MASK_ALLOW_GROUP)
ereport(FATAL,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("data directory \"%s\" has group or world access",
+ errmsg("data directory \"%s\" has invalid permissions",
DataDir),
- errdetail("Permissions should be u=rwx (0700).")));
+ errdetail("Permissions should be u=rwx (0700) or u=rwx,g=rx (0750).")));
+#endif
+
+ /*
+ * Reset the file mode creation mask based on the mode of the data
+ * directory.
+ *
+ * Suppress when on Windows, because there may not be proper support
+ * for Unix-y file permissions.
+ */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+ umask(PG_MODE_MASK_DEFAULT & ~stat_buf.st_mode);
#endif
/* Look for PG_VERSION before looking for pg_control */
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 278e56bf95..f77b58bf74 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -145,6 +145,7 @@ static bool data_checksums = false;
static char *xlog_dir = NULL;
static char *str_wal_segment_size_mb = NULL;
static int wal_segment_size_mb;
+static mode_t file_mode_mask = PG_MODE_MASK_DEFAULT;
/* internal vars */
@@ -1171,7 +1172,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
+ if (chmod(path, PG_FILE_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1191,7 +1192,7 @@ setup_config(void)
sprintf(path, "%s/postgresql.auto.conf", pg_data);
writefile(path, autoconflines);
- if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
+ if (chmod(path, PG_FILE_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1278,7 +1279,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_hba.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
+ if (chmod(path, PG_FILE_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1294,7 +1295,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_ident.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
+ if (chmod(path, PG_FILE_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -2312,6 +2313,7 @@ usage(const char *progname)
printf(_(" --auth-local=METHOD default authentication method for local-socket connections\n"));
printf(_(" [-D, --pgdata=]DATADIR location for this database cluster\n"));
printf(_(" -E, --encoding=ENCODING set default encoding for new databases\n"));
+ printf(_(" -g, --allow-group-access allow group read/execute on data directory\n"));
printf(_(" --locale=LOCALE set default locale for new databases\n"));
printf(_(" --lc-collate=, --lc-ctype=, --lc-messages=LOCALE\n"
" --lc-monetary=, --lc-numeric=, --lc-time=LOCALE\n"
@@ -2711,7 +2713,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (chmod(pg_data, PG_DIR_MODE_DEFAULT) != 0)
+ if (chmod(pg_data, PG_DIR_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2797,7 +2799,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (chmod(xlog_dir, PG_DIR_MODE_DEFAULT) != 0)
+ if (chmod(xlog_dir, PG_DIR_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2884,7 +2886,7 @@ initialize_data_directory(void)
setup_signals();
/* Set the file mode creation mask */
- umask(PG_MODE_MASK_DEFAULT);
+ umask(file_mode_mask);
create_data_directory();
@@ -3018,6 +3020,7 @@ main(int argc, char *argv[])
{"waldir", required_argument, NULL, 'X'},
{"wal-segsize", required_argument, NULL, 12},
{"data-checksums", no_argument, NULL, 'k'},
+ {"allow-group-access", no_argument, NULL, 'g'},
{NULL, 0, NULL, 0}
};
@@ -3059,7 +3062,7 @@ main(int argc, char *argv[])
/* process command-line options */
- while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:", long_options, &option_index)) != -1)
+ while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:g", long_options, &option_index)) != -1)
{
switch (c)
{
@@ -3153,6 +3156,9 @@ main(int argc, char *argv[])
case 12:
str_wal_segment_size_mb = pg_strdup(optarg);
break;
+ case 'g':
+ file_mode_mask = PG_MODE_MASK_ALLOW_GROUP;
+ break;
default:
/* getopt_long already emitted a complaint */
fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index 3331560445..f08fc6ea8f 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -4,9 +4,11 @@
use strict;
use warnings;
+use Fcntl ':mode';
+use File::stat qw{lstat};
use PostgresNode;
use TestLib;
-use Test::More tests => 16;
+use Test::More tests => 18;
my $tempdir = TestLib::tempdir;
my $xlogdir = "$tempdir/pgxlog";
@@ -50,3 +52,12 @@ mkdir $datadir;
}
command_ok([ 'initdb', '-S', $datadir ], 'sync only');
command_fails([ 'initdb', $datadir ], 'existing data directory');
+
+# Init a new db with group access
+my $datadir_group = "$tempdir/data_group";
+
+command_ok(
+ [ 'initdb', '-g', $datadir_group ],
+ 'successful creation with group access');
+
+ok(check_mode_recursive($datadir_group, 0750, 0640));
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index 1eef7179cc..bb70e625fd 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -2171,6 +2171,7 @@ main(int argc, char **argv)
*/
argv0 = argv[0];
+ /* Set restrictive mode mask until PGDATA permissions are checked */
umask(PG_MODE_MASK_DEFAULT);
/* support --help and --version even if invoked as root */
@@ -2406,6 +2407,9 @@ main(int argc, char **argv)
snprintf(version_file, MAXPGPATH, "%s/PG_VERSION", pg_data);
snprintf(pid_file, MAXPGPATH, "%s/postmaster.pid", pg_data);
snprintf(backup_file, MAXPGPATH, "%s/backup_label", pg_data);
+
+ /* Set mask based on PGDATA permissions */
+ umask(DataDirectoryMask(pg_data));
}
switch (ctl_command)
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl
index 3d82abe696..7318c27c85 100644
--- a/src/bin/pg_ctl/t/001_start_stop.pl
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -2,9 +2,11 @@ use strict;
use warnings;
use Config;
+use Fcntl ':mode';
+use File::stat qw{lstat};
use PostgresNode;
use TestLib;
-use Test::More tests => 20;
+use Test::More tests => 24;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -68,4 +70,24 @@ command_ok(
ok(-f $logFileName);
ok(check_mode_recursive("$tempdir/data", 0700, 0600));
+# Log file for second perm test
+$logFileName = "$tempdir/data/perm-test-640.log";
+
+# Change the data dir mode so log file will be created with group read
+# privileges on the next start
+system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
+
+chmod_recursive("$tempdir/data", 0750, 0640);
+
+command_ok(
+ [ 'pg_ctl', 'start', '-D', "$tempdir/data", '-l', $logFileName ],
+ 'start server to check group permissions');
+
+ok(-f $logFileName);
+ok(check_mode_recursive("$tempdir/data", 0750, 0640, ['postmaster.pid']));
+
+command_ok(
+ [ 'pg_ctl', 'restart', '-D', "$tempdir/data", '-l', $logFileName ],
+ 'pg_ctl restart with server running');
+
system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 47abd41fc3..6a4dc502c4 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -328,8 +328,8 @@ main(int argc, char *argv[])
exit(1);
}
- /* Set dir/file mode mask */
- umask(PG_MODE_MASK_DEFAULT);
+ /* Set mask based on PGDATA permissions */
+ umask(DataDirectoryMask(DataDir));
/* Check that data directory matches our server version */
CheckDataVersion();
diff --git a/src/bin/pg_resetwal/t/001_basic.pl b/src/bin/pg_resetwal/t/001_basic.pl
index 8f3f2990f3..8736130731 100644
--- a/src/bin/pg_resetwal/t/001_basic.pl
+++ b/src/bin/pg_resetwal/t/001_basic.pl
@@ -4,7 +4,7 @@ use warnings;
use Config;
use PostgresNode;
use TestLib;
-use Test::More tests => 14;
+use Test::More tests => 15;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -40,9 +40,12 @@ ok(check_mode_recursive($pgdata, 0700, 0600), 'check PGDATA permissions');
$node->start;
-# Reset to specific WAL segment
+# Reset to specific WAL segment. Also enable group access to make sure files
+# and directories are created with group permissions.
$node->stop;
+chmod_recursive($pgdata, 0750, 0640);
+
$node->command_ok(
['pg_resetwal', '-l', '000000070000000700000007', '-D', $pgdata],
'set to specific WAL');
@@ -52,4 +55,6 @@ is_deeply(
[sort(qw(. .. archive_status 000000070000000700000007))],
'WAL recreated');
+ok(check_mode_recursive($pgdata, 0750, 0640), 'check PGDATA permissions');
+
$node->start;
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index 1751de1a35..9dab6bd7e8 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -115,11 +115,13 @@ sub check_query
sub setup_cluster
{
- my $extra_name = shift;
+ my $extra_name = shift; # Used to differentiate clusters
+ my $extra = shift; # Extra params for initdb
# Initialize master, data checksums are mandatory
$node_master = get_new_node('master' . ($extra_name ? "_${extra_name}" : ''));
- $node_master->init(allows_streaming => 1);
+ $node_master->init(
+ allows_streaming => 1, extra => $extra);
# Set wal_keep_segments to prevent WAL segment recycling after enforced
# checkpoints in the tests.
$node_master->append_conf('postgresql.conf', qq(
@@ -237,7 +239,8 @@ sub run_pg_rewind
"$tmp_folder/master-postgresql.conf.tmp",
"$master_pgdata/postgresql.conf");
- chmod(0600, "$master_pgdata/postgresql.conf")
+ chmod($node_master->group_access() ? 0640 : 0600,
+ "$master_pgdata/postgresql.conf")
or die("unable to set permissions for $master_pgdata/postgresql.conf");
# Plug-in rewound node to the now-promoted standby node
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 3d179e2c7d..11153e5f2f 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -186,8 +186,8 @@ main(int argc, char **argv)
exit(1);
}
- /* Set dir/file mode mask */
- umask(PG_MODE_MASK_DEFAULT);
+ /* Set mask based on PGDATA permissions */
+ umask(DataDirectoryMask(datadir_target));
/*
* Don't allow pg_rewind to be run as root, to avoid overwriting the
diff --git a/src/bin/pg_rewind/t/002_databases.pl b/src/bin/pg_rewind/t/002_databases.pl
index 37cdd712f3..570fa5cee0 100644
--- a/src/bin/pg_rewind/t/002_databases.pl
+++ b/src/bin/pg_rewind/t/002_databases.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 6;
use RewindTest;
@@ -9,7 +9,7 @@ sub run_test
{
my $test_mode = shift;
- RewindTest::setup_cluster($test_mode);
+ RewindTest::setup_cluster($test_mode, ['-g']);
RewindTest::start_master();
# Create a database in master.
@@ -42,6 +42,8 @@ template1
),
'database names');
+ ok (check_mode_recursive(
+ $node_master->data_dir(), 0750, 0640, ['postmaster.pid']));
RewindTest::clean_rewind_test();
}
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 59df6fd88f..54f54fb0a4 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -79,7 +79,7 @@ main(int argc, char **argv)
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_upgrade"));
- /* Ensure that all files created by pg_upgrade are non-world-readable */
+ /* Set default restrictive mask until new cluster permissions are read */
umask(PG_MODE_MASK_DEFAULT);
parseCommandLine(argc, argv);
@@ -100,6 +100,9 @@ main(int argc, char **argv)
check_cluster_compatibility(live_check);
+ /* Set mask based on new PGDATA permissions */
+ umask(DataDirectoryMask(new_cluster.pgdata));
+
check_and_dump_old_cluster(live_check);
diff --git a/src/bin/pg_upgrade/test.sh b/src/bin/pg_upgrade/test.sh
index 4e8db4aaf4..24631a91c6 100644
--- a/src/bin/pg_upgrade/test.sh
+++ b/src/bin/pg_upgrade/test.sh
@@ -20,9 +20,9 @@ unset MAKELEVEL
# Run a given "initdb" binary and overlay the regression testing
# authentication configuration.
standard_initdb() {
- # To increase coverage of non-standard segment size without
- # increase test runtime, run these tests with a lower setting.
- "$1" -N --wal-segsize 1
+ # To increase coverage of non-standard segment size and group access
+ # without increasing test runtime, run these tests with a custom setting.
+ "$1" -N --wal-segsize 1 -g
if [ -n "$TEMP_CONFIG" -a -r "$TEMP_CONFIG" ]
then
cat "$TEMP_CONFIG" >> "$PGDATA/postgresql.conf"
@@ -231,13 +231,13 @@ standard_initdb 'initdb'
pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "${PGDATA}" -b "$oldbindir" -B "$bindir" -p "$PGPORT" -P "$PGPORT"
# make sure all directories and files have group permissions
-if [ $(find ${PGDATA} -type f ! -perm 600 | wc -l) -ne 0 ]; then
- echo "files in PGDATA with permission != 600";
+if [ $(find ${PGDATA} -type f ! -perm 640 | wc -l) -ne 0 ]; then
+ echo "files in PGDATA with permission != 640";
exit 1;
fi
-if [ $(find ${PGDATA} -type d ! -perm 700 | wc -l) -ne 0 ]; then
- echo "directories in PGDATA with permission != 700";
+if [ $(find ${PGDATA} -type d ! -perm 750 | wc -l) -ne 0 ]; then
+ echo "directories in PGDATA with permission != 750";
exit 1;
fi
diff --git a/src/common/Makefile b/src/common/Makefile
index 80e78d72fe..504375bef4 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -51,7 +51,7 @@ else
OBJS_COMMON += sha2.o
endif
-OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o file_utils.o restricted_token.o
+OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o file_perm.o file_utils.o restricted_token.o
OBJS_SRV = $(OBJS_COMMON:%.o=%_srv.o)
diff --git a/src/common/file_perm.c b/src/common/file_perm.c
new file mode 100644
index 0000000000..f01d43725e
--- /dev/null
+++ b/src/common/file_perm.c
@@ -0,0 +1,48 @@
+/*-------------------------------------------------------------------------
+ *
+ * File and directory permission routines
+ *
+ * Group read/execute may optional be enabled on PGDATA so any frontend tools
+ * That write into PGDATA must know what mask to set and the permissions to
+ * use for creating files and directories.
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/file_perm.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#error "This file is not expected to be compiled for backend code"
+#endif
+
+#include <sys/stat.h>
+
+#include "common/file_perm.h"
+
+/*
+ * Determine the mask to use when writing to PGDATA.
+ *
+ * Errors are not handled here and should be checked by the frontend
+ * application.
+ */
+mode_t
+DataDirectoryMask(const char *dataDir)
+{
+ struct stat statBuf;
+
+ /*
+ * If an error occurs getting the mode then return the more restrictive mask.
+ * It is the reponsibility of the frontend application to generate an error.
+ */
+ if (stat(dataDir, &statBuf) != 0)
+ return PG_MODE_MASK_DEFAULT;
+
+ /*
+ * Construct the mask that the caller should pass to umask().
+ */
+ return PG_MODE_MASK_DEFAULT & ~statBuf.st_mode;
+}
diff --git a/src/include/common/file_perm.h b/src/include/common/file_perm.h
index 3c6da0e000..35dcbe6108 100644
--- a/src/include/common/file_perm.h
+++ b/src/include/common/file_perm.h
@@ -1,6 +1,10 @@
/*-------------------------------------------------------------------------
*
- * File and directory permission constants
+ * File and directory permission constants and routines
+ *
+ * Group read/execute may optional be enabled on PGDATA so any frontend tools
+ * That write into PGDATA must know what mask to set and the permissions to
+ * use for creating files and directories.
*
*
* Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
@@ -20,13 +24,30 @@
#define PG_MODE_MASK_DEFAULT (S_IRWXG | S_IRWXO)
/*
- * Default mode for created files.
+ * Optional mode mask for data directory permissions that allows group
+ * read/execute.
+ */
+#define PG_MODE_MASK_ALLOW_GROUP (S_IWGRP | S_IRWXO)
+
+/*
+ * Default mode for created files, unless something else is specified using
+ * the *Perm() function variants.
+ */
+#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR | S_IRGRP)
+
+/*
+ * Default mode for directories created with MakeDirectoryDefaultPerm().
*/
-#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR)
+#define PG_DIR_MODE_DEFAULT (S_IRWXU | S_IRGRP | S_IXGRP)
+
+#ifdef FRONTEND
/*
- * Default mode for directories.
+ * Determine the mask to use when writing to PGDATA
*/
-#define PG_DIR_MODE_DEFAULT S_IRWXU
+mode_t DataDirectoryMask(const char *dataDir);
+
+#endif /* FRONTEND */
+
#endif /* FILE_PERM_H */
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 76e571b98c..1bd91524d7 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -86,9 +86,11 @@ use Carp;
use Config;
use Cwd;
use Exporter 'import';
+use Fcntl qw(:mode);
use File::Basename;
use File::Path qw(rmtree);
use File::Spec;
+use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
use RecursiveCopy;
@@ -269,6 +271,26 @@ sub connstr
=pod
+=item $node->group_access()
+
+Does the data dir allow group access?
+
+=cut
+
+sub group_access
+{
+ my ($self) = @_;
+
+ my $dir_stat = stat($self->data_dir);
+
+ defined($dir_stat)
+ or die('unable to stat ' . $self->data_dir);
+
+ return (S_IMODE($dir_stat->mode) == 0750);
+}
+
+=pod
+
=item $node->data_dir()
Returns the path to the data directory. postgresql.conf and pg_hba.conf are
@@ -460,6 +482,9 @@ sub init
}
close $conf;
+ chmod($self->group_access ? 0640 : 0600, "$pgdata/postgresql.conf")
+ or die("unable to set permissions for $pgdata/postgresql.conf");
+
$self->set_replication_conf if $params{allows_streaming};
$self->enable_archiving if $params{has_archiving};
}
@@ -485,7 +510,7 @@ sub append_conf
TestLib::append_to_file($conffile, $str . "\n");
- chmod(0600, $conffile)
+ chmod($self->group_access() ? 0640 : 0600, $conffile)
or die("unable to set permissions for $conffile");
}
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index 9de8dc9041..93ac24e261 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -30,6 +30,7 @@ our @EXPORT = qw(
slurp_file
append_to_file
check_mode_recursive
+ chmod_recursive
check_pg_config
system_or_bail
system_log
@@ -295,6 +296,30 @@ sub check_mode_recursive
return $result;
}
+# Change mode recursively on a directory
+sub chmod_recursive
+{
+ my ($dir, $dir_mode, $file_mode) = @_;
+
+ find
+ (
+ {follow_fast => 1,
+ wanted =>
+ sub
+ {
+ my $file_stat = stat($File::Find::name);
+
+ if (defined($file_stat))
+ {
+ chmod(S_ISDIR($file_stat->mode) ? $dir_mode : $file_mode,
+ $File::Find::name)
+ or die "unable to chmod $File::Find::name";
+ }
+ }},
+ $dir
+ );
+}
+
# Check presence of a given regexp within pg_config.h for the installation
# where tests are running, returning a match status result depending on
# that.
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 72976f44d8..78d4934ec6 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -125,7 +125,7 @@ sub mkvcbuild
}
our @pgcommonfrontendfiles = (
- @pgcommonallfiles, qw(fe_memutils.c file_utils.c
+ @pgcommonallfiles, qw(fe_memutils.c file_perm.c file_utils.c
restricted_token.c));
our @pgcommonbkndfiles = @pgcommonallfiles;
--
2.14.3 (Apple Git-98)
On Fri, Mar 09, 2018 at 01:51:14PM -0500, David Steele wrote:
How about a GUC that enforces one mode or the other on startup? Default
would be 700. The GUC can be set automatically by initdb based on the
-g option. We had this GUC originally, but since the front-end tools
can't read it we abandoned it. Seems like it would be good as an
enforcing mechanism, though.
Hm. OK. I can see the whole set of points about that. Please let me
think a bit more about that bit. Do you think that there could be a
pool of users willing to switch from one mode to another? Compared to
your v1, we could indeed have a GUC which enforces a restriction to not
allow group access, and enabled by default. As the commit fest is
running and we don't have a clear picture yet, I am afraid that it may
be better to move that to v12, and focus on getting patches 1 and 2
committed. This will provide a good base for the next move.
There are three places where things are still not correct:
- if (chmod(location, S_IRWXU) != 0)
+ current_umask = umask(0);
+ umask(current_umask);
+
+ if (chmod(location, PG_DIR_MODE_DEFAULT & ~current_umask) != 0)
This is in tablespace.c.
@@ -185,6 +186,9 @@ main(int argc, char **argv)
exit(1);
}
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_DEFAULT);
+
In pg_rewind and pg_resetwal, isn't that also a portion which is not
necessary without the group access feature?
This is all I have basically for patch 2, which would be good for
shipping.
--
Michael
Michael, David,
* Michael Paquier (michael@paquier.xyz) wrote:
On Fri, Mar 09, 2018 at 01:51:14PM -0500, David Steele wrote:
How about a GUC that enforces one mode or the other on startup? Default
would be 700. The GUC can be set automatically by initdb based on the
-g option. We had this GUC originally, but since the front-end tools
can't read it we abandoned it. Seems like it would be good as an
enforcing mechanism, though.Hm. OK. I can see the whole set of points about that. Please let me
think a bit more about that bit. Do you think that there could be a
pool of users willing to switch from one mode to another? Compared to
your v1, we could indeed have a GUC which enforces a restriction to not
allow group access, and enabled by default. As the commit fest is
running and we don't have a clear picture yet, I am afraid that it may
be better to move that to v12, and focus on getting patches 1 and 2
committed. This will provide a good base for the next move.
We already had a discussion about having a GUC for this and concluded,
rightly in my view, that it's not sensible to have since we don't want
all of the various tools having to read and parse out postgresql.conf.
I don't see anything in the discussion which has changed that and I
don't agree that there's an issue with using the privileges on the data
directory for this- it's a simple solution which all of the tools can
use and work with easily. I certainly don't agree that it's a serious
issue to relax the explicit check- it's just a check, which a user could
implement themselves if they wished to and had a concern for. On the
other hand, with the explicit check, we are actively preventing an
entirely reasonable goal of wanting to use a read-only role to perform a
backup of the system.
Thanks!
Stephen
On Mon, Mar 12, 2018 at 03:14:13PM -0400, Stephen Frost wrote:
We already had a discussion about having a GUC for this and concluded,
rightly in my view, that it's not sensible to have since we don't want
all of the various tools having to read and parse out postgresql.conf.
If the problem is parsing, it could as well be more portable to put that
in the control file, no? I have finished for example finished by
implementing my own flavor of pg_controldata to save parsing efforts
soas it prints control file fields on a per-call basis using individual
fields, which saved also games with locales for as translations of
pg_controldata can disturb the parsing logic.
I don't see anything in the discussion which has changed that and I
don't agree that there's an issue with using the privileges on the data
directory for this- it's a simple solution which all of the tools can
use and work with easily. I certainly don't agree that it's a serious
issue to relax the explicit check- it's just a check, which a user could
implement themselves if they wished to and had a concern for. On the
other hand, with the explicit check, we are actively preventing an
entirely reasonable goal of wanting to use a read-only role to perform a
backup of the system.
Well, one thing is that the current checks in the postmaster make sure
that a data folder is never using anything else than 0700. From a
security point of view, making it possible to allow a postmaster to
start with 0750 is a step backwards if users don't authorize it
explicitely. There are a lot of systems which use a bunch of users with
only single group with systemd. So this would remove an existing
safeguard. I am not against the idea of this thread, just that I think
that secured defaults should be user-enforceable if they want Postgres
to behave so.
--
Michael
Michael,
* Michael Paquier (michael@paquier.xyz) wrote:
On Mon, Mar 12, 2018 at 03:14:13PM -0400, Stephen Frost wrote:
We already had a discussion about having a GUC for this and concluded,
rightly in my view, that it's not sensible to have since we don't want
all of the various tools having to read and parse out postgresql.conf.If the problem is parsing, it could as well be more portable to put that
in the control file, no? I have finished for example finished by
implementing my own flavor of pg_controldata to save parsing efforts
soas it prints control file fields on a per-call basis using individual
fields, which saved also games with locales for as translations of
pg_controldata can disturb the parsing logic.
Then we'd need a tool to allow changing it for people who do want to
change it. There's no reason we should have to have an extra tool for
this- an administrator who chooses to change the privileges on the data
folder should be able to do so and I don't think anyone is going to
thank us for requiring them to use some special tool to do so for
PostgreSQL.
I don't see anything in the discussion which has changed that and I
don't agree that there's an issue with using the privileges on the data
directory for this- it's a simple solution which all of the tools can
use and work with easily. I certainly don't agree that it's a serious
issue to relax the explicit check- it's just a check, which a user could
implement themselves if they wished to and had a concern for. On the
other hand, with the explicit check, we are actively preventing an
entirely reasonable goal of wanting to use a read-only role to perform a
backup of the system.Well, one thing is that the current checks in the postmaster make sure
that a data folder is never using anything else than 0700. From a
security point of view, making it possible to allow a postmaster to
start with 0750 is a step backwards if users don't authorize it
explicitely. There are a lot of systems which use a bunch of users with
only single group with systemd. So this would remove an existing
safeguard. I am not against the idea of this thread, just that I think
that secured defaults should be user-enforceable if they want Postgres
to behave so.
I'm aware of what the current one-time check in the postmaster does, and
that we don't implement it on all platforms, making me seriously doubt
that the level of concern being raised here makes sense. Should we
consider it a security issue that the Windows builds don't perform this
check, and never has?
Further, if the permissions are changed without authorization, it's
probably done while the database is running and unlikely to be noticed
for days, weeks, or longer, if the administrator is depending on PG to
let them know of the change. Considering that the only user who can
change the privileges is a database owner or root, it seems even less
likely to help (why would an attacker change those privileges when they
already have full access?).
Lastly, the user *is* able to enforce the privileges on the data
directory if they wish to, using tools such as tripwire which are built
specifically to provide such checks and to do so regularly instead of
the extremely ad-hoc check provided by PG.
PostgreSQL should, and does, secure the data directory when it's created
by initdb, and subsequent files and directories are similairly secured
appropriately. Ultimately, the default which makes sense here isn't a
one-size-fits-all but is system dependent and the administrator should
be able to choose what permissions they want to have.
Thanks!
Stephen
On 3/13/18 2:46 AM, Michael Paquier wrote:
On Mon, Mar 12, 2018 at 03:14:13PM -0400, Stephen Frost wrote:
We already had a discussion about having a GUC for this and concluded,
rightly in my view, that it's not sensible to have since we don't want
all of the various tools having to read and parse out postgresql.conf.If the problem is parsing, it could as well be more portable to put that
in the control file, no?
The current approach is based on early discussion of this patch, around
[1]: /messages/by-id/20526.1489428968@sss.pgh.pa.us
there wasn't any interest in the idea.
I definitely think it's overkill to put a field in pg_control as that
requires more tooling to update values.
I don't see anything in the discussion which has changed that and I
don't agree that there's an issue with using the privileges on the data
directory for this- it's a simple solution which all of the tools can
use and work with easily. I certainly don't agree that it's a serious
issue to relax the explicit check- it's just a check, which a user could
implement themselves if they wished to and had a concern for. On the
other hand, with the explicit check, we are actively preventing an
entirely reasonable goal of wanting to use a read-only role to perform a
backup of the system.Well, one thing is that the current checks in the postmaster make sure
that a data folder is never using anything else than 0700. From a
security point of view, making it possible to allow a postmaster to
start with 0750 is a step backwards if users don't authorize it
explicitely.
I would argue that changing the mode of PGDATA is explicit, even if it
is accidental. To be clear, after a pg_upgrade the behavior of the
cluster WRT to setting the mode would be exactly the same as now. The
user would need to specify -g at initdb time or explicitly update PGDATA
to 750 for group access to be enabled.
There are a lot of systems which use a bunch of users with
only single group with systemd. So this would remove an existing
safeguard. I am not against the idea of this thread, just that I think
that secured defaults should be user-enforceable if they want Postgres
to behave so.
As Stephen notes, this can be enforced by the user if they want to, and
without much effort (and with better tools).
Regards,
--
-David
david@pgmasters.net
[1]: /messages/by-id/20526.1489428968@sss.pgh.pa.us
[2]: /messages/by-id/22248.1489431803@sss.pgh.pa.us
Stephen Frost <sfrost@snowman.net> writes:
* Michael Paquier (michael@paquier.xyz) wrote:
If the problem is parsing, it could as well be more portable to put that
in the control file, no?
Then we'd need a tool to allow changing it for people who do want to
change it. There's no reason we should have to have an extra tool for
this- an administrator who chooses to change the privileges on the data
folder should be able to do so and I don't think anyone is going to
thank us for requiring them to use some special tool to do so for
PostgreSQL.
FWIW, I took a quick look through this patch and don't have any problem
with the approach, which appears to be "use the data directory's
startup-time permissions as the status indicator". I am kinda wondering
about this though:
+ (These files can confuse <application>pg_ctl</application>.) If group read
+ access is enabled on the data directory and an unprivileged user in the
+ <productname>PostgreSQL</productname> group is performing the backup, then
+ <filename>postmaster.pid</filename> will not be readable and must be
+ excluded.
If we're allowing group read on the data directory, why should that not
include postmaster.pid? There's nothing terribly security-relevant in
there, and being able to find out the postmaster PID seems useful. I can
see the point of restricting server key files, as documented elsewhere,
but it's possible to keep those somewhere else so they don't cause
problems for simple backup software.
Also, in general, this patch desperately needs a round of copy-editing,
particularly with attention to the comments. For instance, there are a
minimum of three grammatical errors in this one comment:
+ * Group read/execute may optional be enabled on PGDATA so any frontend tools
+ * That write into PGDATA must know what mask to set and the permissions to
+ * use for creating files and directories.
In a larger sense, this fails to explain the operating principle,
namely what I stated above, that we allow the existing permissions
on PGDATA to decide what we allow as group permissions. If you
don't already understand that, you're going to have a hard time
extracting it from either file_perm.h or file_perm.c. (The latter
fails to even explain what the argument of DataDirectoryMask is.)
regards, tom lane
On 3/13/18 11:00 AM, Tom Lane wrote:
Stephen Frost <sfrost@snowman.net> writes:
* Michael Paquier (michael@paquier.xyz) wrote:
If the problem is parsing, it could as well be more portable to put that
in the control file, no?Then we'd need a tool to allow changing it for people who do want to
change it. There's no reason we should have to have an extra tool for
this- an administrator who chooses to change the privileges on the data
folder should be able to do so and I don't think anyone is going to
thank us for requiring them to use some special tool to do so for
PostgreSQL.FWIW, I took a quick look through this patch and don't have any problem
with the approach, which appears to be "use the data directory's
startup-time permissions as the status indicator". I am kinda wondering
about this though:+ (These files can confuse <application>pg_ctl</application>.) If group read + access is enabled on the data directory and an unprivileged user in the + <productname>PostgreSQL</productname> group is performing the backup, then + <filename>postmaster.pid</filename> will not be readable and must be + excluded.If we're allowing group read on the data directory, why should that not
include postmaster.pid? There's nothing terribly security-relevant in
there, and being able to find out the postmaster PID seems useful. I can
see the point of restricting server key files, as documented elsewhere,
but it's possible to keep those somewhere else so they don't cause
problems for simple backup software.
I'm OK with that. I had a look at the warnings regarding the required
mode of postmaster.pid in miscinit.c (889-911) and it seems to me they
still hold true if the mode is 640 instead of 600.
Do you agree, Tom? Stephen?
If so, I'll make those changes.
Also, in general, this patch desperately needs a round of copy-editing,
particularly with attention to the comments. For instance, there are a
minimum of three grammatical errors in this one comment:+ * Group read/execute may optional be enabled on PGDATA so any frontend tools + * That write into PGDATA must know what mask to set and the permissions to + * use for creating files and directories.
In a larger sense, this fails to explain the operating principle,
namely what I stated above, that we allow the existing permissions
on PGDATA to decide what we allow as group permissions. If you
don't already understand that, you're going to have a hard time
extracting it from either file_perm.h or file_perm.c. (The latter
fails to even explain what the argument of DataDirectoryMask is.)
I'll do comment/doc review for the next round of patches.
Thanks,
--
-David
david@pgmasters.net
Greetings,
* David Steele (david@pgmasters.net) wrote:
On 3/13/18 11:00 AM, Tom Lane wrote:
Stephen Frost <sfrost@snowman.net> writes:
* Michael Paquier (michael@paquier.xyz) wrote:
If the problem is parsing, it could as well be more portable to put that
in the control file, no?Then we'd need a tool to allow changing it for people who do want to
change it. There's no reason we should have to have an extra tool for
this- an administrator who chooses to change the privileges on the data
folder should be able to do so and I don't think anyone is going to
thank us for requiring them to use some special tool to do so for
PostgreSQL.FWIW, I took a quick look through this patch and don't have any problem
with the approach, which appears to be "use the data directory's
startup-time permissions as the status indicator". I am kinda wondering
about this though:+ (These files can confuse <application>pg_ctl</application>.) If group read + access is enabled on the data directory and an unprivileged user in the + <productname>PostgreSQL</productname> group is performing the backup, then + <filename>postmaster.pid</filename> will not be readable and must be + excluded.If we're allowing group read on the data directory, why should that not
include postmaster.pid? There's nothing terribly security-relevant in
there, and being able to find out the postmaster PID seems useful. I can
see the point of restricting server key files, as documented elsewhere,
but it's possible to keep those somewhere else so they don't cause
problems for simple backup software.I'm OK with that. I had a look at the warnings regarding the required
mode of postmaster.pid in miscinit.c (889-911) and it seems to me they
still hold true if the mode is 640 instead of 600.Do you agree, Tom? Stephen?
If so, I'll make those changes.
I agree that we can still consider an EPERM-error case as being ok even
with the changes proposed, and with the same-user check happening
earlier in checkDataDir(), we won't even get to the point of looking at
the pid file if the userid's don't match. The historical comment about
the old datadir permissions can likely just be removed, perhaps replaced
with a bit more commentary above that check in checkDataDir(). The
open() call should also fail if we only have group-read privileges on
the file (0640), but surely the kill() will in any case.
Thanks!
Stephen
Hi Michael,
On 3/12/18 3:28 AM, Michael Paquier wrote:
On Fri, Mar 09, 2018 at 01:51:14PM -0500, David Steele wrote:
How about a GUC that enforces one mode or the other on startup? Default
would be 700. The GUC can be set automatically by initdb based on the
-g option. We had this GUC originally, but since the front-end tools
can't read it we abandoned it. Seems like it would be good as an
enforcing mechanism, though.Hm. OK. I can see the whole set of points about that. Please let me
think a bit more about that bit. Do you think that there could be a
pool of users willing to switch from one mode to another? Compared to
your v1, we could indeed have a GUC which enforces a restriction to not
allow group access, and enabled by default. As the commit fest is
running and we don't have a clear picture yet, I am afraid that it may
be better to move that to v12, and focus on getting patches 1 and 2
committed. This will provide a good base for the next move.There are three places where things are still not correct:
- if (chmod(location, S_IRWXU) != 0) + current_umask = umask(0); + umask(current_umask); + + if (chmod(location, PG_DIR_MODE_DEFAULT & ~current_umask) != 0) This is in tablespace.c.
I have moved this hunk to 03 and used only PG_DIR_MODE_DEFAULT instead.
@@ -185,6 +186,9 @@ main(int argc, char **argv)
exit(1);
}+ /* Set dir/file mode mask */ + umask(PG_MODE_MASK_DEFAULT); + In pg_rewind and pg_resetwal, isn't that also a portion which is not necessary without the group access feature?
These seem like a good idea to me with or without patch 03. Some of our
front-end tools (initdb, pg_upgrade) were setting umask and others
weren't. I think it's more consistent (and safer) if they all do, at
least if they are writing into PGDATA.
This is all I have basically for patch 2, which would be good for
shipping.
Thanks!
I'll attach new patches in a reply to [1]/messages/by-id/22928.1520953220@sss.pgh.pa.us once I have made the changes
Tom requested.
--
-David
david@pgmasters.net
David Steele <david@pgmasters.net> writes:
On 3/12/18 3:28 AM, Michael Paquier wrote:
In pg_rewind and pg_resetwal, isn't that also a portion which is not
necessary without the group access feature?
These seem like a good idea to me with or without patch 03. Some of our
front-end tools (initdb, pg_upgrade) were setting umask and others
weren't. I think it's more consistent (and safer) if they all do, at
least if they are writing into PGDATA.
+1 ... see a926eb84e for an example of how easy it is to screw up if
the process's overall umask is permissive.
regards, tom lane
On 03/13/2018 10:40 AM, Stephen Frost wrote:
* Michael Paquier (michael@paquier.xyz) wrote:
Well, one thing is that the current checks in the postmaster make sure
that a data folder is never using anything else than 0700. From a
security point of view, making it possible to allow a postmaster to
start with 0750 is a step backwards ...
Lastly, the user *is* able to enforce the privileges on the data
directory if they wish to, using tools such as tripwire which are built
specifically to provide such checks and to do so regularly instead of
the extremely ad-hoc check provided by PG.... Ultimately, the default which makes sense here isn't a
one-size-fits-all but is system dependent and the administrator should
be able to choose what permissions they want to have.
Hear, hear. Returning for a moment again to
/messages/by-id/8b16cc30-0187-311e-04b5-1f32446d89dd@anastigmatix.net
we see that a stat() returning mode 0750 on a modern system may not
even mean there is any group access at all. In that example, the
datadir had these permissions:
# getfacl .
# file: .
# owner: postgres
# group: postgres
user::rwx
user:root:r-x
group::---
mask::r-x
other::---
While PostgreSQL does its stat() and interprets the mode as if it is
still on a Unix box from the '80s, two things have changed underneath
it: POSIX ACLs and Linux capabilities. Capabilities take the place of
the former specialness of root, who now needs to be granted r-x
explicitly in the ACL to be able to read stuff there at all, and there
is clearly no access to group and no access to other. It would be hard
for anybody to call this an insecure configuration. But when you stat()
an object with a POSIX ACL, you get the 'mask' value where the 'group'
bits used to go, so postgres sees this as 0750, thinks the 5 represents
group access, and refuses to start. Purely a mistake.
It's the kind of mistake that is inherent in this sort of check,
which tries to draw conclusions from the semantics it assumes, while
systems evolve and semantics march along. One hypothetical fix would
be to add:
#ifdef HAS_POSIX_ACLS
... check if there's really an ACL here, and the S_IRWXG bits are
really just the mask, or even try to pass judgment on whether the
admin's chosen ACL is adequately secure ...
#endif
but then sooner or later it will still end up making assumptions
that aren't true under, say, SELinux, so there's another #ifdef,
and where does it end?
On 03/13/2018 11:00 AM, Tom Lane wrote:
In a larger sense, this fails to explain the operating principle,
namely what I stated above, that we allow the existing permissions
on PGDATA to decide what we allow as group permissions.
I admit I've only skimmed the patches, trying to determine what
that will mean in practice. In a case like the ACL example above,
does this mean that postgres will stat PGDATA, conclude incorrectly
that group access is granted, and then, based on that, actually go
granting unwanted group access for real on newly-created files
and directories?
That doesn't seem ideal.
On 03/13/2018 10:45 AM, David Steele wrote:
As Stephen notes, this can be enforced by the user if they want to,
and without much effort (and with better tools).
To me, that seems really the key. An --allow-group-access option is
nice (but, as we see, misleading if its assumptions are outdated
regarding how the filesystem works), but I would plug even harder for
a --permission-transparency option, which would just assume that the
admin is making security arrangements, through mechanisms that
postgres may or may not even understand. The admin can create ACLs
with default entries that propagate to newly created objects.
SELinux contexts can work in similar ways. The admin controls the
umask inherited by the postmaster. A --permission-transparency option
would simply use open and mkdir in the traditional ways, allowing
the chosen umask, ACL defaults, SELinux contexts, etc., to do their
thing, and would avoid then stepping on the results with explicit
chmods (and of course skip the I-refuse-to-start-because-I-
misunderstand-your-setup check).
It wouldn't be for every casual install, but it would be the best
way to stay out of the way of an admin securing a system with modern
facilities.
A lot of the design effort put into debating what postgres itself
should or shouldn't insist on could then, perhaps, go into writing
postgres-specific configuration rule packages for some of those
better configuration-checking tools, and there it might even be
possible to write tests that incorporate knowledge of ACLs, SELinux,
etc.
-Chap
Greetings Chapman,
* Chapman Flack (chap@anastigmatix.net) wrote:
On 03/13/2018 10:40 AM, Stephen Frost wrote:
... Ultimately, the default which makes sense here isn't a
one-size-fits-all but is system dependent and the administrator should
be able to choose what permissions they want to have.Hear, hear. Returning for a moment again to
/messages/by-id/8b16cc30-0187-311e-04b5-1f32446d89dd@anastigmatix.net
we see that a stat() returning mode 0750 on a modern system may not
even mean there is any group access at all. In that example, the
datadir had these permissions:
Yes, that's true, but PG has never paid attention to POSIX ACLs or Linux
capabilities. Changing it to do so is quite a bit beyond the scope of
this particular patch and I don't see anything in what this patch is
doing which would preclude someone from putting in that effort in the
future.
While PostgreSQL does its stat() and interprets the mode as if it is
still on a Unix box from the '80s, two things have changed underneath
it: POSIX ACLs and Linux capabilities. Capabilities take the place of
the former specialness of root, who now needs to be granted r-x
explicitly in the ACL to be able to read stuff there at all, and there
is clearly no access to group and no access to other. It would be hard
for anybody to call this an insecure configuration. But when you stat()
an object with a POSIX ACL, you get the 'mask' value where the 'group'
bits used to go, so postgres sees this as 0750, thinks the 5 represents
group access, and refuses to start. Purely a mistake.
I'll point out that PG does run on quite a few other OS's beyond Linux.
It's the kind of mistake that is inherent in this sort of check,
which tries to draw conclusions from the semantics it assumes, while
systems evolve and semantics march along. One hypothetical fix would
be to add:#ifdef HAS_POSIX_ACLS
... check if there's really an ACL here, and the S_IRWXG bits are
really just the mask, or even try to pass judgment on whether the
admin's chosen ACL is adequately secure ...
#endifbut then sooner or later it will still end up making assumptions
that aren't true under, say, SELinux, so there's another #ifdef,
and where does it end?
I agree with this general concern.
On 03/13/2018 11:00 AM, Tom Lane wrote:
In a larger sense, this fails to explain the operating principle,
namely what I stated above, that we allow the existing permissions
on PGDATA to decide what we allow as group permissions.I admit I've only skimmed the patches, trying to determine what
that will mean in practice. In a case like the ACL example above,
does this mean that postgres will stat PGDATA, conclude incorrectly
that group access is granted, and then, based on that, actually go
granting unwanted group access for real on newly-created files
and directories?
PG will stat PGDATA and conclude that the system is saying that group
access has been granted on PGDATA and will do the same for subsequent
files created later on. This is new in PG, so there isn't any concern
about this causing problems in an existing environment- you couldn't
have had those ACLs on an existing PGDATA dir in the first place, as you
note above. The only issue that remains is that PG doesn't understand
or work with POSIX ACLs or Linux capabilities, but that's not anything
new.
On 03/13/2018 10:45 AM, David Steele wrote:
As Stephen notes, this can be enforced by the user if they want to,
and without much effort (and with better tools).To me, that seems really the key. An --allow-group-access option is
nice (but, as we see, misleading if its assumptions are outdated
regarding how the filesystem works), but I would plug even harder for
a --permission-transparency option, which would just assume that the
admin is making security arrangements, through mechanisms that
postgres may or may not even understand. The admin can create ACLs
with default entries that propagate to newly created objects.
SELinux contexts can work in similar ways. The admin controls the
umask inherited by the postmaster. A --permission-transparency option
would simply use open and mkdir in the traditional ways, allowing
the chosen umask, ACL defaults, SELinux contexts, etc., to do their
thing, and would avoid then stepping on the results with explicit
chmods (and of course skip the I-refuse-to-start-because-I-
misunderstand-your-setup check).It wouldn't be for every casual install, but it would be the best
way to stay out of the way of an admin securing a system with modern
facilities.A lot of the design effort put into debating what postgres itself
should or shouldn't insist on could then, perhaps, go into writing
postgres-specific configuration rule packages for some of those
better configuration-checking tools, and there it might even be
possible to write tests that incorporate knowledge of ACLs, SELinux,
etc.
I'm a fan of this idea in general, but it's unclear how that
--permission-transparency option would work in practice. Are you
suggesting that it be a compile-time flag, which would certainly work
but also then have to be debated among packagers as to the right setting
and if there's any need to be concerned about users misunderstanding it,
or a flag for each program, which hardly seems ideal as some invokations
of programs might forget to specify that, leading to inconsistent
permissions, or something else..? We've pretty well established that
there's no particularly good way for PG to just "know" beyond looking at
the privileges on the data directory, but we'd have to build in complete
support for POSIX ACLs and Linux capabilities if we go down a route
where we want to enforce the same POSIX ACLs for every file that exists
in the data directory based on the data directory's ACLs. I'm not
entirely sure that would even be possible when it comes to capabilities,
or if it would make sense..
From what I've seen with SELinux, thankfully, these same issues don't
really crop up (probably because PG never tried to force anything
SELinux related, and SELinux is largely independent of the POSIX ACLs
and Linux capabilities, so it doesn't impact things in this way).
Thanks!
Stephen
On 03/13/2018 01:50 PM, Stephen Frost wrote:
Yes, that's true, but PG has never paid attention to POSIX ACLs or Linux
capabilities. Changing it to do so is quite a bit beyond the scope...
I think we're largely in agreement here, as my aim was not to advocate
that PG should work harder to understand the subtleties of every system
it could be installed on, but rather that it should work less hard at
pretending to understand them when it doesn't, and thus avoid
obstructing the admin, who presumably does.
I'll point out that PG does run on quite a few other OS's beyond Linux.
I'll second that, as I think it is making my argument. When I can
supply three or four examples of semantic subtleties that undermine
PG's assumptions under Linux alone, the picture when broadened to
those quite-a-few other platforms as well certainly doesn't become
simpler!
but then sooner or later it will still end up making assumptions
that aren't true under, say, SELinux, so there's another #ifdef,
and where does it end?I agree with this general concern.
:) That's probably where it became clear that I'm not advocating
an add-#ifdefs-for-everything approach.
PG will stat PGDATA and conclude that the system is saying that group
access has been granted on PGDATA and will do the same for subsequent
files created later on. ... The only issue that remains is that PG
doesn't understand or work with POSIX ACLs or Linux capabilities,
but that's not anything new.
What's new is that it is now pretending even more extravagantly to
understand what it doesn't understand. Where it would previously draw
in incorrect conclusion and refuse to start, which is annoying but
not very difficult to work around if need be, it would now draw the
same incorrect conclusion and then actively go about making the real
world embody the incorrect conclusion, contrary to the admin's efforts.
umask inherited by the postmaster. A --permission-transparency option
would simply use open and mkdir in the traditional ways, allowing
the chosen umask, ACL defaults, SELinux contexts, etc., to do their
thing, and would avoid then stepping on the results with explicit
chmods (and of course skip the I-refuse-to-start-because-I-
misunderstand-your-setup check).
...I'm a fan of this idea in general, but it's unclear how that
--permission-transparency option would work in practice. Are you
suggesting that it be a compile-time flag, which would certainly work
but also then have to be debated among packagers as to the right setting
and if there's any need to be concerned about users misunderstanding it,
or a flag for each program,
I was thinking of a command-line option ...
which hardly seems ideal as some invokations
of programs might forget to specify that, leading to inconsistent
permissions, or something else..?
... but I see how that gets complicated with the various other command-
line utilities included.
.. we'd have to build in complete
support for POSIX ACLs and Linux capabilities if we go down a route
I'm wary of an argument that we can't do better except by building
in complete support for POSIX ACLs, and capabilities (and NFSv4
ACLs, and SELinux, and AppArmor, and #ifdef and #ifdef and #ifdef).
It seems to me that, in most cases, the designers of these sorts of
extensions to old traditional Unix behavior take great pains to design
them such that they can still usefully function in the presence of
programs that "don't pay attention to or understand or use" them, as
long as those programs are in some sense well-behaved, and not going
out of their way with active steps that insist on or impose permissions
that aren't appropriate under the non-understood circumstances.
So my suggestion boils down to PG having at least an option, somehow,
to be well-behaved in that sense.
-Chap
Chapman Flack <chap@anastigmatix.net> writes:
On 03/13/2018 01:50 PM, Stephen Frost wrote:
I'll point out that PG does run on quite a few other OS's beyond Linux.
I'll second that, as I think it is making my argument. When I can
supply three or four examples of semantic subtleties that undermine
PG's assumptions under Linux alone, the picture when broadened to
those quite-a-few other platforms as well certainly doesn't become
simpler!
Well, to be blunt, what we target is POSIX-compatible systems. If
you're telling us to worry about non-POSIX filesystem semantics,
and your name is not Microsoft, it's going to be a hard sell.
We have enough to do just keeping up with that scope of target
systems.
regards, tom lane
Greetings Chapman,
* Chapman Flack (chap@anastigmatix.net) wrote:
On 03/13/2018 01:50 PM, Stephen Frost wrote:
PG will stat PGDATA and conclude that the system is saying that group
access has been granted on PGDATA and will do the same for subsequent
files created later on. ... The only issue that remains is that PG
doesn't understand or work with POSIX ACLs or Linux capabilities,
but that's not anything new.What's new is that it is now pretending even more extravagantly to
understand what it doesn't understand. Where it would previously draw
in incorrect conclusion and refuse to start, which is annoying but
not very difficult to work around if need be, it would now draw the
same incorrect conclusion and then actively go about making the real
world embody the incorrect conclusion, contrary to the admin's efforts.
I have to say that I disagree about it being "easy to work-around" PG
refusing to start. Also, we're not pretending any more or less, we're
sticking to exactly what we do understand- which is the 80's unix
permission system, as you put it. The options that I see here are to
stick with the user/group system and our naive understanding of it, to
go whole-hog and try to completely understand everything (with lots of
#ifdef's, as discussed), or to completely remove all checks- but we
don't have a clear proposal for that and it strikes me as at least
unlikely to go over well anyway, given all of the discussion here about
trying to simply change the one check we have.
umask inherited by the postmaster. A --permission-transparency option
would simply use open and mkdir in the traditional ways, allowing
the chosen umask, ACL defaults, SELinux contexts, etc., to do their
thing, and would avoid then stepping on the results with explicit
chmods (and of course skip the I-refuse-to-start-because-I-
misunderstand-your-setup check).
...I'm a fan of this idea in general, but it's unclear how that
--permission-transparency option would work in practice. Are you
suggesting that it be a compile-time flag, which would certainly work
but also then have to be debated among packagers as to the right setting
and if there's any need to be concerned about users misunderstanding it,
or a flag for each program,I was thinking of a command-line option ...
which hardly seems ideal as some invokations
of programs might forget to specify that, leading to inconsistent
permissions, or something else..?... but I see how that gets complicated with the various other command-
line utilities included.
Indeed.
.. we'd have to build in complete
support for POSIX ACLs and Linux capabilities if we go down a routeI'm wary of an argument that we can't do better except by building
in complete support for POSIX ACLs, and capabilities (and NFSv4
ACLs, and SELinux, and AppArmor, and #ifdef and #ifdef and #ifdef).
I don't think I meant to imply that we can't do better, I was just
trying to enumate what I saw the different options being.
It seems to me that, in most cases, the designers of these sorts of
extensions to old traditional Unix behavior take great pains to design
them such that they can still usefully function in the presence of
programs that "don't pay attention to or understand or use" them, as
long as those programs are in some sense well-behaved, and not going
out of their way with active steps that insist on or impose permissions
that aren't appropriate under the non-understood circumstances.So my suggestion boils down to PG having at least an option, somehow,
to be well-behaved in that sense.
I'm afraid that we haven't got any great answer to that "somehow". I
was hoping you might have some other ideas beyond command-line switches
which could leave the system in an inconsistent state a bit too easily.
Unless there's a better way then the approach proposed by Tom
(originally) and implemented by David seems like the way to go and at
least an improvement over the current situation.
Thanks!
Stephen
On 03/13/2018 02:47 PM, Tom Lane wrote:
Well, to be blunt, what we target is POSIX-compatible systems. If
you're telling us to worry about non-POSIX filesystem semantics,
and your name is not Microsoft, it's going to be a hard sell.
We have enough to do just keeping up with that scope of target
systems.
So, how many POSIX-compatible systems are available today (if any),
where you can actually safely assume that there are no additional
security/access-control-related considerations in effect beyond
three user bits/three group bits/three other bits, and not be wrong?
I'm not advocating the Sisyphean task of having PG incorporate
knowledge of all those possibilities. I'm advocating the conservative
approach: have PG be that well-behaved application that those extended
semantics are generally all designed to play well with, and just not
do stuff that obstructs or tramples over what the admin takes care
to set up.
On 03/13/2018 03:44 PM, Stephen Frost wrote:
* Chapman Flack (chap@anastigmatix.net) wrote:
So my suggestion boils down to PG having at least an option, somehow,
to be well-behaved in that sense.I'm afraid that we haven't got any great answer to that "somehow". I
was hoping you might have some other ideas beyond command-line
switches which could leave the system in an inconsistent state a bit
too easily.
I wonder how complicated it would have to be really. On any system
with a POSIX base, I guess it's possible for PGDATA to have an S_ISVTX
"sticky" bit in the mode, which does have an official significance (but
one that only affects whether non-postgres can rename or unlink things
in the directory, which might be of little practical significance).
Perhaps its meaning could be overloaded with "the admin is handling
the permissions, thank you", and postmaster and various command-line
utilities could see it, and refrain from any gratuitous chmods or
refusals to function.
Or, if overloading S_ISVTX seems in poor taste, what would be wrong
with simply checking for an empty file PERMISSIONS-ARE-MANAGED in
PGDATA and responding the same way?
Or, assuming some form of ACL is available, just let the admin
change the owner and group of PGDATA to other than postgres,
grant no access to other, and give rwx to postgres in the ACL?
PG could then reason as follows: * I do not own this directory.
* I am not the group of this directory. * It grants no access to other.
* Yet, I find myself listing and accessing files in it without
difficulty. * The admin has set this up for me in a way I do not
understand. * I will refrain from messing with it.
Three ideas off the top of my head. Probably more where they came from.
:)
-Chap
Chapman,
* Chapman Flack (chap@anastigmatix.net) wrote:
I'm not advocating the Sisyphean task of having PG incorporate
knowledge of all those possibilities. I'm advocating the conservative
approach: have PG be that well-behaved application that those extended
semantics are generally all designed to play well with, and just not
do stuff that obstructs or tramples over what the admin takes care
to set up.
I think we get that you're advocating removing the checks and
permissions-setting that the PG tools do, however...
I wonder how complicated it would have to be really. On any system
with a POSIX base, I guess it's possible for PGDATA to have an S_ISVTX
"sticky" bit in the mode, which does have an official significance (but
one that only affects whether non-postgres can rename or unlink things
in the directory, which might be of little practical significance).
Perhaps its meaning could be overloaded with "the admin is handling
the permissions, thank you", and postmaster and various command-line
utilities could see it, and refrain from any gratuitous chmods or
refusals to function.Or, if overloading S_ISVTX seems in poor taste, what would be wrong
with simply checking for an empty file PERMISSIONS-ARE-MANAGED in
PGDATA and responding the same way?Or, assuming some form of ACL is available, just let the admin
change the owner and group of PGDATA to other than postgres,
grant no access to other, and give rwx to postgres in the ACL?
None of these suggestions really sound workable to me. I certainly
don't think we should be overloading the meaning of a specific and
entirely independent filesystem permission (and really don't even
want to imagine what it would require to do something like that on
Windows...), dropping an empty file in the directory strikes me as a
ripe target for confusion, and we have specific checks to try and
make sure we are running as the owner that owns the directory with
some pretty good reasons around that (avoiding multiple postmasters
running in the same PGDATA directory, specifically).
PG could then reason as follows: * I do not own this directory.
* I am not the group of this directory. * It grants no access to other.
* Yet, I find myself listing and accessing files in it without
difficulty. * The admin has set this up for me in a way I do not
understand. * I will refrain from messing with it.
Removing the check that says "we aren't going to try to run PG in this
directory if we aren't the owner of it" also doesn't seem like it's
necessairly a great plan.
Three ideas off the top of my head. Probably more where they came from.
None of them really seem workable though. On the other hand, let's
consider what this patch actually ends up doing when POSIX ACLs are
involved. This allows ACLs to be used where they weren't before and
with appropriate defaults set even to work for new files being created,
which wouldn't work before. Yes, if you create a default ACL which
says "grant this other user write access to files in the PG data
directory" then that won't actually be honored because we will
chmod(640) the file and the POSIX ACL system actually works with the
user/group privilege system, like so:
Create the "data" dir, as initdb would:
➜ ~ mkdir xyz
➜ ~ chmod 750 xyz
➜ ~ ls -ld xyz
drwxr-x--- 2 sfrost sfrost 4096 Mar 13 19:07 xyz
Set a default ACL to allow the "daemon" user read/write access to files
created:
➜ ~ setfacl -dm u:daemon:rw xyz
➜ ~ getfacl xyz
# file: xyz
# owner: sfrost
# group: sfrost
user::rwx
group::r-x
other::---
default:user::rwx
default:user:daemon:rw-
default:group::r-x
default:mask::rwx
default:other::---
Create a file the way PG would:
➜ ~ touch xyz/a
➜ ~ chmod 640 xyz/a
➜ ~ getfacl xyz/a
# file: xyz/a
# owner: sfrost
# group: sfrost
user::rw-
user:daemon:rw- #effective:r--
group::r-x #effective:r--
mask::r--
other::---
The daemon user ends up with read-only access (note the '#effective',
which shows that the POSIX ACL system isn't overriding the "regular"
ACLs).
... but that's basically what we want.
Multiple users having write access to the data directory could be quite
bad as you might possibly get two postmasters running against the same
data directory at the same time and there's basically no case where
that's a good thing to have happen. This change does let users grant
out read access to other users/groups, even beyond what's possible using
the traditional user/group system, so this opens up a lot more possible
options for advanced users, provided they set the defaults
appropriately at the directory level (which, presumably, an
administrator versed in POSIX ACLs and wishing to use them would know,
or would figure out quickly).
Yes, perhaps there's some argument to be made that we should have an
option where we don't force any privileges, but that can certainly be
considered a future capability and what's being implemented here doesn't
actually break use-cases which worked before and allows users more
freedom than they had before to use POSIX ACLs if they want to use them,
and without having to modify PG to understand POSIX ACLs explicitly.
Also, to the point you raise up-thread, where you remove access to the
data directory from the group that owns the directory, but grant it to
some other user, yes, files inside the data directory would end up with
group access for that other group. That won't matter as long as the
group doesn't have rights on the directory, though it would certainly be
cleaner to just have a dedicated group where it isn't an issue for that
group to have read access to the files. I would imagine anyone using
POSIX ACLs would understand this without too much difficulty. Of
course, POSIX default ACLs would also continue to work for files created
under the data directory, as illustrated above.
In all, this is looking like a pretty good improvement while also having
some caution about what privileges are effectively allowed.
Thanks!
Stephen
On Tue, Mar 13, 2018 at 01:28:17PM -0400, Tom Lane wrote:
David Steele <david@pgmasters.net> writes:
On 3/12/18 3:28 AM, Michael Paquier wrote:
In pg_rewind and pg_resetwal, isn't that also a portion which is not
necessary without the group access feature?These seem like a good idea to me with or without patch 03. Some of our
front-end tools (initdb, pg_upgrade) were setting umask and others
weren't. I think it's more consistent (and safer) if they all do, at
least if they are writing into PGDATA.+1 ... see a926eb84e for an example of how easy it is to screw up if
the process's overall umask is permissive.
Okay. A suggestion that I have here would be to split those extra calls
into a separate patch. That's a useful self-contained improvement.
--
Michael
On Tue, Mar 13, 2018 at 12:19:07PM -0400, David Steele wrote:
I'll attach new patches in a reply to [1] once I have made the changes
Tom requested.
Cool, thanks for your patience. Looking forward to seeing those. I'll
spend time on 0003 at the same time. Let's also put the additional
umask calls for pg_rewind and pg_resetwal into a separate patch.
--
Michael
Hi,
On 3/13/18 12:13 PM, Stephen Frost wrote:
* David Steele (david@pgmasters.net) wrote:
On 3/13/18 11:00 AM, Tom Lane wrote:
FWIW, I took a quick look through this patch and don't have any problem
with the approach, which appears to be "use the data directory's
startup-time permissions as the status indicator". I am kinda wondering
about this though:+ (These files can confuse <application>pg_ctl</application>.) If group read + access is enabled on the data directory and an unprivileged user in the + <productname>PostgreSQL</productname> group is performing the backup, then + <filename>postmaster.pid</filename> will not be readable and must be + excluded.If we're allowing group read on the data directory, why should that not
include postmaster.pid? There's nothing terribly security-relevant in
there, and being able to find out the postmaster PID seems useful. I can
see the point of restricting server key files, as documented elsewhere,
but it's possible to keep those somewhere else so they don't cause
problems for simple backup software.I'm OK with that. I had a look at the warnings regarding the required
mode of postmaster.pid in miscinit.c (889-911) and it seems to me they
still hold true if the mode is 640 instead of 600.Do you agree, Tom? Stephen?
If so, I'll make those changes.
I agree that we can still consider an EPERM-error case as being ok even
with the changes proposed, and with the same-user check happening
earlier in checkDataDir(), we won't even get to the point of looking at
the pid file if the userid's don't match. The historical comment about
the old datadir permissions can likely just be removed, perhaps replaced
with a bit more commentary above that check in checkDataDir(). The
open() call should also fail if we only have group-read privileges on
the file (0640), but surely the kill() will in any case.
OK, that being the case a new patch set is attached that sets the mode
of postmaster.pid the same as other files in PGDATA.
I also cleaned up / corrected / added comments in various places.
Thanks,
--
-David
david@pgmasters.net
Attachments:
group-access-v11-01-pgresetwal-test.patchtext/plain; charset=UTF-8; name=group-access-v11-01-pgresetwal-test.patch; x-mac-creator=0; x-mac-type=0Download
From 026179cc58166d2b0c6e233ee28ea1d745cffba9 Mon Sep 17 00:00:00 2001
From: David Steele <david@pgmasters.net>
Date: Wed, 14 Mar 2018 13:48:35 -0400
Subject: [PATCH 1/3] pg_resetwal tests.
Adds a very basic test suite for pg_resetwal.
---
src/bin/pg_resetwal/.gitignore | 1 +
src/bin/pg_resetwal/Makefile | 6 +++++
src/bin/pg_resetwal/t/001_basic.pl | 53 ++++++++++++++++++++++++++++++++++++++
3 files changed, 60 insertions(+)
create mode 100644 src/bin/pg_resetwal/t/001_basic.pl
diff --git a/src/bin/pg_resetwal/.gitignore b/src/bin/pg_resetwal/.gitignore
index 236abb4323..a950255fd7 100644
--- a/src/bin/pg_resetwal/.gitignore
+++ b/src/bin/pg_resetwal/.gitignore
@@ -1 +1,2 @@
/pg_resetwal
+/tmp_check
diff --git a/src/bin/pg_resetwal/Makefile b/src/bin/pg_resetwal/Makefile
index 5ab7ad33e0..13c9ca6279 100644
--- a/src/bin/pg_resetwal/Makefile
+++ b/src/bin/pg_resetwal/Makefile
@@ -33,3 +33,9 @@ uninstall:
clean distclean maintainer-clean:
rm -f pg_resetwal$(X) $(OBJS)
+
+check:
+ $(prove_check)
+
+installcheck:
+ $(prove_installcheck)
diff --git a/src/bin/pg_resetwal/t/001_basic.pl b/src/bin/pg_resetwal/t/001_basic.pl
new file mode 100644
index 0000000000..b867fe3dba
--- /dev/null
+++ b/src/bin/pg_resetwal/t/001_basic.pl
@@ -0,0 +1,53 @@
+use strict;
+use warnings;
+
+use Config;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 13;
+
+my $tempdir = TestLib::tempdir;
+my $tempdir_short = TestLib::tempdir_short;
+
+program_help_ok('pg_resetwal');
+program_version_ok('pg_resetwal');
+program_options_handling_ok('pg_resetwal');
+
+# Initialize node without replication settings
+my $node = get_new_node('main');
+my $pgdata = $node->data_dir;
+my $pgwal = "$pgdata/pg_wal";
+$node->init;
+$node->start;
+
+# Remove WAL from pg_wal and make sure it gets rebuilt
+$node->stop;
+
+unlink("$pgwal/000000010000000000000001") == 1
+ or BAIL_OUT("unable to remove 000000010000000000000001");
+
+is_deeply(
+ [sort(slurp_dir($pgwal))], [sort(qw(. .. archive_status))], 'no WAL');
+
+$node->command_ok(['pg_resetwal', '-D', $pgdata], 'recreate pg_wal');
+
+is_deeply(
+ [sort(slurp_dir($pgwal))],
+ [sort(qw(. .. archive_status 000000010000000000000002))],
+ 'WAL recreated');
+
+$node->start;
+
+# Reset to specific WAL segment
+$node->stop;
+
+$node->command_ok(
+ ['pg_resetwal', '-l', '000000070000000700000007', '-D', $pgdata],
+ 'set to specific WAL');
+
+is_deeply(
+ [sort(slurp_dir($pgwal))],
+ [sort(qw(. .. archive_status 000000070000000700000007))],
+ 'WAL recreated');
+
+$node->start;
--
2.14.3 (Apple Git-98)
group-access-v11-02-file-perm.patchtext/plain; charset=UTF-8; name=group-access-v11-02-file-perm.patch; x-mac-creator=0; x-mac-type=0Download
From 7d091c7b46feabd706fd1a564f634bc718d72cc6 Mon Sep 17 00:00:00 2001
From: David Steele <david@pgmasters.net>
Date: Wed, 14 Mar 2018 13:53:03 -0400
Subject: [PATCH 2/3] Refactor file permissions in backend/frontend
Adds a new header (file_perm.h) and makes all front-end utilities use the new constants for setting umask. Converts mkdir() calls in the backend to MakeDirectoryDefaultPerm() if the original call used default permissions. Adds tests to make sure permissions in PGDATA are set correctly by the front-end tools.
---
src/backend/access/transam/xlog.c | 2 +-
src/backend/commands/tablespace.c | 18 +++++----
src/backend/postmaster/postmaster.c | 5 ++-
src/backend/postmaster/syslogger.c | 5 ++-
src/backend/replication/slot.c | 5 ++-
src/backend/storage/file/copydir.c | 2 +-
src/backend/storage/file/fd.c | 32 ++++++++++------
src/backend/storage/ipc/ipc.c | 4 ++
src/backend/utils/init/miscinit.c | 5 ++-
src/bin/initdb/initdb.c | 24 ++++++------
src/bin/initdb/t/001_initdb.pl | 4 +-
src/bin/pg_ctl/pg_ctl.c | 4 +-
src/bin/pg_ctl/t/001_start_stop.pl | 13 +++++--
src/bin/pg_resetwal/pg_resetwal.c | 8 +++-
src/bin/pg_resetwal/t/001_basic.pl | 4 +-
src/bin/pg_rewind/RewindTest.pm | 4 ++
src/bin/pg_rewind/file_ops.c | 7 ++--
src/bin/pg_rewind/pg_rewind.c | 4 ++
src/bin/pg_rewind/t/001_basic.pl | 3 +-
src/bin/pg_upgrade/file.c | 5 ++-
src/bin/pg_upgrade/pg_upgrade.c | 3 +-
src/bin/pg_upgrade/test.sh | 11 ++++++
src/include/common/file_perm.h | 32 ++++++++++++++++
src/include/storage/fd.h | 3 ++
src/test/perl/PostgresNode.pm | 3 ++
src/test/perl/TestLib.pm | 73 +++++++++++++++++++++++++++++++++++++
26 files changed, 226 insertions(+), 57 deletions(-)
create mode 100644 src/include/common/file_perm.h
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 47a6c4d895..bf07bbd746 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4086,7 +4086,7 @@ ValidateXLOGDirectoryStructure(void)
{
ereport(LOG,
(errmsg("creating missing WAL directory \"%s\"", path)));
- if (mkdir(path, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(path) < 0)
ereport(FATAL,
(errmsg("could not create missing directory \"%s\": %m",
path)));
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 5c450caa4e..f7e1818350 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -68,6 +68,7 @@
#include "commands/seclabel.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
+#include "common/file_perm.h"
#include "miscadmin.h"
#include "postmaster/bgwriter.h"
#include "storage/fd.h"
@@ -151,7 +152,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
else
{
/* Directory creation failed? */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(dir) < 0)
{
char *parentdir;
@@ -173,7 +174,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
get_parent_directory(parentdir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -184,7 +185,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
parentdir = pstrdup(dir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -192,7 +193,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
pfree(parentdir);
/* Create database directory */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(dir) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -279,7 +280,8 @@ CreateTableSpace(CreateTableSpaceStmt *stmt)
/*
* Check that location isn't too long. Remember that we're going to append
* 'PG_XXX/<dboid>/<relid>_<fork>.<nnn>'. FYI, we never actually
- * reference the whole path here, but mkdir() uses the first two parts.
+ * reference the whole path here, but MakeDirectoryDefaultPerm() uses the first
+ * two parts.
*/
if (strlen(location) + 1 + strlen(TABLESPACE_VERSION_DIRECTORY) + 1 +
OIDCHARS + 1 + OIDCHARS + 1 + FORKNAMECHARS + 1 + OIDCHARS > MAXPGPATH)
@@ -574,7 +576,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* Attempt to coerce target directory to safe permissions. If this fails,
* it doesn't exist or has the wrong owner.
*/
- if (chmod(location, S_IRWXU) != 0)
+ if (chmod(location, PG_DIR_MODE_DEFAULT) != 0)
{
if (errno == ENOENT)
ereport(ERROR,
@@ -599,7 +601,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
if (stat(location_with_version_dir, &st) == 0 && S_ISDIR(st.st_mode))
{
if (!rmtree(location_with_version_dir, true))
- /* If this failed, mkdir() below is going to error. */
+ /* If this failed, MakeDirectoryDefaultPerm() below is going to error. */
ereport(WARNING,
(errmsg("some useless files may be left behind in old database directory \"%s\"",
location_with_version_dir)));
@@ -610,7 +612,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* The creation of the version directory prevents more than one tablespace
* in a single location.
*/
- if (mkdir(location_with_version_dir, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(location_with_version_dir) < 0)
{
if (errno == EEXIST)
ereport(ERROR,
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 660f3185e6..868fba8cac 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -97,6 +97,7 @@
#include "access/xlog.h"
#include "bootstrap/bootstrap.h"
#include "catalog/pg_control.h"
+#include "common/file_perm.h"
#include "common/ip.h"
#include "lib/ilist.h"
#include "libpq/auth.h"
@@ -589,7 +590,7 @@ PostmasterMain(int argc, char *argv[])
/*
* for security, no dir or file created can be group or other accessible
*/
- umask(S_IRWXG | S_IRWXO);
+ umask(PG_MODE_MASK_DEFAULT);
/*
* Initialize random(3) so we don't get the same values in every run.
@@ -4492,7 +4493,7 @@ internal_forkexec(int argc, char *argv[], Port *port)
* As in OpenTemporaryFileInTablespace, try to make the temp-file
* directory
*/
- mkdir(PG_TEMP_FILES_DIR, S_IRWXU);
+ MakeDirectoryDefaultPerm(PG_TEMP_FILES_DIR);
fp = AllocateFile(tmpfilename, PG_BINARY_W);
if (!fp)
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index f70eea37df..ae23941f59 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -41,6 +41,7 @@
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
#include "storage/dsm.h"
+#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pg_shmem.h"
@@ -322,7 +323,7 @@ SysLoggerMain(int argc, char *argv[])
/*
* Also, create new directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ MakeDirectoryDefaultPerm(Log_directory);
}
if (strcmp(Log_filename, currentLogFilename) != 0)
{
@@ -564,7 +565,7 @@ SysLogger_Start(void)
/*
* Create log directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ MakeDirectoryDefaultPerm(Log_directory);
/*
* The initial logfile is created right in the postmaster, to verify that
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index fc9ef22b0b..3a98360b50 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -1166,13 +1166,14 @@ CreateSlotOnDisk(ReplicationSlot *slot)
* It's just barely possible that some previous effort to create or drop a
* slot with this name left a temp directory lying around. If that seems
* to be the case, try to remove it. If the rmtree() fails, we'll error
- * out at the mkdir() below, so we don't bother checking success.
+ * out at the MakeDirectoryDefaultPerm() below, so we don't bother checking
+ * success.
*/
if (stat(tmppath, &st) == 0 && S_ISDIR(st.st_mode))
rmtree(tmppath, true);
/* Create and fsync the temporary slot directory. */
- if (mkdir(tmppath, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(tmppath) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
diff --git a/src/backend/storage/file/copydir.c b/src/backend/storage/file/copydir.c
index ca6342db0d..ff980dc1f5 100644
--- a/src/backend/storage/file/copydir.c
+++ b/src/backend/storage/file/copydir.c
@@ -41,7 +41,7 @@ copydir(char *fromdir, char *todir, bool recurse)
char fromfile[MAXPGPATH * 2];
char tofile[MAXPGPATH * 2];
- if (mkdir(todir, S_IRWXU) != 0)
+ if (MakeDirectoryDefaultPerm(todir) != 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m", todir)));
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index 2a18e94ff4..1491665375 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -84,6 +84,7 @@
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "catalog/pg_tablespace.h"
+#include "common/file_perm.h"
#include "pgstat.h"
#include "portability/mem.h"
#include "storage/fd.h"
@@ -124,12 +125,6 @@
*/
#define FD_MINFREE 10
-/*
- * Default mode for created files, unless something else is specified using
- * the *Perm() function variants.
- */
-#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR)
-
/*
* A number of platforms allow individual processes to open many more files
* than they can really support when *many* processes do the same thing.
@@ -1435,7 +1430,7 @@ PathNameOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode)
void
PathNameCreateTemporaryDir(const char *basedir, const char *directory)
{
- if (mkdir(directory, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(directory) < 0)
{
if (errno == EEXIST)
return;
@@ -1445,14 +1440,14 @@ PathNameCreateTemporaryDir(const char *basedir, const char *directory)
* EEXIST to close a race against another process following the same
* algorithm.
*/
- if (mkdir(basedir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(basedir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary directory \"%s\": %m",
basedir)));
/* Try again. */
- if (mkdir(directory, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(directory) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary subdirectory \"%s\": %m",
@@ -1602,11 +1597,11 @@ OpenTemporaryFileInTablespace(Oid tblspcOid, bool rejectError)
* We might need to create the tablespace's tempfile directory, if no
* one has yet done so.
*
- * Don't check for error from mkdir; it could fail if someone else
- * just did the same thing. If it doesn't work then we'll bomb out on
+ * Don't check error from MakeDirectoryDefaultPerm; it could fail if someone
+ * else just did the same thing. If it doesn't work then we'll bomb out on
* the second create attempt, instead.
*/
- mkdir(tempdirpath, S_IRWXU);
+ MakeDirectoryDefaultPerm(tempdirpath);
file = PathNameOpenFile(tempfilepath,
O_RDWR | O_CREAT | O_TRUNC | PG_BINARY);
@@ -3555,3 +3550,16 @@ fsync_parent_path(const char *fname, int elevel)
return 0;
}
+
+/*
+ * Create a directory using PG_DIR_MODE_DEFAULT for permissions
+ *
+ * Directories in PGDATA should normally have specific permissions -- when
+ * using this function the caller does not need to know what they should be.
+ * For permissions other than the default use mkdir() directly.
+ */
+int
+MakeDirectoryDefaultPerm(const char *directoryName)
+{
+ return mkdir(directoryName, PG_DIR_MODE_DEFAULT);
+}
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index 726db7b7f1..466613e4af 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -137,6 +137,10 @@ proc_exit(int code)
else
snprintf(gprofDirName, 32, "gprof/%d", (int) getpid());
+ /*
+ * Use mkdir() instead of MakeDirectoryDefaultPerm() here because non-default
+ * permissions are required.
+ */
mkdir("gprof", S_IRWXU | S_IRWXG | S_IRWXO);
mkdir(gprofDirName, S_IRWXU | S_IRWXG | S_IRWXO);
chdir(gprofDirName);
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 87ed7d3f71..82da4b9f27 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -32,6 +32,7 @@
#include "access/htup_details.h"
#include "catalog/pg_authid.h"
+#include "common/file_perm.h"
#include "libpq/libpq.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
@@ -831,7 +832,7 @@ CreateLockFile(const char *filename, bool amPostmaster,
* Think not to make the file protection weaker than 0600. See
* comments below.
*/
- fd = open(filename, O_RDWR | O_CREAT | O_EXCL, 0600);
+ fd = open(filename, O_RDWR | O_CREAT | O_EXCL, PG_FILE_MODE_DEFAULT);
if (fd >= 0)
break; /* Success; exit the retry loop */
@@ -848,7 +849,7 @@ CreateLockFile(const char *filename, bool amPostmaster,
* Read the file to get the old owner's PID. Note race condition
* here: file might have been deleted since we tried to create it.
*/
- fd = open(filename, O_RDONLY, 0600);
+ fd = open(filename, O_RDONLY, PG_FILE_MODE_DEFAULT);
if (fd < 0)
{
if (errno == ENOENT)
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 65eba7d42f..0129ecf8c1 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -64,6 +64,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_class.h"
#include "catalog/pg_collation.h"
+#include "common/file_perm.h"
#include "common/file_utils.h"
#include "common/restricted_token.h"
#include "common/username.h"
@@ -1170,7 +1171,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1190,7 +1191,7 @@ setup_config(void)
sprintf(path, "%s/postgresql.auto.conf", pg_data);
writefile(path, autoconflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1277,7 +1278,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_hba.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1293,7 +1294,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_ident.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -2692,7 +2693,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (pg_mkdir_p(pg_data, S_IRWXU) != 0)
+ if (pg_mkdir_p(pg_data, PG_DIR_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2710,7 +2711,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (chmod(pg_data, S_IRWXU) != 0)
+ if (chmod(pg_data, PG_DIR_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2778,7 +2779,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (pg_mkdir_p(xlog_dir, S_IRWXU) != 0)
+ if (pg_mkdir_p(xlog_dir, PG_DIR_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2796,7 +2797,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (chmod(xlog_dir, S_IRWXU) != 0)
+ if (chmod(xlog_dir, PG_DIR_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2846,7 +2847,7 @@ create_xlog_or_symlink(void)
else
{
/* Without -X option, just make the subdirectory normally */
- if (mkdir(subdirloc, S_IRWXU) < 0)
+ if (mkdir(subdirloc, PG_DIR_MODE_DEFAULT) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, subdirloc, strerror(errno));
@@ -2882,7 +2883,8 @@ initialize_data_directory(void)
setup_signals();
- umask(S_IRWXG | S_IRWXO);
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_DEFAULT);
create_data_directory();
@@ -2902,7 +2904,7 @@ initialize_data_directory(void)
* The parent directory already exists, so we only need mkdir() not
* pg_mkdir_p() here, which avoids some failure modes; cf bug #13853.
*/
- if (mkdir(path, S_IRWXU) < 0)
+ if (mkdir(path, PG_DIR_MODE_DEFAULT) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, path, strerror(errno));
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index c0cfa6e92c..3331560445 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 15;
+use Test::More tests => 16;
my $tempdir = TestLib::tempdir;
my $xlogdir = "$tempdir/pgxlog";
@@ -45,6 +45,8 @@ mkdir $datadir;
command_ok([ 'initdb', '-N', '-T', 'german', '-X', $xlogdir, $datadir ],
'successful creation');
+
+ ok(check_mode_recursive($datadir, 0700, 0600));
}
command_ok([ 'initdb', '-S', $datadir ], 'sync only');
command_fails([ 'initdb', $datadir ], 'existing data directory');
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index 9bc830b085..e83a7179ea 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -25,6 +25,7 @@
#include "catalog/pg_control.h"
#include "common/controldata_utils.h"
+#include "common/file_perm.h"
#include "getopt_long.h"
#include "utils/pidfile.h"
@@ -2170,7 +2171,8 @@ main(int argc, char **argv)
*/
argv0 = argv[0];
- umask(S_IRWXG | S_IRWXO);
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_DEFAULT);
/* support --help and --version even if invoked as root */
if (argc > 1)
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl
index 5da4746cb4..3d82abe696 100644
--- a/src/bin/pg_ctl/t/001_start_stop.pl
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -4,7 +4,7 @@ use warnings;
use Config;
use PostgresNode;
use TestLib;
-use Test::More tests => 19;
+use Test::More tests => 20;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -57,10 +57,15 @@ command_ok([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ], 'pg_ctl stop');
command_fails([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ],
'second pg_ctl stop fails');
+# Log file for first perm test
+my $logFileName = "$tempdir/data/perm-test-600.log";
+
command_ok(
- [ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
+ [ 'pg_ctl', 'restart', '-D', "$tempdir/data", '-l', $logFileName ],
'pg_ctl restart with server not running');
-command_ok([ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
- 'pg_ctl restart with server running');
+
+# Log file should exist and have no group permissions
+ok(-f $logFileName);
+ok(check_mode_recursive("$tempdir/data", 0700, 0600));
system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index a132cf2e9f..47abd41fc3 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -52,6 +52,7 @@
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
#include "common/fe_memutils.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "storage/large_object.h"
#include "pg_getopt.h"
@@ -327,6 +328,9 @@ main(int argc, char *argv[])
exit(1);
}
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_DEFAULT);
+
/* Check that data directory matches our server version */
CheckDataVersion();
@@ -919,7 +923,7 @@ RewriteControlFile(void)
fd = open(XLOG_CONTROL_FILE,
O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ PG_FILE_MODE_DEFAULT);
if (fd < 0)
{
fprintf(stderr, _("%s: could not create pg_control file: %s\n"),
@@ -1201,7 +1205,7 @@ WriteEmptyXLOG(void)
unlink(path);
fd = open(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ PG_FILE_MODE_DEFAULT);
if (fd < 0)
{
fprintf(stderr, _("%s: could not open file \"%s\": %s\n"),
diff --git a/src/bin/pg_resetwal/t/001_basic.pl b/src/bin/pg_resetwal/t/001_basic.pl
index b867fe3dba..674e11b692 100644
--- a/src/bin/pg_resetwal/t/001_basic.pl
+++ b/src/bin/pg_resetwal/t/001_basic.pl
@@ -4,7 +4,7 @@ use warnings;
use Config;
use PostgresNode;
use TestLib;
-use Test::More tests => 13;
+use Test::More tests => 14;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -36,6 +36,8 @@ is_deeply(
[sort(qw(. .. archive_status 000000010000000000000002))],
'WAL recreated');
+ok(check_mode_recursive($pgdata, 0700, 0600), 'check PGDATA permissions');
+
$node->start;
# Reset to specific WAL segment
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index 00b5b42dd7..7b632c7dcd 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -237,6 +237,10 @@ sub run_pg_rewind
"$tmp_folder/master-postgresql.conf.tmp",
"$master_pgdata/postgresql.conf");
+ chmod(0600, "$master_pgdata/postgresql.conf")
+ or BAIL_OUT(
+ "unable to set permissions for $master_pgdata/postgresql.conf");
+
# Plug-in rewound node to the now-promoted standby node
my $port_standby = $node_standby->port;
$node_master->append_conf(
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 705383d184..d51914e901 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -18,6 +18,7 @@
#include <fcntl.h>
#include <unistd.h>
+#include "common/file_perm.h"
#include "file_ops.h"
#include "filemap.h"
#include "logging.h"
@@ -58,7 +59,7 @@ open_target_file(const char *path, bool trunc)
mode = O_WRONLY | O_CREAT | PG_BINARY;
if (trunc)
mode |= O_TRUNC;
- dstfd = open(dstpath, mode, 0600);
+ dstfd = open(dstpath, mode, PG_FILE_MODE_DEFAULT);
if (dstfd < 0)
pg_fatal("could not open target file \"%s\": %s\n",
dstpath, strerror(errno));
@@ -190,7 +191,7 @@ truncate_target_file(const char *path, off_t newsize)
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
- fd = open(dstpath, O_WRONLY, 0);
+ fd = open(dstpath, O_WRONLY, PG_FILE_MODE_DEFAULT);
if (fd < 0)
pg_fatal("could not open file \"%s\" for truncation: %s\n",
dstpath, strerror(errno));
@@ -211,7 +212,7 @@ create_target_dir(const char *path)
return;
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
- if (mkdir(dstpath, S_IRWXU) != 0)
+ if (mkdir(dstpath, PG_DIR_MODE_DEFAULT) != 0)
pg_fatal("could not create directory \"%s\": %s\n",
dstpath, strerror(errno));
}
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 72ab2f8d5e..3d179e2c7d 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -24,6 +24,7 @@
#include "access/xlog_internal.h"
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "getopt_long.h"
#include "storage/bufpage.h"
@@ -185,6 +186,9 @@ main(int argc, char **argv)
exit(1);
}
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_DEFAULT);
+
/*
* Don't allow pg_rewind to be run as root, to avoid overwriting the
* ownership of files in the data directory. We need only check for root
diff --git a/src/bin/pg_rewind/t/001_basic.pl b/src/bin/pg_rewind/t/001_basic.pl
index 736f34eae3..559dc53602 100644
--- a/src/bin/pg_rewind/t/001_basic.pl
+++ b/src/bin/pg_rewind/t/001_basic.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 8;
+use Test::More tests => 10;
use RewindTest;
@@ -86,6 +86,7 @@ in master, before promotion
),
'tail-copy');
+ ok (check_mode_recursive($node_master->data_dir(), 0700, 0600));
RewindTest::clean_rewind_test();
}
diff --git a/src/bin/pg_upgrade/file.c b/src/bin/pg_upgrade/file.c
index f38bfacf02..595d43ee31 100644
--- a/src/bin/pg_upgrade/file.c
+++ b/src/bin/pg_upgrade/file.c
@@ -10,6 +10,7 @@
#include "postgres_fe.h"
#include "access/visibilitymap.h"
+#include "common/file_perm.h"
#include "pg_upgrade.h"
#include "storage/bufpage.h"
#include "storage/checksum.h"
@@ -44,7 +45,7 @@ copyFile(const char *src, const char *dst,
schemaName, relName, src, strerror(errno));
if ((dest_fd = open(dst, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR)) < 0)
+ PG_FILE_MODE_DEFAULT)) < 0)
pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
schemaName, relName, dst, strerror(errno));
@@ -151,7 +152,7 @@ rewriteVisibilityMap(const char *fromfile, const char *tofile,
schemaName, relName, fromfile, strerror(errno));
if ((dst_fd = open(tofile, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR)) < 0)
+ PG_FILE_MODE_DEFAULT)) < 0)
pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
schemaName, relName, tofile, strerror(errno));
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index d12412799f..59df6fd88f 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -38,6 +38,7 @@
#include "pg_upgrade.h"
#include "catalog/pg_class.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "fe_utils/string_utils.h"
@@ -79,7 +80,7 @@ main(int argc, char **argv)
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_upgrade"));
/* Ensure that all files created by pg_upgrade are non-world-readable */
- umask(S_IRWXG | S_IRWXO);
+ umask(PG_MODE_MASK_DEFAULT);
parseCommandLine(argc, argv);
diff --git a/src/bin/pg_upgrade/test.sh b/src/bin/pg_upgrade/test.sh
index 39983abea1..4e8db4aaf4 100644
--- a/src/bin/pg_upgrade/test.sh
+++ b/src/bin/pg_upgrade/test.sh
@@ -230,6 +230,17 @@ standard_initdb 'initdb'
pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "${PGDATA}" -b "$oldbindir" -B "$bindir" -p "$PGPORT" -P "$PGPORT"
+# make sure all directories and files have group permissions
+if [ $(find ${PGDATA} -type f ! -perm 600 | wc -l) -ne 0 ]; then
+ echo "files in PGDATA with permission != 600";
+ exit 1;
+fi
+
+if [ $(find ${PGDATA} -type d ! -perm 700 | wc -l) -ne 0 ]; then
+ echo "directories in PGDATA with permission != 700";
+ exit 1;
+fi
+
pg_ctl start -l "$logdir/postmaster2.log" -o "$POSTMASTER_OPTS" -w
case $testhost in
diff --git a/src/include/common/file_perm.h b/src/include/common/file_perm.h
new file mode 100644
index 0000000000..3c6da0e000
--- /dev/null
+++ b/src/include/common/file_perm.h
@@ -0,0 +1,32 @@
+/*-------------------------------------------------------------------------
+ *
+ * File and directory permission constants
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/file_perm.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FILE_PERM_H
+#define FILE_PERM_H
+
+/*
+ * Default mode mask for data directory permissions that does not allow
+ * group execute/read.
+ */
+#define PG_MODE_MASK_DEFAULT (S_IRWXG | S_IRWXO)
+
+/*
+ * Default mode for created files.
+ */
+#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR)
+
+/*
+ * Default mode for directories.
+ */
+#define PG_DIR_MODE_DEFAULT S_IRWXU
+
+#endif /* FILE_PERM_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 4244e7b1fd..33c636471b 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -112,6 +112,9 @@ extern int CloseTransientFile(int fd);
extern int BasicOpenFile(const char *fileName, int fileFlags);
extern int BasicOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode);
+ /* Make a directory with default permissions */
+extern int MakeDirectoryDefaultPerm(const char *directoryName);
+
/* Miscellaneous support routines */
extern void InitFileAccess(void);
extern void set_max_safe_fds(void);
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 80188315f1..76e571b98c 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -484,6 +484,9 @@ sub append_conf
my $conffile = $self->data_dir . '/' . $filename;
TestLib::append_to_file($conffile, $str . "\n");
+
+ chmod(0600, $conffile)
+ or die("unable to set permissions for $conffile");
}
=pod
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index 7d017d49bd..9de8dc9041 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -12,8 +12,11 @@ use warnings;
use Config;
use Exporter 'import';
+use Fcntl qw(:mode);
use File::Basename;
+use File::Find;
use File::Spec;
+use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
use SimpleTee;
@@ -26,6 +29,7 @@ our @EXPORT = qw(
slurp_dir
slurp_file
append_to_file
+ check_mode_recursive
check_pg_config
system_or_bail
system_log
@@ -222,6 +226,75 @@ sub append_to_file
close $fh;
}
+# Check that all file/dir modes in a directory match the expected values,
+# ignoring the mode of any specified files.
+sub check_mode_recursive
+{
+ my ($dir, $expected_dir_mode, $expected_file_mode, $ignore_list) = @_;
+
+ # Result defaults to true
+ my $result = 1;
+
+ find
+ (
+ {follow_fast => 1,
+ wanted =>
+ sub
+ {
+ my $file_stat = stat($File::Find::name);
+
+ # Is file in the ignore list?
+ foreach my $ignore ($ignore_list ? @{$ignore_list} : [])
+ {
+ if ("$dir/$ignore" eq $File::Find::name)
+ {
+ return;
+ }
+ }
+
+ defined($file_stat)
+ or die("unable to stat $File::Find::name");
+
+ my $file_mode = S_IMODE($file_stat->mode);
+
+ # Is this a file?
+ if (S_ISREG($file_stat->mode))
+ {
+ if ($file_mode != $expected_file_mode)
+ {
+ print(*STDERR,
+ sprintf("$File::Find::name mode must be %04o\n",
+ $expected_file_mode));
+
+ $result = 0;
+ return;
+ }
+ }
+ # Else a directory?
+ elsif (S_ISDIR($file_stat->mode))
+ {
+ if ($file_mode != $expected_dir_mode)
+ {
+ print(*STDERR,
+ sprintf("$File::Find::name mode must be %04o\n",
+ $expected_dir_mode));
+
+ $result = 0;
+ return;
+ }
+ }
+ # Else something we can't handle
+ else
+ {
+ die "unknown file type for $File::Find::name";
+ }
+ }},
+ $dir
+ );
+
+ return $result;
+}
+
# Check presence of a given regexp within pg_config.h for the installation
# where tests are running, returning a match status result depending on
# that.
--
2.14.3 (Apple Git-98)
group-access-v11-03-group.patchtext/plain; charset=UTF-8; name=group-access-v11-03-group.patch; x-mac-creator=0; x-mac-type=0Download
From 6de93d57ea256bfd606ee56adf2f9725781c618b Mon Sep 17 00:00:00 2001
From: David Steele <david@pgmasters.net>
Date: Wed, 14 Mar 2018 13:54:48 -0400
Subject: [PATCH 3/3] Allow group access on PGDATA.
This includes backend changes, utility changes (initdb, pg_ctl, pg_upgrade, etc.) and tests for each utility.
---
doc/src/sgml/ref/initdb.sgml | 19 ++++++++++++++
doc/src/sgml/runtime.sgml | 14 ++++++++++-
src/backend/postmaster/postmaster.c | 34 +++++++++++++++++--------
src/backend/utils/init/miscinit.c | 21 +++++++---------
src/bin/initdb/initdb.c | 22 +++++++++++------
src/bin/initdb/t/001_initdb.pl | 13 +++++++++-
src/bin/pg_ctl/pg_ctl.c | 5 +++-
src/bin/pg_ctl/t/001_start_stop.pl | 24 +++++++++++++++++-
src/bin/pg_resetwal/pg_resetwal.c | 4 +--
src/bin/pg_resetwal/t/001_basic.pl | 9 +++++--
src/bin/pg_rewind/RewindTest.pm | 9 ++++---
src/bin/pg_rewind/pg_rewind.c | 4 +--
src/bin/pg_rewind/t/002_databases.pl | 6 +++--
src/bin/pg_upgrade/pg_upgrade.c | 5 +++-
src/bin/pg_upgrade/test.sh | 14 +++++------
src/common/Makefile | 2 +-
src/common/file_perm.c | 48 ++++++++++++++++++++++++++++++++++++
src/include/common/file_perm.h | 27 ++++++++++++++++----
src/test/perl/PostgresNode.pm | 27 +++++++++++++++++++-
src/test/perl/TestLib.pm | 25 +++++++++++++++++++
src/tools/msvc/Mkvcbuild.pm | 2 +-
21 files changed, 273 insertions(+), 61 deletions(-)
create mode 100644 src/common/file_perm.c
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 585665f161..5f57b933b9 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -76,6 +76,14 @@ PostgreSQL documentation
to do so.)
</para>
+ <para>
+ For security reasons the new cluster created by <command>initdb</command>
+ will only be accessible by the cluster owner by default. The
+ <option>--allow-group-access</option> option allows any user in the same
+ group as the cluster owner to read files in the cluster. This is useful
+ for performing backups as a non-privileged user.
+ </para>
+
<para>
<command>initdb</command> initializes the database cluster's default
locale and character set encoding. The character set encoding,
@@ -301,6 +309,17 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-g</option></term>
+ <term><option>--allow-group-access</option></term>
+ <listitem>
+ <para>
+ Allows users in the same group as the cluster owner to read all cluster
+ files created by <command>initdb</command>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-W</option></term>
<term><option>--pwprompt</option></term>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 587b430527..40ce3d9813 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -137,7 +137,10 @@ postgres$ <userinput>initdb -D /usr/local/pgsql/data</userinput>
database, it is essential that it be secured from unauthorized
access. <command>initdb</command> therefore revokes access
permissions from everyone but the
- <productname>PostgreSQL</productname> user.
+ <productname>PostgreSQL</productname> user, and optionally, group.
+ Group access, when enabled, is read-only. This allows an unprivileged
+ user in the <productname>PostgreSQL</productname> group to take a backup of
+ the cluster data or perform other operations that only require read access.
</para>
<para>
@@ -2194,6 +2197,15 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
member of the group that has access to those certificate and key files.
</para>
+ <para>
+ If the data directory allows group read access then certificate files may
+ need to be located outside of the data directory in order to conform to the
+ security requirements outlined above. Generally, group access is enabled
+ to allow an unprivileged user to backup the database, and in that case the
+ backup software will not be able to read the certificate files and will
+ likely error.
+ </para>
+
<para>
If the private key is protected with a passphrase, the
server will prompt for the passphrase and will not start until it has
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 868fba8cac..4b79f4b7e7 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -588,7 +588,9 @@ PostmasterMain(int argc, char *argv[])
IsPostmasterEnvironment = true;
/*
- * for security, no dir or file created can be group or other accessible
+ * By default, no directory or file created can be group or other
+ * accessible. This may be modified later depending on the permissions of
+ * the data directory.
*/
umask(PG_MODE_MASK_DEFAULT);
@@ -1522,25 +1524,37 @@ checkDataDir(void)
#endif
/*
- * Check if the directory has group or world access. If so, reject.
+ * Check if the directory has correct permissions. If not, 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.
+ * 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 & (S_IRWXG | S_IRWXO))
+ if (stat_buf.st_mode & PG_MODE_MASK_ALLOW_GROUP)
ereport(FATAL,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("data directory \"%s\" has group or world access",
+ errmsg("data directory \"%s\" has invalid permissions",
DataDir),
- errdetail("Permissions should be u=rwx (0700).")));
+ errdetail("Permissions should be u=rwx (0700) or u=rwx,g=rx (0750).")));
+#endif
+
+ /*
+ * Reset the file mode creation 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 mask to
+ * allow group read/execute on newly created files and directories.
+ *
+ * Suppress when on Windows, because there may not be proper support
+ * for Unix-y file permissions.
+ */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+ umask(PG_MODE_MASK_DEFAULT & ~stat_buf.st_mode);
#endif
/* Look for PG_VERSION before looking for pg_control */
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 82da4b9f27..b14817dd32 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -829,7 +829,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_MODE_DEFAULT);
@@ -899,17 +899,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/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 0129ecf8c1..f322610679 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -145,6 +145,7 @@ static bool data_checksums = false;
static char *xlog_dir = NULL;
static char *str_wal_segment_size_mb = NULL;
static int wal_segment_size_mb;
+static mode_t file_mode_mask = PG_MODE_MASK_DEFAULT;
/* internal vars */
@@ -1171,7 +1172,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
+ if (chmod(path, PG_FILE_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1191,7 +1192,7 @@ setup_config(void)
sprintf(path, "%s/postgresql.auto.conf", pg_data);
writefile(path, autoconflines);
- if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
+ if (chmod(path, PG_FILE_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1278,7 +1279,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_hba.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
+ if (chmod(path, PG_FILE_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1294,7 +1295,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_ident.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
+ if (chmod(path, PG_FILE_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -2312,6 +2313,7 @@ usage(const char *progname)
printf(_(" --auth-local=METHOD default authentication method for local-socket connections\n"));
printf(_(" [-D, --pgdata=]DATADIR location for this database cluster\n"));
printf(_(" -E, --encoding=ENCODING set default encoding for new databases\n"));
+ printf(_(" -g, --allow-group-access allow group read/execute on data directory\n"));
printf(_(" --locale=LOCALE set default locale for new databases\n"));
printf(_(" --lc-collate=, --lc-ctype=, --lc-messages=LOCALE\n"
" --lc-monetary=, --lc-numeric=, --lc-time=LOCALE\n"
@@ -2711,7 +2713,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (chmod(pg_data, PG_DIR_MODE_DEFAULT) != 0)
+ if (chmod(pg_data, PG_DIR_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2797,7 +2799,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (chmod(xlog_dir, PG_DIR_MODE_DEFAULT) != 0)
+ if (chmod(xlog_dir, PG_DIR_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2884,7 +2886,7 @@ initialize_data_directory(void)
setup_signals();
/* Set dir/file mode mask */
- umask(PG_MODE_MASK_DEFAULT);
+ umask(file_mode_mask);
create_data_directory();
@@ -3018,6 +3020,7 @@ main(int argc, char *argv[])
{"waldir", required_argument, NULL, 'X'},
{"wal-segsize", required_argument, NULL, 12},
{"data-checksums", no_argument, NULL, 'k'},
+ {"allow-group-access", no_argument, NULL, 'g'},
{NULL, 0, NULL, 0}
};
@@ -3059,7 +3062,7 @@ main(int argc, char *argv[])
/* process command-line options */
- while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:", long_options, &option_index)) != -1)
+ while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:g", long_options, &option_index)) != -1)
{
switch (c)
{
@@ -3153,6 +3156,9 @@ main(int argc, char *argv[])
case 12:
str_wal_segment_size_mb = pg_strdup(optarg);
break;
+ case 'g':
+ file_mode_mask = PG_MODE_MASK_ALLOW_GROUP;
+ break;
default:
/* getopt_long already emitted a complaint */
fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index 3331560445..f08fc6ea8f 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -4,9 +4,11 @@
use strict;
use warnings;
+use Fcntl ':mode';
+use File::stat qw{lstat};
use PostgresNode;
use TestLib;
-use Test::More tests => 16;
+use Test::More tests => 18;
my $tempdir = TestLib::tempdir;
my $xlogdir = "$tempdir/pgxlog";
@@ -50,3 +52,12 @@ mkdir $datadir;
}
command_ok([ 'initdb', '-S', $datadir ], 'sync only');
command_fails([ 'initdb', $datadir ], 'existing data directory');
+
+# Init a new db with group access
+my $datadir_group = "$tempdir/data_group";
+
+command_ok(
+ [ 'initdb', '-g', $datadir_group ],
+ 'successful creation with group access');
+
+ok(check_mode_recursive($datadir_group, 0750, 0640));
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index e83a7179ea..bb70e625fd 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -2171,7 +2171,7 @@ main(int argc, char **argv)
*/
argv0 = argv[0];
- /* Set dir/file mode mask */
+ /* Set restrictive mode mask until PGDATA permissions are checked */
umask(PG_MODE_MASK_DEFAULT);
/* support --help and --version even if invoked as root */
@@ -2407,6 +2407,9 @@ main(int argc, char **argv)
snprintf(version_file, MAXPGPATH, "%s/PG_VERSION", pg_data);
snprintf(pid_file, MAXPGPATH, "%s/postmaster.pid", pg_data);
snprintf(backup_file, MAXPGPATH, "%s/backup_label", pg_data);
+
+ /* Set mask based on PGDATA permissions */
+ umask(DataDirectoryMask(pg_data));
}
switch (ctl_command)
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl
index 3d82abe696..91e7f00326 100644
--- a/src/bin/pg_ctl/t/001_start_stop.pl
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -2,9 +2,11 @@ use strict;
use warnings;
use Config;
+use Fcntl ':mode';
+use File::stat qw{lstat};
use PostgresNode;
use TestLib;
-use Test::More tests => 20;
+use Test::More tests => 24;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -68,4 +70,24 @@ command_ok(
ok(-f $logFileName);
ok(check_mode_recursive("$tempdir/data", 0700, 0600));
+# Log file for second perm test
+$logFileName = "$tempdir/data/perm-test-640.log";
+
+# Change the data dir mode so log file will be created with group read
+# privileges on the next start
+system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
+
+chmod_recursive("$tempdir/data", 0750, 0640);
+
+command_ok(
+ [ 'pg_ctl', 'start', '-D', "$tempdir/data", '-l', $logFileName ],
+ 'start server to check group permissions');
+
+ok(-f $logFileName);
+ok(check_mode_recursive("$tempdir/data", 0750, 0640));
+
+command_ok(
+ [ 'pg_ctl', 'restart', '-D', "$tempdir/data", '-l', $logFileName ],
+ 'pg_ctl restart with server running');
+
system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 47abd41fc3..6a4dc502c4 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -328,8 +328,8 @@ main(int argc, char *argv[])
exit(1);
}
- /* Set dir/file mode mask */
- umask(PG_MODE_MASK_DEFAULT);
+ /* Set mask based on PGDATA permissions */
+ umask(DataDirectoryMask(DataDir));
/* Check that data directory matches our server version */
CheckDataVersion();
diff --git a/src/bin/pg_resetwal/t/001_basic.pl b/src/bin/pg_resetwal/t/001_basic.pl
index 674e11b692..f1bcb682f9 100644
--- a/src/bin/pg_resetwal/t/001_basic.pl
+++ b/src/bin/pg_resetwal/t/001_basic.pl
@@ -4,7 +4,7 @@ use warnings;
use Config;
use PostgresNode;
use TestLib;
-use Test::More tests => 14;
+use Test::More tests => 15;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -40,9 +40,12 @@ ok(check_mode_recursive($pgdata, 0700, 0600), 'check PGDATA permissions');
$node->start;
-# Reset to specific WAL segment
+# Reset to specific WAL segment. Also enable group access to make sure files
+# and directories are created with group permissions.
$node->stop;
+chmod_recursive($pgdata, 0750, 0640);
+
$node->command_ok(
['pg_resetwal', '-l', '000000070000000700000007', '-D', $pgdata],
'set to specific WAL');
@@ -52,4 +55,6 @@ is_deeply(
[sort(qw(. .. archive_status 000000070000000700000007))],
'WAL recreated');
+ok(check_mode_recursive($pgdata, 0750, 0640), 'check PGDATA permissions');
+
$node->start;
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index 7b632c7dcd..63d9bd517d 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -115,11 +115,13 @@ sub check_query
sub setup_cluster
{
- my $extra_name = shift;
+ my $extra_name = shift; # Used to differentiate clusters
+ my $extra = shift; # Extra params for initdb
# Initialize master, data checksums are mandatory
$node_master = get_new_node('master' . ($extra_name ? "_${extra_name}" : ''));
- $node_master->init(allows_streaming => 1);
+ $node_master->init(
+ allows_streaming => 1, extra => $extra);
# Set wal_keep_segments to prevent WAL segment recycling after enforced
# checkpoints in the tests.
$node_master->append_conf('postgresql.conf', qq(
@@ -237,7 +239,8 @@ sub run_pg_rewind
"$tmp_folder/master-postgresql.conf.tmp",
"$master_pgdata/postgresql.conf");
- chmod(0600, "$master_pgdata/postgresql.conf")
+ chmod($node_master->group_access() ? 0640 : 0600,
+ "$master_pgdata/postgresql.conf")
or BAIL_OUT(
"unable to set permissions for $master_pgdata/postgresql.conf");
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 3d179e2c7d..11153e5f2f 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -186,8 +186,8 @@ main(int argc, char **argv)
exit(1);
}
- /* Set dir/file mode mask */
- umask(PG_MODE_MASK_DEFAULT);
+ /* Set mask based on PGDATA permissions */
+ umask(DataDirectoryMask(datadir_target));
/*
* Don't allow pg_rewind to be run as root, to avoid overwriting the
diff --git a/src/bin/pg_rewind/t/002_databases.pl b/src/bin/pg_rewind/t/002_databases.pl
index 37cdd712f3..570fa5cee0 100644
--- a/src/bin/pg_rewind/t/002_databases.pl
+++ b/src/bin/pg_rewind/t/002_databases.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 6;
use RewindTest;
@@ -9,7 +9,7 @@ sub run_test
{
my $test_mode = shift;
- RewindTest::setup_cluster($test_mode);
+ RewindTest::setup_cluster($test_mode, ['-g']);
RewindTest::start_master();
# Create a database in master.
@@ -42,6 +42,8 @@ template1
),
'database names');
+ ok (check_mode_recursive(
+ $node_master->data_dir(), 0750, 0640, ['postmaster.pid']));
RewindTest::clean_rewind_test();
}
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 59df6fd88f..54f54fb0a4 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -79,7 +79,7 @@ main(int argc, char **argv)
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_upgrade"));
- /* Ensure that all files created by pg_upgrade are non-world-readable */
+ /* Set default restrictive mask until new cluster permissions are read */
umask(PG_MODE_MASK_DEFAULT);
parseCommandLine(argc, argv);
@@ -100,6 +100,9 @@ main(int argc, char **argv)
check_cluster_compatibility(live_check);
+ /* Set mask based on new PGDATA permissions */
+ umask(DataDirectoryMask(new_cluster.pgdata));
+
check_and_dump_old_cluster(live_check);
diff --git a/src/bin/pg_upgrade/test.sh b/src/bin/pg_upgrade/test.sh
index 4e8db4aaf4..24631a91c6 100644
--- a/src/bin/pg_upgrade/test.sh
+++ b/src/bin/pg_upgrade/test.sh
@@ -20,9 +20,9 @@ unset MAKELEVEL
# Run a given "initdb" binary and overlay the regression testing
# authentication configuration.
standard_initdb() {
- # To increase coverage of non-standard segment size without
- # increase test runtime, run these tests with a lower setting.
- "$1" -N --wal-segsize 1
+ # To increase coverage of non-standard segment size and group access
+ # without increasing test runtime, run these tests with a custom setting.
+ "$1" -N --wal-segsize 1 -g
if [ -n "$TEMP_CONFIG" -a -r "$TEMP_CONFIG" ]
then
cat "$TEMP_CONFIG" >> "$PGDATA/postgresql.conf"
@@ -231,13 +231,13 @@ standard_initdb 'initdb'
pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "${PGDATA}" -b "$oldbindir" -B "$bindir" -p "$PGPORT" -P "$PGPORT"
# make sure all directories and files have group permissions
-if [ $(find ${PGDATA} -type f ! -perm 600 | wc -l) -ne 0 ]; then
- echo "files in PGDATA with permission != 600";
+if [ $(find ${PGDATA} -type f ! -perm 640 | wc -l) -ne 0 ]; then
+ echo "files in PGDATA with permission != 640";
exit 1;
fi
-if [ $(find ${PGDATA} -type d ! -perm 700 | wc -l) -ne 0 ]; then
- echo "directories in PGDATA with permission != 700";
+if [ $(find ${PGDATA} -type d ! -perm 750 | wc -l) -ne 0 ]; then
+ echo "directories in PGDATA with permission != 750";
exit 1;
fi
diff --git a/src/common/Makefile b/src/common/Makefile
index 80e78d72fe..504375bef4 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -51,7 +51,7 @@ else
OBJS_COMMON += sha2.o
endif
-OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o file_utils.o restricted_token.o
+OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o file_perm.o file_utils.o restricted_token.o
OBJS_SRV = $(OBJS_COMMON:%.o=%_srv.o)
diff --git a/src/common/file_perm.c b/src/common/file_perm.c
new file mode 100644
index 0000000000..b490220b64
--- /dev/null
+++ b/src/common/file_perm.c
@@ -0,0 +1,48 @@
+/*-------------------------------------------------------------------------
+ *
+ * File and directory permission routines
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/file_perm.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#error "This file is not expected to be compiled for backend code"
+#endif
+
+#include <sys/stat.h>
+
+#include "common/file_perm.h"
+
+/*
+ * Determine the mask to use when writing to PGDATA by examining the mode of the
+ * PGDATA directory. If group read/execute are present in the PGDATA directory
+ * mode, the mask will be relaxed to allow group read/execute on all newly
+ * created files and directories.
+ *
+ * Errors are not handled here and should be checked by the frontend
+ * application.
+ */
+mode_t
+DataDirectoryMask(const char *dataDir)
+{
+ struct stat statBuf;
+
+ /*
+ * If an error occurs getting the mode then return the more restrictive
+ * mask. It is the reponsibility of the frontend application to generate an
+ * error if the PGDATA directory cannot be accessed.
+ */
+ if (stat(dataDir, &statBuf) != 0)
+ return PG_MODE_MASK_DEFAULT;
+
+ /*
+ * Construct the mask that the caller should pass to umask().
+ */
+ return PG_MODE_MASK_DEFAULT & ~statBuf.st_mode;
+}
diff --git a/src/include/common/file_perm.h b/src/include/common/file_perm.h
index 3c6da0e000..2176e5cae5 100644
--- a/src/include/common/file_perm.h
+++ b/src/include/common/file_perm.h
@@ -1,6 +1,6 @@
/*-------------------------------------------------------------------------
*
- * File and directory permission constants
+ * File and directory permission constants and routines
*
*
* Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
@@ -20,13 +20,30 @@
#define PG_MODE_MASK_DEFAULT (S_IRWXG | S_IRWXO)
/*
- * Default mode for created files.
+ * Optional mode mask for data directory permissions that allows group
+ * read/execute.
*/
-#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR)
+#define PG_MODE_MASK_ALLOW_GROUP (S_IWGRP | S_IRWXO)
/*
- * Default mode for directories.
+ * Default mode for created files, unless something else is specified using
+ * the *Perm() function variants.
*/
-#define PG_DIR_MODE_DEFAULT S_IRWXU
+#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR | S_IRGRP)
+
+/*
+ * Default mode for directories created with MakeDirectoryDefaultPerm().
+ */
+#define PG_DIR_MODE_DEFAULT (S_IRWXU | S_IRGRP | S_IXGRP)
+
+#ifdef FRONTEND
+
+/*
+ * Determine the mask to use when writing to PGDATA
+ */
+mode_t DataDirectoryMask(const char *dataDir);
+
+#endif /* FRONTEND */
+
#endif /* FILE_PERM_H */
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 76e571b98c..1bd91524d7 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -86,9 +86,11 @@ use Carp;
use Config;
use Cwd;
use Exporter 'import';
+use Fcntl qw(:mode);
use File::Basename;
use File::Path qw(rmtree);
use File::Spec;
+use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
use RecursiveCopy;
@@ -269,6 +271,26 @@ sub connstr
=pod
+=item $node->group_access()
+
+Does the data dir allow group access?
+
+=cut
+
+sub group_access
+{
+ my ($self) = @_;
+
+ my $dir_stat = stat($self->data_dir);
+
+ defined($dir_stat)
+ or die('unable to stat ' . $self->data_dir);
+
+ return (S_IMODE($dir_stat->mode) == 0750);
+}
+
+=pod
+
=item $node->data_dir()
Returns the path to the data directory. postgresql.conf and pg_hba.conf are
@@ -460,6 +482,9 @@ sub init
}
close $conf;
+ chmod($self->group_access ? 0640 : 0600, "$pgdata/postgresql.conf")
+ or die("unable to set permissions for $pgdata/postgresql.conf");
+
$self->set_replication_conf if $params{allows_streaming};
$self->enable_archiving if $params{has_archiving};
}
@@ -485,7 +510,7 @@ sub append_conf
TestLib::append_to_file($conffile, $str . "\n");
- chmod(0600, $conffile)
+ chmod($self->group_access() ? 0640 : 0600, $conffile)
or die("unable to set permissions for $conffile");
}
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index 9de8dc9041..93ac24e261 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -30,6 +30,7 @@ our @EXPORT = qw(
slurp_file
append_to_file
check_mode_recursive
+ chmod_recursive
check_pg_config
system_or_bail
system_log
@@ -295,6 +296,30 @@ sub check_mode_recursive
return $result;
}
+# Change mode recursively on a directory
+sub chmod_recursive
+{
+ my ($dir, $dir_mode, $file_mode) = @_;
+
+ find
+ (
+ {follow_fast => 1,
+ wanted =>
+ sub
+ {
+ my $file_stat = stat($File::Find::name);
+
+ if (defined($file_stat))
+ {
+ chmod(S_ISDIR($file_stat->mode) ? $dir_mode : $file_mode,
+ $File::Find::name)
+ or die "unable to chmod $File::Find::name";
+ }
+ }},
+ $dir
+ );
+}
+
# Check presence of a given regexp within pg_config.h for the installation
# where tests are running, returning a match status result depending on
# that.
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 72976f44d8..78d4934ec6 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -125,7 +125,7 @@ sub mkvcbuild
}
our @pgcommonfrontendfiles = (
- @pgcommonallfiles, qw(fe_memutils.c file_utils.c
+ @pgcommonallfiles, qw(fe_memutils.c file_perm.c file_utils.c
restricted_token.c));
our @pgcommonbkndfiles = @pgcommonallfiles;
--
2.14.3 (Apple Git-98)
Hi Michael,
On 3/13/18 9:31 PM, Michael Paquier wrote:
On Tue, Mar 13, 2018 at 12:19:07PM -0400, David Steele wrote:
I'll attach new patches in a reply to [1] once I have made the changes
Tom requested.Cool, thanks for your patience. Looking forward to seeing those. I'll
spend time on 0003 at the same time. Let's also put the additional
umask calls for pg_rewind and pg_resetwal into a separate patch.
Do you mean a separate patch in this patch set, or a separate patch
entirely? 02 depends on this logic, so I guess you mean create a new
patch between 01 and 02?
Are you just trying to make sure it gets in? I'd rather wait a bit - if
02 doesn't look like it will get in, then I'll pull this logic out into
a separate patch.
Thanks,
--
-David
david@pgmasters.net
On Wed, Mar 14, 2018 at 02:15:03PM -0400, David Steele wrote:
Do you mean a separate patch in this patch set, or a separate patch
entirely? 02 depends on this logic, so I guess you mean create a new
patch between 01 and 02?
Yes, one new patch that which can be applied on top of 1.
Are you just trying to make sure it gets in? I'd rather wait a bit - if
02 doesn't look like it will get in, then I'll pull this logic out into
a separate patch.
Okay.
--
Michael
On Wed, Mar 14, 2018 at 02:08:19PM -0400, David Steele wrote:
OK, that being the case a new patch set is attached that sets the mode
of postmaster.pid the same as other files in PGDATA.
+1.
I also cleaned up / corrected / added comments in various places.
Patches 1 and 2 look fine to me. The new umask calls in pg_rewind and
pg_resetwal could be split into a separate patch but that's a minor
issue.
When taking a base backup from a data folder which has group access,
then the tar data, as well as the untar'ed data, are still using
0600 as umask for files and 0700 for folders. Is that an expected
behavior? I would have imagined that sendFileWithContent() and
_tarWriteDir() should enforce the file mode to have group access if the
cluster has been initialized to work as such. Still as this is a
feature aimed at being used for custom backups, that's not really a
blocker I guess. Visibly there would be no need for a -g switch in
pg_basebackup as it is possible to guess from the received untar'ed
files what should be the permissions of the data based on what is
received in pg_basebackup.c. It would also be necessary to change the
permissions of pg_wal as this is created before receiving any files.
Speaking of which, we may want to switch the values used for st_mode to
what file_perm.h is giving in basebackup.c?
We should also replace the hardcoded 0700 value in pg_backup_directory.c
by what file_perm.h offers? I would recommend to not touch at mkdtemp.c
as this comes from NetBSD.
+=item $node->group_access()
+
+Does the data dir allow group access?
+
Nit: s/dir/directory/.
Indentation is weird in PostgresNode.pm for some of the chmod calls
(tabs not spaces please).
--
Michael
On 3/15/18 3:17 AM, Michael Paquier wrote:
On Wed, Mar 14, 2018 at 02:08:19PM -0400, David Steele wrote:
When taking a base backup from a data folder which has group access,
then the tar data, as well as the untar'ed data, are still using
0600 as umask for files and 0700 for folders. Is that an expected
behavior? I would have imagined that sendFileWithContent() and
_tarWriteDir() should enforce the file mode to have group access if the
cluster has been initialized to work as such.
We can certainly make base backup understand the group access mode.
Should we continue hard-coding the mode, or use the actual dir/file mode?
Still as this is a
feature aimed at being used for custom backups, that's not really a
blocker I guess.
Seems like a good thing to do, though, so I'll have a look for the next
patch.
Visibly there would be no need for a -g switch in
pg_basebackup as it is possible to guess from the received untar'ed
files what should be the permissions of the data based on what is
received in pg_basebackup.c. It would also be necessary to change the
permissions of pg_wal as this is created before receiving any files.
This part might be trickier.
Speaking of which, we may want to switch the values used for st_mode to
what file_perm.h is giving in basebackup.c?
Will do.
We should also replace the hardcoded 0700 value in pg_backup_directory.c
by what file_perm.h offers? I would recommend to not touch at mkdtemp.c
as this comes from NetBSD.
Will do.
+=item $node->group_access() + +Does the data dir allow group access? + Nit: s/dir/directory/.Indentation is weird in PostgresNode.pm for some of the chmod calls
(tabs not spaces please).
I'll fix these in the next patch as well.
Thanks,
--
-David
david@pgmasters.net
Greetings David, Michael, all,
* David Steele (david@pgmasters.net) wrote:
On 3/15/18 3:17 AM, Michael Paquier wrote:
On Wed, Mar 14, 2018 at 02:08:19PM -0400, David Steele wrote:
When taking a base backup from a data folder which has group access,
then the tar data, as well as the untar'ed data, are still using
0600 as umask for files and 0700 for folders. Is that an expected
behavior? I would have imagined that sendFileWithContent() and
_tarWriteDir() should enforce the file mode to have group access if the
cluster has been initialized to work as such.We can certainly make base backup understand the group access mode.
Should we continue hard-coding the mode, or use the actual dir/file mode?
Given that we're already, as I recall, including the owner/group of the
file being backed up through pg_basebackup in the tarball, it seems like
we should be including whatever the current dir/file mode is too. Those
files may not have anything to do with PostgreSQL, after all, and a
'natural' tar of the directory would capture that information too.
Still as this is a
feature aimed at being used for custom backups, that's not really a
blocker I guess.Seems like a good thing to do, though, so I'll have a look for the next
patch.
+1.
Visibly there would be no need for a -g switch in
pg_basebackup as it is possible to guess from the received untar'ed
files what should be the permissions of the data based on what is
received in pg_basebackup.c. It would also be necessary to change the
permissions of pg_wal as this is created before receiving any files.This part might be trickier.
This seems like another case where what we should be doing, and what
people will be expecting, I'd think, is just what they're used to tar
doing in these cases- which would be setting the dir/file mode for each
file based on what's in the tarball. Again, the files which are in the
data dir are, sadly, not always just those that PG is familiar with.
Thanks!
Stephen
On Fri, Mar 16, 2018 at 11:12:44AM -0400, Stephen Frost wrote:
Given that we're already, as I recall, including the owner/group of the
file being backed up through pg_basebackup in the tarball, it seems like
we should be including whatever the current dir/file mode is too. Those
files may not have anything to do with PostgreSQL, after all, and a
'natural' tar of the directory would capture that information too.
Yes. tar does include this information in the header associated to each
file. Do we want an additional switch for pg_receivexlog as well by the
way which generates WAL segments with group access? Some people take
base backups without slots and rely on an archive to look for remaining
segments up to the end-of-backup record.
--
Michael
On 3/16/18 11:12 AM, Stephen Frost wrote:
Visibly there would be no need for a -g switch in
pg_basebackup as it is possible to guess from the received untar'ed
files what should be the permissions of the data based on what is
received in pg_basebackup.c. It would also be necessary to change the
permissions of pg_wal as this is created before receiving any files.This part might be trickier.
This seems like another case where what we should be doing, and what
people will be expecting, I'd think, is just what they're used to tar
doing in these cases- which would be setting the dir/file mode for each
file based on what's in the tarball. Again, the files which are in the
data dir are, sadly, not always just those that PG is familiar with.
I've been working on this and have become convinced that adding group
permissions to files that pg_basebackup writes to disk based on whether
group permissions are enabled in PGDATA isn't the right way to go.
To be clear, I'm not taking about the permissions set within the tar
file - I think it makes sense to use the actual PGDATA permissions in
that case.
pg_basebackup may not be running as postgres, and even if it is I don't
think we can assume that group access is appropriate for the files that
it writes. It's a different environment and different security rules
may apply.
It seems to me that pg_basebackup and pg_receivexlog should have a -g
option to control the mode of the files that they write to disk (not
including the modes stored in the tar files).
Or perhaps we should just update the perms in the tar files for now and
leave the rest alone.
Thoughts?
--
-David
david@pgmasters.net
David,
* David Steele (david@pgmasters.net) wrote:
On 3/16/18 11:12 AM, Stephen Frost wrote:
Visibly there would be no need for a -g switch in
pg_basebackup as it is possible to guess from the received untar'ed
files what should be the permissions of the data based on what is
received in pg_basebackup.c. It would also be necessary to change the
permissions of pg_wal as this is created before receiving any files.This part might be trickier.
This seems like another case where what we should be doing, and what
people will be expecting, I'd think, is just what they're used to tar
doing in these cases- which would be setting the dir/file mode for each
file based on what's in the tarball. Again, the files which are in the
data dir are, sadly, not always just those that PG is familiar with.I've been working on this and have become convinced that adding group
permissions to files that pg_basebackup writes to disk based on whether
group permissions are enabled in PGDATA isn't the right way to go.To be clear, I'm not taking about the permissions set within the tar
file - I think it makes sense to use the actual PGDATA permissions in
that case.
What that implies then, if I'm following, is that the results of a
pg_basebackup -Ft && tar -xvf ; would be different from the results of a
pg_basebackup -Fp ; .
That seems like it would be rather odd and confusing to users and so I
have a hard time agreeing that such an inconsistency makes sense.
pg_basebackup may not be running as postgres, and even if it is I don't
think we can assume that group access is appropriate for the files that
it writes. It's a different environment and different security rules
may apply.
Sure, and the same security rules may also apply. Consider an
environment which is managed through a change management system (as many
are these days...) where the pg_basebackup is being run specifically to
set up a replica (which is quite commonly done..) and then allow a tool
like pgbackrest to use both the primary and the replica for backups,
where pgbackrest is run as an independent user which shares the same
group as the PG user and needs group-read access on the replica. After
building such a replica, the user would have to do a chmod across the
entire data directory, even though the primary was initialized with
group-read access, or, oddly, perform the pg_basebackup to tar files and
then extract those tar files instead of using the plain format.
The general pg_basebackup->replica process works great today and the
primary and the replica more-or-less match as if they were both initdb'd
the same way, or a backup/restore was done, and not preserving the
privileges as they exist would end up diverging from that.
In these cases we're really talking about the defaults here; as I
mention below, I agree that having the ability to control what ends up
happening definitely makes sense (as tar does...).
It seems to me that pg_basebackup and pg_receivexlog should have a -g
option to control the mode of the files that they write to disk (not
including the modes stored in the tar files).Or perhaps we should just update the perms in the tar files for now and
leave the rest alone.
Having options to pg_basebackup to control what's done makes sense to
me- but whatever those options do, I'd expect them to apply equally to
the tar files and to the files extracted with plain mode. Having those
be different really strikes me as very odd.
Thanks!
Stephen
On Tue, Mar 20, 2018 at 05:44:22PM -0400, Stephen Frost wrote:
* David Steele (david@pgmasters.net) wrote:
On 3/16/18 11:12 AM, Stephen Frost wrote:
It seems to me that pg_basebackup and pg_receivexlog should have a -g
option to control the mode of the files that they write to disk (not
including the modes stored in the tar files).Or perhaps we should just update the perms in the tar files for now and
leave the rest alone.Having options to pg_basebackup to control what's done makes sense to
me- but whatever those options do, I'd expect them to apply equally to
the tar files and to the files extracted with plain mode. Having those
be different really strikes me as very odd.
Agreed for the consistency part, permissions should be applied
consistently for the folder and the tar format.
Having the option for pg_receivewal definitely makes sense to me, as it
is the one in charge of opening and writing the WAL segments. For
pg_basebackup, let's not forget that there is one tar file for each
tablespace, and that each file is received separately using a COPY
stream. There is some logic already which parses the tar header part of
an individual file in order to look for recovery.conf (see
ReceiveTarFile() in pg_basebackup.c). It would be possible to enforce
grouping permissions when receiving each file, and this would be rather
low-cost in performance I think. Honestly, my vote would go for having
the permissions set correctly by the source server as this brings
consistency to the whole experience without complicating the interface
of pg_basebackup, and this also makes the footprint of this patch on
pg_basebackup way lighter.
--
Michael
I have committed a basic pg_resetwal test suite as part of another
patch, so please adjust accordingly.
It looks to me like your pg_resetwal tests contain a bit of useless
copy-and-paste. For example, you are not using use Config, nor
$tempdir, and you run $node->stop right after $node->start. Please
clean that up a bit, or comment, if appropriate.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Hi Peter,
On 3/23/18 10:36 AM, Peter Eisentraut wrote:
I have committed a basic pg_resetwal test suite as part of another
patch, so please adjust accordingly.It looks to me like your pg_resetwal tests contain a bit of useless
copy-and-paste. For example, you are not using use Config, nor
$tempdir, and you run $node->stop right after $node->start. Please
clean that up a bit, or comment, if appropriate.
Yeah, definitely too much copy-paste going on.
The various start/stops were intended the ensure that PG actually starts
with the reset WAL. I see that these tests don't do them, though, so
perhaps that's not a good use of test time.
The pg_rewind tests work for my purposes but it seems worth preserving
the ones I wrote since there is no overlap.
I've attached a patch that integrates my tests with the current tests.
If you don't think they are worth adding then I'll just drop them from
my patchset.
Thanks,
--
-David
david@pgmasters.net
Attachments:
pgrewind-more-tests.patchtext/plain; charset=UTF-8; name=pgrewind-more-tests.patch; x-mac-creator=0; x-mac-type=0Download
diff --git a/src/bin/pg_resetwal/t/001_basic.pl b/src/bin/pg_resetwal/t/001_basic.pl
index 1b157cb555..474ece99c7 100644
--- a/src/bin/pg_resetwal/t/001_basic.pl
+++ b/src/bin/pg_resetwal/t/001_basic.pl
@@ -3,7 +3,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 11;
+use Test::More tests => 16;
program_help_ok('pg_resetwal');
program_version_ok('pg_resetwal');
@@ -15,3 +15,29 @@ $node->init;
command_like([ 'pg_resetwal', '-n', $node->data_dir ],
qr/checkpoint/,
'pg_resetwal -n produces output');
+
+# Reset WAL after segment has been removed
+my $pgwal = $node->data_dir . '/pg_wal';
+
+unlink("$pgwal/000000010000000000000001") == 1
+ or BAIL_OUT("unable to remove 000000010000000000000001");
+
+is_deeply(
+ [sort(slurp_dir($pgwal))], [sort(qw(. .. archive_status))], 'no WAL');
+
+$node->command_ok(['pg_resetwal', '-D', $node->data_dir], 'recreate pg_wal');
+
+is_deeply(
+ [sort(slurp_dir($pgwal))],
+ [sort(qw(. .. archive_status 000000010000000000000002))],
+ 'WAL recreated');
+
+# Reset to specific WAL segment
+$node->command_ok(
+ ['pg_resetwal', '-l', '000000070000000700000007', '-D', $node->data_dir],
+ 'set to specific WAL');
+
+is_deeply(
+ [sort(slurp_dir($pgwal))],
+ [sort(qw(. .. archive_status 000000070000000700000007))],
+ 'WAL recreated');
On Fri, Mar 23, 2018 at 12:26:47PM -0400, David Steele wrote:
I've attached a patch that integrates my tests with the current tests.
If you don't think they are worth adding then I'll just drop them from
my patchset.
It seems to me that those tests have values (we can add more tests for
transaction ID and such in the future), but I would recommend to put
them in a separate file named like t/003_reset.pl for tests which
execute resets and checks their consistency on the cluster.
--
Michael
On 3/20/18 11:14 PM, Michael Paquier wrote:
On Tue, Mar 20, 2018 at 05:44:22PM -0400, Stephen Frost wrote:
* David Steele (david@pgmasters.net) wrote:
On 3/16/18 11:12 AM, Stephen Frost wrote:
It seems to me that pg_basebackup and pg_receivexlog should have a -g
option to control the mode of the files that they write to disk (not
including the modes stored in the tar files).Or perhaps we should just update the perms in the tar files for now and
leave the rest alone.Having options to pg_basebackup to control what's done makes sense to
me- but whatever those options do, I'd expect them to apply equally to
the tar files and to the files extracted with plain mode. Having those
be different really strikes me as very odd.Agreed for the consistency part, permissions should be applied
consistently for the folder and the tar format.Having the option for pg_receivewal definitely makes sense to me, as it
is the one in charge of opening and writing the WAL segments. For
pg_basebackup, let's not forget that there is one tar file for each
tablespace, and that each file is received separately using a COPY
stream. There is some logic already which parses the tar header part of
an individual file in order to look for recovery.conf (see
ReceiveTarFile() in pg_basebackup.c). It would be possible to enforce
grouping permissions when receiving each file, and this would be rather
low-cost in performance I think. Honestly, my vote would go for having
the permissions set correctly by the source server as this brings
consistency to the whole experience without complicating the interface
of pg_basebackup, and this also makes the footprint of this patch on
pg_basebackup way lighter.
These updates address Michael's latest review and implement group access
for pg_basebackup, pg_receivewal, and pg_recvlogical. A new internal
GUC, data_directory_group_access, allows remote processes to determine
the correct mode using the existing SHOW protocol command.
I have dropped patch 01, which added the pg_resetwal tests. The tests
Peter added recently are sufficient for this patch so I'll pursue adding
the other tests separately to avoid noise on this thread.
Thanks,
--
-David
david@pgmasters.net
Attachments:
group-access-v12-01-file-perm.patchtext/plain; charset=UTF-8; name=group-access-v12-01-file-perm.patch; x-mac-creator=0; x-mac-type=0Download
From 60752c2e12fa2132c24d71ff030cefe4b2b6c502 Mon Sep 17 00:00:00 2001
From: David Steele <david@pgmasters.net>
Date: Tue, 27 Mar 2018 15:47:15 -0400
Subject: [PATCH 1/2] Refactor file permissions in backend/frontend
Adds a new header (file_perm.h) and makes all front-end utilities use the new constants for setting umask. Converts mkdir() calls in the backend to MakeDirectoryDefaultPerm() if the original call used default permissions. Adds tests to make sure permissions in PGDATA are set correctly by the front-end tools.
---
src/backend/access/transam/xlog.c | 2 +-
src/backend/commands/tablespace.c | 18 ++++---
src/backend/postmaster/postmaster.c | 5 +-
src/backend/postmaster/syslogger.c | 5 +-
src/backend/replication/basebackup.c | 5 +-
src/backend/replication/slot.c | 5 +-
src/backend/storage/file/copydir.c | 2 +-
src/backend/storage/file/fd.c | 32 +++++++-----
src/backend/storage/ipc/ipc.c | 4 ++
src/backend/utils/init/miscinit.c | 5 +-
src/bin/initdb/initdb.c | 24 ++++-----
src/bin/initdb/t/001_initdb.pl | 4 +-
src/bin/pg_basebackup/pg_basebackup.c | 7 +--
src/bin/pg_basebackup/t/010_pg_basebackup.pl | 9 +++-
src/bin/pg_basebackup/t/020_pg_receivewal.pl | 9 +++-
src/bin/pg_basebackup/walmethods.c | 6 ++-
src/bin/pg_ctl/pg_ctl.c | 4 +-
src/bin/pg_ctl/t/001_start_stop.pl | 13 +++--
src/bin/pg_dump/pg_backup_directory.c | 3 +-
src/bin/pg_resetwal/pg_resetwal.c | 8 ++-
src/bin/pg_resetwal/t/001_basic.pl | 5 +-
src/bin/pg_rewind/RewindTest.pm | 4 ++
src/bin/pg_rewind/file_ops.c | 7 +--
src/bin/pg_rewind/pg_rewind.c | 4 ++
src/bin/pg_rewind/t/001_basic.pl | 3 +-
src/bin/pg_upgrade/file.c | 5 +-
src/bin/pg_upgrade/pg_upgrade.c | 3 +-
src/bin/pg_upgrade/test.sh | 11 +++++
src/include/common/file_perm.h | 32 ++++++++++++
src/include/storage/fd.h | 3 ++
src/test/perl/PostgresNode.pm | 3 ++
src/test/perl/TestLib.pm | 73 ++++++++++++++++++++++++++++
32 files changed, 256 insertions(+), 67 deletions(-)
create mode 100644 src/include/common/file_perm.h
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index cb9c2a29cb..95c0fda4e3 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4086,7 +4086,7 @@ ValidateXLOGDirectoryStructure(void)
{
ereport(LOG,
(errmsg("creating missing WAL directory \"%s\"", path)));
- if (mkdir(path, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(path) < 0)
ereport(FATAL,
(errmsg("could not create missing directory \"%s\": %m",
path)));
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 5c450caa4e..f7e1818350 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -68,6 +68,7 @@
#include "commands/seclabel.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
+#include "common/file_perm.h"
#include "miscadmin.h"
#include "postmaster/bgwriter.h"
#include "storage/fd.h"
@@ -151,7 +152,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
else
{
/* Directory creation failed? */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(dir) < 0)
{
char *parentdir;
@@ -173,7 +174,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
get_parent_directory(parentdir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -184,7 +185,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
parentdir = pstrdup(dir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -192,7 +193,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
pfree(parentdir);
/* Create database directory */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(dir) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -279,7 +280,8 @@ CreateTableSpace(CreateTableSpaceStmt *stmt)
/*
* Check that location isn't too long. Remember that we're going to append
* 'PG_XXX/<dboid>/<relid>_<fork>.<nnn>'. FYI, we never actually
- * reference the whole path here, but mkdir() uses the first two parts.
+ * reference the whole path here, but MakeDirectoryDefaultPerm() uses the first
+ * two parts.
*/
if (strlen(location) + 1 + strlen(TABLESPACE_VERSION_DIRECTORY) + 1 +
OIDCHARS + 1 + OIDCHARS + 1 + FORKNAMECHARS + 1 + OIDCHARS > MAXPGPATH)
@@ -574,7 +576,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* Attempt to coerce target directory to safe permissions. If this fails,
* it doesn't exist or has the wrong owner.
*/
- if (chmod(location, S_IRWXU) != 0)
+ if (chmod(location, PG_DIR_MODE_DEFAULT) != 0)
{
if (errno == ENOENT)
ereport(ERROR,
@@ -599,7 +601,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
if (stat(location_with_version_dir, &st) == 0 && S_ISDIR(st.st_mode))
{
if (!rmtree(location_with_version_dir, true))
- /* If this failed, mkdir() below is going to error. */
+ /* If this failed, MakeDirectoryDefaultPerm() below is going to error. */
ereport(WARNING,
(errmsg("some useless files may be left behind in old database directory \"%s\"",
location_with_version_dir)));
@@ -610,7 +612,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* The creation of the version directory prevents more than one tablespace
* in a single location.
*/
- if (mkdir(location_with_version_dir, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(location_with_version_dir) < 0)
{
if (errno == EEXIST)
ereport(ERROR,
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 660f3185e6..868fba8cac 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -97,6 +97,7 @@
#include "access/xlog.h"
#include "bootstrap/bootstrap.h"
#include "catalog/pg_control.h"
+#include "common/file_perm.h"
#include "common/ip.h"
#include "lib/ilist.h"
#include "libpq/auth.h"
@@ -589,7 +590,7 @@ PostmasterMain(int argc, char *argv[])
/*
* for security, no dir or file created can be group or other accessible
*/
- umask(S_IRWXG | S_IRWXO);
+ umask(PG_MODE_MASK_DEFAULT);
/*
* Initialize random(3) so we don't get the same values in every run.
@@ -4492,7 +4493,7 @@ internal_forkexec(int argc, char *argv[], Port *port)
* As in OpenTemporaryFileInTablespace, try to make the temp-file
* directory
*/
- mkdir(PG_TEMP_FILES_DIR, S_IRWXU);
+ MakeDirectoryDefaultPerm(PG_TEMP_FILES_DIR);
fp = AllocateFile(tmpfilename, PG_BINARY_W);
if (!fp)
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index f70eea37df..ae23941f59 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -41,6 +41,7 @@
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
#include "storage/dsm.h"
+#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pg_shmem.h"
@@ -322,7 +323,7 @@ SysLoggerMain(int argc, char *argv[])
/*
* Also, create new directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ MakeDirectoryDefaultPerm(Log_directory);
}
if (strcmp(Log_filename, currentLogFilename) != 0)
{
@@ -564,7 +565,7 @@ SysLogger_Start(void)
/*
* Create log directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ MakeDirectoryDefaultPerm(Log_directory);
/*
* The initial logfile is created right in the postmaster, to verify that
diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c
index 654d0832da..e4a80edb8a 100644
--- a/src/backend/replication/basebackup.c
+++ b/src/backend/replication/basebackup.c
@@ -19,6 +19,7 @@
#include "access/xlog_internal.h" /* for pg_start/stop_backup */
#include "catalog/catalog.h"
#include "catalog/pg_type.h"
+#include "common/file_perm.h"
#include "lib/stringinfo.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
@@ -875,7 +876,7 @@ sendFileWithContent(const char *filename, const char *content)
statbuf.st_gid = getegid();
#endif
statbuf.st_mtime = time(NULL);
- statbuf.st_mode = S_IRUSR | S_IWUSR;
+ statbuf.st_mode = PG_FILE_MODE_DEFAULT;
statbuf.st_size = len;
_tarWriteHeader(filename, NULL, &statbuf, false);
@@ -1394,7 +1395,7 @@ _tarWriteDir(const char *pathbuf, int basepathlen, struct stat *statbuf,
#else
if (pgwin32_is_junction(pathbuf))
#endif
- statbuf->st_mode = S_IFDIR | S_IRWXU;
+ statbuf->st_mode = S_IFDIR | PG_DIR_MODE_DEFAULT;
return _tarWriteHeader(pathbuf + basepathlen + 1, NULL, statbuf, sizeonly);
}
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index fc9ef22b0b..3a98360b50 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -1166,13 +1166,14 @@ CreateSlotOnDisk(ReplicationSlot *slot)
* It's just barely possible that some previous effort to create or drop a
* slot with this name left a temp directory lying around. If that seems
* to be the case, try to remove it. If the rmtree() fails, we'll error
- * out at the mkdir() below, so we don't bother checking success.
+ * out at the MakeDirectoryDefaultPerm() below, so we don't bother checking
+ * success.
*/
if (stat(tmppath, &st) == 0 && S_ISDIR(st.st_mode))
rmtree(tmppath, true);
/* Create and fsync the temporary slot directory. */
- if (mkdir(tmppath, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(tmppath) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
diff --git a/src/backend/storage/file/copydir.c b/src/backend/storage/file/copydir.c
index ca6342db0d..ff980dc1f5 100644
--- a/src/backend/storage/file/copydir.c
+++ b/src/backend/storage/file/copydir.c
@@ -41,7 +41,7 @@ copydir(char *fromdir, char *todir, bool recurse)
char fromfile[MAXPGPATH * 2];
char tofile[MAXPGPATH * 2];
- if (mkdir(todir, S_IRWXU) != 0)
+ if (MakeDirectoryDefaultPerm(todir) != 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m", todir)));
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index d30a725f90..40f754afb6 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -84,6 +84,7 @@
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "catalog/pg_tablespace.h"
+#include "common/file_perm.h"
#include "pgstat.h"
#include "portability/mem.h"
#include "storage/fd.h"
@@ -124,12 +125,6 @@
*/
#define FD_MINFREE 10
-/*
- * Default mode for created files, unless something else is specified using
- * the *Perm() function variants.
- */
-#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR)
-
/*
* A number of platforms allow individual processes to open many more files
* than they can really support when *many* processes do the same thing.
@@ -1434,7 +1429,7 @@ PathNameOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode)
void
PathNameCreateTemporaryDir(const char *basedir, const char *directory)
{
- if (mkdir(directory, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(directory) < 0)
{
if (errno == EEXIST)
return;
@@ -1444,14 +1439,14 @@ PathNameCreateTemporaryDir(const char *basedir, const char *directory)
* EEXIST to close a race against another process following the same
* algorithm.
*/
- if (mkdir(basedir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(basedir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary directory \"%s\": %m",
basedir)));
/* Try again. */
- if (mkdir(directory, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(directory) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary subdirectory \"%s\": %m",
@@ -1601,11 +1596,11 @@ OpenTemporaryFileInTablespace(Oid tblspcOid, bool rejectError)
* We might need to create the tablespace's tempfile directory, if no
* one has yet done so.
*
- * Don't check for error from mkdir; it could fail if someone else
- * just did the same thing. If it doesn't work then we'll bomb out on
+ * Don't check error from MakeDirectoryDefaultPerm; it could fail if someone
+ * else just did the same thing. If it doesn't work then we'll bomb out on
* the second create attempt, instead.
*/
- mkdir(tempdirpath, S_IRWXU);
+ MakeDirectoryDefaultPerm(tempdirpath);
file = PathNameOpenFile(tempfilepath,
O_RDWR | O_CREAT | O_TRUNC | PG_BINARY);
@@ -3554,3 +3549,16 @@ fsync_parent_path(const char *fname, int elevel)
return 0;
}
+
+/*
+ * Create a directory using PG_DIR_MODE_DEFAULT for permissions
+ *
+ * Directories in PGDATA should normally have specific permissions -- when
+ * using this function the caller does not need to know what they should be.
+ * For permissions other than the default use mkdir() directly.
+ */
+int
+MakeDirectoryDefaultPerm(const char *directoryName)
+{
+ return mkdir(directoryName, PG_DIR_MODE_DEFAULT);
+}
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index 726db7b7f1..466613e4af 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -137,6 +137,10 @@ proc_exit(int code)
else
snprintf(gprofDirName, 32, "gprof/%d", (int) getpid());
+ /*
+ * Use mkdir() instead of MakeDirectoryDefaultPerm() here because non-default
+ * permissions are required.
+ */
mkdir("gprof", S_IRWXU | S_IRWXG | S_IRWXO);
mkdir(gprofDirName, S_IRWXU | S_IRWXG | S_IRWXO);
chdir(gprofDirName);
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 87ed7d3f71..82da4b9f27 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -32,6 +32,7 @@
#include "access/htup_details.h"
#include "catalog/pg_authid.h"
+#include "common/file_perm.h"
#include "libpq/libpq.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
@@ -831,7 +832,7 @@ CreateLockFile(const char *filename, bool amPostmaster,
* Think not to make the file protection weaker than 0600. See
* comments below.
*/
- fd = open(filename, O_RDWR | O_CREAT | O_EXCL, 0600);
+ fd = open(filename, O_RDWR | O_CREAT | O_EXCL, PG_FILE_MODE_DEFAULT);
if (fd >= 0)
break; /* Success; exit the retry loop */
@@ -848,7 +849,7 @@ CreateLockFile(const char *filename, bool amPostmaster,
* Read the file to get the old owner's PID. Note race condition
* here: file might have been deleted since we tried to create it.
*/
- fd = open(filename, O_RDONLY, 0600);
+ fd = open(filename, O_RDONLY, PG_FILE_MODE_DEFAULT);
if (fd < 0)
{
if (errno == ENOENT)
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 78990f5a27..0b69eded81 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -64,6 +64,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_class.h"
#include "catalog/pg_collation.h"
+#include "common/file_perm.h"
#include "common/file_utils.h"
#include "common/restricted_token.h"
#include "common/username.h"
@@ -1170,7 +1171,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1190,7 +1191,7 @@ setup_config(void)
sprintf(path, "%s/postgresql.auto.conf", pg_data);
writefile(path, autoconflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1277,7 +1278,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_hba.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1293,7 +1294,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_ident.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -2692,7 +2693,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (pg_mkdir_p(pg_data, S_IRWXU) != 0)
+ if (pg_mkdir_p(pg_data, PG_DIR_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2710,7 +2711,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (chmod(pg_data, S_IRWXU) != 0)
+ if (chmod(pg_data, PG_DIR_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2778,7 +2779,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (pg_mkdir_p(xlog_dir, S_IRWXU) != 0)
+ if (pg_mkdir_p(xlog_dir, PG_DIR_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2796,7 +2797,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (chmod(xlog_dir, S_IRWXU) != 0)
+ if (chmod(xlog_dir, PG_DIR_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2846,7 +2847,7 @@ create_xlog_or_symlink(void)
else
{
/* Without -X option, just make the subdirectory normally */
- if (mkdir(subdirloc, S_IRWXU) < 0)
+ if (mkdir(subdirloc, PG_DIR_MODE_DEFAULT) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, subdirloc, strerror(errno));
@@ -2882,7 +2883,8 @@ initialize_data_directory(void)
setup_signals();
- umask(S_IRWXG | S_IRWXO);
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_DEFAULT);
create_data_directory();
@@ -2902,7 +2904,7 @@ initialize_data_directory(void)
* The parent directory already exists, so we only need mkdir() not
* pg_mkdir_p() here, which avoids some failure modes; cf bug #13853.
*/
- if (mkdir(path, S_IRWXU) < 0)
+ if (mkdir(path, PG_DIR_MODE_DEFAULT) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, path, strerror(errno));
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index c0cfa6e92c..3331560445 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 15;
+use Test::More tests => 16;
my $tempdir = TestLib::tempdir;
my $xlogdir = "$tempdir/pgxlog";
@@ -45,6 +45,8 @@ mkdir $datadir;
command_ok([ 'initdb', '-N', '-T', 'german', '-X', $xlogdir, $datadir ],
'successful creation');
+
+ ok(check_mode_recursive($datadir, 0700, 0600));
}
command_ok([ 'initdb', '-S', $datadir ], 'sync only');
command_fails([ 'initdb', $datadir ], 'existing data directory');
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 1b32592063..3d6e47008a 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -27,6 +27,7 @@
#endif
#include "access/xlog_internal.h"
+#include "common/file_perm.h"
#include "common/file_utils.h"
#include "common/string.h"
#include "fe_utils/string_utils.h"
@@ -624,7 +625,7 @@ StartLogStreamer(char *startpos, uint32 timeline, char *sysidentifier)
PQserverVersion(conn) < MINIMUM_VERSION_FOR_PG_WAL ?
"pg_xlog" : "pg_wal");
- if (pg_mkdir_p(statusdir, S_IRWXU) != 0 && errno != EEXIST)
+ if (pg_mkdir_p(statusdir, PG_DIR_MODE_DEFAULT) != 0 && errno != EEXIST)
{
fprintf(stderr,
_("%s: could not create directory \"%s\": %s\n"),
@@ -680,7 +681,7 @@ verify_dir_is_empty_or_create(char *dirname, bool *created, bool *found)
/*
* Does not exist, so create
*/
- if (pg_mkdir_p(dirname, S_IRWXU) == -1)
+ if (pg_mkdir_p(dirname, PG_DIR_MODE_DEFAULT) == -1)
{
fprintf(stderr,
_("%s: could not create directory \"%s\": %s\n"),
@@ -1436,7 +1437,7 @@ ReceiveAndUnpackTarFile(PGconn *conn, PGresult *res, int rownum)
* Directory
*/
filename[strlen(filename) - 1] = '\0'; /* Remove trailing slash */
- if (mkdir(filename, S_IRWXU) != 0)
+ if (mkdir(filename, PG_DIR_MODE_DEFAULT) != 0)
{
/*
* When streaming WAL, pg_wal (or pg_xlog for pre-9.6
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index 32d21ce644..8d9f9c21f4 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -5,7 +5,7 @@ use Config;
use File::Basename qw(basename dirname);
use PostgresNode;
use TestLib;
-use Test::More tests => 93;
+use Test::More tests => 94;
program_help_ok('pg_basebackup');
program_version_ok('pg_basebackup');
@@ -15,6 +15,9 @@ my $tempdir = TestLib::tempdir;
my $node = get_new_node('main');
+# Set umask so no test directories or files are created with group permissions
+umask (0077);
+
# Initialize node without replication settings
$node->init;
$node->start;
@@ -93,6 +96,10 @@ $node->command_ok([ 'pg_basebackup', '-D', "$tempdir/backup", '-X', 'none' ],
'pg_basebackup runs');
ok(-f "$tempdir/backup/PG_VERSION", 'backup was created');
+# Group access should not be enabled on backup
+ok(check_mode_recursive("$tempdir/backup", 0700, 0600),
+ "check backup dir permissions");
+
# Only archive_status directory should be copied in pg_wal/.
is_deeply(
[ sort(slurp_dir("$tempdir/backup/pg_wal/")) ],
diff --git a/src/bin/pg_basebackup/t/020_pg_receivewal.pl b/src/bin/pg_basebackup/t/020_pg_receivewal.pl
index 64e3a35a87..d155d5b711 100644
--- a/src/bin/pg_basebackup/t/020_pg_receivewal.pl
+++ b/src/bin/pg_basebackup/t/020_pg_receivewal.pl
@@ -2,12 +2,15 @@ use strict;
use warnings;
use TestLib;
use PostgresNode;
-use Test::More tests => 18;
+use Test::More tests => 19;
program_help_ok('pg_receivewal');
program_version_ok('pg_receivewal');
program_options_handling_ok('pg_receivewal');
+# Set umask so no test directories or files are created with group permissions
+umask (0077);
+
my $primary = get_new_node('primary');
$primary->init(allows_streaming => 1);
$primary->start;
@@ -56,3 +59,7 @@ $primary->command_ok(
[ 'pg_receivewal', '-D', $stream_dir, '--verbose',
'--endpos', $nextlsn, '--synchronous', '--no-loop' ],
'streaming some WAL with --synchronous');
+
+# Group access should not be enabled on WAL files
+ok(check_mode_recursive($stream_dir, 0700, 0600),
+ "check stream dir permissions");
diff --git a/src/bin/pg_basebackup/walmethods.c b/src/bin/pg_basebackup/walmethods.c
index b4558a0184..aa3bb35d92 100644
--- a/src/bin/pg_basebackup/walmethods.c
+++ b/src/bin/pg_basebackup/walmethods.c
@@ -22,6 +22,7 @@
#endif
#include "pgtar.h"
+#include "common/file_perm.h"
#include "common/file_utils.h"
#include "receivelog.h"
@@ -89,7 +90,7 @@ dir_open_for_write(const char *pathname, const char *temp_suffix, size_t pad_to_
* does not do any system calls to fsync() to make changes permanent on
* disk.
*/
- fd = open(tmppath, O_WRONLY | O_CREAT | PG_BINARY, S_IRUSR | S_IWUSR);
+ fd = open(tmppath, O_WRONLY | O_CREAT | PG_BINARY, PG_FILE_MODE_DEFAULT);
if (fd < 0)
return NULL;
@@ -534,7 +535,8 @@ tar_open_for_write(const char *pathname, const char *temp_suffix, size_t pad_to_
* We open the tar file only when we first try to write to it.
*/
tar_data->fd = open(tar_data->tarfilename,
- O_WRONLY | O_CREAT | PG_BINARY, S_IRUSR | S_IWUSR);
+ O_WRONLY | O_CREAT | PG_BINARY,
+ PG_FILE_MODE_DEFAULT);
if (tar_data->fd < 0)
return NULL;
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index 9bc830b085..e83a7179ea 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -25,6 +25,7 @@
#include "catalog/pg_control.h"
#include "common/controldata_utils.h"
+#include "common/file_perm.h"
#include "getopt_long.h"
#include "utils/pidfile.h"
@@ -2170,7 +2171,8 @@ main(int argc, char **argv)
*/
argv0 = argv[0];
- umask(S_IRWXG | S_IRWXO);
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_DEFAULT);
/* support --help and --version even if invoked as root */
if (argc > 1)
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl
index 5da4746cb4..3d82abe696 100644
--- a/src/bin/pg_ctl/t/001_start_stop.pl
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -4,7 +4,7 @@ use warnings;
use Config;
use PostgresNode;
use TestLib;
-use Test::More tests => 19;
+use Test::More tests => 20;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -57,10 +57,15 @@ command_ok([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ], 'pg_ctl stop');
command_fails([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ],
'second pg_ctl stop fails');
+# Log file for first perm test
+my $logFileName = "$tempdir/data/perm-test-600.log";
+
command_ok(
- [ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
+ [ 'pg_ctl', 'restart', '-D', "$tempdir/data", '-l', $logFileName ],
'pg_ctl restart with server not running');
-command_ok([ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
- 'pg_ctl restart with server running');
+
+# Log file should exist and have no group permissions
+ok(-f $logFileName);
+ok(check_mode_recursive("$tempdir/data", 0700, 0600));
system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
diff --git a/src/bin/pg_dump/pg_backup_directory.c b/src/bin/pg_dump/pg_backup_directory.c
index 4aabb40f59..6fcd72a0f2 100644
--- a/src/bin/pg_dump/pg_backup_directory.c
+++ b/src/bin/pg_dump/pg_backup_directory.c
@@ -37,6 +37,7 @@
#include "compress_io.h"
#include "parallel.h"
#include "pg_backup_utils.h"
+#include "common/file_perm.h"
#include "common/file_utils.h"
#include <dirent.h>
@@ -192,7 +193,7 @@ InitArchiveFmt_Directory(ArchiveHandle *AH)
}
}
- if (!is_empty && mkdir(ctx->directory, 0700) < 0)
+ if (!is_empty && mkdir(ctx->directory, PG_DIR_MODE_DEFAULT) < 0)
exit_horribly(modulename, "could not create directory \"%s\": %s\n",
ctx->directory, strerror(errno));
}
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index eba7c5fdee..49670a0d29 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -52,6 +52,7 @@
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
#include "common/fe_memutils.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "storage/large_object.h"
#include "pg_getopt.h"
@@ -362,6 +363,9 @@ main(int argc, char *argv[])
exit(1);
}
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_DEFAULT);
+
/* Check that data directory matches our server version */
CheckDataVersion();
@@ -967,7 +971,7 @@ RewriteControlFile(void)
fd = open(XLOG_CONTROL_FILE,
O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ PG_FILE_MODE_DEFAULT);
if (fd < 0)
{
fprintf(stderr, _("%s: could not create pg_control file: %s\n"),
@@ -1249,7 +1253,7 @@ WriteEmptyXLOG(void)
unlink(path);
fd = open(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ PG_FILE_MODE_DEFAULT);
if (fd < 0)
{
fprintf(stderr, _("%s: could not open file \"%s\": %s\n"),
diff --git a/src/bin/pg_resetwal/t/001_basic.pl b/src/bin/pg_resetwal/t/001_basic.pl
index 1b157cb555..7a68a00638 100644
--- a/src/bin/pg_resetwal/t/001_basic.pl
+++ b/src/bin/pg_resetwal/t/001_basic.pl
@@ -3,7 +3,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 11;
+use Test::More tests => 12;
program_help_ok('pg_resetwal');
program_version_ok('pg_resetwal');
@@ -15,3 +15,6 @@ $node->init;
command_like([ 'pg_resetwal', '-n', $node->data_dir ],
qr/checkpoint/,
'pg_resetwal -n produces output');
+
+ok(check_mode_recursive(
+ $node->data_dir, 0700, 0600), 'check PGDATA permissions');
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index 00b5b42dd7..7b632c7dcd 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -237,6 +237,10 @@ sub run_pg_rewind
"$tmp_folder/master-postgresql.conf.tmp",
"$master_pgdata/postgresql.conf");
+ chmod(0600, "$master_pgdata/postgresql.conf")
+ or BAIL_OUT(
+ "unable to set permissions for $master_pgdata/postgresql.conf");
+
# Plug-in rewound node to the now-promoted standby node
my $port_standby = $node_standby->port;
$node_master->append_conf(
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 705383d184..d51914e901 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -18,6 +18,7 @@
#include <fcntl.h>
#include <unistd.h>
+#include "common/file_perm.h"
#include "file_ops.h"
#include "filemap.h"
#include "logging.h"
@@ -58,7 +59,7 @@ open_target_file(const char *path, bool trunc)
mode = O_WRONLY | O_CREAT | PG_BINARY;
if (trunc)
mode |= O_TRUNC;
- dstfd = open(dstpath, mode, 0600);
+ dstfd = open(dstpath, mode, PG_FILE_MODE_DEFAULT);
if (dstfd < 0)
pg_fatal("could not open target file \"%s\": %s\n",
dstpath, strerror(errno));
@@ -190,7 +191,7 @@ truncate_target_file(const char *path, off_t newsize)
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
- fd = open(dstpath, O_WRONLY, 0);
+ fd = open(dstpath, O_WRONLY, PG_FILE_MODE_DEFAULT);
if (fd < 0)
pg_fatal("could not open file \"%s\" for truncation: %s\n",
dstpath, strerror(errno));
@@ -211,7 +212,7 @@ create_target_dir(const char *path)
return;
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
- if (mkdir(dstpath, S_IRWXU) != 0)
+ if (mkdir(dstpath, PG_DIR_MODE_DEFAULT) != 0)
pg_fatal("could not create directory \"%s\": %s\n",
dstpath, strerror(errno));
}
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 72ab2f8d5e..3d179e2c7d 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -24,6 +24,7 @@
#include "access/xlog_internal.h"
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "getopt_long.h"
#include "storage/bufpage.h"
@@ -185,6 +186,9 @@ main(int argc, char **argv)
exit(1);
}
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_DEFAULT);
+
/*
* Don't allow pg_rewind to be run as root, to avoid overwriting the
* ownership of files in the data directory. We need only check for root
diff --git a/src/bin/pg_rewind/t/001_basic.pl b/src/bin/pg_rewind/t/001_basic.pl
index 736f34eae3..559dc53602 100644
--- a/src/bin/pg_rewind/t/001_basic.pl
+++ b/src/bin/pg_rewind/t/001_basic.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 8;
+use Test::More tests => 10;
use RewindTest;
@@ -86,6 +86,7 @@ in master, before promotion
),
'tail-copy');
+ ok (check_mode_recursive($node_master->data_dir(), 0700, 0600));
RewindTest::clean_rewind_test();
}
diff --git a/src/bin/pg_upgrade/file.c b/src/bin/pg_upgrade/file.c
index f38bfacf02..595d43ee31 100644
--- a/src/bin/pg_upgrade/file.c
+++ b/src/bin/pg_upgrade/file.c
@@ -10,6 +10,7 @@
#include "postgres_fe.h"
#include "access/visibilitymap.h"
+#include "common/file_perm.h"
#include "pg_upgrade.h"
#include "storage/bufpage.h"
#include "storage/checksum.h"
@@ -44,7 +45,7 @@ copyFile(const char *src, const char *dst,
schemaName, relName, src, strerror(errno));
if ((dest_fd = open(dst, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR)) < 0)
+ PG_FILE_MODE_DEFAULT)) < 0)
pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
schemaName, relName, dst, strerror(errno));
@@ -151,7 +152,7 @@ rewriteVisibilityMap(const char *fromfile, const char *tofile,
schemaName, relName, fromfile, strerror(errno));
if ((dst_fd = open(tofile, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR)) < 0)
+ PG_FILE_MODE_DEFAULT)) < 0)
pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
schemaName, relName, tofile, strerror(errno));
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index d12412799f..59df6fd88f 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -38,6 +38,7 @@
#include "pg_upgrade.h"
#include "catalog/pg_class.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "fe_utils/string_utils.h"
@@ -79,7 +80,7 @@ main(int argc, char **argv)
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_upgrade"));
/* Ensure that all files created by pg_upgrade are non-world-readable */
- umask(S_IRWXG | S_IRWXO);
+ umask(PG_MODE_MASK_DEFAULT);
parseCommandLine(argc, argv);
diff --git a/src/bin/pg_upgrade/test.sh b/src/bin/pg_upgrade/test.sh
index 39983abea1..4e8db4aaf4 100644
--- a/src/bin/pg_upgrade/test.sh
+++ b/src/bin/pg_upgrade/test.sh
@@ -230,6 +230,17 @@ standard_initdb 'initdb'
pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "${PGDATA}" -b "$oldbindir" -B "$bindir" -p "$PGPORT" -P "$PGPORT"
+# make sure all directories and files have group permissions
+if [ $(find ${PGDATA} -type f ! -perm 600 | wc -l) -ne 0 ]; then
+ echo "files in PGDATA with permission != 600";
+ exit 1;
+fi
+
+if [ $(find ${PGDATA} -type d ! -perm 700 | wc -l) -ne 0 ]; then
+ echo "directories in PGDATA with permission != 700";
+ exit 1;
+fi
+
pg_ctl start -l "$logdir/postmaster2.log" -o "$POSTMASTER_OPTS" -w
case $testhost in
diff --git a/src/include/common/file_perm.h b/src/include/common/file_perm.h
new file mode 100644
index 0000000000..3c6da0e000
--- /dev/null
+++ b/src/include/common/file_perm.h
@@ -0,0 +1,32 @@
+/*-------------------------------------------------------------------------
+ *
+ * File and directory permission constants
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/file_perm.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FILE_PERM_H
+#define FILE_PERM_H
+
+/*
+ * Default mode mask for data directory permissions that does not allow
+ * group execute/read.
+ */
+#define PG_MODE_MASK_DEFAULT (S_IRWXG | S_IRWXO)
+
+/*
+ * Default mode for created files.
+ */
+#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR)
+
+/*
+ * Default mode for directories.
+ */
+#define PG_DIR_MODE_DEFAULT S_IRWXU
+
+#endif /* FILE_PERM_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index e49b42ce86..340dbd4d3b 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -112,6 +112,9 @@ extern int CloseTransientFile(int fd);
extern int BasicOpenFile(const char *fileName, int fileFlags);
extern int BasicOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode);
+ /* Make a directory with default permissions */
+extern int MakeDirectoryDefaultPerm(const char *directoryName);
+
/* Miscellaneous support routines */
extern void InitFileAccess(void);
extern void set_max_safe_fds(void);
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 80188315f1..76e571b98c 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -484,6 +484,9 @@ sub append_conf
my $conffile = $self->data_dir . '/' . $filename;
TestLib::append_to_file($conffile, $str . "\n");
+
+ chmod(0600, $conffile)
+ or die("unable to set permissions for $conffile");
}
=pod
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index b6862688d4..93610e4bc4 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -13,8 +13,11 @@ use warnings;
use Config;
use Cwd;
use Exporter 'import';
+use Fcntl qw(:mode);
use File::Basename;
+use File::Find;
use File::Spec;
+use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
use SimpleTee;
@@ -27,6 +30,7 @@ our @EXPORT = qw(
slurp_dir
slurp_file
append_to_file
+ check_mode_recursive
check_pg_config
system_or_bail
system_log
@@ -240,6 +244,75 @@ sub append_to_file
close $fh;
}
+# Check that all file/dir modes in a directory match the expected values,
+# ignoring the mode of any specified files.
+sub check_mode_recursive
+{
+ my ($dir, $expected_dir_mode, $expected_file_mode, $ignore_list) = @_;
+
+ # Result defaults to true
+ my $result = 1;
+
+ find
+ (
+ {follow_fast => 1,
+ wanted =>
+ sub
+ {
+ my $file_stat = stat($File::Find::name);
+
+ # Is file in the ignore list?
+ foreach my $ignore ($ignore_list ? @{$ignore_list} : [])
+ {
+ if ("$dir/$ignore" eq $File::Find::name)
+ {
+ return;
+ }
+ }
+
+ defined($file_stat)
+ or die("unable to stat $File::Find::name");
+
+ my $file_mode = S_IMODE($file_stat->mode);
+
+ # Is this a file?
+ if (S_ISREG($file_stat->mode))
+ {
+ if ($file_mode != $expected_file_mode)
+ {
+ print(*STDERR,
+ sprintf("$File::Find::name mode must be %04o\n",
+ $expected_file_mode));
+
+ $result = 0;
+ return;
+ }
+ }
+ # Else a directory?
+ elsif (S_ISDIR($file_stat->mode))
+ {
+ if ($file_mode != $expected_dir_mode)
+ {
+ print(*STDERR,
+ sprintf("$File::Find::name mode must be %04o\n",
+ $expected_dir_mode));
+
+ $result = 0;
+ return;
+ }
+ }
+ # Else something we can't handle
+ else
+ {
+ die "unknown file type for $File::Find::name";
+ }
+ }},
+ $dir
+ );
+
+ return $result;
+}
+
# Check presence of a given regexp within pg_config.h for the installation
# where tests are running, returning a match status result depending on
# that.
--
2.14.3 (Apple Git-98)
group-access-v12-02-group.patchtext/plain; charset=UTF-8; name=group-access-v12-02-group.patch; x-mac-creator=0; x-mac-type=0Download
From 45aa81b70282f34b89f87ee9a45de1af4fdf25da Mon Sep 17 00:00:00 2001
From: David Steele <david@pgmasters.net>
Date: Tue, 27 Mar 2018 16:10:22 -0400
Subject: [PATCH 2/2] Allow group access on PGDATA.
This includes backend changes, utility changes (initdb, pg_ctl, pg_upgrade, etc.) and tests for each utility.
---
doc/src/sgml/ref/initdb.sgml | 19 +++++++++
doc/src/sgml/runtime.sgml | 14 ++++++-
src/backend/commands/tablespace.c | 2 +-
src/backend/postmaster/postmaster.c | 39 ++++++++++++-----
src/backend/replication/basebackup.c | 4 +-
src/backend/utils/init/globals.c | 6 +++
src/backend/utils/init/miscinit.c | 30 ++++++++------
src/backend/utils/misc/guc.c | 11 +++++
src/bin/initdb/initdb.c | 22 ++++++----
src/bin/initdb/t/001_initdb.pl | 13 +++++-
src/bin/pg_basebackup/pg_basebackup.c | 26 ++++++++----
src/bin/pg_basebackup/pg_receivewal.c | 11 +++++
src/bin/pg_basebackup/pg_recvlogical.c | 11 +++++
src/bin/pg_basebackup/streamutil.c | 62 ++++++++++++++++++++++++++++
src/bin/pg_basebackup/streamutil.h | 2 +
src/bin/pg_basebackup/t/010_pg_basebackup.pl | 26 ++++++++----
src/bin/pg_ctl/pg_ctl.c | 5 ++-
src/bin/pg_ctl/t/001_start_stop.pl | 24 ++++++++++-
src/bin/pg_resetwal/pg_resetwal.c | 4 +-
src/bin/pg_rewind/RewindTest.pm | 9 ++--
src/bin/pg_rewind/pg_rewind.c | 4 +-
src/bin/pg_rewind/t/002_databases.pl | 6 ++-
src/bin/pg_upgrade/pg_upgrade.c | 5 ++-
src/bin/pg_upgrade/test.sh | 14 +++----
src/common/Makefile | 2 +-
src/common/file_perm.c | 48 +++++++++++++++++++++
src/include/common/file_perm.h | 27 +++++++++---
src/include/miscadmin.h | 2 +
src/test/perl/PostgresNode.pm | 27 +++++++++++-
src/test/perl/TestLib.pm | 25 +++++++++++
src/tools/msvc/Mkvcbuild.pm | 2 +-
31 files changed, 425 insertions(+), 77 deletions(-)
create mode 100644 src/common/file_perm.c
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 949b5a220f..1d41e91794 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -76,6 +76,14 @@ PostgreSQL documentation
to do so.)
</para>
+ <para>
+ For security reasons the new cluster created by <command>initdb</command>
+ will only be accessible by the cluster owner by default. The
+ <option>--allow-group-access</option> option allows any user in the same
+ group as the cluster owner to read files in the cluster. This is useful
+ for performing backups as a non-privileged user.
+ </para>
+
<para>
<command>initdb</command> initializes the database cluster's default
locale and character set encoding. The character set encoding,
@@ -301,6 +309,17 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-g</option></term>
+ <term><option>--allow-group-access</option></term>
+ <listitem>
+ <para>
+ Allows users in the same group as the cluster owner to read all cluster
+ files created by <command>initdb</command>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-W</option></term>
<term><option>--pwprompt</option></term>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 587b430527..40ce3d9813 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -137,7 +137,10 @@ postgres$ <userinput>initdb -D /usr/local/pgsql/data</userinput>
database, it is essential that it be secured from unauthorized
access. <command>initdb</command> therefore revokes access
permissions from everyone but the
- <productname>PostgreSQL</productname> user.
+ <productname>PostgreSQL</productname> user, and optionally, group.
+ Group access, when enabled, is read-only. This allows an unprivileged
+ user in the <productname>PostgreSQL</productname> group to take a backup of
+ the cluster data or perform other operations that only require read access.
</para>
<para>
@@ -2194,6 +2197,15 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
member of the group that has access to those certificate and key files.
</para>
+ <para>
+ If the data directory allows group read access then certificate files may
+ need to be located outside of the data directory in order to conform to the
+ security requirements outlined above. Generally, group access is enabled
+ to allow an unprivileged user to backup the database, and in that case the
+ backup software will not be able to read the certificate files and will
+ likely error.
+ </para>
+
<para>
If the private key is protected with a passphrase, the
server will prompt for the passphrase and will not start until it has
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index f7e1818350..2a844a56e6 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -576,7 +576,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* Attempt to coerce target directory to safe permissions. If this fails,
* it doesn't exist or has the wrong owner.
*/
- if (chmod(location, PG_DIR_MODE_DEFAULT) != 0)
+ if (chmod(location, DataDirMode()) != 0)
{
if (errno == ENOENT)
ereport(ERROR,
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 868fba8cac..46b705be29 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -588,7 +588,9 @@ PostmasterMain(int argc, char *argv[])
IsPostmasterEnvironment = true;
/*
- * for security, no dir or file created can be group or other accessible
+ * By default, no directory or file created can be group or other
+ * accessible. This may be modified later depending on the permissions of
+ * the data directory.
*/
umask(PG_MODE_MASK_DEFAULT);
@@ -1522,25 +1524,42 @@ checkDataDir(void)
#endif
/*
- * Check if the directory has group or world access. If so, reject.
+ * Check if the directory has correct permissions. If not, 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.
+ * 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 & (S_IRWXG | S_IRWXO))
+ if (stat_buf.st_mode & PG_MODE_MASK_ALLOW_GROUP)
ereport(FATAL,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("data directory \"%s\" has group or world access",
+ errmsg("data directory \"%s\" has invalid permissions",
DataDir),
- errdetail("Permissions should be u=rwx (0700).")));
+ errdetail("Permissions should be u=rwx (0700) or u=rwx,g=rx (0750).")));
+#endif
+
+ /*
+ * Reset the file mode creation 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 mask to
+ * allow group read/execute on newly created files and directories and set
+ * the data_directory_group_access GUC to true.
+ *
+ * Suppress when on Windows, because there may not be proper support
+ * for Unix-y file permissions.
+ */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+ if ((stat_buf.st_mode & PG_DIR_MODE_DEFAULT) == PG_DIR_MODE_DEFAULT)
+ {
+ umask(PG_MODE_MASK_ALLOW_GROUP);
+ DataDirGroupAccess = true;
+ }
#endif
/* Look for PG_VERSION before looking for pg_control */
diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c
index e4a80edb8a..e0ae5f33d2 100644
--- a/src/backend/replication/basebackup.c
+++ b/src/backend/replication/basebackup.c
@@ -876,7 +876,7 @@ sendFileWithContent(const char *filename, const char *content)
statbuf.st_gid = getegid();
#endif
statbuf.st_mtime = time(NULL);
- statbuf.st_mode = PG_FILE_MODE_DEFAULT;
+ statbuf.st_mode = DataDirMode() & PG_FILE_MODE_DEFAULT;
statbuf.st_size = len;
_tarWriteHeader(filename, NULL, &statbuf, false);
@@ -1395,7 +1395,7 @@ _tarWriteDir(const char *pathbuf, int basepathlen, struct stat *statbuf,
#else
if (pgwin32_is_junction(pathbuf))
#endif
- statbuf->st_mode = S_IFDIR | PG_DIR_MODE_DEFAULT;
+ statbuf->st_mode = S_IFDIR | DataDirMode();
return _tarWriteHeader(pathbuf + basepathlen + 1, NULL, statbuf, sizeonly);
}
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 446040d816..2ac923c3ee 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -59,6 +59,12 @@ struct Latch *MyLatch;
*/
char *DataDir = NULL;
+/*
+ * Does the data directory allow group read access? The default is false, i.e.
+ * mode 0700, but may be changed in checkDataDir() to true, i.e. mode 0750.
+ */
+bool DataDirGroupAccess = false;
+
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 82da4b9f27..627a5da49a 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -125,6 +125,15 @@ ChangeToDataDir(void)
DataDir)));
}
+/*
+ * Get mode of DataDir which can be either 700 or 750.
+ */
+int
+DataDirMode(void)
+{
+ return DataDirGroupAccess ? PG_DIR_MODE_DEFAULT :
+ PG_DIR_MODE_DEFAULT & ~PG_MODE_MASK_DEFAULT;
+}
/* ----------------------------------------------------------------
* User ID state
@@ -829,7 +838,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_MODE_DEFAULT);
@@ -899,17 +908,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 d075cb139a..42501bd317 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -1694,6 +1694,17 @@ static struct config_bool ConfigureNamesBool[] =
NULL, NULL, NULL
},
+ {
+ {"data_directory_group_access", PGC_INTERNAL, PRESET_OPTIONS,
+ gettext_noop("Shows whether the data directory allows group read access."),
+ NULL,
+ GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
+ },
+ &DataDirGroupAccess,
+ false,
+ NULL, NULL, NULL
+ },
+
{
{"syslog_sequence_numbers", PGC_SIGHUP, LOGGING_WHERE,
gettext_noop("Add sequence number to syslog messages to avoid duplicate suppression."),
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 0b69eded81..8d16232693 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -145,6 +145,7 @@ static bool data_checksums = false;
static char *xlog_dir = NULL;
static char *str_wal_segment_size_mb = NULL;
static int wal_segment_size_mb;
+static mode_t file_mode_mask = PG_MODE_MASK_DEFAULT;
/* internal vars */
@@ -1171,7 +1172,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
+ if (chmod(path, PG_FILE_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1191,7 +1192,7 @@ setup_config(void)
sprintf(path, "%s/postgresql.auto.conf", pg_data);
writefile(path, autoconflines);
- if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
+ if (chmod(path, PG_FILE_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1278,7 +1279,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_hba.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
+ if (chmod(path, PG_FILE_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1294,7 +1295,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_ident.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
+ if (chmod(path, PG_FILE_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -2312,6 +2313,7 @@ usage(const char *progname)
printf(_(" --auth-local=METHOD default authentication method for local-socket connections\n"));
printf(_(" [-D, --pgdata=]DATADIR location for this database cluster\n"));
printf(_(" -E, --encoding=ENCODING set default encoding for new databases\n"));
+ printf(_(" -g, --allow-group-access allow group read/execute on data directory\n"));
printf(_(" --locale=LOCALE set default locale for new databases\n"));
printf(_(" --lc-collate=, --lc-ctype=, --lc-messages=LOCALE\n"
" --lc-monetary=, --lc-numeric=, --lc-time=LOCALE\n"
@@ -2711,7 +2713,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (chmod(pg_data, PG_DIR_MODE_DEFAULT) != 0)
+ if (chmod(pg_data, PG_DIR_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2797,7 +2799,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (chmod(xlog_dir, PG_DIR_MODE_DEFAULT) != 0)
+ if (chmod(xlog_dir, PG_DIR_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2884,7 +2886,7 @@ initialize_data_directory(void)
setup_signals();
/* Set dir/file mode mask */
- umask(PG_MODE_MASK_DEFAULT);
+ umask(file_mode_mask);
create_data_directory();
@@ -3018,6 +3020,7 @@ main(int argc, char *argv[])
{"waldir", required_argument, NULL, 'X'},
{"wal-segsize", required_argument, NULL, 12},
{"data-checksums", no_argument, NULL, 'k'},
+ {"allow-group-access", no_argument, NULL, 'g'},
{NULL, 0, NULL, 0}
};
@@ -3059,7 +3062,7 @@ main(int argc, char *argv[])
/* process command-line options */
- while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:", long_options, &option_index)) != -1)
+ while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:g", long_options, &option_index)) != -1)
{
switch (c)
{
@@ -3153,6 +3156,9 @@ main(int argc, char *argv[])
case 12:
str_wal_segment_size_mb = pg_strdup(optarg);
break;
+ case 'g':
+ file_mode_mask = PG_MODE_MASK_ALLOW_GROUP;
+ break;
default:
/* getopt_long already emitted a complaint */
fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index 3331560445..f08fc6ea8f 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -4,9 +4,11 @@
use strict;
use warnings;
+use Fcntl ':mode';
+use File::stat qw{lstat};
use PostgresNode;
use TestLib;
-use Test::More tests => 16;
+use Test::More tests => 18;
my $tempdir = TestLib::tempdir;
my $xlogdir = "$tempdir/pgxlog";
@@ -50,3 +52,12 @@ mkdir $datadir;
}
command_ok([ 'initdb', '-S', $datadir ], 'sync only');
command_fails([ 'initdb', $datadir ], 'existing data directory');
+
+# Init a new db with group access
+my $datadir_group = "$tempdir/data_group";
+
+command_ok(
+ [ 'initdb', '-g', $datadir_group ],
+ 'successful creation with group access');
+
+ok(check_mode_recursive($datadir_group, 0750, 0640));
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 3d6e47008a..9c587ebdc4 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -2448,14 +2448,6 @@ main(int argc, char **argv)
}
#endif
- /*
- * Verify that the target directory exists, or create it. For plaintext
- * backups, always require the directory. For tar backups, require it
- * unless we are writing to stdout.
- */
- if (format == 'p' || strcmp(basedir, "-") != 0)
- verify_dir_is_empty_or_create(basedir, &made_new_pgdata, &found_existing_pgdata);
-
/* connection in replication mode to server */
conn = GetConnection();
if (!conn)
@@ -2464,6 +2456,24 @@ main(int argc, char **argv)
exit(1);
}
+ /*
+ * Determine if group access is enabled for the source data directory and
+ * if so enable it for any created directories and files by setting the
+ * umask appropriately.
+ */
+ if (!RetrieveDataDirGroupAccess(conn))
+ disconnect_and_exit(1);
+
+ umask(DataDirGroupAccess ? PG_MODE_MASK_ALLOW_GROUP : PG_MODE_MASK_DEFAULT);
+
+ /*
+ * Verify that the target directory exists, or create it. For plaintext
+ * backups, always require the directory. For tar backups, require it
+ * unless we are writing to stdout.
+ */
+ if (format == 'p' || strcmp(basedir, "-") != 0)
+ verify_dir_is_empty_or_create(basedir, &made_new_pgdata, &found_existing_pgdata);
+
/* determine remote server's xlog segment size */
if (!RetrieveWalSegSize(conn))
disconnect_and_exit(1);
diff --git a/src/bin/pg_basebackup/pg_receivewal.c b/src/bin/pg_basebackup/pg_receivewal.c
index b82e073b86..1e36f02553 100644
--- a/src/bin/pg_basebackup/pg_receivewal.c
+++ b/src/bin/pg_basebackup/pg_receivewal.c
@@ -19,6 +19,7 @@
#include <sys/stat.h>
#include <unistd.h>
+#include "common/file_perm.h"
#include "libpq-fe.h"
#include "access/xlog_internal.h"
#include "getopt_long.h"
@@ -703,6 +704,16 @@ main(int argc, char **argv)
if (!RunIdentifySystem(conn, NULL, NULL, NULL, &db_name))
disconnect_and_exit(1);
+ /*
+ * Determine if group access is enabled for the source data directory and
+ * if so enable it for any created directories and files by setting the
+ * umask appropriately.
+ */
+ if (!RetrieveDataDirGroupAccess(conn))
+ disconnect_and_exit(1);
+
+ umask(DataDirGroupAccess ? PG_MODE_MASK_ALLOW_GROUP : PG_MODE_MASK_DEFAULT);
+
/* determine remote server's xlog segment size */
if (!RetrieveWalSegSize(conn))
disconnect_and_exit(1);
diff --git a/src/bin/pg_basebackup/pg_recvlogical.c b/src/bin/pg_basebackup/pg_recvlogical.c
index 53e4661d68..04aa2cf260 100644
--- a/src/bin/pg_basebackup/pg_recvlogical.c
+++ b/src/bin/pg_basebackup/pg_recvlogical.c
@@ -23,6 +23,7 @@
#include "streamutil.h"
#include "access/xlog_internal.h"
+#include "common/file_perm.h"
#include "common/fe_memutils.h"
#include "getopt_long.h"
#include "libpq-fe.h"
@@ -959,6 +960,16 @@ main(int argc, char **argv)
disconnect_and_exit(1);
}
+ /*
+ * Determine if group access is enabled for the source data directory and
+ * if so enable it for any created directories and files by setting the
+ * umask appropriately.
+ */
+ if (!RetrieveDataDirGroupAccess(conn))
+ disconnect_and_exit(1);
+
+ umask(DataDirGroupAccess ? PG_MODE_MASK_ALLOW_GROUP : PG_MODE_MASK_DEFAULT);
+
/* Drop a replication slot. */
if (do_drop_slot)
{
diff --git a/src/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c
index 1438f368ed..6da9f5f423 100644
--- a/src/bin/pg_basebackup/streamutil.c
+++ b/src/bin/pg_basebackup/streamutil.c
@@ -32,9 +32,17 @@
uint32 WalSegSz;
+/* Does the source data directory have group access? */
+bool DataDirGroupAccess = false;
+
/* SHOW command for replication connection was introduced in version 10 */
#define MINIMUM_VERSION_FOR_SHOW_CMD 100000
+/*
+ * Group access is supported from version 11.
+ */
+#define MINIMUM_VERSION_FOR_GROUP_ACCESS 110000
+
const char *progname;
char *connection_string = NULL;
char *dbhost = NULL;
@@ -327,6 +335,60 @@ RetrieveWalSegSize(PGconn *conn)
return true;
}
+/*
+ * From version 11, check if group access is enabled on the source data
+ * directory.
+ */
+bool
+RetrieveDataDirGroupAccess(PGconn *conn)
+{
+ PGresult *res;
+
+ /* check connection existence */
+ Assert(conn != NULL);
+
+ /* for previous versions set the default group access */
+ if (PQserverVersion(conn) < MINIMUM_VERSION_FOR_GROUP_ACCESS)
+ {
+ DataDirGroupAccess = false;
+ return false;
+ }
+
+ res = PQexec(conn, "SHOW data_directory_group_access");
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ fprintf(stderr, _("%s: could not send replication command \"%s\": %s\n"),
+ progname, "SHOW data_directory_group_access", PQerrorMessage(conn));
+
+ PQclear(res);
+ return false;
+ }
+ if (PQntuples(res) != 1 || PQnfields(res) < 1)
+ {
+ fprintf(stderr,
+ _("%s: could not fetch group access flag: got %d rows and %d fields, expected %d rows and %d or more fields\n"),
+ progname, PQntuples(res), PQnfields(res), 1, 1);
+
+ PQclear(res);
+ return false;
+ }
+
+ /* Fetch group access flag from the result */
+ if (strcmp(PQgetvalue(res, 0, 0), "on") == 0)
+ DataDirGroupAccess = true;
+ else if (strcmp(PQgetvalue(res, 0, 0), "off") == 0)
+ DataDirGroupAccess = false;
+ else
+ {
+ fprintf(stderr, _("%s: group access flag could not be parsed: %s\n"),
+ progname, PQgetvalue(res, 0, 0));
+ return false;
+ }
+
+ PQclear(res);
+ return true;
+}
+
/*
* Run IDENTIFY_SYSTEM through a given connection and give back to caller
* some result information if requested:
diff --git a/src/bin/pg_basebackup/streamutil.h b/src/bin/pg_basebackup/streamutil.h
index 6854bbc31d..927e34fd34 100644
--- a/src/bin/pg_basebackup/streamutil.h
+++ b/src/bin/pg_basebackup/streamutil.h
@@ -25,6 +25,7 @@ extern char *dbport;
extern char *dbname;
extern int dbgetpassword;
extern uint32 WalSegSz;
+extern bool DataDirGroupAccess;
/* Connection kept global so we can disconnect easily */
extern PGconn *conn;
@@ -42,6 +43,7 @@ extern bool RunIdentifySystem(PGconn *conn, char **sysid,
XLogRecPtr *startpos,
char **db_name);
extern bool RetrieveWalSegSize(PGconn *conn);
+extern bool RetrieveDataDirGroupAccess(PGconn *conn);
extern TimestampTz feGetCurrentTimestamp(void);
extern void feTimestampDifference(TimestampTz start_time, TimestampTz stop_time,
long *secs, int *microsecs);
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index 8d9f9c21f4..ce87f77e37 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -5,7 +5,7 @@ use Config;
use File::Basename qw(basename dirname);
use PostgresNode;
use TestLib;
-use Test::More tests => 94;
+use Test::More tests => 95;
program_help_ok('pg_basebackup');
program_version_ok('pg_basebackup');
@@ -43,11 +43,6 @@ $node->command_fails(
ok(!-d "$tempdir/backup", 'backup directory was cleaned up');
-$node->command_fails([ 'pg_basebackup', '-D', "$tempdir/backup", '-n' ],
- 'failing run with no-clean option');
-
-ok(-d "$tempdir/backup", 'backup directory was created and left behind');
-
open my $conf, '>>', "$pgdata/postgresql.conf";
print $conf "max_replication_slots = 10\n";
print $conf "max_wal_senders = 10\n";
@@ -157,6 +152,13 @@ ok(-f "$tempdir/tarbackup/base.tar", 'backup tar was created');
$node->command_fails(
[ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T=/foo" ],
'-T with empty old directory fails');
+
+$node->command_fails(
+ [ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T=/foo", "-n" ],
+ 'failing run with no-clean option');
+
+ok(-d "$tempdir/backup", 'backup directory was created and left behind');
+
$node->command_fails(
[ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T/foo=" ],
'-T with empty new directory fails');
@@ -190,11 +192,17 @@ unlink "$pgdata/$superlongname";
# skip on Windows.
SKIP:
{
- skip "symlinks not supported on Windows", 17 if ($windows_os);
+ skip "symlinks not supported on Windows", 18 if ($windows_os);
# Move pg_replslot out of $pgdata and create a symlink to it.
$node->stop;
+ # Set umask so test directories and files are created with group permissions
+ umask (0027);
+
+ # Enable group permissions on PGDATA
+ chmod_recursive("$pgdata", 0750, 0640);
+
rename("$pgdata/pg_replslot", "$tempdir/pg_replslot")
or BAIL_OUT "could not move $pgdata/pg_replslot";
symlink("$tempdir/pg_replslot", "$pgdata/pg_replslot")
@@ -264,6 +272,10 @@ SKIP:
"tablespace symlink was updated");
closedir $dh;
+ # Group access should be enabled on all backup files
+ ok(check_mode_recursive("$tempdir/backup1", 0750, 0640),
+ "check backup dir permissions");
+
# Unlogged relation forks other than init should not be copied
my ($tblspc1UnloggedBackupPath) = $tblspc1UnloggedPath =~ /[^\/]*\/[^\/]*\/[^\/]*$/g;
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index e83a7179ea..bb70e625fd 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -2171,7 +2171,7 @@ main(int argc, char **argv)
*/
argv0 = argv[0];
- /* Set dir/file mode mask */
+ /* Set restrictive mode mask until PGDATA permissions are checked */
umask(PG_MODE_MASK_DEFAULT);
/* support --help and --version even if invoked as root */
@@ -2407,6 +2407,9 @@ main(int argc, char **argv)
snprintf(version_file, MAXPGPATH, "%s/PG_VERSION", pg_data);
snprintf(pid_file, MAXPGPATH, "%s/postmaster.pid", pg_data);
snprintf(backup_file, MAXPGPATH, "%s/backup_label", pg_data);
+
+ /* Set mask based on PGDATA permissions */
+ umask(DataDirectoryMask(pg_data));
}
switch (ctl_command)
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl
index 3d82abe696..91e7f00326 100644
--- a/src/bin/pg_ctl/t/001_start_stop.pl
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -2,9 +2,11 @@ use strict;
use warnings;
use Config;
+use Fcntl ':mode';
+use File::stat qw{lstat};
use PostgresNode;
use TestLib;
-use Test::More tests => 20;
+use Test::More tests => 24;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -68,4 +70,24 @@ command_ok(
ok(-f $logFileName);
ok(check_mode_recursive("$tempdir/data", 0700, 0600));
+# Log file for second perm test
+$logFileName = "$tempdir/data/perm-test-640.log";
+
+# Change the data dir mode so log file will be created with group read
+# privileges on the next start
+system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
+
+chmod_recursive("$tempdir/data", 0750, 0640);
+
+command_ok(
+ [ 'pg_ctl', 'start', '-D', "$tempdir/data", '-l', $logFileName ],
+ 'start server to check group permissions');
+
+ok(-f $logFileName);
+ok(check_mode_recursive("$tempdir/data", 0750, 0640));
+
+command_ok(
+ [ 'pg_ctl', 'restart', '-D', "$tempdir/data", '-l', $logFileName ],
+ 'pg_ctl restart with server running');
+
system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 49670a0d29..a311bb4128 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -363,8 +363,8 @@ main(int argc, char *argv[])
exit(1);
}
- /* Set dir/file mode mask */
- umask(PG_MODE_MASK_DEFAULT);
+ /* Set mask based on PGDATA permissions */
+ umask(DataDirectoryMask(DataDir));
/* Check that data directory matches our server version */
CheckDataVersion();
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index 7b632c7dcd..63d9bd517d 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -115,11 +115,13 @@ sub check_query
sub setup_cluster
{
- my $extra_name = shift;
+ my $extra_name = shift; # Used to differentiate clusters
+ my $extra = shift; # Extra params for initdb
# Initialize master, data checksums are mandatory
$node_master = get_new_node('master' . ($extra_name ? "_${extra_name}" : ''));
- $node_master->init(allows_streaming => 1);
+ $node_master->init(
+ allows_streaming => 1, extra => $extra);
# Set wal_keep_segments to prevent WAL segment recycling after enforced
# checkpoints in the tests.
$node_master->append_conf('postgresql.conf', qq(
@@ -237,7 +239,8 @@ sub run_pg_rewind
"$tmp_folder/master-postgresql.conf.tmp",
"$master_pgdata/postgresql.conf");
- chmod(0600, "$master_pgdata/postgresql.conf")
+ chmod($node_master->group_access() ? 0640 : 0600,
+ "$master_pgdata/postgresql.conf")
or BAIL_OUT(
"unable to set permissions for $master_pgdata/postgresql.conf");
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 3d179e2c7d..11153e5f2f 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -186,8 +186,8 @@ main(int argc, char **argv)
exit(1);
}
- /* Set dir/file mode mask */
- umask(PG_MODE_MASK_DEFAULT);
+ /* Set mask based on PGDATA permissions */
+ umask(DataDirectoryMask(datadir_target));
/*
* Don't allow pg_rewind to be run as root, to avoid overwriting the
diff --git a/src/bin/pg_rewind/t/002_databases.pl b/src/bin/pg_rewind/t/002_databases.pl
index 37cdd712f3..570fa5cee0 100644
--- a/src/bin/pg_rewind/t/002_databases.pl
+++ b/src/bin/pg_rewind/t/002_databases.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 6;
use RewindTest;
@@ -9,7 +9,7 @@ sub run_test
{
my $test_mode = shift;
- RewindTest::setup_cluster($test_mode);
+ RewindTest::setup_cluster($test_mode, ['-g']);
RewindTest::start_master();
# Create a database in master.
@@ -42,6 +42,8 @@ template1
),
'database names');
+ ok (check_mode_recursive(
+ $node_master->data_dir(), 0750, 0640, ['postmaster.pid']));
RewindTest::clean_rewind_test();
}
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 59df6fd88f..54f54fb0a4 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -79,7 +79,7 @@ main(int argc, char **argv)
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_upgrade"));
- /* Ensure that all files created by pg_upgrade are non-world-readable */
+ /* Set default restrictive mask until new cluster permissions are read */
umask(PG_MODE_MASK_DEFAULT);
parseCommandLine(argc, argv);
@@ -100,6 +100,9 @@ main(int argc, char **argv)
check_cluster_compatibility(live_check);
+ /* Set mask based on new PGDATA permissions */
+ umask(DataDirectoryMask(new_cluster.pgdata));
+
check_and_dump_old_cluster(live_check);
diff --git a/src/bin/pg_upgrade/test.sh b/src/bin/pg_upgrade/test.sh
index 4e8db4aaf4..24631a91c6 100644
--- a/src/bin/pg_upgrade/test.sh
+++ b/src/bin/pg_upgrade/test.sh
@@ -20,9 +20,9 @@ unset MAKELEVEL
# Run a given "initdb" binary and overlay the regression testing
# authentication configuration.
standard_initdb() {
- # To increase coverage of non-standard segment size without
- # increase test runtime, run these tests with a lower setting.
- "$1" -N --wal-segsize 1
+ # To increase coverage of non-standard segment size and group access
+ # without increasing test runtime, run these tests with a custom setting.
+ "$1" -N --wal-segsize 1 -g
if [ -n "$TEMP_CONFIG" -a -r "$TEMP_CONFIG" ]
then
cat "$TEMP_CONFIG" >> "$PGDATA/postgresql.conf"
@@ -231,13 +231,13 @@ standard_initdb 'initdb'
pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "${PGDATA}" -b "$oldbindir" -B "$bindir" -p "$PGPORT" -P "$PGPORT"
# make sure all directories and files have group permissions
-if [ $(find ${PGDATA} -type f ! -perm 600 | wc -l) -ne 0 ]; then
- echo "files in PGDATA with permission != 600";
+if [ $(find ${PGDATA} -type f ! -perm 640 | wc -l) -ne 0 ]; then
+ echo "files in PGDATA with permission != 640";
exit 1;
fi
-if [ $(find ${PGDATA} -type d ! -perm 700 | wc -l) -ne 0 ]; then
- echo "directories in PGDATA with permission != 700";
+if [ $(find ${PGDATA} -type d ! -perm 750 | wc -l) -ne 0 ]; then
+ echo "directories in PGDATA with permission != 750";
exit 1;
fi
diff --git a/src/common/Makefile b/src/common/Makefile
index 80e78d72fe..504375bef4 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -51,7 +51,7 @@ else
OBJS_COMMON += sha2.o
endif
-OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o file_utils.o restricted_token.o
+OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o file_perm.o file_utils.o restricted_token.o
OBJS_SRV = $(OBJS_COMMON:%.o=%_srv.o)
diff --git a/src/common/file_perm.c b/src/common/file_perm.c
new file mode 100644
index 0000000000..b490220b64
--- /dev/null
+++ b/src/common/file_perm.c
@@ -0,0 +1,48 @@
+/*-------------------------------------------------------------------------
+ *
+ * File and directory permission routines
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/file_perm.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#error "This file is not expected to be compiled for backend code"
+#endif
+
+#include <sys/stat.h>
+
+#include "common/file_perm.h"
+
+/*
+ * Determine the mask to use when writing to PGDATA by examining the mode of the
+ * PGDATA directory. If group read/execute are present in the PGDATA directory
+ * mode, the mask will be relaxed to allow group read/execute on all newly
+ * created files and directories.
+ *
+ * Errors are not handled here and should be checked by the frontend
+ * application.
+ */
+mode_t
+DataDirectoryMask(const char *dataDir)
+{
+ struct stat statBuf;
+
+ /*
+ * If an error occurs getting the mode then return the more restrictive
+ * mask. It is the reponsibility of the frontend application to generate an
+ * error if the PGDATA directory cannot be accessed.
+ */
+ if (stat(dataDir, &statBuf) != 0)
+ return PG_MODE_MASK_DEFAULT;
+
+ /*
+ * Construct the mask that the caller should pass to umask().
+ */
+ return PG_MODE_MASK_DEFAULT & ~statBuf.st_mode;
+}
diff --git a/src/include/common/file_perm.h b/src/include/common/file_perm.h
index 3c6da0e000..2176e5cae5 100644
--- a/src/include/common/file_perm.h
+++ b/src/include/common/file_perm.h
@@ -1,6 +1,6 @@
/*-------------------------------------------------------------------------
*
- * File and directory permission constants
+ * File and directory permission constants and routines
*
*
* Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
@@ -20,13 +20,30 @@
#define PG_MODE_MASK_DEFAULT (S_IRWXG | S_IRWXO)
/*
- * Default mode for created files.
+ * Optional mode mask for data directory permissions that allows group
+ * read/execute.
*/
-#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR)
+#define PG_MODE_MASK_ALLOW_GROUP (S_IWGRP | S_IRWXO)
/*
- * Default mode for directories.
+ * Default mode for created files, unless something else is specified using
+ * the *Perm() function variants.
*/
-#define PG_DIR_MODE_DEFAULT S_IRWXU
+#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR | S_IRGRP)
+
+/*
+ * Default mode for directories created with MakeDirectoryDefaultPerm().
+ */
+#define PG_DIR_MODE_DEFAULT (S_IRWXU | S_IRGRP | S_IXGRP)
+
+#ifdef FRONTEND
+
+/*
+ * Determine the mask to use when writing to PGDATA
+ */
+mode_t DataDirectoryMask(const char *dataDir);
+
+#endif /* FRONTEND */
+
#endif /* FILE_PERM_H */
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index a4574cd533..13d0009573 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -153,6 +153,7 @@ extern PGDLLIMPORT bool IsBinaryUpgrade;
extern PGDLLIMPORT bool ExitOnAnyError;
extern PGDLLIMPORT char *DataDir;
+extern PGDLLIMPORT bool DataDirGroupAccess;
extern PGDLLIMPORT int NBuffers;
extern PGDLLIMPORT int MaxBackends;
@@ -323,6 +324,7 @@ extern void SetCurrentRoleId(Oid roleid, bool is_superuser);
extern void SetDataDir(const char *dir);
extern void ChangeToDataDir(void);
+extern int DataDirMode(void);
extern void SwitchToSharedLatch(void);
extern void SwitchBackToLocalLatch(void);
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 76e571b98c..1bd91524d7 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -86,9 +86,11 @@ use Carp;
use Config;
use Cwd;
use Exporter 'import';
+use Fcntl qw(:mode);
use File::Basename;
use File::Path qw(rmtree);
use File::Spec;
+use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
use RecursiveCopy;
@@ -269,6 +271,26 @@ sub connstr
=pod
+=item $node->group_access()
+
+Does the data dir allow group access?
+
+=cut
+
+sub group_access
+{
+ my ($self) = @_;
+
+ my $dir_stat = stat($self->data_dir);
+
+ defined($dir_stat)
+ or die('unable to stat ' . $self->data_dir);
+
+ return (S_IMODE($dir_stat->mode) == 0750);
+}
+
+=pod
+
=item $node->data_dir()
Returns the path to the data directory. postgresql.conf and pg_hba.conf are
@@ -460,6 +482,9 @@ sub init
}
close $conf;
+ chmod($self->group_access ? 0640 : 0600, "$pgdata/postgresql.conf")
+ or die("unable to set permissions for $pgdata/postgresql.conf");
+
$self->set_replication_conf if $params{allows_streaming};
$self->enable_archiving if $params{has_archiving};
}
@@ -485,7 +510,7 @@ sub append_conf
TestLib::append_to_file($conffile, $str . "\n");
- chmod(0600, $conffile)
+ chmod($self->group_access() ? 0640 : 0600, $conffile)
or die("unable to set permissions for $conffile");
}
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index 93610e4bc4..8047404efd 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -31,6 +31,7 @@ our @EXPORT = qw(
slurp_file
append_to_file
check_mode_recursive
+ chmod_recursive
check_pg_config
system_or_bail
system_log
@@ -313,6 +314,30 @@ sub check_mode_recursive
return $result;
}
+# Change mode recursively on a directory
+sub chmod_recursive
+{
+ my ($dir, $dir_mode, $file_mode) = @_;
+
+ find
+ (
+ {follow_fast => 1,
+ wanted =>
+ sub
+ {
+ my $file_stat = stat($File::Find::name);
+
+ if (defined($file_stat))
+ {
+ chmod(S_ISDIR($file_stat->mode) ? $dir_mode : $file_mode,
+ $File::Find::name)
+ or die "unable to chmod $File::Find::name";
+ }
+ }},
+ $dir
+ );
+}
+
# Check presence of a given regexp within pg_config.h for the installation
# where tests are running, returning a match status result depending on
# that.
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 5762c68c4e..c5cf3d2dfb 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -125,7 +125,7 @@ sub mkvcbuild
}
our @pgcommonfrontendfiles = (
- @pgcommonallfiles, qw(fe_memutils.c file_utils.c
+ @pgcommonallfiles, qw(fe_memutils.c file_perm.c file_utils.c
restricted_token.c));
our @pgcommonbkndfiles = @pgcommonallfiles;
--
2.14.3 (Apple Git-98)
On Tue, Mar 27, 2018 at 04:21:09PM -0400, David Steele wrote:
These updates address Michael's latest review and implement group access
for pg_basebackup, pg_receivewal, and pg_recvlogical. A new internal
GUC, data_directory_group_access, allows remote processes to determine
the correct mode using the existing SHOW protocol command.
Neat idea. This is another use case where the SHOW command in the
replication protocol proves to be useful.
I have dropped patch 01, which added the pg_resetwal tests. The tests
Peter added recently are sufficient for this patch so I'll pursue adding
the other tests separately to avoid noise on this thread.
That's fair.
Some nits from patch 1...
+ ok (check_mode_recursive($node_master->data_dir(), 0700, 0600));
[...]
+ ok (check_mode_recursive($node_master->data_dir(), 0700, 0600));
Incorrect indentations (space after "ok", yes that's a nit...).
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_DEFAULT);
I would still see that rather committed as a separate patch. I am fine
to delegate the decision to the committer who is hopefully going to pick
up this patch.
And more things about patch 1 which are not nits.
In pg_backup_tar.c, we still have a 0600 hardcoded. You should use
PG_FILE_MODE_DEFAULT there as well.
dsm_impl_posix() can create a new segment with shm_open using as mode
0600. This is present in pg_dynshmem, which would be included in
backups. First, it seems to me that this should use
PG_FILE_MODE_DEFAULT. Then, with patch 2 it seems to me that if an
instance is using DSM then there is a risk of breaking a simple backup
which uses for example "tar" without --exclude filters with group access
(sometimes scripts are not smart enough to skip the same contents as
base backups). So it seems to me that DSM should be also made more
aware of group access to ease the life of users.
And then for patch 2, a couple of issues spotted..
+ /* for previous versions set the default group access */
+ if (PQserverVersion(conn) < MINIMUM_VERSION_FOR_GROUP_ACCESS)
+ {
+ DataDirGroupAccess = false;
+ return false;
+ }
Enforcing DataDirGroupAccess to false for servers older than v11 is
fine, but RetrieveDataDirGroupAccess() should return true. If I read
your patch correctly then support for pg_basebackup on older server
would just fail.
+#if !defined(WIN32) && !defined(__CYGWIN__)
+ if ((stat_buf.st_mode & PG_DIR_MODE_DEFAULT) == PG_DIR_MODE_DEFAULT)
+ {
+ umask(PG_MODE_MASK_ALLOW_GROUP);
+ DataDirGroupAccess = true;
+ }
This should use SetConfigOption() or I am missing something?
+/*
+ * Does the data directory allow group read access? The default is false, i.e.
+ * mode 0700, but may be changed in checkDataDir() to true, i.e. mode 0750.
+ */
+bool DataDirGroupAccess = false;
Instead of a boolean, I would suggest to use an integer, this is more
consistent with log_file_mode. Actually, about that, should we complain
if log_file_mode is set to a value incompatible?
--
Michael
On 3/28/18 1:59 AM, Michael Paquier wrote:
On Tue, Mar 27, 2018 at 04:21:09PM -0400, David Steele wrote:
These updates address Michael's latest review and implement group access
for pg_basebackup, pg_receivewal, and pg_recvlogical. A new internal
GUC, data_directory_group_access, allows remote processes to determine
the correct mode using the existing SHOW protocol command.Some nits from patch 1...
+ ok (check_mode_recursive($node_master->data_dir(), 0700, 0600)); [...] + ok (check_mode_recursive($node_master->data_dir(), 0700, 0600)); Incorrect indentations (space after "ok", yes that's a nit...).
Fixed the space, not sure about the indentations?
And more things about patch 1 which are not nits.
In pg_backup_tar.c, we still have a 0600 hardcoded. You should use
PG_FILE_MODE_DEFAULT there as well.
I'm starting to wonder if these changes in pg_dump make sense. The
file/tar permissions here do not map directly to anything in the PGDATA
directory (since the dump and restore are logical). Perhaps we should
be adding a -g option for pg_dump (in a separate patch) if we want this
functionality?
dsm_impl_posix() can create a new segment with shm_open using as mode
0600. This is present in pg_dynshmem, which would be included in
backups. First, it seems to me that this should use
PG_FILE_MODE_DEFAULT. Then, with patch 2 it seems to me that if an
instance is using DSM then there is a risk of breaking a simple backup
which uses for example "tar" without --exclude filters with group access
(sometimes scripts are not smart enough to skip the same contents as
base backups). So it seems to me that DSM should be also made more
aware of group access to ease the life of users.
Done in patch 1. For patch 2, do you see any issues with the shared
memory being group readable from a security perspective? The user can
read everything on disk so it's hard to see how it's a problem. Also,
if these files are ending up in people's backups...
And then for patch 2, a couple of issues spotted..
+ /* for previous versions set the default group access */ + if (PQserverVersion(conn) < MINIMUM_VERSION_FOR_GROUP_ACCESS) + { + DataDirGroupAccess = false; + return false; + } Enforcing DataDirGroupAccess to false for servers older than v11 is fine, but RetrieveDataDirGroupAccess() should return true. If I read your patch correctly then support for pg_basebackup on older server would just fail.
You are correct, fixed.
+#if !defined(WIN32) && !defined(__CYGWIN__) + if ((stat_buf.st_mode & PG_DIR_MODE_DEFAULT) == PG_DIR_MODE_DEFAULT) + { + umask(PG_MODE_MASK_ALLOW_GROUP); + DataDirGroupAccess = true; + } This should use SetConfigOption() or I am missing something?
Done.
+/* + * Does the data directory allow group read access? The default is false, i.e. + * mode 0700, but may be changed in checkDataDir() to true, i.e. mode 0750. + */ +bool DataDirGroupAccess = false;Instead of a boolean, I would suggest to use an integer, this is more
consistent with log_file_mode.
Well, the goal was to make this implementation independent, but I'm not
against the idea. Anybody have a preference?
Actually, about that, should we complain
if log_file_mode is set to a value incompatible?
I think control of the log file mode should be independent. I usually
don't store log files in PGDATA at all. What if we set log_file_mode
based on the -g option passed to initdb? That will work well for
default installations while providing flexibility to others.
Thanks,
--
-David
david@pgmasters.net
On Thu, Mar 29, 2018 at 10:59:59AM -0400, David Steele wrote:
On 3/28/18 1:59 AM, Michael Paquier wrote:
On Tue, Mar 27, 2018 at 04:21:09PM -0400, David Steele wrote:
These updates address Michael's latest review and implement group access
for pg_basebackup, pg_receivewal, and pg_recvlogical. A new internal
GUC, data_directory_group_access, allows remote processes to determine
the correct mode using the existing SHOW protocol command.Some nits from patch 1...
+ ok (check_mode_recursive($node_master->data_dir(), 0700, 0600)); [...] + ok (check_mode_recursive($node_master->data_dir(), 0700, 0600)); Incorrect indentations (space after "ok", yes that's a nit...).Fixed the space, not sure about the indentations?
That was only the space issues. A nit.
And more things about patch 1 which are not nits.
In pg_backup_tar.c, we still have a 0600 hardcoded. You should use
PG_FILE_MODE_DEFAULT there as well.I'm starting to wonder if these changes in pg_dump make sense. The
file/tar permissions here do not map directly to anything in the PGDATA
directory (since the dump and restore are logical). Perhaps we should
be adding a -g option for pg_dump (in a separate patch) if we want this
functionality?
Yeah. I am having second thoughts on this one actually. pg_dump
handles logical backups which require just a connection to Postgres and
it does not care about the physical state of the relation files. So I
am dropping my comment, and let's not bother about changing things
here.
dsm_impl_posix() can create a new segment with shm_open using as mode
0600. This is present in pg_dynshmem, which would be included in
backups. First, it seems to me that this should use
PG_FILE_MODE_DEFAULT. Then, with patch 2 it seems to me that if an
instance is using DSM then there is a risk of breaking a simple backup
which uses for example "tar" without --exclude filters with group access
(sometimes scripts are not smart enough to skip the same contents as
base backups). So it seems to me that DSM should be also made more
aware of group access to ease the life of users.Done in patch 1. For patch 2, do you see any issues with the shared
memory being group readable from a security perspective? The user can
read everything on disk so it's hard to see how it's a problem. Also,
if these files are ending up in people's backups...
They would be nuked from the surface of earth when recovery kicks.
People should filter this folder, which is why any popular Postgres
backup tool I believe does so, and now so do both pg_rewind and
pg_basebackup. Still if a user is able to read everything, being able
to read as well pg_dynshmem does not change much from a security's point
of view. So consistency makes the most sense to me.
+/* + * Does the data directory allow group read access? The default is false, i.e. + * mode 0700, but may be changed in checkDataDir() to true, i.e. mode 0750. + */ +bool DataDirGroupAccess = false;Instead of a boolean, I would suggest to use an integer, this is more
consistent with log_file_mode.Well, the goal was to make this implementation independent, but I'm not
against the idea. Anybody have a preference?
You mean Windows here? Perhaps you are right and just using a boolean
would be fine. When commenting on that I have been likely wondering
about potential extensions in the future, like allowing a user to write
files in a data folder as well if member of the same group as the user
managing the Postgres intance, like for backup deployment? Perhaps
that's just a crazy man's thoughts..
Actually, about that, should we complain
if log_file_mode is set to a value incompatible?I think control of the log file mode should be independent. I usually
don't store log files in PGDATA at all. What if we set log_file_mode
based on the -g option passed to initdb? That will work well for
default installations while providing flexibility to others.
Let's do nothing here as well. This will keep the code more simple, and
normally-sane deployments put the log directory in an absolute path out
of the data folder. Normally... So it means that if I create a number
out of thin air I would imagine that less than 20% of deployments are
like that.
--
Michael
On 3/29/18 11:04 PM, Michael Paquier wrote:
On Thu, Mar 29, 2018 at 10:59:59AM -0400, David Steele wrote:
On 3/28/18 1:59 AM, Michael Paquier wrote:
In pg_backup_tar.c, we still have a 0600 hardcoded. You should use
PG_FILE_MODE_DEFAULT there as well.I'm starting to wonder if these changes in pg_dump make sense. The
file/tar permissions here do not map directly to anything in the PGDATA
directory (since the dump and restore are logical). Perhaps we should
be adding a -g option for pg_dump (in a separate patch) if we want this
functionality?Yeah. I am having second thoughts on this one actually. pg_dump
handles logical backups which require just a connection to Postgres and
it does not care about the physical state of the relation files. So I
am dropping my comment, and let's not bother about changing things
here.
Glad you agree. I have reverted the mode changes in pg_dump.
dsm_impl_posix() can create a new segment with shm_open using as mode
0600. This is present in pg_dynshmem, which would be included in
backups. First, it seems to me that this should use
PG_FILE_MODE_DEFAULT. Then, with patch 2 it seems to me that if an
instance is using DSM then there is a risk of breaking a simple backup
which uses for example "tar" without --exclude filters with group access
(sometimes scripts are not smart enough to skip the same contents as
base backups). So it seems to me that DSM should be also made more
aware of group access to ease the life of users.Done in patch 1. For patch 2, do you see any issues with the shared
memory being group readable from a security perspective? The user can
read everything on disk so it's hard to see how it's a problem. Also,
if these files are ending up in people's backups...They would be nuked from the surface of earth when recovery kicks.
People should filter this folder, which is why any popular Postgres
backup tool I believe does so, and now so do both pg_rewind and
pg_basebackup. Still if a user is able to read everything, being able
to read as well pg_dynshmem does not change much from a security's point
of view. So consistency makes the most sense to me.
Done.
+/* + * Does the data directory allow group read access? The default is false, i.e. + * mode 0700, but may be changed in checkDataDir() to true, i.e. mode 0750. + */ +bool DataDirGroupAccess = false;Instead of a boolean, I would suggest to use an integer, this is more
consistent with log_file_mode.Well, the goal was to make this implementation independent, but I'm not
against the idea. Anybody have a preference?You mean Windows here? Perhaps you are right and just using a boolean
would be fine. When commenting on that I have been likely wondering
about potential extensions in the future, like allowing a user to write
files in a data folder as well if member of the same group as the user
managing the Postgres intance, like for backup deployment? Perhaps
that's just a crazy man's thoughts..
Haha! But I think you are right that using the mode is more consistent
with how other GUCs are done. It hardly makes sense to take a stand on
unix-y things here when we use them in other GUCs already.
I have replaced data_directory_group_access with data_directory_mode.
Actually, about that, should we complain
if log_file_mode is set to a value incompatible?I think control of the log file mode should be independent. I usually
don't store log files in PGDATA at all. What if we set log_file_mode
based on the -g option passed to initdb? That will work well for
default installations while providing flexibility to others.Let's do nothing here as well. This will keep the code more simple, and
normally-sane deployments put the log directory in an absolute path out
of the data folder. Normally... So it means that if I create a number
out of thin air I would imagine that less than 20% of deployments are
like that.
I decided this made sense to do. It was only a few lines in initdb.c
using a very well established pattern. It would be surprising if log
files did not follow the mode of the rest of PGDATA after initdb -g,
even if it is standard practice to relocate them.
Thanks,
--
-David
david@pgmasters.net
Attachments:
group-access-v13-01-file-perm.patchtext/plain; charset=UTF-8; name=group-access-v13-01-file-perm.patch; x-mac-creator=0; x-mac-type=0Download
From b48aef8b82742683017bbb7b8ac2f509ed914414 Mon Sep 17 00:00:00 2001
From: David Steele <david@pgmasters.net>
Date: Fri, 30 Mar 2018 13:22:01 -0400
Subject: [PATCH 1/1] Refactor file permissions in backend/frontend
Adds a new header (file_perm.h) and makes all front-end utilities use the new constants for setting umask. Converts mkdir() calls in the backend to MakeDirectoryDefaultPerm() if the original call used default permissions. Adds tests to make sure permissions in PGDATA are set correctly by the front-end tools.
---
src/backend/access/transam/xlog.c | 2 +-
src/backend/commands/tablespace.c | 18 ++++---
src/backend/postmaster/postmaster.c | 5 +-
src/backend/postmaster/syslogger.c | 5 +-
src/backend/replication/basebackup.c | 5 +-
src/backend/replication/slot.c | 5 +-
src/backend/storage/file/copydir.c | 2 +-
src/backend/storage/file/fd.c | 32 +++++++-----
src/backend/storage/ipc/dsm_impl.c | 3 +-
src/backend/storage/ipc/ipc.c | 4 ++
src/backend/utils/init/miscinit.c | 5 +-
src/bin/initdb/initdb.c | 24 ++++-----
src/bin/initdb/t/001_initdb.pl | 4 +-
src/bin/pg_basebackup/pg_basebackup.c | 7 +--
src/bin/pg_basebackup/t/010_pg_basebackup.pl | 9 +++-
src/bin/pg_basebackup/t/020_pg_receivewal.pl | 9 +++-
src/bin/pg_basebackup/walmethods.c | 6 ++-
src/bin/pg_ctl/pg_ctl.c | 4 +-
src/bin/pg_ctl/t/001_start_stop.pl | 13 +++--
src/bin/pg_resetwal/pg_resetwal.c | 8 ++-
src/bin/pg_resetwal/t/001_basic.pl | 5 +-
src/bin/pg_rewind/RewindTest.pm | 4 ++
src/bin/pg_rewind/file_ops.c | 7 +--
src/bin/pg_rewind/pg_rewind.c | 4 ++
src/bin/pg_rewind/t/001_basic.pl | 3 +-
src/bin/pg_upgrade/file.c | 5 +-
src/bin/pg_upgrade/pg_upgrade.c | 3 +-
src/bin/pg_upgrade/test.sh | 11 +++++
src/include/common/file_perm.h | 32 ++++++++++++
src/include/storage/fd.h | 3 ++
src/test/perl/PostgresNode.pm | 3 ++
src/test/perl/TestLib.pm | 73 ++++++++++++++++++++++++++++
32 files changed, 256 insertions(+), 67 deletions(-)
create mode 100644 src/include/common/file_perm.h
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 0bca449eac..0a0ab1aff9 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4086,7 +4086,7 @@ ValidateXLOGDirectoryStructure(void)
{
ereport(LOG,
(errmsg("creating missing WAL directory \"%s\"", path)));
- if (mkdir(path, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(path) < 0)
ereport(FATAL,
(errmsg("could not create missing directory \"%s\": %m",
path)));
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 5c450caa4e..f7e1818350 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -68,6 +68,7 @@
#include "commands/seclabel.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
+#include "common/file_perm.h"
#include "miscadmin.h"
#include "postmaster/bgwriter.h"
#include "storage/fd.h"
@@ -151,7 +152,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
else
{
/* Directory creation failed? */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(dir) < 0)
{
char *parentdir;
@@ -173,7 +174,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
get_parent_directory(parentdir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -184,7 +185,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
parentdir = pstrdup(dir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -192,7 +193,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
pfree(parentdir);
/* Create database directory */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(dir) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -279,7 +280,8 @@ CreateTableSpace(CreateTableSpaceStmt *stmt)
/*
* Check that location isn't too long. Remember that we're going to append
* 'PG_XXX/<dboid>/<relid>_<fork>.<nnn>'. FYI, we never actually
- * reference the whole path here, but mkdir() uses the first two parts.
+ * reference the whole path here, but MakeDirectoryDefaultPerm() uses the first
+ * two parts.
*/
if (strlen(location) + 1 + strlen(TABLESPACE_VERSION_DIRECTORY) + 1 +
OIDCHARS + 1 + OIDCHARS + 1 + FORKNAMECHARS + 1 + OIDCHARS > MAXPGPATH)
@@ -574,7 +576,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* Attempt to coerce target directory to safe permissions. If this fails,
* it doesn't exist or has the wrong owner.
*/
- if (chmod(location, S_IRWXU) != 0)
+ if (chmod(location, PG_DIR_MODE_DEFAULT) != 0)
{
if (errno == ENOENT)
ereport(ERROR,
@@ -599,7 +601,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
if (stat(location_with_version_dir, &st) == 0 && S_ISDIR(st.st_mode))
{
if (!rmtree(location_with_version_dir, true))
- /* If this failed, mkdir() below is going to error. */
+ /* If this failed, MakeDirectoryDefaultPerm() below is going to error. */
ereport(WARNING,
(errmsg("some useless files may be left behind in old database directory \"%s\"",
location_with_version_dir)));
@@ -610,7 +612,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* The creation of the version directory prevents more than one tablespace
* in a single location.
*/
- if (mkdir(location_with_version_dir, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(location_with_version_dir) < 0)
{
if (errno == EEXIST)
ereport(ERROR,
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 660f3185e6..868fba8cac 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -97,6 +97,7 @@
#include "access/xlog.h"
#include "bootstrap/bootstrap.h"
#include "catalog/pg_control.h"
+#include "common/file_perm.h"
#include "common/ip.h"
#include "lib/ilist.h"
#include "libpq/auth.h"
@@ -589,7 +590,7 @@ PostmasterMain(int argc, char *argv[])
/*
* for security, no dir or file created can be group or other accessible
*/
- umask(S_IRWXG | S_IRWXO);
+ umask(PG_MODE_MASK_DEFAULT);
/*
* Initialize random(3) so we don't get the same values in every run.
@@ -4492,7 +4493,7 @@ internal_forkexec(int argc, char *argv[], Port *port)
* As in OpenTemporaryFileInTablespace, try to make the temp-file
* directory
*/
- mkdir(PG_TEMP_FILES_DIR, S_IRWXU);
+ MakeDirectoryDefaultPerm(PG_TEMP_FILES_DIR);
fp = AllocateFile(tmpfilename, PG_BINARY_W);
if (!fp)
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index f70eea37df..ae23941f59 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -41,6 +41,7 @@
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
#include "storage/dsm.h"
+#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pg_shmem.h"
@@ -322,7 +323,7 @@ SysLoggerMain(int argc, char *argv[])
/*
* Also, create new directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ MakeDirectoryDefaultPerm(Log_directory);
}
if (strcmp(Log_filename, currentLogFilename) != 0)
{
@@ -564,7 +565,7 @@ SysLogger_Start(void)
/*
* Create log directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ MakeDirectoryDefaultPerm(Log_directory);
/*
* The initial logfile is created right in the postmaster, to verify that
diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c
index 516eea57f8..97e2bdfc73 100644
--- a/src/backend/replication/basebackup.c
+++ b/src/backend/replication/basebackup.c
@@ -19,6 +19,7 @@
#include "access/xlog_internal.h" /* for pg_start/stop_backup */
#include "catalog/catalog.h"
#include "catalog/pg_type.h"
+#include "common/file_perm.h"
#include "lib/stringinfo.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
@@ -878,7 +879,7 @@ sendFileWithContent(const char *filename, const char *content)
statbuf.st_gid = getegid();
#endif
statbuf.st_mtime = time(NULL);
- statbuf.st_mode = S_IRUSR | S_IWUSR;
+ statbuf.st_mode = PG_FILE_MODE_DEFAULT;
statbuf.st_size = len;
_tarWriteHeader(filename, NULL, &statbuf, false);
@@ -1397,7 +1398,7 @@ _tarWriteDir(const char *pathbuf, int basepathlen, struct stat *statbuf,
#else
if (pgwin32_is_junction(pathbuf))
#endif
- statbuf->st_mode = S_IFDIR | S_IRWXU;
+ statbuf->st_mode = S_IFDIR | PG_DIR_MODE_DEFAULT;
return _tarWriteHeader(pathbuf + basepathlen + 1, NULL, statbuf, sizeonly);
}
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index fc9ef22b0b..3a98360b50 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -1166,13 +1166,14 @@ CreateSlotOnDisk(ReplicationSlot *slot)
* It's just barely possible that some previous effort to create or drop a
* slot with this name left a temp directory lying around. If that seems
* to be the case, try to remove it. If the rmtree() fails, we'll error
- * out at the mkdir() below, so we don't bother checking success.
+ * out at the MakeDirectoryDefaultPerm() below, so we don't bother checking
+ * success.
*/
if (stat(tmppath, &st) == 0 && S_ISDIR(st.st_mode))
rmtree(tmppath, true);
/* Create and fsync the temporary slot directory. */
- if (mkdir(tmppath, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(tmppath) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
diff --git a/src/backend/storage/file/copydir.c b/src/backend/storage/file/copydir.c
index ca6342db0d..ff980dc1f5 100644
--- a/src/backend/storage/file/copydir.c
+++ b/src/backend/storage/file/copydir.c
@@ -41,7 +41,7 @@ copydir(char *fromdir, char *todir, bool recurse)
char fromfile[MAXPGPATH * 2];
char tofile[MAXPGPATH * 2];
- if (mkdir(todir, S_IRWXU) != 0)
+ if (MakeDirectoryDefaultPerm(todir) != 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m", todir)));
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index d30a725f90..40f754afb6 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -84,6 +84,7 @@
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "catalog/pg_tablespace.h"
+#include "common/file_perm.h"
#include "pgstat.h"
#include "portability/mem.h"
#include "storage/fd.h"
@@ -124,12 +125,6 @@
*/
#define FD_MINFREE 10
-/*
- * Default mode for created files, unless something else is specified using
- * the *Perm() function variants.
- */
-#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR)
-
/*
* A number of platforms allow individual processes to open many more files
* than they can really support when *many* processes do the same thing.
@@ -1434,7 +1429,7 @@ PathNameOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode)
void
PathNameCreateTemporaryDir(const char *basedir, const char *directory)
{
- if (mkdir(directory, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(directory) < 0)
{
if (errno == EEXIST)
return;
@@ -1444,14 +1439,14 @@ PathNameCreateTemporaryDir(const char *basedir, const char *directory)
* EEXIST to close a race against another process following the same
* algorithm.
*/
- if (mkdir(basedir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(basedir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary directory \"%s\": %m",
basedir)));
/* Try again. */
- if (mkdir(directory, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(directory) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary subdirectory \"%s\": %m",
@@ -1601,11 +1596,11 @@ OpenTemporaryFileInTablespace(Oid tblspcOid, bool rejectError)
* We might need to create the tablespace's tempfile directory, if no
* one has yet done so.
*
- * Don't check for error from mkdir; it could fail if someone else
- * just did the same thing. If it doesn't work then we'll bomb out on
+ * Don't check error from MakeDirectoryDefaultPerm; it could fail if someone
+ * else just did the same thing. If it doesn't work then we'll bomb out on
* the second create attempt, instead.
*/
- mkdir(tempdirpath, S_IRWXU);
+ MakeDirectoryDefaultPerm(tempdirpath);
file = PathNameOpenFile(tempfilepath,
O_RDWR | O_CREAT | O_TRUNC | PG_BINARY);
@@ -3554,3 +3549,16 @@ fsync_parent_path(const char *fname, int elevel)
return 0;
}
+
+/*
+ * Create a directory using PG_DIR_MODE_DEFAULT for permissions
+ *
+ * Directories in PGDATA should normally have specific permissions -- when
+ * using this function the caller does not need to know what they should be.
+ * For permissions other than the default use mkdir() directly.
+ */
+int
+MakeDirectoryDefaultPerm(const char *directoryName)
+{
+ return mkdir(directoryName, PG_DIR_MODE_DEFAULT);
+}
diff --git a/src/backend/storage/ipc/dsm_impl.c b/src/backend/storage/ipc/dsm_impl.c
index 67e76b98fe..0ba45b4f93 100644
--- a/src/backend/storage/ipc/dsm_impl.c
+++ b/src/backend/storage/ipc/dsm_impl.c
@@ -60,6 +60,7 @@
#ifdef HAVE_SYS_SHM_H
#include <sys/shm.h>
#endif
+#include "common/file_perm.h"
#include "pgstat.h"
#include "portability/mem.h"
@@ -285,7 +286,7 @@ dsm_impl_posix(dsm_op op, dsm_handle handle, Size request_size,
* returning.
*/
flags = O_RDWR | (op == DSM_OP_CREATE ? O_CREAT | O_EXCL : 0);
- if ((fd = shm_open(name, flags, 0600)) == -1)
+ if ((fd = shm_open(name, flags, PG_FILE_MODE_DEFAULT)) == -1)
{
if (errno != EEXIST)
ereport(elevel,
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index 726db7b7f1..466613e4af 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -137,6 +137,10 @@ proc_exit(int code)
else
snprintf(gprofDirName, 32, "gprof/%d", (int) getpid());
+ /*
+ * Use mkdir() instead of MakeDirectoryDefaultPerm() here because non-default
+ * permissions are required.
+ */
mkdir("gprof", S_IRWXU | S_IRWXG | S_IRWXO);
mkdir(gprofDirName, S_IRWXU | S_IRWXG | S_IRWXO);
chdir(gprofDirName);
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 87ed7d3f71..82da4b9f27 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -32,6 +32,7 @@
#include "access/htup_details.h"
#include "catalog/pg_authid.h"
+#include "common/file_perm.h"
#include "libpq/libpq.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
@@ -831,7 +832,7 @@ CreateLockFile(const char *filename, bool amPostmaster,
* Think not to make the file protection weaker than 0600. See
* comments below.
*/
- fd = open(filename, O_RDWR | O_CREAT | O_EXCL, 0600);
+ fd = open(filename, O_RDWR | O_CREAT | O_EXCL, PG_FILE_MODE_DEFAULT);
if (fd >= 0)
break; /* Success; exit the retry loop */
@@ -848,7 +849,7 @@ CreateLockFile(const char *filename, bool amPostmaster,
* Read the file to get the old owner's PID. Note race condition
* here: file might have been deleted since we tried to create it.
*/
- fd = open(filename, O_RDONLY, 0600);
+ fd = open(filename, O_RDONLY, PG_FILE_MODE_DEFAULT);
if (fd < 0)
{
if (errno == ENOENT)
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 78990f5a27..0b69eded81 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -64,6 +64,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_class.h"
#include "catalog/pg_collation.h"
+#include "common/file_perm.h"
#include "common/file_utils.h"
#include "common/restricted_token.h"
#include "common/username.h"
@@ -1170,7 +1171,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1190,7 +1191,7 @@ setup_config(void)
sprintf(path, "%s/postgresql.auto.conf", pg_data);
writefile(path, autoconflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1277,7 +1278,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_hba.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1293,7 +1294,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_ident.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -2692,7 +2693,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (pg_mkdir_p(pg_data, S_IRWXU) != 0)
+ if (pg_mkdir_p(pg_data, PG_DIR_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2710,7 +2711,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (chmod(pg_data, S_IRWXU) != 0)
+ if (chmod(pg_data, PG_DIR_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2778,7 +2779,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (pg_mkdir_p(xlog_dir, S_IRWXU) != 0)
+ if (pg_mkdir_p(xlog_dir, PG_DIR_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2796,7 +2797,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (chmod(xlog_dir, S_IRWXU) != 0)
+ if (chmod(xlog_dir, PG_DIR_MODE_DEFAULT) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2846,7 +2847,7 @@ create_xlog_or_symlink(void)
else
{
/* Without -X option, just make the subdirectory normally */
- if (mkdir(subdirloc, S_IRWXU) < 0)
+ if (mkdir(subdirloc, PG_DIR_MODE_DEFAULT) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, subdirloc, strerror(errno));
@@ -2882,7 +2883,8 @@ initialize_data_directory(void)
setup_signals();
- umask(S_IRWXG | S_IRWXO);
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_DEFAULT);
create_data_directory();
@@ -2902,7 +2904,7 @@ initialize_data_directory(void)
* The parent directory already exists, so we only need mkdir() not
* pg_mkdir_p() here, which avoids some failure modes; cf bug #13853.
*/
- if (mkdir(path, S_IRWXU) < 0)
+ if (mkdir(path, PG_DIR_MODE_DEFAULT) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, path, strerror(errno));
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index c0cfa6e92c..3331560445 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 15;
+use Test::More tests => 16;
my $tempdir = TestLib::tempdir;
my $xlogdir = "$tempdir/pgxlog";
@@ -45,6 +45,8 @@ mkdir $datadir;
command_ok([ 'initdb', '-N', '-T', 'german', '-X', $xlogdir, $datadir ],
'successful creation');
+
+ ok(check_mode_recursive($datadir, 0700, 0600));
}
command_ok([ 'initdb', '-S', $datadir ], 'sync only');
command_fails([ 'initdb', $datadir ], 'existing data directory');
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 1b32592063..3d6e47008a 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -27,6 +27,7 @@
#endif
#include "access/xlog_internal.h"
+#include "common/file_perm.h"
#include "common/file_utils.h"
#include "common/string.h"
#include "fe_utils/string_utils.h"
@@ -624,7 +625,7 @@ StartLogStreamer(char *startpos, uint32 timeline, char *sysidentifier)
PQserverVersion(conn) < MINIMUM_VERSION_FOR_PG_WAL ?
"pg_xlog" : "pg_wal");
- if (pg_mkdir_p(statusdir, S_IRWXU) != 0 && errno != EEXIST)
+ if (pg_mkdir_p(statusdir, PG_DIR_MODE_DEFAULT) != 0 && errno != EEXIST)
{
fprintf(stderr,
_("%s: could not create directory \"%s\": %s\n"),
@@ -680,7 +681,7 @@ verify_dir_is_empty_or_create(char *dirname, bool *created, bool *found)
/*
* Does not exist, so create
*/
- if (pg_mkdir_p(dirname, S_IRWXU) == -1)
+ if (pg_mkdir_p(dirname, PG_DIR_MODE_DEFAULT) == -1)
{
fprintf(stderr,
_("%s: could not create directory \"%s\": %s\n"),
@@ -1436,7 +1437,7 @@ ReceiveAndUnpackTarFile(PGconn *conn, PGresult *res, int rownum)
* Directory
*/
filename[strlen(filename) - 1] = '\0'; /* Remove trailing slash */
- if (mkdir(filename, S_IRWXU) != 0)
+ if (mkdir(filename, PG_DIR_MODE_DEFAULT) != 0)
{
/*
* When streaming WAL, pg_wal (or pg_xlog for pre-9.6
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index 32d21ce644..8d9f9c21f4 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -5,7 +5,7 @@ use Config;
use File::Basename qw(basename dirname);
use PostgresNode;
use TestLib;
-use Test::More tests => 93;
+use Test::More tests => 94;
program_help_ok('pg_basebackup');
program_version_ok('pg_basebackup');
@@ -15,6 +15,9 @@ my $tempdir = TestLib::tempdir;
my $node = get_new_node('main');
+# Set umask so no test directories or files are created with group permissions
+umask (0077);
+
# Initialize node without replication settings
$node->init;
$node->start;
@@ -93,6 +96,10 @@ $node->command_ok([ 'pg_basebackup', '-D', "$tempdir/backup", '-X', 'none' ],
'pg_basebackup runs');
ok(-f "$tempdir/backup/PG_VERSION", 'backup was created');
+# Group access should not be enabled on backup
+ok(check_mode_recursive("$tempdir/backup", 0700, 0600),
+ "check backup dir permissions");
+
# Only archive_status directory should be copied in pg_wal/.
is_deeply(
[ sort(slurp_dir("$tempdir/backup/pg_wal/")) ],
diff --git a/src/bin/pg_basebackup/t/020_pg_receivewal.pl b/src/bin/pg_basebackup/t/020_pg_receivewal.pl
index 64e3a35a87..d155d5b711 100644
--- a/src/bin/pg_basebackup/t/020_pg_receivewal.pl
+++ b/src/bin/pg_basebackup/t/020_pg_receivewal.pl
@@ -2,12 +2,15 @@ use strict;
use warnings;
use TestLib;
use PostgresNode;
-use Test::More tests => 18;
+use Test::More tests => 19;
program_help_ok('pg_receivewal');
program_version_ok('pg_receivewal');
program_options_handling_ok('pg_receivewal');
+# Set umask so no test directories or files are created with group permissions
+umask (0077);
+
my $primary = get_new_node('primary');
$primary->init(allows_streaming => 1);
$primary->start;
@@ -56,3 +59,7 @@ $primary->command_ok(
[ 'pg_receivewal', '-D', $stream_dir, '--verbose',
'--endpos', $nextlsn, '--synchronous', '--no-loop' ],
'streaming some WAL with --synchronous');
+
+# Group access should not be enabled on WAL files
+ok(check_mode_recursive($stream_dir, 0700, 0600),
+ "check stream dir permissions");
diff --git a/src/bin/pg_basebackup/walmethods.c b/src/bin/pg_basebackup/walmethods.c
index b4558a0184..aa3bb35d92 100644
--- a/src/bin/pg_basebackup/walmethods.c
+++ b/src/bin/pg_basebackup/walmethods.c
@@ -22,6 +22,7 @@
#endif
#include "pgtar.h"
+#include "common/file_perm.h"
#include "common/file_utils.h"
#include "receivelog.h"
@@ -89,7 +90,7 @@ dir_open_for_write(const char *pathname, const char *temp_suffix, size_t pad_to_
* does not do any system calls to fsync() to make changes permanent on
* disk.
*/
- fd = open(tmppath, O_WRONLY | O_CREAT | PG_BINARY, S_IRUSR | S_IWUSR);
+ fd = open(tmppath, O_WRONLY | O_CREAT | PG_BINARY, PG_FILE_MODE_DEFAULT);
if (fd < 0)
return NULL;
@@ -534,7 +535,8 @@ tar_open_for_write(const char *pathname, const char *temp_suffix, size_t pad_to_
* We open the tar file only when we first try to write to it.
*/
tar_data->fd = open(tar_data->tarfilename,
- O_WRONLY | O_CREAT | PG_BINARY, S_IRUSR | S_IWUSR);
+ O_WRONLY | O_CREAT | PG_BINARY,
+ PG_FILE_MODE_DEFAULT);
if (tar_data->fd < 0)
return NULL;
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index 9bc830b085..e83a7179ea 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -25,6 +25,7 @@
#include "catalog/pg_control.h"
#include "common/controldata_utils.h"
+#include "common/file_perm.h"
#include "getopt_long.h"
#include "utils/pidfile.h"
@@ -2170,7 +2171,8 @@ main(int argc, char **argv)
*/
argv0 = argv[0];
- umask(S_IRWXG | S_IRWXO);
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_DEFAULT);
/* support --help and --version even if invoked as root */
if (argc > 1)
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl
index 5da4746cb4..3d82abe696 100644
--- a/src/bin/pg_ctl/t/001_start_stop.pl
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -4,7 +4,7 @@ use warnings;
use Config;
use PostgresNode;
use TestLib;
-use Test::More tests => 19;
+use Test::More tests => 20;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -57,10 +57,15 @@ command_ok([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ], 'pg_ctl stop');
command_fails([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ],
'second pg_ctl stop fails');
+# Log file for first perm test
+my $logFileName = "$tempdir/data/perm-test-600.log";
+
command_ok(
- [ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
+ [ 'pg_ctl', 'restart', '-D', "$tempdir/data", '-l', $logFileName ],
'pg_ctl restart with server not running');
-command_ok([ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
- 'pg_ctl restart with server running');
+
+# Log file should exist and have no group permissions
+ok(-f $logFileName);
+ok(check_mode_recursive("$tempdir/data", 0700, 0600));
system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index eba7c5fdee..49670a0d29 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -52,6 +52,7 @@
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
#include "common/fe_memutils.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "storage/large_object.h"
#include "pg_getopt.h"
@@ -362,6 +363,9 @@ main(int argc, char *argv[])
exit(1);
}
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_DEFAULT);
+
/* Check that data directory matches our server version */
CheckDataVersion();
@@ -967,7 +971,7 @@ RewriteControlFile(void)
fd = open(XLOG_CONTROL_FILE,
O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ PG_FILE_MODE_DEFAULT);
if (fd < 0)
{
fprintf(stderr, _("%s: could not create pg_control file: %s\n"),
@@ -1249,7 +1253,7 @@ WriteEmptyXLOG(void)
unlink(path);
fd = open(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ PG_FILE_MODE_DEFAULT);
if (fd < 0)
{
fprintf(stderr, _("%s: could not open file \"%s\": %s\n"),
diff --git a/src/bin/pg_resetwal/t/001_basic.pl b/src/bin/pg_resetwal/t/001_basic.pl
index 1b157cb555..7a68a00638 100644
--- a/src/bin/pg_resetwal/t/001_basic.pl
+++ b/src/bin/pg_resetwal/t/001_basic.pl
@@ -3,7 +3,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 11;
+use Test::More tests => 12;
program_help_ok('pg_resetwal');
program_version_ok('pg_resetwal');
@@ -15,3 +15,6 @@ $node->init;
command_like([ 'pg_resetwal', '-n', $node->data_dir ],
qr/checkpoint/,
'pg_resetwal -n produces output');
+
+ok(check_mode_recursive(
+ $node->data_dir, 0700, 0600), 'check PGDATA permissions');
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index 00b5b42dd7..7b632c7dcd 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -237,6 +237,10 @@ sub run_pg_rewind
"$tmp_folder/master-postgresql.conf.tmp",
"$master_pgdata/postgresql.conf");
+ chmod(0600, "$master_pgdata/postgresql.conf")
+ or BAIL_OUT(
+ "unable to set permissions for $master_pgdata/postgresql.conf");
+
# Plug-in rewound node to the now-promoted standby node
my $port_standby = $node_standby->port;
$node_master->append_conf(
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index f491ed7f5c..5a7f769dd6 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -18,6 +18,7 @@
#include <fcntl.h>
#include <unistd.h>
+#include "common/file_perm.h"
#include "file_ops.h"
#include "filemap.h"
#include "logging.h"
@@ -57,7 +58,7 @@ open_target_file(const char *path, bool trunc)
mode = O_WRONLY | O_CREAT | PG_BINARY;
if (trunc)
mode |= O_TRUNC;
- dstfd = open(dstpath, mode, 0600);
+ dstfd = open(dstpath, mode, PG_FILE_MODE_DEFAULT);
if (dstfd < 0)
pg_fatal("could not open target file \"%s\": %s\n",
dstpath, strerror(errno));
@@ -198,7 +199,7 @@ truncate_target_file(const char *path, off_t newsize)
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
- fd = open(dstpath, O_WRONLY, 0);
+ fd = open(dstpath, O_WRONLY, PG_FILE_MODE_DEFAULT);
if (fd < 0)
pg_fatal("could not open file \"%s\" for truncation: %s\n",
dstpath, strerror(errno));
@@ -219,7 +220,7 @@ create_target_dir(const char *path)
return;
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
- if (mkdir(dstpath, S_IRWXU) != 0)
+ if (mkdir(dstpath, PG_DIR_MODE_DEFAULT) != 0)
pg_fatal("could not create directory \"%s\": %s\n",
dstpath, strerror(errno));
}
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 72ab2f8d5e..3d179e2c7d 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -24,6 +24,7 @@
#include "access/xlog_internal.h"
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "getopt_long.h"
#include "storage/bufpage.h"
@@ -185,6 +186,9 @@ main(int argc, char **argv)
exit(1);
}
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_DEFAULT);
+
/*
* Don't allow pg_rewind to be run as root, to avoid overwriting the
* ownership of files in the data directory. We need only check for root
diff --git a/src/bin/pg_rewind/t/001_basic.pl b/src/bin/pg_rewind/t/001_basic.pl
index 736f34eae3..ae7b116532 100644
--- a/src/bin/pg_rewind/t/001_basic.pl
+++ b/src/bin/pg_rewind/t/001_basic.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 8;
+use Test::More tests => 10;
use RewindTest;
@@ -86,6 +86,7 @@ in master, before promotion
),
'tail-copy');
+ ok(check_mode_recursive($node_master->data_dir(), 0700, 0600));
RewindTest::clean_rewind_test();
}
diff --git a/src/bin/pg_upgrade/file.c b/src/bin/pg_upgrade/file.c
index f38bfacf02..595d43ee31 100644
--- a/src/bin/pg_upgrade/file.c
+++ b/src/bin/pg_upgrade/file.c
@@ -10,6 +10,7 @@
#include "postgres_fe.h"
#include "access/visibilitymap.h"
+#include "common/file_perm.h"
#include "pg_upgrade.h"
#include "storage/bufpage.h"
#include "storage/checksum.h"
@@ -44,7 +45,7 @@ copyFile(const char *src, const char *dst,
schemaName, relName, src, strerror(errno));
if ((dest_fd = open(dst, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR)) < 0)
+ PG_FILE_MODE_DEFAULT)) < 0)
pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
schemaName, relName, dst, strerror(errno));
@@ -151,7 +152,7 @@ rewriteVisibilityMap(const char *fromfile, const char *tofile,
schemaName, relName, fromfile, strerror(errno));
if ((dst_fd = open(tofile, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR)) < 0)
+ PG_FILE_MODE_DEFAULT)) < 0)
pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
schemaName, relName, tofile, strerror(errno));
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index d12412799f..59df6fd88f 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -38,6 +38,7 @@
#include "pg_upgrade.h"
#include "catalog/pg_class.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "fe_utils/string_utils.h"
@@ -79,7 +80,7 @@ main(int argc, char **argv)
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_upgrade"));
/* Ensure that all files created by pg_upgrade are non-world-readable */
- umask(S_IRWXG | S_IRWXO);
+ umask(PG_MODE_MASK_DEFAULT);
parseCommandLine(argc, argv);
diff --git a/src/bin/pg_upgrade/test.sh b/src/bin/pg_upgrade/test.sh
index 39983abea1..4e8db4aaf4 100644
--- a/src/bin/pg_upgrade/test.sh
+++ b/src/bin/pg_upgrade/test.sh
@@ -230,6 +230,17 @@ standard_initdb 'initdb'
pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "${PGDATA}" -b "$oldbindir" -B "$bindir" -p "$PGPORT" -P "$PGPORT"
+# make sure all directories and files have group permissions
+if [ $(find ${PGDATA} -type f ! -perm 600 | wc -l) -ne 0 ]; then
+ echo "files in PGDATA with permission != 600";
+ exit 1;
+fi
+
+if [ $(find ${PGDATA} -type d ! -perm 700 | wc -l) -ne 0 ]; then
+ echo "directories in PGDATA with permission != 700";
+ exit 1;
+fi
+
pg_ctl start -l "$logdir/postmaster2.log" -o "$POSTMASTER_OPTS" -w
case $testhost in
diff --git a/src/include/common/file_perm.h b/src/include/common/file_perm.h
new file mode 100644
index 0000000000..3c6da0e000
--- /dev/null
+++ b/src/include/common/file_perm.h
@@ -0,0 +1,32 @@
+/*-------------------------------------------------------------------------
+ *
+ * File and directory permission constants
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/file_perm.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FILE_PERM_H
+#define FILE_PERM_H
+
+/*
+ * Default mode mask for data directory permissions that does not allow
+ * group execute/read.
+ */
+#define PG_MODE_MASK_DEFAULT (S_IRWXG | S_IRWXO)
+
+/*
+ * Default mode for created files.
+ */
+#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR)
+
+/*
+ * Default mode for directories.
+ */
+#define PG_DIR_MODE_DEFAULT S_IRWXU
+
+#endif /* FILE_PERM_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index e49b42ce86..340dbd4d3b 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -112,6 +112,9 @@ extern int CloseTransientFile(int fd);
extern int BasicOpenFile(const char *fileName, int fileFlags);
extern int BasicOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode);
+ /* Make a directory with default permissions */
+extern int MakeDirectoryDefaultPerm(const char *directoryName);
+
/* Miscellaneous support routines */
extern void InitFileAccess(void);
extern void set_max_safe_fds(void);
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 80188315f1..76e571b98c 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -484,6 +484,9 @@ sub append_conf
my $conffile = $self->data_dir . '/' . $filename;
TestLib::append_to_file($conffile, $str . "\n");
+
+ chmod(0600, $conffile)
+ or die("unable to set permissions for $conffile");
}
=pod
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index b6862688d4..93610e4bc4 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -13,8 +13,11 @@ use warnings;
use Config;
use Cwd;
use Exporter 'import';
+use Fcntl qw(:mode);
use File::Basename;
+use File::Find;
use File::Spec;
+use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
use SimpleTee;
@@ -27,6 +30,7 @@ our @EXPORT = qw(
slurp_dir
slurp_file
append_to_file
+ check_mode_recursive
check_pg_config
system_or_bail
system_log
@@ -240,6 +244,75 @@ sub append_to_file
close $fh;
}
+# Check that all file/dir modes in a directory match the expected values,
+# ignoring the mode of any specified files.
+sub check_mode_recursive
+{
+ my ($dir, $expected_dir_mode, $expected_file_mode, $ignore_list) = @_;
+
+ # Result defaults to true
+ my $result = 1;
+
+ find
+ (
+ {follow_fast => 1,
+ wanted =>
+ sub
+ {
+ my $file_stat = stat($File::Find::name);
+
+ # Is file in the ignore list?
+ foreach my $ignore ($ignore_list ? @{$ignore_list} : [])
+ {
+ if ("$dir/$ignore" eq $File::Find::name)
+ {
+ return;
+ }
+ }
+
+ defined($file_stat)
+ or die("unable to stat $File::Find::name");
+
+ my $file_mode = S_IMODE($file_stat->mode);
+
+ # Is this a file?
+ if (S_ISREG($file_stat->mode))
+ {
+ if ($file_mode != $expected_file_mode)
+ {
+ print(*STDERR,
+ sprintf("$File::Find::name mode must be %04o\n",
+ $expected_file_mode));
+
+ $result = 0;
+ return;
+ }
+ }
+ # Else a directory?
+ elsif (S_ISDIR($file_stat->mode))
+ {
+ if ($file_mode != $expected_dir_mode)
+ {
+ print(*STDERR,
+ sprintf("$File::Find::name mode must be %04o\n",
+ $expected_dir_mode));
+
+ $result = 0;
+ return;
+ }
+ }
+ # Else something we can't handle
+ else
+ {
+ die "unknown file type for $File::Find::name";
+ }
+ }},
+ $dir
+ );
+
+ return $result;
+}
+
# Check presence of a given regexp within pg_config.h for the installation
# where tests are running, returning a match status result depending on
# that.
--
2.14.3 (Apple Git-98)
group-access-v13-02-group.patchtext/plain; charset=UTF-8; name=group-access-v13-02-group.patch; x-mac-creator=0; x-mac-type=0Download
From e5b9b1b14536f39a288656b6fbdbeec346fbea7c Mon Sep 17 00:00:00 2001
From: David Steele <david@pgmasters.net>
Date: Fri, 30 Mar 2018 13:23:43 -0400
Subject: [PATCH 1/1] Allow group access on PGDATA.
This includes backend changes, utility changes (initdb, pg_ctl, pg_upgrade, etc.) and tests for each utility.
---
doc/src/sgml/ref/initdb.sgml | 19 +++++++
doc/src/sgml/runtime.sgml | 14 +++++-
src/backend/commands/tablespace.c | 2 +-
src/backend/postmaster/postmaster.c | 41 +++++++++++----
src/backend/replication/basebackup.c | 4 +-
src/backend/utils/init/globals.c | 7 +++
src/backend/utils/init/miscinit.c | 22 ++++----
src/backend/utils/misc/guc.c | 25 ++++++++++
src/bin/initdb/initdb.c | 35 ++++++++++---
src/bin/initdb/t/001_initdb.pl | 13 ++++-
src/bin/pg_basebackup/pg_basebackup.c | 22 +++++---
src/bin/pg_basebackup/pg_receivewal.c | 7 +++
src/bin/pg_basebackup/pg_recvlogical.c | 7 +++
src/bin/pg_basebackup/streamutil.c | 75 ++++++++++++++++++++++++++++
src/bin/pg_basebackup/streamutil.h | 2 +
src/bin/pg_basebackup/t/010_pg_basebackup.pl | 26 +++++++---
src/bin/pg_ctl/pg_ctl.c | 5 +-
src/bin/pg_ctl/t/001_start_stop.pl | 24 ++++++++-
src/bin/pg_resetwal/pg_resetwal.c | 4 +-
src/bin/pg_rewind/RewindTest.pm | 9 ++--
src/bin/pg_rewind/pg_rewind.c | 4 +-
src/bin/pg_rewind/t/002_databases.pl | 6 ++-
src/bin/pg_upgrade/pg_upgrade.c | 5 +-
src/bin/pg_upgrade/test.sh | 14 +++---
src/common/Makefile | 2 +-
src/common/file_perm.c | 48 ++++++++++++++++++
src/include/common/file_perm.h | 35 +++++++++++--
src/include/miscadmin.h | 1 +
src/test/perl/PostgresNode.pm | 27 +++++++++-
src/test/perl/TestLib.pm | 25 ++++++++++
src/tools/msvc/Mkvcbuild.pm | 2 +-
31 files changed, 454 insertions(+), 78 deletions(-)
create mode 100644 src/common/file_perm.c
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 949b5a220f..1d41e91794 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -76,6 +76,14 @@ PostgreSQL documentation
to do so.)
</para>
+ <para>
+ For security reasons the new cluster created by <command>initdb</command>
+ will only be accessible by the cluster owner by default. The
+ <option>--allow-group-access</option> option allows any user in the same
+ group as the cluster owner to read files in the cluster. This is useful
+ for performing backups as a non-privileged user.
+ </para>
+
<para>
<command>initdb</command> initializes the database cluster's default
locale and character set encoding. The character set encoding,
@@ -301,6 +309,17 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-g</option></term>
+ <term><option>--allow-group-access</option></term>
+ <listitem>
+ <para>
+ Allows users in the same group as the cluster owner to read all cluster
+ files created by <command>initdb</command>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-W</option></term>
<term><option>--pwprompt</option></term>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 587b430527..40ce3d9813 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -137,7 +137,10 @@ postgres$ <userinput>initdb -D /usr/local/pgsql/data</userinput>
database, it is essential that it be secured from unauthorized
access. <command>initdb</command> therefore revokes access
permissions from everyone but the
- <productname>PostgreSQL</productname> user.
+ <productname>PostgreSQL</productname> user, and optionally, group.
+ Group access, when enabled, is read-only. This allows an unprivileged
+ user in the <productname>PostgreSQL</productname> group to take a backup of
+ the cluster data or perform other operations that only require read access.
</para>
<para>
@@ -2194,6 +2197,15 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
member of the group that has access to those certificate and key files.
</para>
+ <para>
+ If the data directory allows group read access then certificate files may
+ need to be located outside of the data directory in order to conform to the
+ security requirements outlined above. Generally, group access is enabled
+ to allow an unprivileged user to backup the database, and in that case the
+ backup software will not be able to read the certificate files and will
+ likely error.
+ </para>
+
<para>
If the private key is protected with a passphrase, the
server will prompt for the passphrase and will not start until it has
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index f7e1818350..704ff6c3df 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -576,7 +576,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* Attempt to coerce target directory to safe permissions. If this fails,
* it doesn't exist or has the wrong owner.
*/
- if (chmod(location, PG_DIR_MODE_DEFAULT) != 0)
+ if (chmod(location, DataDirMode) != 0)
{
if (errno == ENOENT)
ereport(ERROR,
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 868fba8cac..2f543ef461 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -588,7 +588,9 @@ PostmasterMain(int argc, char *argv[])
IsPostmasterEnvironment = true;
/*
- * for security, no dir or file created can be group or other accessible
+ * By default, no directory or file created can be group or other
+ * accessible. This may be modified later depending on the permissions of
+ * the data directory.
*/
umask(PG_MODE_MASK_DEFAULT);
@@ -1522,25 +1524,44 @@ checkDataDir(void)
#endif
/*
- * Check if the directory has group or world access. If so, reject.
+ * Check if the directory has correct permissions. If not, 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.
+ * 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 & (S_IRWXG | S_IRWXO))
+ if (stat_buf.st_mode & PG_MODE_MASK_ALLOW_GROUP)
ereport(FATAL,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("data directory \"%s\" has group or world access",
+ errmsg("data directory \"%s\" has invalid permissions",
DataDir),
- errdetail("Permissions should be u=rwx (0700).")));
+ errdetail("Permissions should be u=rwx (0700) or u=rwx,g=rx (0750).")));
+#endif
+
+ /*
+ * Reset the file mode creation 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 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__)
+ if ((stat_buf.st_mode & PG_DIR_MODE_DEFAULT) == PG_DIR_MODE_DEFAULT)
+ {
+ umask(PG_MODE_MASK_ALLOW_GROUP);
+
+ SetConfigOption("data_directory_mode", "0750", PGC_INTERNAL,
+ PGC_S_OVERRIDE);
+ }
#endif
/* Look for PG_VERSION before looking for pg_control */
diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c
index 97e2bdfc73..778d2dbed3 100644
--- a/src/backend/replication/basebackup.c
+++ b/src/backend/replication/basebackup.c
@@ -879,7 +879,7 @@ sendFileWithContent(const char *filename, const char *content)
statbuf.st_gid = getegid();
#endif
statbuf.st_mtime = time(NULL);
- statbuf.st_mode = PG_FILE_MODE_DEFAULT;
+ statbuf.st_mode = DataDirMode & PG_FILE_MODE_DEFAULT;
statbuf.st_size = len;
_tarWriteHeader(filename, NULL, &statbuf, false);
@@ -1398,7 +1398,7 @@ _tarWriteDir(const char *pathbuf, int basepathlen, struct stat *statbuf,
#else
if (pgwin32_is_junction(pathbuf))
#endif
- statbuf->st_mode = S_IFDIR | PG_DIR_MODE_DEFAULT;
+ statbuf->st_mode = S_IFDIR | DataDirMode;
return _tarWriteHeader(pathbuf + basepathlen + 1, NULL, statbuf, sizeonly);
}
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 446040d816..10a7ac6c42 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -18,6 +18,7 @@
*/
#include "postgres.h"
+#include "common/file_perm.h"
#include "libpq/libpq-be.h"
#include "libpq/pqcomm.h"
#include "miscadmin.h"
@@ -59,6 +60,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 DataDirMode = PG_DIR_MODE_NOGROUP;
+
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 82da4b9f27..192e0afa81 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -125,7 +125,6 @@ ChangeToDataDir(void)
DataDir)));
}
-
/* ----------------------------------------------------------------
* User ID state
*
@@ -829,7 +828,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_MODE_DEFAULT);
@@ -899,17 +898,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 4ffc8451ca..33b9ad53e3 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -192,6 +192,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,
@@ -2042,6 +2043,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
+ },
+ &DataDirMode,
+ 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."),
@@ -10721,4 +10737,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", DataDirMode);
+ return buf;
+}
+
#include "guc-file.c"
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 0b69eded81..96f162fb0b 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -145,6 +145,7 @@ static bool data_checksums = false;
static char *xlog_dir = NULL;
static char *str_wal_segment_size_mb = NULL;
static int wal_segment_size_mb;
+static mode_t file_mode_mask = PG_MODE_MASK_DEFAULT;
/* internal vars */
@@ -1168,10 +1169,23 @@ setup_config(void)
"password_encryption = scram-sha-256");
}
+ /*
+ * If group access has been enabled for the cluster then it makes sense
+ * to ensure that the log files also allow group access. Otherwise a
+ * backup from a user in the group would fail if the log files were not
+ * relocated.
+ */
+ if (file_mode_mask == PG_MODE_MASK_ALLOW_GROUP)
+ {
+ conflines = replace_token(conflines,
+ "#log_file_mode = 0600",
+ "log_file_mode = 0640");
+ }
+
snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
+ if (chmod(path, PG_FILE_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1191,7 +1205,7 @@ setup_config(void)
sprintf(path, "%s/postgresql.auto.conf", pg_data);
writefile(path, autoconflines);
- if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
+ if (chmod(path, PG_FILE_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1278,7 +1292,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_hba.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
+ if (chmod(path, PG_FILE_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1294,7 +1308,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_ident.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
+ if (chmod(path, PG_FILE_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -2312,6 +2326,7 @@ usage(const char *progname)
printf(_(" --auth-local=METHOD default authentication method for local-socket connections\n"));
printf(_(" [-D, --pgdata=]DATADIR location for this database cluster\n"));
printf(_(" -E, --encoding=ENCODING set default encoding for new databases\n"));
+ printf(_(" -g, --allow-group-access allow group read/execute on data directory\n"));
printf(_(" --locale=LOCALE set default locale for new databases\n"));
printf(_(" --lc-collate=, --lc-ctype=, --lc-messages=LOCALE\n"
" --lc-monetary=, --lc-numeric=, --lc-time=LOCALE\n"
@@ -2711,7 +2726,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (chmod(pg_data, PG_DIR_MODE_DEFAULT) != 0)
+ if (chmod(pg_data, PG_DIR_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2797,7 +2812,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (chmod(xlog_dir, PG_DIR_MODE_DEFAULT) != 0)
+ if (chmod(xlog_dir, PG_DIR_MODE_DEFAULT & ~file_mode_mask) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2884,7 +2899,7 @@ initialize_data_directory(void)
setup_signals();
/* Set dir/file mode mask */
- umask(PG_MODE_MASK_DEFAULT);
+ umask(file_mode_mask);
create_data_directory();
@@ -3018,6 +3033,7 @@ main(int argc, char *argv[])
{"waldir", required_argument, NULL, 'X'},
{"wal-segsize", required_argument, NULL, 12},
{"data-checksums", no_argument, NULL, 'k'},
+ {"allow-group-access", no_argument, NULL, 'g'},
{NULL, 0, NULL, 0}
};
@@ -3059,7 +3075,7 @@ main(int argc, char *argv[])
/* process command-line options */
- while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:", long_options, &option_index)) != -1)
+ while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:g", long_options, &option_index)) != -1)
{
switch (c)
{
@@ -3153,6 +3169,9 @@ main(int argc, char *argv[])
case 12:
str_wal_segment_size_mb = pg_strdup(optarg);
break;
+ case 'g':
+ file_mode_mask = PG_MODE_MASK_ALLOW_GROUP;
+ break;
default:
/* getopt_long already emitted a complaint */
fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index 3331560445..f08fc6ea8f 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -4,9 +4,11 @@
use strict;
use warnings;
+use Fcntl ':mode';
+use File::stat qw{lstat};
use PostgresNode;
use TestLib;
-use Test::More tests => 16;
+use Test::More tests => 18;
my $tempdir = TestLib::tempdir;
my $xlogdir = "$tempdir/pgxlog";
@@ -50,3 +52,12 @@ mkdir $datadir;
}
command_ok([ 'initdb', '-S', $datadir ], 'sync only');
command_fails([ 'initdb', $datadir ], 'existing data directory');
+
+# Init a new db with group access
+my $datadir_group = "$tempdir/data_group";
+
+command_ok(
+ [ 'initdb', '-g', $datadir_group ],
+ 'successful creation with group access');
+
+ok(check_mode_recursive($datadir_group, 0750, 0640));
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 3d6e47008a..db3ed9e0fe 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -2448,14 +2448,6 @@ main(int argc, char **argv)
}
#endif
- /*
- * Verify that the target directory exists, or create it. For plaintext
- * backups, always require the directory. For tar backups, require it
- * unless we are writing to stdout.
- */
- if (format == 'p' || strcmp(basedir, "-") != 0)
- verify_dir_is_empty_or_create(basedir, &made_new_pgdata, &found_existing_pgdata);
-
/* connection in replication mode to server */
conn = GetConnection();
if (!conn)
@@ -2464,6 +2456,20 @@ main(int argc, char **argv)
exit(1);
}
+ /*
+ * Set umask so that directories/files are created with the same permissions
+ * as directories/files in the source data directory.
+ */
+ umask(DataDirMask);
+
+ /*
+ * Verify that the target directory exists, or create it. For plaintext
+ * backups, always require the directory. For tar backups, require it
+ * unless we are writing to stdout.
+ */
+ if (format == 'p' || strcmp(basedir, "-") != 0)
+ verify_dir_is_empty_or_create(basedir, &made_new_pgdata, &found_existing_pgdata);
+
/* determine remote server's xlog segment size */
if (!RetrieveWalSegSize(conn))
disconnect_and_exit(1);
diff --git a/src/bin/pg_basebackup/pg_receivewal.c b/src/bin/pg_basebackup/pg_receivewal.c
index b82e073b86..643fb16db1 100644
--- a/src/bin/pg_basebackup/pg_receivewal.c
+++ b/src/bin/pg_basebackup/pg_receivewal.c
@@ -19,6 +19,7 @@
#include <sys/stat.h>
#include <unistd.h>
+#include "common/file_perm.h"
#include "libpq-fe.h"
#include "access/xlog_internal.h"
#include "getopt_long.h"
@@ -703,6 +704,12 @@ main(int argc, char **argv)
if (!RunIdentifySystem(conn, NULL, NULL, NULL, &db_name))
disconnect_and_exit(1);
+ /*
+ * Set umask so that directories/files are created with the same permissions
+ * as directories/files in the source data directory.
+ */
+ umask(DataDirMask);
+
/* determine remote server's xlog segment size */
if (!RetrieveWalSegSize(conn))
disconnect_and_exit(1);
diff --git a/src/bin/pg_basebackup/pg_recvlogical.c b/src/bin/pg_basebackup/pg_recvlogical.c
index 53e4661d68..b0f001cd1c 100644
--- a/src/bin/pg_basebackup/pg_recvlogical.c
+++ b/src/bin/pg_basebackup/pg_recvlogical.c
@@ -23,6 +23,7 @@
#include "streamutil.h"
#include "access/xlog_internal.h"
+#include "common/file_perm.h"
#include "common/fe_memutils.h"
#include "getopt_long.h"
#include "libpq-fe.h"
@@ -959,6 +960,12 @@ main(int argc, char **argv)
disconnect_and_exit(1);
}
+ /*
+ * Set umask so that directories/files are created with the same permissions
+ * as directories/files in the source data directory.
+ */
+ umask(DataDirMask);
+
/* Drop a replication slot. */
if (do_drop_slot)
{
diff --git a/src/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c
index 1438f368ed..a072d3c658 100644
--- a/src/bin/pg_basebackup/streamutil.c
+++ b/src/bin/pg_basebackup/streamutil.c
@@ -23,6 +23,7 @@
#include "access/xlog_internal.h"
#include "common/fe_memutils.h"
+#include "common/file_perm.h"
#include "datatype/timestamp.h"
#include "fe_utils/connect.h"
#include "port/pg_bswap.h"
@@ -32,9 +33,20 @@
uint32 WalSegSz;
+/* Store the mode and umask of the source data directory */
+int DataDirMode;
+int DataDirMask;
+
+static bool RetrieveDataDirMode(PGconn *conn);
+
/* SHOW command for replication connection was introduced in version 10 */
#define MINIMUM_VERSION_FOR_SHOW_CMD 100000
+/*
+ * Group access is supported from version 11.
+ */
+#define MINIMUM_VERSION_FOR_GROUP_ACCESS 110000
+
const char *progname;
char *connection_string = NULL;
char *dbhost = NULL;
@@ -254,6 +266,18 @@ GetConnection(void)
exit(1);
}
+ /*
+ * Retrieve the source data directory mode and use to construct a umask
+ * for creating directories and files
+ */
+ if (!RetrieveDataDirMode(tmpconn))
+ {
+ PQfinish(tmpconn);
+ exit(1);
+ }
+
+ DataDirMask = ~DataDirMode & PG_MODE_MASK_DEFAULT;
+
return tmpconn;
}
@@ -327,6 +351,57 @@ RetrieveWalSegSize(PGconn *conn)
return true;
}
+/*
+ * From version 11, check the mode of the data directory to determine
+ * permissions to use for directories created by the utility.
+ */
+static bool
+RetrieveDataDirMode(PGconn *conn)
+{
+ PGresult *res;
+
+ /* check connection existence */
+ Assert(conn != NULL);
+
+ /* for previous versions set the default group access */
+ if (PQserverVersion(conn) < MINIMUM_VERSION_FOR_GROUP_ACCESS)
+ {
+ DataDirMode = PG_DIR_MODE_NOGROUP;
+ return true;
+ }
+
+ res = PQexec(conn, "SHOW data_directory_mode");
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ fprintf(stderr, _("%s: could not send replication command \"%s\": %s\n"),
+ progname, "SHOW data_directory_mode", PQerrorMessage(conn));
+
+ PQclear(res);
+ return false;
+ }
+ if (PQntuples(res) != 1 || PQnfields(res) < 1)
+ {
+ fprintf(stderr,
+ _("%s: could not fetch group access flag: got %d rows and %d fields, expected %d rows and %d or more fields\n"),
+ progname, PQntuples(res), PQnfields(res), 1, 1);
+
+ PQclear(res);
+ return false;
+ }
+
+ if (sscanf(PQgetvalue(res, 0, 0), "%o", &DataDirMode) != 1)
+ {
+ fprintf(stderr, _("%s: group access flag could not be parsed: %s\n"),
+ progname, PQgetvalue(res, 0, 0));
+
+ PQclear(res);
+ return false;
+ }
+
+ PQclear(res);
+ return true;
+}
+
/*
* Run IDENTIFY_SYSTEM through a given connection and give back to caller
* some result information if requested:
diff --git a/src/bin/pg_basebackup/streamutil.h b/src/bin/pg_basebackup/streamutil.h
index 6854bbc31d..df8fb36a2e 100644
--- a/src/bin/pg_basebackup/streamutil.h
+++ b/src/bin/pg_basebackup/streamutil.h
@@ -25,6 +25,8 @@ extern char *dbport;
extern char *dbname;
extern int dbgetpassword;
extern uint32 WalSegSz;
+extern int DataDirMode;
+extern int DataDirMask;
/* Connection kept global so we can disconnect easily */
extern PGconn *conn;
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index 8d9f9c21f4..ce87f77e37 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -5,7 +5,7 @@ use Config;
use File::Basename qw(basename dirname);
use PostgresNode;
use TestLib;
-use Test::More tests => 94;
+use Test::More tests => 95;
program_help_ok('pg_basebackup');
program_version_ok('pg_basebackup');
@@ -43,11 +43,6 @@ $node->command_fails(
ok(!-d "$tempdir/backup", 'backup directory was cleaned up');
-$node->command_fails([ 'pg_basebackup', '-D', "$tempdir/backup", '-n' ],
- 'failing run with no-clean option');
-
-ok(-d "$tempdir/backup", 'backup directory was created and left behind');
-
open my $conf, '>>', "$pgdata/postgresql.conf";
print $conf "max_replication_slots = 10\n";
print $conf "max_wal_senders = 10\n";
@@ -157,6 +152,13 @@ ok(-f "$tempdir/tarbackup/base.tar", 'backup tar was created');
$node->command_fails(
[ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T=/foo" ],
'-T with empty old directory fails');
+
+$node->command_fails(
+ [ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T=/foo", "-n" ],
+ 'failing run with no-clean option');
+
+ok(-d "$tempdir/backup", 'backup directory was created and left behind');
+
$node->command_fails(
[ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T/foo=" ],
'-T with empty new directory fails');
@@ -190,11 +192,17 @@ unlink "$pgdata/$superlongname";
# skip on Windows.
SKIP:
{
- skip "symlinks not supported on Windows", 17 if ($windows_os);
+ skip "symlinks not supported on Windows", 18 if ($windows_os);
# Move pg_replslot out of $pgdata and create a symlink to it.
$node->stop;
+ # Set umask so test directories and files are created with group permissions
+ umask (0027);
+
+ # Enable group permissions on PGDATA
+ chmod_recursive("$pgdata", 0750, 0640);
+
rename("$pgdata/pg_replslot", "$tempdir/pg_replslot")
or BAIL_OUT "could not move $pgdata/pg_replslot";
symlink("$tempdir/pg_replslot", "$pgdata/pg_replslot")
@@ -264,6 +272,10 @@ SKIP:
"tablespace symlink was updated");
closedir $dh;
+ # Group access should be enabled on all backup files
+ ok(check_mode_recursive("$tempdir/backup1", 0750, 0640),
+ "check backup dir permissions");
+
# Unlogged relation forks other than init should not be copied
my ($tblspc1UnloggedBackupPath) = $tblspc1UnloggedPath =~ /[^\/]*\/[^\/]*\/[^\/]*$/g;
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index e83a7179ea..bb70e625fd 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -2171,7 +2171,7 @@ main(int argc, char **argv)
*/
argv0 = argv[0];
- /* Set dir/file mode mask */
+ /* Set restrictive mode mask until PGDATA permissions are checked */
umask(PG_MODE_MASK_DEFAULT);
/* support --help and --version even if invoked as root */
@@ -2407,6 +2407,9 @@ main(int argc, char **argv)
snprintf(version_file, MAXPGPATH, "%s/PG_VERSION", pg_data);
snprintf(pid_file, MAXPGPATH, "%s/postmaster.pid", pg_data);
snprintf(backup_file, MAXPGPATH, "%s/backup_label", pg_data);
+
+ /* Set mask based on PGDATA permissions */
+ umask(DataDirectoryMask(pg_data));
}
switch (ctl_command)
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl
index 3d82abe696..91e7f00326 100644
--- a/src/bin/pg_ctl/t/001_start_stop.pl
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -2,9 +2,11 @@ use strict;
use warnings;
use Config;
+use Fcntl ':mode';
+use File::stat qw{lstat};
use PostgresNode;
use TestLib;
-use Test::More tests => 20;
+use Test::More tests => 24;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -68,4 +70,24 @@ command_ok(
ok(-f $logFileName);
ok(check_mode_recursive("$tempdir/data", 0700, 0600));
+# Log file for second perm test
+$logFileName = "$tempdir/data/perm-test-640.log";
+
+# Change the data dir mode so log file will be created with group read
+# privileges on the next start
+system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
+
+chmod_recursive("$tempdir/data", 0750, 0640);
+
+command_ok(
+ [ 'pg_ctl', 'start', '-D', "$tempdir/data", '-l', $logFileName ],
+ 'start server to check group permissions');
+
+ok(-f $logFileName);
+ok(check_mode_recursive("$tempdir/data", 0750, 0640));
+
+command_ok(
+ [ 'pg_ctl', 'restart', '-D', "$tempdir/data", '-l', $logFileName ],
+ 'pg_ctl restart with server running');
+
system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 49670a0d29..a311bb4128 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -363,8 +363,8 @@ main(int argc, char *argv[])
exit(1);
}
- /* Set dir/file mode mask */
- umask(PG_MODE_MASK_DEFAULT);
+ /* Set mask based on PGDATA permissions */
+ umask(DataDirectoryMask(DataDir));
/* Check that data directory matches our server version */
CheckDataVersion();
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index 7b632c7dcd..63d9bd517d 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -115,11 +115,13 @@ sub check_query
sub setup_cluster
{
- my $extra_name = shift;
+ my $extra_name = shift; # Used to differentiate clusters
+ my $extra = shift; # Extra params for initdb
# Initialize master, data checksums are mandatory
$node_master = get_new_node('master' . ($extra_name ? "_${extra_name}" : ''));
- $node_master->init(allows_streaming => 1);
+ $node_master->init(
+ allows_streaming => 1, extra => $extra);
# Set wal_keep_segments to prevent WAL segment recycling after enforced
# checkpoints in the tests.
$node_master->append_conf('postgresql.conf', qq(
@@ -237,7 +239,8 @@ sub run_pg_rewind
"$tmp_folder/master-postgresql.conf.tmp",
"$master_pgdata/postgresql.conf");
- chmod(0600, "$master_pgdata/postgresql.conf")
+ chmod($node_master->group_access() ? 0640 : 0600,
+ "$master_pgdata/postgresql.conf")
or BAIL_OUT(
"unable to set permissions for $master_pgdata/postgresql.conf");
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 3d179e2c7d..11153e5f2f 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -186,8 +186,8 @@ main(int argc, char **argv)
exit(1);
}
- /* Set dir/file mode mask */
- umask(PG_MODE_MASK_DEFAULT);
+ /* Set mask based on PGDATA permissions */
+ umask(DataDirectoryMask(datadir_target));
/*
* Don't allow pg_rewind to be run as root, to avoid overwriting the
diff --git a/src/bin/pg_rewind/t/002_databases.pl b/src/bin/pg_rewind/t/002_databases.pl
index 37cdd712f3..570fa5cee0 100644
--- a/src/bin/pg_rewind/t/002_databases.pl
+++ b/src/bin/pg_rewind/t/002_databases.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 6;
use RewindTest;
@@ -9,7 +9,7 @@ sub run_test
{
my $test_mode = shift;
- RewindTest::setup_cluster($test_mode);
+ RewindTest::setup_cluster($test_mode, ['-g']);
RewindTest::start_master();
# Create a database in master.
@@ -42,6 +42,8 @@ template1
),
'database names');
+ ok (check_mode_recursive(
+ $node_master->data_dir(), 0750, 0640, ['postmaster.pid']));
RewindTest::clean_rewind_test();
}
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 59df6fd88f..54f54fb0a4 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -79,7 +79,7 @@ main(int argc, char **argv)
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_upgrade"));
- /* Ensure that all files created by pg_upgrade are non-world-readable */
+ /* Set default restrictive mask until new cluster permissions are read */
umask(PG_MODE_MASK_DEFAULT);
parseCommandLine(argc, argv);
@@ -100,6 +100,9 @@ main(int argc, char **argv)
check_cluster_compatibility(live_check);
+ /* Set mask based on new PGDATA permissions */
+ umask(DataDirectoryMask(new_cluster.pgdata));
+
check_and_dump_old_cluster(live_check);
diff --git a/src/bin/pg_upgrade/test.sh b/src/bin/pg_upgrade/test.sh
index 4e8db4aaf4..24631a91c6 100644
--- a/src/bin/pg_upgrade/test.sh
+++ b/src/bin/pg_upgrade/test.sh
@@ -20,9 +20,9 @@ unset MAKELEVEL
# Run a given "initdb" binary and overlay the regression testing
# authentication configuration.
standard_initdb() {
- # To increase coverage of non-standard segment size without
- # increase test runtime, run these tests with a lower setting.
- "$1" -N --wal-segsize 1
+ # To increase coverage of non-standard segment size and group access
+ # without increasing test runtime, run these tests with a custom setting.
+ "$1" -N --wal-segsize 1 -g
if [ -n "$TEMP_CONFIG" -a -r "$TEMP_CONFIG" ]
then
cat "$TEMP_CONFIG" >> "$PGDATA/postgresql.conf"
@@ -231,13 +231,13 @@ standard_initdb 'initdb'
pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "${PGDATA}" -b "$oldbindir" -B "$bindir" -p "$PGPORT" -P "$PGPORT"
# make sure all directories and files have group permissions
-if [ $(find ${PGDATA} -type f ! -perm 600 | wc -l) -ne 0 ]; then
- echo "files in PGDATA with permission != 600";
+if [ $(find ${PGDATA} -type f ! -perm 640 | wc -l) -ne 0 ]; then
+ echo "files in PGDATA with permission != 640";
exit 1;
fi
-if [ $(find ${PGDATA} -type d ! -perm 700 | wc -l) -ne 0 ]; then
- echo "directories in PGDATA with permission != 700";
+if [ $(find ${PGDATA} -type d ! -perm 750 | wc -l) -ne 0 ]; then
+ echo "directories in PGDATA with permission != 750";
exit 1;
fi
diff --git a/src/common/Makefile b/src/common/Makefile
index 80e78d72fe..504375bef4 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -51,7 +51,7 @@ else
OBJS_COMMON += sha2.o
endif
-OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o file_utils.o restricted_token.o
+OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o file_perm.o file_utils.o restricted_token.o
OBJS_SRV = $(OBJS_COMMON:%.o=%_srv.o)
diff --git a/src/common/file_perm.c b/src/common/file_perm.c
new file mode 100644
index 0000000000..b490220b64
--- /dev/null
+++ b/src/common/file_perm.c
@@ -0,0 +1,48 @@
+/*-------------------------------------------------------------------------
+ *
+ * File and directory permission routines
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/file_perm.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#error "This file is not expected to be compiled for backend code"
+#endif
+
+#include <sys/stat.h>
+
+#include "common/file_perm.h"
+
+/*
+ * Determine the mask to use when writing to PGDATA by examining the mode of the
+ * PGDATA directory. If group read/execute are present in the PGDATA directory
+ * mode, the mask will be relaxed to allow group read/execute on all newly
+ * created files and directories.
+ *
+ * Errors are not handled here and should be checked by the frontend
+ * application.
+ */
+mode_t
+DataDirectoryMask(const char *dataDir)
+{
+ struct stat statBuf;
+
+ /*
+ * If an error occurs getting the mode then return the more restrictive
+ * mask. It is the reponsibility of the frontend application to generate an
+ * error if the PGDATA directory cannot be accessed.
+ */
+ if (stat(dataDir, &statBuf) != 0)
+ return PG_MODE_MASK_DEFAULT;
+
+ /*
+ * Construct the mask that the caller should pass to umask().
+ */
+ return PG_MODE_MASK_DEFAULT & ~statBuf.st_mode;
+}
diff --git a/src/include/common/file_perm.h b/src/include/common/file_perm.h
index 3c6da0e000..4266f7780a 100644
--- a/src/include/common/file_perm.h
+++ b/src/include/common/file_perm.h
@@ -1,6 +1,6 @@
/*-------------------------------------------------------------------------
*
- * File and directory permission constants
+ * File and directory permission constants and routines
*
*
* Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
@@ -13,6 +13,8 @@
#ifndef FILE_PERM_H
#define FILE_PERM_H
+#include <sys/stat.h>
+
/*
* Default mode mask for data directory permissions that does not allow
* group execute/read.
@@ -20,13 +22,36 @@
#define PG_MODE_MASK_DEFAULT (S_IRWXG | S_IRWXO)
/*
- * Default mode for created files.
+ * Optional mode mask for data directory permissions that allows group
+ * read/execute.
+ */
+#define PG_MODE_MASK_ALLOW_GROUP (S_IWGRP | S_IRWXO)
+
+/*
+ * Default mode for created files, unless something else is specified using
+ * the *Perm() function variants.
+ */
+#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR | S_IRGRP)
+
+/*
+ * Default mode for directories created with MakeDirectoryDefaultPerm().
*/
-#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR)
+#define PG_DIR_MODE_DEFAULT (S_IRWXU | S_IRGRP | S_IXGRP)
/*
- * Default mode for directories.
+ * Mode for directories that don't allow group access, even when it is enabled
+ * on PGDATA.
*/
-#define PG_DIR_MODE_DEFAULT S_IRWXU
+#define PG_DIR_MODE_NOGROUP S_IRWXU
+
+#ifdef FRONTEND
+
+/*
+ * Determine the mask to use when writing to PGDATA
+ */
+mode_t DataDirectoryMask(const char *dataDir);
+
+#endif /* FRONTEND */
+
#endif /* FILE_PERM_H */
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index a4574cd533..4d55b9d029 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -153,6 +153,7 @@ extern PGDLLIMPORT bool IsBinaryUpgrade;
extern PGDLLIMPORT bool ExitOnAnyError;
extern PGDLLIMPORT char *DataDir;
+extern PGDLLIMPORT int DataDirMode;
extern PGDLLIMPORT int NBuffers;
extern PGDLLIMPORT int MaxBackends;
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 76e571b98c..1bd91524d7 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -86,9 +86,11 @@ use Carp;
use Config;
use Cwd;
use Exporter 'import';
+use Fcntl qw(:mode);
use File::Basename;
use File::Path qw(rmtree);
use File::Spec;
+use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
use RecursiveCopy;
@@ -269,6 +271,26 @@ sub connstr
=pod
+=item $node->group_access()
+
+Does the data dir allow group access?
+
+=cut
+
+sub group_access
+{
+ my ($self) = @_;
+
+ my $dir_stat = stat($self->data_dir);
+
+ defined($dir_stat)
+ or die('unable to stat ' . $self->data_dir);
+
+ return (S_IMODE($dir_stat->mode) == 0750);
+}
+
+=pod
+
=item $node->data_dir()
Returns the path to the data directory. postgresql.conf and pg_hba.conf are
@@ -460,6 +482,9 @@ sub init
}
close $conf;
+ chmod($self->group_access ? 0640 : 0600, "$pgdata/postgresql.conf")
+ or die("unable to set permissions for $pgdata/postgresql.conf");
+
$self->set_replication_conf if $params{allows_streaming};
$self->enable_archiving if $params{has_archiving};
}
@@ -485,7 +510,7 @@ sub append_conf
TestLib::append_to_file($conffile, $str . "\n");
- chmod(0600, $conffile)
+ chmod($self->group_access() ? 0640 : 0600, $conffile)
or die("unable to set permissions for $conffile");
}
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index 93610e4bc4..8047404efd 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -31,6 +31,7 @@ our @EXPORT = qw(
slurp_file
append_to_file
check_mode_recursive
+ chmod_recursive
check_pg_config
system_or_bail
system_log
@@ -313,6 +314,30 @@ sub check_mode_recursive
return $result;
}
+# Change mode recursively on a directory
+sub chmod_recursive
+{
+ my ($dir, $dir_mode, $file_mode) = @_;
+
+ find
+ (
+ {follow_fast => 1,
+ wanted =>
+ sub
+ {
+ my $file_stat = stat($File::Find::name);
+
+ if (defined($file_stat))
+ {
+ chmod(S_ISDIR($file_stat->mode) ? $dir_mode : $file_mode,
+ $File::Find::name)
+ or die "unable to chmod $File::Find::name";
+ }
+ }},
+ $dir
+ );
+}
+
# Check presence of a given regexp within pg_config.h for the installation
# where tests are running, returning a match status result depending on
# that.
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index f3ec75f874..26e6fc090f 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -126,7 +126,7 @@ sub mkvcbuild
}
our @pgcommonfrontendfiles = (
- @pgcommonallfiles, qw(fe_memutils.c file_utils.c
+ @pgcommonallfiles, qw(fe_memutils.c file_perm.c file_utils.c
restricted_token.c));
our @pgcommonbkndfiles = @pgcommonallfiles;
--
2.14.3 (Apple Git-98)
On Fri, Mar 30, 2018 at 01:27:11PM -0400, David Steele wrote:
I have replaced data_directory_group_access with data_directory_mode.
That looks way better. Thanks for considering it.
I decided this made sense to do. It was only a few lines in initdb.c
using a very well established pattern. It would be surprising if log
files did not follow the mode of the rest of PGDATA after initdb -g,
even if it is standard practice to relocate them.
Okay for me.
@@ -285,7 +286,7 @@ dsm_impl_posix(dsm_op op, dsm_handle handle, Size request_size,
* returning.
*/
flags = O_RDWR | (op == DSM_OP_CREATE ? O_CREAT | O_EXCL : 0);
- if ((fd = shm_open(name, flags, 0600)) == -1)
+ if ((fd = shm_open(name, flags, PG_FILE_MODE_DEFAULT)) == -1)
Hm. Sorry for the extra noise. This one is actually incorrect as
shm_open will use the path specified by glibc, which is out of
pg_dynshmem so it does not matter for base backups, and we can keep
0600. pg_dymshmem is used for mmap, still this would map with the umask
setup by the postmaster as OpenTransientFile & friends are used. sysv
uses IPCProtection but there is no need to care about it as well. No
need for a comment perhaps..
pg_basebackup.c creates recovery.conf with 0600 all the time ;)
Except for those two nits, I am fine to pass down to a committer patch
number 1. This has value in my opinion per the refactoring it does and
the umask handling of pg_rewind and pg_resetwal added.
Now for patch 2...
+ <para>
+ If the data directory allows group read access then certificate files may
+ need to be located outside of the data directory in order to conform to the
+ security requirements outlined above. Generally, group access is enabled
+ to allow an unprivileged user to backup the database, and in that case the
+ backup software will not be able to read the certificate files and will
+ likely error.
+ </para
The answer is no for me and likely the same for others, but I am raising
the point for the archives. Should we relax
check_ssl_key_file_permissions() for group permissions by the way from
0600 to 0640 when the file is owned by the user running Postgres? If we
don't do that, then SSL private keys will need to be used with 0600,
potentially breaking backups... At the same time this reduces the
security of private keys but if the administrator is ready to accept
group permissions that should be a risk he is ready to accept?
+ &DataDirMode,
+ 0700, 0000, 0777,
+ NULL, NULL, show_data_directory_mode
Instead of a camel case, renaming that to data_directory_mode would be
nice to ease future greps. I do that all the time for existing code,
pesting when things are not consistent. A nit.
There is a noise diff in miscinit.c.
I am pretty sure that we want more documentation in pg_basebackup,
pg_rewind and pg_resetwal telling that those consider grouping access.
There is no need to include sys/stat.h as this is already part of
file_perm.h as DataDirectoryMask uses mode_t for its definition.
+/*
+ * 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 DataDirMode = PG_DIR_MODE_NOGROUP
/*
- * Default mode for directories.
+ * Default mode for created files, unless something else is specified using
+ * the *Perm() function variants.
*/
-#define PG_DIR_MODE_DEFAULT S_IRWXU
+#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR | S_IRGRP)
To reduce the code churn and simplify the logic, I would recommend to
not use variables which have a negative meaning, so PG_DIR_MODE_DEFAULT
should remain the same in patch 2, and there should be PG_DIR_MODE_GROUP
instead of PG_DIR_MODE_NOGROUP. That would be more consistent with the
file modes as well.
Yes, we can.
--
Michael
Hi Michael,
On 4/2/18 2:28 AM, Michael Paquier wrote:
@@ -285,7 +286,7 @@ dsm_impl_posix(dsm_op op, dsm_handle handle, Size request_size, * returning. */ flags = O_RDWR | (op == DSM_OP_CREATE ? O_CREAT | O_EXCL : 0); - if ((fd = shm_open(name, flags, 0600)) == -1) + if ((fd = shm_open(name, flags, PG_FILE_MODE_DEFAULT)) == -1)Hm. Sorry for the extra noise. This one is actually incorrect as
shm_open will use the path specified by glibc, which is out of
pg_dynshmem so it does not matter for base backups, and we can keep
0600. pg_dymshmem is used for mmap, still this would map with the umask
setup by the postmaster as OpenTransientFile & friends are used. sysv
uses IPCProtection but there is no need to care about it as well. No
need for a comment perhaps..
Yeah, I think I figured that out when I first went through the code but
managed to forget it. Reverted.
pg_basebackup.c creates recovery.conf with 0600 all the time ;)
Fixed.
+ <para> + If the data directory allows group read access then certificate files may + need to be located outside of the data directory in order to conform to the + security requirements outlined above. Generally, group access is enabled + to allow an unprivileged user to backup the database, and in that case the + backup software will not be able to read the certificate files and will + likely error. + </para The answer is no for me and likely the same for others, but I am raising the point for the archives. Should we relax check_ssl_key_file_permissions() for group permissions by the way from 0600 to 0640 when the file is owned by the user running Postgres? If we don't do that, then SSL private keys will need to be used with 0600, potentially breaking backups... At the same time this reduces the security of private keys but if the administrator is ready to accept group permissions that should be a risk he is ready to accept?
I feel this should be considered in a separate patch. These files are
not created by initdb so it seems to be an admin/packaging issue. There
is always the option to locate the certs outside the data directory and
some distros do that by default.
+ &DataDirMode, + 0700, 0000, 0777, + NULL, NULL, show_data_directory_mode Instead of a camel case, renaming that to data_directory_mode would be nice to ease future greps. I do that all the time for existing code, pesting when things are not consistent. A nit.
Done.
There is a noise diff in miscinit.c.
Fixed.
I am pretty sure that we want more documentation in pg_basebackup,
pg_rewind and pg_resetwal telling that those consider grouping access.
I think this makes sense for pg_basebackup, pg_receivewal, and
pg_recvlogical so I have added notes for those. Not sure I'm happy with
the language but at least we have something to bikeshed.
It seems to me that pg_resetwal and pg_rewind should be expected to not
break the permissions in PGDATA, just as they do now.
There is no need to include sys/stat.h as this is already part of
file_perm.h as DataDirectoryMask uses mode_t for its definition.
I removed it from file_perm.h instead. With the new variables (see
below), most call sites will have no need for the mode constants.
+/* + * 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 DataDirMode = PG_DIR_MODE_NOGROUP/* - * Default mode for directories. + * Default mode for created files, unless something else is specified using + * the *Perm() function variants. */ -#define PG_DIR_MODE_DEFAULT S_IRWXU +#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR | S_IRGRP) To reduce the code churn and simplify the logic, I would recommend to not use variables which have a negative meaning, so PG_DIR_MODE_DEFAULT should remain the same in patch 2, and there should be PG_DIR_MODE_GROUP instead of PG_DIR_MODE_NOGROUP. That would be more consistent with the file modes as well.
I decided that the constants were a bit confusing in general. What does
"default" mean, anyway? Instead I have created variables in file_perm.c
that hold the current file create mode, dir create mode, and mode mask.
All call sites use those variables (e.g. pg_dir_create_mode), which I
think are much clear.
This causes a bit of churn with the constants we added last September
but those were added for v11 so it won't create more complications for
back-patching.
Yes, we can.
Yes! We can!
New patches attached.
Thanks,
--
-David
david@pgmasters.net
Attachments:
group-access-v14-01-file-perm.patchtext/plain; charset=UTF-8; name=group-access-v14-01-file-perm.patch; x-mac-creator=0; x-mac-type=0Download
From f8c2604ebb6ae16b3b843c2db491d10efe2737fe Mon Sep 17 00:00:00 2001
From: David Steele <david@pgmasters.net>
Date: Wed, 4 Apr 2018 19:17:46 -0400
Subject: [PATCH 1/2] Refactor file permissions in backend/frontend
Adds a new header (file_perm.h) and makes all front-end utilities use the new constants for setting umask. Converts mkdir() calls in the backend to MakeDirectoryDefaultPerm() if the original call used default permissions. Adds tests to make sure permissions in PGDATA are set correctly by the front-end tools.
---
src/backend/access/transam/xlog.c | 2 +-
src/backend/commands/tablespace.c | 18 ++++---
src/backend/postmaster/postmaster.c | 5 +-
src/backend/postmaster/syslogger.c | 5 +-
src/backend/replication/basebackup.c | 5 +-
src/backend/replication/slot.c | 5 +-
src/backend/storage/file/copydir.c | 2 +-
src/backend/storage/file/fd.c | 38 +++++++++------
src/backend/storage/ipc/dsm_impl.c | 3 +-
src/backend/storage/ipc/ipc.c | 4 ++
src/backend/utils/init/miscinit.c | 5 +-
src/bin/initdb/initdb.c | 24 ++++-----
src/bin/initdb/t/001_initdb.pl | 4 +-
src/bin/pg_basebackup/pg_basebackup.c | 9 ++--
src/bin/pg_basebackup/t/010_pg_basebackup.pl | 9 +++-
src/bin/pg_basebackup/t/020_pg_receivewal.pl | 9 +++-
src/bin/pg_basebackup/walmethods.c | 6 ++-
src/bin/pg_ctl/pg_ctl.c | 4 +-
src/bin/pg_ctl/t/001_start_stop.pl | 13 +++--
src/bin/pg_resetwal/pg_resetwal.c | 5 +-
src/bin/pg_resetwal/t/001_basic.pl | 5 +-
src/bin/pg_rewind/RewindTest.pm | 4 ++
src/bin/pg_rewind/file_ops.c | 7 +--
src/bin/pg_rewind/t/001_basic.pl | 3 +-
src/bin/pg_upgrade/file.c | 5 +-
src/bin/pg_upgrade/pg_upgrade.c | 3 +-
src/bin/pg_upgrade/test.sh | 11 +++++
src/common/Makefile | 4 +-
src/common/file_perm.c | 19 ++++++++
src/include/common/file_perm.h | 32 ++++++++++++
src/include/storage/fd.h | 3 ++
src/test/perl/PostgresNode.pm | 3 ++
src/test/perl/TestLib.pm | 73 ++++++++++++++++++++++++++++
src/tools/msvc/Mkvcbuild.pm | 4 +-
34 files changed, 276 insertions(+), 75 deletions(-)
create mode 100644 src/common/file_perm.c
create mode 100644 src/include/common/file_perm.h
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index b4fd8395b7..5398d2f925 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4106,7 +4106,7 @@ ValidateXLOGDirectoryStructure(void)
{
ereport(LOG,
(errmsg("creating missing WAL directory \"%s\"", path)));
- if (mkdir(path, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(path) < 0)
ereport(FATAL,
(errmsg("could not create missing directory \"%s\": %m",
path)));
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 5c450caa4e..6449b71243 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -68,6 +68,7 @@
#include "commands/seclabel.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
+#include "common/file_perm.h"
#include "miscadmin.h"
#include "postmaster/bgwriter.h"
#include "storage/fd.h"
@@ -151,7 +152,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
else
{
/* Directory creation failed? */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(dir) < 0)
{
char *parentdir;
@@ -173,7 +174,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
get_parent_directory(parentdir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -184,7 +185,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
parentdir = pstrdup(dir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -192,7 +193,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
pfree(parentdir);
/* Create database directory */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(dir) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -279,7 +280,8 @@ CreateTableSpace(CreateTableSpaceStmt *stmt)
/*
* Check that location isn't too long. Remember that we're going to append
* 'PG_XXX/<dboid>/<relid>_<fork>.<nnn>'. FYI, we never actually
- * reference the whole path here, but mkdir() uses the first two parts.
+ * reference the whole path here, but MakeDirectoryDefaultPerm() uses the first
+ * two parts.
*/
if (strlen(location) + 1 + strlen(TABLESPACE_VERSION_DIRECTORY) + 1 +
OIDCHARS + 1 + OIDCHARS + 1 + FORKNAMECHARS + 1 + OIDCHARS > MAXPGPATH)
@@ -574,7 +576,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* Attempt to coerce target directory to safe permissions. If this fails,
* it doesn't exist or has the wrong owner.
*/
- if (chmod(location, S_IRWXU) != 0)
+ if (chmod(location, pg_dir_create_mode) != 0)
{
if (errno == ENOENT)
ereport(ERROR,
@@ -599,7 +601,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
if (stat(location_with_version_dir, &st) == 0 && S_ISDIR(st.st_mode))
{
if (!rmtree(location_with_version_dir, true))
- /* If this failed, mkdir() below is going to error. */
+ /* If this failed, MakeDirectoryDefaultPerm() below is going to error. */
ereport(WARNING,
(errmsg("some useless files may be left behind in old database directory \"%s\"",
location_with_version_dir)));
@@ -610,7 +612,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* The creation of the version directory prevents more than one tablespace
* in a single location.
*/
- if (mkdir(location_with_version_dir, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(location_with_version_dir) < 0)
{
if (errno == EEXIST)
ereport(ERROR,
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 660f3185e6..868fba8cac 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -97,6 +97,7 @@
#include "access/xlog.h"
#include "bootstrap/bootstrap.h"
#include "catalog/pg_control.h"
+#include "common/file_perm.h"
#include "common/ip.h"
#include "lib/ilist.h"
#include "libpq/auth.h"
@@ -589,7 +590,7 @@ PostmasterMain(int argc, char *argv[])
/*
* for security, no dir or file created can be group or other accessible
*/
- umask(S_IRWXG | S_IRWXO);
+ umask(PG_MODE_MASK_DEFAULT);
/*
* Initialize random(3) so we don't get the same values in every run.
@@ -4492,7 +4493,7 @@ internal_forkexec(int argc, char *argv[], Port *port)
* As in OpenTemporaryFileInTablespace, try to make the temp-file
* directory
*/
- mkdir(PG_TEMP_FILES_DIR, S_IRWXU);
+ MakeDirectoryDefaultPerm(PG_TEMP_FILES_DIR);
fp = AllocateFile(tmpfilename, PG_BINARY_W);
if (!fp)
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index f70eea37df..ae23941f59 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -41,6 +41,7 @@
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
#include "storage/dsm.h"
+#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pg_shmem.h"
@@ -322,7 +323,7 @@ SysLoggerMain(int argc, char *argv[])
/*
* Also, create new directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ MakeDirectoryDefaultPerm(Log_directory);
}
if (strcmp(Log_filename, currentLogFilename) != 0)
{
@@ -564,7 +565,7 @@ SysLogger_Start(void)
/*
* Create log directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ MakeDirectoryDefaultPerm(Log_directory);
/*
* The initial logfile is created right in the postmaster, to verify that
diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c
index 1a0bae4c15..bfbd1fd382 100644
--- a/src/backend/replication/basebackup.c
+++ b/src/backend/replication/basebackup.c
@@ -19,6 +19,7 @@
#include "access/xlog_internal.h" /* for pg_start/stop_backup */
#include "catalog/catalog.h"
#include "catalog/pg_type.h"
+#include "common/file_perm.h"
#include "lib/stringinfo.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
@@ -930,7 +931,7 @@ sendFileWithContent(const char *filename, const char *content)
statbuf.st_gid = getegid();
#endif
statbuf.st_mtime = time(NULL);
- statbuf.st_mode = S_IRUSR | S_IWUSR;
+ statbuf.st_mode = pg_file_create_mode;
statbuf.st_size = len;
_tarWriteHeader(filename, NULL, &statbuf, false);
@@ -1628,7 +1629,7 @@ _tarWriteDir(const char *pathbuf, int basepathlen, struct stat *statbuf,
#else
if (pgwin32_is_junction(pathbuf))
#endif
- statbuf->st_mode = S_IFDIR | S_IRWXU;
+ statbuf->st_mode = S_IFDIR | pg_dir_create_mode;
return _tarWriteHeader(pathbuf + basepathlen + 1, NULL, statbuf, sizeonly);
}
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index fc9ef22b0b..3a98360b50 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -1166,13 +1166,14 @@ CreateSlotOnDisk(ReplicationSlot *slot)
* It's just barely possible that some previous effort to create or drop a
* slot with this name left a temp directory lying around. If that seems
* to be the case, try to remove it. If the rmtree() fails, we'll error
- * out at the mkdir() below, so we don't bother checking success.
+ * out at the MakeDirectoryDefaultPerm() below, so we don't bother checking
+ * success.
*/
if (stat(tmppath, &st) == 0 && S_ISDIR(st.st_mode))
rmtree(tmppath, true);
/* Create and fsync the temporary slot directory. */
- if (mkdir(tmppath, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(tmppath) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
diff --git a/src/backend/storage/file/copydir.c b/src/backend/storage/file/copydir.c
index ca6342db0d..ff980dc1f5 100644
--- a/src/backend/storage/file/copydir.c
+++ b/src/backend/storage/file/copydir.c
@@ -41,7 +41,7 @@ copydir(char *fromdir, char *todir, bool recurse)
char fromfile[MAXPGPATH * 2];
char tofile[MAXPGPATH * 2];
- if (mkdir(todir, S_IRWXU) != 0)
+ if (MakeDirectoryDefaultPerm(todir) != 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m", todir)));
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index d30a725f90..ebea857e1e 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -84,6 +84,7 @@
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "catalog/pg_tablespace.h"
+#include "common/file_perm.h"
#include "pgstat.h"
#include "portability/mem.h"
#include "storage/fd.h"
@@ -124,12 +125,6 @@
*/
#define FD_MINFREE 10
-/*
- * Default mode for created files, unless something else is specified using
- * the *Perm() function variants.
- */
-#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR)
-
/*
* A number of platforms allow individual processes to open many more files
* than they can really support when *many* processes do the same thing.
@@ -937,7 +932,7 @@ set_max_safe_fds(void)
int
BasicOpenFile(const char *fileName, int fileFlags)
{
- return BasicOpenFilePerm(fileName, fileFlags, PG_FILE_MODE_DEFAULT);
+ return BasicOpenFilePerm(fileName, fileFlags, pg_file_create_mode);
}
/*
@@ -1356,7 +1351,7 @@ FileInvalidate(File file)
File
PathNameOpenFile(const char *fileName, int fileFlags)
{
- return PathNameOpenFilePerm(fileName, fileFlags, PG_FILE_MODE_DEFAULT);
+ return PathNameOpenFilePerm(fileName, fileFlags, pg_file_create_mode);
}
/*
@@ -1434,7 +1429,7 @@ PathNameOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode)
void
PathNameCreateTemporaryDir(const char *basedir, const char *directory)
{
- if (mkdir(directory, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(directory) < 0)
{
if (errno == EEXIST)
return;
@@ -1444,14 +1439,14 @@ PathNameCreateTemporaryDir(const char *basedir, const char *directory)
* EEXIST to close a race against another process following the same
* algorithm.
*/
- if (mkdir(basedir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(basedir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary directory \"%s\": %m",
basedir)));
/* Try again. */
- if (mkdir(directory, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(directory) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary subdirectory \"%s\": %m",
@@ -1601,11 +1596,11 @@ OpenTemporaryFileInTablespace(Oid tblspcOid, bool rejectError)
* We might need to create the tablespace's tempfile directory, if no
* one has yet done so.
*
- * Don't check for error from mkdir; it could fail if someone else
- * just did the same thing. If it doesn't work then we'll bomb out on
+ * Don't check error from MakeDirectoryDefaultPerm; it could fail if someone
+ * else just did the same thing. If it doesn't work then we'll bomb out on
* the second create attempt, instead.
*/
- mkdir(tempdirpath, S_IRWXU);
+ MakeDirectoryDefaultPerm(tempdirpath);
file = PathNameOpenFile(tempfilepath,
O_RDWR | O_CREAT | O_TRUNC | PG_BINARY);
@@ -2401,7 +2396,7 @@ TryAgain:
int
OpenTransientFile(const char *fileName, int fileFlags)
{
- return OpenTransientFilePerm(fileName, fileFlags, PG_FILE_MODE_DEFAULT);
+ return OpenTransientFilePerm(fileName, fileFlags, pg_file_create_mode);
}
/*
@@ -3554,3 +3549,16 @@ fsync_parent_path(const char *fname, int elevel)
return 0;
}
+
+/*
+ * Create a directory using pg_dir_create_mode for permissions
+ *
+ * Directories in PGDATA should normally have specific permissions -- when
+ * using this function the caller does not need to know what they should be.
+ * For permissions other than the default use mkdir() directly.
+ */
+int
+MakeDirectoryDefaultPerm(const char *directoryName)
+{
+ return mkdir(directoryName, pg_dir_create_mode);
+}
diff --git a/src/backend/storage/ipc/dsm_impl.c b/src/backend/storage/ipc/dsm_impl.c
index 67e76b98fe..0ba45b4f93 100644
--- a/src/backend/storage/ipc/dsm_impl.c
+++ b/src/backend/storage/ipc/dsm_impl.c
@@ -60,6 +60,7 @@
#ifdef HAVE_SYS_SHM_H
#include <sys/shm.h>
#endif
+#include "common/file_perm.h"
#include "pgstat.h"
#include "portability/mem.h"
@@ -285,7 +286,7 @@ dsm_impl_posix(dsm_op op, dsm_handle handle, Size request_size,
* returning.
*/
flags = O_RDWR | (op == DSM_OP_CREATE ? O_CREAT | O_EXCL : 0);
- if ((fd = shm_open(name, flags, 0600)) == -1)
+ if ((fd = shm_open(name, flags, PG_FILE_MODE_DEFAULT)) == -1)
{
if (errno != EEXIST)
ereport(elevel,
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index fc0a9c0756..4f2370a027 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -137,6 +137,10 @@ proc_exit(int code)
else
snprintf(gprofDirName, 32, "gprof/%d", (int) getpid());
+ /*
+ * Use mkdir() instead of MakeDirectoryDefaultPerm() here because non-default
+ * permissions are required.
+ */
mkdir("gprof", S_IRWXU | S_IRWXG | S_IRWXO);
mkdir(gprofDirName, S_IRWXU | S_IRWXG | S_IRWXO);
chdir(gprofDirName);
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 87ed7d3f71..f8f08f3f88 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -32,6 +32,7 @@
#include "access/htup_details.h"
#include "catalog/pg_authid.h"
+#include "common/file_perm.h"
#include "libpq/libpq.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
@@ -831,7 +832,7 @@ CreateLockFile(const char *filename, bool amPostmaster,
* Think not to make the file protection weaker than 0600. See
* comments below.
*/
- fd = open(filename, O_RDWR | O_CREAT | O_EXCL, 0600);
+ fd = open(filename, O_RDWR | O_CREAT | O_EXCL, pg_file_create_mode);
if (fd >= 0)
break; /* Success; exit the retry loop */
@@ -848,7 +849,7 @@ CreateLockFile(const char *filename, bool amPostmaster,
* Read the file to get the old owner's PID. Note race condition
* here: file might have been deleted since we tried to create it.
*/
- fd = open(filename, O_RDONLY, 0600);
+ fd = open(filename, O_RDONLY, pg_file_create_mode);
if (fd < 0)
{
if (errno == ENOENT)
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 78990f5a27..83472d9ceb 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -64,6 +64,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_class.h"
#include "catalog/pg_collation.h"
+#include "common/file_perm.h"
#include "common/file_utils.h"
#include "common/restricted_token.h"
#include "common/username.h"
@@ -1170,7 +1171,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, pg_file_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1190,7 +1191,7 @@ setup_config(void)
sprintf(path, "%s/postgresql.auto.conf", pg_data);
writefile(path, autoconflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, pg_file_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1277,7 +1278,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_hba.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, pg_file_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1293,7 +1294,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_ident.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, pg_file_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -2692,7 +2693,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (pg_mkdir_p(pg_data, S_IRWXU) != 0)
+ if (pg_mkdir_p(pg_data, pg_dir_create_mode) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2710,7 +2711,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (chmod(pg_data, S_IRWXU) != 0)
+ if (chmod(pg_data, pg_dir_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2778,7 +2779,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (pg_mkdir_p(xlog_dir, S_IRWXU) != 0)
+ if (pg_mkdir_p(xlog_dir, pg_dir_create_mode) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2796,7 +2797,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (chmod(xlog_dir, S_IRWXU) != 0)
+ if (chmod(xlog_dir, pg_dir_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2846,7 +2847,7 @@ create_xlog_or_symlink(void)
else
{
/* Without -X option, just make the subdirectory normally */
- if (mkdir(subdirloc, S_IRWXU) < 0)
+ if (mkdir(subdirloc, pg_dir_create_mode) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, subdirloc, strerror(errno));
@@ -2882,7 +2883,8 @@ initialize_data_directory(void)
setup_signals();
- umask(S_IRWXG | S_IRWXO);
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_DEFAULT);
create_data_directory();
@@ -2902,7 +2904,7 @@ initialize_data_directory(void)
* The parent directory already exists, so we only need mkdir() not
* pg_mkdir_p() here, which avoids some failure modes; cf bug #13853.
*/
- if (mkdir(path, S_IRWXU) < 0)
+ if (mkdir(path, pg_dir_create_mode) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, path, strerror(errno));
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index c0cfa6e92c..3331560445 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 15;
+use Test::More tests => 16;
my $tempdir = TestLib::tempdir;
my $xlogdir = "$tempdir/pgxlog";
@@ -45,6 +45,8 @@ mkdir $datadir;
command_ok([ 'initdb', '-N', '-T', 'german', '-X', $xlogdir, $datadir ],
'successful creation');
+
+ ok(check_mode_recursive($datadir, 0700, 0600));
}
command_ok([ 'initdb', '-S', $datadir ], 'sync only');
command_fails([ 'initdb', $datadir ], 'existing data directory');
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 8610fe8a1a..32b41e313c 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -27,6 +27,7 @@
#endif
#include "access/xlog_internal.h"
+#include "common/file_perm.h"
#include "common/file_utils.h"
#include "common/string.h"
#include "fe_utils/string_utils.h"
@@ -629,7 +630,7 @@ StartLogStreamer(char *startpos, uint32 timeline, char *sysidentifier)
PQserverVersion(conn) < MINIMUM_VERSION_FOR_PG_WAL ?
"pg_xlog" : "pg_wal");
- if (pg_mkdir_p(statusdir, S_IRWXU) != 0 && errno != EEXIST)
+ if (pg_mkdir_p(statusdir, pg_dir_create_mode) != 0 && errno != EEXIST)
{
fprintf(stderr,
_("%s: could not create directory \"%s\": %s\n"),
@@ -685,7 +686,7 @@ verify_dir_is_empty_or_create(char *dirname, bool *created, bool *found)
/*
* Does not exist, so create
*/
- if (pg_mkdir_p(dirname, S_IRWXU) == -1)
+ if (pg_mkdir_p(dirname, pg_dir_create_mode) == -1)
{
fprintf(stderr,
_("%s: could not create directory \"%s\": %s\n"),
@@ -1129,7 +1130,7 @@ ReceiveTarFile(PGconn *conn, PGresult *res, int rownum)
tarCreateHeader(header, "recovery.conf", NULL,
recoveryconfcontents->len,
- 0600, 04000, 02000,
+ pg_file_create_mode, 04000, 02000,
time(NULL));
padding = ((recoveryconfcontents->len + 511) & ~511) - recoveryconfcontents->len;
@@ -1441,7 +1442,7 @@ ReceiveAndUnpackTarFile(PGconn *conn, PGresult *res, int rownum)
* Directory
*/
filename[strlen(filename) - 1] = '\0'; /* Remove trailing slash */
- if (mkdir(filename, S_IRWXU) != 0)
+ if (mkdir(filename, pg_dir_create_mode) != 0)
{
/*
* When streaming WAL, pg_wal (or pg_xlog for pre-9.6
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index e2f1465472..496c22da7d 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -5,7 +5,7 @@ use Config;
use File::Basename qw(basename dirname);
use PostgresNode;
use TestLib;
-use Test::More tests => 104;
+use Test::More tests => 105;
program_help_ok('pg_basebackup');
program_version_ok('pg_basebackup');
@@ -15,6 +15,9 @@ my $tempdir = TestLib::tempdir;
my $node = get_new_node('main');
+# Set umask so no test directories or files are created with group permissions
+umask (0077);
+
# Initialize node without replication settings
$node->init(extra => [ '--data-checksums' ]);
$node->start;
@@ -93,6 +96,10 @@ $node->command_ok([ 'pg_basebackup', '-D', "$tempdir/backup", '-X', 'none' ],
'pg_basebackup runs');
ok(-f "$tempdir/backup/PG_VERSION", 'backup was created');
+# Group access should not be enabled on backup
+ok(check_mode_recursive("$tempdir/backup", 0700, 0600),
+ "check backup dir permissions");
+
# Only archive_status directory should be copied in pg_wal/.
is_deeply(
[ sort(slurp_dir("$tempdir/backup/pg_wal/")) ],
diff --git a/src/bin/pg_basebackup/t/020_pg_receivewal.pl b/src/bin/pg_basebackup/t/020_pg_receivewal.pl
index 64e3a35a87..d155d5b711 100644
--- a/src/bin/pg_basebackup/t/020_pg_receivewal.pl
+++ b/src/bin/pg_basebackup/t/020_pg_receivewal.pl
@@ -2,12 +2,15 @@ use strict;
use warnings;
use TestLib;
use PostgresNode;
-use Test::More tests => 18;
+use Test::More tests => 19;
program_help_ok('pg_receivewal');
program_version_ok('pg_receivewal');
program_options_handling_ok('pg_receivewal');
+# Set umask so no test directories or files are created with group permissions
+umask (0077);
+
my $primary = get_new_node('primary');
$primary->init(allows_streaming => 1);
$primary->start;
@@ -56,3 +59,7 @@ $primary->command_ok(
[ 'pg_receivewal', '-D', $stream_dir, '--verbose',
'--endpos', $nextlsn, '--synchronous', '--no-loop' ],
'streaming some WAL with --synchronous');
+
+# Group access should not be enabled on WAL files
+ok(check_mode_recursive($stream_dir, 0700, 0600),
+ "check stream dir permissions");
diff --git a/src/bin/pg_basebackup/walmethods.c b/src/bin/pg_basebackup/walmethods.c
index b4558a0184..267a40debb 100644
--- a/src/bin/pg_basebackup/walmethods.c
+++ b/src/bin/pg_basebackup/walmethods.c
@@ -22,6 +22,7 @@
#endif
#include "pgtar.h"
+#include "common/file_perm.h"
#include "common/file_utils.h"
#include "receivelog.h"
@@ -89,7 +90,7 @@ dir_open_for_write(const char *pathname, const char *temp_suffix, size_t pad_to_
* does not do any system calls to fsync() to make changes permanent on
* disk.
*/
- fd = open(tmppath, O_WRONLY | O_CREAT | PG_BINARY, S_IRUSR | S_IWUSR);
+ fd = open(tmppath, O_WRONLY | O_CREAT | PG_BINARY, pg_file_create_mode);
if (fd < 0)
return NULL;
@@ -534,7 +535,8 @@ tar_open_for_write(const char *pathname, const char *temp_suffix, size_t pad_to_
* We open the tar file only when we first try to write to it.
*/
tar_data->fd = open(tar_data->tarfilename,
- O_WRONLY | O_CREAT | PG_BINARY, S_IRUSR | S_IWUSR);
+ O_WRONLY | O_CREAT | PG_BINARY,
+ pg_file_create_mode);
if (tar_data->fd < 0)
return NULL;
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index 9bc830b085..e83a7179ea 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -25,6 +25,7 @@
#include "catalog/pg_control.h"
#include "common/controldata_utils.h"
+#include "common/file_perm.h"
#include "getopt_long.h"
#include "utils/pidfile.h"
@@ -2170,7 +2171,8 @@ main(int argc, char **argv)
*/
argv0 = argv[0];
- umask(S_IRWXG | S_IRWXO);
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_DEFAULT);
/* support --help and --version even if invoked as root */
if (argc > 1)
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl
index 5da4746cb4..3d82abe696 100644
--- a/src/bin/pg_ctl/t/001_start_stop.pl
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -4,7 +4,7 @@ use warnings;
use Config;
use PostgresNode;
use TestLib;
-use Test::More tests => 19;
+use Test::More tests => 20;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -57,10 +57,15 @@ command_ok([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ], 'pg_ctl stop');
command_fails([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ],
'second pg_ctl stop fails');
+# Log file for first perm test
+my $logFileName = "$tempdir/data/perm-test-600.log";
+
command_ok(
- [ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
+ [ 'pg_ctl', 'restart', '-D', "$tempdir/data", '-l', $logFileName ],
'pg_ctl restart with server not running');
-command_ok([ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
- 'pg_ctl restart with server running');
+
+# Log file should exist and have no group permissions
+ok(-f $logFileName);
+ok(check_mode_recursive("$tempdir/data", 0700, 0600));
system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index eba7c5fdee..bdf71886ee 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -52,6 +52,7 @@
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
#include "common/fe_memutils.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "storage/large_object.h"
#include "pg_getopt.h"
@@ -967,7 +968,7 @@ RewriteControlFile(void)
fd = open(XLOG_CONTROL_FILE,
O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ pg_file_create_mode);
if (fd < 0)
{
fprintf(stderr, _("%s: could not create pg_control file: %s\n"),
@@ -1249,7 +1250,7 @@ WriteEmptyXLOG(void)
unlink(path);
fd = open(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ pg_file_create_mode);
if (fd < 0)
{
fprintf(stderr, _("%s: could not open file \"%s\": %s\n"),
diff --git a/src/bin/pg_resetwal/t/001_basic.pl b/src/bin/pg_resetwal/t/001_basic.pl
index 1b157cb555..7a68a00638 100644
--- a/src/bin/pg_resetwal/t/001_basic.pl
+++ b/src/bin/pg_resetwal/t/001_basic.pl
@@ -3,7 +3,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 11;
+use Test::More tests => 12;
program_help_ok('pg_resetwal');
program_version_ok('pg_resetwal');
@@ -15,3 +15,6 @@ $node->init;
command_like([ 'pg_resetwal', '-n', $node->data_dir ],
qr/checkpoint/,
'pg_resetwal -n produces output');
+
+ok(check_mode_recursive(
+ $node->data_dir, 0700, 0600), 'check PGDATA permissions');
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index 00b5b42dd7..7b632c7dcd 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -237,6 +237,10 @@ sub run_pg_rewind
"$tmp_folder/master-postgresql.conf.tmp",
"$master_pgdata/postgresql.conf");
+ chmod(0600, "$master_pgdata/postgresql.conf")
+ or BAIL_OUT(
+ "unable to set permissions for $master_pgdata/postgresql.conf");
+
# Plug-in rewound node to the now-promoted standby node
my $port_standby = $node_standby->port;
$node_master->append_conf(
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index f491ed7f5c..94bcc13ae8 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -18,6 +18,7 @@
#include <fcntl.h>
#include <unistd.h>
+#include "common/file_perm.h"
#include "file_ops.h"
#include "filemap.h"
#include "logging.h"
@@ -57,7 +58,7 @@ open_target_file(const char *path, bool trunc)
mode = O_WRONLY | O_CREAT | PG_BINARY;
if (trunc)
mode |= O_TRUNC;
- dstfd = open(dstpath, mode, 0600);
+ dstfd = open(dstpath, mode, pg_file_create_mode);
if (dstfd < 0)
pg_fatal("could not open target file \"%s\": %s\n",
dstpath, strerror(errno));
@@ -198,7 +199,7 @@ truncate_target_file(const char *path, off_t newsize)
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
- fd = open(dstpath, O_WRONLY, 0);
+ fd = open(dstpath, O_WRONLY, pg_file_create_mode);
if (fd < 0)
pg_fatal("could not open file \"%s\" for truncation: %s\n",
dstpath, strerror(errno));
@@ -219,7 +220,7 @@ create_target_dir(const char *path)
return;
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
- if (mkdir(dstpath, S_IRWXU) != 0)
+ if (mkdir(dstpath, pg_dir_create_mode) != 0)
pg_fatal("could not create directory \"%s\": %s\n",
dstpath, strerror(errno));
}
diff --git a/src/bin/pg_rewind/t/001_basic.pl b/src/bin/pg_rewind/t/001_basic.pl
index 736f34eae3..ae7b116532 100644
--- a/src/bin/pg_rewind/t/001_basic.pl
+++ b/src/bin/pg_rewind/t/001_basic.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 8;
+use Test::More tests => 10;
use RewindTest;
@@ -86,6 +86,7 @@ in master, before promotion
),
'tail-copy');
+ ok(check_mode_recursive($node_master->data_dir(), 0700, 0600));
RewindTest::clean_rewind_test();
}
diff --git a/src/bin/pg_upgrade/file.c b/src/bin/pg_upgrade/file.c
index f38bfacf02..f68211aa20 100644
--- a/src/bin/pg_upgrade/file.c
+++ b/src/bin/pg_upgrade/file.c
@@ -10,6 +10,7 @@
#include "postgres_fe.h"
#include "access/visibilitymap.h"
+#include "common/file_perm.h"
#include "pg_upgrade.h"
#include "storage/bufpage.h"
#include "storage/checksum.h"
@@ -44,7 +45,7 @@ copyFile(const char *src, const char *dst,
schemaName, relName, src, strerror(errno));
if ((dest_fd = open(dst, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR)) < 0)
+ pg_file_create_mode)) < 0)
pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
schemaName, relName, dst, strerror(errno));
@@ -151,7 +152,7 @@ rewriteVisibilityMap(const char *fromfile, const char *tofile,
schemaName, relName, fromfile, strerror(errno));
if ((dst_fd = open(tofile, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR)) < 0)
+ pg_file_create_mode)) < 0)
pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
schemaName, relName, tofile, strerror(errno));
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index d12412799f..59df6fd88f 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -38,6 +38,7 @@
#include "pg_upgrade.h"
#include "catalog/pg_class.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "fe_utils/string_utils.h"
@@ -79,7 +80,7 @@ main(int argc, char **argv)
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_upgrade"));
/* Ensure that all files created by pg_upgrade are non-world-readable */
- umask(S_IRWXG | S_IRWXO);
+ umask(PG_MODE_MASK_DEFAULT);
parseCommandLine(argc, argv);
diff --git a/src/bin/pg_upgrade/test.sh b/src/bin/pg_upgrade/test.sh
index 39983abea1..574639d47e 100644
--- a/src/bin/pg_upgrade/test.sh
+++ b/src/bin/pg_upgrade/test.sh
@@ -230,6 +230,17 @@ standard_initdb 'initdb'
pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "${PGDATA}" -b "$oldbindir" -B "$bindir" -p "$PGPORT" -P "$PGPORT"
+# make sure all directories and files have correct permissions
+if [ $(find ${PGDATA} -type f ! -perm 600 | wc -l) -ne 0 ]; then
+ echo "files in PGDATA with permission != 600";
+ exit 1;
+fi
+
+if [ $(find ${PGDATA} -type d ! -perm 700 | wc -l) -ne 0 ]; then
+ echo "directories in PGDATA with permission != 700";
+ exit 1;
+fi
+
pg_ctl start -l "$logdir/postmaster2.log" -o "$POSTMASTER_OPTS" -w
case $testhost in
diff --git a/src/common/Makefile b/src/common/Makefile
index 873fbb6438..e9e75867f3 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -40,8 +40,8 @@ override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
override CPPFLAGS := -DFRONTEND $(CPPFLAGS)
LIBS += $(PTHREAD_LIBS)
-OBJS_COMMON = base64.o config_info.o controldata_utils.o exec.o ip.o \
- keywords.o md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
+OBJS_COMMON = base64.o config_info.o controldata_utils.o exec.o file_perm.o \
+ ip.o keywords.o md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
rmtree.o saslprep.o scram-common.o string.o unicode_norm.o \
username.o wait_error.o
diff --git a/src/common/file_perm.c b/src/common/file_perm.c
new file mode 100644
index 0000000000..5f0c02fb04
--- /dev/null
+++ b/src/common/file_perm.c
@@ -0,0 +1,19 @@
+/*-------------------------------------------------------------------------
+ *
+ * File and directory permission routines
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/file_perm.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include <sys/stat.h>
+
+#include "common/file_perm.h"
+
+/* Modes for creating directories and files in the data directory */
+int pg_dir_create_mode = PG_DIR_MODE_DEFAULT;
+int pg_file_create_mode = PG_FILE_MODE_DEFAULT;
diff --git a/src/include/common/file_perm.h b/src/include/common/file_perm.h
new file mode 100644
index 0000000000..46b91f0c7b
--- /dev/null
+++ b/src/include/common/file_perm.h
@@ -0,0 +1,32 @@
+/*-------------------------------------------------------------------------
+ *
+ * File and directory permission constants
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/file_perm.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FILE_PERM_H
+#define FILE_PERM_H
+
+/*
+ * Default mode mask for data directory permissions only allows the owner to
+ * read/write directories and files.
+ */
+#define PG_MODE_MASK_DEFAULT (S_IRWXG | S_IRWXO)
+
+/* Default mode for creating directories */
+#define PG_DIR_MODE_DEFAULT S_IRWXU
+
+/* Default mode for creating files */
+#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR)
+
+/* Modes for creating directories and files in the data directory */
+extern int pg_dir_create_mode;
+extern int pg_file_create_mode;
+
+#endif /* FILE_PERM_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index e49b42ce86..340dbd4d3b 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -112,6 +112,9 @@ extern int CloseTransientFile(int fd);
extern int BasicOpenFile(const char *fileName, int fileFlags);
extern int BasicOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode);
+ /* Make a directory with default permissions */
+extern int MakeDirectoryDefaultPerm(const char *directoryName);
+
/* Miscellaneous support routines */
extern void InitFileAccess(void);
extern void set_max_safe_fds(void);
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 80188315f1..76e571b98c 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -484,6 +484,9 @@ sub append_conf
my $conffile = $self->data_dir . '/' . $filename;
TestLib::append_to_file($conffile, $str . "\n");
+
+ chmod(0600, $conffile)
+ or die("unable to set permissions for $conffile");
}
=pod
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index b6862688d4..93610e4bc4 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -13,8 +13,11 @@ use warnings;
use Config;
use Cwd;
use Exporter 'import';
+use Fcntl qw(:mode);
use File::Basename;
+use File::Find;
use File::Spec;
+use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
use SimpleTee;
@@ -27,6 +30,7 @@ our @EXPORT = qw(
slurp_dir
slurp_file
append_to_file
+ check_mode_recursive
check_pg_config
system_or_bail
system_log
@@ -240,6 +244,75 @@ sub append_to_file
close $fh;
}
+# Check that all file/dir modes in a directory match the expected values,
+# ignoring the mode of any specified files.
+sub check_mode_recursive
+{
+ my ($dir, $expected_dir_mode, $expected_file_mode, $ignore_list) = @_;
+
+ # Result defaults to true
+ my $result = 1;
+
+ find
+ (
+ {follow_fast => 1,
+ wanted =>
+ sub
+ {
+ my $file_stat = stat($File::Find::name);
+
+ # Is file in the ignore list?
+ foreach my $ignore ($ignore_list ? @{$ignore_list} : [])
+ {
+ if ("$dir/$ignore" eq $File::Find::name)
+ {
+ return;
+ }
+ }
+
+ defined($file_stat)
+ or die("unable to stat $File::Find::name");
+
+ my $file_mode = S_IMODE($file_stat->mode);
+
+ # Is this a file?
+ if (S_ISREG($file_stat->mode))
+ {
+ if ($file_mode != $expected_file_mode)
+ {
+ print(*STDERR,
+ sprintf("$File::Find::name mode must be %04o\n",
+ $expected_file_mode));
+
+ $result = 0;
+ return;
+ }
+ }
+ # Else a directory?
+ elsif (S_ISDIR($file_stat->mode))
+ {
+ if ($file_mode != $expected_dir_mode)
+ {
+ print(*STDERR,
+ sprintf("$File::Find::name mode must be %04o\n",
+ $expected_dir_mode));
+
+ $result = 0;
+ return;
+ }
+ }
+ # Else something we can't handle
+ else
+ {
+ die "unknown file type for $File::Find::name";
+ }
+ }},
+ $dir
+ );
+
+ return $result;
+}
+
# Check presence of a given regexp within pg_config.h for the installation
# where tests are running, returning a match status result depending on
# that.
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 41d720880a..1d3ed6b0b1 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -111,8 +111,8 @@ sub mkvcbuild
}
our @pgcommonallfiles = qw(
- base64.c config_info.c controldata_utils.c exec.c ip.c keywords.c
- md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
+ base64.c config_info.c controldata_utils.c exec.c file_perm.c ip.c
+ keywords.c md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
saslprep.c scram-common.c string.c unicode_norm.c username.c
wait_error.c);
--
2.14.3 (Apple Git-98)
group-access-v14-02-group.patchtext/plain; charset=UTF-8; name=group-access-v14-02-group.patch; x-mac-creator=0; x-mac-type=0Download
From 8922c26e6a78168284f42489af1a28a01104b635 Mon Sep 17 00:00:00 2001
From: David Steele <david@pgmasters.net>
Date: Wed, 4 Apr 2018 19:23:08 -0400
Subject: [PATCH 2/2] Allow group access on PGDATA.
This includes backend changes, utility changes (initdb, pg_ctl, pg_upgrade, etc.) and tests for each utility.
---
doc/src/sgml/ref/initdb.sgml | 19 ++++++++
doc/src/sgml/ref/pg_basebackup.sgml | 6 +++
doc/src/sgml/ref/pg_receivewal.sgml | 6 +++
doc/src/sgml/ref/pg_recvlogical.sgml | 11 +++++
doc/src/sgml/runtime.sgml | 14 +++++-
src/backend/bootstrap/bootstrap.c | 7 ++-
src/backend/main/main.c | 1 +
src/backend/postmaster/postmaster.c | 44 ++++++++++++++----
src/backend/tcop/postgres.c | 8 +++-
src/backend/utils/init/globals.c | 9 ++++
src/backend/utils/init/miscinit.c | 21 ++++-----
src/backend/utils/misc/guc.c | 25 ++++++++++
src/bin/initdb/initdb.c | 32 ++++++++++---
src/bin/initdb/t/001_initdb.pl | 13 +++++-
src/bin/pg_basebackup/pg_basebackup.c | 22 +++++----
src/bin/pg_basebackup/pg_receivewal.c | 7 +++
src/bin/pg_basebackup/pg_recvlogical.c | 7 +++
src/bin/pg_basebackup/streamutil.c | 69 ++++++++++++++++++++++++++++
src/bin/pg_basebackup/t/010_pg_basebackup.pl | 28 +++++++----
src/bin/pg_basebackup/t/020_pg_receivewal.pl | 2 +-
src/bin/pg_ctl/pg_ctl.c | 12 ++++-
src/bin/pg_ctl/t/001_start_stop.pl | 24 +++++++++-
src/bin/pg_resetwal/pg_resetwal.c | 10 ++++
src/bin/pg_rewind/RewindTest.pm | 9 ++--
src/bin/pg_rewind/pg_rewind.c | 11 +++++
src/bin/pg_rewind/t/002_databases.pl | 6 ++-
src/bin/pg_upgrade/pg_upgrade.c | 12 ++++-
src/bin/pg_upgrade/test.sh | 16 +++----
src/common/Makefile | 2 +-
src/common/file_perm.c | 68 +++++++++++++++++++++++++++
src/include/common/file_perm.h | 20 ++++++++
src/include/miscadmin.h | 1 +
src/test/perl/PostgresNode.pm | 27 ++++++++++-
src/test/perl/TestLib.pm | 25 ++++++++++
src/tools/msvc/Mkvcbuild.pm | 2 +-
35 files changed, 528 insertions(+), 68 deletions(-)
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 949b5a220f..1d41e91794 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -76,6 +76,14 @@ PostgreSQL documentation
to do so.)
</para>
+ <para>
+ For security reasons the new cluster created by <command>initdb</command>
+ will only be accessible by the cluster owner by default. The
+ <option>--allow-group-access</option> option allows any user in the same
+ group as the cluster owner to read files in the cluster. This is useful
+ for performing backups as a non-privileged user.
+ </para>
+
<para>
<command>initdb</command> initializes the database cluster's default
locale and character set encoding. The character set encoding,
@@ -301,6 +309,17 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-g</option></term>
+ <term><option>--allow-group-access</option></term>
+ <listitem>
+ <para>
+ Allows users in the same group as the cluster owner to read all cluster
+ files created by <command>initdb</command>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-W</option></term>
<term><option>--pwprompt</option></term>
diff --git a/doc/src/sgml/ref/pg_basebackup.sgml b/doc/src/sgml/ref/pg_basebackup.sgml
index 95045669c9..fc1edf4864 100644
--- a/doc/src/sgml/ref/pg_basebackup.sgml
+++ b/doc/src/sgml/ref/pg_basebackup.sgml
@@ -737,6 +737,12 @@ PostgreSQL documentation
or later.
</para>
+ <para>
+ <application>pg_basebackup</application> will preserve group permissions in
+ both the <literal>plain</literal> and <literal>tar</literal> formats if group
+ permissions are enabled on the source cluster.
+ </para>
+
</refsect1>
<refsect1>
diff --git a/doc/src/sgml/ref/pg_receivewal.sgml b/doc/src/sgml/ref/pg_receivewal.sgml
index e3f2ce1fcb..a18ddd4bff 100644
--- a/doc/src/sgml/ref/pg_receivewal.sgml
+++ b/doc/src/sgml/ref/pg_receivewal.sgml
@@ -425,6 +425,12 @@ PostgreSQL documentation
not keep up with fetching the WAL data.
</para>
+ <para>
+ <application>pg_receivewal</application> will preserve group permissions on
+ the received WAL files if group permissions are enabled on the source
+ cluster.
+ </para>
+
</refsect1>
<refsect1>
diff --git a/doc/src/sgml/ref/pg_recvlogical.sgml b/doc/src/sgml/ref/pg_recvlogical.sgml
index a79ca20084..141c5cddce 100644
--- a/doc/src/sgml/ref/pg_recvlogical.sgml
+++ b/doc/src/sgml/ref/pg_recvlogical.sgml
@@ -399,6 +399,17 @@ PostgreSQL documentation
</para>
</refsect1>
+ <refsect1>
+ <title>Notes</title>
+
+ <para>
+ <application>pg_recvlogical</application> will preserve group permissions on
+ the received WAL files if group permissions are enabled on the source
+ cluster.
+ </para>
+
+ </refsect1>
+
<refsect1>
<title>Examples</title>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 587b430527..40ce3d9813 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -137,7 +137,10 @@ postgres$ <userinput>initdb -D /usr/local/pgsql/data</userinput>
database, it is essential that it be secured from unauthorized
access. <command>initdb</command> therefore revokes access
permissions from everyone but the
- <productname>PostgreSQL</productname> user.
+ <productname>PostgreSQL</productname> user, and optionally, group.
+ Group access, when enabled, is read-only. This allows an unprivileged
+ user in the <productname>PostgreSQL</productname> group to take a backup of
+ the cluster data or perform other operations that only require read access.
</para>
<para>
@@ -2194,6 +2197,15 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
member of the group that has access to those certificate and key files.
</para>
+ <para>
+ If the data directory allows group read access then certificate files may
+ need to be located outside of the data directory in order to conform to the
+ security requirements outlined above. Generally, group access is enabled
+ to allow an unprivileged user to backup the database, and in that case the
+ backup software will not be able to read the certificate files and will
+ likely error.
+ </para>
+
<para>
If the private key is protected with a passphrase, the
server will prompt for the passphrase and will not start until it has
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 28ff2f0979..9b74d1a158 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -24,6 +24,7 @@
#include "catalog/index.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
+#include "common/file_perm.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -223,7 +224,7 @@ AuxiliaryProcessMain(int argc, char *argv[])
/* If no -x argument, we are a CheckerProcess */
MyAuxProcType = CheckerProcess;
- while ((flag = getopt(argc, argv, "B:c:d:D:Fkr:x:X:-:")) != -1)
+ while ((flag = getopt(argc, argv, "B:c:d:D:Fkr:x:X:-:g")) != -1)
{
switch (flag)
{
@@ -297,6 +298,10 @@ AuxiliaryProcessMain(int argc, char *argv[])
free(value);
break;
}
+ case 'g':
+ SetDataDirectoryCreatePerm(PG_DIR_MODE_GROUP);
+ umask(pg_mode_mask);
+ break;
default:
write_stderr("Try \"%s --help\" for more information.\n",
progname);
diff --git a/src/backend/main/main.c b/src/backend/main/main.c
index 38853e38eb..306cded3f7 100644
--- a/src/backend/main/main.c
+++ b/src/backend/main/main.c
@@ -334,6 +334,7 @@ help(const char *progname)
printf(_(" -d 1-5 debugging level\n"));
printf(_(" -D DATADIR database directory\n"));
printf(_(" -e use European date input format (DMY)\n"));
+ printf(_(" -g allow group read/execute on data directory\n"));
printf(_(" -F turn fsync off\n"));
printf(_(" -h HOSTNAME host name or IP address to listen on\n"));
printf(_(" -i enable TCP/IP connections\n"));
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 868fba8cac..fee602a056 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -588,7 +588,9 @@ PostmasterMain(int argc, char *argv[])
IsPostmasterEnvironment = true;
/*
- * for security, no dir or file created can be group or other accessible
+ * By default, no directory or file created can be group or other
+ * accessible. This may be modified later depending on the permissions of
+ * the data directory.
*/
umask(PG_MODE_MASK_DEFAULT);
@@ -1522,25 +1524,47 @@ checkDataDir(void)
#endif
/*
- * Check if the directory has group or world access. If so, reject.
+ * Check if the directory has correct permissions. If not, 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.
+ * 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 & (S_IRWXG | S_IRWXO))
+ if (stat_buf.st_mode & PG_MODE_MASK_GROUP)
ereport(FATAL,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("data directory \"%s\" has group or world access",
+ errmsg("data directory \"%s\" has invalid permissions",
DataDir),
- errdetail("Permissions should be u=rwx (0700).")));
+ 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);
+
+ if (pg_dir_create_mode == PG_DIR_MODE_GROUP)
+ {
+ umask(pg_mode_mask);
+
+ SetConfigOption("data_directory_mode", "0750", PGC_INTERNAL,
+ PGC_S_OVERRIDE);
+ }
#endif
/* Look for PG_VERSION before looking for pg_control */
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 6fc1cc272b..2cc27e31ea 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -42,6 +42,7 @@
#include "catalog/pg_type.h"
#include "commands/async.h"
#include "commands/prepare.h"
+#include "common/file_perm.h"
#include "jit/jit.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
@@ -3397,7 +3398,7 @@ process_postgres_switches(int argc, char *argv[], GucContext ctx,
* postmaster/postmaster.c (the option sets should not conflict) and with
* the common help() function in main/main.c.
*/
- while ((flag = getopt(argc, argv, "B:bc:C:D:d:EeFf:h:ijk:lN:nOo:Pp:r:S:sTt:v:W:-:")) != -1)
+ while ((flag = getopt(argc, argv, "B:bc:C:D:d:EeFf:h:ijk:lN:nOo:Pp:r:S:sTt:v:W:-:g")) != -1)
{
switch (flag)
{
@@ -3560,6 +3561,11 @@ process_postgres_switches(int argc, char *argv[], GucContext ctx,
break;
}
+ case 'g':
+ SetDataDirectoryCreatePerm(PG_DIR_MODE_GROUP);
+ umask(pg_mode_mask);
+ break;
+
default:
errs++;
break;
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index c1f0441b08..3de7add3f9 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_GROUP;
+
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 f8f08f3f88..6235fab64e 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -829,7 +829,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 +899,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 260ae264d8..fa92ce2e68 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -192,6 +192,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,
@@ -2042,6 +2043,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."),
@@ -10731,4 +10747,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"
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 83472d9ceb..c678e2ef0d 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -1168,6 +1168,19 @@ setup_config(void)
"password_encryption = scram-sha-256");
}
+ /*
+ * If group access has been enabled for the cluster then it makes sense
+ * to ensure that the log files also allow group access. Otherwise a
+ * backup from a user in the group would fail if the log files were not
+ * relocated.
+ */
+ if (pg_dir_create_mode == PG_DIR_MODE_GROUP)
+ {
+ conflines = replace_token(conflines,
+ "#log_file_mode = 0600",
+ "log_file_mode = 0640");
+ }
+
snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
writefile(path, conflines);
@@ -1382,12 +1395,13 @@ bootstrap_template1(void)
unsetenv("PGCLIENTENCODING");
snprintf(cmd, sizeof(cmd),
- "\"%s\" --boot -x1 -X %u %s %s %s",
+ "\"%s\" --boot -x1 -X %u %s %s %s %s",
backend_exec,
wal_segment_size_mb * (1024 * 1024),
data_checksums ? "-k" : "",
boot_options,
- debug ? "-d 5" : "");
+ debug ? "-d 5" : "",
+ pg_dir_create_mode == PG_DIR_MODE_GROUP ? "-g" : "");
PG_CMD_OPEN;
@@ -2312,6 +2326,7 @@ usage(const char *progname)
printf(_(" --auth-local=METHOD default authentication method for local-socket connections\n"));
printf(_(" [-D, --pgdata=]DATADIR location for this database cluster\n"));
printf(_(" -E, --encoding=ENCODING set default encoding for new databases\n"));
+ printf(_(" -g, --allow-group-access allow group read/execute on data directory\n"));
printf(_(" --locale=LOCALE set default locale for new databases\n"));
printf(_(" --lc-collate=, --lc-ctype=, --lc-messages=LOCALE\n"
" --lc-monetary=, --lc-numeric=, --lc-time=LOCALE\n"
@@ -2883,8 +2898,8 @@ initialize_data_directory(void)
setup_signals();
- /* Set dir/file mode mask */
- umask(PG_MODE_MASK_DEFAULT);
+ /* Set mask based on requested PGDATA permissions */
+ umask(pg_mode_mask);
create_data_directory();
@@ -2942,8 +2957,9 @@ initialize_data_directory(void)
fflush(stdout);
snprintf(cmd, sizeof(cmd),
- "\"%s\" %s template1 >%s",
+ "\"%s\" %s %s template1 >%s",
backend_exec, backend_options,
+ pg_dir_create_mode == PG_DIR_MODE_GROUP ? "-g" : "",
DEVNULL);
PG_CMD_OPEN;
@@ -3018,6 +3034,7 @@ main(int argc, char *argv[])
{"waldir", required_argument, NULL, 'X'},
{"wal-segsize", required_argument, NULL, 12},
{"data-checksums", no_argument, NULL, 'k'},
+ {"allow-group-access", no_argument, NULL, 'g'},
{NULL, 0, NULL, 0}
};
@@ -3059,7 +3076,7 @@ main(int argc, char *argv[])
/* process command-line options */
- while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:", long_options, &option_index)) != -1)
+ while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:g", long_options, &option_index)) != -1)
{
switch (c)
{
@@ -3153,6 +3170,9 @@ main(int argc, char *argv[])
case 12:
str_wal_segment_size_mb = pg_strdup(optarg);
break;
+ case 'g':
+ SetDataDirectoryCreatePerm(PG_DIR_MODE_GROUP);
+ break;
default:
/* getopt_long already emitted a complaint */
fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index 3331560445..f08fc6ea8f 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -4,9 +4,11 @@
use strict;
use warnings;
+use Fcntl ':mode';
+use File::stat qw{lstat};
use PostgresNode;
use TestLib;
-use Test::More tests => 16;
+use Test::More tests => 18;
my $tempdir = TestLib::tempdir;
my $xlogdir = "$tempdir/pgxlog";
@@ -50,3 +52,12 @@ mkdir $datadir;
}
command_ok([ 'initdb', '-S', $datadir ], 'sync only');
command_fails([ 'initdb', $datadir ], 'existing data directory');
+
+# Init a new db with group access
+my $datadir_group = "$tempdir/data_group";
+
+command_ok(
+ [ 'initdb', '-g', $datadir_group ],
+ 'successful creation with group access');
+
+ok(check_mode_recursive($datadir_group, 0750, 0640));
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 32b41e313c..5cd535184a 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -2470,14 +2470,6 @@ main(int argc, char **argv)
}
#endif
- /*
- * Verify that the target directory exists, or create it. For plaintext
- * backups, always require the directory. For tar backups, require it
- * unless we are writing to stdout.
- */
- if (format == 'p' || strcmp(basedir, "-") != 0)
- verify_dir_is_empty_or_create(basedir, &made_new_pgdata, &found_existing_pgdata);
-
/* connection in replication mode to server */
conn = GetConnection();
if (!conn)
@@ -2486,6 +2478,20 @@ main(int argc, char **argv)
exit(1);
}
+ /*
+ * Set umask so that directories/files are created with the same permissions
+ * as directories/files in the source data directory.
+ */
+ umask(pg_mode_mask);
+
+ /*
+ * Verify that the target directory exists, or create it. For plaintext
+ * backups, always require the directory. For tar backups, require it
+ * unless we are writing to stdout.
+ */
+ if (format == 'p' || strcmp(basedir, "-") != 0)
+ verify_dir_is_empty_or_create(basedir, &made_new_pgdata, &found_existing_pgdata);
+
/* determine remote server's xlog segment size */
if (!RetrieveWalSegSize(conn))
disconnect_and_exit(1);
diff --git a/src/bin/pg_basebackup/pg_receivewal.c b/src/bin/pg_basebackup/pg_receivewal.c
index b82e073b86..f23ecf799f 100644
--- a/src/bin/pg_basebackup/pg_receivewal.c
+++ b/src/bin/pg_basebackup/pg_receivewal.c
@@ -19,6 +19,7 @@
#include <sys/stat.h>
#include <unistd.h>
+#include "common/file_perm.h"
#include "libpq-fe.h"
#include "access/xlog_internal.h"
#include "getopt_long.h"
@@ -703,6 +704,12 @@ main(int argc, char **argv)
if (!RunIdentifySystem(conn, NULL, NULL, NULL, &db_name))
disconnect_and_exit(1);
+ /*
+ * Set umask so that directories/files are created with the same permissions
+ * as directories/files in the source data directory.
+ */
+ umask(pg_mode_mask);
+
/* determine remote server's xlog segment size */
if (!RetrieveWalSegSize(conn))
disconnect_and_exit(1);
diff --git a/src/bin/pg_basebackup/pg_recvlogical.c b/src/bin/pg_basebackup/pg_recvlogical.c
index 0866395944..e3b6404b1f 100644
--- a/src/bin/pg_basebackup/pg_recvlogical.c
+++ b/src/bin/pg_basebackup/pg_recvlogical.c
@@ -23,6 +23,7 @@
#include "streamutil.h"
#include "access/xlog_internal.h"
+#include "common/file_perm.h"
#include "common/fe_memutils.h"
#include "getopt_long.h"
#include "libpq-fe.h"
@@ -959,6 +960,12 @@ main(int argc, char **argv)
disconnect_and_exit(1);
}
+ /*
+ * Set umask so that directories/files are created with the same permissions
+ * as directories/files in the source data directory.
+ */
+ umask(pg_mode_mask);
+
/* Drop a replication slot. */
if (do_drop_slot)
{
diff --git a/src/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c
index 1438f368ed..494acfec0e 100644
--- a/src/bin/pg_basebackup/streamutil.c
+++ b/src/bin/pg_basebackup/streamutil.c
@@ -23,6 +23,7 @@
#include "access/xlog_internal.h"
#include "common/fe_memutils.h"
+#include "common/file_perm.h"
#include "datatype/timestamp.h"
#include "fe_utils/connect.h"
#include "port/pg_bswap.h"
@@ -32,9 +33,16 @@
uint32 WalSegSz;
+static bool RetrieveDataDirCreatePerm(PGconn *conn);
+
/* SHOW command for replication connection was introduced in version 10 */
#define MINIMUM_VERSION_FOR_SHOW_CMD 100000
+/*
+ * Group access is supported from version 11.
+ */
+#define MINIMUM_VERSION_FOR_GROUP_ACCESS 110000
+
const char *progname;
char *connection_string = NULL;
char *dbhost = NULL;
@@ -254,6 +262,16 @@ GetConnection(void)
exit(1);
}
+ /*
+ * Retrieve the source data directory mode and use to construct a umask
+ * for creating directories and files
+ */
+ if (!RetrieveDataDirCreatePerm(tmpconn))
+ {
+ PQfinish(tmpconn);
+ exit(1);
+ }
+
return tmpconn;
}
@@ -327,6 +345,57 @@ RetrieveWalSegSize(PGconn *conn)
return true;
}
+/*
+ * From version 11, check the mode of the data directory to determine
+ * permissions to use for directories created by the utility.
+ */
+static bool
+RetrieveDataDirCreatePerm(PGconn *conn)
+{
+ PGresult *res;
+ int data_directory_mode;
+
+ /* check connection existence */
+ Assert(conn != NULL);
+
+ /* for previous versions leave the default group access */
+ if (PQserverVersion(conn) < MINIMUM_VERSION_FOR_GROUP_ACCESS)
+ return true;
+
+ res = PQexec(conn, "SHOW data_directory_mode");
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ fprintf(stderr, _("%s: could not send replication command \"%s\": %s\n"),
+ progname, "SHOW data_directory_mode", PQerrorMessage(conn));
+
+ PQclear(res);
+ return false;
+ }
+ if (PQntuples(res) != 1 || PQnfields(res) < 1)
+ {
+ fprintf(stderr,
+ _("%s: could not fetch group access flag: got %d rows and %d fields, expected %d rows and %d or more fields\n"),
+ progname, PQntuples(res), PQnfields(res), 1, 1);
+
+ PQclear(res);
+ return false;
+ }
+
+ if (sscanf(PQgetvalue(res, 0, 0), "%o", &data_directory_mode) != 1)
+ {
+ fprintf(stderr, _("%s: group access flag could not be parsed: %s\n"),
+ progname, PQgetvalue(res, 0, 0));
+
+ PQclear(res);
+ return false;
+ }
+
+ SetDataDirectoryCreatePerm(data_directory_mode);
+
+ PQclear(res);
+ return true;
+}
+
/*
* Run IDENTIFY_SYSTEM through a given connection and give back to caller
* some result information if requested:
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index 496c22da7d..9a29e6416a 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -5,7 +5,7 @@ use Config;
use File::Basename qw(basename dirname);
use PostgresNode;
use TestLib;
-use Test::More tests => 105;
+use Test::More tests => 106;
program_help_ok('pg_basebackup');
program_version_ok('pg_basebackup');
@@ -16,7 +16,7 @@ my $tempdir = TestLib::tempdir;
my $node = get_new_node('main');
# Set umask so no test directories or files are created with group permissions
-umask (0077);
+umask(0077);
# Initialize node without replication settings
$node->init(extra => [ '--data-checksums' ]);
@@ -43,11 +43,6 @@ $node->command_fails(
ok(!-d "$tempdir/backup", 'backup directory was cleaned up');
-$node->command_fails([ 'pg_basebackup', '-D', "$tempdir/backup", '-n' ],
- 'failing run with no-clean option');
-
-ok(-d "$tempdir/backup", 'backup directory was created and left behind');
-
open my $conf, '>>', "$pgdata/postgresql.conf";
print $conf "max_replication_slots = 10\n";
print $conf "max_wal_senders = 10\n";
@@ -157,6 +152,13 @@ ok(-f "$tempdir/tarbackup/base.tar", 'backup tar was created');
$node->command_fails(
[ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T=/foo" ],
'-T with empty old directory fails');
+
+$node->command_fails(
+ [ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T=/foo", "-n" ],
+ 'failing run with no-clean option');
+
+ok(-d "$tempdir/backup", 'backup directory was created and left behind');
+
$node->command_fails(
[ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T/foo=" ],
'-T with empty new directory fails');
@@ -190,11 +192,17 @@ unlink "$pgdata/$superlongname";
# skip on Windows.
SKIP:
{
- skip "symlinks not supported on Windows", 17 if ($windows_os);
+ skip "symlinks not supported on Windows", 18 if ($windows_os);
# Move pg_replslot out of $pgdata and create a symlink to it.
$node->stop;
+ # Set umask so test directories and files are created with group permissions
+ umask (0027);
+
+ # Enable group permissions on PGDATA
+ chmod_recursive("$pgdata", 0750, 0640);
+
rename("$pgdata/pg_replslot", "$tempdir/pg_replslot")
or BAIL_OUT "could not move $pgdata/pg_replslot";
symlink("$tempdir/pg_replslot", "$pgdata/pg_replslot")
@@ -264,6 +272,10 @@ SKIP:
"tablespace symlink was updated");
closedir $dh;
+ # Group access should be enabled on all backup files
+ ok(check_mode_recursive("$tempdir/backup1", 0750, 0640),
+ "check backup dir permissions");
+
# Unlogged relation forks other than init should not be copied
my ($tblspc1UnloggedBackupPath) = $tblspc1UnloggedPath =~ /[^\/]*\/[^\/]*\/[^\/]*$/g;
diff --git a/src/bin/pg_basebackup/t/020_pg_receivewal.pl b/src/bin/pg_basebackup/t/020_pg_receivewal.pl
index d155d5b711..752ba5df93 100644
--- a/src/bin/pg_basebackup/t/020_pg_receivewal.pl
+++ b/src/bin/pg_basebackup/t/020_pg_receivewal.pl
@@ -9,7 +9,7 @@ program_version_ok('pg_receivewal');
program_options_handling_ok('pg_receivewal');
# Set umask so no test directories or files are created with group permissions
-umask (0077);
+umask(0077);
my $primary = get_new_node('primary');
$primary->init(allows_streaming => 1);
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index e83a7179ea..e0c9687a35 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -2171,7 +2171,7 @@ main(int argc, char **argv)
*/
argv0 = argv[0];
- /* Set dir/file mode mask */
+ /* Set restrictive mode mask until PGDATA permissions are checked */
umask(PG_MODE_MASK_DEFAULT);
/* support --help and --version even if invoked as root */
@@ -2407,6 +2407,16 @@ main(int argc, char **argv)
snprintf(version_file, MAXPGPATH, "%s/PG_VERSION", pg_data);
snprintf(pid_file, MAXPGPATH, "%s/postmaster.pid", pg_data);
snprintf(backup_file, MAXPGPATH, "%s/backup_label", pg_data);
+
+ /*
+ * Set mask based on PGDATA permissions,
+ *
+ * Don't error here if the data directory cannot be stat'd. This is
+ * handled differently based on the command and we don't want to
+ * interfere with that logic.
+ */
+ if (GetDataDirectoryCreatePerm(pg_data))
+ umask(pg_mode_mask);
}
switch (ctl_command)
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl
index 3d82abe696..91e7f00326 100644
--- a/src/bin/pg_ctl/t/001_start_stop.pl
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -2,9 +2,11 @@ use strict;
use warnings;
use Config;
+use Fcntl ':mode';
+use File::stat qw{lstat};
use PostgresNode;
use TestLib;
-use Test::More tests => 20;
+use Test::More tests => 24;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -68,4 +70,24 @@ command_ok(
ok(-f $logFileName);
ok(check_mode_recursive("$tempdir/data", 0700, 0600));
+# Log file for second perm test
+$logFileName = "$tempdir/data/perm-test-640.log";
+
+# Change the data dir mode so log file will be created with group read
+# privileges on the next start
+system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
+
+chmod_recursive("$tempdir/data", 0750, 0640);
+
+command_ok(
+ [ 'pg_ctl', 'start', '-D', "$tempdir/data", '-l', $logFileName ],
+ 'start server to check group permissions');
+
+ok(-f $logFileName);
+ok(check_mode_recursive("$tempdir/data", 0750, 0640));
+
+command_ok(
+ [ 'pg_ctl', 'restart', '-D', "$tempdir/data", '-l', $logFileName ],
+ 'pg_ctl restart with server running');
+
system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index bdf71886ee..8a0a805f1e 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -363,6 +363,16 @@ main(int argc, char *argv[])
exit(1);
}
+ /* Set mask based on PGDATA permissions */
+ if (!GetDataDirectoryCreatePerm(DataDir))
+ {
+ fprintf(stderr, _("%s: unable to read permissions from \"%s\"\n"),
+ progname, DataDir);
+ exit(1);
+ }
+
+ umask(pg_mode_mask);
+
/* Check that data directory matches our server version */
CheckDataVersion();
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index 7b632c7dcd..63d9bd517d 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -115,11 +115,13 @@ sub check_query
sub setup_cluster
{
- my $extra_name = shift;
+ my $extra_name = shift; # Used to differentiate clusters
+ my $extra = shift; # Extra params for initdb
# Initialize master, data checksums are mandatory
$node_master = get_new_node('master' . ($extra_name ? "_${extra_name}" : ''));
- $node_master->init(allows_streaming => 1);
+ $node_master->init(
+ allows_streaming => 1, extra => $extra);
# Set wal_keep_segments to prevent WAL segment recycling after enforced
# checkpoints in the tests.
$node_master->append_conf('postgresql.conf', qq(
@@ -237,7 +239,8 @@ sub run_pg_rewind
"$tmp_folder/master-postgresql.conf.tmp",
"$master_pgdata/postgresql.conf");
- chmod(0600, "$master_pgdata/postgresql.conf")
+ chmod($node_master->group_access() ? 0640 : 0600,
+ "$master_pgdata/postgresql.conf")
or BAIL_OUT(
"unable to set permissions for $master_pgdata/postgresql.conf");
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 72ab2f8d5e..b9ea6a4c21 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -24,6 +24,7 @@
#include "access/xlog_internal.h"
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "getopt_long.h"
#include "storage/bufpage.h"
@@ -185,6 +186,16 @@ main(int argc, char **argv)
exit(1);
}
+ /* Set mask based on PGDATA permissions */
+ if (!GetDataDirectoryCreatePerm(datadir_target))
+ {
+ fprintf(stderr, _("%s: unable to read permissions from \"%s\"\n"),
+ progname, datadir_target);
+ exit(1);
+ }
+
+ umask(pg_mode_mask);
+
/*
* Don't allow pg_rewind to be run as root, to avoid overwriting the
* ownership of files in the data directory. We need only check for root
diff --git a/src/bin/pg_rewind/t/002_databases.pl b/src/bin/pg_rewind/t/002_databases.pl
index 37cdd712f3..570fa5cee0 100644
--- a/src/bin/pg_rewind/t/002_databases.pl
+++ b/src/bin/pg_rewind/t/002_databases.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 6;
use RewindTest;
@@ -9,7 +9,7 @@ sub run_test
{
my $test_mode = shift;
- RewindTest::setup_cluster($test_mode);
+ RewindTest::setup_cluster($test_mode, ['-g']);
RewindTest::start_master();
# Create a database in master.
@@ -42,6 +42,8 @@ template1
),
'database names');
+ ok (check_mode_recursive(
+ $node_master->data_dir(), 0750, 0640, ['postmaster.pid']));
RewindTest::clean_rewind_test();
}
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 59df6fd88f..4abf4dc893 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -79,7 +79,7 @@ main(int argc, char **argv)
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_upgrade"));
- /* Ensure that all files created by pg_upgrade are non-world-readable */
+ /* Set default restrictive mask until new cluster permissions are read */
umask(PG_MODE_MASK_DEFAULT);
parseCommandLine(argc, argv);
@@ -100,6 +100,16 @@ main(int argc, char **argv)
check_cluster_compatibility(live_check);
+ /* Set mask based on PGDATA permissions */
+ if (!GetDataDirectoryCreatePerm(new_cluster.pgdata))
+ {
+ pg_log(PG_FATAL, "unable to read permissions from \"%s\"\n",
+ new_cluster.pgdata);
+ exit(1);
+ }
+
+ umask(pg_mode_mask);
+
check_and_dump_old_cluster(live_check);
diff --git a/src/bin/pg_upgrade/test.sh b/src/bin/pg_upgrade/test.sh
index 574639d47e..24631a91c6 100644
--- a/src/bin/pg_upgrade/test.sh
+++ b/src/bin/pg_upgrade/test.sh
@@ -20,9 +20,9 @@ unset MAKELEVEL
# Run a given "initdb" binary and overlay the regression testing
# authentication configuration.
standard_initdb() {
- # To increase coverage of non-standard segment size without
- # increase test runtime, run these tests with a lower setting.
- "$1" -N --wal-segsize 1
+ # To increase coverage of non-standard segment size and group access
+ # without increasing test runtime, run these tests with a custom setting.
+ "$1" -N --wal-segsize 1 -g
if [ -n "$TEMP_CONFIG" -a -r "$TEMP_CONFIG" ]
then
cat "$TEMP_CONFIG" >> "$PGDATA/postgresql.conf"
@@ -230,14 +230,14 @@ standard_initdb 'initdb'
pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "${PGDATA}" -b "$oldbindir" -B "$bindir" -p "$PGPORT" -P "$PGPORT"
-# make sure all directories and files have correct permissions
-if [ $(find ${PGDATA} -type f ! -perm 600 | wc -l) -ne 0 ]; then
- echo "files in PGDATA with permission != 600";
+# make sure all directories and files have group permissions
+if [ $(find ${PGDATA} -type f ! -perm 640 | wc -l) -ne 0 ]; then
+ echo "files in PGDATA with permission != 640";
exit 1;
fi
-if [ $(find ${PGDATA} -type d ! -perm 700 | wc -l) -ne 0 ]; then
- echo "directories in PGDATA with permission != 700";
+if [ $(find ${PGDATA} -type d ! -perm 750 | wc -l) -ne 0 ]; then
+ echo "directories in PGDATA with permission != 750";
exit 1;
fi
diff --git a/src/common/Makefile b/src/common/Makefile
index e9e75867f3..2bab992de0 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -51,7 +51,7 @@ else
OBJS_COMMON += sha2.o
endif
-OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o file_utils.o restricted_token.o
+OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o file_perm.o file_utils.o restricted_token.o
OBJS_SRV = $(OBJS_COMMON:%.o=%_srv.o)
diff --git a/src/common/file_perm.c b/src/common/file_perm.c
index 5f0c02fb04..571d37116b 100644
--- a/src/common/file_perm.c
+++ b/src/common/file_perm.c
@@ -12,8 +12,76 @@
*/
#include <sys/stat.h>
+#include "c.h"
#include "common/file_perm.h"
/* Modes for creating directories and files in the data directory */
int pg_dir_create_mode = PG_DIR_MODE_DEFAULT;
int pg_file_create_mode = PG_FILE_MODE_DEFAULT;
+
+/*
+ * Mode mask to pass to umask(). This is more of a preventative measure since
+ * all file/directory creates should be performed using the create modes above.
+ */
+int pg_mode_mask = PG_MODE_MASK_DEFAULT;
+
+/*
+ * Set create modes and mask to use when writing to PGDATA based on the data
+ * directory mode passed. If group read/execute are present in the
+ * mode, then create modes and mask will be relaxed to allow group read/execute
+ * on all newly created files and directories.
+ */
+void
+SetDataDirectoryCreatePerm(int dataDirMode)
+{
+ /* If the data directory mode has group access */
+ if ((PG_DIR_MODE_GROUP & dataDirMode) == PG_DIR_MODE_GROUP)
+ {
+ pg_dir_create_mode = PG_DIR_MODE_GROUP;
+ pg_file_create_mode = PG_FILE_MODE_GROUP;
+ pg_mode_mask = PG_MODE_MASK_GROUP;
+ }
+ /* Else use default permissions */
+ else
+ {
+ pg_dir_create_mode = PG_DIR_MODE_DEFAULT;
+ pg_file_create_mode = PG_FILE_MODE_DEFAULT;
+ pg_mode_mask = PG_MODE_MASK_DEFAULT;
+ }
+}
+
+#ifdef FRONTEND
+
+/*
+ * Get the create modes and mask to use when writing to PGDATA by examining the
+ * mode of the PGDATA directory and calling SetDataDirectoryCreatePerm().
+ *
+ * Errors are not handled here and should be reported by the frontend
+ * application when false is returned.
+ *
+ * Suppress when on Windows, because there may not be proper support for Unix-y
+ * file permissions.
+ */
+bool
+GetDataDirectoryCreatePerm(const char *dataDir)
+{
+#if !defined(WIN32) && !defined(__CYGWIN__)
+ struct stat statBuf;
+
+ /*
+ * If an error occurs getting the mode then false It is the reponsibility of
+ * the frontend application to generate an error if the PGDATA directory
+ * cannot be accessed.
+ */
+ if (stat(dataDir, &statBuf) == -1)
+ return false;
+
+ /* Set permissions */
+ SetDataDirectoryCreatePerm(statBuf.st_mode);
+
+ return true;
+#endif /* !defined(WIN32) && !defined(__CYGWIN__) */
+}
+
+
+#endif /* FRONTEND */
diff --git a/src/include/common/file_perm.h b/src/include/common/file_perm.h
index 46b91f0c7b..22afb14b55 100644
--- a/src/include/common/file_perm.h
+++ b/src/include/common/file_perm.h
@@ -19,14 +19,34 @@
*/
#define PG_MODE_MASK_DEFAULT (S_IRWXG | S_IRWXO)
+/*
+ * Mode mask for data directory permissions that also allows group read/execute.
+ */
+#define PG_MODE_MASK_GROUP (S_IWGRP | S_IRWXO)
+
/* Default mode for creating directories */
#define PG_DIR_MODE_DEFAULT S_IRWXU
+/* Mode for creating directories that allows group read/execute */
+#define PG_DIR_MODE_GROUP (S_IRWXU | S_IRGRP | S_IXGRP)
+
/* Default mode for creating files */
#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR)
+/* Mode for creating files that allows group read */
+#define PG_FILE_MODE_GROUP (S_IRUSR | S_IWUSR | S_IRGRP)
+
/* Modes for creating directories and files in the data directory */
extern int pg_dir_create_mode;
extern int pg_file_create_mode;
+/* Mode mask to pass to umask() */
+extern int pg_mode_mask;
+
+/* Set permissions and mask based on the provided mode */
+extern void SetDataDirectoryCreatePerm(int dataDirMode);
+
+/* Set permissions and mask based on the mode of the data directory */
+extern bool GetDataDirectoryCreatePerm(const char *dataDir);
+
#endif /* FILE_PERM_H */
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index a429a19964..0438ea809c 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -153,6 +153,7 @@ extern PGDLLIMPORT bool IsBinaryUpgrade;
extern PGDLLIMPORT bool ExitOnAnyError;
extern PGDLLIMPORT char *DataDir;
+extern PGDLLIMPORT int data_directory_mode;
extern PGDLLIMPORT int NBuffers;
extern PGDLLIMPORT int MaxBackends;
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 76e571b98c..1bd91524d7 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -86,9 +86,11 @@ use Carp;
use Config;
use Cwd;
use Exporter 'import';
+use Fcntl qw(:mode);
use File::Basename;
use File::Path qw(rmtree);
use File::Spec;
+use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
use RecursiveCopy;
@@ -269,6 +271,26 @@ sub connstr
=pod
+=item $node->group_access()
+
+Does the data dir allow group access?
+
+=cut
+
+sub group_access
+{
+ my ($self) = @_;
+
+ my $dir_stat = stat($self->data_dir);
+
+ defined($dir_stat)
+ or die('unable to stat ' . $self->data_dir);
+
+ return (S_IMODE($dir_stat->mode) == 0750);
+}
+
+=pod
+
=item $node->data_dir()
Returns the path to the data directory. postgresql.conf and pg_hba.conf are
@@ -460,6 +482,9 @@ sub init
}
close $conf;
+ chmod($self->group_access ? 0640 : 0600, "$pgdata/postgresql.conf")
+ or die("unable to set permissions for $pgdata/postgresql.conf");
+
$self->set_replication_conf if $params{allows_streaming};
$self->enable_archiving if $params{has_archiving};
}
@@ -485,7 +510,7 @@ sub append_conf
TestLib::append_to_file($conffile, $str . "\n");
- chmod(0600, $conffile)
+ chmod($self->group_access() ? 0640 : 0600, $conffile)
or die("unable to set permissions for $conffile");
}
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index 93610e4bc4..8047404efd 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -31,6 +31,7 @@ our @EXPORT = qw(
slurp_file
append_to_file
check_mode_recursive
+ chmod_recursive
check_pg_config
system_or_bail
system_log
@@ -313,6 +314,30 @@ sub check_mode_recursive
return $result;
}
+# Change mode recursively on a directory
+sub chmod_recursive
+{
+ my ($dir, $dir_mode, $file_mode) = @_;
+
+ find
+ (
+ {follow_fast => 1,
+ wanted =>
+ sub
+ {
+ my $file_stat = stat($File::Find::name);
+
+ if (defined($file_stat))
+ {
+ chmod(S_ISDIR($file_stat->mode) ? $dir_mode : $file_mode,
+ $File::Find::name)
+ or die "unable to chmod $File::Find::name";
+ }
+ }},
+ $dir
+ );
+}
+
# Check presence of a given regexp within pg_config.h for the installation
# where tests are running, returning a match status result depending on
# that.
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 1d3ed6b0b1..764c8b8cd2 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -126,7 +126,7 @@ sub mkvcbuild
}
our @pgcommonfrontendfiles = (
- @pgcommonallfiles, qw(fe_memutils.c file_utils.c
+ @pgcommonallfiles, qw(fe_memutils.c file_perm.c file_utils.c
restricted_token.c));
our @pgcommonbkndfiles = @pgcommonallfiles;
--
2.14.3 (Apple Git-98)
On Wed, Apr 04, 2018 at 08:03:54PM -0400, David Steele wrote:
On 4/2/18 2:28 AM, Michael Paquier wrote:
The answer is no for me and likely the same for others, but I am raising
the point for the archives. Should we relax
check_ssl_key_file_permissions() for group permissions by the way from
0600 to 0640 when the file is owned by the user running Postgres? If we
don't do that, then SSL private keys will need to be used with 0600,
potentially breaking backups... At the same time this reduces the
security of private keys but if the administrator is ready to accept
group permissions that should be a risk he is ready to accept?I feel this should be considered in a separate patch. These files are
not created by initdb so it seems to be an admin/packaging issue. There
is always the option to locate the certs outside the data directory and
some distros do that by default.
OK, I agree with your final position. Let's treat it later in a
separate thread, if need be. However this is not really mandatory.
I am pretty sure that we want more documentation in pg_basebackup,
pg_rewind and pg_resetwal telling that those consider grouping access.I think this makes sense for pg_basebackup, pg_receivewal, and
pg_recvlogical so I have added notes for those. Not sure I'm happy with
the language but at least we have something to bikeshed.It seems to me that pg_resetwal and pg_rewind should be expected to not
break the permissions in PGDATA, just as they do now.
OK, I think that I am fine with this logic.
I decided that the constants were a bit confusing in general. What does
"default" mean, anyway?
The value that user should have if he decides to not enforce it.
Instead I have created variables in file_perm.c
that hold the current file create mode, dir create mode, and mode mask.
All call sites use those variables (e.g. pg_dir_create_mode), which I
think are much clear.
Hm. I personally find that even more confusing, especially with
SetDataDirectoryCreatePerm which basically is the same as
SetConfigOption for the backend. In your case pg_dir_create_mode is not
aimed at remaining a constant as you may change it depending on if
grouping is enabled or not... And all the other iterations of such
variables in src/common/ are constants (see NumScanKeywords or
forkNames[] for example), so I cannot see with a good eye this change.
You also forgot a call to SetDataDirectoryCreatePerm or
pg_dir_create_mode remains to its default.
The interface of file_perm.h that you are introducing is not confusing
anymore though..
--
Michael
Hi Michael,
On 4/5/18 2:55 AM, Michael Paquier wrote:
On Wed, Apr 04, 2018 at 08:03:54PM -0400, David Steele wrote:
Instead I have created variables in file_perm.c
that hold the current file create mode, dir create mode, and mode mask.
All call sites use those variables (e.g. pg_dir_create_mode), which I
think are much clear.Hm. I personally find that even more confusing, especially with
SetDataDirectoryCreatePerm which basically is the same as
SetConfigOption for the backend.
The GUC shows the current mode of the data directory, while the
variables in file_perm.c store the mode that should be used to create
new dirs/files. One is certainly based on the other but I thought it
best to split them for clarity.
In your case pg_dir_create_mode is not
aimed at remaining a constant as you may change it depending on if
grouping is enabled or not... And all the other iterations of such
variables in src/common/ are constants (see NumScanKeywords or
forkNames[] for example), so I cannot see with a good eye this change.
The idea is to have a variable that give the fir dir/file create mode
without having to trust that umask() is set correctly or do mode masking
on chmod(). This makes a number of call sites simpler and, I hope,
easier to read.
You also forgot a call to SetDataDirectoryCreatePerm or
pg_dir_create_mode remains to its default.
Are saying *if* a call is forgotten?
Yes, the file/dir create modes will use the default restrictive
permissions unless set otherwise. I see this as a good thing. Worst
case, we miss some areas where group access should be enabled and we
find those over the next few months.
What we don't want is a regression in the current, default behavior.
This is design is intended to avoid that outcome.
The interface of file_perm.h that you are introducing is not confusing
anymore though..
Yes, that was the idea. I think it makes it clearer what we are trying
to do and centralizes variables related to create modes in one place.
I've attached new patches that exclude Windows from permissions tests
and deal with bootstrap permissions in a better way. PostgresMain() and
AuxiliaryProcessMain() now use checkDataDir() to set permissions.
Thanks!
--
-David
david@pgmasters.net
Attachments:
group-access-v15-01-file-perm.patchtext/plain; charset=UTF-8; name=group-access-v15-01-file-perm.patch; x-mac-creator=0; x-mac-type=0Download
From 30599b54e0f31e6c6842e028eeca3048b9a371ca Mon Sep 17 00:00:00 2001
From: David Steele <david@pgmasters.net>
Date: Thu, 5 Apr 2018 11:34:58 -0400
Subject: [PATCH 1/2] Refactor file permissions in backend/frontend
Adds a new header (file_perm.h) and makes all front-end utilities use the new constants for setting umask. Converts mkdir() calls in the backend to MakeDirectoryDefaultPerm() if the original call used default permissions. Adds tests to make sure permissions in PGDATA are set correctly by the front-end tools.
---
src/backend/access/transam/xlog.c | 2 +-
src/backend/commands/tablespace.c | 18 ++++---
src/backend/postmaster/postmaster.c | 5 +-
src/backend/postmaster/syslogger.c | 5 +-
src/backend/replication/basebackup.c | 5 +-
src/backend/replication/slot.c | 5 +-
src/backend/storage/file/copydir.c | 2 +-
src/backend/storage/file/fd.c | 38 +++++++++------
src/backend/storage/ipc/dsm_impl.c | 3 +-
src/backend/storage/ipc/ipc.c | 4 ++
src/backend/utils/init/miscinit.c | 5 +-
src/bin/initdb/initdb.c | 24 ++++-----
src/bin/initdb/t/001_initdb.pl | 11 ++++-
src/bin/pg_basebackup/pg_basebackup.c | 9 ++--
src/bin/pg_basebackup/t/010_pg_basebackup.pl | 14 +++++-
src/bin/pg_basebackup/t/020_pg_receivewal.pl | 14 +++++-
src/bin/pg_basebackup/walmethods.c | 6 ++-
src/bin/pg_ctl/pg_ctl.c | 4 +-
src/bin/pg_ctl/t/001_start_stop.pl | 18 ++++++-
src/bin/pg_resetwal/pg_resetwal.c | 5 +-
src/bin/pg_resetwal/t/001_basic.pl | 12 ++++-
src/bin/pg_rewind/RewindTest.pm | 4 ++
src/bin/pg_rewind/file_ops.c | 7 +--
src/bin/pg_rewind/t/001_basic.pl | 11 ++++-
src/bin/pg_upgrade/file.c | 5 +-
src/bin/pg_upgrade/pg_upgrade.c | 3 +-
src/bin/pg_upgrade/test.sh | 11 +++++
src/common/Makefile | 4 +-
src/common/file_perm.c | 19 ++++++++
src/include/common/file_perm.h | 32 ++++++++++++
src/include/storage/fd.h | 3 ++
src/test/perl/PostgresNode.pm | 3 ++
src/test/perl/TestLib.pm | 73 ++++++++++++++++++++++++++++
src/tools/msvc/Mkvcbuild.pm | 4 +-
34 files changed, 315 insertions(+), 73 deletions(-)
create mode 100644 src/common/file_perm.c
create mode 100644 src/include/common/file_perm.h
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index b4fd8395b7..5398d2f925 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4106,7 +4106,7 @@ ValidateXLOGDirectoryStructure(void)
{
ereport(LOG,
(errmsg("creating missing WAL directory \"%s\"", path)));
- if (mkdir(path, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(path) < 0)
ereport(FATAL,
(errmsg("could not create missing directory \"%s\": %m",
path)));
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 5c450caa4e..6449b71243 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -68,6 +68,7 @@
#include "commands/seclabel.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
+#include "common/file_perm.h"
#include "miscadmin.h"
#include "postmaster/bgwriter.h"
#include "storage/fd.h"
@@ -151,7 +152,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
else
{
/* Directory creation failed? */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(dir) < 0)
{
char *parentdir;
@@ -173,7 +174,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
get_parent_directory(parentdir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -184,7 +185,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
parentdir = pstrdup(dir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -192,7 +193,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
pfree(parentdir);
/* Create database directory */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(dir) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -279,7 +280,8 @@ CreateTableSpace(CreateTableSpaceStmt *stmt)
/*
* Check that location isn't too long. Remember that we're going to append
* 'PG_XXX/<dboid>/<relid>_<fork>.<nnn>'. FYI, we never actually
- * reference the whole path here, but mkdir() uses the first two parts.
+ * reference the whole path here, but MakeDirectoryDefaultPerm() uses the first
+ * two parts.
*/
if (strlen(location) + 1 + strlen(TABLESPACE_VERSION_DIRECTORY) + 1 +
OIDCHARS + 1 + OIDCHARS + 1 + FORKNAMECHARS + 1 + OIDCHARS > MAXPGPATH)
@@ -574,7 +576,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* Attempt to coerce target directory to safe permissions. If this fails,
* it doesn't exist or has the wrong owner.
*/
- if (chmod(location, S_IRWXU) != 0)
+ if (chmod(location, pg_dir_create_mode) != 0)
{
if (errno == ENOENT)
ereport(ERROR,
@@ -599,7 +601,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
if (stat(location_with_version_dir, &st) == 0 && S_ISDIR(st.st_mode))
{
if (!rmtree(location_with_version_dir, true))
- /* If this failed, mkdir() below is going to error. */
+ /* If this failed, MakeDirectoryDefaultPerm() below is going to error. */
ereport(WARNING,
(errmsg("some useless files may be left behind in old database directory \"%s\"",
location_with_version_dir)));
@@ -610,7 +612,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* The creation of the version directory prevents more than one tablespace
* in a single location.
*/
- if (mkdir(location_with_version_dir, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(location_with_version_dir) < 0)
{
if (errno == EEXIST)
ereport(ERROR,
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 660f3185e6..868fba8cac 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -97,6 +97,7 @@
#include "access/xlog.h"
#include "bootstrap/bootstrap.h"
#include "catalog/pg_control.h"
+#include "common/file_perm.h"
#include "common/ip.h"
#include "lib/ilist.h"
#include "libpq/auth.h"
@@ -589,7 +590,7 @@ PostmasterMain(int argc, char *argv[])
/*
* for security, no dir or file created can be group or other accessible
*/
- umask(S_IRWXG | S_IRWXO);
+ umask(PG_MODE_MASK_DEFAULT);
/*
* Initialize random(3) so we don't get the same values in every run.
@@ -4492,7 +4493,7 @@ internal_forkexec(int argc, char *argv[], Port *port)
* As in OpenTemporaryFileInTablespace, try to make the temp-file
* directory
*/
- mkdir(PG_TEMP_FILES_DIR, S_IRWXU);
+ MakeDirectoryDefaultPerm(PG_TEMP_FILES_DIR);
fp = AllocateFile(tmpfilename, PG_BINARY_W);
if (!fp)
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index f70eea37df..ae23941f59 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -41,6 +41,7 @@
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
#include "storage/dsm.h"
+#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pg_shmem.h"
@@ -322,7 +323,7 @@ SysLoggerMain(int argc, char *argv[])
/*
* Also, create new directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ MakeDirectoryDefaultPerm(Log_directory);
}
if (strcmp(Log_filename, currentLogFilename) != 0)
{
@@ -564,7 +565,7 @@ SysLogger_Start(void)
/*
* Create log directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ MakeDirectoryDefaultPerm(Log_directory);
/*
* The initial logfile is created right in the postmaster, to verify that
diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c
index 1a0bae4c15..bfbd1fd382 100644
--- a/src/backend/replication/basebackup.c
+++ b/src/backend/replication/basebackup.c
@@ -19,6 +19,7 @@
#include "access/xlog_internal.h" /* for pg_start/stop_backup */
#include "catalog/catalog.h"
#include "catalog/pg_type.h"
+#include "common/file_perm.h"
#include "lib/stringinfo.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
@@ -930,7 +931,7 @@ sendFileWithContent(const char *filename, const char *content)
statbuf.st_gid = getegid();
#endif
statbuf.st_mtime = time(NULL);
- statbuf.st_mode = S_IRUSR | S_IWUSR;
+ statbuf.st_mode = pg_file_create_mode;
statbuf.st_size = len;
_tarWriteHeader(filename, NULL, &statbuf, false);
@@ -1628,7 +1629,7 @@ _tarWriteDir(const char *pathbuf, int basepathlen, struct stat *statbuf,
#else
if (pgwin32_is_junction(pathbuf))
#endif
- statbuf->st_mode = S_IFDIR | S_IRWXU;
+ statbuf->st_mode = S_IFDIR | pg_dir_create_mode;
return _tarWriteHeader(pathbuf + basepathlen + 1, NULL, statbuf, sizeonly);
}
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index fc9ef22b0b..3a98360b50 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -1166,13 +1166,14 @@ CreateSlotOnDisk(ReplicationSlot *slot)
* It's just barely possible that some previous effort to create or drop a
* slot with this name left a temp directory lying around. If that seems
* to be the case, try to remove it. If the rmtree() fails, we'll error
- * out at the mkdir() below, so we don't bother checking success.
+ * out at the MakeDirectoryDefaultPerm() below, so we don't bother checking
+ * success.
*/
if (stat(tmppath, &st) == 0 && S_ISDIR(st.st_mode))
rmtree(tmppath, true);
/* Create and fsync the temporary slot directory. */
- if (mkdir(tmppath, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(tmppath) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
diff --git a/src/backend/storage/file/copydir.c b/src/backend/storage/file/copydir.c
index ca6342db0d..ff980dc1f5 100644
--- a/src/backend/storage/file/copydir.c
+++ b/src/backend/storage/file/copydir.c
@@ -41,7 +41,7 @@ copydir(char *fromdir, char *todir, bool recurse)
char fromfile[MAXPGPATH * 2];
char tofile[MAXPGPATH * 2];
- if (mkdir(todir, S_IRWXU) != 0)
+ if (MakeDirectoryDefaultPerm(todir) != 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m", todir)));
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index d30a725f90..ebea857e1e 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -84,6 +84,7 @@
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "catalog/pg_tablespace.h"
+#include "common/file_perm.h"
#include "pgstat.h"
#include "portability/mem.h"
#include "storage/fd.h"
@@ -124,12 +125,6 @@
*/
#define FD_MINFREE 10
-/*
- * Default mode for created files, unless something else is specified using
- * the *Perm() function variants.
- */
-#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR)
-
/*
* A number of platforms allow individual processes to open many more files
* than they can really support when *many* processes do the same thing.
@@ -937,7 +932,7 @@ set_max_safe_fds(void)
int
BasicOpenFile(const char *fileName, int fileFlags)
{
- return BasicOpenFilePerm(fileName, fileFlags, PG_FILE_MODE_DEFAULT);
+ return BasicOpenFilePerm(fileName, fileFlags, pg_file_create_mode);
}
/*
@@ -1356,7 +1351,7 @@ FileInvalidate(File file)
File
PathNameOpenFile(const char *fileName, int fileFlags)
{
- return PathNameOpenFilePerm(fileName, fileFlags, PG_FILE_MODE_DEFAULT);
+ return PathNameOpenFilePerm(fileName, fileFlags, pg_file_create_mode);
}
/*
@@ -1434,7 +1429,7 @@ PathNameOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode)
void
PathNameCreateTemporaryDir(const char *basedir, const char *directory)
{
- if (mkdir(directory, S_IRWXU) < 0)
+ if (MakeDirectoryDefaultPerm(directory) < 0)
{
if (errno == EEXIST)
return;
@@ -1444,14 +1439,14 @@ PathNameCreateTemporaryDir(const char *basedir, const char *directory)
* EEXIST to close a race against another process following the same
* algorithm.
*/
- if (mkdir(basedir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(basedir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary directory \"%s\": %m",
basedir)));
/* Try again. */
- if (mkdir(directory, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakeDirectoryDefaultPerm(directory) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary subdirectory \"%s\": %m",
@@ -1601,11 +1596,11 @@ OpenTemporaryFileInTablespace(Oid tblspcOid, bool rejectError)
* We might need to create the tablespace's tempfile directory, if no
* one has yet done so.
*
- * Don't check for error from mkdir; it could fail if someone else
- * just did the same thing. If it doesn't work then we'll bomb out on
+ * Don't check error from MakeDirectoryDefaultPerm; it could fail if someone
+ * else just did the same thing. If it doesn't work then we'll bomb out on
* the second create attempt, instead.
*/
- mkdir(tempdirpath, S_IRWXU);
+ MakeDirectoryDefaultPerm(tempdirpath);
file = PathNameOpenFile(tempfilepath,
O_RDWR | O_CREAT | O_TRUNC | PG_BINARY);
@@ -2401,7 +2396,7 @@ TryAgain:
int
OpenTransientFile(const char *fileName, int fileFlags)
{
- return OpenTransientFilePerm(fileName, fileFlags, PG_FILE_MODE_DEFAULT);
+ return OpenTransientFilePerm(fileName, fileFlags, pg_file_create_mode);
}
/*
@@ -3554,3 +3549,16 @@ fsync_parent_path(const char *fname, int elevel)
return 0;
}
+
+/*
+ * Create a directory using pg_dir_create_mode for permissions
+ *
+ * Directories in PGDATA should normally have specific permissions -- when
+ * using this function the caller does not need to know what they should be.
+ * For permissions other than the default use mkdir() directly.
+ */
+int
+MakeDirectoryDefaultPerm(const char *directoryName)
+{
+ return mkdir(directoryName, pg_dir_create_mode);
+}
diff --git a/src/backend/storage/ipc/dsm_impl.c b/src/backend/storage/ipc/dsm_impl.c
index 67e76b98fe..0ba45b4f93 100644
--- a/src/backend/storage/ipc/dsm_impl.c
+++ b/src/backend/storage/ipc/dsm_impl.c
@@ -60,6 +60,7 @@
#ifdef HAVE_SYS_SHM_H
#include <sys/shm.h>
#endif
+#include "common/file_perm.h"
#include "pgstat.h"
#include "portability/mem.h"
@@ -285,7 +286,7 @@ dsm_impl_posix(dsm_op op, dsm_handle handle, Size request_size,
* returning.
*/
flags = O_RDWR | (op == DSM_OP_CREATE ? O_CREAT | O_EXCL : 0);
- if ((fd = shm_open(name, flags, 0600)) == -1)
+ if ((fd = shm_open(name, flags, PG_FILE_MODE_DEFAULT)) == -1)
{
if (errno != EEXIST)
ereport(elevel,
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index fc0a9c0756..4f2370a027 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -137,6 +137,10 @@ proc_exit(int code)
else
snprintf(gprofDirName, 32, "gprof/%d", (int) getpid());
+ /*
+ * Use mkdir() instead of MakeDirectoryDefaultPerm() here because non-default
+ * permissions are required.
+ */
mkdir("gprof", S_IRWXU | S_IRWXG | S_IRWXO);
mkdir(gprofDirName, S_IRWXU | S_IRWXG | S_IRWXO);
chdir(gprofDirName);
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 87ed7d3f71..f8f08f3f88 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -32,6 +32,7 @@
#include "access/htup_details.h"
#include "catalog/pg_authid.h"
+#include "common/file_perm.h"
#include "libpq/libpq.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
@@ -831,7 +832,7 @@ CreateLockFile(const char *filename, bool amPostmaster,
* Think not to make the file protection weaker than 0600. See
* comments below.
*/
- fd = open(filename, O_RDWR | O_CREAT | O_EXCL, 0600);
+ fd = open(filename, O_RDWR | O_CREAT | O_EXCL, pg_file_create_mode);
if (fd >= 0)
break; /* Success; exit the retry loop */
@@ -848,7 +849,7 @@ CreateLockFile(const char *filename, bool amPostmaster,
* Read the file to get the old owner's PID. Note race condition
* here: file might have been deleted since we tried to create it.
*/
- fd = open(filename, O_RDONLY, 0600);
+ fd = open(filename, O_RDONLY, pg_file_create_mode);
if (fd < 0)
{
if (errno == ENOENT)
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 78990f5a27..83472d9ceb 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -64,6 +64,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_class.h"
#include "catalog/pg_collation.h"
+#include "common/file_perm.h"
#include "common/file_utils.h"
#include "common/restricted_token.h"
#include "common/username.h"
@@ -1170,7 +1171,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, pg_file_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1190,7 +1191,7 @@ setup_config(void)
sprintf(path, "%s/postgresql.auto.conf", pg_data);
writefile(path, autoconflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, pg_file_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1277,7 +1278,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_hba.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, pg_file_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1293,7 +1294,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_ident.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, pg_file_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -2692,7 +2693,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (pg_mkdir_p(pg_data, S_IRWXU) != 0)
+ if (pg_mkdir_p(pg_data, pg_dir_create_mode) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2710,7 +2711,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (chmod(pg_data, S_IRWXU) != 0)
+ if (chmod(pg_data, pg_dir_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2778,7 +2779,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (pg_mkdir_p(xlog_dir, S_IRWXU) != 0)
+ if (pg_mkdir_p(xlog_dir, pg_dir_create_mode) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2796,7 +2797,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (chmod(xlog_dir, S_IRWXU) != 0)
+ if (chmod(xlog_dir, pg_dir_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2846,7 +2847,7 @@ create_xlog_or_symlink(void)
else
{
/* Without -X option, just make the subdirectory normally */
- if (mkdir(subdirloc, S_IRWXU) < 0)
+ if (mkdir(subdirloc, pg_dir_create_mode) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, subdirloc, strerror(errno));
@@ -2882,7 +2883,8 @@ initialize_data_directory(void)
setup_signals();
- umask(S_IRWXG | S_IRWXO);
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_DEFAULT);
create_data_directory();
@@ -2902,7 +2904,7 @@ initialize_data_directory(void)
* The parent directory already exists, so we only need mkdir() not
* pg_mkdir_p() here, which avoids some failure modes; cf bug #13853.
*/
- if (mkdir(path, S_IRWXU) < 0)
+ if (mkdir(path, pg_dir_create_mode) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, path, strerror(errno));
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index c0cfa6e92c..9dfb2ed96f 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 15;
+use Test::More tests => 16;
my $tempdir = TestLib::tempdir;
my $xlogdir = "$tempdir/pgxlog";
@@ -45,6 +45,15 @@ mkdir $datadir;
command_ok([ 'initdb', '-N', '-T', 'german', '-X', $xlogdir, $datadir ],
'successful creation');
+
+ # Permissions on PGDATA should be default
+ SKIP:
+ {
+ skip "unix-style permissions not supported on Windows", 1 if ($windows_os);
+
+ ok(check_mode_recursive($datadir, 0700, 0600),
+ "check PGDATA permissions");
+ }
}
command_ok([ 'initdb', '-S', $datadir ], 'sync only');
command_fails([ 'initdb', $datadir ], 'existing data directory');
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 8610fe8a1a..32b41e313c 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -27,6 +27,7 @@
#endif
#include "access/xlog_internal.h"
+#include "common/file_perm.h"
#include "common/file_utils.h"
#include "common/string.h"
#include "fe_utils/string_utils.h"
@@ -629,7 +630,7 @@ StartLogStreamer(char *startpos, uint32 timeline, char *sysidentifier)
PQserverVersion(conn) < MINIMUM_VERSION_FOR_PG_WAL ?
"pg_xlog" : "pg_wal");
- if (pg_mkdir_p(statusdir, S_IRWXU) != 0 && errno != EEXIST)
+ if (pg_mkdir_p(statusdir, pg_dir_create_mode) != 0 && errno != EEXIST)
{
fprintf(stderr,
_("%s: could not create directory \"%s\": %s\n"),
@@ -685,7 +686,7 @@ verify_dir_is_empty_or_create(char *dirname, bool *created, bool *found)
/*
* Does not exist, so create
*/
- if (pg_mkdir_p(dirname, S_IRWXU) == -1)
+ if (pg_mkdir_p(dirname, pg_dir_create_mode) == -1)
{
fprintf(stderr,
_("%s: could not create directory \"%s\": %s\n"),
@@ -1129,7 +1130,7 @@ ReceiveTarFile(PGconn *conn, PGresult *res, int rownum)
tarCreateHeader(header, "recovery.conf", NULL,
recoveryconfcontents->len,
- 0600, 04000, 02000,
+ pg_file_create_mode, 04000, 02000,
time(NULL));
padding = ((recoveryconfcontents->len + 511) & ~511) - recoveryconfcontents->len;
@@ -1441,7 +1442,7 @@ ReceiveAndUnpackTarFile(PGconn *conn, PGresult *res, int rownum)
* Directory
*/
filename[strlen(filename) - 1] = '\0'; /* Remove trailing slash */
- if (mkdir(filename, S_IRWXU) != 0)
+ if (mkdir(filename, pg_dir_create_mode) != 0)
{
/*
* When streaming WAL, pg_wal (or pg_xlog for pre-9.6
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index e2f1465472..ec54b555ed 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -5,7 +5,7 @@ use Config;
use File::Basename qw(basename dirname);
use PostgresNode;
use TestLib;
-use Test::More tests => 104;
+use Test::More tests => 105;
program_help_ok('pg_basebackup');
program_version_ok('pg_basebackup');
@@ -15,6 +15,9 @@ my $tempdir = TestLib::tempdir;
my $node = get_new_node('main');
+# Set umask so test directories and files are created with default permissions
+umask(0077);
+
# Initialize node without replication settings
$node->init(extra => [ '--data-checksums' ]);
$node->start;
@@ -93,6 +96,15 @@ $node->command_ok([ 'pg_basebackup', '-D', "$tempdir/backup", '-X', 'none' ],
'pg_basebackup runs');
ok(-f "$tempdir/backup/PG_VERSION", 'backup was created');
+# Permissions on backup should be default
+SKIP:
+{
+ skip "unix-style permissions not supported on Windows", 1 if ($windows_os);
+
+ ok(check_mode_recursive("$tempdir/backup", 0700, 0600),
+ "check backup dir permissions");
+}
+
# Only archive_status directory should be copied in pg_wal/.
is_deeply(
[ sort(slurp_dir("$tempdir/backup/pg_wal/")) ],
diff --git a/src/bin/pg_basebackup/t/020_pg_receivewal.pl b/src/bin/pg_basebackup/t/020_pg_receivewal.pl
index 64e3a35a87..19c106d9f5 100644
--- a/src/bin/pg_basebackup/t/020_pg_receivewal.pl
+++ b/src/bin/pg_basebackup/t/020_pg_receivewal.pl
@@ -2,12 +2,15 @@ use strict;
use warnings;
use TestLib;
use PostgresNode;
-use Test::More tests => 18;
+use Test::More tests => 19;
program_help_ok('pg_receivewal');
program_version_ok('pg_receivewal');
program_options_handling_ok('pg_receivewal');
+# Set umask so test directories and files are created with default permissions
+umask(0077);
+
my $primary = get_new_node('primary');
$primary->init(allows_streaming => 1);
$primary->start;
@@ -56,3 +59,12 @@ $primary->command_ok(
[ 'pg_receivewal', '-D', $stream_dir, '--verbose',
'--endpos', $nextlsn, '--synchronous', '--no-loop' ],
'streaming some WAL with --synchronous');
+
+# Permissions on WAL files should be default
+SKIP:
+{
+ skip "unix-style permissions not supported on Windows", 1 if ($windows_os);
+
+ ok(check_mode_recursive($stream_dir, 0700, 0600),
+ "check stream dir permissions");
+}
diff --git a/src/bin/pg_basebackup/walmethods.c b/src/bin/pg_basebackup/walmethods.c
index b4558a0184..267a40debb 100644
--- a/src/bin/pg_basebackup/walmethods.c
+++ b/src/bin/pg_basebackup/walmethods.c
@@ -22,6 +22,7 @@
#endif
#include "pgtar.h"
+#include "common/file_perm.h"
#include "common/file_utils.h"
#include "receivelog.h"
@@ -89,7 +90,7 @@ dir_open_for_write(const char *pathname, const char *temp_suffix, size_t pad_to_
* does not do any system calls to fsync() to make changes permanent on
* disk.
*/
- fd = open(tmppath, O_WRONLY | O_CREAT | PG_BINARY, S_IRUSR | S_IWUSR);
+ fd = open(tmppath, O_WRONLY | O_CREAT | PG_BINARY, pg_file_create_mode);
if (fd < 0)
return NULL;
@@ -534,7 +535,8 @@ tar_open_for_write(const char *pathname, const char *temp_suffix, size_t pad_to_
* We open the tar file only when we first try to write to it.
*/
tar_data->fd = open(tar_data->tarfilename,
- O_WRONLY | O_CREAT | PG_BINARY, S_IRUSR | S_IWUSR);
+ O_WRONLY | O_CREAT | PG_BINARY,
+ pg_file_create_mode);
if (tar_data->fd < 0)
return NULL;
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index 9bc830b085..e83a7179ea 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -25,6 +25,7 @@
#include "catalog/pg_control.h"
#include "common/controldata_utils.h"
+#include "common/file_perm.h"
#include "getopt_long.h"
#include "utils/pidfile.h"
@@ -2170,7 +2171,8 @@ main(int argc, char **argv)
*/
argv0 = argv[0];
- umask(S_IRWXG | S_IRWXO);
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_DEFAULT);
/* support --help and --version even if invoked as root */
if (argc > 1)
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl
index 5da4746cb4..067a084c87 100644
--- a/src/bin/pg_ctl/t/001_start_stop.pl
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -4,7 +4,7 @@ use warnings;
use Config;
use PostgresNode;
use TestLib;
-use Test::More tests => 19;
+use Test::More tests => 21;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -57,9 +57,23 @@ command_ok([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ], 'pg_ctl stop');
command_fails([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ],
'second pg_ctl stop fails');
+# Log file for default permission test. The permissions won't be checked on
+# Windows but we still want to do the restart test.
+my $logFileName = "$tempdir/data/perm-test-600.log";
+
command_ok(
- [ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
+ [ 'pg_ctl', 'restart', '-D', "$tempdir/data", '-l', $logFileName ],
'pg_ctl restart with server not running');
+
+# Permissions on log file should be default
+SKIP:
+{
+ skip "unix-style permissions not supported on Windows", 2 if ($windows_os);
+
+ ok(-f $logFileName);
+ ok(check_mode_recursive("$tempdir/data", 0700, 0600));
+}
+
command_ok([ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
'pg_ctl restart with server running');
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index eba7c5fdee..bdf71886ee 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -52,6 +52,7 @@
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
#include "common/fe_memutils.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "storage/large_object.h"
#include "pg_getopt.h"
@@ -967,7 +968,7 @@ RewriteControlFile(void)
fd = open(XLOG_CONTROL_FILE,
O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ pg_file_create_mode);
if (fd < 0)
{
fprintf(stderr, _("%s: could not create pg_control file: %s\n"),
@@ -1249,7 +1250,7 @@ WriteEmptyXLOG(void)
unlink(path);
fd = open(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ pg_file_create_mode);
if (fd < 0)
{
fprintf(stderr, _("%s: could not open file \"%s\": %s\n"),
diff --git a/src/bin/pg_resetwal/t/001_basic.pl b/src/bin/pg_resetwal/t/001_basic.pl
index 1b157cb555..0d6ab20073 100644
--- a/src/bin/pg_resetwal/t/001_basic.pl
+++ b/src/bin/pg_resetwal/t/001_basic.pl
@@ -3,7 +3,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 11;
+use Test::More tests => 12;
program_help_ok('pg_resetwal');
program_version_ok('pg_resetwal');
@@ -15,3 +15,13 @@ $node->init;
command_like([ 'pg_resetwal', '-n', $node->data_dir ],
qr/checkpoint/,
'pg_resetwal -n produces output');
+
+
+# Permissions on PGDATA should be default
+SKIP:
+{
+ skip "unix-style permissions not supported on Windows", 1 if ($windows_os);
+
+ ok(check_mode_recursive($node->data_dir, 0700, 0600),
+ 'check PGDATA permissions');
+}
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index 00b5b42dd7..7b632c7dcd 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -237,6 +237,10 @@ sub run_pg_rewind
"$tmp_folder/master-postgresql.conf.tmp",
"$master_pgdata/postgresql.conf");
+ chmod(0600, "$master_pgdata/postgresql.conf")
+ or BAIL_OUT(
+ "unable to set permissions for $master_pgdata/postgresql.conf");
+
# Plug-in rewound node to the now-promoted standby node
my $port_standby = $node_standby->port;
$node_master->append_conf(
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index f491ed7f5c..94bcc13ae8 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -18,6 +18,7 @@
#include <fcntl.h>
#include <unistd.h>
+#include "common/file_perm.h"
#include "file_ops.h"
#include "filemap.h"
#include "logging.h"
@@ -57,7 +58,7 @@ open_target_file(const char *path, bool trunc)
mode = O_WRONLY | O_CREAT | PG_BINARY;
if (trunc)
mode |= O_TRUNC;
- dstfd = open(dstpath, mode, 0600);
+ dstfd = open(dstpath, mode, pg_file_create_mode);
if (dstfd < 0)
pg_fatal("could not open target file \"%s\": %s\n",
dstpath, strerror(errno));
@@ -198,7 +199,7 @@ truncate_target_file(const char *path, off_t newsize)
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
- fd = open(dstpath, O_WRONLY, 0);
+ fd = open(dstpath, O_WRONLY, pg_file_create_mode);
if (fd < 0)
pg_fatal("could not open file \"%s\" for truncation: %s\n",
dstpath, strerror(errno));
@@ -219,7 +220,7 @@ create_target_dir(const char *path)
return;
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
- if (mkdir(dstpath, S_IRWXU) != 0)
+ if (mkdir(dstpath, pg_dir_create_mode) != 0)
pg_fatal("could not create directory \"%s\": %s\n",
dstpath, strerror(errno));
}
diff --git a/src/bin/pg_rewind/t/001_basic.pl b/src/bin/pg_rewind/t/001_basic.pl
index 736f34eae3..1b0f823b0c 100644
--- a/src/bin/pg_rewind/t/001_basic.pl
+++ b/src/bin/pg_rewind/t/001_basic.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 8;
+use Test::More tests => 10;
use RewindTest;
@@ -86,6 +86,15 @@ in master, before promotion
),
'tail-copy');
+ # Permissions on PGDATA should be default
+ SKIP:
+ {
+ skip "unix-style permissions not supported on Windows", 1 if ($windows_os);
+
+ ok(check_mode_recursive($node_master->data_dir(), 0700, 0600),
+ 'check PGDATA permissions');
+ }
+
RewindTest::clean_rewind_test();
}
diff --git a/src/bin/pg_upgrade/file.c b/src/bin/pg_upgrade/file.c
index f38bfacf02..f68211aa20 100644
--- a/src/bin/pg_upgrade/file.c
+++ b/src/bin/pg_upgrade/file.c
@@ -10,6 +10,7 @@
#include "postgres_fe.h"
#include "access/visibilitymap.h"
+#include "common/file_perm.h"
#include "pg_upgrade.h"
#include "storage/bufpage.h"
#include "storage/checksum.h"
@@ -44,7 +45,7 @@ copyFile(const char *src, const char *dst,
schemaName, relName, src, strerror(errno));
if ((dest_fd = open(dst, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR)) < 0)
+ pg_file_create_mode)) < 0)
pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
schemaName, relName, dst, strerror(errno));
@@ -151,7 +152,7 @@ rewriteVisibilityMap(const char *fromfile, const char *tofile,
schemaName, relName, fromfile, strerror(errno));
if ((dst_fd = open(tofile, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR)) < 0)
+ pg_file_create_mode)) < 0)
pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
schemaName, relName, tofile, strerror(errno));
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index d12412799f..59df6fd88f 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -38,6 +38,7 @@
#include "pg_upgrade.h"
#include "catalog/pg_class.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "fe_utils/string_utils.h"
@@ -79,7 +80,7 @@ main(int argc, char **argv)
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_upgrade"));
/* Ensure that all files created by pg_upgrade are non-world-readable */
- umask(S_IRWXG | S_IRWXO);
+ umask(PG_MODE_MASK_DEFAULT);
parseCommandLine(argc, argv);
diff --git a/src/bin/pg_upgrade/test.sh b/src/bin/pg_upgrade/test.sh
index 39983abea1..574639d47e 100644
--- a/src/bin/pg_upgrade/test.sh
+++ b/src/bin/pg_upgrade/test.sh
@@ -230,6 +230,17 @@ standard_initdb 'initdb'
pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "${PGDATA}" -b "$oldbindir" -B "$bindir" -p "$PGPORT" -P "$PGPORT"
+# make sure all directories and files have correct permissions
+if [ $(find ${PGDATA} -type f ! -perm 600 | wc -l) -ne 0 ]; then
+ echo "files in PGDATA with permission != 600";
+ exit 1;
+fi
+
+if [ $(find ${PGDATA} -type d ! -perm 700 | wc -l) -ne 0 ]; then
+ echo "directories in PGDATA with permission != 700";
+ exit 1;
+fi
+
pg_ctl start -l "$logdir/postmaster2.log" -o "$POSTMASTER_OPTS" -w
case $testhost in
diff --git a/src/common/Makefile b/src/common/Makefile
index 873fbb6438..e9e75867f3 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -40,8 +40,8 @@ override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
override CPPFLAGS := -DFRONTEND $(CPPFLAGS)
LIBS += $(PTHREAD_LIBS)
-OBJS_COMMON = base64.o config_info.o controldata_utils.o exec.o ip.o \
- keywords.o md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
+OBJS_COMMON = base64.o config_info.o controldata_utils.o exec.o file_perm.o \
+ ip.o keywords.o md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
rmtree.o saslprep.o scram-common.o string.o unicode_norm.o \
username.o wait_error.o
diff --git a/src/common/file_perm.c b/src/common/file_perm.c
new file mode 100644
index 0000000000..5f0c02fb04
--- /dev/null
+++ b/src/common/file_perm.c
@@ -0,0 +1,19 @@
+/*-------------------------------------------------------------------------
+ *
+ * File and directory permission routines
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/file_perm.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include <sys/stat.h>
+
+#include "common/file_perm.h"
+
+/* Modes for creating directories and files in the data directory */
+int pg_dir_create_mode = PG_DIR_MODE_DEFAULT;
+int pg_file_create_mode = PG_FILE_MODE_DEFAULT;
diff --git a/src/include/common/file_perm.h b/src/include/common/file_perm.h
new file mode 100644
index 0000000000..46b91f0c7b
--- /dev/null
+++ b/src/include/common/file_perm.h
@@ -0,0 +1,32 @@
+/*-------------------------------------------------------------------------
+ *
+ * File and directory permission constants
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/file_perm.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FILE_PERM_H
+#define FILE_PERM_H
+
+/*
+ * Default mode mask for data directory permissions only allows the owner to
+ * read/write directories and files.
+ */
+#define PG_MODE_MASK_DEFAULT (S_IRWXG | S_IRWXO)
+
+/* Default mode for creating directories */
+#define PG_DIR_MODE_DEFAULT S_IRWXU
+
+/* Default mode for creating files */
+#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR)
+
+/* Modes for creating directories and files in the data directory */
+extern int pg_dir_create_mode;
+extern int pg_file_create_mode;
+
+#endif /* FILE_PERM_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index e49b42ce86..340dbd4d3b 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -112,6 +112,9 @@ extern int CloseTransientFile(int fd);
extern int BasicOpenFile(const char *fileName, int fileFlags);
extern int BasicOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode);
+ /* Make a directory with default permissions */
+extern int MakeDirectoryDefaultPerm(const char *directoryName);
+
/* Miscellaneous support routines */
extern void InitFileAccess(void);
extern void set_max_safe_fds(void);
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 80188315f1..76e571b98c 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -484,6 +484,9 @@ sub append_conf
my $conffile = $self->data_dir . '/' . $filename;
TestLib::append_to_file($conffile, $str . "\n");
+
+ chmod(0600, $conffile)
+ or die("unable to set permissions for $conffile");
}
=pod
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index b6862688d4..93610e4bc4 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -13,8 +13,11 @@ use warnings;
use Config;
use Cwd;
use Exporter 'import';
+use Fcntl qw(:mode);
use File::Basename;
+use File::Find;
use File::Spec;
+use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
use SimpleTee;
@@ -27,6 +30,7 @@ our @EXPORT = qw(
slurp_dir
slurp_file
append_to_file
+ check_mode_recursive
check_pg_config
system_or_bail
system_log
@@ -240,6 +244,75 @@ sub append_to_file
close $fh;
}
+# Check that all file/dir modes in a directory match the expected values,
+# ignoring the mode of any specified files.
+sub check_mode_recursive
+{
+ my ($dir, $expected_dir_mode, $expected_file_mode, $ignore_list) = @_;
+
+ # Result defaults to true
+ my $result = 1;
+
+ find
+ (
+ {follow_fast => 1,
+ wanted =>
+ sub
+ {
+ my $file_stat = stat($File::Find::name);
+
+ # Is file in the ignore list?
+ foreach my $ignore ($ignore_list ? @{$ignore_list} : [])
+ {
+ if ("$dir/$ignore" eq $File::Find::name)
+ {
+ return;
+ }
+ }
+
+ defined($file_stat)
+ or die("unable to stat $File::Find::name");
+
+ my $file_mode = S_IMODE($file_stat->mode);
+
+ # Is this a file?
+ if (S_ISREG($file_stat->mode))
+ {
+ if ($file_mode != $expected_file_mode)
+ {
+ print(*STDERR,
+ sprintf("$File::Find::name mode must be %04o\n",
+ $expected_file_mode));
+
+ $result = 0;
+ return;
+ }
+ }
+ # Else a directory?
+ elsif (S_ISDIR($file_stat->mode))
+ {
+ if ($file_mode != $expected_dir_mode)
+ {
+ print(*STDERR,
+ sprintf("$File::Find::name mode must be %04o\n",
+ $expected_dir_mode));
+
+ $result = 0;
+ return;
+ }
+ }
+ # Else something we can't handle
+ else
+ {
+ die "unknown file type for $File::Find::name";
+ }
+ }},
+ $dir
+ );
+
+ return $result;
+}
+
# Check presence of a given regexp within pg_config.h for the installation
# where tests are running, returning a match status result depending on
# that.
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 41d720880a..1d3ed6b0b1 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -111,8 +111,8 @@ sub mkvcbuild
}
our @pgcommonallfiles = qw(
- base64.c config_info.c controldata_utils.c exec.c ip.c keywords.c
- md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
+ base64.c config_info.c controldata_utils.c exec.c file_perm.c ip.c
+ keywords.c md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
saslprep.c scram-common.c string.c unicode_norm.c username.c
wait_error.c);
--
2.14.3 (Apple Git-98)
group-access-v15-02-group.patchtext/plain; charset=UTF-8; name=group-access-v15-02-group.patch; x-mac-creator=0; x-mac-type=0Download
From 9555d403c5188d4dc8cad70c2f4a509c3e8440c3 Mon Sep 17 00:00:00 2001
From: David Steele <david@pgmasters.net>
Date: Thu, 5 Apr 2018 11:37:30 -0400
Subject: [PATCH 2/2] Allow group access on PGDATA.
This includes backend changes, utility changes (initdb, pg_ctl, pg_upgrade, etc.) and tests for each utility.
---
doc/src/sgml/ref/initdb.sgml | 19 +++++
doc/src/sgml/ref/pg_basebackup.sgml | 6 ++
doc/src/sgml/ref/pg_receivewal.sgml | 6 ++
doc/src/sgml/ref/pg_recvlogical.sgml | 11 +++
doc/src/sgml/runtime.sgml | 14 +++-
src/backend/bootstrap/bootstrap.c | 12 +--
src/backend/postmaster/postmaster.c | 81 ++----------------
src/backend/tcop/postgres.c | 3 +-
src/backend/utils/init/globals.c | 9 ++
src/backend/utils/init/miscinit.c | 120 ++++++++++++++++++++++++---
src/backend/utils/misc/guc.c | 25 ++++++
src/bin/initdb/initdb.c | 24 +++++-
src/bin/initdb/t/001_initdb.pl | 20 ++++-
src/bin/pg_basebackup/pg_basebackup.c | 22 +++--
src/bin/pg_basebackup/pg_receivewal.c | 7 ++
src/bin/pg_basebackup/pg_recvlogical.c | 7 ++
src/bin/pg_basebackup/streamutil.c | 69 +++++++++++++++
src/bin/pg_basebackup/t/010_pg_basebackup.pl | 26 ++++--
src/bin/pg_ctl/pg_ctl.c | 12 ++-
src/bin/pg_ctl/t/001_start_stop.pl | 25 +++++-
src/bin/pg_resetwal/pg_resetwal.c | 10 +++
src/bin/pg_rewind/RewindTest.pm | 9 +-
src/bin/pg_rewind/pg_rewind.c | 11 +++
src/bin/pg_rewind/t/002_databases.pl | 13 ++-
src/bin/pg_upgrade/pg_upgrade.c | 12 ++-
src/bin/pg_upgrade/test.sh | 16 ++--
src/common/file_perm.c | 68 +++++++++++++++
src/include/common/file_perm.h | 20 +++++
src/include/miscadmin.h | 2 +
src/test/perl/PostgresNode.pm | 27 +++++-
src/test/perl/TestLib.pm | 25 ++++++
31 files changed, 603 insertions(+), 128 deletions(-)
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 949b5a220f..1d41e91794 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -76,6 +76,14 @@ PostgreSQL documentation
to do so.)
</para>
+ <para>
+ For security reasons the new cluster created by <command>initdb</command>
+ will only be accessible by the cluster owner by default. The
+ <option>--allow-group-access</option> option allows any user in the same
+ group as the cluster owner to read files in the cluster. This is useful
+ for performing backups as a non-privileged user.
+ </para>
+
<para>
<command>initdb</command> initializes the database cluster's default
locale and character set encoding. The character set encoding,
@@ -301,6 +309,17 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-g</option></term>
+ <term><option>--allow-group-access</option></term>
+ <listitem>
+ <para>
+ Allows users in the same group as the cluster owner to read all cluster
+ files created by <command>initdb</command>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-W</option></term>
<term><option>--pwprompt</option></term>
diff --git a/doc/src/sgml/ref/pg_basebackup.sgml b/doc/src/sgml/ref/pg_basebackup.sgml
index 95045669c9..fc1edf4864 100644
--- a/doc/src/sgml/ref/pg_basebackup.sgml
+++ b/doc/src/sgml/ref/pg_basebackup.sgml
@@ -737,6 +737,12 @@ PostgreSQL documentation
or later.
</para>
+ <para>
+ <application>pg_basebackup</application> will preserve group permissions in
+ both the <literal>plain</literal> and <literal>tar</literal> formats if group
+ permissions are enabled on the source cluster.
+ </para>
+
</refsect1>
<refsect1>
diff --git a/doc/src/sgml/ref/pg_receivewal.sgml b/doc/src/sgml/ref/pg_receivewal.sgml
index e3f2ce1fcb..a18ddd4bff 100644
--- a/doc/src/sgml/ref/pg_receivewal.sgml
+++ b/doc/src/sgml/ref/pg_receivewal.sgml
@@ -425,6 +425,12 @@ PostgreSQL documentation
not keep up with fetching the WAL data.
</para>
+ <para>
+ <application>pg_receivewal</application> will preserve group permissions on
+ the received WAL files if group permissions are enabled on the source
+ cluster.
+ </para>
+
</refsect1>
<refsect1>
diff --git a/doc/src/sgml/ref/pg_recvlogical.sgml b/doc/src/sgml/ref/pg_recvlogical.sgml
index a79ca20084..141c5cddce 100644
--- a/doc/src/sgml/ref/pg_recvlogical.sgml
+++ b/doc/src/sgml/ref/pg_recvlogical.sgml
@@ -399,6 +399,17 @@ PostgreSQL documentation
</para>
</refsect1>
+ <refsect1>
+ <title>Notes</title>
+
+ <para>
+ <application>pg_recvlogical</application> will preserve group permissions on
+ the received WAL files if group permissions are enabled on the source
+ cluster.
+ </para>
+
+ </refsect1>
+
<refsect1>
<title>Examples</title>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 587b430527..40ce3d9813 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -137,7 +137,10 @@ postgres$ <userinput>initdb -D /usr/local/pgsql/data</userinput>
database, it is essential that it be secured from unauthorized
access. <command>initdb</command> therefore revokes access
permissions from everyone but the
- <productname>PostgreSQL</productname> user.
+ <productname>PostgreSQL</productname> user, and optionally, group.
+ Group access, when enabled, is read-only. This allows an unprivileged
+ user in the <productname>PostgreSQL</productname> group to take a backup of
+ the cluster data or perform other operations that only require read access.
</para>
<para>
@@ -2194,6 +2197,15 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
member of the group that has access to those certificate and key files.
</para>
+ <para>
+ If the data directory allows group read access then certificate files may
+ need to be located outside of the data directory in order to conform to the
+ security requirements outlined above. Generally, group access is enabled
+ to allow an unprivileged user to backup the database, and in that case the
+ backup software will not be able to read the certificate files and will
+ likely error.
+ </para>
+
<para>
If the private key is protected with a passphrase, the
server will prompt for the passphrase and will not start until it has
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 28ff2f0979..cc9436a6be 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 868fba8cac..21343e3449 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,9 @@ PostmasterMain(int argc, char *argv[])
IsPostmasterEnvironment = true;
/*
- * for security, no dir or file created can be group or other accessible
+ * By default, no directory or file created can be group or other
+ * accessible. This may be modified later depending on the permissions of
+ * the data directory.
*/
umask(PG_MODE_MASK_DEFAULT);
@@ -877,6 +879,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 +1474,14 @@ getInstallationPaths(const char *argv0)
*/
}
-
/*
- * Validate the proposed data directory
+ * Check that pg_control exists in the correct location in the 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 6fc1cc272b..ec3ef1a0a6 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 c1f0441b08..1d463a9ade 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_DEFAULT;
+
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 f8f08f3f88..d1dc103e94 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -88,6 +88,105 @@ SetDatabasePath(const char *path)
DatabasePath = MemoryContextStrdup(TopMemoryContext, 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);
+
+ if (pg_dir_create_mode == PG_DIR_MODE_GROUP)
+ {
+ umask(pg_mode_mask);
+
+ SetConfigOption("data_directory_mode", "0750", PGC_INTERNAL,
+ PGC_S_OVERRIDE);
+ }
+#endif
+
+ /* Check for for PG_VERSION */
+ ValidatePgVersion(DataDir);
+}
+
/*
* Set data directory, but make sure it's an absolute path. Use this,
* never set DataDir directly.
@@ -829,7 +928,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 +998,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 260ae264d8..fa92ce2e68 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -192,6 +192,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,
@@ -2042,6 +2043,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."),
@@ -10731,4 +10747,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"
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 83472d9ceb..a40c07649f 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -1168,6 +1168,19 @@ setup_config(void)
"password_encryption = scram-sha-256");
}
+ /*
+ * If group access has been enabled for the cluster then it makes sense
+ * to ensure that the log files also allow group access. Otherwise a
+ * backup from a user in the group would fail if the log files were not
+ * relocated.
+ */
+ if (pg_dir_create_mode == PG_DIR_MODE_GROUP)
+ {
+ conflines = replace_token(conflines,
+ "#log_file_mode = 0600",
+ "log_file_mode = 0640");
+ }
+
snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
writefile(path, conflines);
@@ -2312,6 +2325,7 @@ usage(const char *progname)
printf(_(" --auth-local=METHOD default authentication method for local-socket connections\n"));
printf(_(" [-D, --pgdata=]DATADIR location for this database cluster\n"));
printf(_(" -E, --encoding=ENCODING set default encoding for new databases\n"));
+ printf(_(" -g, --allow-group-access allow group read/execute on data directory\n"));
printf(_(" --locale=LOCALE set default locale for new databases\n"));
printf(_(" --lc-collate=, --lc-ctype=, --lc-messages=LOCALE\n"
" --lc-monetary=, --lc-numeric=, --lc-time=LOCALE\n"
@@ -2883,8 +2897,8 @@ initialize_data_directory(void)
setup_signals();
- /* Set dir/file mode mask */
- umask(PG_MODE_MASK_DEFAULT);
+ /* Set mask based on requested PGDATA permissions */
+ umask(pg_mode_mask);
create_data_directory();
@@ -3018,6 +3032,7 @@ main(int argc, char *argv[])
{"waldir", required_argument, NULL, 'X'},
{"wal-segsize", required_argument, NULL, 12},
{"data-checksums", no_argument, NULL, 'k'},
+ {"allow-group-access", no_argument, NULL, 'g'},
{NULL, 0, NULL, 0}
};
@@ -3059,7 +3074,7 @@ main(int argc, char *argv[])
/* process command-line options */
- while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:", long_options, &option_index)) != -1)
+ while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:g", long_options, &option_index)) != -1)
{
switch (c)
{
@@ -3153,6 +3168,9 @@ main(int argc, char *argv[])
case 12:
str_wal_segment_size_mb = pg_strdup(optarg);
break;
+ case 'g':
+ SetDataDirectoryCreatePerm(PG_DIR_MODE_GROUP);
+ break;
default:
/* getopt_long already emitted a complaint */
fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index 9dfb2ed96f..8609d2ecbc 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -4,9 +4,11 @@
use strict;
use warnings;
+use Fcntl ':mode';
+use File::stat qw{lstat};
use PostgresNode;
use TestLib;
-use Test::More tests => 16;
+use Test::More tests => 18;
my $tempdir = TestLib::tempdir;
my $xlogdir = "$tempdir/pgxlog";
@@ -57,3 +59,19 @@ mkdir $datadir;
}
command_ok([ 'initdb', '-S', $datadir ], 'sync only');
command_fails([ 'initdb', $datadir ], 'existing data directory');
+
+# Check group access on PGDATA
+SKIP:
+{
+ skip "unix-style permissions not supported on Windows", 2 if ($windows_os);
+
+ # Init a new db with group access
+ my $datadir_group = "$tempdir/data_group";
+
+ command_ok(
+ [ 'initdb', '-g', $datadir_group ],
+ 'successful creation with group access');
+
+ ok(check_mode_recursive($datadir_group, 0750, 0640),
+ 'check PGDATA permissions');
+}
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 32b41e313c..5cd535184a 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -2470,14 +2470,6 @@ main(int argc, char **argv)
}
#endif
- /*
- * Verify that the target directory exists, or create it. For plaintext
- * backups, always require the directory. For tar backups, require it
- * unless we are writing to stdout.
- */
- if (format == 'p' || strcmp(basedir, "-") != 0)
- verify_dir_is_empty_or_create(basedir, &made_new_pgdata, &found_existing_pgdata);
-
/* connection in replication mode to server */
conn = GetConnection();
if (!conn)
@@ -2486,6 +2478,20 @@ main(int argc, char **argv)
exit(1);
}
+ /*
+ * Set umask so that directories/files are created with the same permissions
+ * as directories/files in the source data directory.
+ */
+ umask(pg_mode_mask);
+
+ /*
+ * Verify that the target directory exists, or create it. For plaintext
+ * backups, always require the directory. For tar backups, require it
+ * unless we are writing to stdout.
+ */
+ if (format == 'p' || strcmp(basedir, "-") != 0)
+ verify_dir_is_empty_or_create(basedir, &made_new_pgdata, &found_existing_pgdata);
+
/* determine remote server's xlog segment size */
if (!RetrieveWalSegSize(conn))
disconnect_and_exit(1);
diff --git a/src/bin/pg_basebackup/pg_receivewal.c b/src/bin/pg_basebackup/pg_receivewal.c
index b82e073b86..f23ecf799f 100644
--- a/src/bin/pg_basebackup/pg_receivewal.c
+++ b/src/bin/pg_basebackup/pg_receivewal.c
@@ -19,6 +19,7 @@
#include <sys/stat.h>
#include <unistd.h>
+#include "common/file_perm.h"
#include "libpq-fe.h"
#include "access/xlog_internal.h"
#include "getopt_long.h"
@@ -703,6 +704,12 @@ main(int argc, char **argv)
if (!RunIdentifySystem(conn, NULL, NULL, NULL, &db_name))
disconnect_and_exit(1);
+ /*
+ * Set umask so that directories/files are created with the same permissions
+ * as directories/files in the source data directory.
+ */
+ umask(pg_mode_mask);
+
/* determine remote server's xlog segment size */
if (!RetrieveWalSegSize(conn))
disconnect_and_exit(1);
diff --git a/src/bin/pg_basebackup/pg_recvlogical.c b/src/bin/pg_basebackup/pg_recvlogical.c
index 0866395944..e3b6404b1f 100644
--- a/src/bin/pg_basebackup/pg_recvlogical.c
+++ b/src/bin/pg_basebackup/pg_recvlogical.c
@@ -23,6 +23,7 @@
#include "streamutil.h"
#include "access/xlog_internal.h"
+#include "common/file_perm.h"
#include "common/fe_memutils.h"
#include "getopt_long.h"
#include "libpq-fe.h"
@@ -959,6 +960,12 @@ main(int argc, char **argv)
disconnect_and_exit(1);
}
+ /*
+ * Set umask so that directories/files are created with the same permissions
+ * as directories/files in the source data directory.
+ */
+ umask(pg_mode_mask);
+
/* Drop a replication slot. */
if (do_drop_slot)
{
diff --git a/src/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c
index 1438f368ed..494acfec0e 100644
--- a/src/bin/pg_basebackup/streamutil.c
+++ b/src/bin/pg_basebackup/streamutil.c
@@ -23,6 +23,7 @@
#include "access/xlog_internal.h"
#include "common/fe_memutils.h"
+#include "common/file_perm.h"
#include "datatype/timestamp.h"
#include "fe_utils/connect.h"
#include "port/pg_bswap.h"
@@ -32,9 +33,16 @@
uint32 WalSegSz;
+static bool RetrieveDataDirCreatePerm(PGconn *conn);
+
/* SHOW command for replication connection was introduced in version 10 */
#define MINIMUM_VERSION_FOR_SHOW_CMD 100000
+/*
+ * Group access is supported from version 11.
+ */
+#define MINIMUM_VERSION_FOR_GROUP_ACCESS 110000
+
const char *progname;
char *connection_string = NULL;
char *dbhost = NULL;
@@ -254,6 +262,16 @@ GetConnection(void)
exit(1);
}
+ /*
+ * Retrieve the source data directory mode and use to construct a umask
+ * for creating directories and files
+ */
+ if (!RetrieveDataDirCreatePerm(tmpconn))
+ {
+ PQfinish(tmpconn);
+ exit(1);
+ }
+
return tmpconn;
}
@@ -327,6 +345,57 @@ RetrieveWalSegSize(PGconn *conn)
return true;
}
+/*
+ * From version 11, check the mode of the data directory to determine
+ * permissions to use for directories created by the utility.
+ */
+static bool
+RetrieveDataDirCreatePerm(PGconn *conn)
+{
+ PGresult *res;
+ int data_directory_mode;
+
+ /* check connection existence */
+ Assert(conn != NULL);
+
+ /* for previous versions leave the default group access */
+ if (PQserverVersion(conn) < MINIMUM_VERSION_FOR_GROUP_ACCESS)
+ return true;
+
+ res = PQexec(conn, "SHOW data_directory_mode");
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ fprintf(stderr, _("%s: could not send replication command \"%s\": %s\n"),
+ progname, "SHOW data_directory_mode", PQerrorMessage(conn));
+
+ PQclear(res);
+ return false;
+ }
+ if (PQntuples(res) != 1 || PQnfields(res) < 1)
+ {
+ fprintf(stderr,
+ _("%s: could not fetch group access flag: got %d rows and %d fields, expected %d rows and %d or more fields\n"),
+ progname, PQntuples(res), PQnfields(res), 1, 1);
+
+ PQclear(res);
+ return false;
+ }
+
+ if (sscanf(PQgetvalue(res, 0, 0), "%o", &data_directory_mode) != 1)
+ {
+ fprintf(stderr, _("%s: group access flag could not be parsed: %s\n"),
+ progname, PQgetvalue(res, 0, 0));
+
+ PQclear(res);
+ return false;
+ }
+
+ SetDataDirectoryCreatePerm(data_directory_mode);
+
+ PQclear(res);
+ return true;
+}
+
/*
* Run IDENTIFY_SYSTEM through a given connection and give back to caller
* some result information if requested:
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index ec54b555ed..a40e9c2d51 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -5,7 +5,7 @@ use Config;
use File::Basename qw(basename dirname);
use PostgresNode;
use TestLib;
-use Test::More tests => 105;
+use Test::More tests => 106;
program_help_ok('pg_basebackup');
program_version_ok('pg_basebackup');
@@ -43,11 +43,6 @@ $node->command_fails(
ok(!-d "$tempdir/backup", 'backup directory was cleaned up');
-$node->command_fails([ 'pg_basebackup', '-D', "$tempdir/backup", '-n' ],
- 'failing run with no-clean option');
-
-ok(-d "$tempdir/backup", 'backup directory was created and left behind');
-
open my $conf, '>>', "$pgdata/postgresql.conf";
print $conf "max_replication_slots = 10\n";
print $conf "max_wal_senders = 10\n";
@@ -162,6 +157,13 @@ ok(-f "$tempdir/tarbackup/base.tar", 'backup tar was created');
$node->command_fails(
[ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T=/foo" ],
'-T with empty old directory fails');
+
+$node->command_fails(
+ [ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T=/foo", "-n" ],
+ 'failing run with no-clean option');
+
+ok(-d "$tempdir/backup", 'backup directory was created and left behind');
+
$node->command_fails(
[ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T/foo=" ],
'-T with empty new directory fails');
@@ -195,11 +197,17 @@ unlink "$pgdata/$superlongname";
# skip on Windows.
SKIP:
{
- skip "symlinks not supported on Windows", 17 if ($windows_os);
+ skip "symlinks not supported on Windows", 18 if ($windows_os);
# Move pg_replslot out of $pgdata and create a symlink to it.
$node->stop;
+ # Set umask so test directories and files are created with group permissions
+ umask(0027);
+
+ # Enable group permissions on PGDATA
+ chmod_recursive("$pgdata", 0750, 0640);
+
rename("$pgdata/pg_replslot", "$tempdir/pg_replslot")
or BAIL_OUT "could not move $pgdata/pg_replslot";
symlink("$tempdir/pg_replslot", "$pgdata/pg_replslot")
@@ -269,6 +277,10 @@ SKIP:
"tablespace symlink was updated");
closedir $dh;
+ # Group access should be enabled on all backup files
+ ok(check_mode_recursive("$tempdir/backup1", 0750, 0640),
+ "check backup dir permissions");
+
# Unlogged relation forks other than init should not be copied
my ($tblspc1UnloggedBackupPath) = $tblspc1UnloggedPath =~ /[^\/]*\/[^\/]*\/[^\/]*$/g;
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index e83a7179ea..e0c9687a35 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -2171,7 +2171,7 @@ main(int argc, char **argv)
*/
argv0 = argv[0];
- /* Set dir/file mode mask */
+ /* Set restrictive mode mask until PGDATA permissions are checked */
umask(PG_MODE_MASK_DEFAULT);
/* support --help and --version even if invoked as root */
@@ -2407,6 +2407,16 @@ main(int argc, char **argv)
snprintf(version_file, MAXPGPATH, "%s/PG_VERSION", pg_data);
snprintf(pid_file, MAXPGPATH, "%s/postmaster.pid", pg_data);
snprintf(backup_file, MAXPGPATH, "%s/backup_label", pg_data);
+
+ /*
+ * Set mask based on PGDATA permissions,
+ *
+ * Don't error here if the data directory cannot be stat'd. This is
+ * handled differently based on the command and we don't want to
+ * interfere with that logic.
+ */
+ if (GetDataDirectoryCreatePerm(pg_data))
+ umask(pg_mode_mask);
}
switch (ctl_command)
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl
index 067a084c87..2f9dfa7b81 100644
--- a/src/bin/pg_ctl/t/001_start_stop.pl
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -2,9 +2,11 @@ use strict;
use warnings;
use Config;
+use Fcntl ':mode';
+use File::stat qw{lstat};
use PostgresNode;
use TestLib;
-use Test::More tests => 21;
+use Test::More tests => 24;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -74,6 +76,27 @@ SKIP:
ok(check_mode_recursive("$tempdir/data", 0700, 0600));
}
+# Log file for group access test
+$logFileName = "$tempdir/data/perm-test-640.log";
+
+SKIP:
+{
+ skip "group access not supported on Windows", 3 if ($windows_os);
+
+ system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
+
+ # Change the data dir mode so log file will be created with group read
+ # privileges on the next start
+ chmod_recursive("$tempdir/data", 0750, 0640);
+
+ command_ok(
+ [ 'pg_ctl', 'start', '-D', "$tempdir/data", '-l', $logFileName ],
+ 'start server to check group permissions');
+
+ ok(-f $logFileName);
+ ok(check_mode_recursive("$tempdir/data", 0750, 0640));
+}
+
command_ok([ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
'pg_ctl restart with server running');
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index bdf71886ee..8a0a805f1e 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -363,6 +363,16 @@ main(int argc, char *argv[])
exit(1);
}
+ /* Set mask based on PGDATA permissions */
+ if (!GetDataDirectoryCreatePerm(DataDir))
+ {
+ fprintf(stderr, _("%s: unable to read permissions from \"%s\"\n"),
+ progname, DataDir);
+ exit(1);
+ }
+
+ umask(pg_mode_mask);
+
/* Check that data directory matches our server version */
CheckDataVersion();
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index 7b632c7dcd..63d9bd517d 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -115,11 +115,13 @@ sub check_query
sub setup_cluster
{
- my $extra_name = shift;
+ my $extra_name = shift; # Used to differentiate clusters
+ my $extra = shift; # Extra params for initdb
# Initialize master, data checksums are mandatory
$node_master = get_new_node('master' . ($extra_name ? "_${extra_name}" : ''));
- $node_master->init(allows_streaming => 1);
+ $node_master->init(
+ allows_streaming => 1, extra => $extra);
# Set wal_keep_segments to prevent WAL segment recycling after enforced
# checkpoints in the tests.
$node_master->append_conf('postgresql.conf', qq(
@@ -237,7 +239,8 @@ sub run_pg_rewind
"$tmp_folder/master-postgresql.conf.tmp",
"$master_pgdata/postgresql.conf");
- chmod(0600, "$master_pgdata/postgresql.conf")
+ chmod($node_master->group_access() ? 0640 : 0600,
+ "$master_pgdata/postgresql.conf")
or BAIL_OUT(
"unable to set permissions for $master_pgdata/postgresql.conf");
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 72ab2f8d5e..b9ea6a4c21 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -24,6 +24,7 @@
#include "access/xlog_internal.h"
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "getopt_long.h"
#include "storage/bufpage.h"
@@ -185,6 +186,16 @@ main(int argc, char **argv)
exit(1);
}
+ /* Set mask based on PGDATA permissions */
+ if (!GetDataDirectoryCreatePerm(datadir_target))
+ {
+ fprintf(stderr, _("%s: unable to read permissions from \"%s\"\n"),
+ progname, datadir_target);
+ exit(1);
+ }
+
+ umask(pg_mode_mask);
+
/*
* Don't allow pg_rewind to be run as root, to avoid overwriting the
* ownership of files in the data directory. We need only check for root
diff --git a/src/bin/pg_rewind/t/002_databases.pl b/src/bin/pg_rewind/t/002_databases.pl
index 37cdd712f3..c364965d3a 100644
--- a/src/bin/pg_rewind/t/002_databases.pl
+++ b/src/bin/pg_rewind/t/002_databases.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 6;
use RewindTest;
@@ -9,7 +9,7 @@ sub run_test
{
my $test_mode = shift;
- RewindTest::setup_cluster($test_mode);
+ RewindTest::setup_cluster($test_mode, ['-g']);
RewindTest::start_master();
# Create a database in master.
@@ -42,6 +42,15 @@ template1
),
'database names');
+ # Permissions on PGDATA should have group permissions
+ SKIP:
+ {
+ skip "unix-style permissions not supported on Windows", 1 if ($windows_os);
+
+ ok(check_mode_recursive($node_master->data_dir(), 0750, 0640),
+ 'check PGDATA permissions');
+ }
+
RewindTest::clean_rewind_test();
}
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 59df6fd88f..4abf4dc893 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -79,7 +79,7 @@ main(int argc, char **argv)
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_upgrade"));
- /* Ensure that all files created by pg_upgrade are non-world-readable */
+ /* Set default restrictive mask until new cluster permissions are read */
umask(PG_MODE_MASK_DEFAULT);
parseCommandLine(argc, argv);
@@ -100,6 +100,16 @@ main(int argc, char **argv)
check_cluster_compatibility(live_check);
+ /* Set mask based on PGDATA permissions */
+ if (!GetDataDirectoryCreatePerm(new_cluster.pgdata))
+ {
+ pg_log(PG_FATAL, "unable to read permissions from \"%s\"\n",
+ new_cluster.pgdata);
+ exit(1);
+ }
+
+ umask(pg_mode_mask);
+
check_and_dump_old_cluster(live_check);
diff --git a/src/bin/pg_upgrade/test.sh b/src/bin/pg_upgrade/test.sh
index 574639d47e..24631a91c6 100644
--- a/src/bin/pg_upgrade/test.sh
+++ b/src/bin/pg_upgrade/test.sh
@@ -20,9 +20,9 @@ unset MAKELEVEL
# Run a given "initdb" binary and overlay the regression testing
# authentication configuration.
standard_initdb() {
- # To increase coverage of non-standard segment size without
- # increase test runtime, run these tests with a lower setting.
- "$1" -N --wal-segsize 1
+ # To increase coverage of non-standard segment size and group access
+ # without increasing test runtime, run these tests with a custom setting.
+ "$1" -N --wal-segsize 1 -g
if [ -n "$TEMP_CONFIG" -a -r "$TEMP_CONFIG" ]
then
cat "$TEMP_CONFIG" >> "$PGDATA/postgresql.conf"
@@ -230,14 +230,14 @@ standard_initdb 'initdb'
pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "${PGDATA}" -b "$oldbindir" -B "$bindir" -p "$PGPORT" -P "$PGPORT"
-# make sure all directories and files have correct permissions
-if [ $(find ${PGDATA} -type f ! -perm 600 | wc -l) -ne 0 ]; then
- echo "files in PGDATA with permission != 600";
+# make sure all directories and files have group permissions
+if [ $(find ${PGDATA} -type f ! -perm 640 | wc -l) -ne 0 ]; then
+ echo "files in PGDATA with permission != 640";
exit 1;
fi
-if [ $(find ${PGDATA} -type d ! -perm 700 | wc -l) -ne 0 ]; then
- echo "directories in PGDATA with permission != 700";
+if [ $(find ${PGDATA} -type d ! -perm 750 | wc -l) -ne 0 ]; then
+ echo "directories in PGDATA with permission != 750";
exit 1;
fi
diff --git a/src/common/file_perm.c b/src/common/file_perm.c
index 5f0c02fb04..571d37116b 100644
--- a/src/common/file_perm.c
+++ b/src/common/file_perm.c
@@ -12,8 +12,76 @@
*/
#include <sys/stat.h>
+#include "c.h"
#include "common/file_perm.h"
/* Modes for creating directories and files in the data directory */
int pg_dir_create_mode = PG_DIR_MODE_DEFAULT;
int pg_file_create_mode = PG_FILE_MODE_DEFAULT;
+
+/*
+ * Mode mask to pass to umask(). This is more of a preventative measure since
+ * all file/directory creates should be performed using the create modes above.
+ */
+int pg_mode_mask = PG_MODE_MASK_DEFAULT;
+
+/*
+ * Set create modes and mask to use when writing to PGDATA based on the data
+ * directory mode passed. If group read/execute are present in the
+ * mode, then create modes and mask will be relaxed to allow group read/execute
+ * on all newly created files and directories.
+ */
+void
+SetDataDirectoryCreatePerm(int dataDirMode)
+{
+ /* If the data directory mode has group access */
+ if ((PG_DIR_MODE_GROUP & dataDirMode) == PG_DIR_MODE_GROUP)
+ {
+ pg_dir_create_mode = PG_DIR_MODE_GROUP;
+ pg_file_create_mode = PG_FILE_MODE_GROUP;
+ pg_mode_mask = PG_MODE_MASK_GROUP;
+ }
+ /* Else use default permissions */
+ else
+ {
+ pg_dir_create_mode = PG_DIR_MODE_DEFAULT;
+ pg_file_create_mode = PG_FILE_MODE_DEFAULT;
+ pg_mode_mask = PG_MODE_MASK_DEFAULT;
+ }
+}
+
+#ifdef FRONTEND
+
+/*
+ * Get the create modes and mask to use when writing to PGDATA by examining the
+ * mode of the PGDATA directory and calling SetDataDirectoryCreatePerm().
+ *
+ * Errors are not handled here and should be reported by the frontend
+ * application when false is returned.
+ *
+ * Suppress when on Windows, because there may not be proper support for Unix-y
+ * file permissions.
+ */
+bool
+GetDataDirectoryCreatePerm(const char *dataDir)
+{
+#if !defined(WIN32) && !defined(__CYGWIN__)
+ struct stat statBuf;
+
+ /*
+ * If an error occurs getting the mode then false It is the reponsibility of
+ * the frontend application to generate an error if the PGDATA directory
+ * cannot be accessed.
+ */
+ if (stat(dataDir, &statBuf) == -1)
+ return false;
+
+ /* Set permissions */
+ SetDataDirectoryCreatePerm(statBuf.st_mode);
+
+ return true;
+#endif /* !defined(WIN32) && !defined(__CYGWIN__) */
+}
+
+
+#endif /* FRONTEND */
diff --git a/src/include/common/file_perm.h b/src/include/common/file_perm.h
index 46b91f0c7b..22afb14b55 100644
--- a/src/include/common/file_perm.h
+++ b/src/include/common/file_perm.h
@@ -19,14 +19,34 @@
*/
#define PG_MODE_MASK_DEFAULT (S_IRWXG | S_IRWXO)
+/*
+ * Mode mask for data directory permissions that also allows group read/execute.
+ */
+#define PG_MODE_MASK_GROUP (S_IWGRP | S_IRWXO)
+
/* Default mode for creating directories */
#define PG_DIR_MODE_DEFAULT S_IRWXU
+/* Mode for creating directories that allows group read/execute */
+#define PG_DIR_MODE_GROUP (S_IRWXU | S_IRGRP | S_IXGRP)
+
/* Default mode for creating files */
#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR)
+/* Mode for creating files that allows group read */
+#define PG_FILE_MODE_GROUP (S_IRUSR | S_IWUSR | S_IRGRP)
+
/* Modes for creating directories and files in the data directory */
extern int pg_dir_create_mode;
extern int pg_file_create_mode;
+/* Mode mask to pass to umask() */
+extern int pg_mode_mask;
+
+/* Set permissions and mask based on the provided mode */
+extern void SetDataDirectoryCreatePerm(int dataDirMode);
+
+/* Set permissions and mask based on the mode of the data directory */
+extern bool GetDataDirectoryCreatePerm(const char *dataDir);
+
#endif /* FILE_PERM_H */
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index a429a19964..492804d758 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -153,6 +153,7 @@ extern PGDLLIMPORT bool IsBinaryUpgrade;
extern PGDLLIMPORT bool ExitOnAnyError;
extern PGDLLIMPORT char *DataDir;
+extern PGDLLIMPORT int data_directory_mode;
extern PGDLLIMPORT int NBuffers;
extern PGDLLIMPORT int MaxBackends;
@@ -323,6 +324,7 @@ extern void SetSessionAuthorization(Oid userid, bool is_superuser);
extern Oid GetCurrentRoleId(void);
extern void SetCurrentRoleId(Oid roleid, bool is_superuser);
+extern void checkDataDir(void);
extern void SetDataDir(const char *dir);
extern void ChangeToDataDir(void);
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 76e571b98c..1bd91524d7 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -86,9 +86,11 @@ use Carp;
use Config;
use Cwd;
use Exporter 'import';
+use Fcntl qw(:mode);
use File::Basename;
use File::Path qw(rmtree);
use File::Spec;
+use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
use RecursiveCopy;
@@ -269,6 +271,26 @@ sub connstr
=pod
+=item $node->group_access()
+
+Does the data dir allow group access?
+
+=cut
+
+sub group_access
+{
+ my ($self) = @_;
+
+ my $dir_stat = stat($self->data_dir);
+
+ defined($dir_stat)
+ or die('unable to stat ' . $self->data_dir);
+
+ return (S_IMODE($dir_stat->mode) == 0750);
+}
+
+=pod
+
=item $node->data_dir()
Returns the path to the data directory. postgresql.conf and pg_hba.conf are
@@ -460,6 +482,9 @@ sub init
}
close $conf;
+ chmod($self->group_access ? 0640 : 0600, "$pgdata/postgresql.conf")
+ or die("unable to set permissions for $pgdata/postgresql.conf");
+
$self->set_replication_conf if $params{allows_streaming};
$self->enable_archiving if $params{has_archiving};
}
@@ -485,7 +510,7 @@ sub append_conf
TestLib::append_to_file($conffile, $str . "\n");
- chmod(0600, $conffile)
+ chmod($self->group_access() ? 0640 : 0600, $conffile)
or die("unable to set permissions for $conffile");
}
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index 93610e4bc4..8047404efd 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -31,6 +31,7 @@ our @EXPORT = qw(
slurp_file
append_to_file
check_mode_recursive
+ chmod_recursive
check_pg_config
system_or_bail
system_log
@@ -313,6 +314,30 @@ sub check_mode_recursive
return $result;
}
+# Change mode recursively on a directory
+sub chmod_recursive
+{
+ my ($dir, $dir_mode, $file_mode) = @_;
+
+ find
+ (
+ {follow_fast => 1,
+ wanted =>
+ sub
+ {
+ my $file_stat = stat($File::Find::name);
+
+ if (defined($file_stat))
+ {
+ chmod(S_ISDIR($file_stat->mode) ? $dir_mode : $file_mode,
+ $File::Find::name)
+ or die "unable to chmod $File::Find::name";
+ }
+ }},
+ $dir
+ );
+}
+
# Check presence of a given regexp within pg_config.h for the installation
# where tests are running, returning a match status result depending on
# that.
--
2.14.3 (Apple Git-98)
On Thu, Apr 05, 2018 at 12:08:15PM -0400, David Steele wrote:
On 4/5/18 2:55 AM, Michael Paquier wrote:
On Wed, Apr 04, 2018 at 08:03:54PM -0400, David Steele wrote:
Instead I have created variables in file_perm.c
that hold the current file create mode, dir create mode, and mode mask.
All call sites use those variables (e.g. pg_dir_create_mode), which I
think are much clear.Hm. I personally find that even more confusing, especially with
SetDataDirectoryCreatePerm which basically is the same as
SetConfigOption for the backend.The GUC shows the current mode of the data directory, while the
variables in file_perm.c store the mode that should be used to create
new dirs/files. One is certainly based on the other but I thought it
best to split them for clarity.
Yeah, there are arguments to actually have both things split so as they
can be concatenated. This makes the code more modular.
You also forgot a call to SetDataDirectoryCreatePerm or
pg_dir_create_mode remains to its default.
My apologies, I forgot the last two words of this sentence: "for
pg_rewind". Still, I am wrong because GetDataDirectoryCreatePerm calls
internally SetDataDirectoryCreatePerm. So the API name is confusing
because not only the permissions are fetched, but they are also
applied.
Are saying *if* a call is forgotten?
That applies as well.
The interface of file_perm.h that you are introducing is not confusing
anymore though..Yes, that was the idea. I think it makes it clearer what we are trying
to do and centralizes variables related to create modes in one place.I've attached new patches that exclude Windows from permissions tests
and deal with bootstrap permissions in a better way. PostgresMain() and
AuxiliaryProcessMain() now use checkDataDir() to set permissions.
GetDataDirectoryCreatePerm() is defined in file_perm.h but it is only
declared for frontends.
At the end, this patch brings in a new abstraction concept to
src/common/ to control a set of pre-determined behaviors:
- The mode to create directories.
- The mode to create files.
- The mode number which can be used for umask.
I am not really convinced that we need to enforce all of them all the
time with a API layer aimed at controlling the behavior of all things at
the same time. Getting this abstraction down one level by allowing each
frontend to set up things the way they want would be nicer in my
opinion. Perhaps others feel differently...
It could be also less confusing if the set of variables in file_perm.h
was designed in such a way that we have the default, and then we can
apply on top of it the set to allow grouping, so as allowing grouping
access would be to do the operation of (default mask + group addition).
The design on the backend is rather neat however there is also
overlapping with SetDataDirectoryCreatePerm() and the GUC
data_directory_mode which are heading toward the same types of goals.
We could reduce that confusion by designing the interface as follows:
- Have read-only GUCs for the directory and file masks on the backend
which get set by the postmaster after looking at the permission of the
data folder at startup. Then if a file or a folder needs to be created,
then look at those values and apply permissions as granted. And also a
GUC to decide the umask to apply but that should not be necessary,
right?
- Frontends are responsible for the decision-making of the permissions.
Not all the variables are used for frontends as well. For example
pg_resetwal and pg_upgrade only touch files.
- For frontends, there are two cases:
-- The client needs to connect to a live Postgres instance, in which
case it can use the SHOW command to get the wanted information. This
applies to pg_rewind with the remote mode (should apply to the second
case actually), pg_basebackup, pg_receivexlog, etc.
-- Binaries work on a local data folder, so permissions can be guessed
from that: pg_rewind, pg_resetwal and pg_upgrade. Having an API in
src/common/ which scans for what to apply is neat. This was in v12 and
some older versions if I recall correctly.
We are two days away from the end of the commit fest, and this patch set
if not yet in a committable stagle, so perhaps at this stage we had
better just retreat and come back to it in next cycle?
--
Michael
Greetings,
* Michael Paquier (michael@paquier.xyz) wrote:
On Thu, Apr 05, 2018 at 12:08:15PM -0400, David Steele wrote:
On 4/5/18 2:55 AM, Michael Paquier wrote:
On Wed, Apr 04, 2018 at 08:03:54PM -0400, David Steele wrote:
Instead I have created variables in file_perm.c
that hold the current file create mode, dir create mode, and mode mask.
All call sites use those variables (e.g. pg_dir_create_mode), which I
think are much clear.Hm. I personally find that even more confusing, especially with
SetDataDirectoryCreatePerm which basically is the same as
SetConfigOption for the backend.The GUC shows the current mode of the data directory, while the
variables in file_perm.c store the mode that should be used to create
new dirs/files. One is certainly based on the other but I thought it
best to split them for clarity.Yeah, there are arguments to actually have both things split so as they
can be concatenated. This makes the code more modular.
I prefer having them split as well.
You also forgot a call to SetDataDirectoryCreatePerm or
pg_dir_create_mode remains to its default.My apologies, I forgot the last two words of this sentence: "for
pg_rewind". Still, I am wrong because GetDataDirectoryCreatePerm calls
internally SetDataDirectoryCreatePerm. So the API name is confusing
because not only the permissions are fetched, but they are also
applied.
They're not *actually* applied though- that just sets the variable, and
having GetDataDirectoryCreatePerm get what the perm is and then set a
variable based off of that, so we know what it's set to later, seems
reasonable to me.
The interface of file_perm.h that you are introducing is not confusing
anymore though..Yes, that was the idea. I think it makes it clearer what we are trying
to do and centralizes variables related to create modes in one place.I've attached new patches that exclude Windows from permissions tests
and deal with bootstrap permissions in a better way. PostgresMain() and
AuxiliaryProcessMain() now use checkDataDir() to set permissions.GetDataDirectoryCreatePerm() is defined in file_perm.h but it is only
declared for frontends.
Right, the job that GetDataDirectoryCreatePerm() has in the front-ends
is more complicated when the backend is starting up because we're also
checking who the data directory is owned by and we're setting the
internal GUC, all of which is done by checkDataDir(), which also calls
SetDataDirectoryCreatePerm() to set the variables.
At the end, this patch brings in a new abstraction concept to
src/common/ to control a set of pre-determined behaviors:
- The mode to create directories.
- The mode to create files.
- The mode number which can be used for umask.
I am not really convinced that we need to enforce all of them all the
time with a API layer aimed at controlling the behavior of all things at
the same time. Getting this abstraction down one level by allowing each
frontend to set up things the way they want would be nicer in my
opinion. Perhaps others feel differently...
I don't think we actually *want* the various tools making different
decisions about what permissions the files in a given data directory
have- that's initdb's job, not the job of pg_resetwal.
It could be also less confusing if the set of variables in file_perm.h
was designed in such a way that we have the default, and then we can
apply on top of it the set to allow grouping, so as allowing grouping
access would be to do the operation of (default mask + group addition).
This seems like we're going around-and-around. We had all the different
masking things happening previously, but that got rather ugly as there
were just small variations between "right" and "wrong". I like this
approach which makes it pretty clear that when we start up, we figure
out what the perms should be for files in this data dir, and then we set
the variables for the permissions and use them.
The design on the backend is rather neat however there is also
overlapping with SetDataDirectoryCreatePerm() and the GUC
data_directory_mode which are heading toward the same types of goals.
Just above we discuss how we want those to be seperate, and here it
seems you're asking for them to be put together. We really can't have
it both ways in this case and I tend to agree with the above discussion
where we keep them seperate.
We could reduce that confusion by designing the interface as follows:
- Have read-only GUCs for the directory and file masks on the backend
which get set by the postmaster after looking at the permission of the
data folder at startup. Then if a file or a folder needs to be created,
then look at those values and apply permissions as granted. And also a
GUC to decide the umask to apply but that should not be necessary,
right?
This is more-or-less what we've got now- a read-only GUC for the
directory and file masks on the backend which gets set by the postmaster
after looking at the permission of the data folder on startup- that all
happens in checkDataDir() now, which looks good to me. We then have the
variables set by SetDataDirectoryCreatePerm() which happens just before
the GUC is set in the same area of checkDataDir(). We need to set them
both if we're going to keep them seperate, which I believe is what we
really want here.
- Frontends are responsible for the decision-making of the permissions.
No. Frontends need to follow what the permissions are on the data
directory- having them decide independently will just lead to things
being inconsistent and that's definitely not something that we want.
Not all the variables are used for frontends as well. For example
pg_resetwal and pg_upgrade only touch files.
While true today, I don't think it's an issue and I'd rather keep the
directory and file privilege settings in the same place (where else
would you put the directory ones?).
- For frontends, there are two cases:
-- The client needs to connect to a live Postgres instance, in which
case it can use the SHOW command to get the wanted information. This
applies to pg_rewind with the remote mode (should apply to the second
case actually), pg_basebackup, pg_receivexlog, etc.
Right, that's what pg_basebackup, pg_receivewal, etc, do now with this
patch..
-- Binaries work on a local data folder, so permissions can be guessed
from that: pg_rewind, pg_resetwal and pg_upgrade. Having an API in
src/common/ which scans for what to apply is neat. This was in v12 and
some older versions if I recall correctly.
I'm confused here as that's what GetDataDirectoryCreatePerm()
specifically does and that's why it's in src/common now for the
front-end tools to use, or were you just agreeing that this was in v12
previously and you're happy to see that it's back..?
We are two days away from the end of the commit fest, and this patch set
if not yet in a committable stagle, so perhaps at this stage we had
better just retreat and come back to it in next cycle?
I've been watching this and discussing things with David while you and
he have been working on it and I think the discussion has generally been
good, so I didn't want to step in and disrupt it, but there's a few
things here which now seem to be going in circles without a lot of
benefit. There's a few comments which I have on the patch after going
over it again, but I tend to feel it's actually pretty close as none of
the comments I have at this point are serious issues- you and David have
addressed those (I'm *very* glad that the pg_dump changes were ripped
out, for instance, and having the some of the regression tests committed
independently was certainly good...) and I don't see any of the above as
being really concerning, but do let me know if you think there's
something I'm missing or some other issue.
I'll reply to David's last email (where the latest set of patches were
included) with my comments/suggestions and I expect we'll be able to get
those addressed today and have a final patch to post tonight, with an
eye towards committing it tomorrow.
Thanks!
Stephen
On Fri, Apr 06, 2018 at 09:15:15AM -0400, Stephen Frost wrote:
I'll reply to David's last email (where the latest set of patches were
included) with my comments/suggestions and I expect we'll be able to get
those addressed today and have a final patch to post tonight, with an
eye towards committing it tomorrow.
The feature freeze is on the 8th, so I am going to have limited room to
comment on things until that day. If something gets committed, I am
pretty sure that I'll get out of my pocket a couple of things to improve
the feature and its interface anyway if of course you are ready to
accept that. I have limited my role to be a reviewer, so I refrained
myself from writing any code ;)
--
Michael
David,
* David Steele (david@pgmasters.net) wrote:
The GUC shows the current mode of the data directory, while the
variables in file_perm.c store the mode that should be used to create
new dirs/files. One is certainly based on the other but I thought it
best to split them for clarity.
Agreed. I did have some other comments about the patches tho-
You also forgot a call to SetDataDirectoryCreatePerm or
pg_dir_create_mode remains to its default.Are saying *if* a call is forgotten?
Yes, the file/dir create modes will use the default restrictive
permissions unless set otherwise. I see this as a good thing. Worst
case, we miss some areas where group access should be enabled and we
find those over the next few months.
I tend to agree with this, though the space of concern is quite limited-
basically between the top of PostmasterMain() and when it calls
checkDataDir(), and we really shouldn't be creating any files before
we've check that the data dir looks sane.
The interface of file_perm.h that you are introducing is not confusing
anymore though..Yes, that was the idea. I think it makes it clearer what we are trying
to do and centralizes variables related to create modes in one place.I've attached new patches that exclude Windows from permissions tests
and deal with bootstrap permissions in a better way. PostgresMain() and
AuxiliaryProcessMain() now use checkDataDir() to set permissions.
Alright, changes I've made, since I got impatient and it didn't seem to
make sense to bounce these back to David instead of just making them (I
did discuss them with him on the phone today tho, just to be clear).
- MakeDirectoryDefaultPerm() was quite a mouthful, so I've simplified
that down to MakePGDirectory(), which I hope is clear in that it's for
making directories related to PG (as it isn't a general mkdir() call
or just some platform-agnostic mkdir(), which MakeDirectory() might
imply, but is specific for working with PG data directories).
- The PG_FILE_MODE_DEFAULT, PG_DIR_MODE_DEFAULT, et al, were confusing.
Those constants are used for the default file/dir mode, sure, but what
that actually *are* is the 'owner'-only style mode, so they've been
changed to PG_FILE_MODE_OWNER, PG_DIR_MODE_OWNER, etc.
- Where we're intentionally ignoring the MakePGDirectory() result,
use (void).
- Various comment improvements.
- Improved the commit messages a bit.
Things that still need doing:
- Go back over with pgindent and make sure it's all happy.
- Improved documentation
- Likely more comments (I don't think I can ever have enough)
- Further discussion in the commit messages
- Perhaps a bit more review to try to minimize the risk that something
breaks on Windows... Looked ok to me, but probably still some risk
that the Windows buildfarm members fall over, though I suppose that's
also what they're there for.
Updated patch attached.
David, if you could look through this again and make sure I didn't break
anything with the changes made, and perhaps make improvements to the
docs/comments/commit messages, that'd be helpful for getting this over
the line.
Thanks!
Stephen
Attachments:
group-access-v16-master.patchtext/x-diff; charset=us-asciiDownload
From d3a3f3e4d660e3cfc163a1787d4acb87d6f537e0 Mon Sep 17 00:00:00 2001
From: David Steele <david@pgmasters.net>
Date: Fri, 6 Apr 2018 09:29:15 -0400
Subject: [PATCH 1/2] Refactor file permissions in backend/frontend
Adds a new header (file_perm.h) and makes all front-end utilities use
the new constants for setting umask. Converts mkdir() calls in the
backend to MakePGDirectory() if the original call used default
permissions (always the case for regular PG directories).
Adds tests to make sure permissions in PGDATA are set correctly by the
front-end tools.
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
---
src/backend/access/transam/xlog.c | 2 +-
src/backend/commands/tablespace.c | 18 ++++---
src/backend/postmaster/postmaster.c | 7 +--
src/backend/postmaster/syslogger.c | 5 +-
src/backend/replication/basebackup.c | 5 +-
src/backend/replication/slot.c | 5 +-
src/backend/storage/file/copydir.c | 2 +-
src/backend/storage/file/fd.c | 38 +++++++++------
src/backend/storage/ipc/dsm_impl.c | 3 +-
src/backend/storage/ipc/ipc.c | 4 ++
src/backend/utils/init/miscinit.c | 5 +-
src/bin/initdb/initdb.c | 24 ++++-----
src/bin/initdb/t/001_initdb.pl | 11 ++++-
src/bin/pg_basebackup/pg_basebackup.c | 9 ++--
src/bin/pg_basebackup/t/010_pg_basebackup.pl | 14 +++++-
src/bin/pg_basebackup/t/020_pg_receivewal.pl | 14 +++++-
src/bin/pg_basebackup/walmethods.c | 6 ++-
src/bin/pg_ctl/pg_ctl.c | 4 +-
src/bin/pg_ctl/t/001_start_stop.pl | 18 ++++++-
src/bin/pg_resetwal/pg_resetwal.c | 5 +-
src/bin/pg_resetwal/t/001_basic.pl | 12 ++++-
src/bin/pg_rewind/RewindTest.pm | 4 ++
src/bin/pg_rewind/file_ops.c | 7 +--
src/bin/pg_rewind/t/001_basic.pl | 11 ++++-
src/bin/pg_upgrade/file.c | 5 +-
src/bin/pg_upgrade/pg_upgrade.c | 3 +-
src/bin/pg_upgrade/test.sh | 11 +++++
src/common/Makefile | 4 +-
src/common/file_perm.c | 19 ++++++++
src/include/common/file_perm.h | 32 ++++++++++++
src/include/storage/fd.h | 3 ++
src/test/perl/PostgresNode.pm | 3 ++
src/test/perl/TestLib.pm | 73 ++++++++++++++++++++++++++++
src/tools/msvc/Mkvcbuild.pm | 4 +-
34 files changed, 316 insertions(+), 74 deletions(-)
create mode 100644 src/common/file_perm.c
create mode 100644 src/include/common/file_perm.h
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 813b2afaac..4a47395174 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4107,7 +4107,7 @@ ValidateXLOGDirectoryStructure(void)
{
ereport(LOG,
(errmsg("creating missing WAL directory \"%s\"", path)));
- if (mkdir(path, S_IRWXU) < 0)
+ if (MakePGDirectory(path) < 0)
ereport(FATAL,
(errmsg("could not create missing directory \"%s\": %m",
path)));
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 5c450caa4e..cfc9fe468c 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -68,6 +68,7 @@
#include "commands/seclabel.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
+#include "common/file_perm.h"
#include "miscadmin.h"
#include "postmaster/bgwriter.h"
#include "storage/fd.h"
@@ -151,7 +152,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
else
{
/* Directory creation failed? */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakePGDirectory(dir) < 0)
{
char *parentdir;
@@ -173,7 +174,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
get_parent_directory(parentdir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakePGDirectory(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -184,7 +185,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
parentdir = pstrdup(dir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakePGDirectory(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -192,7 +193,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
pfree(parentdir);
/* Create database directory */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakePGDirectory(dir) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -279,7 +280,8 @@ CreateTableSpace(CreateTableSpaceStmt *stmt)
/*
* Check that location isn't too long. Remember that we're going to append
* 'PG_XXX/<dboid>/<relid>_<fork>.<nnn>'. FYI, we never actually
- * reference the whole path here, but mkdir() uses the first two parts.
+ * reference the whole path here, but MakePGDirectory() uses the first
+ * two parts.
*/
if (strlen(location) + 1 + strlen(TABLESPACE_VERSION_DIRECTORY) + 1 +
OIDCHARS + 1 + OIDCHARS + 1 + FORKNAMECHARS + 1 + OIDCHARS > MAXPGPATH)
@@ -574,7 +576,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* Attempt to coerce target directory to safe permissions. If this fails,
* it doesn't exist or has the wrong owner.
*/
- if (chmod(location, S_IRWXU) != 0)
+ if (chmod(location, pg_dir_create_mode) != 0)
{
if (errno == ENOENT)
ereport(ERROR,
@@ -599,7 +601,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
if (stat(location_with_version_dir, &st) == 0 && S_ISDIR(st.st_mode))
{
if (!rmtree(location_with_version_dir, true))
- /* If this failed, mkdir() below is going to error. */
+ /* If this failed, MakePGDirectory() below is going to error. */
ereport(WARNING,
(errmsg("some useless files may be left behind in old database directory \"%s\"",
location_with_version_dir)));
@@ -610,7 +612,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* The creation of the version directory prevents more than one tablespace
* in a single location.
*/
- if (mkdir(location_with_version_dir, S_IRWXU) < 0)
+ if (MakePGDirectory(location_with_version_dir) < 0)
{
if (errno == EEXIST)
ereport(ERROR,
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 3dfb87d701..39801db5e9 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -97,6 +97,7 @@
#include "access/xlog.h"
#include "bootstrap/bootstrap.h"
#include "catalog/pg_control.h"
+#include "common/file_perm.h"
#include "common/ip.h"
#include "lib/ilist.h"
#include "libpq/auth.h"
@@ -589,7 +590,7 @@ PostmasterMain(int argc, char *argv[])
/*
* for security, no dir or file created can be group or other accessible
*/
- umask(S_IRWXG | S_IRWXO);
+ umask(PG_MODE_MASK_DEFAULT);
/*
* Initialize random(3) so we don't get the same values in every run.
@@ -4490,9 +4491,9 @@ internal_forkexec(int argc, char *argv[], Port *port)
{
/*
* As in OpenTemporaryFileInTablespace, try to make the temp-file
- * directory
+ * directory, ignoring errors.
*/
- mkdir(PG_TEMP_FILES_DIR, S_IRWXU);
+ (void) MakePGDirectory(PG_TEMP_FILES_DIR);
fp = AllocateFile(tmpfilename, PG_BINARY_W);
if (!fp)
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index f70eea37df..58b759f305 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -41,6 +41,7 @@
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
#include "storage/dsm.h"
+#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pg_shmem.h"
@@ -322,7 +323,7 @@ SysLoggerMain(int argc, char *argv[])
/*
* Also, create new directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ (void) MakePGDirectory(Log_directory);
}
if (strcmp(Log_filename, currentLogFilename) != 0)
{
@@ -564,7 +565,7 @@ SysLogger_Start(void)
/*
* Create log directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ (void) MakePGDirectory(Log_directory);
/*
* The initial logfile is created right in the postmaster, to verify that
diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c
index 8ba29453b9..babf85a6ea 100644
--- a/src/backend/replication/basebackup.c
+++ b/src/backend/replication/basebackup.c
@@ -19,6 +19,7 @@
#include "access/xlog_internal.h" /* for pg_start/stop_backup */
#include "catalog/catalog.h"
#include "catalog/pg_type.h"
+#include "common/file_perm.h"
#include "lib/stringinfo.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
@@ -930,7 +931,7 @@ sendFileWithContent(const char *filename, const char *content)
statbuf.st_gid = getegid();
#endif
statbuf.st_mtime = time(NULL);
- statbuf.st_mode = S_IRUSR | S_IWUSR;
+ statbuf.st_mode = pg_file_create_mode;
statbuf.st_size = len;
_tarWriteHeader(filename, NULL, &statbuf, false);
@@ -1628,7 +1629,7 @@ _tarWriteDir(const char *pathbuf, int basepathlen, struct stat *statbuf,
#else
if (pgwin32_is_junction(pathbuf))
#endif
- statbuf->st_mode = S_IFDIR | S_IRWXU;
+ statbuf->st_mode = S_IFDIR | pg_dir_create_mode;
return _tarWriteHeader(pathbuf + basepathlen + 1, NULL, statbuf, sizeonly);
}
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index fc9ef22b0b..056628fe8e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -1166,13 +1166,14 @@ CreateSlotOnDisk(ReplicationSlot *slot)
* It's just barely possible that some previous effort to create or drop a
* slot with this name left a temp directory lying around. If that seems
* to be the case, try to remove it. If the rmtree() fails, we'll error
- * out at the mkdir() below, so we don't bother checking success.
+ * out at the MakePGDirectory() below, so we don't bother checking
+ * success.
*/
if (stat(tmppath, &st) == 0 && S_ISDIR(st.st_mode))
rmtree(tmppath, true);
/* Create and fsync the temporary slot directory. */
- if (mkdir(tmppath, S_IRWXU) < 0)
+ if (MakePGDirectory(tmppath) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
diff --git a/src/backend/storage/file/copydir.c b/src/backend/storage/file/copydir.c
index ca6342db0d..4a0d23b11e 100644
--- a/src/backend/storage/file/copydir.c
+++ b/src/backend/storage/file/copydir.c
@@ -41,7 +41,7 @@ copydir(char *fromdir, char *todir, bool recurse)
char fromfile[MAXPGPATH * 2];
char tofile[MAXPGPATH * 2];
- if (mkdir(todir, S_IRWXU) != 0)
+ if (MakePGDirectory(todir) != 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m", todir)));
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index d30a725f90..3231ffe2b9 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -84,6 +84,7 @@
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "catalog/pg_tablespace.h"
+#include "common/file_perm.h"
#include "pgstat.h"
#include "portability/mem.h"
#include "storage/fd.h"
@@ -124,12 +125,6 @@
*/
#define FD_MINFREE 10
-/*
- * Default mode for created files, unless something else is specified using
- * the *Perm() function variants.
- */
-#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR)
-
/*
* A number of platforms allow individual processes to open many more files
* than they can really support when *many* processes do the same thing.
@@ -937,7 +932,7 @@ set_max_safe_fds(void)
int
BasicOpenFile(const char *fileName, int fileFlags)
{
- return BasicOpenFilePerm(fileName, fileFlags, PG_FILE_MODE_DEFAULT);
+ return BasicOpenFilePerm(fileName, fileFlags, pg_file_create_mode);
}
/*
@@ -1356,7 +1351,7 @@ FileInvalidate(File file)
File
PathNameOpenFile(const char *fileName, int fileFlags)
{
- return PathNameOpenFilePerm(fileName, fileFlags, PG_FILE_MODE_DEFAULT);
+ return PathNameOpenFilePerm(fileName, fileFlags, pg_file_create_mode);
}
/*
@@ -1434,7 +1429,7 @@ PathNameOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode)
void
PathNameCreateTemporaryDir(const char *basedir, const char *directory)
{
- if (mkdir(directory, S_IRWXU) < 0)
+ if (MakePGDirectory(directory) < 0)
{
if (errno == EEXIST)
return;
@@ -1444,14 +1439,14 @@ PathNameCreateTemporaryDir(const char *basedir, const char *directory)
* EEXIST to close a race against another process following the same
* algorithm.
*/
- if (mkdir(basedir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakePGDirectory(basedir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary directory \"%s\": %m",
basedir)));
/* Try again. */
- if (mkdir(directory, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakePGDirectory(directory) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary subdirectory \"%s\": %m",
@@ -1601,11 +1596,11 @@ OpenTemporaryFileInTablespace(Oid tblspcOid, bool rejectError)
* We might need to create the tablespace's tempfile directory, if no
* one has yet done so.
*
- * Don't check for error from mkdir; it could fail if someone else
- * just did the same thing. If it doesn't work then we'll bomb out on
+ * Don't check error from MakePGDirectory; it could fail if someone
+ * else just did the same thing. If it doesn't work then we'll bomb out on
* the second create attempt, instead.
*/
- mkdir(tempdirpath, S_IRWXU);
+ (void) MakePGDirectory(tempdirpath);
file = PathNameOpenFile(tempfilepath,
O_RDWR | O_CREAT | O_TRUNC | PG_BINARY);
@@ -2401,7 +2396,7 @@ TryAgain:
int
OpenTransientFile(const char *fileName, int fileFlags)
{
- return OpenTransientFilePerm(fileName, fileFlags, PG_FILE_MODE_DEFAULT);
+ return OpenTransientFilePerm(fileName, fileFlags, pg_file_create_mode);
}
/*
@@ -3554,3 +3549,16 @@ fsync_parent_path(const char *fname, int elevel)
return 0;
}
+
+/*
+ * Create a directory using pg_dir_create_mode for permissions
+ *
+ * Directories in PGDATA should normally have specific permissions -- when
+ * using this function the caller does not need to know what they should be.
+ * For permissions other than the default use mkdir() directly.
+ */
+int
+MakePGDirectory(const char *directoryName)
+{
+ return mkdir(directoryName, pg_dir_create_mode);
+}
diff --git a/src/backend/storage/ipc/dsm_impl.c b/src/backend/storage/ipc/dsm_impl.c
index 67e76b98fe..2fca9fae51 100644
--- a/src/backend/storage/ipc/dsm_impl.c
+++ b/src/backend/storage/ipc/dsm_impl.c
@@ -60,6 +60,7 @@
#ifdef HAVE_SYS_SHM_H
#include <sys/shm.h>
#endif
+#include "common/file_perm.h"
#include "pgstat.h"
#include "portability/mem.h"
@@ -285,7 +286,7 @@ dsm_impl_posix(dsm_op op, dsm_handle handle, Size request_size,
* returning.
*/
flags = O_RDWR | (op == DSM_OP_CREATE ? O_CREAT | O_EXCL : 0);
- if ((fd = shm_open(name, flags, 0600)) == -1)
+ if ((fd = shm_open(name, flags, PG_FILE_MODE_OWNER)) == -1)
{
if (errno != EEXIST)
ereport(elevel,
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index fc0a9c0756..6cf69fe55b 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -137,6 +137,10 @@ proc_exit(int code)
else
snprintf(gprofDirName, 32, "gprof/%d", (int) getpid());
+ /*
+ * Use mkdir() instead of MakePGDirectory() since we aren't making a PG
+ * directory here.
+ */
mkdir("gprof", S_IRWXU | S_IRWXG | S_IRWXO);
mkdir(gprofDirName, S_IRWXU | S_IRWXG | S_IRWXO);
chdir(gprofDirName);
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 87ed7d3f71..f8f08f3f88 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -32,6 +32,7 @@
#include "access/htup_details.h"
#include "catalog/pg_authid.h"
+#include "common/file_perm.h"
#include "libpq/libpq.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
@@ -831,7 +832,7 @@ CreateLockFile(const char *filename, bool amPostmaster,
* Think not to make the file protection weaker than 0600. See
* comments below.
*/
- fd = open(filename, O_RDWR | O_CREAT | O_EXCL, 0600);
+ fd = open(filename, O_RDWR | O_CREAT | O_EXCL, pg_file_create_mode);
if (fd >= 0)
break; /* Success; exit the retry loop */
@@ -848,7 +849,7 @@ CreateLockFile(const char *filename, bool amPostmaster,
* Read the file to get the old owner's PID. Note race condition
* here: file might have been deleted since we tried to create it.
*/
- fd = open(filename, O_RDONLY, 0600);
+ fd = open(filename, O_RDONLY, pg_file_create_mode);
if (fd < 0)
{
if (errno == ENOENT)
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 78990f5a27..83472d9ceb 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -64,6 +64,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_class.h"
#include "catalog/pg_collation.h"
+#include "common/file_perm.h"
#include "common/file_utils.h"
#include "common/restricted_token.h"
#include "common/username.h"
@@ -1170,7 +1171,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, pg_file_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1190,7 +1191,7 @@ setup_config(void)
sprintf(path, "%s/postgresql.auto.conf", pg_data);
writefile(path, autoconflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, pg_file_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1277,7 +1278,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_hba.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, pg_file_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1293,7 +1294,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_ident.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, pg_file_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -2692,7 +2693,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (pg_mkdir_p(pg_data, S_IRWXU) != 0)
+ if (pg_mkdir_p(pg_data, pg_dir_create_mode) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2710,7 +2711,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (chmod(pg_data, S_IRWXU) != 0)
+ if (chmod(pg_data, pg_dir_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2778,7 +2779,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (pg_mkdir_p(xlog_dir, S_IRWXU) != 0)
+ if (pg_mkdir_p(xlog_dir, pg_dir_create_mode) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2796,7 +2797,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (chmod(xlog_dir, S_IRWXU) != 0)
+ if (chmod(xlog_dir, pg_dir_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2846,7 +2847,7 @@ create_xlog_or_symlink(void)
else
{
/* Without -X option, just make the subdirectory normally */
- if (mkdir(subdirloc, S_IRWXU) < 0)
+ if (mkdir(subdirloc, pg_dir_create_mode) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, subdirloc, strerror(errno));
@@ -2882,7 +2883,8 @@ initialize_data_directory(void)
setup_signals();
- umask(S_IRWXG | S_IRWXO);
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_DEFAULT);
create_data_directory();
@@ -2902,7 +2904,7 @@ initialize_data_directory(void)
* The parent directory already exists, so we only need mkdir() not
* pg_mkdir_p() here, which avoids some failure modes; cf bug #13853.
*/
- if (mkdir(path, S_IRWXU) < 0)
+ if (mkdir(path, pg_dir_create_mode) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, path, strerror(errno));
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index c0cfa6e92c..9dfb2ed96f 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 15;
+use Test::More tests => 16;
my $tempdir = TestLib::tempdir;
my $xlogdir = "$tempdir/pgxlog";
@@ -45,6 +45,15 @@ mkdir $datadir;
command_ok([ 'initdb', '-N', '-T', 'german', '-X', $xlogdir, $datadir ],
'successful creation');
+
+ # Permissions on PGDATA should be default
+ SKIP:
+ {
+ skip "unix-style permissions not supported on Windows", 1 if ($windows_os);
+
+ ok(check_mode_recursive($datadir, 0700, 0600),
+ "check PGDATA permissions");
+ }
}
command_ok([ 'initdb', '-S', $datadir ], 'sync only');
command_fails([ 'initdb', $datadir ], 'existing data directory');
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 8610fe8a1a..32b41e313c 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -27,6 +27,7 @@
#endif
#include "access/xlog_internal.h"
+#include "common/file_perm.h"
#include "common/file_utils.h"
#include "common/string.h"
#include "fe_utils/string_utils.h"
@@ -629,7 +630,7 @@ StartLogStreamer(char *startpos, uint32 timeline, char *sysidentifier)
PQserverVersion(conn) < MINIMUM_VERSION_FOR_PG_WAL ?
"pg_xlog" : "pg_wal");
- if (pg_mkdir_p(statusdir, S_IRWXU) != 0 && errno != EEXIST)
+ if (pg_mkdir_p(statusdir, pg_dir_create_mode) != 0 && errno != EEXIST)
{
fprintf(stderr,
_("%s: could not create directory \"%s\": %s\n"),
@@ -685,7 +686,7 @@ verify_dir_is_empty_or_create(char *dirname, bool *created, bool *found)
/*
* Does not exist, so create
*/
- if (pg_mkdir_p(dirname, S_IRWXU) == -1)
+ if (pg_mkdir_p(dirname, pg_dir_create_mode) == -1)
{
fprintf(stderr,
_("%s: could not create directory \"%s\": %s\n"),
@@ -1129,7 +1130,7 @@ ReceiveTarFile(PGconn *conn, PGresult *res, int rownum)
tarCreateHeader(header, "recovery.conf", NULL,
recoveryconfcontents->len,
- 0600, 04000, 02000,
+ pg_file_create_mode, 04000, 02000,
time(NULL));
padding = ((recoveryconfcontents->len + 511) & ~511) - recoveryconfcontents->len;
@@ -1441,7 +1442,7 @@ ReceiveAndUnpackTarFile(PGconn *conn, PGresult *res, int rownum)
* Directory
*/
filename[strlen(filename) - 1] = '\0'; /* Remove trailing slash */
- if (mkdir(filename, S_IRWXU) != 0)
+ if (mkdir(filename, pg_dir_create_mode) != 0)
{
/*
* When streaming WAL, pg_wal (or pg_xlog for pre-9.6
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index e2f1465472..ec54b555ed 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -5,7 +5,7 @@ use Config;
use File::Basename qw(basename dirname);
use PostgresNode;
use TestLib;
-use Test::More tests => 104;
+use Test::More tests => 105;
program_help_ok('pg_basebackup');
program_version_ok('pg_basebackup');
@@ -15,6 +15,9 @@ my $tempdir = TestLib::tempdir;
my $node = get_new_node('main');
+# Set umask so test directories and files are created with default permissions
+umask(0077);
+
# Initialize node without replication settings
$node->init(extra => [ '--data-checksums' ]);
$node->start;
@@ -93,6 +96,15 @@ $node->command_ok([ 'pg_basebackup', '-D', "$tempdir/backup", '-X', 'none' ],
'pg_basebackup runs');
ok(-f "$tempdir/backup/PG_VERSION", 'backup was created');
+# Permissions on backup should be default
+SKIP:
+{
+ skip "unix-style permissions not supported on Windows", 1 if ($windows_os);
+
+ ok(check_mode_recursive("$tempdir/backup", 0700, 0600),
+ "check backup dir permissions");
+}
+
# Only archive_status directory should be copied in pg_wal/.
is_deeply(
[ sort(slurp_dir("$tempdir/backup/pg_wal/")) ],
diff --git a/src/bin/pg_basebackup/t/020_pg_receivewal.pl b/src/bin/pg_basebackup/t/020_pg_receivewal.pl
index 64e3a35a87..19c106d9f5 100644
--- a/src/bin/pg_basebackup/t/020_pg_receivewal.pl
+++ b/src/bin/pg_basebackup/t/020_pg_receivewal.pl
@@ -2,12 +2,15 @@ use strict;
use warnings;
use TestLib;
use PostgresNode;
-use Test::More tests => 18;
+use Test::More tests => 19;
program_help_ok('pg_receivewal');
program_version_ok('pg_receivewal');
program_options_handling_ok('pg_receivewal');
+# Set umask so test directories and files are created with default permissions
+umask(0077);
+
my $primary = get_new_node('primary');
$primary->init(allows_streaming => 1);
$primary->start;
@@ -56,3 +59,12 @@ $primary->command_ok(
[ 'pg_receivewal', '-D', $stream_dir, '--verbose',
'--endpos', $nextlsn, '--synchronous', '--no-loop' ],
'streaming some WAL with --synchronous');
+
+# Permissions on WAL files should be default
+SKIP:
+{
+ skip "unix-style permissions not supported on Windows", 1 if ($windows_os);
+
+ ok(check_mode_recursive($stream_dir, 0700, 0600),
+ "check stream dir permissions");
+}
diff --git a/src/bin/pg_basebackup/walmethods.c b/src/bin/pg_basebackup/walmethods.c
index b4558a0184..267a40debb 100644
--- a/src/bin/pg_basebackup/walmethods.c
+++ b/src/bin/pg_basebackup/walmethods.c
@@ -22,6 +22,7 @@
#endif
#include "pgtar.h"
+#include "common/file_perm.h"
#include "common/file_utils.h"
#include "receivelog.h"
@@ -89,7 +90,7 @@ dir_open_for_write(const char *pathname, const char *temp_suffix, size_t pad_to_
* does not do any system calls to fsync() to make changes permanent on
* disk.
*/
- fd = open(tmppath, O_WRONLY | O_CREAT | PG_BINARY, S_IRUSR | S_IWUSR);
+ fd = open(tmppath, O_WRONLY | O_CREAT | PG_BINARY, pg_file_create_mode);
if (fd < 0)
return NULL;
@@ -534,7 +535,8 @@ tar_open_for_write(const char *pathname, const char *temp_suffix, size_t pad_to_
* We open the tar file only when we first try to write to it.
*/
tar_data->fd = open(tar_data->tarfilename,
- O_WRONLY | O_CREAT | PG_BINARY, S_IRUSR | S_IWUSR);
+ O_WRONLY | O_CREAT | PG_BINARY,
+ pg_file_create_mode);
if (tar_data->fd < 0)
return NULL;
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index 9bc830b085..e83a7179ea 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -25,6 +25,7 @@
#include "catalog/pg_control.h"
#include "common/controldata_utils.h"
+#include "common/file_perm.h"
#include "getopt_long.h"
#include "utils/pidfile.h"
@@ -2170,7 +2171,8 @@ main(int argc, char **argv)
*/
argv0 = argv[0];
- umask(S_IRWXG | S_IRWXO);
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_DEFAULT);
/* support --help and --version even if invoked as root */
if (argc > 1)
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl
index 5da4746cb4..067a084c87 100644
--- a/src/bin/pg_ctl/t/001_start_stop.pl
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -4,7 +4,7 @@ use warnings;
use Config;
use PostgresNode;
use TestLib;
-use Test::More tests => 19;
+use Test::More tests => 21;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -57,9 +57,23 @@ command_ok([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ], 'pg_ctl stop');
command_fails([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ],
'second pg_ctl stop fails');
+# Log file for default permission test. The permissions won't be checked on
+# Windows but we still want to do the restart test.
+my $logFileName = "$tempdir/data/perm-test-600.log";
+
command_ok(
- [ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
+ [ 'pg_ctl', 'restart', '-D', "$tempdir/data", '-l', $logFileName ],
'pg_ctl restart with server not running');
+
+# Permissions on log file should be default
+SKIP:
+{
+ skip "unix-style permissions not supported on Windows", 2 if ($windows_os);
+
+ ok(-f $logFileName);
+ ok(check_mode_recursive("$tempdir/data", 0700, 0600));
+}
+
command_ok([ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
'pg_ctl restart with server running');
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index eba7c5fdee..bdf71886ee 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -52,6 +52,7 @@
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
#include "common/fe_memutils.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "storage/large_object.h"
#include "pg_getopt.h"
@@ -967,7 +968,7 @@ RewriteControlFile(void)
fd = open(XLOG_CONTROL_FILE,
O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ pg_file_create_mode);
if (fd < 0)
{
fprintf(stderr, _("%s: could not create pg_control file: %s\n"),
@@ -1249,7 +1250,7 @@ WriteEmptyXLOG(void)
unlink(path);
fd = open(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ pg_file_create_mode);
if (fd < 0)
{
fprintf(stderr, _("%s: could not open file \"%s\": %s\n"),
diff --git a/src/bin/pg_resetwal/t/001_basic.pl b/src/bin/pg_resetwal/t/001_basic.pl
index 1b157cb555..0d6ab20073 100644
--- a/src/bin/pg_resetwal/t/001_basic.pl
+++ b/src/bin/pg_resetwal/t/001_basic.pl
@@ -3,7 +3,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 11;
+use Test::More tests => 12;
program_help_ok('pg_resetwal');
program_version_ok('pg_resetwal');
@@ -15,3 +15,13 @@ $node->init;
command_like([ 'pg_resetwal', '-n', $node->data_dir ],
qr/checkpoint/,
'pg_resetwal -n produces output');
+
+
+# Permissions on PGDATA should be default
+SKIP:
+{
+ skip "unix-style permissions not supported on Windows", 1 if ($windows_os);
+
+ ok(check_mode_recursive($node->data_dir, 0700, 0600),
+ 'check PGDATA permissions');
+}
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index 00b5b42dd7..7b632c7dcd 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -237,6 +237,10 @@ sub run_pg_rewind
"$tmp_folder/master-postgresql.conf.tmp",
"$master_pgdata/postgresql.conf");
+ chmod(0600, "$master_pgdata/postgresql.conf")
+ or BAIL_OUT(
+ "unable to set permissions for $master_pgdata/postgresql.conf");
+
# Plug-in rewound node to the now-promoted standby node
my $port_standby = $node_standby->port;
$node_master->append_conf(
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index f491ed7f5c..94bcc13ae8 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -18,6 +18,7 @@
#include <fcntl.h>
#include <unistd.h>
+#include "common/file_perm.h"
#include "file_ops.h"
#include "filemap.h"
#include "logging.h"
@@ -57,7 +58,7 @@ open_target_file(const char *path, bool trunc)
mode = O_WRONLY | O_CREAT | PG_BINARY;
if (trunc)
mode |= O_TRUNC;
- dstfd = open(dstpath, mode, 0600);
+ dstfd = open(dstpath, mode, pg_file_create_mode);
if (dstfd < 0)
pg_fatal("could not open target file \"%s\": %s\n",
dstpath, strerror(errno));
@@ -198,7 +199,7 @@ truncate_target_file(const char *path, off_t newsize)
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
- fd = open(dstpath, O_WRONLY, 0);
+ fd = open(dstpath, O_WRONLY, pg_file_create_mode);
if (fd < 0)
pg_fatal("could not open file \"%s\" for truncation: %s\n",
dstpath, strerror(errno));
@@ -219,7 +220,7 @@ create_target_dir(const char *path)
return;
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
- if (mkdir(dstpath, S_IRWXU) != 0)
+ if (mkdir(dstpath, pg_dir_create_mode) != 0)
pg_fatal("could not create directory \"%s\": %s\n",
dstpath, strerror(errno));
}
diff --git a/src/bin/pg_rewind/t/001_basic.pl b/src/bin/pg_rewind/t/001_basic.pl
index 736f34eae3..1b0f823b0c 100644
--- a/src/bin/pg_rewind/t/001_basic.pl
+++ b/src/bin/pg_rewind/t/001_basic.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 8;
+use Test::More tests => 10;
use RewindTest;
@@ -86,6 +86,15 @@ in master, before promotion
),
'tail-copy');
+ # Permissions on PGDATA should be default
+ SKIP:
+ {
+ skip "unix-style permissions not supported on Windows", 1 if ($windows_os);
+
+ ok(check_mode_recursive($node_master->data_dir(), 0700, 0600),
+ 'check PGDATA permissions');
+ }
+
RewindTest::clean_rewind_test();
}
diff --git a/src/bin/pg_upgrade/file.c b/src/bin/pg_upgrade/file.c
index f38bfacf02..f68211aa20 100644
--- a/src/bin/pg_upgrade/file.c
+++ b/src/bin/pg_upgrade/file.c
@@ -10,6 +10,7 @@
#include "postgres_fe.h"
#include "access/visibilitymap.h"
+#include "common/file_perm.h"
#include "pg_upgrade.h"
#include "storage/bufpage.h"
#include "storage/checksum.h"
@@ -44,7 +45,7 @@ copyFile(const char *src, const char *dst,
schemaName, relName, src, strerror(errno));
if ((dest_fd = open(dst, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR)) < 0)
+ pg_file_create_mode)) < 0)
pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
schemaName, relName, dst, strerror(errno));
@@ -151,7 +152,7 @@ rewriteVisibilityMap(const char *fromfile, const char *tofile,
schemaName, relName, fromfile, strerror(errno));
if ((dst_fd = open(tofile, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR)) < 0)
+ pg_file_create_mode)) < 0)
pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
schemaName, relName, tofile, strerror(errno));
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index d12412799f..59df6fd88f 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -38,6 +38,7 @@
#include "pg_upgrade.h"
#include "catalog/pg_class.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "fe_utils/string_utils.h"
@@ -79,7 +80,7 @@ main(int argc, char **argv)
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_upgrade"));
/* Ensure that all files created by pg_upgrade are non-world-readable */
- umask(S_IRWXG | S_IRWXO);
+ umask(PG_MODE_MASK_DEFAULT);
parseCommandLine(argc, argv);
diff --git a/src/bin/pg_upgrade/test.sh b/src/bin/pg_upgrade/test.sh
index 39983abea1..574639d47e 100644
--- a/src/bin/pg_upgrade/test.sh
+++ b/src/bin/pg_upgrade/test.sh
@@ -230,6 +230,17 @@ standard_initdb 'initdb'
pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "${PGDATA}" -b "$oldbindir" -B "$bindir" -p "$PGPORT" -P "$PGPORT"
+# make sure all directories and files have correct permissions
+if [ $(find ${PGDATA} -type f ! -perm 600 | wc -l) -ne 0 ]; then
+ echo "files in PGDATA with permission != 600";
+ exit 1;
+fi
+
+if [ $(find ${PGDATA} -type d ! -perm 700 | wc -l) -ne 0 ]; then
+ echo "directories in PGDATA with permission != 700";
+ exit 1;
+fi
+
pg_ctl start -l "$logdir/postmaster2.log" -o "$POSTMASTER_OPTS" -w
case $testhost in
diff --git a/src/common/Makefile b/src/common/Makefile
index 873fbb6438..e9e75867f3 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -40,8 +40,8 @@ override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
override CPPFLAGS := -DFRONTEND $(CPPFLAGS)
LIBS += $(PTHREAD_LIBS)
-OBJS_COMMON = base64.o config_info.o controldata_utils.o exec.o ip.o \
- keywords.o md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
+OBJS_COMMON = base64.o config_info.o controldata_utils.o exec.o file_perm.o \
+ ip.o keywords.o md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
rmtree.o saslprep.o scram-common.o string.o unicode_norm.o \
username.o wait_error.o
diff --git a/src/common/file_perm.c b/src/common/file_perm.c
new file mode 100644
index 0000000000..fdfbb9a44c
--- /dev/null
+++ b/src/common/file_perm.c
@@ -0,0 +1,19 @@
+/*-------------------------------------------------------------------------
+ *
+ * File and directory permission routines
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/file_perm.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include <sys/stat.h>
+
+#include "common/file_perm.h"
+
+/* Modes for creating directories and files in the data directory */
+int pg_dir_create_mode = PG_DIR_MODE_OWNER;
+int pg_file_create_mode = PG_FILE_MODE_OWNER;
diff --git a/src/include/common/file_perm.h b/src/include/common/file_perm.h
new file mode 100644
index 0000000000..e34a128af9
--- /dev/null
+++ b/src/include/common/file_perm.h
@@ -0,0 +1,32 @@
+/*-------------------------------------------------------------------------
+ *
+ * File and directory permission constants
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/file_perm.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FILE_PERM_H
+#define FILE_PERM_H
+
+/*
+ * Default mode mask for data directory permissions only allows the owner to
+ * read/write directories and files.
+ */
+#define PG_MODE_MASK_OWNER (S_IRWXG | S_IRWXO)
+
+/* Default mode for creating directories */
+#define PG_DIR_MODE_OWNER S_IRWXU
+
+/* Default mode for creating files */
+#define PG_FILE_MODE_OWNER (S_IRUSR | S_IWUSR)
+
+/* Modes for creating directories and files in the data directory */
+extern int pg_dir_create_mode;
+extern int pg_file_create_mode;
+
+#endif /* FILE_PERM_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index e49b42ce86..785d9058e9 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -112,6 +112,9 @@ extern int CloseTransientFile(int fd);
extern int BasicOpenFile(const char *fileName, int fileFlags);
extern int BasicOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode);
+ /* Make a directory with default permissions */
+extern int MakePGDirectory(const char *directoryName);
+
/* Miscellaneous support routines */
extern void InitFileAccess(void);
extern void set_max_safe_fds(void);
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 80188315f1..76e571b98c 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -484,6 +484,9 @@ sub append_conf
my $conffile = $self->data_dir . '/' . $filename;
TestLib::append_to_file($conffile, $str . "\n");
+
+ chmod(0600, $conffile)
+ or die("unable to set permissions for $conffile");
}
=pod
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index b6862688d4..93610e4bc4 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -13,8 +13,11 @@ use warnings;
use Config;
use Cwd;
use Exporter 'import';
+use Fcntl qw(:mode);
use File::Basename;
+use File::Find;
use File::Spec;
+use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
use SimpleTee;
@@ -27,6 +30,7 @@ our @EXPORT = qw(
slurp_dir
slurp_file
append_to_file
+ check_mode_recursive
check_pg_config
system_or_bail
system_log
@@ -240,6 +244,75 @@ sub append_to_file
close $fh;
}
+# Check that all file/dir modes in a directory match the expected values,
+# ignoring the mode of any specified files.
+sub check_mode_recursive
+{
+ my ($dir, $expected_dir_mode, $expected_file_mode, $ignore_list) = @_;
+
+ # Result defaults to true
+ my $result = 1;
+
+ find
+ (
+ {follow_fast => 1,
+ wanted =>
+ sub
+ {
+ my $file_stat = stat($File::Find::name);
+
+ # Is file in the ignore list?
+ foreach my $ignore ($ignore_list ? @{$ignore_list} : [])
+ {
+ if ("$dir/$ignore" eq $File::Find::name)
+ {
+ return;
+ }
+ }
+
+ defined($file_stat)
+ or die("unable to stat $File::Find::name");
+
+ my $file_mode = S_IMODE($file_stat->mode);
+
+ # Is this a file?
+ if (S_ISREG($file_stat->mode))
+ {
+ if ($file_mode != $expected_file_mode)
+ {
+ print(*STDERR,
+ sprintf("$File::Find::name mode must be %04o\n",
+ $expected_file_mode));
+
+ $result = 0;
+ return;
+ }
+ }
+ # Else a directory?
+ elsif (S_ISDIR($file_stat->mode))
+ {
+ if ($file_mode != $expected_dir_mode)
+ {
+ print(*STDERR,
+ sprintf("$File::Find::name mode must be %04o\n",
+ $expected_dir_mode));
+
+ $result = 0;
+ return;
+ }
+ }
+ # Else something we can't handle
+ else
+ {
+ die "unknown file type for $File::Find::name";
+ }
+ }},
+ $dir
+ );
+
+ return $result;
+}
+
# Check presence of a given regexp within pg_config.h for the installation
# where tests are running, returning a match status result depending on
# that.
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 41d720880a..1d3ed6b0b1 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -111,8 +111,8 @@ sub mkvcbuild
}
our @pgcommonallfiles = qw(
- base64.c config_info.c controldata_utils.c exec.c ip.c keywords.c
- md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
+ base64.c config_info.c controldata_utils.c exec.c file_perm.c ip.c
+ keywords.c md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
saslprep.c scram-common.c string.c unicode_norm.c username.c
wait_error.c);
--
2.14.1
From 07b0c54c6764b926ee64b3c0671f1ea7d84fd2f5 Mon Sep 17 00:00:00 2001
From: David Steele <david@pgmasters.net>
Date: Fri, 6 Apr 2018 09:29:23 -0400
Subject: [PATCH 2/2] Allow group access on PGDATA.
This includes backend changes, utility changes (initdb, pg_ctl,
pg_upgrade, etc.) and tests for each utility.
Author: David Steele <david@pgmasters.net>
Reviewed-By: Michael Paquier, with discussion amongst many others.
Discussion: https://postgr.es/m/https://www.postgresql.org/message-id/ad346fe6-b23e-59f1-ecb7-0e08390ad629%40pgmasters.net
---
doc/src/sgml/ref/initdb.sgml | 19 +++++
doc/src/sgml/ref/pg_basebackup.sgml | 6 ++
doc/src/sgml/ref/pg_receivewal.sgml | 6 ++
doc/src/sgml/ref/pg_recvlogical.sgml | 11 +++
doc/src/sgml/runtime.sgml | 14 +++-
src/backend/bootstrap/bootstrap.c | 12 +--
src/backend/postmaster/postmaster.c | 86 +++-----------------
src/backend/tcop/postgres.c | 3 +-
src/backend/utils/init/globals.c | 9 +++
src/backend/utils/init/miscinit.c | 115 ++++++++++++++++++++++++---
src/backend/utils/misc/guc.c | 25 ++++++
src/bin/initdb/initdb.c | 24 +++++-
src/bin/initdb/t/001_initdb.pl | 20 ++++-
src/bin/pg_basebackup/pg_basebackup.c | 22 +++--
src/bin/pg_basebackup/pg_receivewal.c | 7 ++
src/bin/pg_basebackup/pg_recvlogical.c | 7 ++
src/bin/pg_basebackup/streamutil.c | 69 ++++++++++++++++
src/bin/pg_basebackup/t/010_pg_basebackup.pl | 26 ++++--
src/bin/pg_ctl/pg_ctl.c | 14 +++-
src/bin/pg_ctl/t/001_start_stop.pl | 25 +++++-
src/bin/pg_resetwal/pg_resetwal.c | 10 +++
src/bin/pg_rewind/RewindTest.pm | 9 ++-
src/bin/pg_rewind/pg_rewind.c | 11 +++
src/bin/pg_rewind/t/002_databases.pl | 13 ++-
src/bin/pg_upgrade/pg_upgrade.c | 14 +++-
src/bin/pg_upgrade/test.sh | 16 ++--
src/common/file_perm.c | 68 ++++++++++++++++
src/include/common/file_perm.h | 20 +++++
src/include/miscadmin.h | 2 +
src/test/perl/PostgresNode.pm | 27 ++++++-
src/test/perl/TestLib.pm | 25 ++++++
31 files changed, 604 insertions(+), 131 deletions(-)
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 826dd91f72..f32c600834 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -76,6 +76,14 @@ PostgreSQL documentation
to do so.)
</para>
+ <para>
+ For security reasons the new cluster created by <command>initdb</command>
+ will only be accessible by the cluster owner by default. The
+ <option>--allow-group-access</option> option allows any user in the same
+ group as the cluster owner to read files in the cluster. This is useful
+ for performing backups as a non-privileged user.
+ </para>
+
<para>
<command>initdb</command> initializes the database cluster's default
locale and character set encoding. The character set encoding,
@@ -301,6 +309,17 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-g</option></term>
+ <term><option>--allow-group-access</option></term>
+ <listitem>
+ <para>
+ Allows users in the same group as the cluster owner to read all cluster
+ files created by <command>initdb</command>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-W</option></term>
<term><option>--pwprompt</option></term>
diff --git a/doc/src/sgml/ref/pg_basebackup.sgml b/doc/src/sgml/ref/pg_basebackup.sgml
index 95045669c9..fc1edf4864 100644
--- a/doc/src/sgml/ref/pg_basebackup.sgml
+++ b/doc/src/sgml/ref/pg_basebackup.sgml
@@ -737,6 +737,12 @@ PostgreSQL documentation
or later.
</para>
+ <para>
+ <application>pg_basebackup</application> will preserve group permissions in
+ both the <literal>plain</literal> and <literal>tar</literal> formats if group
+ permissions are enabled on the source cluster.
+ </para>
+
</refsect1>
<refsect1>
diff --git a/doc/src/sgml/ref/pg_receivewal.sgml b/doc/src/sgml/ref/pg_receivewal.sgml
index e3f2ce1fcb..a18ddd4bff 100644
--- a/doc/src/sgml/ref/pg_receivewal.sgml
+++ b/doc/src/sgml/ref/pg_receivewal.sgml
@@ -425,6 +425,12 @@ PostgreSQL documentation
not keep up with fetching the WAL data.
</para>
+ <para>
+ <application>pg_receivewal</application> will preserve group permissions on
+ the received WAL files if group permissions are enabled on the source
+ cluster.
+ </para>
+
</refsect1>
<refsect1>
diff --git a/doc/src/sgml/ref/pg_recvlogical.sgml b/doc/src/sgml/ref/pg_recvlogical.sgml
index a79ca20084..141c5cddce 100644
--- a/doc/src/sgml/ref/pg_recvlogical.sgml
+++ b/doc/src/sgml/ref/pg_recvlogical.sgml
@@ -399,6 +399,17 @@ PostgreSQL documentation
</para>
</refsect1>
+ <refsect1>
+ <title>Notes</title>
+
+ <para>
+ <application>pg_recvlogical</application> will preserve group permissions on
+ the received WAL files if group permissions are enabled on the source
+ cluster.
+ </para>
+
+ </refsect1>
+
<refsect1>
<title>Examples</title>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 587b430527..40ce3d9813 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -137,7 +137,10 @@ postgres$ <userinput>initdb -D /usr/local/pgsql/data</userinput>
database, it is essential that it be secured from unauthorized
access. <command>initdb</command> therefore revokes access
permissions from everyone but the
- <productname>PostgreSQL</productname> user.
+ <productname>PostgreSQL</productname> user, and optionally, group.
+ Group access, when enabled, is read-only. This allows an unprivileged
+ user in the <productname>PostgreSQL</productname> group to take a backup of
+ the cluster data or perform other operations that only require read access.
</para>
<para>
@@ -2194,6 +2197,15 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
member of the group that has access to those certificate and key files.
</para>
+ <para>
+ If the data directory allows group read access then certificate files may
+ need to be located outside of the data directory in order to conform to the
+ security requirements outlined above. Generally, group access is enabled
+ to allow an unprivileged user to backup the database, and in that case the
+ backup software will not be able to read the certificate files and will
+ likely error.
+ </para>
+
<para>
If the private key is protected with a passphrase, the
server will prompt for the passphrase and will not start until it has
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 1430894ad2..b8ecbda444 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 39801db5e9..aa78bf3a69 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,9 +588,14 @@ 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 (onwer-only) permissions.
+ *
+ * checkDataDir() will reset the umask based on the data directory
+ * permissions.
*/
- umask(PG_MODE_MASK_DEFAULT);
+ umask(PG_MODE_MASK_OWNER);
/*
* Initialize random(3) so we don't get the same values in every run.
@@ -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,14 @@ getInstallationPaths(const char *argv0)
*/
}
-
/*
- * Validate the proposed data directory
+ * Check that pg_control exists in the correct location in the 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 7bdecc32ec..5095a4f686 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 c1f0441b08..c6b616d61f 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 f8f08f3f88..436f89912e 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -88,6 +88,100 @@ SetDatabasePath(const char *path)
DatabasePath = MemoryContextStrdup(TopMemoryContext, 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 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 71c2b4eff1..8441837e0f 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"
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 83472d9ceb..a40c07649f 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -1168,6 +1168,19 @@ setup_config(void)
"password_encryption = scram-sha-256");
}
+ /*
+ * If group access has been enabled for the cluster then it makes sense
+ * to ensure that the log files also allow group access. Otherwise a
+ * backup from a user in the group would fail if the log files were not
+ * relocated.
+ */
+ if (pg_dir_create_mode == PG_DIR_MODE_GROUP)
+ {
+ conflines = replace_token(conflines,
+ "#log_file_mode = 0600",
+ "log_file_mode = 0640");
+ }
+
snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
writefile(path, conflines);
@@ -2312,6 +2325,7 @@ usage(const char *progname)
printf(_(" --auth-local=METHOD default authentication method for local-socket connections\n"));
printf(_(" [-D, --pgdata=]DATADIR location for this database cluster\n"));
printf(_(" -E, --encoding=ENCODING set default encoding for new databases\n"));
+ printf(_(" -g, --allow-group-access allow group read/execute on data directory\n"));
printf(_(" --locale=LOCALE set default locale for new databases\n"));
printf(_(" --lc-collate=, --lc-ctype=, --lc-messages=LOCALE\n"
" --lc-monetary=, --lc-numeric=, --lc-time=LOCALE\n"
@@ -2883,8 +2897,8 @@ initialize_data_directory(void)
setup_signals();
- /* Set dir/file mode mask */
- umask(PG_MODE_MASK_DEFAULT);
+ /* Set mask based on requested PGDATA permissions */
+ umask(pg_mode_mask);
create_data_directory();
@@ -3018,6 +3032,7 @@ main(int argc, char *argv[])
{"waldir", required_argument, NULL, 'X'},
{"wal-segsize", required_argument, NULL, 12},
{"data-checksums", no_argument, NULL, 'k'},
+ {"allow-group-access", no_argument, NULL, 'g'},
{NULL, 0, NULL, 0}
};
@@ -3059,7 +3074,7 @@ main(int argc, char *argv[])
/* process command-line options */
- while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:", long_options, &option_index)) != -1)
+ while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:g", long_options, &option_index)) != -1)
{
switch (c)
{
@@ -3153,6 +3168,9 @@ main(int argc, char *argv[])
case 12:
str_wal_segment_size_mb = pg_strdup(optarg);
break;
+ case 'g':
+ SetDataDirectoryCreatePerm(PG_DIR_MODE_GROUP);
+ break;
default:
/* getopt_long already emitted a complaint */
fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index 9dfb2ed96f..8609d2ecbc 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -4,9 +4,11 @@
use strict;
use warnings;
+use Fcntl ':mode';
+use File::stat qw{lstat};
use PostgresNode;
use TestLib;
-use Test::More tests => 16;
+use Test::More tests => 18;
my $tempdir = TestLib::tempdir;
my $xlogdir = "$tempdir/pgxlog";
@@ -57,3 +59,19 @@ mkdir $datadir;
}
command_ok([ 'initdb', '-S', $datadir ], 'sync only');
command_fails([ 'initdb', $datadir ], 'existing data directory');
+
+# Check group access on PGDATA
+SKIP:
+{
+ skip "unix-style permissions not supported on Windows", 2 if ($windows_os);
+
+ # Init a new db with group access
+ my $datadir_group = "$tempdir/data_group";
+
+ command_ok(
+ [ 'initdb', '-g', $datadir_group ],
+ 'successful creation with group access');
+
+ ok(check_mode_recursive($datadir_group, 0750, 0640),
+ 'check PGDATA permissions');
+}
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 32b41e313c..5cd535184a 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -2470,14 +2470,6 @@ main(int argc, char **argv)
}
#endif
- /*
- * Verify that the target directory exists, or create it. For plaintext
- * backups, always require the directory. For tar backups, require it
- * unless we are writing to stdout.
- */
- if (format == 'p' || strcmp(basedir, "-") != 0)
- verify_dir_is_empty_or_create(basedir, &made_new_pgdata, &found_existing_pgdata);
-
/* connection in replication mode to server */
conn = GetConnection();
if (!conn)
@@ -2486,6 +2478,20 @@ main(int argc, char **argv)
exit(1);
}
+ /*
+ * Set umask so that directories/files are created with the same permissions
+ * as directories/files in the source data directory.
+ */
+ umask(pg_mode_mask);
+
+ /*
+ * Verify that the target directory exists, or create it. For plaintext
+ * backups, always require the directory. For tar backups, require it
+ * unless we are writing to stdout.
+ */
+ if (format == 'p' || strcmp(basedir, "-") != 0)
+ verify_dir_is_empty_or_create(basedir, &made_new_pgdata, &found_existing_pgdata);
+
/* determine remote server's xlog segment size */
if (!RetrieveWalSegSize(conn))
disconnect_and_exit(1);
diff --git a/src/bin/pg_basebackup/pg_receivewal.c b/src/bin/pg_basebackup/pg_receivewal.c
index b82e073b86..f23ecf799f 100644
--- a/src/bin/pg_basebackup/pg_receivewal.c
+++ b/src/bin/pg_basebackup/pg_receivewal.c
@@ -19,6 +19,7 @@
#include <sys/stat.h>
#include <unistd.h>
+#include "common/file_perm.h"
#include "libpq-fe.h"
#include "access/xlog_internal.h"
#include "getopt_long.h"
@@ -703,6 +704,12 @@ main(int argc, char **argv)
if (!RunIdentifySystem(conn, NULL, NULL, NULL, &db_name))
disconnect_and_exit(1);
+ /*
+ * Set umask so that directories/files are created with the same permissions
+ * as directories/files in the source data directory.
+ */
+ umask(pg_mode_mask);
+
/* determine remote server's xlog segment size */
if (!RetrieveWalSegSize(conn))
disconnect_and_exit(1);
diff --git a/src/bin/pg_basebackup/pg_recvlogical.c b/src/bin/pg_basebackup/pg_recvlogical.c
index 0866395944..e3b6404b1f 100644
--- a/src/bin/pg_basebackup/pg_recvlogical.c
+++ b/src/bin/pg_basebackup/pg_recvlogical.c
@@ -23,6 +23,7 @@
#include "streamutil.h"
#include "access/xlog_internal.h"
+#include "common/file_perm.h"
#include "common/fe_memutils.h"
#include "getopt_long.h"
#include "libpq-fe.h"
@@ -959,6 +960,12 @@ main(int argc, char **argv)
disconnect_and_exit(1);
}
+ /*
+ * Set umask so that directories/files are created with the same permissions
+ * as directories/files in the source data directory.
+ */
+ umask(pg_mode_mask);
+
/* Drop a replication slot. */
if (do_drop_slot)
{
diff --git a/src/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c
index 1438f368ed..494acfec0e 100644
--- a/src/bin/pg_basebackup/streamutil.c
+++ b/src/bin/pg_basebackup/streamutil.c
@@ -23,6 +23,7 @@
#include "access/xlog_internal.h"
#include "common/fe_memutils.h"
+#include "common/file_perm.h"
#include "datatype/timestamp.h"
#include "fe_utils/connect.h"
#include "port/pg_bswap.h"
@@ -32,9 +33,16 @@
uint32 WalSegSz;
+static bool RetrieveDataDirCreatePerm(PGconn *conn);
+
/* SHOW command for replication connection was introduced in version 10 */
#define MINIMUM_VERSION_FOR_SHOW_CMD 100000
+/*
+ * Group access is supported from version 11.
+ */
+#define MINIMUM_VERSION_FOR_GROUP_ACCESS 110000
+
const char *progname;
char *connection_string = NULL;
char *dbhost = NULL;
@@ -254,6 +262,16 @@ GetConnection(void)
exit(1);
}
+ /*
+ * Retrieve the source data directory mode and use to construct a umask
+ * for creating directories and files
+ */
+ if (!RetrieveDataDirCreatePerm(tmpconn))
+ {
+ PQfinish(tmpconn);
+ exit(1);
+ }
+
return tmpconn;
}
@@ -327,6 +345,57 @@ RetrieveWalSegSize(PGconn *conn)
return true;
}
+/*
+ * From version 11, check the mode of the data directory to determine
+ * permissions to use for directories created by the utility.
+ */
+static bool
+RetrieveDataDirCreatePerm(PGconn *conn)
+{
+ PGresult *res;
+ int data_directory_mode;
+
+ /* check connection existence */
+ Assert(conn != NULL);
+
+ /* for previous versions leave the default group access */
+ if (PQserverVersion(conn) < MINIMUM_VERSION_FOR_GROUP_ACCESS)
+ return true;
+
+ res = PQexec(conn, "SHOW data_directory_mode");
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ fprintf(stderr, _("%s: could not send replication command \"%s\": %s\n"),
+ progname, "SHOW data_directory_mode", PQerrorMessage(conn));
+
+ PQclear(res);
+ return false;
+ }
+ if (PQntuples(res) != 1 || PQnfields(res) < 1)
+ {
+ fprintf(stderr,
+ _("%s: could not fetch group access flag: got %d rows and %d fields, expected %d rows and %d or more fields\n"),
+ progname, PQntuples(res), PQnfields(res), 1, 1);
+
+ PQclear(res);
+ return false;
+ }
+
+ if (sscanf(PQgetvalue(res, 0, 0), "%o", &data_directory_mode) != 1)
+ {
+ fprintf(stderr, _("%s: group access flag could not be parsed: %s\n"),
+ progname, PQgetvalue(res, 0, 0));
+
+ PQclear(res);
+ return false;
+ }
+
+ SetDataDirectoryCreatePerm(data_directory_mode);
+
+ PQclear(res);
+ return true;
+}
+
/*
* Run IDENTIFY_SYSTEM through a given connection and give back to caller
* some result information if requested:
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index ec54b555ed..a40e9c2d51 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -5,7 +5,7 @@ use Config;
use File::Basename qw(basename dirname);
use PostgresNode;
use TestLib;
-use Test::More tests => 105;
+use Test::More tests => 106;
program_help_ok('pg_basebackup');
program_version_ok('pg_basebackup');
@@ -43,11 +43,6 @@ $node->command_fails(
ok(!-d "$tempdir/backup", 'backup directory was cleaned up');
-$node->command_fails([ 'pg_basebackup', '-D', "$tempdir/backup", '-n' ],
- 'failing run with no-clean option');
-
-ok(-d "$tempdir/backup", 'backup directory was created and left behind');
-
open my $conf, '>>', "$pgdata/postgresql.conf";
print $conf "max_replication_slots = 10\n";
print $conf "max_wal_senders = 10\n";
@@ -162,6 +157,13 @@ ok(-f "$tempdir/tarbackup/base.tar", 'backup tar was created');
$node->command_fails(
[ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T=/foo" ],
'-T with empty old directory fails');
+
+$node->command_fails(
+ [ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T=/foo", "-n" ],
+ 'failing run with no-clean option');
+
+ok(-d "$tempdir/backup", 'backup directory was created and left behind');
+
$node->command_fails(
[ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T/foo=" ],
'-T with empty new directory fails');
@@ -195,11 +197,17 @@ unlink "$pgdata/$superlongname";
# skip on Windows.
SKIP:
{
- skip "symlinks not supported on Windows", 17 if ($windows_os);
+ skip "symlinks not supported on Windows", 18 if ($windows_os);
# Move pg_replslot out of $pgdata and create a symlink to it.
$node->stop;
+ # Set umask so test directories and files are created with group permissions
+ umask(0027);
+
+ # Enable group permissions on PGDATA
+ chmod_recursive("$pgdata", 0750, 0640);
+
rename("$pgdata/pg_replslot", "$tempdir/pg_replslot")
or BAIL_OUT "could not move $pgdata/pg_replslot";
symlink("$tempdir/pg_replslot", "$pgdata/pg_replslot")
@@ -269,6 +277,10 @@ SKIP:
"tablespace symlink was updated");
closedir $dh;
+ # Group access should be enabled on all backup files
+ ok(check_mode_recursive("$tempdir/backup1", 0750, 0640),
+ "check backup dir permissions");
+
# Unlogged relation forks other than init should not be copied
my ($tblspc1UnloggedBackupPath) = $tblspc1UnloggedPath =~ /[^\/]*\/[^\/]*\/[^\/]*$/g;
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index e83a7179ea..143021de05 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -2171,8 +2171,8 @@ main(int argc, char **argv)
*/
argv0 = argv[0];
- /* Set dir/file mode mask */
- umask(PG_MODE_MASK_DEFAULT);
+ /* Set restrictive mode mask until PGDATA permissions are checked */
+ umask(PG_MODE_MASK_OWNER);
/* support --help and --version even if invoked as root */
if (argc > 1)
@@ -2407,6 +2407,16 @@ main(int argc, char **argv)
snprintf(version_file, MAXPGPATH, "%s/PG_VERSION", pg_data);
snprintf(pid_file, MAXPGPATH, "%s/postmaster.pid", pg_data);
snprintf(backup_file, MAXPGPATH, "%s/backup_label", pg_data);
+
+ /*
+ * Set mask based on PGDATA permissions,
+ *
+ * Don't error here if the data directory cannot be stat'd. This is
+ * handled differently based on the command and we don't want to
+ * interfere with that logic.
+ */
+ if (GetDataDirectoryCreatePerm(pg_data))
+ umask(pg_mode_mask);
}
switch (ctl_command)
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl
index 067a084c87..2f9dfa7b81 100644
--- a/src/bin/pg_ctl/t/001_start_stop.pl
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -2,9 +2,11 @@ use strict;
use warnings;
use Config;
+use Fcntl ':mode';
+use File::stat qw{lstat};
use PostgresNode;
use TestLib;
-use Test::More tests => 21;
+use Test::More tests => 24;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -74,6 +76,27 @@ SKIP:
ok(check_mode_recursive("$tempdir/data", 0700, 0600));
}
+# Log file for group access test
+$logFileName = "$tempdir/data/perm-test-640.log";
+
+SKIP:
+{
+ skip "group access not supported on Windows", 3 if ($windows_os);
+
+ system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
+
+ # Change the data dir mode so log file will be created with group read
+ # privileges on the next start
+ chmod_recursive("$tempdir/data", 0750, 0640);
+
+ command_ok(
+ [ 'pg_ctl', 'start', '-D', "$tempdir/data", '-l', $logFileName ],
+ 'start server to check group permissions');
+
+ ok(-f $logFileName);
+ ok(check_mode_recursive("$tempdir/data", 0750, 0640));
+}
+
command_ok([ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
'pg_ctl restart with server running');
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index bdf71886ee..8a0a805f1e 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -363,6 +363,16 @@ main(int argc, char *argv[])
exit(1);
}
+ /* Set mask based on PGDATA permissions */
+ if (!GetDataDirectoryCreatePerm(DataDir))
+ {
+ fprintf(stderr, _("%s: unable to read permissions from \"%s\"\n"),
+ progname, DataDir);
+ exit(1);
+ }
+
+ umask(pg_mode_mask);
+
/* Check that data directory matches our server version */
CheckDataVersion();
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index 7b632c7dcd..63d9bd517d 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -115,11 +115,13 @@ sub check_query
sub setup_cluster
{
- my $extra_name = shift;
+ my $extra_name = shift; # Used to differentiate clusters
+ my $extra = shift; # Extra params for initdb
# Initialize master, data checksums are mandatory
$node_master = get_new_node('master' . ($extra_name ? "_${extra_name}" : ''));
- $node_master->init(allows_streaming => 1);
+ $node_master->init(
+ allows_streaming => 1, extra => $extra);
# Set wal_keep_segments to prevent WAL segment recycling after enforced
# checkpoints in the tests.
$node_master->append_conf('postgresql.conf', qq(
@@ -237,7 +239,8 @@ sub run_pg_rewind
"$tmp_folder/master-postgresql.conf.tmp",
"$master_pgdata/postgresql.conf");
- chmod(0600, "$master_pgdata/postgresql.conf")
+ chmod($node_master->group_access() ? 0640 : 0600,
+ "$master_pgdata/postgresql.conf")
or BAIL_OUT(
"unable to set permissions for $master_pgdata/postgresql.conf");
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 72ab2f8d5e..b9ea6a4c21 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -24,6 +24,7 @@
#include "access/xlog_internal.h"
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "getopt_long.h"
#include "storage/bufpage.h"
@@ -185,6 +186,16 @@ main(int argc, char **argv)
exit(1);
}
+ /* Set mask based on PGDATA permissions */
+ if (!GetDataDirectoryCreatePerm(datadir_target))
+ {
+ fprintf(stderr, _("%s: unable to read permissions from \"%s\"\n"),
+ progname, datadir_target);
+ exit(1);
+ }
+
+ umask(pg_mode_mask);
+
/*
* Don't allow pg_rewind to be run as root, to avoid overwriting the
* ownership of files in the data directory. We need only check for root
diff --git a/src/bin/pg_rewind/t/002_databases.pl b/src/bin/pg_rewind/t/002_databases.pl
index 37cdd712f3..c364965d3a 100644
--- a/src/bin/pg_rewind/t/002_databases.pl
+++ b/src/bin/pg_rewind/t/002_databases.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 6;
use RewindTest;
@@ -9,7 +9,7 @@ sub run_test
{
my $test_mode = shift;
- RewindTest::setup_cluster($test_mode);
+ RewindTest::setup_cluster($test_mode, ['-g']);
RewindTest::start_master();
# Create a database in master.
@@ -42,6 +42,15 @@ template1
),
'database names');
+ # Permissions on PGDATA should have group permissions
+ SKIP:
+ {
+ skip "unix-style permissions not supported on Windows", 1 if ($windows_os);
+
+ ok(check_mode_recursive($node_master->data_dir(), 0750, 0640),
+ 'check PGDATA permissions');
+ }
+
RewindTest::clean_rewind_test();
}
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 59df6fd88f..cc8e8c94c5 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -79,8 +79,8 @@ main(int argc, char **argv)
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_upgrade"));
- /* Ensure that all files created by pg_upgrade are non-world-readable */
- umask(PG_MODE_MASK_DEFAULT);
+ /* Set default restrictive mask until new cluster permissions are read */
+ umask(PG_MODE_MASK_OWNER);
parseCommandLine(argc, argv);
@@ -100,6 +100,16 @@ main(int argc, char **argv)
check_cluster_compatibility(live_check);
+ /* Set mask based on PGDATA permissions */
+ if (!GetDataDirectoryCreatePerm(new_cluster.pgdata))
+ {
+ pg_log(PG_FATAL, "unable to read permissions from \"%s\"\n",
+ new_cluster.pgdata);
+ exit(1);
+ }
+
+ umask(pg_mode_mask);
+
check_and_dump_old_cluster(live_check);
diff --git a/src/bin/pg_upgrade/test.sh b/src/bin/pg_upgrade/test.sh
index 574639d47e..24631a91c6 100644
--- a/src/bin/pg_upgrade/test.sh
+++ b/src/bin/pg_upgrade/test.sh
@@ -20,9 +20,9 @@ unset MAKELEVEL
# Run a given "initdb" binary and overlay the regression testing
# authentication configuration.
standard_initdb() {
- # To increase coverage of non-standard segment size without
- # increase test runtime, run these tests with a lower setting.
- "$1" -N --wal-segsize 1
+ # To increase coverage of non-standard segment size and group access
+ # without increasing test runtime, run these tests with a custom setting.
+ "$1" -N --wal-segsize 1 -g
if [ -n "$TEMP_CONFIG" -a -r "$TEMP_CONFIG" ]
then
cat "$TEMP_CONFIG" >> "$PGDATA/postgresql.conf"
@@ -230,14 +230,14 @@ standard_initdb 'initdb'
pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "${PGDATA}" -b "$oldbindir" -B "$bindir" -p "$PGPORT" -P "$PGPORT"
-# make sure all directories and files have correct permissions
-if [ $(find ${PGDATA} -type f ! -perm 600 | wc -l) -ne 0 ]; then
- echo "files in PGDATA with permission != 600";
+# make sure all directories and files have group permissions
+if [ $(find ${PGDATA} -type f ! -perm 640 | wc -l) -ne 0 ]; then
+ echo "files in PGDATA with permission != 640";
exit 1;
fi
-if [ $(find ${PGDATA} -type d ! -perm 700 | wc -l) -ne 0 ]; then
- echo "directories in PGDATA with permission != 700";
+if [ $(find ${PGDATA} -type d ! -perm 750 | wc -l) -ne 0 ]; then
+ echo "directories in PGDATA with permission != 750";
exit 1;
fi
diff --git a/src/common/file_perm.c b/src/common/file_perm.c
index fdfbb9a44c..f4a093e8f2 100644
--- a/src/common/file_perm.c
+++ b/src/common/file_perm.c
@@ -12,8 +12,76 @@
*/
#include <sys/stat.h>
+#include "c.h"
#include "common/file_perm.h"
/* Modes for creating directories and files in the data directory */
int pg_dir_create_mode = PG_DIR_MODE_OWNER;
int pg_file_create_mode = PG_FILE_MODE_OWNER;
+
+/*
+ * Mode mask to pass to umask(). This is more of a preventative measure since
+ * all file/directory creates should be performed using the create modes above.
+ */
+int pg_mode_mask = PG_MODE_MASK_OWNER;
+
+/*
+ * Set create modes and mask to use when writing to PGDATA based on the data
+ * directory mode passed. If group read/execute are present in the
+ * mode, then create modes and mask will be relaxed to allow group read/execute
+ * on all newly created files and directories.
+ */
+void
+SetDataDirectoryCreatePerm(int dataDirMode)
+{
+ /* If the data directory mode has group access */
+ if ((PG_DIR_MODE_GROUP & dataDirMode) == PG_DIR_MODE_GROUP)
+ {
+ pg_dir_create_mode = PG_DIR_MODE_GROUP;
+ pg_file_create_mode = PG_FILE_MODE_GROUP;
+ pg_mode_mask = PG_MODE_MASK_GROUP;
+ }
+ /* Else use default permissions */
+ else
+ {
+ pg_dir_create_mode = PG_DIR_MODE_OWNER;
+ pg_file_create_mode = PG_FILE_MODE_OWNER;
+ pg_mode_mask = PG_MODE_MASK_OWNER;
+ }
+}
+
+#ifdef FRONTEND
+
+/*
+ * Get the create modes and mask to use when writing to PGDATA by examining the
+ * mode of the PGDATA directory and calling SetDataDirectoryCreatePerm().
+ *
+ * Errors are not handled here and should be reported by the frontend
+ * application when false is returned.
+ *
+ * Suppress when on Windows, because there may not be proper support for Unix-y
+ * file permissions.
+ */
+bool
+GetDataDirectoryCreatePerm(const char *dataDir)
+{
+#if !defined(WIN32) && !defined(__CYGWIN__)
+ struct stat statBuf;
+
+ /*
+ * If an error occurs getting the mode then false It is the reponsibility of
+ * the frontend application to generate an error if the PGDATA directory
+ * cannot be accessed.
+ */
+ if (stat(dataDir, &statBuf) == -1)
+ return false;
+
+ /* Set permissions */
+ SetDataDirectoryCreatePerm(statBuf.st_mode);
+
+ return true;
+#endif /* !defined(WIN32) && !defined(__CYGWIN__) */
+}
+
+
+#endif /* FRONTEND */
diff --git a/src/include/common/file_perm.h b/src/include/common/file_perm.h
index e34a128af9..d8735fa273 100644
--- a/src/include/common/file_perm.h
+++ b/src/include/common/file_perm.h
@@ -19,14 +19,34 @@
*/
#define PG_MODE_MASK_OWNER (S_IRWXG | S_IRWXO)
+/*
+ * Mode mask for data directory permissions that also allows group read/execute.
+ */
+#define PG_MODE_MASK_GROUP (S_IWGRP | S_IRWXO)
+
/* Default mode for creating directories */
#define PG_DIR_MODE_OWNER S_IRWXU
+/* Mode for creating directories that allows group read/execute */
+#define PG_DIR_MODE_GROUP (S_IRWXU | S_IRGRP | S_IXGRP)
+
/* Default mode for creating files */
#define PG_FILE_MODE_OWNER (S_IRUSR | S_IWUSR)
+/* Mode for creating files that allows group read */
+#define PG_FILE_MODE_GROUP (S_IRUSR | S_IWUSR | S_IRGRP)
+
/* Modes for creating directories and files in the data directory */
extern int pg_dir_create_mode;
extern int pg_file_create_mode;
+/* Mode mask to pass to umask() */
+extern int pg_mode_mask;
+
+/* Set permissions and mask based on the provided mode */
+extern void SetDataDirectoryCreatePerm(int dataDirMode);
+
+/* Set permissions and mask based on the mode of the data directory */
+extern bool GetDataDirectoryCreatePerm(const char *dataDir);
+
#endif /* FILE_PERM_H */
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index b5ad841968..e167ee8fcb 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -153,6 +153,7 @@ extern PGDLLIMPORT bool IsBinaryUpgrade;
extern PGDLLIMPORT bool ExitOnAnyError;
extern PGDLLIMPORT char *DataDir;
+extern PGDLLIMPORT int data_directory_mode;
extern PGDLLIMPORT int NBuffers;
extern PGDLLIMPORT int MaxBackends;
@@ -323,6 +324,7 @@ extern void SetSessionAuthorization(Oid userid, bool is_superuser);
extern Oid GetCurrentRoleId(void);
extern void SetCurrentRoleId(Oid roleid, bool is_superuser);
+extern void checkDataDir(void);
extern void SetDataDir(const char *dir);
extern void ChangeToDataDir(void);
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 76e571b98c..1bd91524d7 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -86,9 +86,11 @@ use Carp;
use Config;
use Cwd;
use Exporter 'import';
+use Fcntl qw(:mode);
use File::Basename;
use File::Path qw(rmtree);
use File::Spec;
+use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
use RecursiveCopy;
@@ -269,6 +271,26 @@ sub connstr
=pod
+=item $node->group_access()
+
+Does the data dir allow group access?
+
+=cut
+
+sub group_access
+{
+ my ($self) = @_;
+
+ my $dir_stat = stat($self->data_dir);
+
+ defined($dir_stat)
+ or die('unable to stat ' . $self->data_dir);
+
+ return (S_IMODE($dir_stat->mode) == 0750);
+}
+
+=pod
+
=item $node->data_dir()
Returns the path to the data directory. postgresql.conf and pg_hba.conf are
@@ -460,6 +482,9 @@ sub init
}
close $conf;
+ chmod($self->group_access ? 0640 : 0600, "$pgdata/postgresql.conf")
+ or die("unable to set permissions for $pgdata/postgresql.conf");
+
$self->set_replication_conf if $params{allows_streaming};
$self->enable_archiving if $params{has_archiving};
}
@@ -485,7 +510,7 @@ sub append_conf
TestLib::append_to_file($conffile, $str . "\n");
- chmod(0600, $conffile)
+ chmod($self->group_access() ? 0640 : 0600, $conffile)
or die("unable to set permissions for $conffile");
}
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index 93610e4bc4..8047404efd 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -31,6 +31,7 @@ our @EXPORT = qw(
slurp_file
append_to_file
check_mode_recursive
+ chmod_recursive
check_pg_config
system_or_bail
system_log
@@ -313,6 +314,30 @@ sub check_mode_recursive
return $result;
}
+# Change mode recursively on a directory
+sub chmod_recursive
+{
+ my ($dir, $dir_mode, $file_mode) = @_;
+
+ find
+ (
+ {follow_fast => 1,
+ wanted =>
+ sub
+ {
+ my $file_stat = stat($File::Find::name);
+
+ if (defined($file_stat))
+ {
+ chmod(S_ISDIR($file_stat->mode) ? $dir_mode : $file_mode,
+ $File::Find::name)
+ or die "unable to chmod $File::Find::name";
+ }
+ }},
+ $dir
+ );
+}
+
# Check presence of a given regexp within pg_config.h for the installation
# where tests are running, returning a match status result depending on
# that.
--
2.14.1
Michael,
* Michael Paquier (michael@paquier.xyz) wrote:
On Fri, Apr 06, 2018 at 09:15:15AM -0400, Stephen Frost wrote:
I'll reply to David's last email (where the latest set of patches were
included) with my comments/suggestions and I expect we'll be able to get
those addressed today and have a final patch to post tonight, with an
eye towards committing it tomorrow.The feature freeze is on the 8th, so I am going to have limited room to
comment on things until that day. If something gets committed, I am
pretty sure that I'll get out of my pocket a couple of things to improve
the feature and its interface anyway if of course you are ready to
accept that. I have limited my role to be a reviewer, so I refrained
myself from writing any code ;)
I'm, of course, happy to accept any further comments or suggestions on
it, pre-commit, post-commit, next year, whenever. ;)
I've posted an updated patch with comments and next steps, as discussed,
which I'm hopeful that David will have a chance to review and comment
on, at least, and perhaps help wrap up those last items. This patch has
been quite a slog, so thank you again for all of your help reviewing it
and moving it forward, it's been really appreciated.
Thanks!
Stephen
Hi Stephen,
On 4/6/18 3:02 PM, Stephen Frost wrote:
Alright, changes I've made, since I got impatient and it didn't seem to
make sense to bounce these back to David instead of just making them (I
did discuss them with him on the phone today tho, just to be clear).- The PG_FILE_MODE_DEFAULT, PG_DIR_MODE_DEFAULT, et al, were confusing.
Those constants are used for the default file/dir mode, sure, but what
that actually *are* is the 'owner'-only style mode, so they've been
changed to PG_FILE_MODE_OWNER, PG_DIR_MODE_OWNER, etc.
This is definitely better.
There were a few missed replacements in 01 so I fixed those.
Things that still need doing:
- Further discussion in the commit messages
Agreed, these need some more work. I'm happy to do that but I'll need a
bit more time. Have a look at the new patches and I'll work on some
better messages.
- Perhaps a bit more review to try to minimize the risk that something
breaks on Windows... Looked ok to me, but probably still some risk
that the Windows buildfarm members fall over, though I suppose that's
also what they're there for.
I did my best on this based on breakage from some of my other patches.
David, if you could look through this again and make sure I didn't break
anything with the changes made, and perhaps make improvements to the
docs/comments/commit messages, that'd be helpful for getting this over
the line.
I'm pretty happy with it overall. As you say, there could always be
more comments, but I'm not sure what to add without just running on.
New patches attached.
--
-David
david@pgmasters.net
Attachments:
group-access-v17-01-file-perm.patchtext/plain; charset=UTF-8; name=group-access-v17-01-file-perm.patch; x-mac-creator=0; x-mac-type=0Download
From 792a6809cd3b6e7c006af6221011bfbc2b376601 Mon Sep 17 00:00:00 2001
From: David Steele <david@pgmasters.net>
Date: Fri, 6 Apr 2018 09:29:15 -0400
Subject: [PATCH 1/2] Refactor file permissions in backend/frontend
Adds a new header (file_perm.h) and makes all front-end utilities use
the new constants for setting umask. Converts mkdir() calls in the
backend to MakePGDirectory() if the original call used default
permissions (always the case for regular PG directories).
Adds tests to make sure permissions in PGDATA are set correctly by the
front-end tools.
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
---
src/backend/access/transam/xlog.c | 2 +-
src/backend/commands/tablespace.c | 18 ++++---
src/backend/postmaster/postmaster.c | 7 +--
src/backend/postmaster/syslogger.c | 5 +-
src/backend/replication/basebackup.c | 5 +-
src/backend/replication/slot.c | 5 +-
src/backend/storage/file/copydir.c | 2 +-
src/backend/storage/file/fd.c | 38 +++++++++------
src/backend/storage/ipc/dsm_impl.c | 3 +-
src/backend/storage/ipc/ipc.c | 4 ++
src/backend/utils/init/miscinit.c | 5 +-
src/bin/initdb/initdb.c | 24 ++++-----
src/bin/initdb/t/001_initdb.pl | 11 ++++-
src/bin/pg_basebackup/pg_basebackup.c | 9 ++--
src/bin/pg_basebackup/t/010_pg_basebackup.pl | 14 +++++-
src/bin/pg_basebackup/t/020_pg_receivewal.pl | 14 +++++-
src/bin/pg_basebackup/walmethods.c | 6 ++-
src/bin/pg_ctl/pg_ctl.c | 4 +-
src/bin/pg_ctl/t/001_start_stop.pl | 18 ++++++-
src/bin/pg_resetwal/pg_resetwal.c | 5 +-
src/bin/pg_resetwal/t/001_basic.pl | 12 ++++-
src/bin/pg_rewind/RewindTest.pm | 4 ++
src/bin/pg_rewind/file_ops.c | 7 +--
src/bin/pg_rewind/t/001_basic.pl | 11 ++++-
src/bin/pg_upgrade/file.c | 5 +-
src/bin/pg_upgrade/pg_upgrade.c | 3 +-
src/bin/pg_upgrade/test.sh | 11 +++++
src/common/Makefile | 4 +-
src/common/file_perm.c | 19 ++++++++
src/include/common/file_perm.h | 32 ++++++++++++
src/include/storage/fd.h | 3 ++
src/test/perl/PostgresNode.pm | 3 ++
src/test/perl/TestLib.pm | 73 ++++++++++++++++++++++++++++
src/tools/msvc/Mkvcbuild.pm | 4 +-
34 files changed, 316 insertions(+), 74 deletions(-)
create mode 100644 src/common/file_perm.c
create mode 100644 src/include/common/file_perm.h
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 813b2afaac..4a47395174 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4107,7 +4107,7 @@ ValidateXLOGDirectoryStructure(void)
{
ereport(LOG,
(errmsg("creating missing WAL directory \"%s\"", path)));
- if (mkdir(path, S_IRWXU) < 0)
+ if (MakePGDirectory(path) < 0)
ereport(FATAL,
(errmsg("could not create missing directory \"%s\": %m",
path)));
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 5c450caa4e..cfc9fe468c 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -68,6 +68,7 @@
#include "commands/seclabel.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
+#include "common/file_perm.h"
#include "miscadmin.h"
#include "postmaster/bgwriter.h"
#include "storage/fd.h"
@@ -151,7 +152,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
else
{
/* Directory creation failed? */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakePGDirectory(dir) < 0)
{
char *parentdir;
@@ -173,7 +174,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
get_parent_directory(parentdir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakePGDirectory(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -184,7 +185,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
parentdir = pstrdup(dir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakePGDirectory(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -192,7 +193,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
pfree(parentdir);
/* Create database directory */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakePGDirectory(dir) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -279,7 +280,8 @@ CreateTableSpace(CreateTableSpaceStmt *stmt)
/*
* Check that location isn't too long. Remember that we're going to append
* 'PG_XXX/<dboid>/<relid>_<fork>.<nnn>'. FYI, we never actually
- * reference the whole path here, but mkdir() uses the first two parts.
+ * reference the whole path here, but MakePGDirectory() uses the first
+ * two parts.
*/
if (strlen(location) + 1 + strlen(TABLESPACE_VERSION_DIRECTORY) + 1 +
OIDCHARS + 1 + OIDCHARS + 1 + FORKNAMECHARS + 1 + OIDCHARS > MAXPGPATH)
@@ -574,7 +576,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* Attempt to coerce target directory to safe permissions. If this fails,
* it doesn't exist or has the wrong owner.
*/
- if (chmod(location, S_IRWXU) != 0)
+ if (chmod(location, pg_dir_create_mode) != 0)
{
if (errno == ENOENT)
ereport(ERROR,
@@ -599,7 +601,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
if (stat(location_with_version_dir, &st) == 0 && S_ISDIR(st.st_mode))
{
if (!rmtree(location_with_version_dir, true))
- /* If this failed, mkdir() below is going to error. */
+ /* If this failed, MakePGDirectory() below is going to error. */
ereport(WARNING,
(errmsg("some useless files may be left behind in old database directory \"%s\"",
location_with_version_dir)));
@@ -610,7 +612,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* The creation of the version directory prevents more than one tablespace
* in a single location.
*/
- if (mkdir(location_with_version_dir, S_IRWXU) < 0)
+ if (MakePGDirectory(location_with_version_dir) < 0)
{
if (errno == EEXIST)
ereport(ERROR,
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 3dfb87d701..10afecffb3 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -97,6 +97,7 @@
#include "access/xlog.h"
#include "bootstrap/bootstrap.h"
#include "catalog/pg_control.h"
+#include "common/file_perm.h"
#include "common/ip.h"
#include "lib/ilist.h"
#include "libpq/auth.h"
@@ -589,7 +590,7 @@ PostmasterMain(int argc, char *argv[])
/*
* for security, no dir or file created can be group or other accessible
*/
- umask(S_IRWXG | S_IRWXO);
+ umask(PG_MODE_MASK_OWNER);
/*
* Initialize random(3) so we don't get the same values in every run.
@@ -4490,9 +4491,9 @@ internal_forkexec(int argc, char *argv[], Port *port)
{
/*
* As in OpenTemporaryFileInTablespace, try to make the temp-file
- * directory
+ * directory, ignoring errors.
*/
- mkdir(PG_TEMP_FILES_DIR, S_IRWXU);
+ (void) MakePGDirectory(PG_TEMP_FILES_DIR);
fp = AllocateFile(tmpfilename, PG_BINARY_W);
if (!fp)
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index f70eea37df..58b759f305 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -41,6 +41,7 @@
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
#include "storage/dsm.h"
+#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pg_shmem.h"
@@ -322,7 +323,7 @@ SysLoggerMain(int argc, char *argv[])
/*
* Also, create new directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ (void) MakePGDirectory(Log_directory);
}
if (strcmp(Log_filename, currentLogFilename) != 0)
{
@@ -564,7 +565,7 @@ SysLogger_Start(void)
/*
* Create log directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ (void) MakePGDirectory(Log_directory);
/*
* The initial logfile is created right in the postmaster, to verify that
diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c
index 8ba29453b9..babf85a6ea 100644
--- a/src/backend/replication/basebackup.c
+++ b/src/backend/replication/basebackup.c
@@ -19,6 +19,7 @@
#include "access/xlog_internal.h" /* for pg_start/stop_backup */
#include "catalog/catalog.h"
#include "catalog/pg_type.h"
+#include "common/file_perm.h"
#include "lib/stringinfo.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
@@ -930,7 +931,7 @@ sendFileWithContent(const char *filename, const char *content)
statbuf.st_gid = getegid();
#endif
statbuf.st_mtime = time(NULL);
- statbuf.st_mode = S_IRUSR | S_IWUSR;
+ statbuf.st_mode = pg_file_create_mode;
statbuf.st_size = len;
_tarWriteHeader(filename, NULL, &statbuf, false);
@@ -1628,7 +1629,7 @@ _tarWriteDir(const char *pathbuf, int basepathlen, struct stat *statbuf,
#else
if (pgwin32_is_junction(pathbuf))
#endif
- statbuf->st_mode = S_IFDIR | S_IRWXU;
+ statbuf->st_mode = S_IFDIR | pg_dir_create_mode;
return _tarWriteHeader(pathbuf + basepathlen + 1, NULL, statbuf, sizeonly);
}
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index fc9ef22b0b..056628fe8e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -1166,13 +1166,14 @@ CreateSlotOnDisk(ReplicationSlot *slot)
* It's just barely possible that some previous effort to create or drop a
* slot with this name left a temp directory lying around. If that seems
* to be the case, try to remove it. If the rmtree() fails, we'll error
- * out at the mkdir() below, so we don't bother checking success.
+ * out at the MakePGDirectory() below, so we don't bother checking
+ * success.
*/
if (stat(tmppath, &st) == 0 && S_ISDIR(st.st_mode))
rmtree(tmppath, true);
/* Create and fsync the temporary slot directory. */
- if (mkdir(tmppath, S_IRWXU) < 0)
+ if (MakePGDirectory(tmppath) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
diff --git a/src/backend/storage/file/copydir.c b/src/backend/storage/file/copydir.c
index ca6342db0d..4a0d23b11e 100644
--- a/src/backend/storage/file/copydir.c
+++ b/src/backend/storage/file/copydir.c
@@ -41,7 +41,7 @@ copydir(char *fromdir, char *todir, bool recurse)
char fromfile[MAXPGPATH * 2];
char tofile[MAXPGPATH * 2];
- if (mkdir(todir, S_IRWXU) != 0)
+ if (MakePGDirectory(todir) != 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m", todir)));
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index d30a725f90..3231ffe2b9 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -84,6 +84,7 @@
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "catalog/pg_tablespace.h"
+#include "common/file_perm.h"
#include "pgstat.h"
#include "portability/mem.h"
#include "storage/fd.h"
@@ -124,12 +125,6 @@
*/
#define FD_MINFREE 10
-/*
- * Default mode for created files, unless something else is specified using
- * the *Perm() function variants.
- */
-#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR)
-
/*
* A number of platforms allow individual processes to open many more files
* than they can really support when *many* processes do the same thing.
@@ -937,7 +932,7 @@ set_max_safe_fds(void)
int
BasicOpenFile(const char *fileName, int fileFlags)
{
- return BasicOpenFilePerm(fileName, fileFlags, PG_FILE_MODE_DEFAULT);
+ return BasicOpenFilePerm(fileName, fileFlags, pg_file_create_mode);
}
/*
@@ -1356,7 +1351,7 @@ FileInvalidate(File file)
File
PathNameOpenFile(const char *fileName, int fileFlags)
{
- return PathNameOpenFilePerm(fileName, fileFlags, PG_FILE_MODE_DEFAULT);
+ return PathNameOpenFilePerm(fileName, fileFlags, pg_file_create_mode);
}
/*
@@ -1434,7 +1429,7 @@ PathNameOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode)
void
PathNameCreateTemporaryDir(const char *basedir, const char *directory)
{
- if (mkdir(directory, S_IRWXU) < 0)
+ if (MakePGDirectory(directory) < 0)
{
if (errno == EEXIST)
return;
@@ -1444,14 +1439,14 @@ PathNameCreateTemporaryDir(const char *basedir, const char *directory)
* EEXIST to close a race against another process following the same
* algorithm.
*/
- if (mkdir(basedir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakePGDirectory(basedir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary directory \"%s\": %m",
basedir)));
/* Try again. */
- if (mkdir(directory, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakePGDirectory(directory) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary subdirectory \"%s\": %m",
@@ -1601,11 +1596,11 @@ OpenTemporaryFileInTablespace(Oid tblspcOid, bool rejectError)
* We might need to create the tablespace's tempfile directory, if no
* one has yet done so.
*
- * Don't check for error from mkdir; it could fail if someone else
- * just did the same thing. If it doesn't work then we'll bomb out on
+ * Don't check error from MakePGDirectory; it could fail if someone
+ * else just did the same thing. If it doesn't work then we'll bomb out on
* the second create attempt, instead.
*/
- mkdir(tempdirpath, S_IRWXU);
+ (void) MakePGDirectory(tempdirpath);
file = PathNameOpenFile(tempfilepath,
O_RDWR | O_CREAT | O_TRUNC | PG_BINARY);
@@ -2401,7 +2396,7 @@ TryAgain:
int
OpenTransientFile(const char *fileName, int fileFlags)
{
- return OpenTransientFilePerm(fileName, fileFlags, PG_FILE_MODE_DEFAULT);
+ return OpenTransientFilePerm(fileName, fileFlags, pg_file_create_mode);
}
/*
@@ -3554,3 +3549,16 @@ fsync_parent_path(const char *fname, int elevel)
return 0;
}
+
+/*
+ * Create a directory using pg_dir_create_mode for permissions
+ *
+ * Directories in PGDATA should normally have specific permissions -- when
+ * using this function the caller does not need to know what they should be.
+ * For permissions other than the default use mkdir() directly.
+ */
+int
+MakePGDirectory(const char *directoryName)
+{
+ return mkdir(directoryName, pg_dir_create_mode);
+}
diff --git a/src/backend/storage/ipc/dsm_impl.c b/src/backend/storage/ipc/dsm_impl.c
index 67e76b98fe..2fca9fae51 100644
--- a/src/backend/storage/ipc/dsm_impl.c
+++ b/src/backend/storage/ipc/dsm_impl.c
@@ -60,6 +60,7 @@
#ifdef HAVE_SYS_SHM_H
#include <sys/shm.h>
#endif
+#include "common/file_perm.h"
#include "pgstat.h"
#include "portability/mem.h"
@@ -285,7 +286,7 @@ dsm_impl_posix(dsm_op op, dsm_handle handle, Size request_size,
* returning.
*/
flags = O_RDWR | (op == DSM_OP_CREATE ? O_CREAT | O_EXCL : 0);
- if ((fd = shm_open(name, flags, 0600)) == -1)
+ if ((fd = shm_open(name, flags, PG_FILE_MODE_OWNER)) == -1)
{
if (errno != EEXIST)
ereport(elevel,
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index fc0a9c0756..6cf69fe55b 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -137,6 +137,10 @@ proc_exit(int code)
else
snprintf(gprofDirName, 32, "gprof/%d", (int) getpid());
+ /*
+ * Use mkdir() instead of MakePGDirectory() since we aren't making a PG
+ * directory here.
+ */
mkdir("gprof", S_IRWXU | S_IRWXG | S_IRWXO);
mkdir(gprofDirName, S_IRWXU | S_IRWXG | S_IRWXO);
chdir(gprofDirName);
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 87ed7d3f71..f8f08f3f88 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -32,6 +32,7 @@
#include "access/htup_details.h"
#include "catalog/pg_authid.h"
+#include "common/file_perm.h"
#include "libpq/libpq.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
@@ -831,7 +832,7 @@ CreateLockFile(const char *filename, bool amPostmaster,
* Think not to make the file protection weaker than 0600. See
* comments below.
*/
- fd = open(filename, O_RDWR | O_CREAT | O_EXCL, 0600);
+ fd = open(filename, O_RDWR | O_CREAT | O_EXCL, pg_file_create_mode);
if (fd >= 0)
break; /* Success; exit the retry loop */
@@ -848,7 +849,7 @@ CreateLockFile(const char *filename, bool amPostmaster,
* Read the file to get the old owner's PID. Note race condition
* here: file might have been deleted since we tried to create it.
*/
- fd = open(filename, O_RDONLY, 0600);
+ fd = open(filename, O_RDONLY, pg_file_create_mode);
if (fd < 0)
{
if (errno == ENOENT)
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 78990f5a27..3765548a24 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -64,6 +64,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_class.h"
#include "catalog/pg_collation.h"
+#include "common/file_perm.h"
#include "common/file_utils.h"
#include "common/restricted_token.h"
#include "common/username.h"
@@ -1170,7 +1171,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, pg_file_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1190,7 +1191,7 @@ setup_config(void)
sprintf(path, "%s/postgresql.auto.conf", pg_data);
writefile(path, autoconflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, pg_file_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1277,7 +1278,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_hba.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, pg_file_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1293,7 +1294,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_ident.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, pg_file_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -2692,7 +2693,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (pg_mkdir_p(pg_data, S_IRWXU) != 0)
+ if (pg_mkdir_p(pg_data, pg_dir_create_mode) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2710,7 +2711,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (chmod(pg_data, S_IRWXU) != 0)
+ if (chmod(pg_data, pg_dir_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2778,7 +2779,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (pg_mkdir_p(xlog_dir, S_IRWXU) != 0)
+ if (pg_mkdir_p(xlog_dir, pg_dir_create_mode) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2796,7 +2797,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (chmod(xlog_dir, S_IRWXU) != 0)
+ if (chmod(xlog_dir, pg_dir_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2846,7 +2847,7 @@ create_xlog_or_symlink(void)
else
{
/* Without -X option, just make the subdirectory normally */
- if (mkdir(subdirloc, S_IRWXU) < 0)
+ if (mkdir(subdirloc, pg_dir_create_mode) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, subdirloc, strerror(errno));
@@ -2882,7 +2883,8 @@ initialize_data_directory(void)
setup_signals();
- umask(S_IRWXG | S_IRWXO);
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_OWNER);
create_data_directory();
@@ -2902,7 +2904,7 @@ initialize_data_directory(void)
* The parent directory already exists, so we only need mkdir() not
* pg_mkdir_p() here, which avoids some failure modes; cf bug #13853.
*/
- if (mkdir(path, S_IRWXU) < 0)
+ if (mkdir(path, pg_dir_create_mode) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, path, strerror(errno));
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index c0cfa6e92c..9dfb2ed96f 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 15;
+use Test::More tests => 16;
my $tempdir = TestLib::tempdir;
my $xlogdir = "$tempdir/pgxlog";
@@ -45,6 +45,15 @@ mkdir $datadir;
command_ok([ 'initdb', '-N', '-T', 'german', '-X', $xlogdir, $datadir ],
'successful creation');
+
+ # Permissions on PGDATA should be default
+ SKIP:
+ {
+ skip "unix-style permissions not supported on Windows", 1 if ($windows_os);
+
+ ok(check_mode_recursive($datadir, 0700, 0600),
+ "check PGDATA permissions");
+ }
}
command_ok([ 'initdb', '-S', $datadir ], 'sync only');
command_fails([ 'initdb', $datadir ], 'existing data directory');
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 8610fe8a1a..32b41e313c 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -27,6 +27,7 @@
#endif
#include "access/xlog_internal.h"
+#include "common/file_perm.h"
#include "common/file_utils.h"
#include "common/string.h"
#include "fe_utils/string_utils.h"
@@ -629,7 +630,7 @@ StartLogStreamer(char *startpos, uint32 timeline, char *sysidentifier)
PQserverVersion(conn) < MINIMUM_VERSION_FOR_PG_WAL ?
"pg_xlog" : "pg_wal");
- if (pg_mkdir_p(statusdir, S_IRWXU) != 0 && errno != EEXIST)
+ if (pg_mkdir_p(statusdir, pg_dir_create_mode) != 0 && errno != EEXIST)
{
fprintf(stderr,
_("%s: could not create directory \"%s\": %s\n"),
@@ -685,7 +686,7 @@ verify_dir_is_empty_or_create(char *dirname, bool *created, bool *found)
/*
* Does not exist, so create
*/
- if (pg_mkdir_p(dirname, S_IRWXU) == -1)
+ if (pg_mkdir_p(dirname, pg_dir_create_mode) == -1)
{
fprintf(stderr,
_("%s: could not create directory \"%s\": %s\n"),
@@ -1129,7 +1130,7 @@ ReceiveTarFile(PGconn *conn, PGresult *res, int rownum)
tarCreateHeader(header, "recovery.conf", NULL,
recoveryconfcontents->len,
- 0600, 04000, 02000,
+ pg_file_create_mode, 04000, 02000,
time(NULL));
padding = ((recoveryconfcontents->len + 511) & ~511) - recoveryconfcontents->len;
@@ -1441,7 +1442,7 @@ ReceiveAndUnpackTarFile(PGconn *conn, PGresult *res, int rownum)
* Directory
*/
filename[strlen(filename) - 1] = '\0'; /* Remove trailing slash */
- if (mkdir(filename, S_IRWXU) != 0)
+ if (mkdir(filename, pg_dir_create_mode) != 0)
{
/*
* When streaming WAL, pg_wal (or pg_xlog for pre-9.6
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index e2f1465472..ec54b555ed 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -5,7 +5,7 @@ use Config;
use File::Basename qw(basename dirname);
use PostgresNode;
use TestLib;
-use Test::More tests => 104;
+use Test::More tests => 105;
program_help_ok('pg_basebackup');
program_version_ok('pg_basebackup');
@@ -15,6 +15,9 @@ my $tempdir = TestLib::tempdir;
my $node = get_new_node('main');
+# Set umask so test directories and files are created with default permissions
+umask(0077);
+
# Initialize node without replication settings
$node->init(extra => [ '--data-checksums' ]);
$node->start;
@@ -93,6 +96,15 @@ $node->command_ok([ 'pg_basebackup', '-D', "$tempdir/backup", '-X', 'none' ],
'pg_basebackup runs');
ok(-f "$tempdir/backup/PG_VERSION", 'backup was created');
+# Permissions on backup should be default
+SKIP:
+{
+ skip "unix-style permissions not supported on Windows", 1 if ($windows_os);
+
+ ok(check_mode_recursive("$tempdir/backup", 0700, 0600),
+ "check backup dir permissions");
+}
+
# Only archive_status directory should be copied in pg_wal/.
is_deeply(
[ sort(slurp_dir("$tempdir/backup/pg_wal/")) ],
diff --git a/src/bin/pg_basebackup/t/020_pg_receivewal.pl b/src/bin/pg_basebackup/t/020_pg_receivewal.pl
index 64e3a35a87..19c106d9f5 100644
--- a/src/bin/pg_basebackup/t/020_pg_receivewal.pl
+++ b/src/bin/pg_basebackup/t/020_pg_receivewal.pl
@@ -2,12 +2,15 @@ use strict;
use warnings;
use TestLib;
use PostgresNode;
-use Test::More tests => 18;
+use Test::More tests => 19;
program_help_ok('pg_receivewal');
program_version_ok('pg_receivewal');
program_options_handling_ok('pg_receivewal');
+# Set umask so test directories and files are created with default permissions
+umask(0077);
+
my $primary = get_new_node('primary');
$primary->init(allows_streaming => 1);
$primary->start;
@@ -56,3 +59,12 @@ $primary->command_ok(
[ 'pg_receivewal', '-D', $stream_dir, '--verbose',
'--endpos', $nextlsn, '--synchronous', '--no-loop' ],
'streaming some WAL with --synchronous');
+
+# Permissions on WAL files should be default
+SKIP:
+{
+ skip "unix-style permissions not supported on Windows", 1 if ($windows_os);
+
+ ok(check_mode_recursive($stream_dir, 0700, 0600),
+ "check stream dir permissions");
+}
diff --git a/src/bin/pg_basebackup/walmethods.c b/src/bin/pg_basebackup/walmethods.c
index b4558a0184..267a40debb 100644
--- a/src/bin/pg_basebackup/walmethods.c
+++ b/src/bin/pg_basebackup/walmethods.c
@@ -22,6 +22,7 @@
#endif
#include "pgtar.h"
+#include "common/file_perm.h"
#include "common/file_utils.h"
#include "receivelog.h"
@@ -89,7 +90,7 @@ dir_open_for_write(const char *pathname, const char *temp_suffix, size_t pad_to_
* does not do any system calls to fsync() to make changes permanent on
* disk.
*/
- fd = open(tmppath, O_WRONLY | O_CREAT | PG_BINARY, S_IRUSR | S_IWUSR);
+ fd = open(tmppath, O_WRONLY | O_CREAT | PG_BINARY, pg_file_create_mode);
if (fd < 0)
return NULL;
@@ -534,7 +535,8 @@ tar_open_for_write(const char *pathname, const char *temp_suffix, size_t pad_to_
* We open the tar file only when we first try to write to it.
*/
tar_data->fd = open(tar_data->tarfilename,
- O_WRONLY | O_CREAT | PG_BINARY, S_IRUSR | S_IWUSR);
+ O_WRONLY | O_CREAT | PG_BINARY,
+ pg_file_create_mode);
if (tar_data->fd < 0)
return NULL;
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index 9bc830b085..5ede385e6a 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -25,6 +25,7 @@
#include "catalog/pg_control.h"
#include "common/controldata_utils.h"
+#include "common/file_perm.h"
#include "getopt_long.h"
#include "utils/pidfile.h"
@@ -2170,7 +2171,8 @@ main(int argc, char **argv)
*/
argv0 = argv[0];
- umask(S_IRWXG | S_IRWXO);
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_OWNER);
/* support --help and --version even if invoked as root */
if (argc > 1)
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl
index 5da4746cb4..067a084c87 100644
--- a/src/bin/pg_ctl/t/001_start_stop.pl
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -4,7 +4,7 @@ use warnings;
use Config;
use PostgresNode;
use TestLib;
-use Test::More tests => 19;
+use Test::More tests => 21;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -57,9 +57,23 @@ command_ok([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ], 'pg_ctl stop');
command_fails([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ],
'second pg_ctl stop fails');
+# Log file for default permission test. The permissions won't be checked on
+# Windows but we still want to do the restart test.
+my $logFileName = "$tempdir/data/perm-test-600.log";
+
command_ok(
- [ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
+ [ 'pg_ctl', 'restart', '-D', "$tempdir/data", '-l', $logFileName ],
'pg_ctl restart with server not running');
+
+# Permissions on log file should be default
+SKIP:
+{
+ skip "unix-style permissions not supported on Windows", 2 if ($windows_os);
+
+ ok(-f $logFileName);
+ ok(check_mode_recursive("$tempdir/data", 0700, 0600));
+}
+
command_ok([ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
'pg_ctl restart with server running');
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index eba7c5fdee..bdf71886ee 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -52,6 +52,7 @@
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
#include "common/fe_memutils.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "storage/large_object.h"
#include "pg_getopt.h"
@@ -967,7 +968,7 @@ RewriteControlFile(void)
fd = open(XLOG_CONTROL_FILE,
O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ pg_file_create_mode);
if (fd < 0)
{
fprintf(stderr, _("%s: could not create pg_control file: %s\n"),
@@ -1249,7 +1250,7 @@ WriteEmptyXLOG(void)
unlink(path);
fd = open(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ pg_file_create_mode);
if (fd < 0)
{
fprintf(stderr, _("%s: could not open file \"%s\": %s\n"),
diff --git a/src/bin/pg_resetwal/t/001_basic.pl b/src/bin/pg_resetwal/t/001_basic.pl
index 1b157cb555..0d6ab20073 100644
--- a/src/bin/pg_resetwal/t/001_basic.pl
+++ b/src/bin/pg_resetwal/t/001_basic.pl
@@ -3,7 +3,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 11;
+use Test::More tests => 12;
program_help_ok('pg_resetwal');
program_version_ok('pg_resetwal');
@@ -15,3 +15,13 @@ $node->init;
command_like([ 'pg_resetwal', '-n', $node->data_dir ],
qr/checkpoint/,
'pg_resetwal -n produces output');
+
+
+# Permissions on PGDATA should be default
+SKIP:
+{
+ skip "unix-style permissions not supported on Windows", 1 if ($windows_os);
+
+ ok(check_mode_recursive($node->data_dir, 0700, 0600),
+ 'check PGDATA permissions');
+}
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index 00b5b42dd7..7b632c7dcd 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -237,6 +237,10 @@ sub run_pg_rewind
"$tmp_folder/master-postgresql.conf.tmp",
"$master_pgdata/postgresql.conf");
+ chmod(0600, "$master_pgdata/postgresql.conf")
+ or BAIL_OUT(
+ "unable to set permissions for $master_pgdata/postgresql.conf");
+
# Plug-in rewound node to the now-promoted standby node
my $port_standby = $node_standby->port;
$node_master->append_conf(
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index f491ed7f5c..94bcc13ae8 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -18,6 +18,7 @@
#include <fcntl.h>
#include <unistd.h>
+#include "common/file_perm.h"
#include "file_ops.h"
#include "filemap.h"
#include "logging.h"
@@ -57,7 +58,7 @@ open_target_file(const char *path, bool trunc)
mode = O_WRONLY | O_CREAT | PG_BINARY;
if (trunc)
mode |= O_TRUNC;
- dstfd = open(dstpath, mode, 0600);
+ dstfd = open(dstpath, mode, pg_file_create_mode);
if (dstfd < 0)
pg_fatal("could not open target file \"%s\": %s\n",
dstpath, strerror(errno));
@@ -198,7 +199,7 @@ truncate_target_file(const char *path, off_t newsize)
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
- fd = open(dstpath, O_WRONLY, 0);
+ fd = open(dstpath, O_WRONLY, pg_file_create_mode);
if (fd < 0)
pg_fatal("could not open file \"%s\" for truncation: %s\n",
dstpath, strerror(errno));
@@ -219,7 +220,7 @@ create_target_dir(const char *path)
return;
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
- if (mkdir(dstpath, S_IRWXU) != 0)
+ if (mkdir(dstpath, pg_dir_create_mode) != 0)
pg_fatal("could not create directory \"%s\": %s\n",
dstpath, strerror(errno));
}
diff --git a/src/bin/pg_rewind/t/001_basic.pl b/src/bin/pg_rewind/t/001_basic.pl
index 736f34eae3..1b0f823b0c 100644
--- a/src/bin/pg_rewind/t/001_basic.pl
+++ b/src/bin/pg_rewind/t/001_basic.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 8;
+use Test::More tests => 10;
use RewindTest;
@@ -86,6 +86,15 @@ in master, before promotion
),
'tail-copy');
+ # Permissions on PGDATA should be default
+ SKIP:
+ {
+ skip "unix-style permissions not supported on Windows", 1 if ($windows_os);
+
+ ok(check_mode_recursive($node_master->data_dir(), 0700, 0600),
+ 'check PGDATA permissions');
+ }
+
RewindTest::clean_rewind_test();
}
diff --git a/src/bin/pg_upgrade/file.c b/src/bin/pg_upgrade/file.c
index f38bfacf02..f68211aa20 100644
--- a/src/bin/pg_upgrade/file.c
+++ b/src/bin/pg_upgrade/file.c
@@ -10,6 +10,7 @@
#include "postgres_fe.h"
#include "access/visibilitymap.h"
+#include "common/file_perm.h"
#include "pg_upgrade.h"
#include "storage/bufpage.h"
#include "storage/checksum.h"
@@ -44,7 +45,7 @@ copyFile(const char *src, const char *dst,
schemaName, relName, src, strerror(errno));
if ((dest_fd = open(dst, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR)) < 0)
+ pg_file_create_mode)) < 0)
pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
schemaName, relName, dst, strerror(errno));
@@ -151,7 +152,7 @@ rewriteVisibilityMap(const char *fromfile, const char *tofile,
schemaName, relName, fromfile, strerror(errno));
if ((dst_fd = open(tofile, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR)) < 0)
+ pg_file_create_mode)) < 0)
pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
schemaName, relName, tofile, strerror(errno));
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index d12412799f..1d35188143 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -38,6 +38,7 @@
#include "pg_upgrade.h"
#include "catalog/pg_class.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "fe_utils/string_utils.h"
@@ -79,7 +80,7 @@ main(int argc, char **argv)
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_upgrade"));
/* Ensure that all files created by pg_upgrade are non-world-readable */
- umask(S_IRWXG | S_IRWXO);
+ umask(PG_MODE_MASK_OWNER);
parseCommandLine(argc, argv);
diff --git a/src/bin/pg_upgrade/test.sh b/src/bin/pg_upgrade/test.sh
index 39983abea1..574639d47e 100644
--- a/src/bin/pg_upgrade/test.sh
+++ b/src/bin/pg_upgrade/test.sh
@@ -230,6 +230,17 @@ standard_initdb 'initdb'
pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "${PGDATA}" -b "$oldbindir" -B "$bindir" -p "$PGPORT" -P "$PGPORT"
+# make sure all directories and files have correct permissions
+if [ $(find ${PGDATA} -type f ! -perm 600 | wc -l) -ne 0 ]; then
+ echo "files in PGDATA with permission != 600";
+ exit 1;
+fi
+
+if [ $(find ${PGDATA} -type d ! -perm 700 | wc -l) -ne 0 ]; then
+ echo "directories in PGDATA with permission != 700";
+ exit 1;
+fi
+
pg_ctl start -l "$logdir/postmaster2.log" -o "$POSTMASTER_OPTS" -w
case $testhost in
diff --git a/src/common/Makefile b/src/common/Makefile
index 873fbb6438..e9e75867f3 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -40,8 +40,8 @@ override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
override CPPFLAGS := -DFRONTEND $(CPPFLAGS)
LIBS += $(PTHREAD_LIBS)
-OBJS_COMMON = base64.o config_info.o controldata_utils.o exec.o ip.o \
- keywords.o md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
+OBJS_COMMON = base64.o config_info.o controldata_utils.o exec.o file_perm.o \
+ ip.o keywords.o md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
rmtree.o saslprep.o scram-common.o string.o unicode_norm.o \
username.o wait_error.o
diff --git a/src/common/file_perm.c b/src/common/file_perm.c
new file mode 100644
index 0000000000..fdfbb9a44c
--- /dev/null
+++ b/src/common/file_perm.c
@@ -0,0 +1,19 @@
+/*-------------------------------------------------------------------------
+ *
+ * File and directory permission routines
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/file_perm.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include <sys/stat.h>
+
+#include "common/file_perm.h"
+
+/* Modes for creating directories and files in the data directory */
+int pg_dir_create_mode = PG_DIR_MODE_OWNER;
+int pg_file_create_mode = PG_FILE_MODE_OWNER;
diff --git a/src/include/common/file_perm.h b/src/include/common/file_perm.h
new file mode 100644
index 0000000000..700f562582
--- /dev/null
+++ b/src/include/common/file_perm.h
@@ -0,0 +1,32 @@
+/*-------------------------------------------------------------------------
+ *
+ * File and directory permission constants
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/file_perm.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FILE_PERM_H
+#define FILE_PERM_H
+
+/*
+ * Default mode mask for data directory permissions only allows the owner to
+ * read/write directories and files.
+ */
+#define PG_MODE_MASK_OWNER (S_IRWXG | S_IRWXO)
+
+/* Default mode for creating directories */
+#define PG_DIR_MODE_OWNER S_IRWXU
+
+/* Default mode for creating files */
+#define PG_FILE_MODE_OWNER (S_IRUSR | S_IWUSR)
+
+/* Modes for creating directories and files in the data directory */
+extern int pg_dir_create_mode;
+extern int pg_file_create_mode;
+
+#endif /* FILE_PERM_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index e49b42ce86..785d9058e9 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -112,6 +112,9 @@ extern int CloseTransientFile(int fd);
extern int BasicOpenFile(const char *fileName, int fileFlags);
extern int BasicOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode);
+ /* Make a directory with default permissions */
+extern int MakePGDirectory(const char *directoryName);
+
/* Miscellaneous support routines */
extern void InitFileAccess(void);
extern void set_max_safe_fds(void);
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 80188315f1..76e571b98c 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -484,6 +484,9 @@ sub append_conf
my $conffile = $self->data_dir . '/' . $filename;
TestLib::append_to_file($conffile, $str . "\n");
+
+ chmod(0600, $conffile)
+ or die("unable to set permissions for $conffile");
}
=pod
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index b6862688d4..93610e4bc4 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -13,8 +13,11 @@ use warnings;
use Config;
use Cwd;
use Exporter 'import';
+use Fcntl qw(:mode);
use File::Basename;
+use File::Find;
use File::Spec;
+use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
use SimpleTee;
@@ -27,6 +30,7 @@ our @EXPORT = qw(
slurp_dir
slurp_file
append_to_file
+ check_mode_recursive
check_pg_config
system_or_bail
system_log
@@ -240,6 +244,75 @@ sub append_to_file
close $fh;
}
+# Check that all file/dir modes in a directory match the expected values,
+# ignoring the mode of any specified files.
+sub check_mode_recursive
+{
+ my ($dir, $expected_dir_mode, $expected_file_mode, $ignore_list) = @_;
+
+ # Result defaults to true
+ my $result = 1;
+
+ find
+ (
+ {follow_fast => 1,
+ wanted =>
+ sub
+ {
+ my $file_stat = stat($File::Find::name);
+
+ # Is file in the ignore list?
+ foreach my $ignore ($ignore_list ? @{$ignore_list} : [])
+ {
+ if ("$dir/$ignore" eq $File::Find::name)
+ {
+ return;
+ }
+ }
+
+ defined($file_stat)
+ or die("unable to stat $File::Find::name");
+
+ my $file_mode = S_IMODE($file_stat->mode);
+
+ # Is this a file?
+ if (S_ISREG($file_stat->mode))
+ {
+ if ($file_mode != $expected_file_mode)
+ {
+ print(*STDERR,
+ sprintf("$File::Find::name mode must be %04o\n",
+ $expected_file_mode));
+
+ $result = 0;
+ return;
+ }
+ }
+ # Else a directory?
+ elsif (S_ISDIR($file_stat->mode))
+ {
+ if ($file_mode != $expected_dir_mode)
+ {
+ print(*STDERR,
+ sprintf("$File::Find::name mode must be %04o\n",
+ $expected_dir_mode));
+
+ $result = 0;
+ return;
+ }
+ }
+ # Else something we can't handle
+ else
+ {
+ die "unknown file type for $File::Find::name";
+ }
+ }},
+ $dir
+ );
+
+ return $result;
+}
+
# Check presence of a given regexp within pg_config.h for the installation
# where tests are running, returning a match status result depending on
# that.
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 41d720880a..1d3ed6b0b1 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -111,8 +111,8 @@ sub mkvcbuild
}
our @pgcommonallfiles = qw(
- base64.c config_info.c controldata_utils.c exec.c ip.c keywords.c
- md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
+ base64.c config_info.c controldata_utils.c exec.c file_perm.c ip.c
+ keywords.c md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
saslprep.c scram-common.c string.c unicode_norm.c username.c
wait_error.c);
--
2.14.3 (Apple Git-98)
group-access-v17-02-group.patchtext/plain; charset=UTF-8; name=group-access-v17-02-group.patch; x-mac-creator=0; x-mac-type=0Download
From c1dd800a4b7ac2d8812c29d11c649da6bfb70a63 Mon Sep 17 00:00:00 2001
From: David Steele <david@pgmasters.net>
Date: Fri, 6 Apr 2018 17:25:04 -0400
Subject: [PATCH 2/2] Allow group access on PGDATA.
This includes backend changes, utility changes (initdb, pg_ctl,
pg_upgrade, etc.) and tests for each utility.
Author: David Steele <david@pgmasters.net>
Reviewed-By: Michael Paquier, with discussion amongst many others.
Discussion: https://postgr.es/m/https://www.postgresql.org/message-id/ad346fe6-b23e-59f1-ecb7-0e08390ad629%40pgmasters.net
---
doc/src/sgml/ref/initdb.sgml | 19 +++++
doc/src/sgml/ref/pg_basebackup.sgml | 6 ++
doc/src/sgml/ref/pg_receivewal.sgml | 6 ++
doc/src/sgml/ref/pg_recvlogical.sgml | 11 +++
doc/src/sgml/runtime.sgml | 14 +++-
src/backend/bootstrap/bootstrap.c | 12 +--
src/backend/postmaster/postmaster.c | 84 +++----------------
src/backend/tcop/postgres.c | 3 +-
src/backend/utils/init/globals.c | 9 +++
src/backend/utils/init/miscinit.c | 115 ++++++++++++++++++++++++---
src/backend/utils/misc/guc.c | 25 ++++++
src/bin/initdb/initdb.c | 24 +++++-
src/bin/initdb/t/001_initdb.pl | 20 ++++-
src/bin/pg_basebackup/pg_basebackup.c | 22 +++--
src/bin/pg_basebackup/pg_receivewal.c | 7 ++
src/bin/pg_basebackup/pg_recvlogical.c | 7 ++
src/bin/pg_basebackup/streamutil.c | 69 ++++++++++++++++
src/bin/pg_basebackup/t/010_pg_basebackup.pl | 26 ++++--
src/bin/pg_ctl/pg_ctl.c | 12 ++-
src/bin/pg_ctl/t/001_start_stop.pl | 25 +++++-
src/bin/pg_resetwal/pg_resetwal.c | 10 +++
src/bin/pg_rewind/RewindTest.pm | 9 ++-
src/bin/pg_rewind/pg_rewind.c | 11 +++
src/bin/pg_rewind/t/002_databases.pl | 13 ++-
src/bin/pg_upgrade/pg_upgrade.c | 12 ++-
src/bin/pg_upgrade/test.sh | 16 ++--
src/common/file_perm.c | 68 ++++++++++++++++
src/include/common/file_perm.h | 20 +++++
src/include/miscadmin.h | 2 +
src/test/perl/PostgresNode.pm | 27 ++++++-
src/test/perl/TestLib.pm | 25 ++++++
31 files changed, 601 insertions(+), 128 deletions(-)
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 826dd91f72..f32c600834 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -76,6 +76,14 @@ PostgreSQL documentation
to do so.)
</para>
+ <para>
+ For security reasons the new cluster created by <command>initdb</command>
+ will only be accessible by the cluster owner by default. The
+ <option>--allow-group-access</option> option allows any user in the same
+ group as the cluster owner to read files in the cluster. This is useful
+ for performing backups as a non-privileged user.
+ </para>
+
<para>
<command>initdb</command> initializes the database cluster's default
locale and character set encoding. The character set encoding,
@@ -301,6 +309,17 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-g</option></term>
+ <term><option>--allow-group-access</option></term>
+ <listitem>
+ <para>
+ Allows users in the same group as the cluster owner to read all cluster
+ files created by <command>initdb</command>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-W</option></term>
<term><option>--pwprompt</option></term>
diff --git a/doc/src/sgml/ref/pg_basebackup.sgml b/doc/src/sgml/ref/pg_basebackup.sgml
index 95045669c9..fc1edf4864 100644
--- a/doc/src/sgml/ref/pg_basebackup.sgml
+++ b/doc/src/sgml/ref/pg_basebackup.sgml
@@ -737,6 +737,12 @@ PostgreSQL documentation
or later.
</para>
+ <para>
+ <application>pg_basebackup</application> will preserve group permissions in
+ both the <literal>plain</literal> and <literal>tar</literal> formats if group
+ permissions are enabled on the source cluster.
+ </para>
+
</refsect1>
<refsect1>
diff --git a/doc/src/sgml/ref/pg_receivewal.sgml b/doc/src/sgml/ref/pg_receivewal.sgml
index e3f2ce1fcb..a18ddd4bff 100644
--- a/doc/src/sgml/ref/pg_receivewal.sgml
+++ b/doc/src/sgml/ref/pg_receivewal.sgml
@@ -425,6 +425,12 @@ PostgreSQL documentation
not keep up with fetching the WAL data.
</para>
+ <para>
+ <application>pg_receivewal</application> will preserve group permissions on
+ the received WAL files if group permissions are enabled on the source
+ cluster.
+ </para>
+
</refsect1>
<refsect1>
diff --git a/doc/src/sgml/ref/pg_recvlogical.sgml b/doc/src/sgml/ref/pg_recvlogical.sgml
index a79ca20084..141c5cddce 100644
--- a/doc/src/sgml/ref/pg_recvlogical.sgml
+++ b/doc/src/sgml/ref/pg_recvlogical.sgml
@@ -399,6 +399,17 @@ PostgreSQL documentation
</para>
</refsect1>
+ <refsect1>
+ <title>Notes</title>
+
+ <para>
+ <application>pg_recvlogical</application> will preserve group permissions on
+ the received WAL files if group permissions are enabled on the source
+ cluster.
+ </para>
+
+ </refsect1>
+
<refsect1>
<title>Examples</title>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 587b430527..40ce3d9813 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -137,7 +137,10 @@ postgres$ <userinput>initdb -D /usr/local/pgsql/data</userinput>
database, it is essential that it be secured from unauthorized
access. <command>initdb</command> therefore revokes access
permissions from everyone but the
- <productname>PostgreSQL</productname> user.
+ <productname>PostgreSQL</productname> user, and optionally, group.
+ Group access, when enabled, is read-only. This allows an unprivileged
+ user in the <productname>PostgreSQL</productname> group to take a backup of
+ the cluster data or perform other operations that only require read access.
</para>
<para>
@@ -2194,6 +2197,15 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
member of the group that has access to those certificate and key files.
</para>
+ <para>
+ If the data directory allows group read access then certificate files may
+ need to be located outside of the data directory in order to conform to the
+ security requirements outlined above. Generally, group access is enabled
+ to allow an unprivileged user to backup the database, and in that case the
+ backup software will not be able to read the certificate files and will
+ likely error.
+ </para>
+
<para>
If the private key is protected with a passphrase, the
server will prompt for the passphrase and will not start until it has
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 1430894ad2..b8ecbda444 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 10afecffb3..aa78bf3a69 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 (onwer-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,14 @@ getInstallationPaths(const char *argv0)
*/
}
-
/*
- * Validate the proposed data directory
+ * Check that pg_control exists in the correct location in the 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 7bdecc32ec..5095a4f686 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 c1f0441b08..c6b616d61f 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 f8f08f3f88..436f89912e 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -88,6 +88,100 @@ SetDatabasePath(const char *path)
DatabasePath = MemoryContextStrdup(TopMemoryContext, 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 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 71c2b4eff1..8441837e0f 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"
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 3765548a24..a40c07649f 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -1168,6 +1168,19 @@ setup_config(void)
"password_encryption = scram-sha-256");
}
+ /*
+ * If group access has been enabled for the cluster then it makes sense
+ * to ensure that the log files also allow group access. Otherwise a
+ * backup from a user in the group would fail if the log files were not
+ * relocated.
+ */
+ if (pg_dir_create_mode == PG_DIR_MODE_GROUP)
+ {
+ conflines = replace_token(conflines,
+ "#log_file_mode = 0600",
+ "log_file_mode = 0640");
+ }
+
snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
writefile(path, conflines);
@@ -2312,6 +2325,7 @@ usage(const char *progname)
printf(_(" --auth-local=METHOD default authentication method for local-socket connections\n"));
printf(_(" [-D, --pgdata=]DATADIR location for this database cluster\n"));
printf(_(" -E, --encoding=ENCODING set default encoding for new databases\n"));
+ printf(_(" -g, --allow-group-access allow group read/execute on data directory\n"));
printf(_(" --locale=LOCALE set default locale for new databases\n"));
printf(_(" --lc-collate=, --lc-ctype=, --lc-messages=LOCALE\n"
" --lc-monetary=, --lc-numeric=, --lc-time=LOCALE\n"
@@ -2883,8 +2897,8 @@ initialize_data_directory(void)
setup_signals();
- /* Set dir/file mode mask */
- umask(PG_MODE_MASK_OWNER);
+ /* Set mask based on requested PGDATA permissions */
+ umask(pg_mode_mask);
create_data_directory();
@@ -3018,6 +3032,7 @@ main(int argc, char *argv[])
{"waldir", required_argument, NULL, 'X'},
{"wal-segsize", required_argument, NULL, 12},
{"data-checksums", no_argument, NULL, 'k'},
+ {"allow-group-access", no_argument, NULL, 'g'},
{NULL, 0, NULL, 0}
};
@@ -3059,7 +3074,7 @@ main(int argc, char *argv[])
/* process command-line options */
- while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:", long_options, &option_index)) != -1)
+ while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:g", long_options, &option_index)) != -1)
{
switch (c)
{
@@ -3153,6 +3168,9 @@ main(int argc, char *argv[])
case 12:
str_wal_segment_size_mb = pg_strdup(optarg);
break;
+ case 'g':
+ SetDataDirectoryCreatePerm(PG_DIR_MODE_GROUP);
+ break;
default:
/* getopt_long already emitted a complaint */
fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index 9dfb2ed96f..8609d2ecbc 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -4,9 +4,11 @@
use strict;
use warnings;
+use Fcntl ':mode';
+use File::stat qw{lstat};
use PostgresNode;
use TestLib;
-use Test::More tests => 16;
+use Test::More tests => 18;
my $tempdir = TestLib::tempdir;
my $xlogdir = "$tempdir/pgxlog";
@@ -57,3 +59,19 @@ mkdir $datadir;
}
command_ok([ 'initdb', '-S', $datadir ], 'sync only');
command_fails([ 'initdb', $datadir ], 'existing data directory');
+
+# Check group access on PGDATA
+SKIP:
+{
+ skip "unix-style permissions not supported on Windows", 2 if ($windows_os);
+
+ # Init a new db with group access
+ my $datadir_group = "$tempdir/data_group";
+
+ command_ok(
+ [ 'initdb', '-g', $datadir_group ],
+ 'successful creation with group access');
+
+ ok(check_mode_recursive($datadir_group, 0750, 0640),
+ 'check PGDATA permissions');
+}
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 32b41e313c..5cd535184a 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -2470,14 +2470,6 @@ main(int argc, char **argv)
}
#endif
- /*
- * Verify that the target directory exists, or create it. For plaintext
- * backups, always require the directory. For tar backups, require it
- * unless we are writing to stdout.
- */
- if (format == 'p' || strcmp(basedir, "-") != 0)
- verify_dir_is_empty_or_create(basedir, &made_new_pgdata, &found_existing_pgdata);
-
/* connection in replication mode to server */
conn = GetConnection();
if (!conn)
@@ -2486,6 +2478,20 @@ main(int argc, char **argv)
exit(1);
}
+ /*
+ * Set umask so that directories/files are created with the same permissions
+ * as directories/files in the source data directory.
+ */
+ umask(pg_mode_mask);
+
+ /*
+ * Verify that the target directory exists, or create it. For plaintext
+ * backups, always require the directory. For tar backups, require it
+ * unless we are writing to stdout.
+ */
+ if (format == 'p' || strcmp(basedir, "-") != 0)
+ verify_dir_is_empty_or_create(basedir, &made_new_pgdata, &found_existing_pgdata);
+
/* determine remote server's xlog segment size */
if (!RetrieveWalSegSize(conn))
disconnect_and_exit(1);
diff --git a/src/bin/pg_basebackup/pg_receivewal.c b/src/bin/pg_basebackup/pg_receivewal.c
index b82e073b86..f23ecf799f 100644
--- a/src/bin/pg_basebackup/pg_receivewal.c
+++ b/src/bin/pg_basebackup/pg_receivewal.c
@@ -19,6 +19,7 @@
#include <sys/stat.h>
#include <unistd.h>
+#include "common/file_perm.h"
#include "libpq-fe.h"
#include "access/xlog_internal.h"
#include "getopt_long.h"
@@ -703,6 +704,12 @@ main(int argc, char **argv)
if (!RunIdentifySystem(conn, NULL, NULL, NULL, &db_name))
disconnect_and_exit(1);
+ /*
+ * Set umask so that directories/files are created with the same permissions
+ * as directories/files in the source data directory.
+ */
+ umask(pg_mode_mask);
+
/* determine remote server's xlog segment size */
if (!RetrieveWalSegSize(conn))
disconnect_and_exit(1);
diff --git a/src/bin/pg_basebackup/pg_recvlogical.c b/src/bin/pg_basebackup/pg_recvlogical.c
index 0866395944..e3b6404b1f 100644
--- a/src/bin/pg_basebackup/pg_recvlogical.c
+++ b/src/bin/pg_basebackup/pg_recvlogical.c
@@ -23,6 +23,7 @@
#include "streamutil.h"
#include "access/xlog_internal.h"
+#include "common/file_perm.h"
#include "common/fe_memutils.h"
#include "getopt_long.h"
#include "libpq-fe.h"
@@ -959,6 +960,12 @@ main(int argc, char **argv)
disconnect_and_exit(1);
}
+ /*
+ * Set umask so that directories/files are created with the same permissions
+ * as directories/files in the source data directory.
+ */
+ umask(pg_mode_mask);
+
/* Drop a replication slot. */
if (do_drop_slot)
{
diff --git a/src/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c
index 1438f368ed..494acfec0e 100644
--- a/src/bin/pg_basebackup/streamutil.c
+++ b/src/bin/pg_basebackup/streamutil.c
@@ -23,6 +23,7 @@
#include "access/xlog_internal.h"
#include "common/fe_memutils.h"
+#include "common/file_perm.h"
#include "datatype/timestamp.h"
#include "fe_utils/connect.h"
#include "port/pg_bswap.h"
@@ -32,9 +33,16 @@
uint32 WalSegSz;
+static bool RetrieveDataDirCreatePerm(PGconn *conn);
+
/* SHOW command for replication connection was introduced in version 10 */
#define MINIMUM_VERSION_FOR_SHOW_CMD 100000
+/*
+ * Group access is supported from version 11.
+ */
+#define MINIMUM_VERSION_FOR_GROUP_ACCESS 110000
+
const char *progname;
char *connection_string = NULL;
char *dbhost = NULL;
@@ -254,6 +262,16 @@ GetConnection(void)
exit(1);
}
+ /*
+ * Retrieve the source data directory mode and use to construct a umask
+ * for creating directories and files
+ */
+ if (!RetrieveDataDirCreatePerm(tmpconn))
+ {
+ PQfinish(tmpconn);
+ exit(1);
+ }
+
return tmpconn;
}
@@ -327,6 +345,57 @@ RetrieveWalSegSize(PGconn *conn)
return true;
}
+/*
+ * From version 11, check the mode of the data directory to determine
+ * permissions to use for directories created by the utility.
+ */
+static bool
+RetrieveDataDirCreatePerm(PGconn *conn)
+{
+ PGresult *res;
+ int data_directory_mode;
+
+ /* check connection existence */
+ Assert(conn != NULL);
+
+ /* for previous versions leave the default group access */
+ if (PQserverVersion(conn) < MINIMUM_VERSION_FOR_GROUP_ACCESS)
+ return true;
+
+ res = PQexec(conn, "SHOW data_directory_mode");
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ fprintf(stderr, _("%s: could not send replication command \"%s\": %s\n"),
+ progname, "SHOW data_directory_mode", PQerrorMessage(conn));
+
+ PQclear(res);
+ return false;
+ }
+ if (PQntuples(res) != 1 || PQnfields(res) < 1)
+ {
+ fprintf(stderr,
+ _("%s: could not fetch group access flag: got %d rows and %d fields, expected %d rows and %d or more fields\n"),
+ progname, PQntuples(res), PQnfields(res), 1, 1);
+
+ PQclear(res);
+ return false;
+ }
+
+ if (sscanf(PQgetvalue(res, 0, 0), "%o", &data_directory_mode) != 1)
+ {
+ fprintf(stderr, _("%s: group access flag could not be parsed: %s\n"),
+ progname, PQgetvalue(res, 0, 0));
+
+ PQclear(res);
+ return false;
+ }
+
+ SetDataDirectoryCreatePerm(data_directory_mode);
+
+ PQclear(res);
+ return true;
+}
+
/*
* Run IDENTIFY_SYSTEM through a given connection and give back to caller
* some result information if requested:
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index ec54b555ed..a40e9c2d51 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -5,7 +5,7 @@ use Config;
use File::Basename qw(basename dirname);
use PostgresNode;
use TestLib;
-use Test::More tests => 105;
+use Test::More tests => 106;
program_help_ok('pg_basebackup');
program_version_ok('pg_basebackup');
@@ -43,11 +43,6 @@ $node->command_fails(
ok(!-d "$tempdir/backup", 'backup directory was cleaned up');
-$node->command_fails([ 'pg_basebackup', '-D', "$tempdir/backup", '-n' ],
- 'failing run with no-clean option');
-
-ok(-d "$tempdir/backup", 'backup directory was created and left behind');
-
open my $conf, '>>', "$pgdata/postgresql.conf";
print $conf "max_replication_slots = 10\n";
print $conf "max_wal_senders = 10\n";
@@ -162,6 +157,13 @@ ok(-f "$tempdir/tarbackup/base.tar", 'backup tar was created');
$node->command_fails(
[ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T=/foo" ],
'-T with empty old directory fails');
+
+$node->command_fails(
+ [ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T=/foo", "-n" ],
+ 'failing run with no-clean option');
+
+ok(-d "$tempdir/backup", 'backup directory was created and left behind');
+
$node->command_fails(
[ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T/foo=" ],
'-T with empty new directory fails');
@@ -195,11 +197,17 @@ unlink "$pgdata/$superlongname";
# skip on Windows.
SKIP:
{
- skip "symlinks not supported on Windows", 17 if ($windows_os);
+ skip "symlinks not supported on Windows", 18 if ($windows_os);
# Move pg_replslot out of $pgdata and create a symlink to it.
$node->stop;
+ # Set umask so test directories and files are created with group permissions
+ umask(0027);
+
+ # Enable group permissions on PGDATA
+ chmod_recursive("$pgdata", 0750, 0640);
+
rename("$pgdata/pg_replslot", "$tempdir/pg_replslot")
or BAIL_OUT "could not move $pgdata/pg_replslot";
symlink("$tempdir/pg_replslot", "$pgdata/pg_replslot")
@@ -269,6 +277,10 @@ SKIP:
"tablespace symlink was updated");
closedir $dh;
+ # Group access should be enabled on all backup files
+ ok(check_mode_recursive("$tempdir/backup1", 0750, 0640),
+ "check backup dir permissions");
+
# Unlogged relation forks other than init should not be copied
my ($tblspc1UnloggedBackupPath) = $tblspc1UnloggedPath =~ /[^\/]*\/[^\/]*\/[^\/]*$/g;
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index 5ede385e6a..143021de05 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -2171,7 +2171,7 @@ main(int argc, char **argv)
*/
argv0 = argv[0];
- /* Set dir/file mode mask */
+ /* Set restrictive mode mask until PGDATA permissions are checked */
umask(PG_MODE_MASK_OWNER);
/* support --help and --version even if invoked as root */
@@ -2407,6 +2407,16 @@ main(int argc, char **argv)
snprintf(version_file, MAXPGPATH, "%s/PG_VERSION", pg_data);
snprintf(pid_file, MAXPGPATH, "%s/postmaster.pid", pg_data);
snprintf(backup_file, MAXPGPATH, "%s/backup_label", pg_data);
+
+ /*
+ * Set mask based on PGDATA permissions,
+ *
+ * Don't error here if the data directory cannot be stat'd. This is
+ * handled differently based on the command and we don't want to
+ * interfere with that logic.
+ */
+ if (GetDataDirectoryCreatePerm(pg_data))
+ umask(pg_mode_mask);
}
switch (ctl_command)
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl
index 067a084c87..2f9dfa7b81 100644
--- a/src/bin/pg_ctl/t/001_start_stop.pl
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -2,9 +2,11 @@ use strict;
use warnings;
use Config;
+use Fcntl ':mode';
+use File::stat qw{lstat};
use PostgresNode;
use TestLib;
-use Test::More tests => 21;
+use Test::More tests => 24;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -74,6 +76,27 @@ SKIP:
ok(check_mode_recursive("$tempdir/data", 0700, 0600));
}
+# Log file for group access test
+$logFileName = "$tempdir/data/perm-test-640.log";
+
+SKIP:
+{
+ skip "group access not supported on Windows", 3 if ($windows_os);
+
+ system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
+
+ # Change the data dir mode so log file will be created with group read
+ # privileges on the next start
+ chmod_recursive("$tempdir/data", 0750, 0640);
+
+ command_ok(
+ [ 'pg_ctl', 'start', '-D', "$tempdir/data", '-l', $logFileName ],
+ 'start server to check group permissions');
+
+ ok(-f $logFileName);
+ ok(check_mode_recursive("$tempdir/data", 0750, 0640));
+}
+
command_ok([ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
'pg_ctl restart with server running');
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index bdf71886ee..8a0a805f1e 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -363,6 +363,16 @@ main(int argc, char *argv[])
exit(1);
}
+ /* Set mask based on PGDATA permissions */
+ if (!GetDataDirectoryCreatePerm(DataDir))
+ {
+ fprintf(stderr, _("%s: unable to read permissions from \"%s\"\n"),
+ progname, DataDir);
+ exit(1);
+ }
+
+ umask(pg_mode_mask);
+
/* Check that data directory matches our server version */
CheckDataVersion();
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index 7b632c7dcd..63d9bd517d 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -115,11 +115,13 @@ sub check_query
sub setup_cluster
{
- my $extra_name = shift;
+ my $extra_name = shift; # Used to differentiate clusters
+ my $extra = shift; # Extra params for initdb
# Initialize master, data checksums are mandatory
$node_master = get_new_node('master' . ($extra_name ? "_${extra_name}" : ''));
- $node_master->init(allows_streaming => 1);
+ $node_master->init(
+ allows_streaming => 1, extra => $extra);
# Set wal_keep_segments to prevent WAL segment recycling after enforced
# checkpoints in the tests.
$node_master->append_conf('postgresql.conf', qq(
@@ -237,7 +239,8 @@ sub run_pg_rewind
"$tmp_folder/master-postgresql.conf.tmp",
"$master_pgdata/postgresql.conf");
- chmod(0600, "$master_pgdata/postgresql.conf")
+ chmod($node_master->group_access() ? 0640 : 0600,
+ "$master_pgdata/postgresql.conf")
or BAIL_OUT(
"unable to set permissions for $master_pgdata/postgresql.conf");
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 72ab2f8d5e..b9ea6a4c21 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -24,6 +24,7 @@
#include "access/xlog_internal.h"
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "getopt_long.h"
#include "storage/bufpage.h"
@@ -185,6 +186,16 @@ main(int argc, char **argv)
exit(1);
}
+ /* Set mask based on PGDATA permissions */
+ if (!GetDataDirectoryCreatePerm(datadir_target))
+ {
+ fprintf(stderr, _("%s: unable to read permissions from \"%s\"\n"),
+ progname, datadir_target);
+ exit(1);
+ }
+
+ umask(pg_mode_mask);
+
/*
* Don't allow pg_rewind to be run as root, to avoid overwriting the
* ownership of files in the data directory. We need only check for root
diff --git a/src/bin/pg_rewind/t/002_databases.pl b/src/bin/pg_rewind/t/002_databases.pl
index 37cdd712f3..c364965d3a 100644
--- a/src/bin/pg_rewind/t/002_databases.pl
+++ b/src/bin/pg_rewind/t/002_databases.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 6;
use RewindTest;
@@ -9,7 +9,7 @@ sub run_test
{
my $test_mode = shift;
- RewindTest::setup_cluster($test_mode);
+ RewindTest::setup_cluster($test_mode, ['-g']);
RewindTest::start_master();
# Create a database in master.
@@ -42,6 +42,15 @@ template1
),
'database names');
+ # Permissions on PGDATA should have group permissions
+ SKIP:
+ {
+ skip "unix-style permissions not supported on Windows", 1 if ($windows_os);
+
+ ok(check_mode_recursive($node_master->data_dir(), 0750, 0640),
+ 'check PGDATA permissions');
+ }
+
RewindTest::clean_rewind_test();
}
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 1d35188143..cc8e8c94c5 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -79,7 +79,7 @@ main(int argc, char **argv)
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_upgrade"));
- /* Ensure that all files created by pg_upgrade are non-world-readable */
+ /* Set default restrictive mask until new cluster permissions are read */
umask(PG_MODE_MASK_OWNER);
parseCommandLine(argc, argv);
@@ -100,6 +100,16 @@ main(int argc, char **argv)
check_cluster_compatibility(live_check);
+ /* Set mask based on PGDATA permissions */
+ if (!GetDataDirectoryCreatePerm(new_cluster.pgdata))
+ {
+ pg_log(PG_FATAL, "unable to read permissions from \"%s\"\n",
+ new_cluster.pgdata);
+ exit(1);
+ }
+
+ umask(pg_mode_mask);
+
check_and_dump_old_cluster(live_check);
diff --git a/src/bin/pg_upgrade/test.sh b/src/bin/pg_upgrade/test.sh
index 574639d47e..24631a91c6 100644
--- a/src/bin/pg_upgrade/test.sh
+++ b/src/bin/pg_upgrade/test.sh
@@ -20,9 +20,9 @@ unset MAKELEVEL
# Run a given "initdb" binary and overlay the regression testing
# authentication configuration.
standard_initdb() {
- # To increase coverage of non-standard segment size without
- # increase test runtime, run these tests with a lower setting.
- "$1" -N --wal-segsize 1
+ # To increase coverage of non-standard segment size and group access
+ # without increasing test runtime, run these tests with a custom setting.
+ "$1" -N --wal-segsize 1 -g
if [ -n "$TEMP_CONFIG" -a -r "$TEMP_CONFIG" ]
then
cat "$TEMP_CONFIG" >> "$PGDATA/postgresql.conf"
@@ -230,14 +230,14 @@ standard_initdb 'initdb'
pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "${PGDATA}" -b "$oldbindir" -B "$bindir" -p "$PGPORT" -P "$PGPORT"
-# make sure all directories and files have correct permissions
-if [ $(find ${PGDATA} -type f ! -perm 600 | wc -l) -ne 0 ]; then
- echo "files in PGDATA with permission != 600";
+# make sure all directories and files have group permissions
+if [ $(find ${PGDATA} -type f ! -perm 640 | wc -l) -ne 0 ]; then
+ echo "files in PGDATA with permission != 640";
exit 1;
fi
-if [ $(find ${PGDATA} -type d ! -perm 700 | wc -l) -ne 0 ]; then
- echo "directories in PGDATA with permission != 700";
+if [ $(find ${PGDATA} -type d ! -perm 750 | wc -l) -ne 0 ]; then
+ echo "directories in PGDATA with permission != 750";
exit 1;
fi
diff --git a/src/common/file_perm.c b/src/common/file_perm.c
index fdfbb9a44c..f4a093e8f2 100644
--- a/src/common/file_perm.c
+++ b/src/common/file_perm.c
@@ -12,8 +12,76 @@
*/
#include <sys/stat.h>
+#include "c.h"
#include "common/file_perm.h"
/* Modes for creating directories and files in the data directory */
int pg_dir_create_mode = PG_DIR_MODE_OWNER;
int pg_file_create_mode = PG_FILE_MODE_OWNER;
+
+/*
+ * Mode mask to pass to umask(). This is more of a preventative measure since
+ * all file/directory creates should be performed using the create modes above.
+ */
+int pg_mode_mask = PG_MODE_MASK_OWNER;
+
+/*
+ * Set create modes and mask to use when writing to PGDATA based on the data
+ * directory mode passed. If group read/execute are present in the
+ * mode, then create modes and mask will be relaxed to allow group read/execute
+ * on all newly created files and directories.
+ */
+void
+SetDataDirectoryCreatePerm(int dataDirMode)
+{
+ /* If the data directory mode has group access */
+ if ((PG_DIR_MODE_GROUP & dataDirMode) == PG_DIR_MODE_GROUP)
+ {
+ pg_dir_create_mode = PG_DIR_MODE_GROUP;
+ pg_file_create_mode = PG_FILE_MODE_GROUP;
+ pg_mode_mask = PG_MODE_MASK_GROUP;
+ }
+ /* Else use default permissions */
+ else
+ {
+ pg_dir_create_mode = PG_DIR_MODE_OWNER;
+ pg_file_create_mode = PG_FILE_MODE_OWNER;
+ pg_mode_mask = PG_MODE_MASK_OWNER;
+ }
+}
+
+#ifdef FRONTEND
+
+/*
+ * Get the create modes and mask to use when writing to PGDATA by examining the
+ * mode of the PGDATA directory and calling SetDataDirectoryCreatePerm().
+ *
+ * Errors are not handled here and should be reported by the frontend
+ * application when false is returned.
+ *
+ * Suppress when on Windows, because there may not be proper support for Unix-y
+ * file permissions.
+ */
+bool
+GetDataDirectoryCreatePerm(const char *dataDir)
+{
+#if !defined(WIN32) && !defined(__CYGWIN__)
+ struct stat statBuf;
+
+ /*
+ * If an error occurs getting the mode then false It is the reponsibility of
+ * the frontend application to generate an error if the PGDATA directory
+ * cannot be accessed.
+ */
+ if (stat(dataDir, &statBuf) == -1)
+ return false;
+
+ /* Set permissions */
+ SetDataDirectoryCreatePerm(statBuf.st_mode);
+
+ return true;
+#endif /* !defined(WIN32) && !defined(__CYGWIN__) */
+}
+
+
+#endif /* FRONTEND */
diff --git a/src/include/common/file_perm.h b/src/include/common/file_perm.h
index 700f562582..eb0f079dd3 100644
--- a/src/include/common/file_perm.h
+++ b/src/include/common/file_perm.h
@@ -19,14 +19,34 @@
*/
#define PG_MODE_MASK_OWNER (S_IRWXG | S_IRWXO)
+/*
+ * Mode mask for data directory permissions that also allows group read/execute.
+ */
+#define PG_MODE_MASK_GROUP (S_IWGRP | S_IRWXO)
+
/* Default mode for creating directories */
#define PG_DIR_MODE_OWNER S_IRWXU
+/* Mode for creating directories that allows group read/execute */
+#define PG_DIR_MODE_GROUP (S_IRWXU | S_IRGRP | S_IXGRP)
+
/* Default mode for creating files */
#define PG_FILE_MODE_OWNER (S_IRUSR | S_IWUSR)
+/* Mode for creating files that allows group read */
+#define PG_FILE_MODE_GROUP (S_IRUSR | S_IWUSR | S_IRGRP)
+
/* Modes for creating directories and files in the data directory */
extern int pg_dir_create_mode;
extern int pg_file_create_mode;
+/* Mode mask to pass to umask() */
+extern int pg_mode_mask;
+
+/* Set permissions and mask based on the provided mode */
+extern void SetDataDirectoryCreatePerm(int dataDirMode);
+
+/* Set permissions and mask based on the mode of the data directory */
+extern bool GetDataDirectoryCreatePerm(const char *dataDir);
+
#endif /* FILE_PERM_H */
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index b5ad841968..e167ee8fcb 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -153,6 +153,7 @@ extern PGDLLIMPORT bool IsBinaryUpgrade;
extern PGDLLIMPORT bool ExitOnAnyError;
extern PGDLLIMPORT char *DataDir;
+extern PGDLLIMPORT int data_directory_mode;
extern PGDLLIMPORT int NBuffers;
extern PGDLLIMPORT int MaxBackends;
@@ -323,6 +324,7 @@ extern void SetSessionAuthorization(Oid userid, bool is_superuser);
extern Oid GetCurrentRoleId(void);
extern void SetCurrentRoleId(Oid roleid, bool is_superuser);
+extern void checkDataDir(void);
extern void SetDataDir(const char *dir);
extern void ChangeToDataDir(void);
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 76e571b98c..1bd91524d7 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -86,9 +86,11 @@ use Carp;
use Config;
use Cwd;
use Exporter 'import';
+use Fcntl qw(:mode);
use File::Basename;
use File::Path qw(rmtree);
use File::Spec;
+use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
use RecursiveCopy;
@@ -269,6 +271,26 @@ sub connstr
=pod
+=item $node->group_access()
+
+Does the data dir allow group access?
+
+=cut
+
+sub group_access
+{
+ my ($self) = @_;
+
+ my $dir_stat = stat($self->data_dir);
+
+ defined($dir_stat)
+ or die('unable to stat ' . $self->data_dir);
+
+ return (S_IMODE($dir_stat->mode) == 0750);
+}
+
+=pod
+
=item $node->data_dir()
Returns the path to the data directory. postgresql.conf and pg_hba.conf are
@@ -460,6 +482,9 @@ sub init
}
close $conf;
+ chmod($self->group_access ? 0640 : 0600, "$pgdata/postgresql.conf")
+ or die("unable to set permissions for $pgdata/postgresql.conf");
+
$self->set_replication_conf if $params{allows_streaming};
$self->enable_archiving if $params{has_archiving};
}
@@ -485,7 +510,7 @@ sub append_conf
TestLib::append_to_file($conffile, $str . "\n");
- chmod(0600, $conffile)
+ chmod($self->group_access() ? 0640 : 0600, $conffile)
or die("unable to set permissions for $conffile");
}
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index 93610e4bc4..8047404efd 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -31,6 +31,7 @@ our @EXPORT = qw(
slurp_file
append_to_file
check_mode_recursive
+ chmod_recursive
check_pg_config
system_or_bail
system_log
@@ -313,6 +314,30 @@ sub check_mode_recursive
return $result;
}
+# Change mode recursively on a directory
+sub chmod_recursive
+{
+ my ($dir, $dir_mode, $file_mode) = @_;
+
+ find
+ (
+ {follow_fast => 1,
+ wanted =>
+ sub
+ {
+ my $file_stat = stat($File::Find::name);
+
+ if (defined($file_stat))
+ {
+ chmod(S_ISDIR($file_stat->mode) ? $dir_mode : $file_mode,
+ $File::Find::name)
+ or die "unable to chmod $File::Find::name";
+ }
+ }},
+ $dir
+ );
+}
+
# Check presence of a given regexp within pg_config.h for the installation
# where tests are running, returning a match status result depending on
# that.
--
2.14.3 (Apple Git-98)
On 4/6/18 6:04 PM, David Steele wrote:
On 4/6/18 3:02 PM, Stephen Frost wrote:
- Further discussion in the commit messages
Agreed, these need some more work.� I'm happy to do that but I'll need a
bit more time.� Have a look at the new patches and I'll work on some
better messages.
I'm sure you'll want to reword some things, but I think these commit
messages capture the essential changes for each patch.
01: Refactor file permissions in backend/frontend
Consolidate directory and file create permissions by adding a new module
(common/file_perm.c) that contains variables (pg_file_create_mode,
pg_dir_create_mode) and constants to initialize them (0600 for files and
0700 for directories).
Convert mkdir() calls in the backend to MakePGDirectory() if the
original call used default permissions (always the case for regular PG
directories).
Add tests to make sure permissions in PGDATA are set correctly by the
front-end tools.
Author: David Steele <david@pgmasters.net>
Reviewed-By: Michael Paquier, with discussion amongst many others.
Discussion:
/messages/by-id/ad346fe6-b23e-59f1-ecb7-0e08390ad629@pgmasters.net
02: 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 because
there are a number of front-end utilities that write into PGDATA but not
all of them read pg_control and none of them load GUCS.
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 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 front-end utilities to ensure
that the correct mode is set based on the PGDATA permissions.
Author: David Steele <david@pgmasters.net>
Reviewed-By: Michael Paquier, with discussion amongst many others.
Discussion:
/messages/by-id/https://www.postgresql.org/message-id/ad346fe6-b23e-59f1-ecb7-0e08390ad629@pgmasters.net
Thanks!
--
-David
david@pgmasters.net
David,
* David Steele (david@pgmasters.net) wrote:
On 4/6/18 6:04 PM, David Steele wrote:
On 4/6/18 3:02 PM, Stephen Frost wrote:
- Further discussion in the commit messages
Agreed, these need some more work. I'm happy to do that but I'll need a
bit more time. Have a look at the new patches and I'll work on some
better messages.I'm sure you'll want to reword some things, but I think these commit
messages capture the essential changes for each patch.
Thanks much. I've taken (most) of these, adjusting a few bits here and
there.
I've been back over the patch again, mostly improving the commit
messages, comments, and docs. I also looked over the code and tests
again and they're looking pretty good to me, so I'll be looking to
commit this tomorrow afternoon or so US/Eastern.
Attached is v18.
Thanks!
Stephen
Attachments:
group-access-v18-master.patchtext/x-diff; charset=us-asciiDownload
From 306d152df68e294e056da1b55b33460f5a585063 Mon Sep 17 00:00:00 2001
From: David Steele <david@pgmasters.net>
Date: Fri, 6 Apr 2018 18:10:43 -0400
Subject: [PATCH 1/2] Refactor dir/file permissions
Consolidate directory and file create permissions for tools which work
with the PG data directory by adding a new module (common/file_perm.c)
that contains variables (pg_file_create_mode, pg_dir_create_mode) and
constants to initialize them (0600 for files and 0700 for directories).
Convert mkdir() calls in the backend to MakePGDirectory() if the
original call used default permissions (always the case for regular PG
directories).
Add tests to make sure permissions in PGDATA are set correctly by the
tools which modify the PG data directory.
Authors: David Steele <david@pgmasters.net>,
Adam Brightwell <adam.brightwell@crunchydata.com>
Reviewed-By: Michael Paquier, with discussion amongst many others.
Discussion: https://postgr.es/m/ad346fe6-b23e-59f1-ecb7-0e08390ad629%40pgmasters.net
---
src/backend/access/transam/xlog.c | 2 +-
src/backend/commands/tablespace.c | 18 ++++---
src/backend/postmaster/postmaster.c | 7 +--
src/backend/postmaster/syslogger.c | 5 +-
src/backend/replication/basebackup.c | 5 +-
src/backend/replication/slot.c | 5 +-
src/backend/storage/file/copydir.c | 2 +-
src/backend/storage/file/fd.c | 51 +++++++++++++------
src/backend/storage/ipc/dsm_impl.c | 3 +-
src/backend/storage/ipc/ipc.c | 4 ++
src/backend/utils/init/miscinit.c | 5 +-
src/bin/initdb/initdb.c | 24 ++++-----
src/bin/initdb/t/001_initdb.pl | 11 ++++-
src/bin/pg_basebackup/pg_basebackup.c | 9 ++--
src/bin/pg_basebackup/t/010_pg_basebackup.pl | 14 +++++-
src/bin/pg_basebackup/t/020_pg_receivewal.pl | 14 +++++-
src/bin/pg_basebackup/walmethods.c | 6 ++-
src/bin/pg_ctl/pg_ctl.c | 4 +-
src/bin/pg_ctl/t/001_start_stop.pl | 18 ++++++-
src/bin/pg_resetwal/pg_resetwal.c | 5 +-
src/bin/pg_resetwal/t/001_basic.pl | 12 ++++-
src/bin/pg_rewind/RewindTest.pm | 4 ++
src/bin/pg_rewind/file_ops.c | 7 +--
src/bin/pg_rewind/t/001_basic.pl | 11 ++++-
src/bin/pg_upgrade/file.c | 5 +-
src/bin/pg_upgrade/pg_upgrade.c | 3 +-
src/bin/pg_upgrade/test.sh | 11 +++++
src/common/Makefile | 4 +-
src/common/file_perm.c | 19 ++++++++
src/include/common/file_perm.h | 34 +++++++++++++
src/include/storage/fd.h | 3 ++
src/test/perl/PostgresNode.pm | 3 ++
src/test/perl/TestLib.pm | 73 ++++++++++++++++++++++++++++
src/tools/msvc/Mkvcbuild.pm | 4 +-
34 files changed, 330 insertions(+), 75 deletions(-)
create mode 100644 src/common/file_perm.c
create mode 100644 src/include/common/file_perm.h
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 813b2afaac..4a47395174 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4107,7 +4107,7 @@ ValidateXLOGDirectoryStructure(void)
{
ereport(LOG,
(errmsg("creating missing WAL directory \"%s\"", path)));
- if (mkdir(path, S_IRWXU) < 0)
+ if (MakePGDirectory(path) < 0)
ereport(FATAL,
(errmsg("could not create missing directory \"%s\": %m",
path)));
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 5c450caa4e..cfc9fe468c 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -68,6 +68,7 @@
#include "commands/seclabel.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
+#include "common/file_perm.h"
#include "miscadmin.h"
#include "postmaster/bgwriter.h"
#include "storage/fd.h"
@@ -151,7 +152,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
else
{
/* Directory creation failed? */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakePGDirectory(dir) < 0)
{
char *parentdir;
@@ -173,7 +174,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
get_parent_directory(parentdir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakePGDirectory(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -184,7 +185,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
parentdir = pstrdup(dir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakePGDirectory(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -192,7 +193,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
pfree(parentdir);
/* Create database directory */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakePGDirectory(dir) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -279,7 +280,8 @@ CreateTableSpace(CreateTableSpaceStmt *stmt)
/*
* Check that location isn't too long. Remember that we're going to append
* 'PG_XXX/<dboid>/<relid>_<fork>.<nnn>'. FYI, we never actually
- * reference the whole path here, but mkdir() uses the first two parts.
+ * reference the whole path here, but MakePGDirectory() uses the first
+ * two parts.
*/
if (strlen(location) + 1 + strlen(TABLESPACE_VERSION_DIRECTORY) + 1 +
OIDCHARS + 1 + OIDCHARS + 1 + FORKNAMECHARS + 1 + OIDCHARS > MAXPGPATH)
@@ -574,7 +576,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* Attempt to coerce target directory to safe permissions. If this fails,
* it doesn't exist or has the wrong owner.
*/
- if (chmod(location, S_IRWXU) != 0)
+ if (chmod(location, pg_dir_create_mode) != 0)
{
if (errno == ENOENT)
ereport(ERROR,
@@ -599,7 +601,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
if (stat(location_with_version_dir, &st) == 0 && S_ISDIR(st.st_mode))
{
if (!rmtree(location_with_version_dir, true))
- /* If this failed, mkdir() below is going to error. */
+ /* If this failed, MakePGDirectory() below is going to error. */
ereport(WARNING,
(errmsg("some useless files may be left behind in old database directory \"%s\"",
location_with_version_dir)));
@@ -610,7 +612,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* The creation of the version directory prevents more than one tablespace
* in a single location.
*/
- if (mkdir(location_with_version_dir, S_IRWXU) < 0)
+ if (MakePGDirectory(location_with_version_dir) < 0)
{
if (errno == EEXIST)
ereport(ERROR,
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 3dfb87d701..10afecffb3 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -97,6 +97,7 @@
#include "access/xlog.h"
#include "bootstrap/bootstrap.h"
#include "catalog/pg_control.h"
+#include "common/file_perm.h"
#include "common/ip.h"
#include "lib/ilist.h"
#include "libpq/auth.h"
@@ -589,7 +590,7 @@ PostmasterMain(int argc, char *argv[])
/*
* for security, no dir or file created can be group or other accessible
*/
- umask(S_IRWXG | S_IRWXO);
+ umask(PG_MODE_MASK_OWNER);
/*
* Initialize random(3) so we don't get the same values in every run.
@@ -4490,9 +4491,9 @@ internal_forkexec(int argc, char *argv[], Port *port)
{
/*
* As in OpenTemporaryFileInTablespace, try to make the temp-file
- * directory
+ * directory, ignoring errors.
*/
- mkdir(PG_TEMP_FILES_DIR, S_IRWXU);
+ (void) MakePGDirectory(PG_TEMP_FILES_DIR);
fp = AllocateFile(tmpfilename, PG_BINARY_W);
if (!fp)
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index f70eea37df..58b759f305 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -41,6 +41,7 @@
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
#include "storage/dsm.h"
+#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pg_shmem.h"
@@ -322,7 +323,7 @@ SysLoggerMain(int argc, char *argv[])
/*
* Also, create new directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ (void) MakePGDirectory(Log_directory);
}
if (strcmp(Log_filename, currentLogFilename) != 0)
{
@@ -564,7 +565,7 @@ SysLogger_Start(void)
/*
* Create log directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ (void) MakePGDirectory(Log_directory);
/*
* The initial logfile is created right in the postmaster, to verify that
diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c
index 8ba29453b9..babf85a6ea 100644
--- a/src/backend/replication/basebackup.c
+++ b/src/backend/replication/basebackup.c
@@ -19,6 +19,7 @@
#include "access/xlog_internal.h" /* for pg_start/stop_backup */
#include "catalog/catalog.h"
#include "catalog/pg_type.h"
+#include "common/file_perm.h"
#include "lib/stringinfo.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
@@ -930,7 +931,7 @@ sendFileWithContent(const char *filename, const char *content)
statbuf.st_gid = getegid();
#endif
statbuf.st_mtime = time(NULL);
- statbuf.st_mode = S_IRUSR | S_IWUSR;
+ statbuf.st_mode = pg_file_create_mode;
statbuf.st_size = len;
_tarWriteHeader(filename, NULL, &statbuf, false);
@@ -1628,7 +1629,7 @@ _tarWriteDir(const char *pathbuf, int basepathlen, struct stat *statbuf,
#else
if (pgwin32_is_junction(pathbuf))
#endif
- statbuf->st_mode = S_IFDIR | S_IRWXU;
+ statbuf->st_mode = S_IFDIR | pg_dir_create_mode;
return _tarWriteHeader(pathbuf + basepathlen + 1, NULL, statbuf, sizeonly);
}
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index fc9ef22b0b..056628fe8e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -1166,13 +1166,14 @@ CreateSlotOnDisk(ReplicationSlot *slot)
* It's just barely possible that some previous effort to create or drop a
* slot with this name left a temp directory lying around. If that seems
* to be the case, try to remove it. If the rmtree() fails, we'll error
- * out at the mkdir() below, so we don't bother checking success.
+ * out at the MakePGDirectory() below, so we don't bother checking
+ * success.
*/
if (stat(tmppath, &st) == 0 && S_ISDIR(st.st_mode))
rmtree(tmppath, true);
/* Create and fsync the temporary slot directory. */
- if (mkdir(tmppath, S_IRWXU) < 0)
+ if (MakePGDirectory(tmppath) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
diff --git a/src/backend/storage/file/copydir.c b/src/backend/storage/file/copydir.c
index ca6342db0d..4a0d23b11e 100644
--- a/src/backend/storage/file/copydir.c
+++ b/src/backend/storage/file/copydir.c
@@ -41,7 +41,7 @@ copydir(char *fromdir, char *todir, bool recurse)
char fromfile[MAXPGPATH * 2];
char tofile[MAXPGPATH * 2];
- if (mkdir(todir, S_IRWXU) != 0)
+ if (MakePGDirectory(todir) != 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m", todir)));
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index d30a725f90..03247c281c 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -84,6 +84,7 @@
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "catalog/pg_tablespace.h"
+#include "common/file_perm.h"
#include "pgstat.h"
#include "portability/mem.h"
#include "storage/fd.h"
@@ -124,12 +125,6 @@
*/
#define FD_MINFREE 10
-/*
- * Default mode for created files, unless something else is specified using
- * the *Perm() function variants.
- */
-#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR)
-
/*
* A number of platforms allow individual processes to open many more files
* than they can really support when *many* processes do the same thing.
@@ -937,7 +932,7 @@ set_max_safe_fds(void)
int
BasicOpenFile(const char *fileName, int fileFlags)
{
- return BasicOpenFilePerm(fileName, fileFlags, PG_FILE_MODE_DEFAULT);
+ return BasicOpenFilePerm(fileName, fileFlags, pg_file_create_mode);
}
/*
@@ -1356,7 +1351,7 @@ FileInvalidate(File file)
File
PathNameOpenFile(const char *fileName, int fileFlags)
{
- return PathNameOpenFilePerm(fileName, fileFlags, PG_FILE_MODE_DEFAULT);
+ return PathNameOpenFilePerm(fileName, fileFlags, pg_file_create_mode);
}
/*
@@ -1434,7 +1429,7 @@ PathNameOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode)
void
PathNameCreateTemporaryDir(const char *basedir, const char *directory)
{
- if (mkdir(directory, S_IRWXU) < 0)
+ if (MakePGDirectory(directory) < 0)
{
if (errno == EEXIST)
return;
@@ -1444,14 +1439,14 @@ PathNameCreateTemporaryDir(const char *basedir, const char *directory)
* EEXIST to close a race against another process following the same
* algorithm.
*/
- if (mkdir(basedir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakePGDirectory(basedir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary directory \"%s\": %m",
basedir)));
/* Try again. */
- if (mkdir(directory, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakePGDirectory(directory) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary subdirectory \"%s\": %m",
@@ -1601,11 +1596,11 @@ OpenTemporaryFileInTablespace(Oid tblspcOid, bool rejectError)
* We might need to create the tablespace's tempfile directory, if no
* one has yet done so.
*
- * Don't check for error from mkdir; it could fail if someone else
- * just did the same thing. If it doesn't work then we'll bomb out on
- * the second create attempt, instead.
+ * Don't check for an error from MakePGDirectory; it could fail if
+ * someone else just did the same thing. If it doesn't work then
+ * we'll bomb out on the second create attempt, instead.
*/
- mkdir(tempdirpath, S_IRWXU);
+ (void) MakePGDirectory(tempdirpath);
file = PathNameOpenFile(tempfilepath,
O_RDWR | O_CREAT | O_TRUNC | PG_BINARY);
@@ -2401,7 +2396,7 @@ TryAgain:
int
OpenTransientFile(const char *fileName, int fileFlags)
{
- return OpenTransientFilePerm(fileName, fileFlags, PG_FILE_MODE_DEFAULT);
+ return OpenTransientFilePerm(fileName, fileFlags, pg_file_create_mode);
}
/*
@@ -3554,3 +3549,27 @@ fsync_parent_path(const char *fname, int elevel)
return 0;
}
+
+/*
+ * Create a PostgreSQL data sub-directory
+ *
+ * The data directory itself, along with most other directories, are created at
+ * initdb-time, but we do have some occations where we create directories from
+ * the backend (CREATE TABLESPACE, for example). In those cases, we want to
+ * make sure that those directories are created consistently. Today, that means
+ * making sure that the created directory has the correct permissions, which is
+ * what pg_dir_create_mode tracks for us.
+ *
+ * Note that we also set the umask() based on what we understand the correct
+ * permissions to be (see file_perm.c).
+ *
+ * For permissions other than the default mkdir() can be used directly, but be
+ * sure to consider carefully such cases- a directory with incorrect permissions
+ * in a PostgreSQL data directory could cause backups and other processes to
+ * fail.
+ */
+int
+MakePGDirectory(const char *directoryName)
+{
+ return mkdir(directoryName, pg_dir_create_mode);
+}
diff --git a/src/backend/storage/ipc/dsm_impl.c b/src/backend/storage/ipc/dsm_impl.c
index 67e76b98fe..2fca9fae51 100644
--- a/src/backend/storage/ipc/dsm_impl.c
+++ b/src/backend/storage/ipc/dsm_impl.c
@@ -60,6 +60,7 @@
#ifdef HAVE_SYS_SHM_H
#include <sys/shm.h>
#endif
+#include "common/file_perm.h"
#include "pgstat.h"
#include "portability/mem.h"
@@ -285,7 +286,7 @@ dsm_impl_posix(dsm_op op, dsm_handle handle, Size request_size,
* returning.
*/
flags = O_RDWR | (op == DSM_OP_CREATE ? O_CREAT | O_EXCL : 0);
- if ((fd = shm_open(name, flags, 0600)) == -1)
+ if ((fd = shm_open(name, flags, PG_FILE_MODE_OWNER)) == -1)
{
if (errno != EEXIST)
ereport(elevel,
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index fc0a9c0756..53f7c1e77e 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -137,6 +137,10 @@ proc_exit(int code)
else
snprintf(gprofDirName, 32, "gprof/%d", (int) getpid());
+ /*
+ * Use mkdir() instead of MakePGDirectory() since we aren't making a
+ * PG directory here.
+ */
mkdir("gprof", S_IRWXU | S_IRWXG | S_IRWXO);
mkdir(gprofDirName, S_IRWXU | S_IRWXG | S_IRWXO);
chdir(gprofDirName);
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 87ed7d3f71..f8f08f3f88 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -32,6 +32,7 @@
#include "access/htup_details.h"
#include "catalog/pg_authid.h"
+#include "common/file_perm.h"
#include "libpq/libpq.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
@@ -831,7 +832,7 @@ CreateLockFile(const char *filename, bool amPostmaster,
* Think not to make the file protection weaker than 0600. See
* comments below.
*/
- fd = open(filename, O_RDWR | O_CREAT | O_EXCL, 0600);
+ fd = open(filename, O_RDWR | O_CREAT | O_EXCL, pg_file_create_mode);
if (fd >= 0)
break; /* Success; exit the retry loop */
@@ -848,7 +849,7 @@ CreateLockFile(const char *filename, bool amPostmaster,
* Read the file to get the old owner's PID. Note race condition
* here: file might have been deleted since we tried to create it.
*/
- fd = open(filename, O_RDONLY, 0600);
+ fd = open(filename, O_RDONLY, pg_file_create_mode);
if (fd < 0)
{
if (errno == ENOENT)
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 78990f5a27..3765548a24 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -64,6 +64,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_class.h"
#include "catalog/pg_collation.h"
+#include "common/file_perm.h"
#include "common/file_utils.h"
#include "common/restricted_token.h"
#include "common/username.h"
@@ -1170,7 +1171,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, pg_file_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1190,7 +1191,7 @@ setup_config(void)
sprintf(path, "%s/postgresql.auto.conf", pg_data);
writefile(path, autoconflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, pg_file_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1277,7 +1278,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_hba.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, pg_file_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1293,7 +1294,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_ident.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, pg_file_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -2692,7 +2693,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (pg_mkdir_p(pg_data, S_IRWXU) != 0)
+ if (pg_mkdir_p(pg_data, pg_dir_create_mode) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2710,7 +2711,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (chmod(pg_data, S_IRWXU) != 0)
+ if (chmod(pg_data, pg_dir_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2778,7 +2779,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (pg_mkdir_p(xlog_dir, S_IRWXU) != 0)
+ if (pg_mkdir_p(xlog_dir, pg_dir_create_mode) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2796,7 +2797,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (chmod(xlog_dir, S_IRWXU) != 0)
+ if (chmod(xlog_dir, pg_dir_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2846,7 +2847,7 @@ create_xlog_or_symlink(void)
else
{
/* Without -X option, just make the subdirectory normally */
- if (mkdir(subdirloc, S_IRWXU) < 0)
+ if (mkdir(subdirloc, pg_dir_create_mode) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, subdirloc, strerror(errno));
@@ -2882,7 +2883,8 @@ initialize_data_directory(void)
setup_signals();
- umask(S_IRWXG | S_IRWXO);
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_OWNER);
create_data_directory();
@@ -2902,7 +2904,7 @@ initialize_data_directory(void)
* The parent directory already exists, so we only need mkdir() not
* pg_mkdir_p() here, which avoids some failure modes; cf bug #13853.
*/
- if (mkdir(path, S_IRWXU) < 0)
+ if (mkdir(path, pg_dir_create_mode) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, path, strerror(errno));
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index c0cfa6e92c..9dfb2ed96f 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 15;
+use Test::More tests => 16;
my $tempdir = TestLib::tempdir;
my $xlogdir = "$tempdir/pgxlog";
@@ -45,6 +45,15 @@ mkdir $datadir;
command_ok([ 'initdb', '-N', '-T', 'german', '-X', $xlogdir, $datadir ],
'successful creation');
+
+ # Permissions on PGDATA should be default
+ SKIP:
+ {
+ skip "unix-style permissions not supported on Windows", 1 if ($windows_os);
+
+ ok(check_mode_recursive($datadir, 0700, 0600),
+ "check PGDATA permissions");
+ }
}
command_ok([ 'initdb', '-S', $datadir ], 'sync only');
command_fails([ 'initdb', $datadir ], 'existing data directory');
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 8610fe8a1a..32b41e313c 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -27,6 +27,7 @@
#endif
#include "access/xlog_internal.h"
+#include "common/file_perm.h"
#include "common/file_utils.h"
#include "common/string.h"
#include "fe_utils/string_utils.h"
@@ -629,7 +630,7 @@ StartLogStreamer(char *startpos, uint32 timeline, char *sysidentifier)
PQserverVersion(conn) < MINIMUM_VERSION_FOR_PG_WAL ?
"pg_xlog" : "pg_wal");
- if (pg_mkdir_p(statusdir, S_IRWXU) != 0 && errno != EEXIST)
+ if (pg_mkdir_p(statusdir, pg_dir_create_mode) != 0 && errno != EEXIST)
{
fprintf(stderr,
_("%s: could not create directory \"%s\": %s\n"),
@@ -685,7 +686,7 @@ verify_dir_is_empty_or_create(char *dirname, bool *created, bool *found)
/*
* Does not exist, so create
*/
- if (pg_mkdir_p(dirname, S_IRWXU) == -1)
+ if (pg_mkdir_p(dirname, pg_dir_create_mode) == -1)
{
fprintf(stderr,
_("%s: could not create directory \"%s\": %s\n"),
@@ -1129,7 +1130,7 @@ ReceiveTarFile(PGconn *conn, PGresult *res, int rownum)
tarCreateHeader(header, "recovery.conf", NULL,
recoveryconfcontents->len,
- 0600, 04000, 02000,
+ pg_file_create_mode, 04000, 02000,
time(NULL));
padding = ((recoveryconfcontents->len + 511) & ~511) - recoveryconfcontents->len;
@@ -1441,7 +1442,7 @@ ReceiveAndUnpackTarFile(PGconn *conn, PGresult *res, int rownum)
* Directory
*/
filename[strlen(filename) - 1] = '\0'; /* Remove trailing slash */
- if (mkdir(filename, S_IRWXU) != 0)
+ if (mkdir(filename, pg_dir_create_mode) != 0)
{
/*
* When streaming WAL, pg_wal (or pg_xlog for pre-9.6
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index e2f1465472..ec54b555ed 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -5,7 +5,7 @@ use Config;
use File::Basename qw(basename dirname);
use PostgresNode;
use TestLib;
-use Test::More tests => 104;
+use Test::More tests => 105;
program_help_ok('pg_basebackup');
program_version_ok('pg_basebackup');
@@ -15,6 +15,9 @@ my $tempdir = TestLib::tempdir;
my $node = get_new_node('main');
+# Set umask so test directories and files are created with default permissions
+umask(0077);
+
# Initialize node without replication settings
$node->init(extra => [ '--data-checksums' ]);
$node->start;
@@ -93,6 +96,15 @@ $node->command_ok([ 'pg_basebackup', '-D', "$tempdir/backup", '-X', 'none' ],
'pg_basebackup runs');
ok(-f "$tempdir/backup/PG_VERSION", 'backup was created');
+# Permissions on backup should be default
+SKIP:
+{
+ skip "unix-style permissions not supported on Windows", 1 if ($windows_os);
+
+ ok(check_mode_recursive("$tempdir/backup", 0700, 0600),
+ "check backup dir permissions");
+}
+
# Only archive_status directory should be copied in pg_wal/.
is_deeply(
[ sort(slurp_dir("$tempdir/backup/pg_wal/")) ],
diff --git a/src/bin/pg_basebackup/t/020_pg_receivewal.pl b/src/bin/pg_basebackup/t/020_pg_receivewal.pl
index 64e3a35a87..19c106d9f5 100644
--- a/src/bin/pg_basebackup/t/020_pg_receivewal.pl
+++ b/src/bin/pg_basebackup/t/020_pg_receivewal.pl
@@ -2,12 +2,15 @@ use strict;
use warnings;
use TestLib;
use PostgresNode;
-use Test::More tests => 18;
+use Test::More tests => 19;
program_help_ok('pg_receivewal');
program_version_ok('pg_receivewal');
program_options_handling_ok('pg_receivewal');
+# Set umask so test directories and files are created with default permissions
+umask(0077);
+
my $primary = get_new_node('primary');
$primary->init(allows_streaming => 1);
$primary->start;
@@ -56,3 +59,12 @@ $primary->command_ok(
[ 'pg_receivewal', '-D', $stream_dir, '--verbose',
'--endpos', $nextlsn, '--synchronous', '--no-loop' ],
'streaming some WAL with --synchronous');
+
+# Permissions on WAL files should be default
+SKIP:
+{
+ skip "unix-style permissions not supported on Windows", 1 if ($windows_os);
+
+ ok(check_mode_recursive($stream_dir, 0700, 0600),
+ "check stream dir permissions");
+}
diff --git a/src/bin/pg_basebackup/walmethods.c b/src/bin/pg_basebackup/walmethods.c
index b4558a0184..267a40debb 100644
--- a/src/bin/pg_basebackup/walmethods.c
+++ b/src/bin/pg_basebackup/walmethods.c
@@ -22,6 +22,7 @@
#endif
#include "pgtar.h"
+#include "common/file_perm.h"
#include "common/file_utils.h"
#include "receivelog.h"
@@ -89,7 +90,7 @@ dir_open_for_write(const char *pathname, const char *temp_suffix, size_t pad_to_
* does not do any system calls to fsync() to make changes permanent on
* disk.
*/
- fd = open(tmppath, O_WRONLY | O_CREAT | PG_BINARY, S_IRUSR | S_IWUSR);
+ fd = open(tmppath, O_WRONLY | O_CREAT | PG_BINARY, pg_file_create_mode);
if (fd < 0)
return NULL;
@@ -534,7 +535,8 @@ tar_open_for_write(const char *pathname, const char *temp_suffix, size_t pad_to_
* We open the tar file only when we first try to write to it.
*/
tar_data->fd = open(tar_data->tarfilename,
- O_WRONLY | O_CREAT | PG_BINARY, S_IRUSR | S_IWUSR);
+ O_WRONLY | O_CREAT | PG_BINARY,
+ pg_file_create_mode);
if (tar_data->fd < 0)
return NULL;
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index 9bc830b085..5ede385e6a 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -25,6 +25,7 @@
#include "catalog/pg_control.h"
#include "common/controldata_utils.h"
+#include "common/file_perm.h"
#include "getopt_long.h"
#include "utils/pidfile.h"
@@ -2170,7 +2171,8 @@ main(int argc, char **argv)
*/
argv0 = argv[0];
- umask(S_IRWXG | S_IRWXO);
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_OWNER);
/* support --help and --version even if invoked as root */
if (argc > 1)
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl
index 5da4746cb4..067a084c87 100644
--- a/src/bin/pg_ctl/t/001_start_stop.pl
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -4,7 +4,7 @@ use warnings;
use Config;
use PostgresNode;
use TestLib;
-use Test::More tests => 19;
+use Test::More tests => 21;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -57,9 +57,23 @@ command_ok([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ], 'pg_ctl stop');
command_fails([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ],
'second pg_ctl stop fails');
+# Log file for default permission test. The permissions won't be checked on
+# Windows but we still want to do the restart test.
+my $logFileName = "$tempdir/data/perm-test-600.log";
+
command_ok(
- [ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
+ [ 'pg_ctl', 'restart', '-D', "$tempdir/data", '-l', $logFileName ],
'pg_ctl restart with server not running');
+
+# Permissions on log file should be default
+SKIP:
+{
+ skip "unix-style permissions not supported on Windows", 2 if ($windows_os);
+
+ ok(-f $logFileName);
+ ok(check_mode_recursive("$tempdir/data", 0700, 0600));
+}
+
command_ok([ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
'pg_ctl restart with server running');
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index eba7c5fdee..bdf71886ee 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -52,6 +52,7 @@
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
#include "common/fe_memutils.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "storage/large_object.h"
#include "pg_getopt.h"
@@ -967,7 +968,7 @@ RewriteControlFile(void)
fd = open(XLOG_CONTROL_FILE,
O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ pg_file_create_mode);
if (fd < 0)
{
fprintf(stderr, _("%s: could not create pg_control file: %s\n"),
@@ -1249,7 +1250,7 @@ WriteEmptyXLOG(void)
unlink(path);
fd = open(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ pg_file_create_mode);
if (fd < 0)
{
fprintf(stderr, _("%s: could not open file \"%s\": %s\n"),
diff --git a/src/bin/pg_resetwal/t/001_basic.pl b/src/bin/pg_resetwal/t/001_basic.pl
index 1b157cb555..0d6ab20073 100644
--- a/src/bin/pg_resetwal/t/001_basic.pl
+++ b/src/bin/pg_resetwal/t/001_basic.pl
@@ -3,7 +3,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 11;
+use Test::More tests => 12;
program_help_ok('pg_resetwal');
program_version_ok('pg_resetwal');
@@ -15,3 +15,13 @@ $node->init;
command_like([ 'pg_resetwal', '-n', $node->data_dir ],
qr/checkpoint/,
'pg_resetwal -n produces output');
+
+
+# Permissions on PGDATA should be default
+SKIP:
+{
+ skip "unix-style permissions not supported on Windows", 1 if ($windows_os);
+
+ ok(check_mode_recursive($node->data_dir, 0700, 0600),
+ 'check PGDATA permissions');
+}
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index 00b5b42dd7..7b632c7dcd 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -237,6 +237,10 @@ sub run_pg_rewind
"$tmp_folder/master-postgresql.conf.tmp",
"$master_pgdata/postgresql.conf");
+ chmod(0600, "$master_pgdata/postgresql.conf")
+ or BAIL_OUT(
+ "unable to set permissions for $master_pgdata/postgresql.conf");
+
# Plug-in rewound node to the now-promoted standby node
my $port_standby = $node_standby->port;
$node_master->append_conf(
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index f491ed7f5c..94bcc13ae8 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -18,6 +18,7 @@
#include <fcntl.h>
#include <unistd.h>
+#include "common/file_perm.h"
#include "file_ops.h"
#include "filemap.h"
#include "logging.h"
@@ -57,7 +58,7 @@ open_target_file(const char *path, bool trunc)
mode = O_WRONLY | O_CREAT | PG_BINARY;
if (trunc)
mode |= O_TRUNC;
- dstfd = open(dstpath, mode, 0600);
+ dstfd = open(dstpath, mode, pg_file_create_mode);
if (dstfd < 0)
pg_fatal("could not open target file \"%s\": %s\n",
dstpath, strerror(errno));
@@ -198,7 +199,7 @@ truncate_target_file(const char *path, off_t newsize)
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
- fd = open(dstpath, O_WRONLY, 0);
+ fd = open(dstpath, O_WRONLY, pg_file_create_mode);
if (fd < 0)
pg_fatal("could not open file \"%s\" for truncation: %s\n",
dstpath, strerror(errno));
@@ -219,7 +220,7 @@ create_target_dir(const char *path)
return;
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
- if (mkdir(dstpath, S_IRWXU) != 0)
+ if (mkdir(dstpath, pg_dir_create_mode) != 0)
pg_fatal("could not create directory \"%s\": %s\n",
dstpath, strerror(errno));
}
diff --git a/src/bin/pg_rewind/t/001_basic.pl b/src/bin/pg_rewind/t/001_basic.pl
index 736f34eae3..1b0f823b0c 100644
--- a/src/bin/pg_rewind/t/001_basic.pl
+++ b/src/bin/pg_rewind/t/001_basic.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 8;
+use Test::More tests => 10;
use RewindTest;
@@ -86,6 +86,15 @@ in master, before promotion
),
'tail-copy');
+ # Permissions on PGDATA should be default
+ SKIP:
+ {
+ skip "unix-style permissions not supported on Windows", 1 if ($windows_os);
+
+ ok(check_mode_recursive($node_master->data_dir(), 0700, 0600),
+ 'check PGDATA permissions');
+ }
+
RewindTest::clean_rewind_test();
}
diff --git a/src/bin/pg_upgrade/file.c b/src/bin/pg_upgrade/file.c
index f38bfacf02..f68211aa20 100644
--- a/src/bin/pg_upgrade/file.c
+++ b/src/bin/pg_upgrade/file.c
@@ -10,6 +10,7 @@
#include "postgres_fe.h"
#include "access/visibilitymap.h"
+#include "common/file_perm.h"
#include "pg_upgrade.h"
#include "storage/bufpage.h"
#include "storage/checksum.h"
@@ -44,7 +45,7 @@ copyFile(const char *src, const char *dst,
schemaName, relName, src, strerror(errno));
if ((dest_fd = open(dst, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR)) < 0)
+ pg_file_create_mode)) < 0)
pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
schemaName, relName, dst, strerror(errno));
@@ -151,7 +152,7 @@ rewriteVisibilityMap(const char *fromfile, const char *tofile,
schemaName, relName, fromfile, strerror(errno));
if ((dst_fd = open(tofile, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR)) < 0)
+ pg_file_create_mode)) < 0)
pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
schemaName, relName, tofile, strerror(errno));
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index d12412799f..1d35188143 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -38,6 +38,7 @@
#include "pg_upgrade.h"
#include "catalog/pg_class.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "fe_utils/string_utils.h"
@@ -79,7 +80,7 @@ main(int argc, char **argv)
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_upgrade"));
/* Ensure that all files created by pg_upgrade are non-world-readable */
- umask(S_IRWXG | S_IRWXO);
+ umask(PG_MODE_MASK_OWNER);
parseCommandLine(argc, argv);
diff --git a/src/bin/pg_upgrade/test.sh b/src/bin/pg_upgrade/test.sh
index 39983abea1..574639d47e 100644
--- a/src/bin/pg_upgrade/test.sh
+++ b/src/bin/pg_upgrade/test.sh
@@ -230,6 +230,17 @@ standard_initdb 'initdb'
pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "${PGDATA}" -b "$oldbindir" -B "$bindir" -p "$PGPORT" -P "$PGPORT"
+# make sure all directories and files have correct permissions
+if [ $(find ${PGDATA} -type f ! -perm 600 | wc -l) -ne 0 ]; then
+ echo "files in PGDATA with permission != 600";
+ exit 1;
+fi
+
+if [ $(find ${PGDATA} -type d ! -perm 700 | wc -l) -ne 0 ]; then
+ echo "directories in PGDATA with permission != 700";
+ exit 1;
+fi
+
pg_ctl start -l "$logdir/postmaster2.log" -o "$POSTMASTER_OPTS" -w
case $testhost in
diff --git a/src/common/Makefile b/src/common/Makefile
index 873fbb6438..e9e75867f3 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -40,8 +40,8 @@ override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
override CPPFLAGS := -DFRONTEND $(CPPFLAGS)
LIBS += $(PTHREAD_LIBS)
-OBJS_COMMON = base64.o config_info.o controldata_utils.o exec.o ip.o \
- keywords.o md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
+OBJS_COMMON = base64.o config_info.o controldata_utils.o exec.o file_perm.o \
+ ip.o keywords.o md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
rmtree.o saslprep.o scram-common.o string.o unicode_norm.o \
username.o wait_error.o
diff --git a/src/common/file_perm.c b/src/common/file_perm.c
new file mode 100644
index 0000000000..fdfbb9a44c
--- /dev/null
+++ b/src/common/file_perm.c
@@ -0,0 +1,19 @@
+/*-------------------------------------------------------------------------
+ *
+ * File and directory permission routines
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/file_perm.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include <sys/stat.h>
+
+#include "common/file_perm.h"
+
+/* Modes for creating directories and files in the data directory */
+int pg_dir_create_mode = PG_DIR_MODE_OWNER;
+int pg_file_create_mode = PG_FILE_MODE_OWNER;
diff --git a/src/include/common/file_perm.h b/src/include/common/file_perm.h
new file mode 100644
index 0000000000..37631a7191
--- /dev/null
+++ b/src/include/common/file_perm.h
@@ -0,0 +1,34 @@
+/*-------------------------------------------------------------------------
+ *
+ * File and directory permission constants
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/file_perm.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FILE_PERM_H
+#define FILE_PERM_H
+
+/*
+ * Mode mask for data directory permissions that only allows the owner to
+ * read/write directories and files.
+ *
+ * This is the default.
+ */
+#define PG_MODE_MASK_OWNER (S_IRWXG | S_IRWXO)
+
+/* Default mode for creating directories */
+#define PG_DIR_MODE_OWNER S_IRWXU
+
+/* Default mode for creating files */
+#define PG_FILE_MODE_OWNER (S_IRUSR | S_IWUSR)
+
+/* Modes for creating directories and files in the data directory */
+extern int pg_dir_create_mode;
+extern int pg_file_create_mode;
+
+#endif /* FILE_PERM_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index e49b42ce86..785d9058e9 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -112,6 +112,9 @@ extern int CloseTransientFile(int fd);
extern int BasicOpenFile(const char *fileName, int fileFlags);
extern int BasicOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode);
+ /* Make a directory with default permissions */
+extern int MakePGDirectory(const char *directoryName);
+
/* Miscellaneous support routines */
extern void InitFileAccess(void);
extern void set_max_safe_fds(void);
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 80188315f1..76e571b98c 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -484,6 +484,9 @@ sub append_conf
my $conffile = $self->data_dir . '/' . $filename;
TestLib::append_to_file($conffile, $str . "\n");
+
+ chmod(0600, $conffile)
+ or die("unable to set permissions for $conffile");
}
=pod
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index b6862688d4..93610e4bc4 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -13,8 +13,11 @@ use warnings;
use Config;
use Cwd;
use Exporter 'import';
+use Fcntl qw(:mode);
use File::Basename;
+use File::Find;
use File::Spec;
+use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
use SimpleTee;
@@ -27,6 +30,7 @@ our @EXPORT = qw(
slurp_dir
slurp_file
append_to_file
+ check_mode_recursive
check_pg_config
system_or_bail
system_log
@@ -240,6 +244,75 @@ sub append_to_file
close $fh;
}
+# Check that all file/dir modes in a directory match the expected values,
+# ignoring the mode of any specified files.
+sub check_mode_recursive
+{
+ my ($dir, $expected_dir_mode, $expected_file_mode, $ignore_list) = @_;
+
+ # Result defaults to true
+ my $result = 1;
+
+ find
+ (
+ {follow_fast => 1,
+ wanted =>
+ sub
+ {
+ my $file_stat = stat($File::Find::name);
+
+ # Is file in the ignore list?
+ foreach my $ignore ($ignore_list ? @{$ignore_list} : [])
+ {
+ if ("$dir/$ignore" eq $File::Find::name)
+ {
+ return;
+ }
+ }
+
+ defined($file_stat)
+ or die("unable to stat $File::Find::name");
+
+ my $file_mode = S_IMODE($file_stat->mode);
+
+ # Is this a file?
+ if (S_ISREG($file_stat->mode))
+ {
+ if ($file_mode != $expected_file_mode)
+ {
+ print(*STDERR,
+ sprintf("$File::Find::name mode must be %04o\n",
+ $expected_file_mode));
+
+ $result = 0;
+ return;
+ }
+ }
+ # Else a directory?
+ elsif (S_ISDIR($file_stat->mode))
+ {
+ if ($file_mode != $expected_dir_mode)
+ {
+ print(*STDERR,
+ sprintf("$File::Find::name mode must be %04o\n",
+ $expected_dir_mode));
+
+ $result = 0;
+ return;
+ }
+ }
+ # Else something we can't handle
+ else
+ {
+ die "unknown file type for $File::Find::name";
+ }
+ }},
+ $dir
+ );
+
+ return $result;
+}
+
# Check presence of a given regexp within pg_config.h for the installation
# where tests are running, returning a match status result depending on
# that.
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 41d720880a..1d3ed6b0b1 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -111,8 +111,8 @@ sub mkvcbuild
}
our @pgcommonallfiles = qw(
- base64.c config_info.c controldata_utils.c exec.c ip.c keywords.c
- md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
+ base64.c config_info.c controldata_utils.c exec.c file_perm.c ip.c
+ keywords.c md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
saslprep.c scram-common.c string.c unicode_norm.c username.c
wait_error.c);
--
2.14.1
From 0ef15de3c25b348a71345d7adc65f0e3df258df9 Mon Sep 17 00:00:00 2001
From: David Steele <david@pgmasters.net>
Date: Fri, 6 Apr 2018 18:10:45 -0400
Subject: [PATCH 2/2] 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
---
doc/src/sgml/ref/initdb.sgml | 19 +++++
doc/src/sgml/ref/pg_basebackup.sgml | 6 ++
doc/src/sgml/ref/pg_receivewal.sgml | 6 ++
doc/src/sgml/ref/pg_recvlogical.sgml | 11 +++
doc/src/sgml/runtime.sgml | 14 +++-
src/backend/bootstrap/bootstrap.c | 12 +--
src/backend/postmaster/postmaster.c | 84 +++----------------
src/backend/tcop/postgres.c | 3 +-
src/backend/utils/init/globals.c | 9 +++
src/backend/utils/init/miscinit.c | 115 ++++++++++++++++++++++++---
src/backend/utils/misc/guc.c | 25 ++++++
src/bin/initdb/initdb.c | 24 +++++-
src/bin/initdb/t/001_initdb.pl | 20 ++++-
src/bin/pg_basebackup/pg_basebackup.c | 22 +++--
src/bin/pg_basebackup/pg_receivewal.c | 7 ++
src/bin/pg_basebackup/pg_recvlogical.c | 7 ++
src/bin/pg_basebackup/streamutil.c | 76 ++++++++++++++++++
src/bin/pg_basebackup/t/010_pg_basebackup.pl | 26 ++++--
src/bin/pg_ctl/pg_ctl.c | 12 ++-
src/bin/pg_ctl/t/001_start_stop.pl | 25 +++++-
src/bin/pg_resetwal/pg_resetwal.c | 10 +++
src/bin/pg_rewind/RewindTest.pm | 9 ++-
src/bin/pg_rewind/pg_rewind.c | 11 +++
src/bin/pg_rewind/t/002_databases.pl | 13 ++-
src/bin/pg_upgrade/pg_upgrade.c | 12 ++-
src/bin/pg_upgrade/test.sh | 16 ++--
src/common/file_perm.c | 72 ++++++++++++++++-
src/include/common/file_perm.h | 24 +++++-
src/include/miscadmin.h | 2 +
src/test/perl/PostgresNode.pm | 27 ++++++-
src/test/perl/TestLib.pm | 25 ++++++
31 files changed, 612 insertions(+), 132 deletions(-)
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 826dd91f72..f32c600834 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -76,6 +76,14 @@ PostgreSQL documentation
to do so.)
</para>
+ <para>
+ For security reasons the new cluster created by <command>initdb</command>
+ will only be accessible by the cluster owner by default. The
+ <option>--allow-group-access</option> option allows any user in the same
+ group as the cluster owner to read files in the cluster. This is useful
+ for performing backups as a non-privileged user.
+ </para>
+
<para>
<command>initdb</command> initializes the database cluster's default
locale and character set encoding. The character set encoding,
@@ -301,6 +309,17 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-g</option></term>
+ <term><option>--allow-group-access</option></term>
+ <listitem>
+ <para>
+ Allows users in the same group as the cluster owner to read all cluster
+ files created by <command>initdb</command>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-W</option></term>
<term><option>--pwprompt</option></term>
diff --git a/doc/src/sgml/ref/pg_basebackup.sgml b/doc/src/sgml/ref/pg_basebackup.sgml
index 95045669c9..fc1edf4864 100644
--- a/doc/src/sgml/ref/pg_basebackup.sgml
+++ b/doc/src/sgml/ref/pg_basebackup.sgml
@@ -737,6 +737,12 @@ PostgreSQL documentation
or later.
</para>
+ <para>
+ <application>pg_basebackup</application> will preserve group permissions in
+ both the <literal>plain</literal> and <literal>tar</literal> formats if group
+ permissions are enabled on the source cluster.
+ </para>
+
</refsect1>
<refsect1>
diff --git a/doc/src/sgml/ref/pg_receivewal.sgml b/doc/src/sgml/ref/pg_receivewal.sgml
index e3f2ce1fcb..a18ddd4bff 100644
--- a/doc/src/sgml/ref/pg_receivewal.sgml
+++ b/doc/src/sgml/ref/pg_receivewal.sgml
@@ -425,6 +425,12 @@ PostgreSQL documentation
not keep up with fetching the WAL data.
</para>
+ <para>
+ <application>pg_receivewal</application> will preserve group permissions on
+ the received WAL files if group permissions are enabled on the source
+ cluster.
+ </para>
+
</refsect1>
<refsect1>
diff --git a/doc/src/sgml/ref/pg_recvlogical.sgml b/doc/src/sgml/ref/pg_recvlogical.sgml
index a79ca20084..141c5cddce 100644
--- a/doc/src/sgml/ref/pg_recvlogical.sgml
+++ b/doc/src/sgml/ref/pg_recvlogical.sgml
@@ -399,6 +399,17 @@ PostgreSQL documentation
</para>
</refsect1>
+ <refsect1>
+ <title>Notes</title>
+
+ <para>
+ <application>pg_recvlogical</application> will preserve group permissions on
+ the received WAL files if group permissions are enabled on the source
+ cluster.
+ </para>
+
+ </refsect1>
+
<refsect1>
<title>Examples</title>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 587b430527..d71cf831b1 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -137,7 +137,10 @@ postgres$ <userinput>initdb -D /usr/local/pgsql/data</userinput>
database, it is essential that it be secured from unauthorized
access. <command>initdb</command> therefore revokes access
permissions from everyone but the
- <productname>PostgreSQL</productname> user.
+ <productname>PostgreSQL</productname> user, and optionally, group.
+ Group access, when enabled, is read-only. This allows an unprivileged
+ user in the same group as the cluster owner to take a backup of the
+ cluster data or perform other operations that only require read access.
</para>
<para>
@@ -2194,6 +2197,15 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
member of the group that has access to those certificate and key files.
</para>
+ <para>
+ If the data directory allows group read access then certificate files may
+ need to be located outside of the data directory in order to conform to the
+ security requirements outlined above. Generally, group access is enabled
+ to allow an unprivileged user to backup the database, and in that case the
+ backup software will not be able to read the certificate files and will
+ likely error.
+ </para>
+
<para>
If the private key is protected with a passphrase, the
server will prompt for the passphrase and will not start until it has
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 1430894ad2..d6da743ddd 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 10afecffb3..d6eab38c7c 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 (onwer-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,14 @@ getInstallationPaths(const char *argv0)
*/
}
-
/*
- * Validate the proposed data directory
+ * Check that pg_control exists in the correct location in the 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 7bdecc32ec..5095a4f686 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 c1f0441b08..0a3163398f 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 f8f08f3f88..33ebcbb54c 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -88,6 +88,100 @@ SetDatabasePath(const char *path)
DatabasePath = MemoryContextStrdup(TopMemoryContext, 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 71c2b4eff1..8441837e0f 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"
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 3765548a24..ba7ced343f 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -1168,6 +1168,19 @@ setup_config(void)
"password_encryption = scram-sha-256");
}
+ /*
+ * If group access has been enabled for the cluster then it makes sense to
+ * ensure that the log files also allow group access. Otherwise a backup
+ * from a user in the group would fail if the log files were not
+ * relocated.
+ */
+ if (pg_dir_create_mode == PG_DIR_MODE_GROUP)
+ {
+ conflines = replace_token(conflines,
+ "#log_file_mode = 0600",
+ "log_file_mode = 0640");
+ }
+
snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
writefile(path, conflines);
@@ -2312,6 +2325,7 @@ usage(const char *progname)
printf(_(" --auth-local=METHOD default authentication method for local-socket connections\n"));
printf(_(" [-D, --pgdata=]DATADIR location for this database cluster\n"));
printf(_(" -E, --encoding=ENCODING set default encoding for new databases\n"));
+ printf(_(" -g, --allow-group-access allow group read/execute on data directory\n"));
printf(_(" --locale=LOCALE set default locale for new databases\n"));
printf(_(" --lc-collate=, --lc-ctype=, --lc-messages=LOCALE\n"
" --lc-monetary=, --lc-numeric=, --lc-time=LOCALE\n"
@@ -2883,8 +2897,8 @@ initialize_data_directory(void)
setup_signals();
- /* Set dir/file mode mask */
- umask(PG_MODE_MASK_OWNER);
+ /* Set mask based on requested PGDATA permissions */
+ umask(pg_mode_mask);
create_data_directory();
@@ -3018,6 +3032,7 @@ main(int argc, char *argv[])
{"waldir", required_argument, NULL, 'X'},
{"wal-segsize", required_argument, NULL, 12},
{"data-checksums", no_argument, NULL, 'k'},
+ {"allow-group-access", no_argument, NULL, 'g'},
{NULL, 0, NULL, 0}
};
@@ -3059,7 +3074,7 @@ main(int argc, char *argv[])
/* process command-line options */
- while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:", long_options, &option_index)) != -1)
+ while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:g", long_options, &option_index)) != -1)
{
switch (c)
{
@@ -3153,6 +3168,9 @@ main(int argc, char *argv[])
case 12:
str_wal_segment_size_mb = pg_strdup(optarg);
break;
+ case 'g':
+ SetDataDirectoryCreatePerm(PG_DIR_MODE_GROUP);
+ break;
default:
/* getopt_long already emitted a complaint */
fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index 9dfb2ed96f..8609d2ecbc 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -4,9 +4,11 @@
use strict;
use warnings;
+use Fcntl ':mode';
+use File::stat qw{lstat};
use PostgresNode;
use TestLib;
-use Test::More tests => 16;
+use Test::More tests => 18;
my $tempdir = TestLib::tempdir;
my $xlogdir = "$tempdir/pgxlog";
@@ -57,3 +59,19 @@ mkdir $datadir;
}
command_ok([ 'initdb', '-S', $datadir ], 'sync only');
command_fails([ 'initdb', $datadir ], 'existing data directory');
+
+# Check group access on PGDATA
+SKIP:
+{
+ skip "unix-style permissions not supported on Windows", 2 if ($windows_os);
+
+ # Init a new db with group access
+ my $datadir_group = "$tempdir/data_group";
+
+ command_ok(
+ [ 'initdb', '-g', $datadir_group ],
+ 'successful creation with group access');
+
+ ok(check_mode_recursive($datadir_group, 0750, 0640),
+ 'check PGDATA permissions');
+}
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 32b41e313c..8cbc902ca3 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -2470,14 +2470,6 @@ main(int argc, char **argv)
}
#endif
- /*
- * Verify that the target directory exists, or create it. For plaintext
- * backups, always require the directory. For tar backups, require it
- * unless we are writing to stdout.
- */
- if (format == 'p' || strcmp(basedir, "-") != 0)
- verify_dir_is_empty_or_create(basedir, &made_new_pgdata, &found_existing_pgdata);
-
/* connection in replication mode to server */
conn = GetConnection();
if (!conn)
@@ -2486,6 +2478,20 @@ main(int argc, char **argv)
exit(1);
}
+ /*
+ * Set umask so that directories/files are created with the same
+ * permissions as directories/files in the source data directory.
+ */
+ umask(pg_mode_mask);
+
+ /*
+ * Verify that the target directory exists, or create it. For plaintext
+ * backups, always require the directory. For tar backups, require it
+ * unless we are writing to stdout.
+ */
+ if (format == 'p' || strcmp(basedir, "-") != 0)
+ verify_dir_is_empty_or_create(basedir, &made_new_pgdata, &found_existing_pgdata);
+
/* determine remote server's xlog segment size */
if (!RetrieveWalSegSize(conn))
disconnect_and_exit(1);
diff --git a/src/bin/pg_basebackup/pg_receivewal.c b/src/bin/pg_basebackup/pg_receivewal.c
index b82e073b86..62b6c686f3 100644
--- a/src/bin/pg_basebackup/pg_receivewal.c
+++ b/src/bin/pg_basebackup/pg_receivewal.c
@@ -19,6 +19,7 @@
#include <sys/stat.h>
#include <unistd.h>
+#include "common/file_perm.h"
#include "libpq-fe.h"
#include "access/xlog_internal.h"
#include "getopt_long.h"
@@ -703,6 +704,12 @@ main(int argc, char **argv)
if (!RunIdentifySystem(conn, NULL, NULL, NULL, &db_name))
disconnect_and_exit(1);
+ /*
+ * Set umask so that directories/files are created with the same
+ * permissions as directories/files in the source data directory.
+ */
+ umask(pg_mode_mask);
+
/* determine remote server's xlog segment size */
if (!RetrieveWalSegSize(conn))
disconnect_and_exit(1);
diff --git a/src/bin/pg_basebackup/pg_recvlogical.c b/src/bin/pg_basebackup/pg_recvlogical.c
index 0866395944..6c272b8f78 100644
--- a/src/bin/pg_basebackup/pg_recvlogical.c
+++ b/src/bin/pg_basebackup/pg_recvlogical.c
@@ -23,6 +23,7 @@
#include "streamutil.h"
#include "access/xlog_internal.h"
+#include "common/file_perm.h"
#include "common/fe_memutils.h"
#include "getopt_long.h"
#include "libpq-fe.h"
@@ -959,6 +960,12 @@ main(int argc, char **argv)
disconnect_and_exit(1);
}
+ /*
+ * Set umask so that directories/files are created with the same
+ * permissions as directories/files in the source data directory.
+ */
+ umask(pg_mode_mask);
+
/* Drop a replication slot. */
if (do_drop_slot)
{
diff --git a/src/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c
index 1438f368ed..c46d670128 100644
--- a/src/bin/pg_basebackup/streamutil.c
+++ b/src/bin/pg_basebackup/streamutil.c
@@ -23,6 +23,7 @@
#include "access/xlog_internal.h"
#include "common/fe_memutils.h"
+#include "common/file_perm.h"
#include "datatype/timestamp.h"
#include "fe_utils/connect.h"
#include "port/pg_bswap.h"
@@ -32,9 +33,16 @@
uint32 WalSegSz;
+static bool RetrieveDataDirCreatePerm(PGconn *conn);
+
/* SHOW command for replication connection was introduced in version 10 */
#define MINIMUM_VERSION_FOR_SHOW_CMD 100000
+/*
+ * Group access is supported from version 11.
+ */
+#define MINIMUM_VERSION_FOR_GROUP_ACCESS 110000
+
const char *progname;
char *connection_string = NULL;
char *dbhost = NULL;
@@ -254,6 +262,16 @@ GetConnection(void)
exit(1);
}
+ /*
+ * Retrieve the source data directory mode and use it to construct a umask
+ * for creating directories and files.
+ */
+ if (!RetrieveDataDirCreatePerm(tmpconn))
+ {
+ PQfinish(tmpconn);
+ exit(1);
+ }
+
return tmpconn;
}
@@ -327,6 +345,64 @@ RetrieveWalSegSize(PGconn *conn)
return true;
}
+/*
+ * RetrieveDataDirCreatePerm
+ *
+ * This function is used to determine what the privileges are on the server's
+ * PG data directory and, based on that, set what the permissions will be for
+ * directories and files we create.
+ *
+ * PG11 added support for (optionally) group read/execute rights to be set on
+ * the data directory. Prior to PG11, only the owner was allowed to have rights
+ * on the data directory.
+ */
+static bool
+RetrieveDataDirCreatePerm(PGconn *conn)
+{
+ PGresult *res;
+ int data_directory_mode;
+
+ /* check connection existence */
+ Assert(conn != NULL);
+
+ /* for previous versions leave the default group access */
+ if (PQserverVersion(conn) < MINIMUM_VERSION_FOR_GROUP_ACCESS)
+ return true;
+
+ res = PQexec(conn, "SHOW data_directory_mode");
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ fprintf(stderr, _("%s: could not send replication command \"%s\": %s\n"),
+ progname, "SHOW data_directory_mode", PQerrorMessage(conn));
+
+ PQclear(res);
+ return false;
+ }
+ if (PQntuples(res) != 1 || PQnfields(res) < 1)
+ {
+ fprintf(stderr,
+ _("%s: could not fetch group access flag: got %d rows and %d fields, expected %d rows and %d or more fields\n"),
+ progname, PQntuples(res), PQnfields(res), 1, 1);
+
+ PQclear(res);
+ return false;
+ }
+
+ if (sscanf(PQgetvalue(res, 0, 0), "%o", &data_directory_mode) != 1)
+ {
+ fprintf(stderr, _("%s: group access flag could not be parsed: %s\n"),
+ progname, PQgetvalue(res, 0, 0));
+
+ PQclear(res);
+ return false;
+ }
+
+ SetDataDirectoryCreatePerm(data_directory_mode);
+
+ PQclear(res);
+ return true;
+}
+
/*
* Run IDENTIFY_SYSTEM through a given connection and give back to caller
* some result information if requested:
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index ec54b555ed..a40e9c2d51 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -5,7 +5,7 @@ use Config;
use File::Basename qw(basename dirname);
use PostgresNode;
use TestLib;
-use Test::More tests => 105;
+use Test::More tests => 106;
program_help_ok('pg_basebackup');
program_version_ok('pg_basebackup');
@@ -43,11 +43,6 @@ $node->command_fails(
ok(!-d "$tempdir/backup", 'backup directory was cleaned up');
-$node->command_fails([ 'pg_basebackup', '-D', "$tempdir/backup", '-n' ],
- 'failing run with no-clean option');
-
-ok(-d "$tempdir/backup", 'backup directory was created and left behind');
-
open my $conf, '>>', "$pgdata/postgresql.conf";
print $conf "max_replication_slots = 10\n";
print $conf "max_wal_senders = 10\n";
@@ -162,6 +157,13 @@ ok(-f "$tempdir/tarbackup/base.tar", 'backup tar was created');
$node->command_fails(
[ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T=/foo" ],
'-T with empty old directory fails');
+
+$node->command_fails(
+ [ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T=/foo", "-n" ],
+ 'failing run with no-clean option');
+
+ok(-d "$tempdir/backup", 'backup directory was created and left behind');
+
$node->command_fails(
[ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T/foo=" ],
'-T with empty new directory fails');
@@ -195,11 +197,17 @@ unlink "$pgdata/$superlongname";
# skip on Windows.
SKIP:
{
- skip "symlinks not supported on Windows", 17 if ($windows_os);
+ skip "symlinks not supported on Windows", 18 if ($windows_os);
# Move pg_replslot out of $pgdata and create a symlink to it.
$node->stop;
+ # Set umask so test directories and files are created with group permissions
+ umask(0027);
+
+ # Enable group permissions on PGDATA
+ chmod_recursive("$pgdata", 0750, 0640);
+
rename("$pgdata/pg_replslot", "$tempdir/pg_replslot")
or BAIL_OUT "could not move $pgdata/pg_replslot";
symlink("$tempdir/pg_replslot", "$pgdata/pg_replslot")
@@ -269,6 +277,10 @@ SKIP:
"tablespace symlink was updated");
closedir $dh;
+ # Group access should be enabled on all backup files
+ ok(check_mode_recursive("$tempdir/backup1", 0750, 0640),
+ "check backup dir permissions");
+
# Unlogged relation forks other than init should not be copied
my ($tblspc1UnloggedBackupPath) = $tblspc1UnloggedPath =~ /[^\/]*\/[^\/]*\/[^\/]*$/g;
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index 5ede385e6a..143021de05 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -2171,7 +2171,7 @@ main(int argc, char **argv)
*/
argv0 = argv[0];
- /* Set dir/file mode mask */
+ /* Set restrictive mode mask until PGDATA permissions are checked */
umask(PG_MODE_MASK_OWNER);
/* support --help and --version even if invoked as root */
@@ -2407,6 +2407,16 @@ main(int argc, char **argv)
snprintf(version_file, MAXPGPATH, "%s/PG_VERSION", pg_data);
snprintf(pid_file, MAXPGPATH, "%s/postmaster.pid", pg_data);
snprintf(backup_file, MAXPGPATH, "%s/backup_label", pg_data);
+
+ /*
+ * Set mask based on PGDATA permissions,
+ *
+ * Don't error here if the data directory cannot be stat'd. This is
+ * handled differently based on the command and we don't want to
+ * interfere with that logic.
+ */
+ if (GetDataDirectoryCreatePerm(pg_data))
+ umask(pg_mode_mask);
}
switch (ctl_command)
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl
index 067a084c87..2f9dfa7b81 100644
--- a/src/bin/pg_ctl/t/001_start_stop.pl
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -2,9 +2,11 @@ use strict;
use warnings;
use Config;
+use Fcntl ':mode';
+use File::stat qw{lstat};
use PostgresNode;
use TestLib;
-use Test::More tests => 21;
+use Test::More tests => 24;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -74,6 +76,27 @@ SKIP:
ok(check_mode_recursive("$tempdir/data", 0700, 0600));
}
+# Log file for group access test
+$logFileName = "$tempdir/data/perm-test-640.log";
+
+SKIP:
+{
+ skip "group access not supported on Windows", 3 if ($windows_os);
+
+ system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
+
+ # Change the data dir mode so log file will be created with group read
+ # privileges on the next start
+ chmod_recursive("$tempdir/data", 0750, 0640);
+
+ command_ok(
+ [ 'pg_ctl', 'start', '-D', "$tempdir/data", '-l', $logFileName ],
+ 'start server to check group permissions');
+
+ ok(-f $logFileName);
+ ok(check_mode_recursive("$tempdir/data", 0750, 0640));
+}
+
command_ok([ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
'pg_ctl restart with server running');
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index bdf71886ee..8a0a805f1e 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -363,6 +363,16 @@ main(int argc, char *argv[])
exit(1);
}
+ /* Set mask based on PGDATA permissions */
+ if (!GetDataDirectoryCreatePerm(DataDir))
+ {
+ fprintf(stderr, _("%s: unable to read permissions from \"%s\"\n"),
+ progname, DataDir);
+ exit(1);
+ }
+
+ umask(pg_mode_mask);
+
/* Check that data directory matches our server version */
CheckDataVersion();
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index 7b632c7dcd..63d9bd517d 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -115,11 +115,13 @@ sub check_query
sub setup_cluster
{
- my $extra_name = shift;
+ my $extra_name = shift; # Used to differentiate clusters
+ my $extra = shift; # Extra params for initdb
# Initialize master, data checksums are mandatory
$node_master = get_new_node('master' . ($extra_name ? "_${extra_name}" : ''));
- $node_master->init(allows_streaming => 1);
+ $node_master->init(
+ allows_streaming => 1, extra => $extra);
# Set wal_keep_segments to prevent WAL segment recycling after enforced
# checkpoints in the tests.
$node_master->append_conf('postgresql.conf', qq(
@@ -237,7 +239,8 @@ sub run_pg_rewind
"$tmp_folder/master-postgresql.conf.tmp",
"$master_pgdata/postgresql.conf");
- chmod(0600, "$master_pgdata/postgresql.conf")
+ chmod($node_master->group_access() ? 0640 : 0600,
+ "$master_pgdata/postgresql.conf")
or BAIL_OUT(
"unable to set permissions for $master_pgdata/postgresql.conf");
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 72ab2f8d5e..b9ea6a4c21 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -24,6 +24,7 @@
#include "access/xlog_internal.h"
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "getopt_long.h"
#include "storage/bufpage.h"
@@ -185,6 +186,16 @@ main(int argc, char **argv)
exit(1);
}
+ /* Set mask based on PGDATA permissions */
+ if (!GetDataDirectoryCreatePerm(datadir_target))
+ {
+ fprintf(stderr, _("%s: unable to read permissions from \"%s\"\n"),
+ progname, datadir_target);
+ exit(1);
+ }
+
+ umask(pg_mode_mask);
+
/*
* Don't allow pg_rewind to be run as root, to avoid overwriting the
* ownership of files in the data directory. We need only check for root
diff --git a/src/bin/pg_rewind/t/002_databases.pl b/src/bin/pg_rewind/t/002_databases.pl
index 37cdd712f3..c364965d3a 100644
--- a/src/bin/pg_rewind/t/002_databases.pl
+++ b/src/bin/pg_rewind/t/002_databases.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 6;
use RewindTest;
@@ -9,7 +9,7 @@ sub run_test
{
my $test_mode = shift;
- RewindTest::setup_cluster($test_mode);
+ RewindTest::setup_cluster($test_mode, ['-g']);
RewindTest::start_master();
# Create a database in master.
@@ -42,6 +42,15 @@ template1
),
'database names');
+ # Permissions on PGDATA should have group permissions
+ SKIP:
+ {
+ skip "unix-style permissions not supported on Windows", 1 if ($windows_os);
+
+ ok(check_mode_recursive($node_master->data_dir(), 0750, 0640),
+ 'check PGDATA permissions');
+ }
+
RewindTest::clean_rewind_test();
}
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 1d35188143..cc8e8c94c5 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -79,7 +79,7 @@ main(int argc, char **argv)
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_upgrade"));
- /* Ensure that all files created by pg_upgrade are non-world-readable */
+ /* Set default restrictive mask until new cluster permissions are read */
umask(PG_MODE_MASK_OWNER);
parseCommandLine(argc, argv);
@@ -100,6 +100,16 @@ main(int argc, char **argv)
check_cluster_compatibility(live_check);
+ /* Set mask based on PGDATA permissions */
+ if (!GetDataDirectoryCreatePerm(new_cluster.pgdata))
+ {
+ pg_log(PG_FATAL, "unable to read permissions from \"%s\"\n",
+ new_cluster.pgdata);
+ exit(1);
+ }
+
+ umask(pg_mode_mask);
+
check_and_dump_old_cluster(live_check);
diff --git a/src/bin/pg_upgrade/test.sh b/src/bin/pg_upgrade/test.sh
index 574639d47e..24631a91c6 100644
--- a/src/bin/pg_upgrade/test.sh
+++ b/src/bin/pg_upgrade/test.sh
@@ -20,9 +20,9 @@ unset MAKELEVEL
# Run a given "initdb" binary and overlay the regression testing
# authentication configuration.
standard_initdb() {
- # To increase coverage of non-standard segment size without
- # increase test runtime, run these tests with a lower setting.
- "$1" -N --wal-segsize 1
+ # To increase coverage of non-standard segment size and group access
+ # without increasing test runtime, run these tests with a custom setting.
+ "$1" -N --wal-segsize 1 -g
if [ -n "$TEMP_CONFIG" -a -r "$TEMP_CONFIG" ]
then
cat "$TEMP_CONFIG" >> "$PGDATA/postgresql.conf"
@@ -230,14 +230,14 @@ standard_initdb 'initdb'
pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "${PGDATA}" -b "$oldbindir" -B "$bindir" -p "$PGPORT" -P "$PGPORT"
-# make sure all directories and files have correct permissions
-if [ $(find ${PGDATA} -type f ! -perm 600 | wc -l) -ne 0 ]; then
- echo "files in PGDATA with permission != 600";
+# make sure all directories and files have group permissions
+if [ $(find ${PGDATA} -type f ! -perm 640 | wc -l) -ne 0 ]; then
+ echo "files in PGDATA with permission != 640";
exit 1;
fi
-if [ $(find ${PGDATA} -type d ! -perm 700 | wc -l) -ne 0 ]; then
- echo "directories in PGDATA with permission != 700";
+if [ $(find ${PGDATA} -type d ! -perm 750 | wc -l) -ne 0 ]; then
+ echo "directories in PGDATA with permission != 750";
exit 1;
fi
diff --git a/src/common/file_perm.c b/src/common/file_perm.c
index fdfbb9a44c..1a4925fd9a 100644
--- a/src/common/file_perm.c
+++ b/src/common/file_perm.c
@@ -12,8 +12,76 @@
*/
#include <sys/stat.h>
+#include "c.h"
#include "common/file_perm.h"
/* Modes for creating directories and files in the data directory */
-int pg_dir_create_mode = PG_DIR_MODE_OWNER;
-int pg_file_create_mode = PG_FILE_MODE_OWNER;
+int pg_dir_create_mode = PG_DIR_MODE_OWNER;
+int pg_file_create_mode = PG_FILE_MODE_OWNER;
+
+/*
+ * Mode mask to pass to umask(). This is more of a preventative measure since
+ * all file/directory creates should be performed using the create modes above.
+ */
+int pg_mode_mask = PG_MODE_MASK_OWNER;
+
+/*
+ * Set create modes and mask to use when writing to PGDATA based on the data
+ * directory mode passed. If group read/execute are present in the
+ * mode, then create modes and mask will be relaxed to allow group read/execute
+ * on all newly created files and directories.
+ */
+void
+SetDataDirectoryCreatePerm(int dataDirMode)
+{
+ /* If the data directory mode has group access */
+ if ((PG_DIR_MODE_GROUP & dataDirMode) == PG_DIR_MODE_GROUP)
+ {
+ pg_dir_create_mode = PG_DIR_MODE_GROUP;
+ pg_file_create_mode = PG_FILE_MODE_GROUP;
+ pg_mode_mask = PG_MODE_MASK_GROUP;
+ }
+ /* Else use default permissions */
+ else
+ {
+ pg_dir_create_mode = PG_DIR_MODE_OWNER;
+ pg_file_create_mode = PG_FILE_MODE_OWNER;
+ pg_mode_mask = PG_MODE_MASK_OWNER;
+ }
+}
+
+#ifdef FRONTEND
+
+/*
+ * Get the create modes and mask to use when writing to PGDATA by examining the
+ * mode of the PGDATA directory and calling SetDataDirectoryCreatePerm().
+ *
+ * Errors are not handled here and should be reported by the application when
+ * false is returned.
+ *
+ * Suppress when on Windows, because there may not be proper support for Unix-y
+ * file permissions.
+ */
+bool
+GetDataDirectoryCreatePerm(const char *dataDir)
+{
+#if !defined(WIN32) && !defined(__CYGWIN__)
+ struct stat statBuf;
+
+ /*
+ * If an error occurs getting the mode then return false. The caller is
+ * responsible for generating an error, if appropriate, indicating that we
+ * were unable to access the data directory.
+ */
+ if (stat(dataDir, &statBuf) == -1)
+ return false;
+
+ /* Set permissions */
+ SetDataDirectoryCreatePerm(statBuf.st_mode);
+
+ return true;
+#endif /* !defined(WIN32) && !defined(__CYGWIN__) */
+}
+
+
+#endif /* FRONTEND */
diff --git a/src/include/common/file_perm.h b/src/include/common/file_perm.h
index 37631a7191..3090f78931 100644
--- a/src/include/common/file_perm.h
+++ b/src/include/common/file_perm.h
@@ -21,14 +21,34 @@
*/
#define PG_MODE_MASK_OWNER (S_IRWXG | S_IRWXO)
+/*
+ * Mode mask for data directory permissions that also allows group read/execute.
+ */
+#define PG_MODE_MASK_GROUP (S_IWGRP | S_IRWXO)
+
/* Default mode for creating directories */
#define PG_DIR_MODE_OWNER S_IRWXU
+/* Mode for creating directories that allows group read/execute */
+#define PG_DIR_MODE_GROUP (S_IRWXU | S_IRGRP | S_IXGRP)
+
/* Default mode for creating files */
#define PG_FILE_MODE_OWNER (S_IRUSR | S_IWUSR)
+/* Mode for creating files that allows group read */
+#define PG_FILE_MODE_GROUP (S_IRUSR | S_IWUSR | S_IRGRP)
+
/* Modes for creating directories and files in the data directory */
-extern int pg_dir_create_mode;
-extern int pg_file_create_mode;
+extern int pg_dir_create_mode;
+extern int pg_file_create_mode;
+
+/* Mode mask to pass to umask() */
+extern int pg_mode_mask;
+
+/* Set permissions and mask based on the provided mode */
+extern void SetDataDirectoryCreatePerm(int dataDirMode);
+
+/* Set permissions and mask based on the mode of the data directory */
+extern bool GetDataDirectoryCreatePerm(const char *dataDir);
#endif /* FILE_PERM_H */
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index b5ad841968..e167ee8fcb 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -153,6 +153,7 @@ extern PGDLLIMPORT bool IsBinaryUpgrade;
extern PGDLLIMPORT bool ExitOnAnyError;
extern PGDLLIMPORT char *DataDir;
+extern PGDLLIMPORT int data_directory_mode;
extern PGDLLIMPORT int NBuffers;
extern PGDLLIMPORT int MaxBackends;
@@ -323,6 +324,7 @@ extern void SetSessionAuthorization(Oid userid, bool is_superuser);
extern Oid GetCurrentRoleId(void);
extern void SetCurrentRoleId(Oid roleid, bool is_superuser);
+extern void checkDataDir(void);
extern void SetDataDir(const char *dir);
extern void ChangeToDataDir(void);
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 76e571b98c..1bd91524d7 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -86,9 +86,11 @@ use Carp;
use Config;
use Cwd;
use Exporter 'import';
+use Fcntl qw(:mode);
use File::Basename;
use File::Path qw(rmtree);
use File::Spec;
+use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
use RecursiveCopy;
@@ -269,6 +271,26 @@ sub connstr
=pod
+=item $node->group_access()
+
+Does the data dir allow group access?
+
+=cut
+
+sub group_access
+{
+ my ($self) = @_;
+
+ my $dir_stat = stat($self->data_dir);
+
+ defined($dir_stat)
+ or die('unable to stat ' . $self->data_dir);
+
+ return (S_IMODE($dir_stat->mode) == 0750);
+}
+
+=pod
+
=item $node->data_dir()
Returns the path to the data directory. postgresql.conf and pg_hba.conf are
@@ -460,6 +482,9 @@ sub init
}
close $conf;
+ chmod($self->group_access ? 0640 : 0600, "$pgdata/postgresql.conf")
+ or die("unable to set permissions for $pgdata/postgresql.conf");
+
$self->set_replication_conf if $params{allows_streaming};
$self->enable_archiving if $params{has_archiving};
}
@@ -485,7 +510,7 @@ sub append_conf
TestLib::append_to_file($conffile, $str . "\n");
- chmod(0600, $conffile)
+ chmod($self->group_access() ? 0640 : 0600, $conffile)
or die("unable to set permissions for $conffile");
}
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index 93610e4bc4..8047404efd 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -31,6 +31,7 @@ our @EXPORT = qw(
slurp_file
append_to_file
check_mode_recursive
+ chmod_recursive
check_pg_config
system_or_bail
system_log
@@ -313,6 +314,30 @@ sub check_mode_recursive
return $result;
}
+# Change mode recursively on a directory
+sub chmod_recursive
+{
+ my ($dir, $dir_mode, $file_mode) = @_;
+
+ find
+ (
+ {follow_fast => 1,
+ wanted =>
+ sub
+ {
+ my $file_stat = stat($File::Find::name);
+
+ if (defined($file_stat))
+ {
+ chmod(S_ISDIR($file_stat->mode) ? $dir_mode : $file_mode,
+ $File::Find::name)
+ or die "unable to chmod $File::Find::name";
+ }
+ }},
+ $dir
+ );
+}
+
# Check presence of a given regexp within pg_config.h for the installation
# where tests are running, returning a match status result depending on
# that.
--
2.14.1
Hi Stephen,
On 4/6/18 10:22 PM, Stephen Frost wrote:
* David Steele (david@pgmasters.net) wrote:
On 4/6/18 6:04 PM, David Steele wrote:
On 4/6/18 3:02 PM, Stephen Frost wrote:
- Further discussion in the commit messages
Agreed, these need some more work. I'm happy to do that but I'll need a
bit more time. Have a look at the new patches and I'll work on some
better messages.I'm sure you'll want to reword some things, but I think these commit
messages capture the essential changes for each patch.Thanks much. I've taken (most) of these, adjusting a few bits here and
there.I've been back over the patch again, mostly improving the commit
messages, comments, and docs. I also looked over the code and tests
again and they're looking pretty good to me, so I'll be looking to
commit this tomorrow afternoon or so US/Eastern.
OK, one last review. I did't make any code changes, but I improved some
comments, added documentation and fixed a test.
01) Refactor dir/file permissions
* Small comment update, replace "such cases-" with "such cases --".
02) Allow group access on PGDATA
* Add data_directory_mode guc to "Preset Options" documentation.
* Add a section to the documentation that discusses changing the
permissions of an existing cluster.
* Improved comment on checkControlFile().
* Comment wordsmithing for SetDataDirectoryCreatePerm() and
RetrieveDataDirCreatePerm().
* Fixed ordering of -g option in initdb documentation.
* Fixed a new test that was "broken" by 032429701e477. It was broken
before but the rmtrees added in 032429701e477 exposed the problem.
Attached are the v19 patches.
Thanks!
--
-David
david@pgmasters.net
Attachments:
group-access-v19-01-file-perm.patchtext/plain; charset=UTF-8; name=group-access-v19-01-file-perm.patch; x-mac-creator=0; x-mac-type=0Download
From d97495e81bb59beb03941398d7746a22b55d48ec Mon Sep 17 00:00:00 2001
From: David Steele <david@pgmasters.net>
Date: Sat, 7 Apr 2018 09:49:15 -0400
Subject: [PATCH 1/2] Refactor dir/file permissions
Consolidate directory and file create permissions for tools which work
with the PG data directory by adding a new module (common/file_perm.c)
that contains variables (pg_file_create_mode, pg_dir_create_mode) and
constants to initialize them (0600 for files and 0700 for directories).
Convert mkdir() calls in the backend to MakePGDirectory() if the
original call used default permissions (always the case for regular PG
directories).
Add tests to make sure permissions in PGDATA are set correctly by the
tools which modify the PG data directory.
Authors: David Steele <david@pgmasters.net>,
Adam Brightwell <adam.brightwell@crunchydata.com>
Reviewed-By: Michael Paquier, with discussion amongst many others.
Discussion: https://postgr.es/m/ad346fe6-b23e-59f1-ecb7-0e08390ad629%40pgmasters.net
---
src/backend/access/transam/xlog.c | 2 +-
src/backend/commands/tablespace.c | 18 ++++---
src/backend/postmaster/postmaster.c | 7 +--
src/backend/postmaster/syslogger.c | 5 +-
src/backend/replication/basebackup.c | 5 +-
src/backend/replication/slot.c | 5 +-
src/backend/storage/file/copydir.c | 2 +-
src/backend/storage/file/fd.c | 51 +++++++++++++------
src/backend/storage/ipc/dsm_impl.c | 3 +-
src/backend/storage/ipc/ipc.c | 4 ++
src/backend/utils/init/miscinit.c | 5 +-
src/bin/initdb/initdb.c | 24 ++++-----
src/bin/initdb/t/001_initdb.pl | 11 ++++-
src/bin/pg_basebackup/pg_basebackup.c | 9 ++--
src/bin/pg_basebackup/t/010_pg_basebackup.pl | 14 +++++-
src/bin/pg_basebackup/t/020_pg_receivewal.pl | 14 +++++-
src/bin/pg_basebackup/walmethods.c | 6 ++-
src/bin/pg_ctl/pg_ctl.c | 4 +-
src/bin/pg_ctl/t/001_start_stop.pl | 18 ++++++-
src/bin/pg_resetwal/pg_resetwal.c | 5 +-
src/bin/pg_resetwal/t/001_basic.pl | 12 ++++-
src/bin/pg_rewind/RewindTest.pm | 4 ++
src/bin/pg_rewind/file_ops.c | 7 +--
src/bin/pg_rewind/t/001_basic.pl | 11 ++++-
src/bin/pg_upgrade/file.c | 5 +-
src/bin/pg_upgrade/pg_upgrade.c | 3 +-
src/bin/pg_upgrade/test.sh | 11 +++++
src/common/Makefile | 4 +-
src/common/file_perm.c | 19 ++++++++
src/include/common/file_perm.h | 34 +++++++++++++
src/include/storage/fd.h | 3 ++
src/test/perl/PostgresNode.pm | 3 ++
src/test/perl/TestLib.pm | 73 ++++++++++++++++++++++++++++
src/tools/msvc/Mkvcbuild.pm | 4 +-
34 files changed, 330 insertions(+), 75 deletions(-)
create mode 100644 src/common/file_perm.c
create mode 100644 src/include/common/file_perm.h
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 813b2afaac..4a47395174 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4107,7 +4107,7 @@ ValidateXLOGDirectoryStructure(void)
{
ereport(LOG,
(errmsg("creating missing WAL directory \"%s\"", path)));
- if (mkdir(path, S_IRWXU) < 0)
+ if (MakePGDirectory(path) < 0)
ereport(FATAL,
(errmsg("could not create missing directory \"%s\": %m",
path)));
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 5c450caa4e..cfc9fe468c 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -68,6 +68,7 @@
#include "commands/seclabel.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
+#include "common/file_perm.h"
#include "miscadmin.h"
#include "postmaster/bgwriter.h"
#include "storage/fd.h"
@@ -151,7 +152,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
else
{
/* Directory creation failed? */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakePGDirectory(dir) < 0)
{
char *parentdir;
@@ -173,7 +174,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
get_parent_directory(parentdir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakePGDirectory(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -184,7 +185,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
parentdir = pstrdup(dir);
get_parent_directory(parentdir);
/* Can't create parent and it doesn't already exist? */
- if (mkdir(parentdir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakePGDirectory(parentdir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -192,7 +193,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
pfree(parentdir);
/* Create database directory */
- if (mkdir(dir, S_IRWXU) < 0)
+ if (MakePGDirectory(dir) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
@@ -279,7 +280,8 @@ CreateTableSpace(CreateTableSpaceStmt *stmt)
/*
* Check that location isn't too long. Remember that we're going to append
* 'PG_XXX/<dboid>/<relid>_<fork>.<nnn>'. FYI, we never actually
- * reference the whole path here, but mkdir() uses the first two parts.
+ * reference the whole path here, but MakePGDirectory() uses the first
+ * two parts.
*/
if (strlen(location) + 1 + strlen(TABLESPACE_VERSION_DIRECTORY) + 1 +
OIDCHARS + 1 + OIDCHARS + 1 + FORKNAMECHARS + 1 + OIDCHARS > MAXPGPATH)
@@ -574,7 +576,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* Attempt to coerce target directory to safe permissions. If this fails,
* it doesn't exist or has the wrong owner.
*/
- if (chmod(location, S_IRWXU) != 0)
+ if (chmod(location, pg_dir_create_mode) != 0)
{
if (errno == ENOENT)
ereport(ERROR,
@@ -599,7 +601,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
if (stat(location_with_version_dir, &st) == 0 && S_ISDIR(st.st_mode))
{
if (!rmtree(location_with_version_dir, true))
- /* If this failed, mkdir() below is going to error. */
+ /* If this failed, MakePGDirectory() below is going to error. */
ereport(WARNING,
(errmsg("some useless files may be left behind in old database directory \"%s\"",
location_with_version_dir)));
@@ -610,7 +612,7 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
* The creation of the version directory prevents more than one tablespace
* in a single location.
*/
- if (mkdir(location_with_version_dir, S_IRWXU) < 0)
+ if (MakePGDirectory(location_with_version_dir) < 0)
{
if (errno == EEXIST)
ereport(ERROR,
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 3dfb87d701..10afecffb3 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -97,6 +97,7 @@
#include "access/xlog.h"
#include "bootstrap/bootstrap.h"
#include "catalog/pg_control.h"
+#include "common/file_perm.h"
#include "common/ip.h"
#include "lib/ilist.h"
#include "libpq/auth.h"
@@ -589,7 +590,7 @@ PostmasterMain(int argc, char *argv[])
/*
* for security, no dir or file created can be group or other accessible
*/
- umask(S_IRWXG | S_IRWXO);
+ umask(PG_MODE_MASK_OWNER);
/*
* Initialize random(3) so we don't get the same values in every run.
@@ -4490,9 +4491,9 @@ internal_forkexec(int argc, char *argv[], Port *port)
{
/*
* As in OpenTemporaryFileInTablespace, try to make the temp-file
- * directory
+ * directory, ignoring errors.
*/
- mkdir(PG_TEMP_FILES_DIR, S_IRWXU);
+ (void) MakePGDirectory(PG_TEMP_FILES_DIR);
fp = AllocateFile(tmpfilename, PG_BINARY_W);
if (!fp)
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index f70eea37df..58b759f305 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -41,6 +41,7 @@
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
#include "storage/dsm.h"
+#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pg_shmem.h"
@@ -322,7 +323,7 @@ SysLoggerMain(int argc, char *argv[])
/*
* Also, create new directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ (void) MakePGDirectory(Log_directory);
}
if (strcmp(Log_filename, currentLogFilename) != 0)
{
@@ -564,7 +565,7 @@ SysLogger_Start(void)
/*
* Create log directory if not present; ignore errors
*/
- mkdir(Log_directory, S_IRWXU);
+ (void) MakePGDirectory(Log_directory);
/*
* The initial logfile is created right in the postmaster, to verify that
diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c
index 8ba29453b9..babf85a6ea 100644
--- a/src/backend/replication/basebackup.c
+++ b/src/backend/replication/basebackup.c
@@ -19,6 +19,7 @@
#include "access/xlog_internal.h" /* for pg_start/stop_backup */
#include "catalog/catalog.h"
#include "catalog/pg_type.h"
+#include "common/file_perm.h"
#include "lib/stringinfo.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
@@ -930,7 +931,7 @@ sendFileWithContent(const char *filename, const char *content)
statbuf.st_gid = getegid();
#endif
statbuf.st_mtime = time(NULL);
- statbuf.st_mode = S_IRUSR | S_IWUSR;
+ statbuf.st_mode = pg_file_create_mode;
statbuf.st_size = len;
_tarWriteHeader(filename, NULL, &statbuf, false);
@@ -1628,7 +1629,7 @@ _tarWriteDir(const char *pathbuf, int basepathlen, struct stat *statbuf,
#else
if (pgwin32_is_junction(pathbuf))
#endif
- statbuf->st_mode = S_IFDIR | S_IRWXU;
+ statbuf->st_mode = S_IFDIR | pg_dir_create_mode;
return _tarWriteHeader(pathbuf + basepathlen + 1, NULL, statbuf, sizeonly);
}
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index fc9ef22b0b..056628fe8e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -1166,13 +1166,14 @@ CreateSlotOnDisk(ReplicationSlot *slot)
* It's just barely possible that some previous effort to create or drop a
* slot with this name left a temp directory lying around. If that seems
* to be the case, try to remove it. If the rmtree() fails, we'll error
- * out at the mkdir() below, so we don't bother checking success.
+ * out at the MakePGDirectory() below, so we don't bother checking
+ * success.
*/
if (stat(tmppath, &st) == 0 && S_ISDIR(st.st_mode))
rmtree(tmppath, true);
/* Create and fsync the temporary slot directory. */
- if (mkdir(tmppath, S_IRWXU) < 0)
+ if (MakePGDirectory(tmppath) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m",
diff --git a/src/backend/storage/file/copydir.c b/src/backend/storage/file/copydir.c
index ca6342db0d..4a0d23b11e 100644
--- a/src/backend/storage/file/copydir.c
+++ b/src/backend/storage/file/copydir.c
@@ -41,7 +41,7 @@ copydir(char *fromdir, char *todir, bool recurse)
char fromfile[MAXPGPATH * 2];
char tofile[MAXPGPATH * 2];
- if (mkdir(todir, S_IRWXU) != 0)
+ if (MakePGDirectory(todir) != 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create directory \"%s\": %m", todir)));
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index d30a725f90..36eea9d11d 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -84,6 +84,7 @@
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "catalog/pg_tablespace.h"
+#include "common/file_perm.h"
#include "pgstat.h"
#include "portability/mem.h"
#include "storage/fd.h"
@@ -124,12 +125,6 @@
*/
#define FD_MINFREE 10
-/*
- * Default mode for created files, unless something else is specified using
- * the *Perm() function variants.
- */
-#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR)
-
/*
* A number of platforms allow individual processes to open many more files
* than they can really support when *many* processes do the same thing.
@@ -937,7 +932,7 @@ set_max_safe_fds(void)
int
BasicOpenFile(const char *fileName, int fileFlags)
{
- return BasicOpenFilePerm(fileName, fileFlags, PG_FILE_MODE_DEFAULT);
+ return BasicOpenFilePerm(fileName, fileFlags, pg_file_create_mode);
}
/*
@@ -1356,7 +1351,7 @@ FileInvalidate(File file)
File
PathNameOpenFile(const char *fileName, int fileFlags)
{
- return PathNameOpenFilePerm(fileName, fileFlags, PG_FILE_MODE_DEFAULT);
+ return PathNameOpenFilePerm(fileName, fileFlags, pg_file_create_mode);
}
/*
@@ -1434,7 +1429,7 @@ PathNameOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode)
void
PathNameCreateTemporaryDir(const char *basedir, const char *directory)
{
- if (mkdir(directory, S_IRWXU) < 0)
+ if (MakePGDirectory(directory) < 0)
{
if (errno == EEXIST)
return;
@@ -1444,14 +1439,14 @@ PathNameCreateTemporaryDir(const char *basedir, const char *directory)
* EEXIST to close a race against another process following the same
* algorithm.
*/
- if (mkdir(basedir, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakePGDirectory(basedir) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary directory \"%s\": %m",
basedir)));
/* Try again. */
- if (mkdir(directory, S_IRWXU) < 0 && errno != EEXIST)
+ if (MakePGDirectory(directory) < 0 && errno != EEXIST)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("cannot create temporary subdirectory \"%s\": %m",
@@ -1601,11 +1596,11 @@ OpenTemporaryFileInTablespace(Oid tblspcOid, bool rejectError)
* We might need to create the tablespace's tempfile directory, if no
* one has yet done so.
*
- * Don't check for error from mkdir; it could fail if someone else
- * just did the same thing. If it doesn't work then we'll bomb out on
- * the second create attempt, instead.
+ * Don't check for an error from MakePGDirectory; it could fail if
+ * someone else just did the same thing. If it doesn't work then
+ * we'll bomb out on the second create attempt, instead.
*/
- mkdir(tempdirpath, S_IRWXU);
+ (void) MakePGDirectory(tempdirpath);
file = PathNameOpenFile(tempfilepath,
O_RDWR | O_CREAT | O_TRUNC | PG_BINARY);
@@ -2401,7 +2396,7 @@ TryAgain:
int
OpenTransientFile(const char *fileName, int fileFlags)
{
- return OpenTransientFilePerm(fileName, fileFlags, PG_FILE_MODE_DEFAULT);
+ return OpenTransientFilePerm(fileName, fileFlags, pg_file_create_mode);
}
/*
@@ -3554,3 +3549,27 @@ fsync_parent_path(const char *fname, int elevel)
return 0;
}
+
+/*
+ * Create a PostgreSQL data sub-directory
+ *
+ * The data directory itself, along with most other directories, are created at
+ * initdb-time, but we do have some occations where we create directories from
+ * the backend (CREATE TABLESPACE, for example). In those cases, we want to
+ * make sure that those directories are created consistently. Today, that means
+ * making sure that the created directory has the correct permissions, which is
+ * what pg_dir_create_mode tracks for us.
+ *
+ * Note that we also set the umask() based on what we understand the correct
+ * permissions to be (see file_perm.c).
+ *
+ * For permissions other than the default mkdir() can be used directly, but be
+ * sure to consider carefully such cases -- a directory with incorrect
+ * permissions in a PostgreSQL data directory could cause backups and other
+ * processes to fail.
+ */
+int
+MakePGDirectory(const char *directoryName)
+{
+ return mkdir(directoryName, pg_dir_create_mode);
+}
diff --git a/src/backend/storage/ipc/dsm_impl.c b/src/backend/storage/ipc/dsm_impl.c
index 67e76b98fe..2fca9fae51 100644
--- a/src/backend/storage/ipc/dsm_impl.c
+++ b/src/backend/storage/ipc/dsm_impl.c
@@ -60,6 +60,7 @@
#ifdef HAVE_SYS_SHM_H
#include <sys/shm.h>
#endif
+#include "common/file_perm.h"
#include "pgstat.h"
#include "portability/mem.h"
@@ -285,7 +286,7 @@ dsm_impl_posix(dsm_op op, dsm_handle handle, Size request_size,
* returning.
*/
flags = O_RDWR | (op == DSM_OP_CREATE ? O_CREAT | O_EXCL : 0);
- if ((fd = shm_open(name, flags, 0600)) == -1)
+ if ((fd = shm_open(name, flags, PG_FILE_MODE_OWNER)) == -1)
{
if (errno != EEXIST)
ereport(elevel,
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index fc0a9c0756..53f7c1e77e 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -137,6 +137,10 @@ proc_exit(int code)
else
snprintf(gprofDirName, 32, "gprof/%d", (int) getpid());
+ /*
+ * Use mkdir() instead of MakePGDirectory() since we aren't making a
+ * PG directory here.
+ */
mkdir("gprof", S_IRWXU | S_IRWXG | S_IRWXO);
mkdir(gprofDirName, S_IRWXU | S_IRWXG | S_IRWXO);
chdir(gprofDirName);
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 87ed7d3f71..f8f08f3f88 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -32,6 +32,7 @@
#include "access/htup_details.h"
#include "catalog/pg_authid.h"
+#include "common/file_perm.h"
#include "libpq/libpq.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
@@ -831,7 +832,7 @@ CreateLockFile(const char *filename, bool amPostmaster,
* Think not to make the file protection weaker than 0600. See
* comments below.
*/
- fd = open(filename, O_RDWR | O_CREAT | O_EXCL, 0600);
+ fd = open(filename, O_RDWR | O_CREAT | O_EXCL, pg_file_create_mode);
if (fd >= 0)
break; /* Success; exit the retry loop */
@@ -848,7 +849,7 @@ CreateLockFile(const char *filename, bool amPostmaster,
* Read the file to get the old owner's PID. Note race condition
* here: file might have been deleted since we tried to create it.
*/
- fd = open(filename, O_RDONLY, 0600);
+ fd = open(filename, O_RDONLY, pg_file_create_mode);
if (fd < 0)
{
if (errno == ENOENT)
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 78990f5a27..3765548a24 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -64,6 +64,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_class.h"
#include "catalog/pg_collation.h"
+#include "common/file_perm.h"
#include "common/file_utils.h"
#include "common/restricted_token.h"
#include "common/username.h"
@@ -1170,7 +1171,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, pg_file_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1190,7 +1191,7 @@ setup_config(void)
sprintf(path, "%s/postgresql.auto.conf", pg_data);
writefile(path, autoconflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, pg_file_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1277,7 +1278,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_hba.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, pg_file_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -1293,7 +1294,7 @@ setup_config(void)
snprintf(path, sizeof(path), "%s/pg_ident.conf", pg_data);
writefile(path, conflines);
- if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+ if (chmod(path, pg_file_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"),
progname, path, strerror(errno));
@@ -2692,7 +2693,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (pg_mkdir_p(pg_data, S_IRWXU) != 0)
+ if (pg_mkdir_p(pg_data, pg_dir_create_mode) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2710,7 +2711,7 @@ create_data_directory(void)
pg_data);
fflush(stdout);
- if (chmod(pg_data, S_IRWXU) != 0)
+ if (chmod(pg_data, pg_dir_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, pg_data, strerror(errno));
@@ -2778,7 +2779,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (pg_mkdir_p(xlog_dir, S_IRWXU) != 0)
+ if (pg_mkdir_p(xlog_dir, pg_dir_create_mode) != 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2796,7 +2797,7 @@ create_xlog_or_symlink(void)
xlog_dir);
fflush(stdout);
- if (chmod(xlog_dir, S_IRWXU) != 0)
+ if (chmod(xlog_dir, pg_dir_create_mode) != 0)
{
fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
progname, xlog_dir, strerror(errno));
@@ -2846,7 +2847,7 @@ create_xlog_or_symlink(void)
else
{
/* Without -X option, just make the subdirectory normally */
- if (mkdir(subdirloc, S_IRWXU) < 0)
+ if (mkdir(subdirloc, pg_dir_create_mode) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, subdirloc, strerror(errno));
@@ -2882,7 +2883,8 @@ initialize_data_directory(void)
setup_signals();
- umask(S_IRWXG | S_IRWXO);
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_OWNER);
create_data_directory();
@@ -2902,7 +2904,7 @@ initialize_data_directory(void)
* The parent directory already exists, so we only need mkdir() not
* pg_mkdir_p() here, which avoids some failure modes; cf bug #13853.
*/
- if (mkdir(path, S_IRWXU) < 0)
+ if (mkdir(path, pg_dir_create_mode) < 0)
{
fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
progname, path, strerror(errno));
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index c0cfa6e92c..9dfb2ed96f 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 15;
+use Test::More tests => 16;
my $tempdir = TestLib::tempdir;
my $xlogdir = "$tempdir/pgxlog";
@@ -45,6 +45,15 @@ mkdir $datadir;
command_ok([ 'initdb', '-N', '-T', 'german', '-X', $xlogdir, $datadir ],
'successful creation');
+
+ # Permissions on PGDATA should be default
+ SKIP:
+ {
+ skip "unix-style permissions not supported on Windows", 1 if ($windows_os);
+
+ ok(check_mode_recursive($datadir, 0700, 0600),
+ "check PGDATA permissions");
+ }
}
command_ok([ 'initdb', '-S', $datadir ], 'sync only');
command_fails([ 'initdb', $datadir ], 'existing data directory');
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 8610fe8a1a..32b41e313c 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -27,6 +27,7 @@
#endif
#include "access/xlog_internal.h"
+#include "common/file_perm.h"
#include "common/file_utils.h"
#include "common/string.h"
#include "fe_utils/string_utils.h"
@@ -629,7 +630,7 @@ StartLogStreamer(char *startpos, uint32 timeline, char *sysidentifier)
PQserverVersion(conn) < MINIMUM_VERSION_FOR_PG_WAL ?
"pg_xlog" : "pg_wal");
- if (pg_mkdir_p(statusdir, S_IRWXU) != 0 && errno != EEXIST)
+ if (pg_mkdir_p(statusdir, pg_dir_create_mode) != 0 && errno != EEXIST)
{
fprintf(stderr,
_("%s: could not create directory \"%s\": %s\n"),
@@ -685,7 +686,7 @@ verify_dir_is_empty_or_create(char *dirname, bool *created, bool *found)
/*
* Does not exist, so create
*/
- if (pg_mkdir_p(dirname, S_IRWXU) == -1)
+ if (pg_mkdir_p(dirname, pg_dir_create_mode) == -1)
{
fprintf(stderr,
_("%s: could not create directory \"%s\": %s\n"),
@@ -1129,7 +1130,7 @@ ReceiveTarFile(PGconn *conn, PGresult *res, int rownum)
tarCreateHeader(header, "recovery.conf", NULL,
recoveryconfcontents->len,
- 0600, 04000, 02000,
+ pg_file_create_mode, 04000, 02000,
time(NULL));
padding = ((recoveryconfcontents->len + 511) & ~511) - recoveryconfcontents->len;
@@ -1441,7 +1442,7 @@ ReceiveAndUnpackTarFile(PGconn *conn, PGresult *res, int rownum)
* Directory
*/
filename[strlen(filename) - 1] = '\0'; /* Remove trailing slash */
- if (mkdir(filename, S_IRWXU) != 0)
+ if (mkdir(filename, pg_dir_create_mode) != 0)
{
/*
* When streaming WAL, pg_wal (or pg_xlog for pre-9.6
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index afb392dbb3..ac5bb99f1b 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -6,7 +6,7 @@ use File::Basename qw(basename dirname);
use File::Path qw(rmtree);
use PostgresNode;
use TestLib;
-use Test::More tests => 104;
+use Test::More tests => 105;
program_help_ok('pg_basebackup');
program_version_ok('pg_basebackup');
@@ -16,6 +16,9 @@ my $tempdir = TestLib::tempdir;
my $node = get_new_node('main');
+# Set umask so test directories and files are created with default permissions
+umask(0077);
+
# Initialize node without replication settings
$node->init(extra => [ '--data-checksums' ]);
$node->start;
@@ -94,6 +97,15 @@ $node->command_ok([ 'pg_basebackup', '-D', "$tempdir/backup", '-X', 'none' ],
'pg_basebackup runs');
ok(-f "$tempdir/backup/PG_VERSION", 'backup was created');
+# Permissions on backup should be default
+SKIP:
+{
+ skip "unix-style permissions not supported on Windows", 1 if ($windows_os);
+
+ ok(check_mode_recursive("$tempdir/backup", 0700, 0600),
+ "check backup dir permissions");
+}
+
# Only archive_status directory should be copied in pg_wal/.
is_deeply(
[ sort(slurp_dir("$tempdir/backup/pg_wal/")) ],
diff --git a/src/bin/pg_basebackup/t/020_pg_receivewal.pl b/src/bin/pg_basebackup/t/020_pg_receivewal.pl
index 64e3a35a87..19c106d9f5 100644
--- a/src/bin/pg_basebackup/t/020_pg_receivewal.pl
+++ b/src/bin/pg_basebackup/t/020_pg_receivewal.pl
@@ -2,12 +2,15 @@ use strict;
use warnings;
use TestLib;
use PostgresNode;
-use Test::More tests => 18;
+use Test::More tests => 19;
program_help_ok('pg_receivewal');
program_version_ok('pg_receivewal');
program_options_handling_ok('pg_receivewal');
+# Set umask so test directories and files are created with default permissions
+umask(0077);
+
my $primary = get_new_node('primary');
$primary->init(allows_streaming => 1);
$primary->start;
@@ -56,3 +59,12 @@ $primary->command_ok(
[ 'pg_receivewal', '-D', $stream_dir, '--verbose',
'--endpos', $nextlsn, '--synchronous', '--no-loop' ],
'streaming some WAL with --synchronous');
+
+# Permissions on WAL files should be default
+SKIP:
+{
+ skip "unix-style permissions not supported on Windows", 1 if ($windows_os);
+
+ ok(check_mode_recursive($stream_dir, 0700, 0600),
+ "check stream dir permissions");
+}
diff --git a/src/bin/pg_basebackup/walmethods.c b/src/bin/pg_basebackup/walmethods.c
index b4558a0184..267a40debb 100644
--- a/src/bin/pg_basebackup/walmethods.c
+++ b/src/bin/pg_basebackup/walmethods.c
@@ -22,6 +22,7 @@
#endif
#include "pgtar.h"
+#include "common/file_perm.h"
#include "common/file_utils.h"
#include "receivelog.h"
@@ -89,7 +90,7 @@ dir_open_for_write(const char *pathname, const char *temp_suffix, size_t pad_to_
* does not do any system calls to fsync() to make changes permanent on
* disk.
*/
- fd = open(tmppath, O_WRONLY | O_CREAT | PG_BINARY, S_IRUSR | S_IWUSR);
+ fd = open(tmppath, O_WRONLY | O_CREAT | PG_BINARY, pg_file_create_mode);
if (fd < 0)
return NULL;
@@ -534,7 +535,8 @@ tar_open_for_write(const char *pathname, const char *temp_suffix, size_t pad_to_
* We open the tar file only when we first try to write to it.
*/
tar_data->fd = open(tar_data->tarfilename,
- O_WRONLY | O_CREAT | PG_BINARY, S_IRUSR | S_IWUSR);
+ O_WRONLY | O_CREAT | PG_BINARY,
+ pg_file_create_mode);
if (tar_data->fd < 0)
return NULL;
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index 9bc830b085..5ede385e6a 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -25,6 +25,7 @@
#include "catalog/pg_control.h"
#include "common/controldata_utils.h"
+#include "common/file_perm.h"
#include "getopt_long.h"
#include "utils/pidfile.h"
@@ -2170,7 +2171,8 @@ main(int argc, char **argv)
*/
argv0 = argv[0];
- umask(S_IRWXG | S_IRWXO);
+ /* Set dir/file mode mask */
+ umask(PG_MODE_MASK_OWNER);
/* support --help and --version even if invoked as root */
if (argc > 1)
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl
index 5da4746cb4..067a084c87 100644
--- a/src/bin/pg_ctl/t/001_start_stop.pl
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -4,7 +4,7 @@ use warnings;
use Config;
use PostgresNode;
use TestLib;
-use Test::More tests => 19;
+use Test::More tests => 21;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -57,9 +57,23 @@ command_ok([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ], 'pg_ctl stop');
command_fails([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ],
'second pg_ctl stop fails');
+# Log file for default permission test. The permissions won't be checked on
+# Windows but we still want to do the restart test.
+my $logFileName = "$tempdir/data/perm-test-600.log";
+
command_ok(
- [ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
+ [ 'pg_ctl', 'restart', '-D', "$tempdir/data", '-l', $logFileName ],
'pg_ctl restart with server not running');
+
+# Permissions on log file should be default
+SKIP:
+{
+ skip "unix-style permissions not supported on Windows", 2 if ($windows_os);
+
+ ok(-f $logFileName);
+ ok(check_mode_recursive("$tempdir/data", 0700, 0600));
+}
+
command_ok([ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
'pg_ctl restart with server running');
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index eba7c5fdee..bdf71886ee 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -52,6 +52,7 @@
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
#include "common/fe_memutils.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "storage/large_object.h"
#include "pg_getopt.h"
@@ -967,7 +968,7 @@ RewriteControlFile(void)
fd = open(XLOG_CONTROL_FILE,
O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ pg_file_create_mode);
if (fd < 0)
{
fprintf(stderr, _("%s: could not create pg_control file: %s\n"),
@@ -1249,7 +1250,7 @@ WriteEmptyXLOG(void)
unlink(path);
fd = open(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR);
+ pg_file_create_mode);
if (fd < 0)
{
fprintf(stderr, _("%s: could not open file \"%s\": %s\n"),
diff --git a/src/bin/pg_resetwal/t/001_basic.pl b/src/bin/pg_resetwal/t/001_basic.pl
index 1b157cb555..0d6ab20073 100644
--- a/src/bin/pg_resetwal/t/001_basic.pl
+++ b/src/bin/pg_resetwal/t/001_basic.pl
@@ -3,7 +3,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 11;
+use Test::More tests => 12;
program_help_ok('pg_resetwal');
program_version_ok('pg_resetwal');
@@ -15,3 +15,13 @@ $node->init;
command_like([ 'pg_resetwal', '-n', $node->data_dir ],
qr/checkpoint/,
'pg_resetwal -n produces output');
+
+
+# Permissions on PGDATA should be default
+SKIP:
+{
+ skip "unix-style permissions not supported on Windows", 1 if ($windows_os);
+
+ ok(check_mode_recursive($node->data_dir, 0700, 0600),
+ 'check PGDATA permissions');
+}
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index 00b5b42dd7..7b632c7dcd 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -237,6 +237,10 @@ sub run_pg_rewind
"$tmp_folder/master-postgresql.conf.tmp",
"$master_pgdata/postgresql.conf");
+ chmod(0600, "$master_pgdata/postgresql.conf")
+ or BAIL_OUT(
+ "unable to set permissions for $master_pgdata/postgresql.conf");
+
# Plug-in rewound node to the now-promoted standby node
my $port_standby = $node_standby->port;
$node_master->append_conf(
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index f491ed7f5c..94bcc13ae8 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -18,6 +18,7 @@
#include <fcntl.h>
#include <unistd.h>
+#include "common/file_perm.h"
#include "file_ops.h"
#include "filemap.h"
#include "logging.h"
@@ -57,7 +58,7 @@ open_target_file(const char *path, bool trunc)
mode = O_WRONLY | O_CREAT | PG_BINARY;
if (trunc)
mode |= O_TRUNC;
- dstfd = open(dstpath, mode, 0600);
+ dstfd = open(dstpath, mode, pg_file_create_mode);
if (dstfd < 0)
pg_fatal("could not open target file \"%s\": %s\n",
dstpath, strerror(errno));
@@ -198,7 +199,7 @@ truncate_target_file(const char *path, off_t newsize)
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
- fd = open(dstpath, O_WRONLY, 0);
+ fd = open(dstpath, O_WRONLY, pg_file_create_mode);
if (fd < 0)
pg_fatal("could not open file \"%s\" for truncation: %s\n",
dstpath, strerror(errno));
@@ -219,7 +220,7 @@ create_target_dir(const char *path)
return;
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
- if (mkdir(dstpath, S_IRWXU) != 0)
+ if (mkdir(dstpath, pg_dir_create_mode) != 0)
pg_fatal("could not create directory \"%s\": %s\n",
dstpath, strerror(errno));
}
diff --git a/src/bin/pg_rewind/t/001_basic.pl b/src/bin/pg_rewind/t/001_basic.pl
index 736f34eae3..1b0f823b0c 100644
--- a/src/bin/pg_rewind/t/001_basic.pl
+++ b/src/bin/pg_rewind/t/001_basic.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 8;
+use Test::More tests => 10;
use RewindTest;
@@ -86,6 +86,15 @@ in master, before promotion
),
'tail-copy');
+ # Permissions on PGDATA should be default
+ SKIP:
+ {
+ skip "unix-style permissions not supported on Windows", 1 if ($windows_os);
+
+ ok(check_mode_recursive($node_master->data_dir(), 0700, 0600),
+ 'check PGDATA permissions');
+ }
+
RewindTest::clean_rewind_test();
}
diff --git a/src/bin/pg_upgrade/file.c b/src/bin/pg_upgrade/file.c
index f38bfacf02..f68211aa20 100644
--- a/src/bin/pg_upgrade/file.c
+++ b/src/bin/pg_upgrade/file.c
@@ -10,6 +10,7 @@
#include "postgres_fe.h"
#include "access/visibilitymap.h"
+#include "common/file_perm.h"
#include "pg_upgrade.h"
#include "storage/bufpage.h"
#include "storage/checksum.h"
@@ -44,7 +45,7 @@ copyFile(const char *src, const char *dst,
schemaName, relName, src, strerror(errno));
if ((dest_fd = open(dst, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR)) < 0)
+ pg_file_create_mode)) < 0)
pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
schemaName, relName, dst, strerror(errno));
@@ -151,7 +152,7 @@ rewriteVisibilityMap(const char *fromfile, const char *tofile,
schemaName, relName, fromfile, strerror(errno));
if ((dst_fd = open(tofile, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- S_IRUSR | S_IWUSR)) < 0)
+ pg_file_create_mode)) < 0)
pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
schemaName, relName, tofile, strerror(errno));
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index d12412799f..1d35188143 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -38,6 +38,7 @@
#include "pg_upgrade.h"
#include "catalog/pg_class.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "fe_utils/string_utils.h"
@@ -79,7 +80,7 @@ main(int argc, char **argv)
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_upgrade"));
/* Ensure that all files created by pg_upgrade are non-world-readable */
- umask(S_IRWXG | S_IRWXO);
+ umask(PG_MODE_MASK_OWNER);
parseCommandLine(argc, argv);
diff --git a/src/bin/pg_upgrade/test.sh b/src/bin/pg_upgrade/test.sh
index 39983abea1..574639d47e 100644
--- a/src/bin/pg_upgrade/test.sh
+++ b/src/bin/pg_upgrade/test.sh
@@ -230,6 +230,17 @@ standard_initdb 'initdb'
pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "${PGDATA}" -b "$oldbindir" -B "$bindir" -p "$PGPORT" -P "$PGPORT"
+# make sure all directories and files have correct permissions
+if [ $(find ${PGDATA} -type f ! -perm 600 | wc -l) -ne 0 ]; then
+ echo "files in PGDATA with permission != 600";
+ exit 1;
+fi
+
+if [ $(find ${PGDATA} -type d ! -perm 700 | wc -l) -ne 0 ]; then
+ echo "directories in PGDATA with permission != 700";
+ exit 1;
+fi
+
pg_ctl start -l "$logdir/postmaster2.log" -o "$POSTMASTER_OPTS" -w
case $testhost in
diff --git a/src/common/Makefile b/src/common/Makefile
index 873fbb6438..e9e75867f3 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -40,8 +40,8 @@ override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
override CPPFLAGS := -DFRONTEND $(CPPFLAGS)
LIBS += $(PTHREAD_LIBS)
-OBJS_COMMON = base64.o config_info.o controldata_utils.o exec.o ip.o \
- keywords.o md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
+OBJS_COMMON = base64.o config_info.o controldata_utils.o exec.o file_perm.o \
+ ip.o keywords.o md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
rmtree.o saslprep.o scram-common.o string.o unicode_norm.o \
username.o wait_error.o
diff --git a/src/common/file_perm.c b/src/common/file_perm.c
new file mode 100644
index 0000000000..fdfbb9a44c
--- /dev/null
+++ b/src/common/file_perm.c
@@ -0,0 +1,19 @@
+/*-------------------------------------------------------------------------
+ *
+ * File and directory permission routines
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/file_perm.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include <sys/stat.h>
+
+#include "common/file_perm.h"
+
+/* Modes for creating directories and files in the data directory */
+int pg_dir_create_mode = PG_DIR_MODE_OWNER;
+int pg_file_create_mode = PG_FILE_MODE_OWNER;
diff --git a/src/include/common/file_perm.h b/src/include/common/file_perm.h
new file mode 100644
index 0000000000..37631a7191
--- /dev/null
+++ b/src/include/common/file_perm.h
@@ -0,0 +1,34 @@
+/*-------------------------------------------------------------------------
+ *
+ * File and directory permission constants
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/file_perm.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FILE_PERM_H
+#define FILE_PERM_H
+
+/*
+ * Mode mask for data directory permissions that only allows the owner to
+ * read/write directories and files.
+ *
+ * This is the default.
+ */
+#define PG_MODE_MASK_OWNER (S_IRWXG | S_IRWXO)
+
+/* Default mode for creating directories */
+#define PG_DIR_MODE_OWNER S_IRWXU
+
+/* Default mode for creating files */
+#define PG_FILE_MODE_OWNER (S_IRUSR | S_IWUSR)
+
+/* Modes for creating directories and files in the data directory */
+extern int pg_dir_create_mode;
+extern int pg_file_create_mode;
+
+#endif /* FILE_PERM_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index e49b42ce86..785d9058e9 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -112,6 +112,9 @@ extern int CloseTransientFile(int fd);
extern int BasicOpenFile(const char *fileName, int fileFlags);
extern int BasicOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode);
+ /* Make a directory with default permissions */
+extern int MakePGDirectory(const char *directoryName);
+
/* Miscellaneous support routines */
extern void InitFileAccess(void);
extern void set_max_safe_fds(void);
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 80188315f1..76e571b98c 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -484,6 +484,9 @@ sub append_conf
my $conffile = $self->data_dir . '/' . $filename;
TestLib::append_to_file($conffile, $str . "\n");
+
+ chmod(0600, $conffile)
+ or die("unable to set permissions for $conffile");
}
=pod
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index b6862688d4..93610e4bc4 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -13,8 +13,11 @@ use warnings;
use Config;
use Cwd;
use Exporter 'import';
+use Fcntl qw(:mode);
use File::Basename;
+use File::Find;
use File::Spec;
+use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
use SimpleTee;
@@ -27,6 +30,7 @@ our @EXPORT = qw(
slurp_dir
slurp_file
append_to_file
+ check_mode_recursive
check_pg_config
system_or_bail
system_log
@@ -240,6 +244,75 @@ sub append_to_file
close $fh;
}
+# Check that all file/dir modes in a directory match the expected values,
+# ignoring the mode of any specified files.
+sub check_mode_recursive
+{
+ my ($dir, $expected_dir_mode, $expected_file_mode, $ignore_list) = @_;
+
+ # Result defaults to true
+ my $result = 1;
+
+ find
+ (
+ {follow_fast => 1,
+ wanted =>
+ sub
+ {
+ my $file_stat = stat($File::Find::name);
+
+ # Is file in the ignore list?
+ foreach my $ignore ($ignore_list ? @{$ignore_list} : [])
+ {
+ if ("$dir/$ignore" eq $File::Find::name)
+ {
+ return;
+ }
+ }
+
+ defined($file_stat)
+ or die("unable to stat $File::Find::name");
+
+ my $file_mode = S_IMODE($file_stat->mode);
+
+ # Is this a file?
+ if (S_ISREG($file_stat->mode))
+ {
+ if ($file_mode != $expected_file_mode)
+ {
+ print(*STDERR,
+ sprintf("$File::Find::name mode must be %04o\n",
+ $expected_file_mode));
+
+ $result = 0;
+ return;
+ }
+ }
+ # Else a directory?
+ elsif (S_ISDIR($file_stat->mode))
+ {
+ if ($file_mode != $expected_dir_mode)
+ {
+ print(*STDERR,
+ sprintf("$File::Find::name mode must be %04o\n",
+ $expected_dir_mode));
+
+ $result = 0;
+ return;
+ }
+ }
+ # Else something we can't handle
+ else
+ {
+ die "unknown file type for $File::Find::name";
+ }
+ }},
+ $dir
+ );
+
+ return $result;
+}
+
# Check presence of a given regexp within pg_config.h for the installation
# where tests are running, returning a match status result depending on
# that.
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 41d720880a..1d3ed6b0b1 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -111,8 +111,8 @@ sub mkvcbuild
}
our @pgcommonallfiles = qw(
- base64.c config_info.c controldata_utils.c exec.c ip.c keywords.c
- md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
+ base64.c config_info.c controldata_utils.c exec.c file_perm.c ip.c
+ keywords.c md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
saslprep.c scram-common.c string.c unicode_norm.c username.c
wait_error.c);
--
2.14.3 (Apple Git-98)
group-access-v19-02-group.patchtext/plain; charset=UTF-8; name=group-access-v19-02-group.patch; x-mac-creator=0; x-mac-type=0Download
From 8ff0e8fe63395efe1ec0f9e13780110629632279 Mon Sep 17 00:00:00 2001
From: David Steele <david@pgmasters.net>
Date: Sat, 7 Apr 2018 11:26:20 -0400
Subject: [PATCH 2/2] 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
---
doc/src/sgml/config.sgml | 17 ++++
doc/src/sgml/ref/initdb.sgml | 19 +++++
doc/src/sgml/ref/pg_basebackup.sgml | 6 ++
doc/src/sgml/ref/pg_receivewal.sgml | 6 ++
doc/src/sgml/ref/pg_recvlogical.sgml | 11 +++
doc/src/sgml/runtime.sgml | 26 +++++-
src/backend/bootstrap/bootstrap.c | 12 +--
src/backend/postmaster/postmaster.c | 87 ++++----------------
src/backend/tcop/postgres.c | 3 +-
src/backend/utils/init/globals.c | 9 +++
src/backend/utils/init/miscinit.c | 115 ++++++++++++++++++++++++---
src/backend/utils/misc/guc.c | 25 ++++++
src/bin/initdb/initdb.c | 24 +++++-
src/bin/initdb/t/001_initdb.pl | 20 ++++-
src/bin/pg_basebackup/pg_basebackup.c | 22 +++--
src/bin/pg_basebackup/pg_receivewal.c | 7 ++
src/bin/pg_basebackup/pg_recvlogical.c | 7 ++
src/bin/pg_basebackup/streamutil.c | 76 ++++++++++++++++++
src/bin/pg_basebackup/t/010_pg_basebackup.pl | 21 ++++-
src/bin/pg_ctl/pg_ctl.c | 12 ++-
src/bin/pg_ctl/t/001_start_stop.pl | 25 +++++-
src/bin/pg_resetwal/pg_resetwal.c | 10 +++
src/bin/pg_rewind/RewindTest.pm | 9 ++-
src/bin/pg_rewind/pg_rewind.c | 11 +++
src/bin/pg_rewind/t/002_databases.pl | 13 ++-
src/bin/pg_upgrade/pg_upgrade.c | 12 ++-
src/bin/pg_upgrade/test.sh | 16 ++--
src/common/file_perm.c | 72 ++++++++++++++++-
src/include/common/file_perm.h | 24 +++++-
src/include/miscadmin.h | 2 +
src/test/perl/PostgresNode.pm | 27 ++++++-
src/test/perl/TestLib.pm | 25 ++++++
32 files changed, 644 insertions(+), 127 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index a189a8efc3..5d5f2d23c4 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -8144,6 +8144,23 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
</listitem>
</varlistentry>
+ <varlistentry id="guc-data-directory-mode" xreflabel="data_directory_mode">
+ <term><varname>data_directory_mode</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>data_directory_mode</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ On Unix systems this parameter reports the permissions of the data
+ directory defined by (<xref linkend="guc-data-directory"/>) at startup.
+ (On Microsoft Windows this parameter will always display
+ <literal>0700</literal>). See
+ <xref linkend="app-initdb-allow-group-access"/> for more information.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-debug-assertions" xreflabel="debug_assertions">
<term><varname>debug_assertions</varname> (<type>boolean</type>)
<indexterm>
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 826dd91f72..10a8a86a03 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -76,6 +76,14 @@ PostgreSQL documentation
to do so.)
</para>
+ <para>
+ For security reasons the new cluster created by <command>initdb</command>
+ will only be accessible by the cluster owner by default. The
+ <option>--allow-group-access</option> option allows any user in the same
+ group as the cluster owner to read files in the cluster. This is useful
+ for performing backups as a non-privileged user.
+ </para>
+
<para>
<command>initdb</command> initializes the database cluster's default
locale and character set encoding. The character set encoding,
@@ -188,6 +196,17 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry id="app-initdb-allow-group-access" xreflabel="group access">
+ <term><option>-g</option></term>
+ <term><option>--allow-group-access</option></term>
+ <listitem>
+ <para>
+ Allows users in the same group as the cluster owner to read all cluster
+ files created by <command>initdb</command>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-initdb-data-checksums" xreflabel="data checksums">
<term><option>-k</option></term>
<term><option>--data-checksums</option></term>
diff --git a/doc/src/sgml/ref/pg_basebackup.sgml b/doc/src/sgml/ref/pg_basebackup.sgml
index 95045669c9..fc1edf4864 100644
--- a/doc/src/sgml/ref/pg_basebackup.sgml
+++ b/doc/src/sgml/ref/pg_basebackup.sgml
@@ -737,6 +737,12 @@ PostgreSQL documentation
or later.
</para>
+ <para>
+ <application>pg_basebackup</application> will preserve group permissions in
+ both the <literal>plain</literal> and <literal>tar</literal> formats if group
+ permissions are enabled on the source cluster.
+ </para>
+
</refsect1>
<refsect1>
diff --git a/doc/src/sgml/ref/pg_receivewal.sgml b/doc/src/sgml/ref/pg_receivewal.sgml
index e3f2ce1fcb..a18ddd4bff 100644
--- a/doc/src/sgml/ref/pg_receivewal.sgml
+++ b/doc/src/sgml/ref/pg_receivewal.sgml
@@ -425,6 +425,12 @@ PostgreSQL documentation
not keep up with fetching the WAL data.
</para>
+ <para>
+ <application>pg_receivewal</application> will preserve group permissions on
+ the received WAL files if group permissions are enabled on the source
+ cluster.
+ </para>
+
</refsect1>
<refsect1>
diff --git a/doc/src/sgml/ref/pg_recvlogical.sgml b/doc/src/sgml/ref/pg_recvlogical.sgml
index a79ca20084..141c5cddce 100644
--- a/doc/src/sgml/ref/pg_recvlogical.sgml
+++ b/doc/src/sgml/ref/pg_recvlogical.sgml
@@ -399,6 +399,17 @@ PostgreSQL documentation
</para>
</refsect1>
+ <refsect1>
+ <title>Notes</title>
+
+ <para>
+ <application>pg_recvlogical</application> will preserve group permissions on
+ the received WAL files if group permissions are enabled on the source
+ cluster.
+ </para>
+
+ </refsect1>
+
<refsect1>
<title>Examples</title>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 587b430527..330e38a29e 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -137,7 +137,22 @@ postgres$ <userinput>initdb -D /usr/local/pgsql/data</userinput>
database, it is essential that it be secured from unauthorized
access. <command>initdb</command> therefore revokes access
permissions from everyone but the
- <productname>PostgreSQL</productname> user.
+ <productname>PostgreSQL</productname> user, and optionally, group.
+ Group access, when enabled, is read-only. This allows an unprivileged
+ user in the same group as the cluster owner to take a backup of the
+ cluster data or perform other operations that only require read access.
+ </para>
+
+ <para>
+ Note that enabling or disabling group access on an existing cluster requires
+ the cluster to be shut down and the appropriate mode to be set on all
+ directories and files before restarting
+ <productname>PostgreSQL</productname>. Otherwise, a mix of modes might
+ exist in the data directory. For clusters that allow access only by the
+ owner, the appropriate modes are <literal>0700</literal> for directories
+ and <literal>0600</literal> for files. For clusters that also allow
+ reads by the group, the appropriate modes are <literal>0750</literal>
+ for directories and <literal>0640</literal> for files.
</para>
<para>
@@ -2194,6 +2209,15 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
member of the group that has access to those certificate and key files.
</para>
+ <para>
+ If the data directory allows group read access then certificate files may
+ need to be located outside of the data directory in order to conform to the
+ security requirements outlined above. Generally, group access is enabled
+ to allow an unprivileged user to backup the database, and in that case the
+ backup software will not be able to read the certificate files and will
+ likely error.
+ </para>
+
<para>
If the private key is protected with a passphrase, the
server will prompt for the passphrase and will not start until it has
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 1430894ad2..d6da743ddd 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 10afecffb3..aa1fcf0bdd 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 (onwer-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 7bdecc32ec..5095a4f686 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 c1f0441b08..0a3163398f 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 f8f08f3f88..33ebcbb54c 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -88,6 +88,100 @@ SetDatabasePath(const char *path)
DatabasePath = MemoryContextStrdup(TopMemoryContext, 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 71c2b4eff1..8441837e0f 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"
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 3765548a24..ba7ced343f 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -1168,6 +1168,19 @@ setup_config(void)
"password_encryption = scram-sha-256");
}
+ /*
+ * If group access has been enabled for the cluster then it makes sense to
+ * ensure that the log files also allow group access. Otherwise a backup
+ * from a user in the group would fail if the log files were not
+ * relocated.
+ */
+ if (pg_dir_create_mode == PG_DIR_MODE_GROUP)
+ {
+ conflines = replace_token(conflines,
+ "#log_file_mode = 0600",
+ "log_file_mode = 0640");
+ }
+
snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
writefile(path, conflines);
@@ -2312,6 +2325,7 @@ usage(const char *progname)
printf(_(" --auth-local=METHOD default authentication method for local-socket connections\n"));
printf(_(" [-D, --pgdata=]DATADIR location for this database cluster\n"));
printf(_(" -E, --encoding=ENCODING set default encoding for new databases\n"));
+ printf(_(" -g, --allow-group-access allow group read/execute on data directory\n"));
printf(_(" --locale=LOCALE set default locale for new databases\n"));
printf(_(" --lc-collate=, --lc-ctype=, --lc-messages=LOCALE\n"
" --lc-monetary=, --lc-numeric=, --lc-time=LOCALE\n"
@@ -2883,8 +2897,8 @@ initialize_data_directory(void)
setup_signals();
- /* Set dir/file mode mask */
- umask(PG_MODE_MASK_OWNER);
+ /* Set mask based on requested PGDATA permissions */
+ umask(pg_mode_mask);
create_data_directory();
@@ -3018,6 +3032,7 @@ main(int argc, char *argv[])
{"waldir", required_argument, NULL, 'X'},
{"wal-segsize", required_argument, NULL, 12},
{"data-checksums", no_argument, NULL, 'k'},
+ {"allow-group-access", no_argument, NULL, 'g'},
{NULL, 0, NULL, 0}
};
@@ -3059,7 +3074,7 @@ main(int argc, char *argv[])
/* process command-line options */
- while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:", long_options, &option_index)) != -1)
+ while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:g", long_options, &option_index)) != -1)
{
switch (c)
{
@@ -3153,6 +3168,9 @@ main(int argc, char *argv[])
case 12:
str_wal_segment_size_mb = pg_strdup(optarg);
break;
+ case 'g':
+ SetDataDirectoryCreatePerm(PG_DIR_MODE_GROUP);
+ break;
default:
/* getopt_long already emitted a complaint */
fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index 9dfb2ed96f..8609d2ecbc 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -4,9 +4,11 @@
use strict;
use warnings;
+use Fcntl ':mode';
+use File::stat qw{lstat};
use PostgresNode;
use TestLib;
-use Test::More tests => 16;
+use Test::More tests => 18;
my $tempdir = TestLib::tempdir;
my $xlogdir = "$tempdir/pgxlog";
@@ -57,3 +59,19 @@ mkdir $datadir;
}
command_ok([ 'initdb', '-S', $datadir ], 'sync only');
command_fails([ 'initdb', $datadir ], 'existing data directory');
+
+# Check group access on PGDATA
+SKIP:
+{
+ skip "unix-style permissions not supported on Windows", 2 if ($windows_os);
+
+ # Init a new db with group access
+ my $datadir_group = "$tempdir/data_group";
+
+ command_ok(
+ [ 'initdb', '-g', $datadir_group ],
+ 'successful creation with group access');
+
+ ok(check_mode_recursive($datadir_group, 0750, 0640),
+ 'check PGDATA permissions');
+}
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 32b41e313c..8cbc902ca3 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -2470,14 +2470,6 @@ main(int argc, char **argv)
}
#endif
- /*
- * Verify that the target directory exists, or create it. For plaintext
- * backups, always require the directory. For tar backups, require it
- * unless we are writing to stdout.
- */
- if (format == 'p' || strcmp(basedir, "-") != 0)
- verify_dir_is_empty_or_create(basedir, &made_new_pgdata, &found_existing_pgdata);
-
/* connection in replication mode to server */
conn = GetConnection();
if (!conn)
@@ -2486,6 +2478,20 @@ main(int argc, char **argv)
exit(1);
}
+ /*
+ * Set umask so that directories/files are created with the same
+ * permissions as directories/files in the source data directory.
+ */
+ umask(pg_mode_mask);
+
+ /*
+ * Verify that the target directory exists, or create it. For plaintext
+ * backups, always require the directory. For tar backups, require it
+ * unless we are writing to stdout.
+ */
+ if (format == 'p' || strcmp(basedir, "-") != 0)
+ verify_dir_is_empty_or_create(basedir, &made_new_pgdata, &found_existing_pgdata);
+
/* determine remote server's xlog segment size */
if (!RetrieveWalSegSize(conn))
disconnect_and_exit(1);
diff --git a/src/bin/pg_basebackup/pg_receivewal.c b/src/bin/pg_basebackup/pg_receivewal.c
index b82e073b86..62b6c686f3 100644
--- a/src/bin/pg_basebackup/pg_receivewal.c
+++ b/src/bin/pg_basebackup/pg_receivewal.c
@@ -19,6 +19,7 @@
#include <sys/stat.h>
#include <unistd.h>
+#include "common/file_perm.h"
#include "libpq-fe.h"
#include "access/xlog_internal.h"
#include "getopt_long.h"
@@ -703,6 +704,12 @@ main(int argc, char **argv)
if (!RunIdentifySystem(conn, NULL, NULL, NULL, &db_name))
disconnect_and_exit(1);
+ /*
+ * Set umask so that directories/files are created with the same
+ * permissions as directories/files in the source data directory.
+ */
+ umask(pg_mode_mask);
+
/* determine remote server's xlog segment size */
if (!RetrieveWalSegSize(conn))
disconnect_and_exit(1);
diff --git a/src/bin/pg_basebackup/pg_recvlogical.c b/src/bin/pg_basebackup/pg_recvlogical.c
index 0866395944..6c272b8f78 100644
--- a/src/bin/pg_basebackup/pg_recvlogical.c
+++ b/src/bin/pg_basebackup/pg_recvlogical.c
@@ -23,6 +23,7 @@
#include "streamutil.h"
#include "access/xlog_internal.h"
+#include "common/file_perm.h"
#include "common/fe_memutils.h"
#include "getopt_long.h"
#include "libpq-fe.h"
@@ -959,6 +960,12 @@ main(int argc, char **argv)
disconnect_and_exit(1);
}
+ /*
+ * Set umask so that directories/files are created with the same
+ * permissions as directories/files in the source data directory.
+ */
+ umask(pg_mode_mask);
+
/* Drop a replication slot. */
if (do_drop_slot)
{
diff --git a/src/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c
index 1438f368ed..4fd536931b 100644
--- a/src/bin/pg_basebackup/streamutil.c
+++ b/src/bin/pg_basebackup/streamutil.c
@@ -23,6 +23,7 @@
#include "access/xlog_internal.h"
#include "common/fe_memutils.h"
+#include "common/file_perm.h"
#include "datatype/timestamp.h"
#include "fe_utils/connect.h"
#include "port/pg_bswap.h"
@@ -32,9 +33,16 @@
uint32 WalSegSz;
+static bool RetrieveDataDirCreatePerm(PGconn *conn);
+
/* SHOW command for replication connection was introduced in version 10 */
#define MINIMUM_VERSION_FOR_SHOW_CMD 100000
+/*
+ * Group access is supported from version 11.
+ */
+#define MINIMUM_VERSION_FOR_GROUP_ACCESS 110000
+
const char *progname;
char *connection_string = NULL;
char *dbhost = NULL;
@@ -254,6 +262,16 @@ GetConnection(void)
exit(1);
}
+ /*
+ * Retrieve the source data directory mode and use it to construct a umask
+ * for creating directories and files.
+ */
+ if (!RetrieveDataDirCreatePerm(tmpconn))
+ {
+ PQfinish(tmpconn);
+ exit(1);
+ }
+
return tmpconn;
}
@@ -327,6 +345,64 @@ RetrieveWalSegSize(PGconn *conn)
return true;
}
+/*
+ * RetrieveDataDirCreatePerm
+ *
+ * This function is used to determine the privileges on the server's PG data
+ * directory and, based on that, set what the permissions will be for
+ * directories and files we create.
+ *
+ * PG11 added support for (optionally) group read/execute rights to be set on
+ * the data directory. Prior to PG11, only the owner was allowed to have rights
+ * on the data directory.
+ */
+static bool
+RetrieveDataDirCreatePerm(PGconn *conn)
+{
+ PGresult *res;
+ int data_directory_mode;
+
+ /* check connection existence */
+ Assert(conn != NULL);
+
+ /* for previous versions leave the default group access */
+ if (PQserverVersion(conn) < MINIMUM_VERSION_FOR_GROUP_ACCESS)
+ return true;
+
+ res = PQexec(conn, "SHOW data_directory_mode");
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ fprintf(stderr, _("%s: could not send replication command \"%s\": %s\n"),
+ progname, "SHOW data_directory_mode", PQerrorMessage(conn));
+
+ PQclear(res);
+ return false;
+ }
+ if (PQntuples(res) != 1 || PQnfields(res) < 1)
+ {
+ fprintf(stderr,
+ _("%s: could not fetch group access flag: got %d rows and %d fields, expected %d rows and %d or more fields\n"),
+ progname, PQntuples(res), PQnfields(res), 1, 1);
+
+ PQclear(res);
+ return false;
+ }
+
+ if (sscanf(PQgetvalue(res, 0, 0), "%o", &data_directory_mode) != 1)
+ {
+ fprintf(stderr, _("%s: group access flag could not be parsed: %s\n"),
+ progname, PQgetvalue(res, 0, 0));
+
+ PQclear(res);
+ return false;
+ }
+
+ SetDataDirectoryCreatePerm(data_directory_mode);
+
+ PQclear(res);
+ return true;
+}
+
/*
* Run IDENTIFY_SYSTEM through a given connection and give back to caller
* some result information if requested:
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index ac5bb99f1b..f502a2e3c7 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -6,7 +6,7 @@ use File::Basename qw(basename dirname);
use File::Path qw(rmtree);
use PostgresNode;
use TestLib;
-use Test::More tests => 105;
+use Test::More tests => 106;
program_help_ok('pg_basebackup');
program_version_ok('pg_basebackup');
@@ -44,10 +44,17 @@ $node->command_fails(
ok(!-d "$tempdir/backup", 'backup directory was cleaned up');
+# Create a backup directory that is not empty so the next commnd will fail
+# but leave the data directory behind
+mkdir("$tempdir/backup")
+ or BAIL_OUT("unable to create $tempdir/backup");
+append_to_file("$tempdir/backup/dir-not-empty.txt");
+
$node->command_fails([ 'pg_basebackup', '-D', "$tempdir/backup", '-n' ],
'failing run with no-clean option');
ok(-d "$tempdir/backup", 'backup directory was created and left behind');
+rmtree("$tempdir/backup");
open my $conf, '>>', "$pgdata/postgresql.conf";
print $conf "max_replication_slots = 10\n";
@@ -200,11 +207,17 @@ unlink "$pgdata/$superlongname";
# skip on Windows.
SKIP:
{
- skip "symlinks not supported on Windows", 17 if ($windows_os);
+ skip "symlinks not supported on Windows", 18 if ($windows_os);
# Move pg_replslot out of $pgdata and create a symlink to it.
$node->stop;
+ # Set umask so test directories and files are created with group permissions
+ umask(0027);
+
+ # Enable group permissions on PGDATA
+ chmod_recursive("$pgdata", 0750, 0640);
+
rename("$pgdata/pg_replslot", "$tempdir/pg_replslot")
or BAIL_OUT "could not move $pgdata/pg_replslot";
symlink("$tempdir/pg_replslot", "$pgdata/pg_replslot")
@@ -275,6 +288,10 @@ SKIP:
"tablespace symlink was updated");
closedir $dh;
+ # Group access should be enabled on all backup files
+ ok(check_mode_recursive("$tempdir/backup1", 0750, 0640),
+ "check backup dir permissions");
+
# Unlogged relation forks other than init should not be copied
my ($tblspc1UnloggedBackupPath) = $tblspc1UnloggedPath =~ /[^\/]*\/[^\/]*\/[^\/]*$/g;
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index 5ede385e6a..143021de05 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -2171,7 +2171,7 @@ main(int argc, char **argv)
*/
argv0 = argv[0];
- /* Set dir/file mode mask */
+ /* Set restrictive mode mask until PGDATA permissions are checked */
umask(PG_MODE_MASK_OWNER);
/* support --help and --version even if invoked as root */
@@ -2407,6 +2407,16 @@ main(int argc, char **argv)
snprintf(version_file, MAXPGPATH, "%s/PG_VERSION", pg_data);
snprintf(pid_file, MAXPGPATH, "%s/postmaster.pid", pg_data);
snprintf(backup_file, MAXPGPATH, "%s/backup_label", pg_data);
+
+ /*
+ * Set mask based on PGDATA permissions,
+ *
+ * Don't error here if the data directory cannot be stat'd. This is
+ * handled differently based on the command and we don't want to
+ * interfere with that logic.
+ */
+ if (GetDataDirectoryCreatePerm(pg_data))
+ umask(pg_mode_mask);
}
switch (ctl_command)
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl
index 067a084c87..2f9dfa7b81 100644
--- a/src/bin/pg_ctl/t/001_start_stop.pl
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -2,9 +2,11 @@ use strict;
use warnings;
use Config;
+use Fcntl ':mode';
+use File::stat qw{lstat};
use PostgresNode;
use TestLib;
-use Test::More tests => 21;
+use Test::More tests => 24;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -74,6 +76,27 @@ SKIP:
ok(check_mode_recursive("$tempdir/data", 0700, 0600));
}
+# Log file for group access test
+$logFileName = "$tempdir/data/perm-test-640.log";
+
+SKIP:
+{
+ skip "group access not supported on Windows", 3 if ($windows_os);
+
+ system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
+
+ # Change the data dir mode so log file will be created with group read
+ # privileges on the next start
+ chmod_recursive("$tempdir/data", 0750, 0640);
+
+ command_ok(
+ [ 'pg_ctl', 'start', '-D', "$tempdir/data", '-l', $logFileName ],
+ 'start server to check group permissions');
+
+ ok(-f $logFileName);
+ ok(check_mode_recursive("$tempdir/data", 0750, 0640));
+}
+
command_ok([ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
'pg_ctl restart with server running');
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index bdf71886ee..8a0a805f1e 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -363,6 +363,16 @@ main(int argc, char *argv[])
exit(1);
}
+ /* Set mask based on PGDATA permissions */
+ if (!GetDataDirectoryCreatePerm(DataDir))
+ {
+ fprintf(stderr, _("%s: unable to read permissions from \"%s\"\n"),
+ progname, DataDir);
+ exit(1);
+ }
+
+ umask(pg_mode_mask);
+
/* Check that data directory matches our server version */
CheckDataVersion();
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index 7b632c7dcd..63d9bd517d 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -115,11 +115,13 @@ sub check_query
sub setup_cluster
{
- my $extra_name = shift;
+ my $extra_name = shift; # Used to differentiate clusters
+ my $extra = shift; # Extra params for initdb
# Initialize master, data checksums are mandatory
$node_master = get_new_node('master' . ($extra_name ? "_${extra_name}" : ''));
- $node_master->init(allows_streaming => 1);
+ $node_master->init(
+ allows_streaming => 1, extra => $extra);
# Set wal_keep_segments to prevent WAL segment recycling after enforced
# checkpoints in the tests.
$node_master->append_conf('postgresql.conf', qq(
@@ -237,7 +239,8 @@ sub run_pg_rewind
"$tmp_folder/master-postgresql.conf.tmp",
"$master_pgdata/postgresql.conf");
- chmod(0600, "$master_pgdata/postgresql.conf")
+ chmod($node_master->group_access() ? 0640 : 0600,
+ "$master_pgdata/postgresql.conf")
or BAIL_OUT(
"unable to set permissions for $master_pgdata/postgresql.conf");
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 72ab2f8d5e..b9ea6a4c21 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -24,6 +24,7 @@
#include "access/xlog_internal.h"
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
+#include "common/file_perm.h"
#include "common/restricted_token.h"
#include "getopt_long.h"
#include "storage/bufpage.h"
@@ -185,6 +186,16 @@ main(int argc, char **argv)
exit(1);
}
+ /* Set mask based on PGDATA permissions */
+ if (!GetDataDirectoryCreatePerm(datadir_target))
+ {
+ fprintf(stderr, _("%s: unable to read permissions from \"%s\"\n"),
+ progname, datadir_target);
+ exit(1);
+ }
+
+ umask(pg_mode_mask);
+
/*
* Don't allow pg_rewind to be run as root, to avoid overwriting the
* ownership of files in the data directory. We need only check for root
diff --git a/src/bin/pg_rewind/t/002_databases.pl b/src/bin/pg_rewind/t/002_databases.pl
index 37cdd712f3..c364965d3a 100644
--- a/src/bin/pg_rewind/t/002_databases.pl
+++ b/src/bin/pg_rewind/t/002_databases.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 6;
use RewindTest;
@@ -9,7 +9,7 @@ sub run_test
{
my $test_mode = shift;
- RewindTest::setup_cluster($test_mode);
+ RewindTest::setup_cluster($test_mode, ['-g']);
RewindTest::start_master();
# Create a database in master.
@@ -42,6 +42,15 @@ template1
),
'database names');
+ # Permissions on PGDATA should have group permissions
+ SKIP:
+ {
+ skip "unix-style permissions not supported on Windows", 1 if ($windows_os);
+
+ ok(check_mode_recursive($node_master->data_dir(), 0750, 0640),
+ 'check PGDATA permissions');
+ }
+
RewindTest::clean_rewind_test();
}
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 1d35188143..cc8e8c94c5 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -79,7 +79,7 @@ main(int argc, char **argv)
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_upgrade"));
- /* Ensure that all files created by pg_upgrade are non-world-readable */
+ /* Set default restrictive mask until new cluster permissions are read */
umask(PG_MODE_MASK_OWNER);
parseCommandLine(argc, argv);
@@ -100,6 +100,16 @@ main(int argc, char **argv)
check_cluster_compatibility(live_check);
+ /* Set mask based on PGDATA permissions */
+ if (!GetDataDirectoryCreatePerm(new_cluster.pgdata))
+ {
+ pg_log(PG_FATAL, "unable to read permissions from \"%s\"\n",
+ new_cluster.pgdata);
+ exit(1);
+ }
+
+ umask(pg_mode_mask);
+
check_and_dump_old_cluster(live_check);
diff --git a/src/bin/pg_upgrade/test.sh b/src/bin/pg_upgrade/test.sh
index 574639d47e..24631a91c6 100644
--- a/src/bin/pg_upgrade/test.sh
+++ b/src/bin/pg_upgrade/test.sh
@@ -20,9 +20,9 @@ unset MAKELEVEL
# Run a given "initdb" binary and overlay the regression testing
# authentication configuration.
standard_initdb() {
- # To increase coverage of non-standard segment size without
- # increase test runtime, run these tests with a lower setting.
- "$1" -N --wal-segsize 1
+ # To increase coverage of non-standard segment size and group access
+ # without increasing test runtime, run these tests with a custom setting.
+ "$1" -N --wal-segsize 1 -g
if [ -n "$TEMP_CONFIG" -a -r "$TEMP_CONFIG" ]
then
cat "$TEMP_CONFIG" >> "$PGDATA/postgresql.conf"
@@ -230,14 +230,14 @@ standard_initdb 'initdb'
pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "${PGDATA}" -b "$oldbindir" -B "$bindir" -p "$PGPORT" -P "$PGPORT"
-# make sure all directories and files have correct permissions
-if [ $(find ${PGDATA} -type f ! -perm 600 | wc -l) -ne 0 ]; then
- echo "files in PGDATA with permission != 600";
+# make sure all directories and files have group permissions
+if [ $(find ${PGDATA} -type f ! -perm 640 | wc -l) -ne 0 ]; then
+ echo "files in PGDATA with permission != 640";
exit 1;
fi
-if [ $(find ${PGDATA} -type d ! -perm 700 | wc -l) -ne 0 ]; then
- echo "directories in PGDATA with permission != 700";
+if [ $(find ${PGDATA} -type d ! -perm 750 | wc -l) -ne 0 ]; then
+ echo "directories in PGDATA with permission != 750";
exit 1;
fi
diff --git a/src/common/file_perm.c b/src/common/file_perm.c
index fdfbb9a44c..d640d6a1fd 100644
--- a/src/common/file_perm.c
+++ b/src/common/file_perm.c
@@ -12,8 +12,76 @@
*/
#include <sys/stat.h>
+#include "c.h"
#include "common/file_perm.h"
/* Modes for creating directories and files in the data directory */
-int pg_dir_create_mode = PG_DIR_MODE_OWNER;
-int pg_file_create_mode = PG_FILE_MODE_OWNER;
+int pg_dir_create_mode = PG_DIR_MODE_OWNER;
+int pg_file_create_mode = PG_FILE_MODE_OWNER;
+
+/*
+ * Mode mask to pass to umask(). This is more of a preventative measure since
+ * all file/directory creates should be performed using the create modes above.
+ */
+int pg_mode_mask = PG_MODE_MASK_OWNER;
+
+/*
+ * Set create modes and mask to use when writing to PGDATA based on the data
+ * directory mode passed. If group read/execute are present in the mode, then
+ * create modes and mask will be relaxed to allow group read/execute on all
+ * newly created files and directories.
+ */
+void
+SetDataDirectoryCreatePerm(int dataDirMode)
+{
+ /* If the data directory mode has group access */
+ if ((PG_DIR_MODE_GROUP & dataDirMode) == PG_DIR_MODE_GROUP)
+ {
+ pg_dir_create_mode = PG_DIR_MODE_GROUP;
+ pg_file_create_mode = PG_FILE_MODE_GROUP;
+ pg_mode_mask = PG_MODE_MASK_GROUP;
+ }
+ /* Else use default permissions */
+ else
+ {
+ pg_dir_create_mode = PG_DIR_MODE_OWNER;
+ pg_file_create_mode = PG_FILE_MODE_OWNER;
+ pg_mode_mask = PG_MODE_MASK_OWNER;
+ }
+}
+
+#ifdef FRONTEND
+
+/*
+ * Get the create modes and mask to use when writing to PGDATA by examining the
+ * mode of the PGDATA directory and calling SetDataDirectoryCreatePerm().
+ *
+ * Errors are not handled here and should be reported by the application when
+ * false is returned.
+ *
+ * Suppress when on Windows, because there may not be proper support for Unix-y
+ * file permissions.
+ */
+bool
+GetDataDirectoryCreatePerm(const char *dataDir)
+{
+#if !defined(WIN32) && !defined(__CYGWIN__)
+ struct stat statBuf;
+
+ /*
+ * If an error occurs getting the mode then return false. The caller is
+ * responsible for generating an error, if appropriate, indicating that we
+ * were unable to access the data directory.
+ */
+ if (stat(dataDir, &statBuf) == -1)
+ return false;
+
+ /* Set permissions */
+ SetDataDirectoryCreatePerm(statBuf.st_mode);
+
+ return true;
+#endif /* !defined(WIN32) && !defined(__CYGWIN__) */
+}
+
+
+#endif /* FRONTEND */
diff --git a/src/include/common/file_perm.h b/src/include/common/file_perm.h
index 37631a7191..3090f78931 100644
--- a/src/include/common/file_perm.h
+++ b/src/include/common/file_perm.h
@@ -21,14 +21,34 @@
*/
#define PG_MODE_MASK_OWNER (S_IRWXG | S_IRWXO)
+/*
+ * Mode mask for data directory permissions that also allows group read/execute.
+ */
+#define PG_MODE_MASK_GROUP (S_IWGRP | S_IRWXO)
+
/* Default mode for creating directories */
#define PG_DIR_MODE_OWNER S_IRWXU
+/* Mode for creating directories that allows group read/execute */
+#define PG_DIR_MODE_GROUP (S_IRWXU | S_IRGRP | S_IXGRP)
+
/* Default mode for creating files */
#define PG_FILE_MODE_OWNER (S_IRUSR | S_IWUSR)
+/* Mode for creating files that allows group read */
+#define PG_FILE_MODE_GROUP (S_IRUSR | S_IWUSR | S_IRGRP)
+
/* Modes for creating directories and files in the data directory */
-extern int pg_dir_create_mode;
-extern int pg_file_create_mode;
+extern int pg_dir_create_mode;
+extern int pg_file_create_mode;
+
+/* Mode mask to pass to umask() */
+extern int pg_mode_mask;
+
+/* Set permissions and mask based on the provided mode */
+extern void SetDataDirectoryCreatePerm(int dataDirMode);
+
+/* Set permissions and mask based on the mode of the data directory */
+extern bool GetDataDirectoryCreatePerm(const char *dataDir);
#endif /* FILE_PERM_H */
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index b5ad841968..e167ee8fcb 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -153,6 +153,7 @@ extern PGDLLIMPORT bool IsBinaryUpgrade;
extern PGDLLIMPORT bool ExitOnAnyError;
extern PGDLLIMPORT char *DataDir;
+extern PGDLLIMPORT int data_directory_mode;
extern PGDLLIMPORT int NBuffers;
extern PGDLLIMPORT int MaxBackends;
@@ -323,6 +324,7 @@ extern void SetSessionAuthorization(Oid userid, bool is_superuser);
extern Oid GetCurrentRoleId(void);
extern void SetCurrentRoleId(Oid roleid, bool is_superuser);
+extern void checkDataDir(void);
extern void SetDataDir(const char *dir);
extern void ChangeToDataDir(void);
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 76e571b98c..1bd91524d7 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -86,9 +86,11 @@ use Carp;
use Config;
use Cwd;
use Exporter 'import';
+use Fcntl qw(:mode);
use File::Basename;
use File::Path qw(rmtree);
use File::Spec;
+use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
use RecursiveCopy;
@@ -269,6 +271,26 @@ sub connstr
=pod
+=item $node->group_access()
+
+Does the data dir allow group access?
+
+=cut
+
+sub group_access
+{
+ my ($self) = @_;
+
+ my $dir_stat = stat($self->data_dir);
+
+ defined($dir_stat)
+ or die('unable to stat ' . $self->data_dir);
+
+ return (S_IMODE($dir_stat->mode) == 0750);
+}
+
+=pod
+
=item $node->data_dir()
Returns the path to the data directory. postgresql.conf and pg_hba.conf are
@@ -460,6 +482,9 @@ sub init
}
close $conf;
+ chmod($self->group_access ? 0640 : 0600, "$pgdata/postgresql.conf")
+ or die("unable to set permissions for $pgdata/postgresql.conf");
+
$self->set_replication_conf if $params{allows_streaming};
$self->enable_archiving if $params{has_archiving};
}
@@ -485,7 +510,7 @@ sub append_conf
TestLib::append_to_file($conffile, $str . "\n");
- chmod(0600, $conffile)
+ chmod($self->group_access() ? 0640 : 0600, $conffile)
or die("unable to set permissions for $conffile");
}
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index 93610e4bc4..8047404efd 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -31,6 +31,7 @@ our @EXPORT = qw(
slurp_file
append_to_file
check_mode_recursive
+ chmod_recursive
check_pg_config
system_or_bail
system_log
@@ -313,6 +314,30 @@ sub check_mode_recursive
return $result;
}
+# Change mode recursively on a directory
+sub chmod_recursive
+{
+ my ($dir, $dir_mode, $file_mode) = @_;
+
+ find
+ (
+ {follow_fast => 1,
+ wanted =>
+ sub
+ {
+ my $file_stat = stat($File::Find::name);
+
+ if (defined($file_stat))
+ {
+ chmod(S_ISDIR($file_stat->mode) ? $dir_mode : $file_mode,
+ $File::Find::name)
+ or die "unable to chmod $File::Find::name";
+ }
+ }},
+ $dir
+ );
+}
+
# Check presence of a given regexp within pg_config.h for the installation
# where tests are running, returning a match status result depending on
# that.
--
2.14.3 (Apple Git-98)
David,
* David Steele (david@pgmasters.net) wrote:
On 4/6/18 10:22 PM, Stephen Frost wrote:
* David Steele (david@pgmasters.net) wrote:
On 4/6/18 6:04 PM, David Steele wrote:
On 4/6/18 3:02 PM, Stephen Frost wrote:
- Further discussion in the commit messages
Agreed, these need some more work. I'm happy to do that but I'll need a
bit more time. Have a look at the new patches and I'll work on some
better messages.I'm sure you'll want to reword some things, but I think these commit
messages capture the essential changes for each patch.Thanks much. I've taken (most) of these, adjusting a few bits here and
there.I've been back over the patch again, mostly improving the commit
messages, comments, and docs. I also looked over the code and tests
again and they're looking pretty good to me, so I'll be looking to
commit this tomorrow afternoon or so US/Eastern.OK, one last review. I did't make any code changes, but I improved some
comments, added documentation and fixed a test.
Thanks! I took those and then added a bit more commentary around the
umask() calls in the utilities and fixed a typo or two and then pushed
it.
Time to watch the buildfarm, particularly for Windows hosts just in case
there's something in the regression tests which aren't working correctly
on that platform. I was able to run the full regression suite locally
before committing, though given the recent activity, we may see failures
attributed to this patch which are due to unrelated instability.
Thanks again!
Stephen
On 4/7/18 5:49 PM, Stephen Frost wrote:
* David Steele (david@pgmasters.net) wrote:
OK, one last review. I did't make any code changes, but I improved some
comments, added documentation and fixed a test.Thanks! I took those and then added a bit more commentary around the
umask() calls in the utilities and fixed a typo or two and then pushed
it.
Excellent, thank you!
Time to watch the buildfarm, particularly for Windows hosts just in case
there's something in the regression tests which aren't working correctly
on that platform. I was able to run the full regression suite locally
before committing, though given the recent activity, we may see failures
attributed to this patch which are due to unrelated instability.
I'll have dinner then come back and take a look.
--
-David
david@pgmasters.net
Stephen Frost <sfrost@snowman.net> writes:
Time to watch the buildfarm,
That didn't take long.
https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=culicidae&dt=2018-04-07%2021%3A50%3A02
(I'm really starting to get a bit upset at the amount of stuff that's
being pushed in on the very last day.)
regards, tom lane
Tom,
* Tom Lane (tgl@sss.pgh.pa.us) wrote:
Stephen Frost <sfrost@snowman.net> writes:
Time to watch the buildfarm,
That didn't take long.
https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=culicidae&dt=2018-04-07%2021%3A50%3A02
Yes, I'm taking a look at it.
Thanks!
Stephen
Stephen Frost <sfrost@snowman.net> writes:
* Tom Lane (tgl@sss.pgh.pa.us) wrote:
https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=culicidae&dt=2018-04-07%2021%3A50%3A02
Yes, I'm taking a look at it.
Since other animals are coming back successfully, my first suspicion
lights on culicidae's use of EXEC_BACKEND. Did you test that case?
(If not, this bodes ill for the Windows results.)
regards, tom lane
Greetings,
* Tom Lane (tgl@sss.pgh.pa.us) wrote:
Stephen Frost <sfrost@snowman.net> writes:
* Tom Lane (tgl@sss.pgh.pa.us) wrote:
https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=culicidae&dt=2018-04-07%2021%3A50%3A02
Yes, I'm taking a look at it.
Since other animals are coming back successfully, my first suspicion
lights on culicidae's use of EXEC_BACKEND. Did you test that case?
(If not, this bodes ill for the Windows results.)
The vast majority of this patch isn't something that's relevant to
Windows anyway, so I'm hopeful that those animals will stay green (if
not, it's probably that we need to mark some other test as
not-on-Windows).
We have EXEC_BACKEND on the Unix-y systems too though, so that needs to
be fixed. I'm pretty sure the issue here is that SubPostmasterMain()
needs to also be checking/updating the umask() based on the data
directory, as is done in PostmasterMain.
Testing that out now and if that looks good then I'll push that and
hopefully it'll make cullcidae green.
Thanks!
Stephen
Greetings,
* Tom Lane (tgl@sss.pgh.pa.us) wrote:
(If not, this bodes ill for the Windows results.)
Ah, there go the Windows builds... Missing a #else clause where we
should have one for those systems, will fix.
Thanks!
Stephen
Hi Michael,
On 4/6/18 10:20 AM, Michael Paquier wrote:
On Fri, Apr 06, 2018 at 09:15:15AM -0400, Stephen Frost wrote:
I'll reply to David's last email (where the latest set of patches were
included) with my comments/suggestions and I expect we'll be able to get
those addressed today and have a final patch to post tonight, with an
eye towards committing it tomorrow.The feature freeze is on the 8th, so I am going to have limited room to
comment on things until that day. If something gets committed, I am
pretty sure that I'll get out of my pocket a couple of things to improve
the feature and its interface anyway if of course you are ready to
accept that.
Improvements are always welcome! The core focus of the patch hasn't
changed over the last year, but we've found better ways to implement it
over time. I'm sure there's more we can do.
I have limited my role to be a reviewer, so I refrained
myself from writing any code ;)
Your dedicated and tireless review helped get this patch over the line.
Thank you very much for all your hard work.
--
-David
david@pgmasters.net