diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 73a75d09ffa..94b590cfd3a 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -631,6 +631,17 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft_empty ORDER BY c1; Remote SQL: SELECT c1, c2 FROM public.loct_empty ORDER BY c1 ASC NULLS LAST (3 rows) +-- test restriction on non-system foreign tables. +SET restrict_nonsystem_relation_kind TO 'foreign-table'; +SELECT * from ft1 where c1 < 1; -- ERROR +ERROR: access to non-system foreign table is restricted +INSERT INTO ft1 (c1) VALUES (1); -- ERROR +ERROR: access to non-system foreign table is restricted +DELETE FROM ft1 WHERE c1 = 1; -- ERROR +ERROR: access to non-system foreign table is restricted +TRUNCATE ft1; -- ERROR +ERROR: access to non-system foreign table is restricted +RESET restrict_nonsystem_relation_kind; -- =================================================================== -- WHERE with remotely-executable conditions -- =================================================================== diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql index a62e1fbc33e..1c983e26a09 100644 --- a/contrib/postgres_fdw/sql/postgres_fdw.sql +++ b/contrib/postgres_fdw/sql/postgres_fdw.sql @@ -321,6 +321,14 @@ DELETE FROM loct_empty; ANALYZE ft_empty; EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft_empty ORDER BY c1; +-- test restriction on non-system foreign tables. +SET restrict_nonsystem_relation_kind TO 'foreign-table'; +SELECT * from ft1 where c1 < 1; -- ERROR +INSERT INTO ft1 (c1) VALUES (1); -- ERROR +DELETE FROM ft1 WHERE c1 = 1; -- ERROR +TRUNCATE ft1; -- ERROR +RESET restrict_nonsystem_relation_kind; + -- =================================================================== -- WHERE with remotely-executable conditions -- =================================================================== diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index efc039b5d26..6e6da596422 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -8967,6 +8967,23 @@ SET XML OPTION { DOCUMENT | CONTENT }; + + restrict_nonsystem_relation_kind (string) + + restrict_nonsystem_relation_kind + configuration parameter + + + + + This variable specifies relation kind to which access is restricted. + It contains a comma-separated list of relation kind. Currently, the + supported relation kinds are view and + foreign-table. + + + + diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml index 29ecb243b1e..9ec8e6d202c 100644 --- a/doc/src/sgml/ref/pg_dump.sgml +++ b/doc/src/sgml/ref/pg_dump.sgml @@ -822,6 +822,14 @@ PostgreSQL documentation The only exception is that an empty pattern is disallowed. + + + Using wildcards in may result + in access to unexpected foreign servers. Also, to use this option securely, + make sure that the named server must have a trusted owner. + + + When is specified, diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c index 5564dc3a1e2..778c193f9d0 100644 --- a/src/backend/foreign/foreign.c +++ b/src/backend/foreign/foreign.c @@ -22,6 +22,7 @@ #include "foreign/foreign.h" #include "lib/stringinfo.h" #include "miscadmin.h" +#include "tcop/tcopprot.h" #include "utils/builtins.h" #include "utils/memutils.h" #include "utils/rel.h" @@ -321,6 +322,15 @@ GetFdwRoutine(Oid fdwhandler) Datum datum; FdwRoutine *routine; + /* Check if the access to foreign tables is restricted */ + if (unlikely((restrict_nonsystem_relation_kind & RESTRICT_RELKIND_FOREIGN_TABLE) != 0)) + { + /* there must not be built-in FDW handler */ + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("access to non-system foreign table is restricted"))); + } + datum = OidFunctionCall0(fdwhandler); routine = (FdwRoutine *) DatumGetPointer(datum); diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 0ed858f305a..0e4686ae841 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -41,6 +41,7 @@ #include "parser/parse_clause.h" #include "parser/parsetree.h" #include "partitioning/partprune.h" +#include "tcop/tcopprot.h" #include "utils/lsyscache.h" @@ -7035,7 +7036,19 @@ make_modifytable(PlannerInfo *root, Plan *subplan, Assert(rte->rtekind == RTE_RELATION); if (rte->relkind == RELKIND_FOREIGN_TABLE) + { + /* Check if the access to foreign tables is restricted */ + if (unlikely((restrict_nonsystem_relation_kind & RESTRICT_RELKIND_FOREIGN_TABLE) != 0)) + { + /* there must not be built-in foreign tables */ + Assert(rte->relid >= FirstNormalObjectId); + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("access to non-system foreign table is restricted"))); + } + fdwroutine = GetFdwRoutineByRelId(rte->relid); + } else fdwroutine = NULL; } diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 091f00a50eb..4c7fa65c058 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -46,6 +46,7 @@ #include "rewrite/rewriteManip.h" #include "statistics/statistics.h" #include "storage/bufmgr.h" +#include "tcop/tcopprot.h" #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/partcache.h" @@ -462,6 +463,17 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, /* Grab foreign-table info using the relcache, while we have it */ if (relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE) { + /* Check if the access to foreign tables is restricted */ + if (unlikely((restrict_nonsystem_relation_kind & RESTRICT_RELKIND_FOREIGN_TABLE) != 0)) + { + /* there must not be built-in foreign tables */ + Assert(RelationGetRelid(relation) >= FirstNormalObjectId); + + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("access to non-system foreign table is restricted"))); + } + rel->serverid = GetForeignServerIdByRelId(RelationGetRelid(relation)); rel->fdwroutine = GetFdwRoutineForRelation(relation, true); } diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index 0438f26620e..84ef8a6978d 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -41,6 +41,7 @@ #include "rewrite/rewriteManip.h" #include "rewrite/rewriteSearchCycle.h" #include "rewrite/rowsecurity.h" +#include "tcop/tcopprot.h" #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/rel.h" @@ -1730,6 +1731,14 @@ ApplyRetrieveRule(Query *parsetree, if (rule->qual != NULL) elog(ERROR, "cannot handle qualified ON SELECT rule"); + /* Check if the expansion of non-system views are restricted */ + if (unlikely((restrict_nonsystem_relation_kind & RESTRICT_RELKIND_VIEW) != 0 && + RelationGetRelid(relation) >= FirstNormalObjectId)) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("access to non-system view \"%s\" is restricted", + RelationGetRelationName(relation)))); + if (rt_index == parsetree->resultRelation) { /* @@ -3124,6 +3133,14 @@ rewriteTargetView(Query *parsetree, Relation view) } } + /* Check if the expansion of non-system views are restricted */ + if (unlikely((restrict_nonsystem_relation_kind & RESTRICT_RELKIND_VIEW) != 0 && + RelationGetRelid(view) >= FirstNormalObjectId)) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("access to non-system view \"%s\" is restricted", + RelationGetRelationName(view)))); + /* * For INSERT/UPDATE the modified columns must all be updatable. */ diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index c613495ee2e..75a36f1b8b1 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -80,6 +80,7 @@ #include "utils/snapmgr.h" #include "utils/timeout.h" #include "utils/timestamp.h" +#include "utils/varlena.h" /* ---------------- * global variables @@ -104,6 +105,9 @@ int PostAuthDelay = 0; /* Time between checks that the client is still connected. */ int client_connection_check_interval = 0; +/* flags for non-system relation kinds to restrict use */ +int restrict_nonsystem_relation_kind; + /* ---------------- * private typedefs etc * ---------------- @@ -3576,6 +3580,66 @@ assign_max_stack_depth(int newval, void *extra) max_stack_depth_bytes = newval_bytes; } +/* + * GUC check_hook for restrict_nonsystem_relation_kind + */ +bool +check_restrict_nonsystem_relation_kind(char **newval, void **extra, GucSource source) +{ + char *rawstring; + List *elemlist; + ListCell *l; + int flags = 0; + + /* Need a modifiable copy of string */ + rawstring = pstrdup(*newval); + + if (!SplitIdentifierString(rawstring, ',', &elemlist)) + { + /* syntax error in list */ + GUC_check_errdetail("List syntax is invalid."); + pfree(rawstring); + list_free(elemlist); + return false; + } + + foreach(l, elemlist) + { + char *tok = (char *) lfirst(l); + + if (pg_strcasecmp(tok, "view") == 0) + flags |= RESTRICT_RELKIND_VIEW; + else if (pg_strcasecmp(tok, "foreign-table") == 0) + flags |= RESTRICT_RELKIND_FOREIGN_TABLE; + else + { + GUC_check_errdetail("Unrecognized key word: \"%s\".", tok); + pfree(rawstring); + list_free(elemlist); + return false; + } + } + + pfree(rawstring); + list_free(elemlist); + + /* Save the flags in *extra, for use by the assign function */ + *extra = malloc(sizeof(int)); + *((int *) *extra) = flags; + + return true; +} + +/* + * GUC assign_hook for restrict_nonsystem_relation_kind + */ +void +assign_restrict_nonsystem_relation_kind(const char *newval, void *extra) +{ + int *flags = (int *) extra; + + restrict_nonsystem_relation_kind = *flags; +} /* * set_debug_options --- apply "-d N" command line option diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 15e3e509137..ffee83dfbbb 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -660,6 +660,7 @@ static char *recovery_target_string; static char *recovery_target_xid_string; static char *recovery_target_name_string; static char *recovery_target_lsn_string; +static char *restrict_nonsystem_relation_kind_string; /* should be static, but commands/variable.c needs to get at this */ @@ -4601,6 +4602,17 @@ static struct config_string ConfigureNamesString[] = check_backtrace_functions, assign_backtrace_functions, NULL }, + { + {"restrict_nonsystem_relation_kind", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets relation kinds of non-system relation to restrict use"), + NULL, + GUC_LIST_INPUT | GUC_NOT_IN_SAMPLE + }, + &restrict_nonsystem_relation_kind_string, + "", + check_restrict_nonsystem_relation_kind, assign_restrict_nonsystem_relation_kind, NULL + }, + /* End-of-list marker */ { {NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index f8ec6130b13..001daf936df 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -301,6 +301,7 @@ static void appendReloptionsArrayAH(PQExpBuffer buffer, const char *reloptions, const char *prefix, Archive *fout); static char *get_synchronized_snapshot(Archive *fout); static void setupDumpWorker(Archive *AHX); +static void set_restrict_relation_kind(Archive *AH, const char *value); static TableInfo *getRootTableInfo(const TableInfo *tbinfo); static bool forcePartitionRootLoad(const TableInfo *tbinfo); @@ -1210,6 +1211,13 @@ setup_connection(Archive *AH, const char *dumpencoding, ExecuteSqlStatement(AH, "SET row_security = off"); } + /* + * For security reasons, we restrict the expansion of non-system views and + * access to foreign tables during the pg_dump process. This restriction + * is adjusted when dumping foreign table data. + */ + set_restrict_relation_kind(AH, "view, foreign-table"); + /* * Start transaction-snapshot mode transaction to dump consistent data. */ @@ -2042,6 +2050,10 @@ dumpTableData_copy(Archive *fout, const void *dcontext) */ if (tdinfo->filtercond || tbinfo->relkind == RELKIND_FOREIGN_TABLE) { + /* Temporary allows to access to foreign tables to dump data */ + if (tbinfo->relkind == RELKIND_FOREIGN_TABLE) + set_restrict_relation_kind(fout, "view"); + /* Note: this syntax is only supported in 8.2 and up */ appendPQExpBufferStr(q, "COPY (SELECT "); /* klugery to get rid of parens in column list */ @@ -2154,6 +2166,11 @@ dumpTableData_copy(Archive *fout, const void *dcontext) classname); destroyPQExpBuffer(q); + + /* Revert back the setting */ + if (tbinfo->relkind == RELKIND_FOREIGN_TABLE) + set_restrict_relation_kind(fout, "view, foreign-table"); + return 1; } @@ -2180,6 +2197,10 @@ dumpTableData_insert(Archive *fout, const void *dcontext) int rows_per_statement = dopt->dump_inserts; int rows_this_statement = 0; + /* Temporary allows to access to foreign tables to dump data */ + if (tbinfo->relkind == RELKIND_FOREIGN_TABLE) + set_restrict_relation_kind(fout, "view"); + /* * If we're going to emit INSERTs with column names, the most efficient * way to deal with generated columns is to exclude them entirely. For @@ -2419,6 +2440,10 @@ dumpTableData_insert(Archive *fout, const void *dcontext) destroyPQExpBuffer(insertStmt); free(attgenerated); + /* Revert back the setting */ + if (tbinfo->relkind == RELKIND_FOREIGN_TABLE) + set_restrict_relation_kind(fout, "view, foreign-table"); + return 1; } @@ -4448,6 +4473,28 @@ is_superuser(Archive *fout) return false; } +/* + * Set the given value to restrict_nonsystem_relation_kind value. Since + * restrict_nonsystem_relation_kind is introduced in minor version releases, + * the setting query is effective only where available. + */ +static void +set_restrict_relation_kind(Archive *AH, const char *value) +{ + PQExpBuffer query = createPQExpBuffer(); + PGresult *res; + + appendPQExpBuffer(query, + "SELECT set_config(name, '%s', false) " + "FROM pg_settings " + "WHERE name = 'restrict_nonsystem_relation_kind'", + value); + res = ExecuteSqlQuery(AH, query->data, PGRES_TUPLES_OK); + + PQclear(res); + destroyPQExpBuffer(query); +} + /* * getSubscriptions * get information about subscriptions diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h index 968345404e5..00da5e66e70 100644 --- a/src/include/tcop/tcopprot.h +++ b/src/include/tcop/tcopprot.h @@ -43,6 +43,12 @@ typedef enum extern PGDLLIMPORT int log_statement; +/* Flags for restrict_nonsystem_relation_kind value */ +#define RESTRICT_RELKIND_VIEW 0x01 +#define RESTRICT_RELKIND_FOREIGN_TABLE 0x02 + +extern PGDLLIMPORT int restrict_nonsystem_relation_kind; + extern List *pg_parse_query(const char *query_string); extern List *pg_rewrite_query(Query *query); extern List *pg_analyze_and_rewrite(RawStmt *parsetree, @@ -63,6 +69,9 @@ extern List *pg_plan_queries(List *querytrees, const char *query_string, extern bool check_max_stack_depth(int *newval, void **extra, GucSource source); extern void assign_max_stack_depth(int newval, void *extra); +extern bool check_restrict_nonsystem_relation_kind(char **newval, void **extra, + GucSource source); +extern void assign_restrict_nonsystem_relation_kind(const char *newval, void *extra); extern void die(SIGNAL_ARGS); extern void quickdie(SIGNAL_ARGS) pg_attribute_noreturn(); diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out index 8c9952b6652..b1b96a88743 100644 --- a/src/test/regress/expected/create_view.out +++ b/src/test/regress/expected/create_view.out @@ -2013,6 +2013,21 @@ CREATE RULE "_RETURN" AS ON SELECT TO tt28 DO INSTEAD SELECT * FROM tt26; CREATE RULE "_RETURN" AS ON SELECT TO tt28 DO INSTEAD SELECT * FROM tt26; ERROR: "tt28" is already a view ROLLBACK; +-- test restriction on non-system view expansion. +create table tt27v_tbl (a int); +create view tt27v as select a from tt27v_tbl; +set restrict_nonsystem_relation_kind to 'view'; +select a from tt27v where a > 0; -- Error +ERROR: access to non-system view "tt27v" is restricted +insert into tt27v values (1); -- Error +ERROR: access to non-system view "tt27v" is restricted +select viewname from pg_views where viewname = 'tt27v'; -- Ok to access a system view. + viewname +---------- + tt27v +(1 row) + +reset restrict_nonsystem_relation_kind; -- clean up all the random objects we made above DROP SCHEMA temp_view_test CASCADE; NOTICE: drop cascades to 27 other objects @@ -2044,7 +2059,7 @@ drop cascades to view aliased_view_2 drop cascades to view aliased_view_3 drop cascades to view aliased_view_4 DROP SCHEMA testviewschm2 CASCADE; -NOTICE: drop cascades to 75 other objects +NOTICE: drop cascades to 77 other objects DETAIL: drop cascades to table t1 drop cascades to view temporal1 drop cascades to view temporal2 @@ -2120,3 +2135,5 @@ drop cascades to view tt24v drop cascades to view tt25v drop cascades to view tt26v drop cascades to table tt26 +drop cascades to table tt27v_tbl +drop cascades to view tt27v diff --git a/src/test/regress/sql/create_view.sql b/src/test/regress/sql/create_view.sql index 77246d0c618..662bf57c86c 100644 --- a/src/test/regress/sql/create_view.sql +++ b/src/test/regress/sql/create_view.sql @@ -708,6 +708,14 @@ CREATE RULE "_RETURN" AS ON SELECT TO tt28 DO INSTEAD SELECT * FROM tt26; CREATE RULE "_RETURN" AS ON SELECT TO tt28 DO INSTEAD SELECT * FROM tt26; ROLLBACK; +-- test restriction on non-system view expansion. +create table tt27v_tbl (a int); +create view tt27v as select a from tt27v_tbl; +set restrict_nonsystem_relation_kind to 'view'; +select a from tt27v where a > 0; -- Error +insert into tt27v values (1); -- Error +select viewname from pg_views where viewname = 'tt27v'; -- Ok to access a system view. +reset restrict_nonsystem_relation_kind; -- clean up all the random objects we made above DROP SCHEMA temp_view_test CASCADE;