Skip to content

Commit 72ef167

Browse files
Restrict accesses to non-system views and foreign tables during pg_dump.
When pg_dump retrieves the list of database objects and performs the data dump, there was possibility that objects are replaced with others of the same name, such as views, and access them. This vulnerability could result in code execution with superuser privileges during the pg_dump process. This issue can arise when dumping data of sequences, foreign tables (only 13 or later), or tables registered with a WHERE clause in the extension configuration table. To address this, pg_dump now utilizes the newly introduced restrict_nonsystem_relation_kind GUC parameter to restrict the accesses to non-system views and foreign tables during the dump process. This new GUC parameter is added to back branches too, but these changes do not require cluster recreation. Back-patch to all supported branches. Reviewed-by: Noah Misch Security: CVE-2024-7348 Backpatch-through: 12
1 parent 426a224 commit 72ef167

File tree

14 files changed

+254
-1
lines changed

14 files changed

+254
-1
lines changed

contrib/postgres_fdw/expected/postgres_fdw.out

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,17 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft_empty ORDER BY c1;
631631
Remote SQL: SELECT c1, c2 FROM public.loct_empty ORDER BY c1 ASC NULLS LAST
632632
(3 rows)
633633

634+
-- test restriction on non-system foreign tables.
635+
SET restrict_nonsystem_relation_kind TO 'foreign-table';
636+
SELECT * from ft1 where c1 < 1; -- ERROR
637+
ERROR: access to non-system foreign table is restricted
638+
INSERT INTO ft1 (c1) VALUES (1); -- ERROR
639+
ERROR: access to non-system foreign table is restricted
640+
DELETE FROM ft1 WHERE c1 = 1; -- ERROR
641+
ERROR: access to non-system foreign table is restricted
642+
TRUNCATE ft1; -- ERROR
643+
ERROR: access to non-system foreign table is restricted
644+
RESET restrict_nonsystem_relation_kind;
634645
-- ===================================================================
635646
-- WHERE with remotely-executable conditions
636647
-- ===================================================================

contrib/postgres_fdw/sql/postgres_fdw.sql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,14 @@ DELETE FROM loct_empty;
321321
ANALYZE ft_empty;
322322
EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft_empty ORDER BY c1;
323323

324+
-- test restriction on non-system foreign tables.
325+
SET restrict_nonsystem_relation_kind TO 'foreign-table';
326+
SELECT * from ft1 where c1 < 1; -- ERROR
327+
INSERT INTO ft1 (c1) VALUES (1); -- ERROR
328+
DELETE FROM ft1 WHERE c1 = 1; -- ERROR
329+
TRUNCATE ft1; -- ERROR
330+
RESET restrict_nonsystem_relation_kind;
331+
324332
-- ===================================================================
325333
-- WHERE with remotely-executable conditions
326334
-- ===================================================================

doc/src/sgml/config.sgml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8967,6 +8967,23 @@ SET XML OPTION { DOCUMENT | CONTENT };
89678967
</listitem>
89688968
</varlistentry>
89698969

8970+
<varlistentry id="guc-restrict-nonsystem-relation-kind" xreflabel="restrict_nonsystem_relation_kind">
8971+
<term><varname>restrict_nonsystem_relation_kind</varname> (<type>string</type>)
8972+
<indexterm>
8973+
<primary><varname>restrict_nonsystem_relation_kind</varname></primary>
8974+
<secondary>configuration parameter</secondary>
8975+
</indexterm>
8976+
</term>
8977+
<listitem>
8978+
<para>
8979+
This variable specifies relation kind to which access is restricted.
8980+
It contains a comma-separated list of relation kind. Currently, the
8981+
supported relation kinds are <literal>view</literal> and
8982+
<literal>foreign-table</literal>.
8983+
</para>
8984+
</listitem>
8985+
</varlistentry>
8986+
89708987
</variablelist>
89718988
</sect2>
89728989
<sect2 id="runtime-config-client-format">

doc/src/sgml/ref/pg_dump.sgml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -822,6 +822,14 @@ PostgreSQL documentation
822822
The only exception is that an empty pattern is disallowed.
823823
</para>
824824

825+
<note>
826+
<para>
827+
Using wildcards in <option>--include-foreign-data</option> may result
828+
in access to unexpected foreign servers. Also, to use this option securely,
829+
make sure that the named server must have a trusted owner.
830+
</para>
831+
</note>
832+
825833
<note>
826834
<para>
827835
When <option>--include-foreign-data</option> is specified,

src/backend/foreign/foreign.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "foreign/foreign.h"
2323
#include "lib/stringinfo.h"
2424
#include "miscadmin.h"
25+
#include "tcop/tcopprot.h"
2526
#include "utils/builtins.h"
2627
#include "utils/memutils.h"
2728
#include "utils/rel.h"
@@ -321,6 +322,15 @@ GetFdwRoutine(Oid fdwhandler)
321322
Datum datum;
322323
FdwRoutine *routine;
323324

325+
/* Check if the access to foreign tables is restricted */
326+
if (unlikely((restrict_nonsystem_relation_kind & RESTRICT_RELKIND_FOREIGN_TABLE) != 0))
327+
{
328+
/* there must not be built-in FDW handler */
329+
ereport(ERROR,
330+
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
331+
errmsg("access to non-system foreign table is restricted")));
332+
}
333+
324334
datum = OidFunctionCall0(fdwhandler);
325335
routine = (FdwRoutine *) DatumGetPointer(datum);
326336

src/backend/optimizer/plan/createplan.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
#include "parser/parse_clause.h"
4242
#include "parser/parsetree.h"
4343
#include "partitioning/partprune.h"
44+
#include "tcop/tcopprot.h"
4445
#include "utils/lsyscache.h"
4546

4647

@@ -7035,7 +7036,19 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
70357036

70367037
Assert(rte->rtekind == RTE_RELATION);
70377038
if (rte->relkind == RELKIND_FOREIGN_TABLE)
7039+
{
7040+
/* Check if the access to foreign tables is restricted */
7041+
if (unlikely((restrict_nonsystem_relation_kind & RESTRICT_RELKIND_FOREIGN_TABLE) != 0))
7042+
{
7043+
/* there must not be built-in foreign tables */
7044+
Assert(rte->relid >= FirstNormalObjectId);
7045+
ereport(ERROR,
7046+
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
7047+
errmsg("access to non-system foreign table is restricted")));
7048+
}
7049+
70387050
fdwroutine = GetFdwRoutineByRelId(rte->relid);
7051+
}
70397052
else
70407053
fdwroutine = NULL;
70417054
}

src/backend/optimizer/util/plancat.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
#include "rewrite/rewriteManip.h"
4747
#include "statistics/statistics.h"
4848
#include "storage/bufmgr.h"
49+
#include "tcop/tcopprot.h"
4950
#include "utils/builtins.h"
5051
#include "utils/lsyscache.h"
5152
#include "utils/partcache.h"
@@ -462,6 +463,17 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
462463
/* Grab foreign-table info using the relcache, while we have it */
463464
if (relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
464465
{
466+
/* Check if the access to foreign tables is restricted */
467+
if (unlikely((restrict_nonsystem_relation_kind & RESTRICT_RELKIND_FOREIGN_TABLE) != 0))
468+
{
469+
/* there must not be built-in foreign tables */
470+
Assert(RelationGetRelid(relation) >= FirstNormalObjectId);
471+
472+
ereport(ERROR,
473+
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
474+
errmsg("access to non-system foreign table is restricted")));
475+
}
476+
465477
rel->serverid = GetForeignServerIdByRelId(RelationGetRelid(relation));
466478
rel->fdwroutine = GetFdwRoutineForRelation(relation, true);
467479
}

src/backend/rewrite/rewriteHandler.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
#include "rewrite/rewriteManip.h"
4242
#include "rewrite/rewriteSearchCycle.h"
4343
#include "rewrite/rowsecurity.h"
44+
#include "tcop/tcopprot.h"
4445
#include "utils/builtins.h"
4546
#include "utils/lsyscache.h"
4647
#include "utils/rel.h"
@@ -1730,6 +1731,14 @@ ApplyRetrieveRule(Query *parsetree,
17301731
if (rule->qual != NULL)
17311732
elog(ERROR, "cannot handle qualified ON SELECT rule");
17321733

1734+
/* Check if the expansion of non-system views are restricted */
1735+
if (unlikely((restrict_nonsystem_relation_kind & RESTRICT_RELKIND_VIEW) != 0 &&
1736+
RelationGetRelid(relation) >= FirstNormalObjectId))
1737+
ereport(ERROR,
1738+
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1739+
errmsg("access to non-system view \"%s\" is restricted",
1740+
RelationGetRelationName(relation))));
1741+
17331742
if (rt_index == parsetree->resultRelation)
17341743
{
17351744
/*
@@ -3124,6 +3133,14 @@ rewriteTargetView(Query *parsetree, Relation view)
31243133
}
31253134
}
31263135

3136+
/* Check if the expansion of non-system views are restricted */
3137+
if (unlikely((restrict_nonsystem_relation_kind & RESTRICT_RELKIND_VIEW) != 0 &&
3138+
RelationGetRelid(view) >= FirstNormalObjectId))
3139+
ereport(ERROR,
3140+
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
3141+
errmsg("access to non-system view \"%s\" is restricted",
3142+
RelationGetRelationName(view))));
3143+
31273144
/*
31283145
* For INSERT/UPDATE the modified columns must all be updatable.
31293146
*/

src/backend/tcop/postgres.c

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
#include "utils/snapmgr.h"
8181
#include "utils/timeout.h"
8282
#include "utils/timestamp.h"
83+
#include "utils/varlena.h"
8384

8485
/* ----------------
8586
* global variables
@@ -104,6 +105,9 @@ int PostAuthDelay = 0;
104105
/* Time between checks that the client is still connected. */
105106
int client_connection_check_interval = 0;
106107

108+
/* flags for non-system relation kinds to restrict use */
109+
int restrict_nonsystem_relation_kind;
110+
107111
/* ----------------
108112
* private typedefs etc
109113
* ----------------
@@ -3576,6 +3580,66 @@ assign_max_stack_depth(int newval, void *extra)
35763580
max_stack_depth_bytes = newval_bytes;
35773581
}
35783582

3583+
/*
3584+
* GUC check_hook for restrict_nonsystem_relation_kind
3585+
*/
3586+
bool
3587+
check_restrict_nonsystem_relation_kind(char **newval, void **extra, GucSource source)
3588+
{
3589+
char *rawstring;
3590+
List *elemlist;
3591+
ListCell *l;
3592+
int flags = 0;
3593+
3594+
/* Need a modifiable copy of string */
3595+
rawstring = pstrdup(*newval);
3596+
3597+
if (!SplitIdentifierString(rawstring, ',', &elemlist))
3598+
{
3599+
/* syntax error in list */
3600+
GUC_check_errdetail("List syntax is invalid.");
3601+
pfree(rawstring);
3602+
list_free(elemlist);
3603+
return false;
3604+
}
3605+
3606+
foreach(l, elemlist)
3607+
{
3608+
char *tok = (char *) lfirst(l);
3609+
3610+
if (pg_strcasecmp(tok, "view") == 0)
3611+
flags |= RESTRICT_RELKIND_VIEW;
3612+
else if (pg_strcasecmp(tok, "foreign-table") == 0)
3613+
flags |= RESTRICT_RELKIND_FOREIGN_TABLE;
3614+
else
3615+
{
3616+
GUC_check_errdetail("Unrecognized key word: \"%s\".", tok);
3617+
pfree(rawstring);
3618+
list_free(elemlist);
3619+
return false;
3620+
}
3621+
}
3622+
3623+
pfree(rawstring);
3624+
list_free(elemlist);
3625+
3626+
/* Save the flags in *extra, for use by the assign function */
3627+
*extra = malloc(sizeof(int));
3628+
*((int *) *extra) = flags;
3629+
3630+
return true;
3631+
}
3632+
3633+
/*
3634+
* GUC assign_hook for restrict_nonsystem_relation_kind
3635+
*/
3636+
void
3637+
assign_restrict_nonsystem_relation_kind(const char *newval, void *extra)
3638+
{
3639+
int *flags = (int *) extra;
3640+
3641+
restrict_nonsystem_relation_kind = *flags;
3642+
}
35793643

35803644
/*
35813645
* set_debug_options --- apply "-d N" command line option

src/backend/utils/misc/guc.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,7 @@ static char *recovery_target_string;
660660
static char *recovery_target_xid_string;
661661
static char *recovery_target_name_string;
662662
static char *recovery_target_lsn_string;
663+
static char *restrict_nonsystem_relation_kind_string;
663664

664665

665666
/* should be static, but commands/variable.c needs to get at this */
@@ -4601,6 +4602,17 @@ static struct config_string ConfigureNamesString[] =
46014602
check_backtrace_functions, assign_backtrace_functions, NULL
46024603
},
46034604

4605+
{
4606+
{"restrict_nonsystem_relation_kind", PGC_USERSET, CLIENT_CONN_STATEMENT,
4607+
gettext_noop("Sets relation kinds of non-system relation to restrict use"),
4608+
NULL,
4609+
GUC_LIST_INPUT | GUC_NOT_IN_SAMPLE
4610+
},
4611+
&restrict_nonsystem_relation_kind_string,
4612+
"",
4613+
check_restrict_nonsystem_relation_kind, assign_restrict_nonsystem_relation_kind, NULL
4614+
},
4615+
46044616
/* End-of-list marker */
46054617
{
46064618
{NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL

src/bin/pg_dump/pg_dump.c

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ static void appendReloptionsArrayAH(PQExpBuffer buffer, const char *reloptions,
301301
const char *prefix, Archive *fout);
302302
static char *get_synchronized_snapshot(Archive *fout);
303303
static void setupDumpWorker(Archive *AHX);
304+
static void set_restrict_relation_kind(Archive *AH, const char *value);
304305
static TableInfo *getRootTableInfo(const TableInfo *tbinfo);
305306
static bool forcePartitionRootLoad(const TableInfo *tbinfo);
306307

@@ -1210,6 +1211,13 @@ setup_connection(Archive *AH, const char *dumpencoding,
12101211
ExecuteSqlStatement(AH, "SET row_security = off");
12111212
}
12121213

1214+
/*
1215+
* For security reasons, we restrict the expansion of non-system views and
1216+
* access to foreign tables during the pg_dump process. This restriction
1217+
* is adjusted when dumping foreign table data.
1218+
*/
1219+
set_restrict_relation_kind(AH, "view, foreign-table");
1220+
12131221
/*
12141222
* Start transaction-snapshot mode transaction to dump consistent data.
12151223
*/
@@ -2042,6 +2050,10 @@ dumpTableData_copy(Archive *fout, const void *dcontext)
20422050
*/
20432051
if (tdinfo->filtercond || tbinfo->relkind == RELKIND_FOREIGN_TABLE)
20442052
{
2053+
/* Temporary allows to access to foreign tables to dump data */
2054+
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
2055+
set_restrict_relation_kind(fout, "view");
2056+
20452057
/* Note: this syntax is only supported in 8.2 and up */
20462058
appendPQExpBufferStr(q, "COPY (SELECT ");
20472059
/* klugery to get rid of parens in column list */
@@ -2154,6 +2166,11 @@ dumpTableData_copy(Archive *fout, const void *dcontext)
21542166
classname);
21552167

21562168
destroyPQExpBuffer(q);
2169+
2170+
/* Revert back the setting */
2171+
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
2172+
set_restrict_relation_kind(fout, "view, foreign-table");
2173+
21572174
return 1;
21582175
}
21592176

@@ -2180,6 +2197,10 @@ dumpTableData_insert(Archive *fout, const void *dcontext)
21802197
int rows_per_statement = dopt->dump_inserts;
21812198
int rows_this_statement = 0;
21822199

2200+
/* Temporary allows to access to foreign tables to dump data */
2201+
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
2202+
set_restrict_relation_kind(fout, "view");
2203+
21832204
/*
21842205
* If we're going to emit INSERTs with column names, the most efficient
21852206
* way to deal with generated columns is to exclude them entirely. For
@@ -2419,6 +2440,10 @@ dumpTableData_insert(Archive *fout, const void *dcontext)
24192440
destroyPQExpBuffer(insertStmt);
24202441
free(attgenerated);
24212442

2443+
/* Revert back the setting */
2444+
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
2445+
set_restrict_relation_kind(fout, "view, foreign-table");
2446+
24222447
return 1;
24232448
}
24242449

@@ -4448,6 +4473,28 @@ is_superuser(Archive *fout)
44484473
return false;
44494474
}
44504475

4476+
/*
4477+
* Set the given value to restrict_nonsystem_relation_kind value. Since
4478+
* restrict_nonsystem_relation_kind is introduced in minor version releases,
4479+
* the setting query is effective only where available.
4480+
*/
4481+
static void
4482+
set_restrict_relation_kind(Archive *AH, const char *value)
4483+
{
4484+
PQExpBuffer query = createPQExpBuffer();
4485+
PGresult *res;
4486+
4487+
appendPQExpBuffer(query,
4488+
"SELECT set_config(name, '%s', false) "
4489+
"FROM pg_settings "
4490+
"WHERE name = 'restrict_nonsystem_relation_kind'",
4491+
value);
4492+
res = ExecuteSqlQuery(AH, query->data, PGRES_TUPLES_OK);
4493+
4494+
PQclear(res);
4495+
destroyPQExpBuffer(query);
4496+
}
4497+
44514498
/*
44524499
* getSubscriptions
44534500
* get information about subscriptions

0 commit comments

Comments
 (0)