Skip to content

Commit 66e9444

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 ca6fde9 commit 66e9444

File tree

15 files changed

+255
-1
lines changed

15 files changed

+255
-1
lines changed

contrib/postgres_fdw/expected/postgres_fdw.out

+11
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

+8
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,14 @@ DELETE FROM loct_empty;
327327
ANALYZE ft_empty;
328328
EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft_empty ORDER BY c1;
329329

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

doc/src/sgml/config.sgml

+17
Original file line numberDiff line numberDiff line change
@@ -9813,6 +9813,23 @@ SET XML OPTION { DOCUMENT | CONTENT };
98139813
</listitem>
98149814
</varlistentry>
98159815

9816+
<varlistentry id="guc-restrict-nonsystem-relation-kind" xreflabel="restrict_nonsystem_relation_kind">
9817+
<term><varname>restrict_nonsystem_relation_kind</varname> (<type>string</type>)
9818+
<indexterm>
9819+
<primary><varname>restrict_nonsystem_relation_kind</varname></primary>
9820+
<secondary>configuration parameter</secondary>
9821+
</indexterm>
9822+
</term>
9823+
<listitem>
9824+
<para>
9825+
This variable specifies relation kind to which access is restricted.
9826+
It contains a comma-separated list of relation kind. Currently, the
9827+
supported relation kinds are <literal>view</literal> and
9828+
<literal>foreign-table</literal>.
9829+
</para>
9830+
</listitem>
9831+
</varlistentry>
9832+
98169833
</variablelist>
98179834
</sect2>
98189835
<sect2 id="runtime-config-client-format">

doc/src/sgml/ref/pg_dump.sgml

+8
Original file line numberDiff line numberDiff line change
@@ -1001,6 +1001,14 @@ PostgreSQL documentation
10011001
The only exception is that an empty pattern is disallowed.
10021002
</para>
10031003

1004+
<note>
1005+
<para>
1006+
Using wildcards in <option>--include-foreign-data</option> may result
1007+
in access to unexpected foreign servers. Also, to use this option securely,
1008+
make sure that the named server must have a trusted owner.
1009+
</para>
1010+
</note>
1011+
10041012
<note>
10051013
<para>
10061014
When <option>--include-foreign-data</option> is specified,

src/backend/foreign/foreign.c

+10
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "foreign/foreign.h"
2323
#include "funcapi.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"
@@ -326,6 +327,15 @@ GetFdwRoutine(Oid fdwhandler)
326327
Datum datum;
327328
FdwRoutine *routine;
328329

330+
/* Check if the access to foreign tables is restricted */
331+
if (unlikely((restrict_nonsystem_relation_kind & RESTRICT_RELKIND_FOREIGN_TABLE) != 0))
332+
{
333+
/* there must not be built-in FDW handler */
334+
ereport(ERROR,
335+
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
336+
errmsg("access to non-system foreign table is restricted")));
337+
}
338+
329339
datum = OidFunctionCall0(fdwhandler);
330340
routine = (FdwRoutine *) DatumGetPointer(datum);
331341

src/backend/optimizer/plan/createplan.c

+13
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

@@ -7136,7 +7137,19 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
71367137

71377138
if (rte->rtekind == RTE_RELATION &&
71387139
rte->relkind == RELKIND_FOREIGN_TABLE)
7140+
{
7141+
/* Check if the access to foreign tables is restricted */
7142+
if (unlikely((restrict_nonsystem_relation_kind & RESTRICT_RELKIND_FOREIGN_TABLE) != 0))
7143+
{
7144+
/* there must not be built-in foreign tables */
7145+
Assert(rte->relid >= FirstNormalObjectId);
7146+
ereport(ERROR,
7147+
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
7148+
errmsg("access to non-system foreign table is restricted")));
7149+
}
7150+
71397151
fdwroutine = GetFdwRoutineByRelId(rte->relid);
7152+
}
71407153
else
71417154
fdwroutine = NULL;
71427155
}

src/backend/optimizer/util/plancat.c

+12
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
#include "rewrite/rewriteManip.h"
4646
#include "statistics/statistics.h"
4747
#include "storage/bufmgr.h"
48+
#include "tcop/tcopprot.h"
4849
#include "utils/builtins.h"
4950
#include "utils/lsyscache.h"
5051
#include "utils/partcache.h"
@@ -528,6 +529,17 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
528529
/* Grab foreign-table info using the relcache, while we have it */
529530
if (relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
530531
{
532+
/* Check if the access to foreign tables is restricted */
533+
if (unlikely((restrict_nonsystem_relation_kind & RESTRICT_RELKIND_FOREIGN_TABLE) != 0))
534+
{
535+
/* there must not be built-in foreign tables */
536+
Assert(RelationGetRelid(relation) >= FirstNormalObjectId);
537+
538+
ereport(ERROR,
539+
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
540+
errmsg("access to non-system foreign table is restricted")));
541+
}
542+
531543
rel->serverid = GetForeignServerIdByRelId(RelationGetRelid(relation));
532544
rel->fdwroutine = GetFdwRoutineForRelation(relation, true);
533545
}

src/backend/rewrite/rewriteHandler.c

+17
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#include "rewrite/rewriteManip.h"
4141
#include "rewrite/rewriteSearchCycle.h"
4242
#include "rewrite/rowsecurity.h"
43+
#include "tcop/tcopprot.h"
4344
#include "utils/builtins.h"
4445
#include "utils/lsyscache.h"
4546
#include "utils/rel.h"
@@ -1729,6 +1730,14 @@ ApplyRetrieveRule(Query *parsetree,
17291730
if (rule->qual != NULL)
17301731
elog(ERROR, "cannot handle qualified ON SELECT rule");
17311732

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

3224+
/* Check if the expansion of non-system views are restricted */
3225+
if (unlikely((restrict_nonsystem_relation_kind & RESTRICT_RELKIND_VIEW) != 0 &&
3226+
RelationGetRelid(view) >= FirstNormalObjectId))
3227+
ereport(ERROR,
3228+
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
3229+
errmsg("access to non-system view \"%s\" is restricted",
3230+
RelationGetRelationName(view))));
3231+
32153232
/*
32163233
* The view must be updatable, else fail.
32173234
*

src/backend/tcop/postgres.c

+64
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
#include "utils/snapmgr.h"
8080
#include "utils/timeout.h"
8181
#include "utils/timestamp.h"
82+
#include "utils/varlena.h"
8283

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

107+
/* flags for non-system relation kinds to restrict use */
108+
int restrict_nonsystem_relation_kind;
109+
106110
/* ----------------
107111
* private typedefs etc
108112
* ----------------
@@ -3673,6 +3677,66 @@ assign_transaction_timeout(int newval, void *extra)
36733677
}
36743678
}
36753679

3680+
/*
3681+
* GUC check_hook for restrict_nonsystem_relation_kind
3682+
*/
3683+
bool
3684+
check_restrict_nonsystem_relation_kind(char **newval, void **extra, GucSource source)
3685+
{
3686+
char *rawstring;
3687+
List *elemlist;
3688+
ListCell *l;
3689+
int flags = 0;
3690+
3691+
/* Need a modifiable copy of string */
3692+
rawstring = pstrdup(*newval);
3693+
3694+
if (!SplitIdentifierString(rawstring, ',', &elemlist))
3695+
{
3696+
/* syntax error in list */
3697+
GUC_check_errdetail("List syntax is invalid.");
3698+
pfree(rawstring);
3699+
list_free(elemlist);
3700+
return false;
3701+
}
3702+
3703+
foreach(l, elemlist)
3704+
{
3705+
char *tok = (char *) lfirst(l);
3706+
3707+
if (pg_strcasecmp(tok, "view") == 0)
3708+
flags |= RESTRICT_RELKIND_VIEW;
3709+
else if (pg_strcasecmp(tok, "foreign-table") == 0)
3710+
flags |= RESTRICT_RELKIND_FOREIGN_TABLE;
3711+
else
3712+
{
3713+
GUC_check_errdetail("Unrecognized key word: \"%s\".", tok);
3714+
pfree(rawstring);
3715+
list_free(elemlist);
3716+
return false;
3717+
}
3718+
}
3719+
3720+
pfree(rawstring);
3721+
list_free(elemlist);
3722+
3723+
/* Save the flags in *extra, for use by the assign function */
3724+
*extra = guc_malloc(ERROR, sizeof(int));
3725+
*((int *) *extra) = flags;
3726+
3727+
return true;
3728+
}
3729+
3730+
/*
3731+
* GUC assign_hook for restrict_nonsystem_relation_kind
3732+
*/
3733+
void
3734+
assign_restrict_nonsystem_relation_kind(const char *newval, void *extra)
3735+
{
3736+
int *flags = (int *) extra;
3737+
3738+
restrict_nonsystem_relation_kind = *flags;
3739+
}
36763740

36773741
/*
36783742
* set_debug_options --- apply "-d N" command line option

src/backend/utils/misc/guc_tables.c

+12
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,7 @@ static char *server_encoding_string;
576576
static char *server_version_string;
577577
static int server_version_num;
578578
static char *debug_io_direct_string;
579+
static char *restrict_nonsystem_relation_kind_string;
579580

580581
#ifdef HAVE_SYSLOG
581582
#define DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -4768,6 +4769,17 @@ struct config_string ConfigureNamesString[] =
47684769
check_synchronized_standby_slots, assign_synchronized_standby_slots, NULL
47694770
},
47704771

4772+
{
4773+
{"restrict_nonsystem_relation_kind", PGC_USERSET, CLIENT_CONN_STATEMENT,
4774+
gettext_noop("Sets relation kinds of non-system relation to restrict use"),
4775+
NULL,
4776+
GUC_LIST_INPUT | GUC_NOT_IN_SAMPLE
4777+
},
4778+
&restrict_nonsystem_relation_kind_string,
4779+
"",
4780+
check_restrict_nonsystem_relation_kind, assign_restrict_nonsystem_relation_kind, NULL
4781+
},
4782+
47714783
/* End-of-list marker */
47724784
{
47734785
{NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL

src/bin/pg_dump/pg_dump.c

+47
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,7 @@ static bool nonemptyReloptions(const char *reloptions);
390390
static void appendReloptionsArrayAH(PQExpBuffer buffer, const char *reloptions,
391391
const char *prefix, Archive *fout);
392392
static char *get_synchronized_snapshot(Archive *fout);
393+
static void set_restrict_relation_kind(Archive *AH, const char *value);
393394
static void setupDumpWorker(Archive *AH);
394395
static TableInfo *getRootTableInfo(const TableInfo *tbinfo);
395396
static bool forcePartitionRootLoad(const TableInfo *tbinfo);
@@ -1354,6 +1355,13 @@ setup_connection(Archive *AH, const char *dumpencoding,
13541355
ExecuteSqlStatement(AH, "SET row_security = off");
13551356
}
13561357

1358+
/*
1359+
* For security reasons, we restrict the expansion of non-system views and
1360+
* access to foreign tables during the pg_dump process. This restriction
1361+
* is adjusted when dumping foreign table data.
1362+
*/
1363+
set_restrict_relation_kind(AH, "view, foreign-table");
1364+
13571365
/*
13581366
* Initialize prepared-query state to "nothing prepared". We do this here
13591367
* so that a parallel dump worker will have its own state.
@@ -2222,6 +2230,10 @@ dumpTableData_copy(Archive *fout, const void *dcontext)
22222230
*/
22232231
if (tdinfo->filtercond || tbinfo->relkind == RELKIND_FOREIGN_TABLE)
22242232
{
2233+
/* Temporary allows to access to foreign tables to dump data */
2234+
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
2235+
set_restrict_relation_kind(fout, "view");
2236+
22252237
appendPQExpBufferStr(q, "COPY (SELECT ");
22262238
/* klugery to get rid of parens in column list */
22272239
if (strlen(column_list) > 2)
@@ -2333,6 +2345,11 @@ dumpTableData_copy(Archive *fout, const void *dcontext)
23332345
classname);
23342346

23352347
destroyPQExpBuffer(q);
2348+
2349+
/* Revert back the setting */
2350+
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
2351+
set_restrict_relation_kind(fout, "view, foreign-table");
2352+
23362353
return 1;
23372354
}
23382355

@@ -2359,6 +2376,10 @@ dumpTableData_insert(Archive *fout, const void *dcontext)
23592376
int rows_per_statement = dopt->dump_inserts;
23602377
int rows_this_statement = 0;
23612378

2379+
/* Temporary allows to access to foreign tables to dump data */
2380+
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
2381+
set_restrict_relation_kind(fout, "view");
2382+
23622383
/*
23632384
* If we're going to emit INSERTs with column names, the most efficient
23642385
* way to deal with generated columns is to exclude them entirely. For
@@ -2598,6 +2619,10 @@ dumpTableData_insert(Archive *fout, const void *dcontext)
25982619
destroyPQExpBuffer(insertStmt);
25992620
free(attgenerated);
26002621

2622+
/* Revert back the setting */
2623+
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
2624+
set_restrict_relation_kind(fout, "view, foreign-table");
2625+
26012626
return 1;
26022627
}
26032628

@@ -4771,6 +4796,28 @@ is_superuser(Archive *fout)
47714796
return false;
47724797
}
47734798

4799+
/*
4800+
* Set the given value to restrict_nonsystem_relation_kind value. Since
4801+
* restrict_nonsystem_relation_kind is introduced in minor version releases,
4802+
* the setting query is effective only where available.
4803+
*/
4804+
static void
4805+
set_restrict_relation_kind(Archive *AH, const char *value)
4806+
{
4807+
PQExpBuffer query = createPQExpBuffer();
4808+
PGresult *res;
4809+
4810+
appendPQExpBuffer(query,
4811+
"SELECT set_config(name, '%s', false) "
4812+
"FROM pg_settings "
4813+
"WHERE name = 'restrict_nonsystem_relation_kind'",
4814+
value);
4815+
res = ExecuteSqlQuery(AH, query->data, PGRES_TUPLES_OK);
4816+
4817+
PQclear(res);
4818+
destroyPQExpBuffer(query);
4819+
}
4820+
47744821
/*
47754822
* getSubscriptions
47764823
* get information about subscriptions

0 commit comments

Comments
 (0)