Skip to content

Commit e81e53a

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 8b57eb6 commit e81e53a

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
@@ -637,6 +637,17 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft_empty ORDER BY c1;
637637
Remote SQL: SELECT c1, c2 FROM public.loct_empty ORDER BY c1 ASC NULLS LAST
638638
(3 rows)
639639

640+
-- test restriction on non-system foreign tables.
641+
SET restrict_nonsystem_relation_kind TO 'foreign-table';
642+
SELECT * from ft1 where c1 < 1; -- ERROR
643+
ERROR: access to non-system foreign table is restricted
644+
INSERT INTO ft1 (c1) VALUES (1); -- ERROR
645+
ERROR: access to non-system foreign table is restricted
646+
DELETE FROM ft1 WHERE c1 = 1; -- ERROR
647+
ERROR: access to non-system foreign table is restricted
648+
TRUNCATE ft1; -- ERROR
649+
ERROR: access to non-system foreign table is restricted
650+
RESET restrict_nonsystem_relation_kind;
640651
-- ===================================================================
641652
-- WHERE with remotely-executable conditions
642653
-- ===================================================================

contrib/postgres_fdw/sql/postgres_fdw.sql

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

329+
-- test restriction on non-system foreign tables.
330+
SET restrict_nonsystem_relation_kind TO 'foreign-table';
331+
SELECT * from ft1 where c1 < 1; -- ERROR
332+
INSERT INTO ft1 (c1) VALUES (1); -- ERROR
333+
DELETE FROM ft1 WHERE c1 = 1; -- ERROR
334+
TRUNCATE ft1; -- ERROR
335+
RESET restrict_nonsystem_relation_kind;
336+
329337
-- ===================================================================
330338
-- WHERE with remotely-executable conditions
331339
-- ===================================================================

doc/src/sgml/config.sgml

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

9388+
<varlistentry id="guc-restrict-nonsystem-relation-kind" xreflabel="restrict_nonsystem_relation_kind">
9389+
<term><varname>restrict_nonsystem_relation_kind</varname> (<type>string</type>)
9390+
<indexterm>
9391+
<primary><varname>restrict_nonsystem_relation_kind</varname></primary>
9392+
<secondary>configuration parameter</secondary>
9393+
</indexterm>
9394+
</term>
9395+
<listitem>
9396+
<para>
9397+
This variable specifies relation kind to which access is restricted.
9398+
It contains a comma-separated list of relation kind. Currently, the
9399+
supported relation kinds are <literal>view</literal> and
9400+
<literal>foreign-table</literal>.
9401+
</para>
9402+
</listitem>
9403+
</varlistentry>
9404+
93889405
</variablelist>
93899406
</sect2>
93909407
<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
@@ -812,6 +812,14 @@ PostgreSQL documentation
812812
The only exception is that an empty pattern is disallowed.
813813
</para>
814814

815+
<note>
816+
<para>
817+
Using wildcards in <option>--include-foreign-data</option> may result
818+
in access to unexpected foreign servers. Also, to use this option securely,
819+
make sure that the named server must have a trusted owner.
820+
</para>
821+
</note>
822+
815823
<note>
816824
<para>
817825
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
@@ -23,6 +23,7 @@
2323
#include "funcapi.h"
2424
#include "lib/stringinfo.h"
2525
#include "miscadmin.h"
26+
#include "tcop/tcopprot.h"
2627
#include "utils/builtins.h"
2728
#include "utils/memutils.h"
2829
#include "utils/rel.h"
@@ -322,6 +323,15 @@ GetFdwRoutine(Oid fdwhandler)
322323
Datum datum;
323324
FdwRoutine *routine;
324325

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

src/backend/optimizer/plan/createplan.c

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

4546

@@ -7111,7 +7112,19 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
71117112
Assert(rte->rtekind == RTE_RELATION);
71127113
Assert(operation != CMD_MERGE);
71137114
if (rte->relkind == RELKIND_FOREIGN_TABLE)
7115+
{
7116+
/* Check if the access to foreign tables is restricted */
7117+
if (unlikely((restrict_nonsystem_relation_kind & RESTRICT_RELKIND_FOREIGN_TABLE) != 0))
7118+
{
7119+
/* there must not be built-in foreign tables */
7120+
Assert(rte->relid >= FirstNormalObjectId);
7121+
ereport(ERROR,
7122+
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
7123+
errmsg("access to non-system foreign table is restricted")));
7124+
}
7125+
71147126
fdwroutine = GetFdwRoutineByRelId(rte->relid);
7127+
}
71157128
else
71167129
fdwroutine = NULL;
71177130
}

src/backend/optimizer/util/plancat.c

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

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"
@@ -1734,6 +1735,14 @@ ApplyRetrieveRule(Query *parsetree,
17341735
if (rule->qual != NULL)
17351736
elog(ERROR, "cannot handle qualified ON SELECT rule");
17361737

1738+
/* Check if the expansion of non-system views are restricted */
1739+
if (unlikely((restrict_nonsystem_relation_kind & RESTRICT_RELKIND_VIEW) != 0 &&
1740+
RelationGetRelid(relation) >= FirstNormalObjectId))
1741+
ereport(ERROR,
1742+
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1743+
errmsg("access to non-system view \"%s\" is restricted",
1744+
RelationGetRelationName(relation))));
1745+
17371746
if (rt_index == parsetree->resultRelation)
17381747
{
17391748
/*
@@ -3128,6 +3137,14 @@ rewriteTargetView(Query *parsetree, Relation view)
31283137
}
31293138
}
31303139

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

src/backend/tcop/postgres.c

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

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

109+
/* flags for non-system relation kinds to restrict use */
110+
int restrict_nonsystem_relation_kind;
111+
108112
/* ----------------
109113
* private typedefs etc
110114
* ----------------
@@ -3601,6 +3605,66 @@ assign_max_stack_depth(int newval, void *extra)
36013605
max_stack_depth_bytes = newval_bytes;
36023606
}
36033607

3608+
/*
3609+
* GUC check_hook for restrict_nonsystem_relation_kind
3610+
*/
3611+
bool
3612+
check_restrict_nonsystem_relation_kind(char **newval, void **extra, GucSource source)
3613+
{
3614+
char *rawstring;
3615+
List *elemlist;
3616+
ListCell *l;
3617+
int flags = 0;
3618+
3619+
/* Need a modifiable copy of string */
3620+
rawstring = pstrdup(*newval);
3621+
3622+
if (!SplitIdentifierString(rawstring, ',', &elemlist))
3623+
{
3624+
/* syntax error in list */
3625+
GUC_check_errdetail("List syntax is invalid.");
3626+
pfree(rawstring);
3627+
list_free(elemlist);
3628+
return false;
3629+
}
3630+
3631+
foreach(l, elemlist)
3632+
{
3633+
char *tok = (char *) lfirst(l);
3634+
3635+
if (pg_strcasecmp(tok, "view") == 0)
3636+
flags |= RESTRICT_RELKIND_VIEW;
3637+
else if (pg_strcasecmp(tok, "foreign-table") == 0)
3638+
flags |= RESTRICT_RELKIND_FOREIGN_TABLE;
3639+
else
3640+
{
3641+
GUC_check_errdetail("Unrecognized key word: \"%s\".", tok);
3642+
pfree(rawstring);
3643+
list_free(elemlist);
3644+
return false;
3645+
}
3646+
}
3647+
3648+
pfree(rawstring);
3649+
list_free(elemlist);
3650+
3651+
/* Save the flags in *extra, for use by the assign function */
3652+
*extra = malloc(sizeof(int));
3653+
*((int *) *extra) = flags;
3654+
3655+
return true;
3656+
}
3657+
3658+
/*
3659+
* GUC assign_hook for restrict_nonsystem_relation_kind
3660+
*/
3661+
void
3662+
assign_restrict_nonsystem_relation_kind(const char *newval, void *extra)
3663+
{
3664+
int *flags = (int *) extra;
3665+
3666+
restrict_nonsystem_relation_kind = *flags;
3667+
}
36043668

36053669
/*
36063670
* 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
@@ -716,6 +716,7 @@ static char *recovery_target_string;
716716
static char *recovery_target_xid_string;
717717
static char *recovery_target_name_string;
718718
static char *recovery_target_lsn_string;
719+
static char *restrict_nonsystem_relation_kind_string;
719720

720721

721722
/* should be static, but commands/variable.c needs to get at this */
@@ -4711,6 +4712,17 @@ static struct config_string ConfigureNamesString[] =
47114712
check_backtrace_functions, assign_backtrace_functions, NULL
47124713
},
47134714

4715+
{
4716+
{"restrict_nonsystem_relation_kind", PGC_USERSET, CLIENT_CONN_STATEMENT,
4717+
gettext_noop("Sets relation kinds of non-system relation to restrict use"),
4718+
NULL,
4719+
GUC_LIST_INPUT | GUC_NOT_IN_SAMPLE
4720+
},
4721+
&restrict_nonsystem_relation_kind_string,
4722+
"",
4723+
check_restrict_nonsystem_relation_kind, assign_restrict_nonsystem_relation_kind, NULL
4724+
},
4725+
47144726
/* End-of-list marker */
47154727
{
47164728
{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
@@ -316,6 +316,7 @@ static void appendReloptionsArrayAH(PQExpBuffer buffer, const char *reloptions,
316316
const char *prefix, Archive *fout);
317317
static char *get_synchronized_snapshot(Archive *fout);
318318
static void setupDumpWorker(Archive *AHX);
319+
static void set_restrict_relation_kind(Archive *AH, const char *value);
319320
static TableInfo *getRootTableInfo(const TableInfo *tbinfo);
320321
static bool forcePartitionRootLoad(const TableInfo *tbinfo);
321322

@@ -1175,6 +1176,13 @@ setup_connection(Archive *AH, const char *dumpencoding,
11751176
ExecuteSqlStatement(AH, "SET row_security = off");
11761177
}
11771178

1179+
/*
1180+
* For security reasons, we restrict the expansion of non-system views and
1181+
* access to foreign tables during the pg_dump process. This restriction
1182+
* is adjusted when dumping foreign table data.
1183+
*/
1184+
set_restrict_relation_kind(AH, "view, foreign-table");
1185+
11781186
/*
11791187
* Initialize prepared-query state to "nothing prepared". We do this here
11801188
* so that a parallel dump worker will have its own state.
@@ -2018,6 +2026,10 @@ dumpTableData_copy(Archive *fout, const void *dcontext)
20182026
*/
20192027
if (tdinfo->filtercond || tbinfo->relkind == RELKIND_FOREIGN_TABLE)
20202028
{
2029+
/* Temporary allows to access to foreign tables to dump data */
2030+
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
2031+
set_restrict_relation_kind(fout, "view");
2032+
20212033
appendPQExpBufferStr(q, "COPY (SELECT ");
20222034
/* klugery to get rid of parens in column list */
20232035
if (strlen(column_list) > 2)
@@ -2129,6 +2141,11 @@ dumpTableData_copy(Archive *fout, const void *dcontext)
21292141
classname);
21302142

21312143
destroyPQExpBuffer(q);
2144+
2145+
/* Revert back the setting */
2146+
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
2147+
set_restrict_relation_kind(fout, "view, foreign-table");
2148+
21322149
return 1;
21332150
}
21342151

@@ -2155,6 +2172,10 @@ dumpTableData_insert(Archive *fout, const void *dcontext)
21552172
int rows_per_statement = dopt->dump_inserts;
21562173
int rows_this_statement = 0;
21572174

2175+
/* Temporary allows to access to foreign tables to dump data */
2176+
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
2177+
set_restrict_relation_kind(fout, "view");
2178+
21582179
/*
21592180
* If we're going to emit INSERTs with column names, the most efficient
21602181
* way to deal with generated columns is to exclude them entirely. For
@@ -2394,6 +2415,10 @@ dumpTableData_insert(Archive *fout, const void *dcontext)
23942415
destroyPQExpBuffer(insertStmt);
23952416
free(attgenerated);
23962417

2418+
/* Revert back the setting */
2419+
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
2420+
set_restrict_relation_kind(fout, "view, foreign-table");
2421+
23972422
return 1;
23982423
}
23992424

@@ -4479,6 +4504,28 @@ is_superuser(Archive *fout)
44794504
return false;
44804505
}
44814506

4507+
/*
4508+
* Set the given value to restrict_nonsystem_relation_kind value. Since
4509+
* restrict_nonsystem_relation_kind is introduced in minor version releases,
4510+
* the setting query is effective only where available.
4511+
*/
4512+
static void
4513+
set_restrict_relation_kind(Archive *AH, const char *value)
4514+
{
4515+
PQExpBuffer query = createPQExpBuffer();
4516+
PGresult *res;
4517+
4518+
appendPQExpBuffer(query,
4519+
"SELECT set_config(name, '%s', false) "
4520+
"FROM pg_settings "
4521+
"WHERE name = 'restrict_nonsystem_relation_kind'",
4522+
value);
4523+
res = ExecuteSqlQuery(AH, query->data, PGRES_TUPLES_OK);
4524+
4525+
PQclear(res);
4526+
destroyPQExpBuffer(query);
4527+
}
4528+
44824529
/*
44834530
* getSubscriptions
44844531
* get information about subscriptions

0 commit comments

Comments
 (0)