1
0
mirror of https://github.com/postgres/postgres.git synced 2025-08-27 07:42:10 +03:00

Avoid using unsafe search_path settings during dump and restore.

Historically, pg_dump has "set search_path = foo, pg_catalog" when
dumping an object in schema "foo", and has also caused that setting
to be used while restoring the object.  This is problematic because
functions and operators in schema "foo" could capture references meant
to refer to pg_catalog entries, both in the queries issued by pg_dump
and those issued during the subsequent restore run.  That could
result in dump/restore misbehavior, or in privilege escalation if a
nefarious user installs trojan-horse functions or operators.

This patch changes pg_dump so that it does not change the search_path
dynamically.  The emitted restore script sets the search_path to what
was used at dump time, and then leaves it alone thereafter.  Created
objects are placed in the correct schema, regardless of the active
search_path, by dint of schema-qualifying their names in the CREATE
commands, as well as in subsequent ALTER and ALTER-like commands.

Since this change requires a change in the behavior of pg_restore
when processing an archive file made according to this new convention,
bump the archive file version number; old versions of pg_restore will
therefore refuse to process files made with new versions of pg_dump.

Security: CVE-2018-1058
This commit is contained in:
Tom Lane
2018-02-26 10:18:22 -05:00
parent 7dd49bdb74
commit a8fc37a638
12 changed files with 882 additions and 1126 deletions

View File

@@ -69,6 +69,7 @@ static void _selectOutputSchema(ArchiveHandle *AH, const char *schemaName);
static void _selectTablespace(ArchiveHandle *AH, const char *tablespace);
static void processEncodingEntry(ArchiveHandle *AH, TocEntry *te);
static void processStdStringsEntry(ArchiveHandle *AH, TocEntry *te);
static void processSearchPathEntry(ArchiveHandle *AH, TocEntry *te);
static teReqs _tocEntryRequired(TocEntry *te, teSection curSection, RestoreOptions *ropt);
static RestorePass _tocEntryRestorePass(TocEntry *te);
static bool _tocEntryIsACL(TocEntry *te);
@@ -886,7 +887,9 @@ restore_toc_entry(ArchiveHandle *AH, TocEntry *te, bool is_parallel)
ahprintf(AH, "TRUNCATE TABLE %s%s;\n\n",
(PQserverVersion(AH->connection) >= 80400 ?
"ONLY " : ""),
fmtId(te->tag));
fmtQualifiedId(PQserverVersion(AH->connection),
te->namespace,
te->tag));
}
/*
@@ -973,10 +976,10 @@ _disableTriggersIfNecessary(ArchiveHandle *AH, TocEntry *te)
/*
* Disable them.
*/
_selectOutputSchema(AH, te->namespace);
ahprintf(AH, "ALTER TABLE %s DISABLE TRIGGER ALL;\n\n",
fmtId(te->tag));
fmtQualifiedId(PQserverVersion(AH->connection),
te->namespace,
te->tag));
}
static void
@@ -1001,10 +1004,10 @@ _enableTriggersIfNecessary(ArchiveHandle *AH, TocEntry *te)
/*
* Enable them.
*/
_selectOutputSchema(AH, te->namespace);
ahprintf(AH, "ALTER TABLE %s ENABLE TRIGGER ALL;\n\n",
fmtId(te->tag));
fmtQualifiedId(PQserverVersion(AH->connection),
te->namespace,
te->tag));
}
/*
@@ -2670,6 +2673,8 @@ ReadToc(ArchiveHandle *AH)
processEncodingEntry(AH, te);
else if (strcmp(te->desc, "STDSTRINGS") == 0)
processStdStringsEntry(AH, te);
else if (strcmp(te->desc, "SEARCHPATH") == 0)
processSearchPathEntry(AH, te);
}
}
@@ -2717,14 +2722,25 @@ processStdStringsEntry(ArchiveHandle *AH, TocEntry *te)
te->defn);
}
static void
processSearchPathEntry(ArchiveHandle *AH, TocEntry *te)
{
/*
* te->defn should contain a command to set search_path. We just copy it
* verbatim for use later.
*/
AH->public.searchpath = pg_strdup(te->defn);
}
static teReqs
_tocEntryRequired(TocEntry *te, teSection curSection, RestoreOptions *ropt)
{
teReqs res = REQ_SCHEMA | REQ_DATA;
/* ENCODING and STDSTRINGS items are treated specially */
/* These items are treated specially */
if (strcmp(te->desc, "ENCODING") == 0 ||
strcmp(te->desc, "STDSTRINGS") == 0)
strcmp(te->desc, "STDSTRINGS") == 0 ||
strcmp(te->desc, "SEARCHPATH") == 0)
return REQ_SPECIAL;
/* If it's an ACL, maybe ignore it */
@@ -2930,6 +2946,10 @@ _doSetFixedOutputState(ArchiveHandle *AH)
if (ropt && ropt->use_role)
ahprintf(AH, "SET ROLE %s;\n", fmtId(ropt->use_role));
/* Select the dump-time search_path */
if (AH->public.searchpath)
ahprintf(AH, "%s", AH->public.searchpath);
/* Make sure function checking is disabled */
ahprintf(AH, "SET check_function_bodies = false;\n");
@@ -3134,6 +3154,15 @@ _selectOutputSchema(ArchiveHandle *AH, const char *schemaName)
{
PQExpBuffer qry;
/*
* If there was a SEARCHPATH TOC entry, we're supposed to just stay with
* that search_path rather than switching to entry-specific paths.
* Otherwise, it's an old archive that will not restore correctly unless
* we set the search_path as it's expecting.
*/
if (AH->public.searchpath)
return;
if (!schemaName || *schemaName == '\0' ||
(AH->currSchema && strcmp(AH->currSchema, schemaName) == 0))
return; /* no need to do anything */
@@ -3263,8 +3292,10 @@ _getObjectDescription(PQExpBuffer buf, TocEntry *te, ArchiveHandle *AH)
strcmp(type, "SERVER") == 0 ||
strcmp(type, "USER MAPPING") == 0)
{
/* We already know that search_path was set properly */
appendPQExpBuffer(buf, "%s %s", type, fmtId(te->tag));
appendPQExpBuffer(buf, "%s ", type);
if (te->namespace && *te->namespace)
appendPQExpBuffer(buf, "%s.", fmtId(te->namespace));
appendPQExpBufferStr(buf, fmtId(te->tag));
return;
}