diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index 9a6ff4c2629..efab3c3299e 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -92,6 +92,18 @@ PostgreSQL documentation
light of the limitations listed below.
+
+
+ Restoring a dump causes the destination to execute arbitrary code of the
+ source superusers' choice. Partial dumps and partial restores do not limit
+ that. If the source superusers are not trusted, the dumped SQL statements
+ must be inspected before restoring. Non-plain-text dumps can be inspected
+ by using pg_restore's
+ option. Note that the client running the dump and restore need not trust
+ the source or destination superusers.
+
+
+
@@ -1078,6 +1090,29 @@ PostgreSQL documentation
+
+
+
+
+ Use the provided string as the psql
+ \restrict key in the dump output. This can only be
+ specified for plain-text dumps, i.e., when is
+ set to plain or the option
+ is omitted. If no restrict key is specified,
+ pg_dump will generate a random one as
+ needed. Keys may contain only alphanumeric characters.
+
+
+ This option is primarily intended for testing purposes and other
+ scenarios that require repeatable output (e.g., comparing dump files).
+ It is not recommended for general use, as a malicious server with
+ advance knowledge of the key may be able to inject arbitrary code that
+ will be executed on the machine that runs
+ psql with the dump output.
+
+
+
+
diff --git a/doc/src/sgml/ref/pg_dumpall.sgml b/doc/src/sgml/ref/pg_dumpall.sgml
index 6ce876d6594..5cc0d8ab6b4 100644
--- a/doc/src/sgml/ref/pg_dumpall.sgml
+++ b/doc/src/sgml/ref/pg_dumpall.sgml
@@ -66,6 +66,16 @@ PostgreSQL documentation
linkend="libpq-pgpass"/> for more information.
+
+
+ Restoring a dump causes the destination to execute arbitrary code of the
+ source superusers' choice. Partial dumps and partial restores do not limit
+ that. If the source superusers are not trusted, the dumped SQL statements
+ must be inspected before restoring. Note that the client running the dump
+ and restore need not trust the source or destination superusers.
+
+
+
@@ -559,6 +569,26 @@ PostgreSQL documentation
+
+
+
+
+ Use the provided string as the psql
+ \restrict key in the dump output. If no restrict
+ key is specified, pg_dumpall will generate a
+ random one as needed. Keys may contain only alphanumeric characters.
+
+
+ This option is primarily intended for testing purposes and other
+ scenarios that require repeatable output (e.g., comparing dump files).
+ It is not recommended for general use, as a malicious server with
+ advance knowledge of the key may be able to inject arbitrary code that
+ will be executed on the machine that runs
+ psql with the dump output.
+
+
+
+
diff --git a/doc/src/sgml/ref/pg_restore.sgml b/doc/src/sgml/ref/pg_restore.sgml
index a81583191c1..d2ff765dc74 100644
--- a/doc/src/sgml/ref/pg_restore.sgml
+++ b/doc/src/sgml/ref/pg_restore.sgml
@@ -68,6 +68,18 @@ PostgreSQL documentation
pg_restore will not be able to load the data
using COPY statements.
+
+
+
+ Restoring a dump causes the destination to execute arbitrary code of the
+ source superusers' choice. Partial dumps and partial restores do not limit
+ that. If the source superusers are not trusted, the dumped SQL statements
+ must be inspected before restoring. Non-plain-text dumps can be inspected
+ by using pg_restore's
+ option. Note that the client running the dump and restore need not trust
+ the source or destination superusers.
+
+
@@ -675,6 +687,28 @@ PostgreSQL documentation
+
+
+
+
+ Use the provided string as the psql
+ \restrict key in the dump output. This can only be
+ specified for SQL script output, i.e., when the
+ option is used. If no restrict key is specified,
+ pg_restore will generate a random one as
+ needed. Keys may contain only alphanumeric characters.
+
+
+ This option is primarily intended for testing purposes and other
+ scenarios that require repeatable output (e.g., comparing dump files).
+ It is not recommended for general use, as a malicious server with
+ advance knowledge of the key may be able to inject arbitrary code that
+ will be executed on the machine that runs
+ psql with the dump output.
+
+
+
+
diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml
index 701e140207b..52649b5dc9d 100644
--- a/doc/src/sgml/ref/pgupgrade.sgml
+++ b/doc/src/sgml/ref/pgupgrade.sgml
@@ -70,6 +70,14 @@ PostgreSQL documentation
pg_upgrade supports upgrades from 9.2.X and later to the current
major release of PostgreSQL, including snapshot and beta releases.
+
+
+
+ Upgrading a cluster causes the destination to execute arbitrary code of the
+ source superusers' choice. Ensure that the source superusers are trusted
+ before upgrading.
+
+
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index fb0e55eb380..4a121a3e5da 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3355,6 +3355,24 @@ lo_import 152801
+
+ \restrict restrict_key
+
+
+ Enter "restricted" mode with the provided key. In this mode, the only
+ allowed meta-command is \unrestrict, to exit
+ restricted mode. The key may contain only alphanumeric characters.
+
+
+ This command is primarily intended for use in plain-text dumps
+ generated by pg_dump,
+ pg_dumpall, and
+ pg_restore, but it may be useful elsewhere.
+
+
+
+
+
\s [ filename ]
@@ -3529,6 +3547,24 @@ testdb=> \setenv LESS -imx4F
+
+ \unrestrict restrict_key
+
+
+ Exit "restricted" mode (i.e., where all other meta-commands are
+ blocked), provided the specified key matches the one given to
+ \restrict when restricted mode was entered.
+
+
+ This command is primarily intended for use in plain-text dumps
+ generated by pg_dump,
+ pg_dumpall, and
+ pg_restore, but it may be useful elsewhere.
+
+
+
+
+
\unset name
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index dc23c17c576..deac62e4d9a 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -180,7 +180,6 @@ static void ExecutePlan(QueryDesc *queryDesc,
ScanDirection direction,
DestReceiver *dest,
bool execute_once);
-static bool ExecCheckOneRelPerms(RTEPermissionInfo *perminfo);
static bool ExecCheckPermissionsModified(Oid relOid, Oid userid,
Bitmapset *modifiedCols,
AclMode requiredPerms);
@@ -1523,7 +1522,7 @@ ExecCheckPermissions(List *rangeTable, List *rteperminfos,
* ExecCheckOneRelPerms
* Check access permissions for a single relation.
*/
-static bool
+bool
ExecCheckOneRelPerms(RTEPermissionInfo *perminfo)
{
AclMode requiredPerms;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 58602135656..94e8f4ff8a0 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -66,6 +66,7 @@
#include "partitioning/partdesc.h"
#include "rewrite/rewriteManip.h"
#include "storage/dsm_impl.h"
+#include "utils/acl.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/selfuncs.h"
@@ -1144,6 +1145,38 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
bms_make_singleton(parse->resultRelation);
}
+ /*
+ * This would be a convenient time to check access permissions for all
+ * relations mentioned in the query, since it would be better to fail now,
+ * before doing any detailed planning. However, for historical reasons,
+ * we leave this to be done at executor startup.
+ *
+ * Note, however, that we do need to check access permissions for any view
+ * relations mentioned in the query, in order to prevent information being
+ * leaked by selectivity estimation functions, which only check view owner
+ * permissions on underlying tables (see all_rows_selectable() and its
+ * callers). This is a little ugly, because it means that access
+ * permissions for views will be checked twice, which is another reason
+ * why it would be better to do all the ACL checks here.
+ */
+ foreach(l, parse->rtable)
+ {
+ RangeTblEntry *rte = lfirst_node(RangeTblEntry, l);
+
+ if (rte->perminfoindex != 0 &&
+ rte->relkind == RELKIND_VIEW)
+ {
+ RTEPermissionInfo *perminfo;
+ bool result;
+
+ perminfo = getRTEPermissionInfo(parse->rteperminfos, rte);
+ result = ExecCheckOneRelPerms(perminfo);
+ if (!result)
+ aclcheck_error(ACLCHECK_NO_PRIV, OBJECT_VIEW,
+ get_rel_name(perminfo->relid));
+ }
+ }
+
/*
* Preprocess RowMark information. We need to do this after subquery
* pullup, so that all base relations are present.
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index 2e0fd083216..5b881c6e5b3 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -1346,6 +1346,9 @@ choose_best_statistics(List *stats, char requiredkind, bool inh,
* so we can't cope with system columns.
* *exprs: input/output parameter collecting primitive subclauses within
* the clause tree
+ * *leakproof: input/output parameter recording the leakproofness of the
+ * clause tree. This should be true initially, and will be set to false
+ * if any operator function used in an OpExpr is not leakproof.
*
* Returns false if there is something we definitively can't handle.
* On true return, we can proceed to match the *exprs against statistics.
@@ -1353,7 +1356,7 @@ choose_best_statistics(List *stats, char requiredkind, bool inh,
static bool
statext_is_compatible_clause_internal(PlannerInfo *root, Node *clause,
Index relid, Bitmapset **attnums,
- List **exprs)
+ List **exprs, bool *leakproof)
{
/* Look inside any binary-compatible relabeling (as in examine_variable) */
if (IsA(clause, RelabelType))
@@ -1388,7 +1391,6 @@ statext_is_compatible_clause_internal(PlannerInfo *root, Node *clause,
/* (Var/Expr op Const) or (Const op Var/Expr) */
if (is_opclause(clause))
{
- RangeTblEntry *rte = root->simple_rte_array[relid];
OpExpr *expr = (OpExpr *) clause;
Node *clause_expr;
@@ -1423,24 +1425,15 @@ statext_is_compatible_clause_internal(PlannerInfo *root, Node *clause,
return false;
}
- /*
- * If there are any securityQuals on the RTE from security barrier
- * views or RLS policies, then the user may not have access to all the
- * table's data, and we must check that the operator is leak-proof.
- *
- * If the operator is leaky, then we must ignore this clause for the
- * purposes of estimating with MCV lists, otherwise the operator might
- * reveal values from the MCV list that the user doesn't have
- * permission to see.
- */
- if (rte->securityQuals != NIL &&
- !get_func_leakproof(get_opcode(expr->opno)))
- return false;
+ /* Check if the operator is leakproof */
+ if (*leakproof)
+ *leakproof = get_func_leakproof(get_opcode(expr->opno));
/* Check (Var op Const) or (Const op Var) clauses by recursing. */
if (IsA(clause_expr, Var))
return statext_is_compatible_clause_internal(root, clause_expr,
- relid, attnums, exprs);
+ relid, attnums,
+ exprs, leakproof);
/* Otherwise we have (Expr op Const) or (Const op Expr). */
*exprs = lappend(*exprs, clause_expr);
@@ -1450,7 +1443,6 @@ statext_is_compatible_clause_internal(PlannerInfo *root, Node *clause,
/* Var/Expr IN Array */
if (IsA(clause, ScalarArrayOpExpr))
{
- RangeTblEntry *rte = root->simple_rte_array[relid];
ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) clause;
Node *clause_expr;
bool expronleft;
@@ -1490,24 +1482,15 @@ statext_is_compatible_clause_internal(PlannerInfo *root, Node *clause,
return false;
}
- /*
- * If there are any securityQuals on the RTE from security barrier
- * views or RLS policies, then the user may not have access to all the
- * table's data, and we must check that the operator is leak-proof.
- *
- * If the operator is leaky, then we must ignore this clause for the
- * purposes of estimating with MCV lists, otherwise the operator might
- * reveal values from the MCV list that the user doesn't have
- * permission to see.
- */
- if (rte->securityQuals != NIL &&
- !get_func_leakproof(get_opcode(expr->opno)))
- return false;
+ /* Check if the operator is leakproof */
+ if (*leakproof)
+ *leakproof = get_func_leakproof(get_opcode(expr->opno));
/* Check Var IN Array clauses by recursing. */
if (IsA(clause_expr, Var))
return statext_is_compatible_clause_internal(root, clause_expr,
- relid, attnums, exprs);
+ relid, attnums,
+ exprs, leakproof);
/* Otherwise we have Expr IN Array. */
*exprs = lappend(*exprs, clause_expr);
@@ -1544,7 +1527,8 @@ statext_is_compatible_clause_internal(PlannerInfo *root, Node *clause,
*/
if (!statext_is_compatible_clause_internal(root,
(Node *) lfirst(lc),
- relid, attnums, exprs))
+ relid, attnums, exprs,
+ leakproof))
return false;
}
@@ -1558,8 +1542,10 @@ statext_is_compatible_clause_internal(PlannerInfo *root, Node *clause,
/* Check Var IS NULL clauses by recursing. */
if (IsA(nt->arg, Var))
- return statext_is_compatible_clause_internal(root, (Node *) (nt->arg),
- relid, attnums, exprs);
+ return statext_is_compatible_clause_internal(root,
+ (Node *) (nt->arg),
+ relid, attnums,
+ exprs, leakproof);
/* Otherwise we have Expr IS NULL. */
*exprs = lappend(*exprs, nt->arg);
@@ -1598,11 +1584,9 @@ static bool
statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
Bitmapset **attnums, List **exprs)
{
- RangeTblEntry *rte = root->simple_rte_array[relid];
- RelOptInfo *rel = root->simple_rel_array[relid];
RestrictInfo *rinfo;
int clause_relid;
- Oid userid;
+ bool leakproof;
/*
* Special-case handling for bare BoolExpr AND clauses, because the
@@ -1642,18 +1626,31 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
clause_relid != relid)
return false;
- /* Check the clause and determine what attributes it references. */
+ /*
+ * Check the clause, determine what attributes it references, and whether
+ * it includes any non-leakproof operators.
+ */
+ leakproof = true;
if (!statext_is_compatible_clause_internal(root, (Node *) rinfo->clause,
- relid, attnums, exprs))
+ relid, attnums, exprs,
+ &leakproof))
return false;
/*
- * Check that the user has permission to read all required attributes.
+ * If the clause includes any non-leakproof operators, check that the user
+ * has permission to read all required attributes, otherwise the operators
+ * might reveal values from the MCV list that the user doesn't have
+ * permission to see. We require all rows to be selectable --- there must
+ * be no securityQuals from security barrier views or RLS policies. See
+ * similar code in examine_variable(), examine_simple_variable(), and
+ * statistic_proc_security_check().
+ *
+ * Note that for an inheritance child, the permission checks are performed
+ * on the inheritance root parent, and whole-table select privilege on the
+ * parent doesn't guarantee that the user could read all columns of the
+ * child. Therefore we must check all referenced columns.
*/
- userid = OidIsValid(rel->userid) ? rel->userid : GetUserId();
-
- /* Table-level SELECT privilege is sufficient for all columns */
- if (pg_class_aclcheck(rte->relid, userid, ACL_SELECT) != ACLCHECK_OK)
+ if (!leakproof)
{
Bitmapset *clause_attnums = NULL;
int attnum = -1;
@@ -1678,26 +1675,9 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
if (*exprs != NIL)
pull_varattnos((Node *) *exprs, relid, &clause_attnums);
- attnum = -1;
- while ((attnum = bms_next_member(clause_attnums, attnum)) >= 0)
- {
- /* Undo the offset */
- AttrNumber attno = attnum + FirstLowInvalidHeapAttributeNumber;
-
- if (attno == InvalidAttrNumber)
- {
- /* Whole-row reference, so must have access to all columns */
- if (pg_attribute_aclcheck_all(rte->relid, userid, ACL_SELECT,
- ACLMASK_ALL) != ACLCHECK_OK)
- return false;
- }
- else
- {
- if (pg_attribute_aclcheck(rte->relid, attno, userid,
- ACL_SELECT) != ACLCHECK_OK)
- return false;
- }
- }
+ /* Must have permission to read all rows from these columns */
+ if (!all_rows_selectable(root, relid, clause_attnums))
+ return false;
}
/* If we reach here, the clause is OK */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index b9fc6c9a5c0..7c6750fb8e6 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5122,8 +5122,8 @@ static void inline adjust_partition_table_statistic_for_parent(HeapTuple statsTu
* this query. (Caution: this should be trusted for statistical
* purposes only, since we do not check indimmediate nor verify that
* the exact same definition of equality applies.)
- * acl_ok: true if current user has permission to read the column(s)
- * underlying the pg_statistic entry. This is consulted by
+ * acl_ok: true if current user has permission to read all table rows from
+ * the column(s) underlying the pg_statistic entry. This is consulted by
* statistic_proc_security_check().
*
* Caller is responsible for doing ReleaseVariableStats() before exiting.
@@ -5238,7 +5238,6 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
*/
ListCell *ilist;
ListCell *slist;
- Oid userid;
/*
* The nullingrels bits within the expression could prevent us from
@@ -5248,17 +5247,6 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
if (bms_overlap(varnos, root->outer_join_rels))
node = remove_nulling_relids(node, root->outer_join_rels, NULL);
- /*
- * Determine the user ID to use for privilege checks: either
- * onerel->userid if it's set (e.g., in case we're accessing the table
- * via a view), or the current user otherwise.
- *
- * If we drill down to child relations, we keep using the same userid:
- * it's going to be the same anyway, due to how we set up the relation
- * tree (q.v. build_simple_rel).
- */
- userid = OidIsValid(onerel->userid) ? onerel->userid : GetUserId();
-
foreach(ilist, onerel->indexlist)
{
IndexOptInfo *index = (IndexOptInfo *) lfirst(ilist);
@@ -5326,69 +5314,32 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
if (HeapTupleIsValid(vardata->statsTuple))
{
- /* Get index's table for permission check */
- RangeTblEntry *rte;
-
- rte = planner_rt_fetch(index->rel->relid, root);
- Assert(rte->rtekind == RTE_RELATION);
-
/*
+ * Test if user has permission to access all
+ * rows from the index's table.
+ *
* For simplicity, we insist on the whole
* table being selectable, rather than trying
* to identify which column(s) the index
- * depends on. Also require all rows to be
- * selectable --- there must be no
- * securityQuals from security barrier views
- * or RLS policies.
+ * depends on.
+ *
+ * Note that for an inheritance child,
+ * permissions are checked on the inheritance
+ * root parent, and whole-table select
+ * privilege on the parent doesn't quite
+ * guarantee that the user could read all
+ * columns of the child. But in practice it's
+ * unlikely that any interesting security
+ * violation could result from allowing access
+ * to the expression index's stats, so we
+ * allow it anyway. See similar code in
+ * examine_simple_variable() for additional
+ * comments.
*/
vardata->acl_ok =
- rte->securityQuals == NIL &&
- (pg_class_aclcheck(rte->relid, userid,
- ACL_SELECT) == ACLCHECK_OK);
-
- /*
- * If the user doesn't have permissions to
- * access an inheritance child relation, check
- * the permissions of the table actually
- * mentioned in the query, since most likely
- * the user does have that permission. Note
- * that whole-table select privilege on the
- * parent doesn't quite guarantee that the
- * user could read all columns of the child.
- * But in practice it's unlikely that any
- * interesting security violation could result
- * from allowing access to the expression
- * index's stats, so we allow it anyway. See
- * similar code in examine_simple_variable()
- * for additional comments.
- */
- if (!vardata->acl_ok &&
- root->append_rel_array != NULL)
- {
- AppendRelInfo *appinfo;
- Index varno = index->rel->relid;
-
- appinfo = root->append_rel_array[varno];
- while (appinfo &&
- planner_rt_fetch(appinfo->parent_relid,
- root)->rtekind == RTE_RELATION)
- {
- varno = appinfo->parent_relid;
- appinfo = root->append_rel_array[varno];
- }
- if (varno != index->rel->relid)
- {
- /* Repeat access check on this rel */
- rte = planner_rt_fetch(varno, root);
- Assert(rte->rtekind == RTE_RELATION);
-
- vardata->acl_ok =
- rte->securityQuals == NIL &&
- (pg_class_aclcheck(rte->relid,
- userid,
- ACL_SELECT) == ACLCHECK_OK);
- }
- }
+ all_rows_selectable(root,
+ index->rel->relid,
+ NULL);
}
else
{
@@ -5469,58 +5420,26 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
vardata->freefunc = ReleaseDummy;
/*
+ * Test if user has permission to access all rows from the
+ * table.
+ *
* For simplicity, we insist on the whole table being
* selectable, rather than trying to identify which
- * column(s) the statistics object depends on. Also
- * require all rows to be selectable --- there must be no
- * securityQuals from security barrier views or RLS
- * policies.
- */
- vardata->acl_ok =
- rte->securityQuals == NIL &&
- (pg_class_aclcheck(rte->relid, userid,
- ACL_SELECT) == ACLCHECK_OK);
-
- /*
- * If the user doesn't have permissions to access an
- * inheritance child relation, check the permissions of
- * the table actually mentioned in the query, since most
- * likely the user does have that permission. Note that
- * whole-table select privilege on the parent doesn't
- * quite guarantee that the user could read all columns of
- * the child. But in practice it's unlikely that any
- * interesting security violation could result from
- * allowing access to the expression stats, so we allow it
- * anyway. See similar code in examine_simple_variable()
- * for additional comments.
+ * column(s) the statistics object depends on.
+ *
+ * Note that for an inheritance child, permissions are
+ * checked on the inheritance root parent, and whole-table
+ * select privilege on the parent doesn't quite guarantee
+ * that the user could read all columns of the child. But
+ * in practice it's unlikely that any interesting security
+ * violation could result from allowing access to the
+ * expression stats, so we allow it anyway. See similar
+ * code in examine_simple_variable() for additional
+ * comments.
*/
- if (!vardata->acl_ok &&
- root->append_rel_array != NULL)
- {
- AppendRelInfo *appinfo;
- Index varno = onerel->relid;
-
- appinfo = root->append_rel_array[varno];
- while (appinfo &&
- planner_rt_fetch(appinfo->parent_relid,
- root)->rtekind == RTE_RELATION)
- {
- varno = appinfo->parent_relid;
- appinfo = root->append_rel_array[varno];
- }
- if (varno != onerel->relid)
- {
- /* Repeat access check on this rel */
- rte = planner_rt_fetch(varno, root);
- Assert(rte->rtekind == RTE_RELATION);
-
- vardata->acl_ok =
- rte->securityQuals == NIL &&
- (pg_class_aclcheck(rte->relid,
- userid,
- ACL_SELECT) == ACLCHECK_OK);
- }
- }
+ vardata->acl_ok = all_rows_selectable(root,
+ onerel->relid,
+ NULL);
break;
}
@@ -5630,96 +5549,20 @@ examine_simple_variable(PlannerInfo *root, Var *var,
if (HeapTupleIsValid(vardata->statsTuple))
{
- RelOptInfo *onerel = find_base_rel(root, var->varno);
- Oid userid;
-
/*
- * Check if user has permission to read this column. We require
- * all rows to be accessible, so there must be no securityQuals
- * from security barrier views or RLS policies. Use
- * onerel->userid if it's set, in case we're accessing the table
- * via a view.
+ * Test if user has permission to read all rows from this column.
+ *
+ * This requires that the user has the appropriate SELECT
+ * privileges and that there are no securityQuals from security
+ * barrier views or RLS policies. If that's not the case, then we
+ * only permit leakproof functions to be passed pg_statistic data
+ * in vardata, otherwise the functions might reveal data that the
+ * user doesn't have permission to see --- see
+ * statistic_proc_security_check().
*/
- userid = OidIsValid(onerel->userid) ? onerel->userid : GetUserId();
-
vardata->acl_ok =
- rte->securityQuals == NIL &&
- ((pg_class_aclcheck(rte->relid, userid,
- ACL_SELECT) == ACLCHECK_OK) ||
- (pg_attribute_aclcheck(rte->relid, var->varattno, userid,
- ACL_SELECT) == ACLCHECK_OK));
-
- /*
- * If the user doesn't have permissions to access an inheritance
- * child relation or specifically this attribute, check the
- * permissions of the table/column actually mentioned in the
- * query, since most likely the user does have that permission
- * (else the query will fail at runtime), and if the user can read
- * the column there then he can get the values of the child table
- * too. To do that, we must find out which of the root parent's
- * attributes the child relation's attribute corresponds to.
- */
- if (!vardata->acl_ok && var->varattno > 0 &&
- root->append_rel_array != NULL)
- {
- AppendRelInfo *appinfo;
- Index varno = var->varno;
- int varattno = var->varattno;
- bool found = false;
-
- appinfo = root->append_rel_array[varno];
-
- /*
- * Partitions are mapped to their immediate parent, not the
- * root parent, so must be ready to walk up multiple
- * AppendRelInfos. But stop if we hit a parent that is not
- * RTE_RELATION --- that's a flattened UNION ALL subquery, not
- * an inheritance parent.
- */
- while (appinfo &&
- planner_rt_fetch(appinfo->parent_relid,
- root)->rtekind == RTE_RELATION)
- {
- int parent_varattno;
-
- found = false;
- if (varattno <= 0 || varattno > appinfo->num_child_cols)
- break; /* safety check */
- parent_varattno = appinfo->parent_colnos[varattno - 1];
- if (parent_varattno == 0)
- break; /* Var is local to child */
-
- varno = appinfo->parent_relid;
- varattno = parent_varattno;
- found = true;
-
- /* If the parent is itself a child, continue up. */
- appinfo = root->append_rel_array[varno];
- }
-
- /*
- * In rare cases, the Var may be local to the child table, in
- * which case, we've got to live with having no access to this
- * column's stats.
- */
- if (!found)
- return;
-
- /* Repeat the access check on this parent rel & column */
- rte = planner_rt_fetch(varno, root);
- Assert(rte->rtekind == RTE_RELATION);
-
- /*
- * Fine to use the same userid as it's the same in all
- * relations of a given inheritance tree.
- */
- vardata->acl_ok =
- rte->securityQuals == NIL &&
- ((pg_class_aclcheck(rte->relid, userid,
- ACL_SELECT) == ACLCHECK_OK) ||
- (pg_attribute_aclcheck(rte->relid, varattno, userid,
- ACL_SELECT) == ACLCHECK_OK));
- }
+ all_rows_selectable(root, var->varno,
+ bms_make_singleton(var->varattno - FirstLowInvalidHeapAttributeNumber));
}
else
{
@@ -5843,17 +5686,212 @@ examine_simple_variable(PlannerInfo *root, Var *var,
}
}
+/*
+ * all_rows_selectable
+ * Test whether the user has permission to select all rows from a given
+ * relation.
+ *
+ * Inputs:
+ * root: the planner info
+ * varno: the index of the relation (assumed to be an RTE_RELATION)
+ * varattnos: the attributes for which permission is required, or NULL if
+ * whole-table access is required
+ *
+ * Returns true if the user has the required select permissions, and there are
+ * no securityQuals from security barrier views or RLS policies.
+ *
+ * Note that if the relation is an inheritance child relation, securityQuals
+ * and access permissions are checked against the inheritance root parent (the
+ * relation actually mentioned in the query) --- see the comments in
+ * expand_single_inheritance_child() for an explanation of why it has to be
+ * done this way.
+ *
+ * If varattnos is non-NULL, its attribute numbers should be offset by
+ * FirstLowInvalidHeapAttributeNumber so that system attributes can be
+ * checked. If varattnos is NULL, only table-level SELECT privileges are
+ * checked, not any column-level privileges.
+ *
+ * Note: if the relation is accessed via a view, this function actually tests
+ * whether the view owner has permission to select from the relation. To
+ * ensure that the current user has permission, it is also necessary to check
+ * that the current user has permission to select from the view, which we do
+ * at planner-startup --- see subquery_planner().
+ *
+ * This is exported so that other estimation functions can use it.
+ */
+bool
+all_rows_selectable(PlannerInfo *root, Index varno, Bitmapset *varattnos)
+{
+ RelOptInfo *rel = find_base_rel(root, varno);
+ RangeTblEntry *rte = planner_rt_fetch(varno, root);
+ Oid userid;
+ int varattno;
+
+ Assert(rte->rtekind == RTE_RELATION);
+
+ /*
+ * User ID to use for privilege checks (either the current user or the
+ * view owner, if we're accessing the table via a view).
+ *
+ * If we navigate up to a parent relation, we keep using the same userid,
+ * since it's the same in all relations of a given inheritance tree.
+ */
+ userid = OidIsValid(rel->userid) ? rel->userid : GetUserId();
+
+ /*
+ * Permissions and securityQuals must be checked on the table actually
+ * mentioned in the query, so if this is an inheritance child, navigate up
+ * to the inheritance root parent. If the user can read the whole table
+ * or the required columns there, then they can read from the child table
+ * too. For per-column checks, we must find out which of the root
+ * parent's attributes the child relation's attributes correspond to.
+ */
+ if (root->append_rel_array != NULL)
+ {
+ AppendRelInfo *appinfo;
+
+ appinfo = root->append_rel_array[varno];
+
+ /*
+ * Partitions are mapped to their immediate parent, not the root
+ * parent, so must be ready to walk up multiple AppendRelInfos. But
+ * stop if we hit a parent that is not RTE_RELATION --- that's a
+ * flattened UNION ALL subquery, not an inheritance parent.
+ */
+ while (appinfo &&
+ planner_rt_fetch(appinfo->parent_relid,
+ root)->rtekind == RTE_RELATION)
+ {
+ Bitmapset *parent_varattnos = NULL;
+
+ /*
+ * For each child attribute, find the corresponding parent
+ * attribute. In rare cases, the attribute may be local to the
+ * child table, in which case, we've got to live with having no
+ * access to this column.
+ */
+ varattno = -1;
+ while ((varattno = bms_next_member(varattnos, varattno)) >= 0)
+ {
+ AttrNumber attno;
+ AttrNumber parent_attno;
+
+ attno = varattno + FirstLowInvalidHeapAttributeNumber;
+
+ if (attno == InvalidAttrNumber)
+ {
+ /*
+ * Whole-row reference, so must map each column of the
+ * child to the parent table.
+ */
+ for (attno = 1; attno <= appinfo->num_child_cols; attno++)
+ {
+ parent_attno = appinfo->parent_colnos[attno - 1];
+ if (parent_attno == 0)
+ return false; /* attr is local to child */
+ parent_varattnos =
+ bms_add_member(parent_varattnos,
+ parent_attno - FirstLowInvalidHeapAttributeNumber);
+ }
+ }
+ else
+ {
+ if (attno < 0)
+ {
+ /* System attnos are the same in all tables */
+ parent_attno = attno;
+ }
+ else
+ {
+ if (attno > appinfo->num_child_cols)
+ return false; /* safety check */
+ parent_attno = appinfo->parent_colnos[attno - 1];
+ if (parent_attno == 0)
+ return false; /* attr is local to child */
+ }
+ parent_varattnos =
+ bms_add_member(parent_varattnos,
+ parent_attno - FirstLowInvalidHeapAttributeNumber);
+ }
+ }
+
+ /* If the parent is itself a child, continue up */
+ varno = appinfo->parent_relid;
+ varattnos = parent_varattnos;
+ appinfo = root->append_rel_array[varno];
+ }
+
+ /* Perform the access check on this parent rel */
+ rte = planner_rt_fetch(varno, root);
+ Assert(rte->rtekind == RTE_RELATION);
+ }
+
+ /*
+ * For all rows to be accessible, there must be no securityQuals from
+ * security barrier views or RLS policies.
+ */
+ if (rte->securityQuals != NIL)
+ return false;
+
+ /*
+ * Test for table-level SELECT privilege.
+ *
+ * If varattnos is non-NULL, this is sufficient to give access to all
+ * requested attributes, even for a child table, since we have verified
+ * that all required child columns have matching parent columns.
+ *
+ * If varattnos is NULL (whole-table access requested), this doesn't
+ * necessarily guarantee that the user can read all columns of a child
+ * table, but we allow it anyway (see comments in examine_variable()) and
+ * don't bother checking any column privileges.
+ */
+ if (pg_class_aclcheck(rte->relid, userid, ACL_SELECT) == ACLCHECK_OK)
+ return true;
+
+ if (varattnos == NULL)
+ return false; /* whole-table access requested */
+
+ /*
+ * Don't have table-level SELECT privilege, so check per-column
+ * privileges.
+ */
+ varattno = -1;
+ while ((varattno = bms_next_member(varattnos, varattno)) >= 0)
+ {
+ AttrNumber attno = varattno + FirstLowInvalidHeapAttributeNumber;
+
+ if (attno == InvalidAttrNumber)
+ {
+ /* Whole-row reference, so must have access to all columns */
+ if (pg_attribute_aclcheck_all(rte->relid, userid, ACL_SELECT,
+ ACLMASK_ALL) != ACLCHECK_OK)
+ return false;
+ }
+ else
+ {
+ if (pg_attribute_aclcheck(rte->relid, attno, userid,
+ ACL_SELECT) != ACLCHECK_OK)
+ return false;
+ }
+ }
+
+ /* If we reach here, have all required column privileges */
+ return true;
+}
+
/*
* Check whether it is permitted to call func_oid passing some of the
- * pg_statistic data in vardata. We allow this either if the user has SELECT
- * privileges on the table or column underlying the pg_statistic data or if
- * the function is marked leak-proof.
+ * pg_statistic data in vardata. We allow this if either of the following
+ * conditions is met: (1) the user has SELECT privileges on the table or
+ * column underlying the pg_statistic data and there are no securityQuals from
+ * security barrier views or RLS policies, or (2) the function is marked
+ * leakproof.
*/
bool
statistic_proc_security_check(VariableStatData *vardata, Oid func_oid)
{
if (vardata->acl_ok)
- return true;
+ return true; /* have SELECT privs and no securityQuals */
if (!OidIsValid(func_oid))
return false;
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index a454d926e3a..7f9f36ad0f7 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -19,6 +19,7 @@
#include "dumputils.h"
#include "fe_utils/string_utils.h"
+static const char restrict_chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
static bool parseAclItem(const char *item, const char *type,
const char *name, const char *subname, int remoteVersion,
@@ -29,6 +30,43 @@ static void AddAcl(PQExpBuffer aclbuf, const char *keyword,
const char *subname);
+/*
+ * Sanitize a string to be included in an SQL comment or TOC listing, by
+ * replacing any newlines with spaces. This ensures each logical output line
+ * is in fact one physical output line, to prevent corruption of the dump
+ * (which could, in the worst case, present an SQL injection vulnerability
+ * if someone were to incautiously load a dump containing objects with
+ * maliciously crafted names).
+ *
+ * The result is a freshly malloc'd string. If the input string is NULL,
+ * return a malloc'ed empty string, unless want_hyphen, in which case return a
+ * malloc'ed hyphen.
+ *
+ * Note that we currently don't bother to quote names, meaning that the name
+ * fields aren't automatically parseable. "pg_restore -L" doesn't care because
+ * it only examines the dumpId field, but someday we might want to try harder.
+ */
+char *
+sanitize_line(const char *str, bool want_hyphen)
+{
+ char *result;
+ char *s;
+
+ if (!str)
+ return pg_strdup(want_hyphen ? "-" : "");
+
+ result = pg_strdup(str);
+
+ for (s = result; *s != '\0'; s++)
+ {
+ if (*s == '\n' || *s == '\r')
+ *s = ' ';
+ }
+
+ return result;
+}
+
+
/*
* Build GRANT/REVOKE command(s) for an object.
*
@@ -887,3 +925,40 @@ makeAlterConfigCommand(PGconn *conn, const char *configitem,
pg_free(mine);
}
+
+/*
+ * Generates a valid restrict key (i.e., an alphanumeric string) for use with
+ * psql's \restrict and \unrestrict meta-commands. For safety, the value is
+ * chosen at random.
+ */
+char *
+generate_restrict_key(void)
+{
+ uint8 buf[64];
+ char *ret = palloc(sizeof(buf));
+
+ if (!pg_strong_random(buf, sizeof(buf)))
+ return NULL;
+
+ for (int i = 0; i < sizeof(buf) - 1; i++)
+ {
+ uint8 idx = buf[i] % strlen(restrict_chars);
+
+ ret[i] = restrict_chars[idx];
+ }
+ ret[sizeof(buf) - 1] = '\0';
+
+ return ret;
+}
+
+/*
+ * Checks that a given restrict key (intended for use with psql's \restrict and
+ * \unrestrict meta-commands) contains only alphanumeric characters.
+ */
+bool
+valid_restrict_key(const char *restrict_key)
+{
+ return restrict_key != NULL &&
+ restrict_key[0] != '\0' &&
+ strspn(restrict_key, restrict_chars) == strlen(restrict_key);
+}
diff --git a/src/bin/pg_dump/dumputils.h b/src/bin/pg_dump/dumputils.h
index 49485528c34..a5055220c49 100644
--- a/src/bin/pg_dump/dumputils.h
+++ b/src/bin/pg_dump/dumputils.h
@@ -35,6 +35,7 @@
#define PGDUMP_STRFTIME_FMT "%Y-%m-%d %H:%M:%S"
#endif
+extern char *sanitize_line(const char *str, bool want_hyphen);
extern bool buildACLCommands(const char *name, const char *subname, const char *nspname,
const char *type, const char *acls, const char *baseacls,
const char *owner, const char *prefix, int remoteVersion,
@@ -66,5 +67,7 @@ extern void makeAlterConfigCommand(PGconn *conn, const char *configitem,
extern char *escape_backslashes(const char *src, bool quotes_too);
extern char *escape_fmtopts_string(const char *src);
extern char *custom_fmtopts_string(const char *src);
+extern char *generate_restrict_key(void);
+extern bool valid_restrict_key(const char *restrict_key);
#endif /* DUMPUTILS_H */
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index e25123acae0..2d9d06459c2 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -156,6 +156,8 @@ typedef struct _restoreOptions
int enable_row_security;
int sequence_data; /* dump sequence data even in schema-only mode */
int binary_upgrade;
+
+ char *restrict_key;
} RestoreOptions;
typedef struct _dumpOptions
@@ -202,6 +204,8 @@ typedef struct _dumpOptions
int sequence_data; /* dump sequence data even in schema-only mode */
int do_nothing;
+
+ char *restrict_key;
} DumpOptions;
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index d954dcf498f..a72f5eb4ce6 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -71,7 +71,6 @@ static ArchiveHandle *_allocAH(const char *FileSpec, const ArchiveFormat fmt,
SetupWorkerPtrType setupWorkerPtr);
static void _getObjectDescription(PQExpBuffer buf, const TocEntry *te);
static void _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData);
-static char *sanitize_line(const char *str, bool want_hyphen);
static void _doSetFixedOutputState(ArchiveHandle *AH);
static void _doSetSessionAuth(ArchiveHandle *AH, const char *user);
static void _reconnectToDB(ArchiveHandle *AH, const char *dbname);
@@ -207,6 +206,7 @@ dumpOptionsFromRestoreOptions(RestoreOptions *ropt)
dopt->include_everything = ropt->include_everything;
dopt->enable_row_security = ropt->enable_row_security;
dopt->sequence_data = ropt->sequence_data;
+ dopt->restrict_key = ropt->restrict_key ? pg_strdup(ropt->restrict_key) : NULL;
return dopt;
}
@@ -469,6 +469,17 @@ RestoreArchive(Archive *AHX)
ahprintf(AH, "--\n-- Apache Cloudberry database dump\n--\n\n");
+ /*
+ * If generating plain-text output, enter restricted mode to block any
+ * unexpected psql meta-commands. A malicious source might try to inject
+ * a variety of things via bogus responses to queries. While we cannot
+ * prevent such sources from affecting the destination at restore time, we
+ * can block psql meta-commands so that the client machine that runs psql
+ * with the dump output remains unaffected.
+ */
+ if (ropt->restrict_key)
+ ahprintf(AH, "\\restrict %s\n\n", ropt->restrict_key);
+
if (AH->archiveRemoteVersion)
ahprintf(AH, "-- Dumped from database version %s\n",
AH->archiveRemoteVersion);
@@ -754,6 +765,14 @@ RestoreArchive(Archive *AHX)
ahprintf(AH, "--\n-- Apache Cloudberry database dump complete\n--\n\n");
+ /*
+ * If generating plain-text output, exit restricted mode at the very end
+ * of the script. This is not pro forma; in particular, pg_dumpall
+ * requires this when transitioning from one database to another.
+ */
+ if (ropt->restrict_key)
+ ahprintf(AH, "\\unrestrict %s\n\n", ropt->restrict_key);
+
/*
* Clean up & we're done.
*/
@@ -3302,11 +3321,21 @@ _reconnectToDB(ArchiveHandle *AH, const char *dbname)
else
{
PQExpBufferData connectbuf;
+ RestoreOptions *ropt = AH->public.ropt;
+
+ /*
+ * We must temporarily exit restricted mode for \connect, etc.
+ * Anything added between this line and the following \restrict must
+ * be careful to avoid any possible meta-command injection vectors.
+ */
+ ahprintf(AH, "\\unrestrict %s\n", ropt->restrict_key);
initPQExpBuffer(&connectbuf);
appendPsqlMetaConnect(&connectbuf, dbname);
- ahprintf(AH, "%s\n", connectbuf.data);
+ ahprintf(AH, "%s", connectbuf.data);
termPQExpBuffer(&connectbuf);
+
+ ahprintf(AH, "\\restrict %s\n\n", ropt->restrict_key);
}
/*
@@ -3761,42 +3790,6 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData)
}
}
-/*
- * Sanitize a string to be included in an SQL comment or TOC listing, by
- * replacing any newlines with spaces. This ensures each logical output line
- * is in fact one physical output line, to prevent corruption of the dump
- * (which could, in the worst case, present an SQL injection vulnerability
- * if someone were to incautiously load a dump containing objects with
- * maliciously crafted names).
- *
- * The result is a freshly malloc'd string. If the input string is NULL,
- * return a malloc'ed empty string, unless want_hyphen, in which case return a
- * malloc'ed hyphen.
- *
- * Note that we currently don't bother to quote names, meaning that the name
- * fields aren't automatically parseable. "pg_restore -L" doesn't care because
- * it only examines the dumpId field, but someday we might want to try harder.
- */
-static char *
-sanitize_line(const char *str, bool want_hyphen)
-{
- char *result;
- char *s;
-
- if (!str)
- return pg_strdup(want_hyphen ? "-" : "");
-
- result = pg_strdup(str);
-
- for (s = result; *s != '\0'; s++)
- {
- if (*s == '\n' || *s == '\r')
- *s = ' ';
- }
-
- return result;
-}
-
/*
* Write the file header for a custom-format archive
*/
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index cd29367a065..7604417e262 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -564,6 +564,7 @@ main(int argc, char **argv)
{"table-and-children", required_argument, NULL, 12},
{"exclude-table-and-children", required_argument, NULL, 13},
{"exclude-table-data-and-children", required_argument, NULL, 14},
+ {"restrict-key", required_argument, NULL, 25},
/* START MPP ADDITION */
@@ -828,6 +829,10 @@ main(int argc, char **argv)
optarg);
break;
+ case 25:
+ dopt.restrict_key = pg_strdup(optarg);
+ break;
+
default:
/* getopt_long already emitted a complaint */
pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -890,8 +895,22 @@ main(int argc, char **argv)
/* archiveFormat specific setup */
if (archiveFormat == archNull)
+ {
plainText = 1;
+ /*
+ * If you don't provide a restrict key, one will be appointed for you.
+ */
+ if (!dopt.restrict_key)
+ dopt.restrict_key = generate_restrict_key();
+ if (!dopt.restrict_key)
+ pg_fatal("could not generate restrict key");
+ if (!valid_restrict_key(dopt.restrict_key))
+ pg_fatal("invalid restrict key");
+ }
+ else if (dopt.restrict_key)
+ pg_fatal("option --restrict-key can only be used with --format=plain");
+
/*
* Custom and directory formats are compressed by default with gzip when
* available, not the others. If gzip is not available, no compression is
@@ -1233,6 +1252,7 @@ main(int argc, char **argv)
ropt->enable_row_security = dopt.enable_row_security;
ropt->sequence_data = dopt.sequence_data;
ropt->binary_upgrade = dopt.binary_upgrade;
+ ropt->restrict_key = dopt.restrict_key ? pg_strdup(dopt.restrict_key) : NULL;
ropt->compression_spec = compression_spec;
@@ -1336,6 +1356,7 @@ help(const char *progname)
printf(_(" --no-unlogged-table-data do not dump unlogged table data\n"));
printf(_(" --on-conflict-do-nothing add ON CONFLICT DO NOTHING to INSERT commands\n"));
printf(_(" --quote-all-identifiers quote all identifiers, even if not key words\n"));
+ printf(_(" --restrict-key=RESTRICT_KEY use provided string as psql \\restrict key\n"));
printf(_(" --rows-per-insert=NROWS number of rows per INSERT; implies --inserts\n"));
printf(_(" --section=SECTION dump named section (pre-data, data, or post-data)\n"));
printf(_(" --serializable-deferrable wait until the dump can run without anomalies\n"));
@@ -2912,11 +2933,14 @@ dumpTableData(Archive *fout, const TableDataInfo *tdinfo)
forcePartitionRootLoad(tbinfo)))
{
TableInfo *parentTbinfo;
+ char *sanitized;
parentTbinfo = getRootTableInfo(tbinfo);
copyFrom = fmtQualifiedDumpable(parentTbinfo);
+ sanitized = sanitize_line(copyFrom, true);
printfPQExpBuffer(copyBuf, "-- load via partition root %s",
- copyFrom);
+ sanitized);
+ free(sanitized);
tdDefn = pg_strdup(copyBuf->data);
}
else
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 8b9e8543733..d6263554502 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -137,6 +137,8 @@ static char *filename = NULL;
static SimpleStringList database_exclude_patterns = {NULL, NULL};
static SimpleStringList database_exclude_names = {NULL, NULL};
+static char *restrict_key;
+
#define exit_nicely(code) exit(code)
int
@@ -195,6 +197,7 @@ main(int argc, char *argv[])
{"no-unlogged-table-data", no_argument, &no_unlogged_table_data, 1},
{"on-conflict-do-nothing", no_argument, &on_conflict_do_nothing, 1},
{"rows-per-insert", required_argument, NULL, 7},
+ {"restrict-key", required_argument, NULL, 9},
/* START MPP ADDITION */
{"gp-syntax", no_argument, NULL, 1000},
@@ -395,6 +398,12 @@ main(int argc, char *argv[])
appendShellString(pgdumpopts, optarg);
break;
+ case 9:
+ restrict_key = pg_strdup(optarg);
+ appendPQExpBufferStr(pgdumpopts, " --restrict-key ");
+ appendShellString(pgdumpopts, optarg);
+ break;
+
/* START MPP ADDITION */
case 1000:
/* gp-format */
@@ -410,7 +419,6 @@ main(int argc, char *argv[])
break;
/* END MPP ADDITION */
-
default:
/* getopt_long already emitted a complaint */
pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -519,6 +527,16 @@ main(int argc, char *argv[])
if (roles_only)
appendPQExpBufferStr(pgdumpopts, " --roles-only");
+ /*
+ * If you don't provide a restrict key, one will be appointed for you.
+ */
+ if (!restrict_key)
+ restrict_key = generate_restrict_key();
+ if (!restrict_key)
+ pg_fatal("could not generate restrict key");
+ if (!valid_restrict_key(restrict_key))
+ pg_fatal("invalid restrict key");
+
/*
* If there was a database specified on the command line, use that,
* otherwise try to connect to database "postgres", and failing that
@@ -606,6 +624,16 @@ main(int argc, char *argv[])
if (verbose)
dumpTimestamp("Started on");
+ /*
+ * Enter restricted mode to block any unexpected psql meta-commands. A
+ * malicious source might try to inject a variety of things via bogus
+ * responses to queries. While we cannot prevent such sources from
+ * affecting the destination at restore time, we can block psql
+ * meta-commands so that the client machine that runs psql with the dump
+ * output remains unaffected.
+ */
+ fprintf(OPF, "\\restrict %s\n\n", restrict_key);
+
/*
* We used to emit \connect postgres here, but that served no purpose
* other than to break things for installations without a postgres
@@ -696,6 +724,12 @@ main(int argc, char *argv[])
dumpTablespaces(conn);
}
+ /*
+ * Exit restricted mode just before dumping the databases. pg_dump will
+ * handle entering restricted mode again as appropriate.
+ */
+ fprintf(OPF, "\\unrestrict %s\n\n", restrict_key);
+
if (!globals_only && !roles_only && !tablespaces_only)
dumpDatabases(conn);
@@ -765,6 +799,7 @@ help(void)
printf(_(" --no-unlogged-table-data do not dump unlogged table data\n"));
printf(_(" --on-conflict-do-nothing add ON CONFLICT DO NOTHING to INSERT commands\n"));
printf(_(" --quote-all-identifiers quote all identifiers, even if not key words\n"));
+ printf(_(" --restrict-key=RESTRICT_KEY use provided string as psql \\restrict key\n"));
printf(_(" --rows-per-insert=NROWS number of rows per INSERT; implies --inserts\n"));
printf(_(" --use-set-session-authorization\n"
" use SET SESSION AUTHORIZATION commands instead of\n"
@@ -2089,7 +2124,13 @@ dumpUserConfig(PGconn *conn, const char *username)
res = executeQuery(conn, buf->data);
if (PQntuples(res) > 0)
- fprintf(OPF, "\n--\n-- User Config \"%s\"\n--\n\n", username);
+ {
+ char *sanitized;
+
+ sanitized = sanitize_line(username, true);
+ fprintf(OPF, "\n--\n-- User Config \"%s\"\n--\n\n", sanitized);
+ free(sanitized);
+ }
for (int i = 0; i < PQntuples(res); i++)
{
@@ -2456,6 +2497,7 @@ dumpDatabases(PGconn *conn)
for (i = 0; i < PQntuples(res); i++)
{
char *dbname = PQgetvalue(res, i, 0);
+ char *sanitized;
const char *create_opts;
int ret;
@@ -2472,7 +2514,9 @@ dumpDatabases(PGconn *conn)
pg_log_info("dumping database \"%s\"", dbname);
- fprintf(OPF, "--\n-- Database \"%s\" dump\n--\n\n", dbname);
+ sanitized = sanitize_line(dbname, true);
+ fprintf(OPF, "--\n-- Database \"%s\" dump\n--\n\n", sanitized);
+ free(sanitized);
/*
* We assume that "template1" and "postgres" already exist in the
diff --git a/src/bin/pg_dump/pg_restore.c b/src/bin/pg_dump/pg_restore.c
index 0249f52fbdf..20ead15055f 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -125,6 +125,7 @@ main(int argc, char **argv)
{"no-publications", no_argument, &no_publications, 1},
{"no-security-labels", no_argument, &no_security_labels, 1},
{"no-subscriptions", no_argument, &no_subscriptions, 1},
+ {"restrict-key", required_argument, NULL, 6},
/* GPDB */
{"binary-upgrade", no_argument, &binary_upgrade, 1},
@@ -291,6 +292,10 @@ main(int argc, char **argv)
set_dump_section(optarg, &(opts->dumpSections));
break;
+ case 6:
+ opts->restrict_key = pg_strdup(optarg);
+ break;
+
default:
/* getopt_long already emitted a complaint */
pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -326,8 +331,24 @@ main(int argc, char **argv)
pg_log_error_hint("Try \"%s --help\" for more information.", progname);
exit_nicely(1);
}
+
+ if (opts->restrict_key)
+ pg_fatal("options -d/--dbname and --restrict-key cannot be used together");
+
opts->useDB = 1;
}
+ else
+ {
+ /*
+ * If you don't provide a restrict key, one will be appointed for you.
+ */
+ if (!opts->restrict_key)
+ opts->restrict_key = generate_restrict_key();
+ if (!opts->restrict_key)
+ pg_fatal("could not generate restrict key");
+ if (!valid_restrict_key(opts->restrict_key))
+ pg_fatal("invalid restrict key");
+ }
if (opts->dataOnly && opts->schemaOnly)
pg_fatal("options -s/--schema-only and -a/--data-only cannot be used together");
@@ -481,6 +502,7 @@ usage(const char *progname)
printf(_(" --no-subscriptions do not restore subscriptions\n"));
printf(_(" --no-table-access-method do not restore table access methods\n"));
printf(_(" --no-tablespaces do not restore tablespace assignments\n"));
+ printf(_(" --restrict-key=RESTRICT_KEY use provided string as psql \\restrict key\n"));
printf(_(" --section=SECTION restore named section (pre-data, data, or post-data)\n"));
printf(_(" --strict-names require table and/or schema include patterns to\n"
" match at least one entity each\n"));
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index d7fad5ba455..201b80ea709 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -729,6 +729,16 @@
# This is where the actual tests are defined.
my %tests = (
+ 'restrict' => {
+ all_runs => 1,
+ regexp => qr/^\\restrict [a-zA-Z0-9]+$/m,
+ },
+
+ 'unrestrict' => {
+ all_runs => 1,
+ regexp => qr/^\\unrestrict [a-zA-Z0-9]+$/m,
+ },
+
'ALTER DEFAULT PRIVILEGES FOR ROLE regress_dump_test_role GRANT' => {
create_order => 14,
create_sql => 'ALTER DEFAULT PRIVILEGES
@@ -1929,6 +1939,27 @@
},
},
+ 'newline of role or table name in comment' => {
+ create_sql => qq{CREATE ROLE regress_newline;
+ ALTER ROLE regress_newline SET enable_seqscan = off;
+ ALTER ROLE regress_newline
+ RENAME TO "regress_newline\nattack";
+
+ -- meet getPartitioningInfo() "unsafe" condition
+ CREATE TYPE pp_colors AS
+ ENUM ('green', 'blue', 'black');
+ CREATE TABLE pp_enumpart (a pp_colors)
+ PARTITION BY HASH (a);
+ CREATE TABLE pp_enumpart1 PARTITION OF pp_enumpart
+ FOR VALUES WITH (MODULUS 2, REMAINDER 0);
+ CREATE TABLE pp_enumpart2 PARTITION OF pp_enumpart
+ FOR VALUES WITH (MODULUS 2, REMAINDER 1);
+ ALTER TABLE pp_enumpart
+ RENAME TO "pp_enumpart\nattack";},
+ regexp => qr/\n--[^\n]*\nattack/s,
+ like => {},
+ },
+
'CREATE DATABASE regression_invalid...' => {
create_order => 1,
create_sql => q(
@@ -3941,7 +3972,6 @@
},
'ALTER TABLE measurement PRIMARY KEY' => {
- all_runs => 1,
catch_all => 'CREATE ... commands',
create_order => 93,
create_sql =>
@@ -3994,7 +4024,6 @@
},
'ALTER INDEX ... ATTACH PARTITION (primary key)' => {
- all_runs => 1,
catch_all => 'CREATE ... commands',
regexp => qr/^
\QALTER INDEX dump_test.measurement_pkey ATTACH PARTITION dump_test_second_schema.measurement_y2006m2_pkey\E
@@ -5029,9 +5058,10 @@
next;
}
- # Run the test listed as a like, unless it is specifically noted
- # as an unlike (generally due to an explicit exclusion or similar).
- if ($tests{$test}->{like}->{$test_key}
+ # Run the test if all_runs is set or if listed as a like, unless it is
+ # specifically noted as an unlike (generally due to an explicit
+ # exclusion or similar).
+ if (($tests{$test}->{like}->{$test_key} || $tests{$test}->{all_runs})
&& !defined($tests{$test}->{unlike}->{$test_key}))
{
if (!ok($output_file =~ $tests{$test}->{regexp},
diff --git a/src/bin/pg_dump/t/003_pg_dump_with_server.pl b/src/bin/pg_dump/t/003_pg_dump_with_server.pl
index ab025c44a43..6c6bee4fe0c 100644
--- a/src/bin/pg_dump/t/003_pg_dump_with_server.pl
+++ b/src/bin/pg_dump/t/003_pg_dump_with_server.pl
@@ -16,6 +16,22 @@
$node->init;
$node->start;
+#########################################
+# pg_dumpall: newline in database name
+
+$node->safe_psql('postgres', qq{CREATE DATABASE "regress_\nattack"});
+
+my (@cmd, $stdout, $stderr);
+@cmd = ("pg_dumpall", '--port' => $port, '--exclude-database=postgres');
+print("# Running: " . join(" ", @cmd) . "\n");
+my $result = IPC::Run::run \@cmd, '>' => \$stdout, '2>' => \$stderr;
+ok(!$result, "newline in dbname: exit code not 0");
+like(
+ $stderr,
+ qr/shell command argument contains a newline/,
+ "newline in dbname: stderr matches");
+unlike($stdout, qr/^attack/m, "newline in dbname: no comment escape");
+
#########################################
# Verify that dumping foreign data includes only foreign tables of
# matching servers
@@ -26,7 +42,6 @@
$node->safe_psql('postgres', "CREATE SERVER s2 FOREIGN DATA WRAPPER dummy");
$node->safe_psql('postgres', "CREATE FOREIGN TABLE t0 (a int) SERVER s0");
$node->safe_psql('postgres', "CREATE FOREIGN TABLE t1 (a int) SERVER s1");
-my ($cmd, $stdout, $stderr, $result);
command_fails_like(
[ "pg_dump", '-p', $port, '--include-foreign-data=s0', 'postgres' ],
diff --git a/src/bin/pg_upgrade/t/002_pg_upgrade.pl b/src/bin/pg_upgrade/t/002_pg_upgrade.pl
index 3bf4e87b178..056538af53c 100644
--- a/src/bin/pg_upgrade/t/002_pg_upgrade.pl
+++ b/src/bin/pg_upgrade/t/002_pg_upgrade.pl
@@ -264,6 +264,7 @@ sub filter_dump
# that we need to use pg_dumpall from the new node here.
my @dump_command = (
'pg_dumpall', '--no-sync', '-d', $oldnode->connstr('postgres'),
+ '--restrict-key=test',
'-f', $dump1_file);
# --extra-float-digits is needed when upgrading from a version older than 11.
push(@dump_command, '--extra-float-digits', '0')
@@ -449,6 +450,7 @@ sub filter_dump
# Second dump from the upgraded instance.
@dump_command = (
'pg_dumpall', '--no-sync', '-d', $newnode->connstr('postgres'),
+ '--restrict-key=test',
'-f', $dump2_file);
# --extra-float-digits is needed when upgrading from a version older than 11.
push(@dump_command, '--extra-float-digits', '0')
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 6dfd388a892..81859e02ae6 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -123,6 +123,8 @@ static backslashResult exec_command_pset(PsqlScanState scan_state, bool active_b
static backslashResult exec_command_quit(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_reset(PsqlScanState scan_state, bool active_branch,
PQExpBuffer query_buf);
+static backslashResult exec_command_restrict(PsqlScanState scan_state, bool active_branch,
+ const char *cmd);
static backslashResult exec_command_s(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_set(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_setenv(PsqlScanState scan_state, bool active_branch,
@@ -132,6 +134,8 @@ static backslashResult exec_command_sf_sv(PsqlScanState scan_state, bool active_
static backslashResult exec_command_t(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_T(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_timing(PsqlScanState scan_state, bool active_branch);
+static backslashResult exec_command_unrestrict(PsqlScanState scan_state, bool active_branch,
+ const char *cmd);
static backslashResult exec_command_unset(PsqlScanState scan_state, bool active_branch,
const char *cmd);
static backslashResult exec_command_write(PsqlScanState scan_state, bool active_branch,
@@ -181,6 +185,8 @@ static char *pset_value_string(const char *param, printQueryOpt *popt);
static void checkWin32Codepage(void);
#endif
+static bool restricted;
+static char *restrict_key;
/*----------
@@ -226,8 +232,19 @@ HandleSlashCmds(PsqlScanState scan_state,
/* Parse off the command name */
cmd = psql_scan_slash_command(scan_state);
- /* And try to execute it */
- status = exec_command(cmd, scan_state, cstack, query_buf, previous_buf);
+ /*
+ * And try to execute it.
+ *
+ * If we are in "restricted" mode, the only allowable backslash command is
+ * \unrestrict (to exit restricted mode).
+ */
+ if (restricted && strcmp(cmd, "unrestrict") != 0)
+ {
+ pg_log_error("backslash commands are restricted; only \\unrestrict is allowed");
+ status = PSQL_CMD_ERROR;
+ }
+ else
+ status = exec_command(cmd, scan_state, cstack, query_buf, previous_buf);
if (status == PSQL_CMD_UNKNOWN)
{
@@ -388,6 +405,8 @@ exec_command(const char *cmd,
status = exec_command_quit(scan_state, active_branch);
else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
status = exec_command_reset(scan_state, active_branch, query_buf);
+ else if (strcmp(cmd, "restrict") == 0)
+ status = exec_command_restrict(scan_state, active_branch, cmd);
else if (strcmp(cmd, "s") == 0)
status = exec_command_s(scan_state, active_branch);
else if (strcmp(cmd, "set") == 0)
@@ -404,6 +423,8 @@ exec_command(const char *cmd,
status = exec_command_T(scan_state, active_branch);
else if (strcmp(cmd, "timing") == 0)
status = exec_command_timing(scan_state, active_branch);
+ else if (strcmp(cmd, "unrestrict") == 0)
+ status = exec_command_unrestrict(scan_state, active_branch, cmd);
else if (strcmp(cmd, "unset") == 0)
status = exec_command_unset(scan_state, active_branch, cmd);
else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0)
@@ -2345,6 +2366,35 @@ exec_command_reset(PsqlScanState scan_state, bool active_branch,
return PSQL_CMD_SKIP_LINE;
}
+/*
+ * \restrict -- enter "restricted mode" with the provided key
+ */
+static backslashResult
+exec_command_restrict(PsqlScanState scan_state, bool active_branch,
+ const char *cmd)
+{
+ if (active_branch)
+ {
+ char *opt;
+
+ Assert(!restricted);
+
+ opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
+ if (opt == NULL || opt[0] == '\0')
+ {
+ pg_log_error("\\%s: missing required argument", cmd);
+ return PSQL_CMD_ERROR;
+ }
+
+ restrict_key = pstrdup(opt);
+ restricted = true;
+ }
+ else
+ ignore_slash_options(scan_state);
+
+ return PSQL_CMD_SKIP_LINE;
+}
+
/*
* \s -- save history in a file or show it on the screen
*/
@@ -2632,6 +2682,46 @@ exec_command_timing(PsqlScanState scan_state, bool active_branch)
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
}
+/*
+ * \unrestrict -- exit "restricted mode" if provided key matches
+ */
+static backslashResult
+exec_command_unrestrict(PsqlScanState scan_state, bool active_branch,
+ const char *cmd)
+{
+ if (active_branch)
+ {
+ char *opt;
+
+ opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
+ if (opt == NULL || opt[0] == '\0')
+ {
+ pg_log_error("\\%s: missing required argument", cmd);
+ return PSQL_CMD_ERROR;
+ }
+
+ if (!restricted)
+ {
+ pg_log_error("\\%s: not currently in restricted mode", cmd);
+ return PSQL_CMD_ERROR;
+ }
+ else if (strcmp(opt, restrict_key) == 0)
+ {
+ pfree(restrict_key);
+ restricted = false;
+ }
+ else
+ {
+ pg_log_error("\\%s: wrong key", cmd);
+ return PSQL_CMD_ERROR;
+ }
+ }
+ else
+ ignore_slash_options(scan_state);
+
+ return PSQL_CMD_SKIP_LINE;
+}
+
/*
* \unset -- unset variable
*/
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 5931faa3e2f..8583af7b0f2 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -203,6 +203,10 @@ slashUsage(unsigned short int pager)
HELP0(" \\gset [PREFIX] execute query and store result in psql variables\n");
HELP0(" \\gx [(OPTIONS)] [FILE] as \\g, but forces expanded output mode\n");
HELP0(" \\q quit psql\n");
+ HELP0(" \\restrict RESTRICT_KEY\n"
+ " enter restricted mode with provided key\n");
+ HELP0(" \\unrestrict RESTRICT_KEY\n"
+ " exit restricted mode if key matches\n");
HELP0(" \\watch [[i=]SEC] [c=N] execute query every SEC seconds, up to N times\n");
HELP0("\n");
diff --git a/src/bin/psql/t/001_basic.pl b/src/bin/psql/t/001_basic.pl
index 3599cc1ef09..f5ec8fab1f3 100644
--- a/src/bin/psql/t/001_basic.pl
+++ b/src/bin/psql/t/001_basic.pl
@@ -388,4 +388,11 @@ sub psql_fails_like
qr/iteration count is specified more than once/,
'\watch, iteration count is specified more than once');
+psql_fails_like(
+ $node,
+ qq{\\restrict test
+\\! should_fail},
+ qr/backslash commands are restricted; only \\unrestrict is allowed/,
+ 'meta-command in restrict mode fails');
+
done_testing();
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 0283a9424c4..81a81578c0b 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1767,10 +1767,10 @@ psql_completion(const char *text, int start, int end)
"\\out",
"\\password", "\\print", "\\prompt", "\\pset",
"\\qecho", "\\quit",
- "\\reset",
+ "\\reset", "\\restrict",
"\\s", "\\set", "\\setenv", "\\sf", "\\sv",
"\\t", "\\T", "\\timing",
- "\\unset",
+ "\\unrestrict", "\\unset",
"\\x",
"\\warn", "\\watch", "\\write",
"\\z",
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 466097c94c7..18e04953f4e 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -238,6 +238,7 @@ extern void ExecutorRewind(QueryDesc *queryDesc);
extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation, ModifyTableState *mtstate);
extern bool ExecCheckPermissions(List *rangeTable,
List *rteperminfos, bool ereport_on_violation);
+extern bool ExecCheckOneRelPerms(RTEPermissionInfo *perminfo);
extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h
index 6886a0a3cdb..9f8d5d93562 100644
--- a/src/include/utils/selfuncs.h
+++ b/src/include/utils/selfuncs.h
@@ -97,7 +97,8 @@ typedef struct VariableStatData
Oid atttype; /* actual type (after stripping relabel) */
int32 atttypmod; /* actual typmod (after stripping relabel) */
bool isunique; /* matches unique index or DISTINCT clause */
- bool acl_ok; /* result of ACL check on table or column */
+ bool acl_ok; /* true if user has SELECT privilege on all
+ * rows from the table or column */
} VariableStatData;
#define ReleaseVariableStats(vardata) \
@@ -151,6 +152,7 @@ extern PGDLLIMPORT get_index_stats_hook_type get_index_stats_hook;
extern void examine_variable(PlannerInfo *root, Node *node, int varRelid,
VariableStatData *vardata);
+extern bool all_rows_selectable(PlannerInfo *root, Index varno, Bitmapset *varattnos);
extern bool statistic_proc_security_check(VariableStatData *vardata, Oid func_oid);
extern bool get_restriction_variable(PlannerInfo *root, List *args,
int varRelid,
diff --git a/src/test/recovery/t/027_stream_regress.pl b/src/test/recovery/t/027_stream_regress.pl
index a68351afadb..f697164a1df 100644
--- a/src/test/recovery/t/027_stream_regress.pl
+++ b/src/test/recovery/t/027_stream_regress.pl
@@ -112,6 +112,7 @@
command_ok(
[
'pg_dumpall', '-f', $outputdir . '/primary.dump',
+ '--restrict-key=test',
'--no-sync', '-p', $node_primary->port,
'--no-unlogged-table-data' # if unlogged, standby has schema only
],
@@ -119,6 +120,7 @@
command_ok(
[
'pg_dumpall', '-f', $outputdir . '/standby.dump',
+ '--restrict-key=test',
'--no-sync', '-p', $node_standby_1->port
],
'dump standby server');
@@ -137,6 +139,7 @@
('--schema', 'pg_catalog'),
('-f', $outputdir . '/catalogs_primary.dump'),
'--no-sync',
+ '--restrict-key=test',
('-p', $node_primary->port),
'--no-unlogged-table-data',
'regression'
@@ -148,6 +151,7 @@
('--schema', 'pg_catalog'),
('-f', $outputdir . '/catalogs_standby.dump'),
'--no-sync',
+ '--restrict-key=test',
('-p', $node_standby_1->port),
'regression'
],
diff --git a/src/test/regress/expected/minirepro.out b/src/test/regress/expected/minirepro.out
index 3b876793628..22fa88b4320 100644
--- a/src/test/regress/expected/minirepro.out
+++ b/src/test/regress/expected/minirepro.out
@@ -305,10 +305,10 @@ SET
SET
SET
SET
-psql:data/minirepro.sql:48: ERROR: only shared relations can be placed in pg_global tablespace
-psql:data/minirepro.sql:50: ERROR: permission denied: "pg_tablespace" is a system catalog
-psql:data/minirepro.sql:58: ERROR: permission denied: "pg_tablespace" is a system catalog
-psql:data/minirepro.sql:66: ERROR: permission denied: "pg_tablespace" is a system catalog
+psql:data/minirepro.sql:50: ERROR: only shared relations can be placed in pg_global tablespace
+psql:data/minirepro.sql:52: ERROR: permission denied: "pg_tablespace" is a system catalog
+psql:data/minirepro.sql:60: ERROR: permission denied: "pg_tablespace" is a system catalog
+psql:data/minirepro.sql:68: ERROR: permission denied: "pg_tablespace" is a system catalog
SET
UPDATE 1
DELETE 1
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index a625d39ea88..335a0b58105 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -477,8 +477,6 @@ CREATE VIEW atest12v AS
SELECT * FROM atest12 WHERE b <<< 5;
CREATE VIEW atest12sbv WITH (security_barrier=true) AS
SELECT * FROM atest12 WHERE b <<< 5;
-GRANT SELECT ON atest12v TO PUBLIC;
-GRANT SELECT ON atest12sbv TO PUBLIC;
-- This plan should use nestloop, knowing that few rows will be selected.
EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y WHERE x.a = y.b;
QUERY PLAN
@@ -537,9 +535,18 @@ CREATE FUNCTION leak2(integer,integer) RETURNS boolean
LANGUAGE plpgsql immutable;
CREATE OPERATOR >>> (procedure = leak2, leftarg = integer, rightarg = integer,
restrict = scalargtsel);
--- This should not show any "leak" notices before failing.
+-- These should not show any "leak" notices before failing.
EXPLAIN (COSTS OFF) SELECT * FROM atest12 WHERE a >>> 0;
ERROR: permission denied for table atest12
+EXPLAIN (COSTS OFF) SELECT * FROM atest12v WHERE a >>> 0;
+ERROR: permission denied for view atest12v
+EXPLAIN (COSTS OFF) SELECT * FROM atest12sbv WHERE a >>> 0;
+ERROR: permission denied for view atest12sbv
+-- Now regress_priv_user1 grants access to regress_priv_user2 via the views.
+SET SESSION AUTHORIZATION regress_priv_user1;
+GRANT SELECT ON atest12v TO PUBLIC;
+GRANT SELECT ON atest12sbv TO PUBLIC;
+SET SESSION AUTHORIZATION regress_priv_user2;
-- These plans should continue to use a nestloop, since they execute with the
-- privileges of the view owner.
EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y WHERE x.a = y.b;
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 36b46acbebf..ab8f55b7358 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -4636,6 +4636,7 @@ invalid command \lo
\pset arg1 arg2
\q
\reset
+ \restrict test
\s arg1
\set arg1 arg2 arg3 arg4 arg5 arg6 arg7
\setenv arg1 arg2
@@ -4644,6 +4645,7 @@ invalid command \lo
\t arg1
\T arg1
\timing arg1
+ \unrestrict not_valid
\unset arg1
\w arg1
\watch arg1 arg2
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index 03fc362557b..71b0f828c5a 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -4612,7 +4612,7 @@ RESET SESSION AUTHORIZATION;
DROP VIEW rls_view;
DROP TABLE rls_tbl;
DROP TABLE ref_tbl;
--- Leaky operator test
+-- Leaky operator tests
CREATE TABLE rls_tbl (a int);
INSERT INTO rls_tbl SELECT x/10 FROM generate_series(1, 100) x;
ANALYZE rls_tbl;
@@ -4629,9 +4629,80 @@ SELECT * FROM rls_tbl WHERE a <<< 1000;
---
(0 rows)
+RESET SESSION AUTHORIZATION;
+CREATE TABLE rls_child_tbl () INHERITS (rls_tbl);
+INSERT INTO rls_child_tbl SELECT x/10 FROM generate_series(1, 100) x;
+ANALYZE rls_child_tbl;
+CREATE TABLE rls_ptbl (a int) PARTITION BY RANGE (a);
+CREATE TABLE rls_part PARTITION OF rls_ptbl FOR VALUES FROM (-100) TO (100);
+INSERT INTO rls_ptbl SELECT x/10 FROM generate_series(1, 100) x;
+ANALYZE rls_ptbl, rls_part;
+ALTER TABLE rls_ptbl ENABLE ROW LEVEL SECURITY;
+ALTER TABLE rls_part ENABLE ROW LEVEL SECURITY;
+GRANT SELECT ON rls_ptbl TO regress_rls_alice;
+GRANT SELECT ON rls_part TO regress_rls_alice;
+CREATE POLICY p1 ON rls_tbl USING (a < 0);
+CREATE POLICY p2 ON rls_ptbl USING (a < 0);
+CREATE POLICY p3 ON rls_part USING (a < 0);
+SET SESSION AUTHORIZATION regress_rls_alice;
+SELECT * FROM rls_tbl WHERE a <<< 1000;
+ a
+---
+(0 rows)
+
+SELECT * FROM rls_child_tbl WHERE a <<< 1000;
+ERROR: permission denied for table rls_child_tbl
+SELECT * FROM rls_ptbl WHERE a <<< 1000;
+ a
+---
+(0 rows)
+
+SELECT * FROM rls_part WHERE a <<< 1000;
+ a
+---
+(0 rows)
+
+SELECT * FROM (SELECT * FROM rls_tbl UNION ALL
+ SELECT * FROM rls_tbl) t WHERE a <<< 1000;
+ a
+---
+(0 rows)
+
+SELECT * FROM (SELECT * FROM rls_child_tbl UNION ALL
+ SELECT * FROM rls_child_tbl) t WHERE a <<< 1000;
+ERROR: permission denied for table rls_child_tbl
+RESET SESSION AUTHORIZATION;
+REVOKE SELECT ON rls_tbl FROM regress_rls_alice;
+CREATE VIEW rls_tbl_view AS SELECT * FROM rls_tbl;
+ALTER TABLE rls_child_tbl ENABLE ROW LEVEL SECURITY;
+GRANT SELECT ON rls_child_tbl TO regress_rls_alice;
+CREATE POLICY p4 ON rls_child_tbl USING (a < 0);
+SET SESSION AUTHORIZATION regress_rls_alice;
+SELECT * FROM rls_tbl WHERE a <<< 1000;
+ERROR: permission denied for table rls_tbl
+SELECT * FROM rls_tbl_view WHERE a <<< 1000;
+ERROR: permission denied for view rls_tbl_view
+SELECT * FROM rls_child_tbl WHERE a <<< 1000;
+ a
+---
+(0 rows)
+
+SELECT * FROM (SELECT * FROM rls_tbl UNION ALL
+ SELECT * FROM rls_tbl) t WHERE a <<< 1000;
+ERROR: permission denied for table rls_tbl
+SELECT * FROM (SELECT * FROM rls_child_tbl UNION ALL
+ SELECT * FROM rls_child_tbl) t WHERE a <<< 1000;
+ a
+---
+(0 rows)
+
DROP OPERATOR <<< (int, int);
DROP FUNCTION op_leak(int, int);
RESET SESSION AUTHORIZATION;
+DROP TABLE rls_part;
+DROP TABLE rls_ptbl;
+DROP TABLE rls_child_tbl;
+DROP VIEW rls_tbl_view;
DROP TABLE rls_tbl;
-- Bug #16006: whole-row Vars in a policy don't play nice with sub-selects
SET SESSION AUTHORIZATION regress_rls_alice;
diff --git a/src/test/regress/expected/rowsecurity_optimizer.out b/src/test/regress/expected/rowsecurity_optimizer.out
index 6cadc77c912..c875309c7eb 100644
--- a/src/test/regress/expected/rowsecurity_optimizer.out
+++ b/src/test/regress/expected/rowsecurity_optimizer.out
@@ -5185,7 +5185,7 @@ RESET SESSION AUTHORIZATION;
DROP VIEW rls_view;
DROP TABLE rls_tbl;
DROP TABLE ref_tbl;
--- Leaky operator test
+-- Leaky operator tests
CREATE TABLE rls_tbl (a int);
NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Greenplum Database data distribution key for this table.
HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew.
@@ -5204,9 +5204,84 @@ SELECT * FROM rls_tbl WHERE a <<< 1000;
---
(0 rows)
+RESET SESSION AUTHORIZATION;
+CREATE TABLE rls_child_tbl () INHERITS (rls_tbl);
+INSERT INTO rls_child_tbl SELECT x/10 FROM generate_series(1, 100) x;
+ANALYZE rls_child_tbl;
+CREATE TABLE rls_ptbl (a int) PARTITION BY RANGE (a);
+CREATE TABLE rls_part PARTITION OF rls_ptbl FOR VALUES FROM (-100) TO (100);
+INSERT INTO rls_ptbl SELECT x/10 FROM generate_series(1, 100) x;
+ANALYZE rls_ptbl, rls_part;
+ALTER TABLE rls_ptbl ENABLE ROW LEVEL SECURITY;
+ALTER TABLE rls_part ENABLE ROW LEVEL SECURITY;
+GRANT SELECT ON rls_ptbl TO regress_rls_alice;
+GRANT SELECT ON rls_part TO regress_rls_alice;
+CREATE POLICY p1 ON rls_tbl USING (a < 0);
+CREATE POLICY p2 ON rls_ptbl USING (a < 0);
+CREATE POLICY p3 ON rls_part USING (a < 0);
+SET SESSION AUTHORIZATION regress_rls_alice;
+SELECT * FROM rls_tbl WHERE a <<< 1000;
+INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner
+DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Inherited tables
+ a
+---
+(0 rows)
+
+SELECT * FROM rls_child_tbl WHERE a <<< 1000;
+ERROR: permission denied for table rls_child_tbl
+SELECT * FROM rls_ptbl WHERE a <<< 1000;
+ a
+---
+(0 rows)
+
+SELECT * FROM rls_part WHERE a <<< 1000;
+ a
+---
+(0 rows)
+
+SELECT * FROM (SELECT * FROM rls_tbl UNION ALL
+ SELECT * FROM rls_tbl) t WHERE a <<< 1000;
+INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner
+DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Inherited tables
+ a
+---
+(0 rows)
+
+SELECT * FROM (SELECT * FROM rls_child_tbl UNION ALL
+ SELECT * FROM rls_child_tbl) t WHERE a <<< 1000;
+ERROR: permission denied for table rls_child_tbl
+RESET SESSION AUTHORIZATION;
+REVOKE SELECT ON rls_tbl FROM regress_rls_alice;
+CREATE VIEW rls_tbl_view AS SELECT * FROM rls_tbl;
+ALTER TABLE rls_child_tbl ENABLE ROW LEVEL SECURITY;
+GRANT SELECT ON rls_child_tbl TO regress_rls_alice;
+CREATE POLICY p4 ON rls_child_tbl USING (a < 0);
+SET SESSION AUTHORIZATION regress_rls_alice;
+SELECT * FROM rls_tbl WHERE a <<< 1000;
+ERROR: permission denied for table rls_tbl
+SELECT * FROM rls_tbl_view WHERE a <<< 1000;
+ERROR: permission denied for view rls_tbl_view
+SELECT * FROM rls_child_tbl WHERE a <<< 1000;
+ a
+---
+(0 rows)
+
+SELECT * FROM (SELECT * FROM rls_tbl UNION ALL
+ SELECT * FROM rls_tbl) t WHERE a <<< 1000;
+ERROR: permission denied for table rls_tbl
+SELECT * FROM (SELECT * FROM rls_child_tbl UNION ALL
+ SELECT * FROM rls_child_tbl) t WHERE a <<< 1000;
+ a
+---
+(0 rows)
+
DROP OPERATOR <<< (int, int);
DROP FUNCTION op_leak(int, int);
RESET SESSION AUTHORIZATION;
+DROP TABLE rls_part;
+DROP TABLE rls_ptbl;
+DROP TABLE rls_child_tbl;
+DROP VIEW rls_tbl_view;
DROP TABLE rls_tbl;
-- Bug #16006: whole-row Vars in a policy don't play nice with sub-selects
SET SESSION AUTHORIZATION regress_rls_alice;
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 80bf1733e89..e7303fd5b2a 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -3265,8 +3265,16 @@ CREATE FUNCTION op_leak(int, int) RETURNS bool
LANGUAGE plpgsql;
CREATE OPERATOR <<< (procedure = op_leak, leftarg = int, rightarg = int,
restrict = scalarltsel);
+CREATE FUNCTION op_leak(record, record) RETURNS bool
+ AS 'BEGIN RAISE NOTICE ''op_leak => %, %'', $1, $2; RETURN $1 < $2; END'
+ LANGUAGE plpgsql;
+CREATE OPERATOR <<< (procedure = op_leak, leftarg = record, rightarg = record,
+ restrict = scalarltsel);
SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied
ERROR: permission denied for table priv_test_tbl
+SELECT * FROM tststats.priv_test_tbl t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Permission denied
+ERROR: permission denied for table priv_test_tbl
DELETE FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied
ERROR: permission denied for table priv_test_tbl
-- Grant access via a security barrier view, but hide all data
@@ -3281,10 +3289,17 @@ SELECT * FROM tststats.priv_test_view WHERE a <<< 0 AND b <<< 0; -- Should not l
---+---
(0 rows)
+SELECT * FROM tststats.priv_test_view t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Should not leak
+ a | b
+---+---
+(0 rows)
+
DELETE FROM tststats.priv_test_view WHERE a <<< 0 AND b <<< 0; -- Should not leak
-- Grant table access, but hide all data with RLS
RESET SESSION AUTHORIZATION;
ALTER TABLE tststats.priv_test_tbl ENABLE ROW LEVEL SECURITY;
+CREATE POLICY priv_test_tbl_pol ON tststats.priv_test_tbl USING (2 * a < 0);
GRANT SELECT, DELETE ON tststats.priv_test_tbl TO regress_stats_user1;
-- Should now have direct table access, but see nothing and leak nothing
SET SESSION AUTHORIZATION regress_stats_user1;
@@ -3293,7 +3308,45 @@ SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Should not le
---+---
(0 rows)
+SELECT * FROM tststats.priv_test_tbl t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Should not leak
+ a | b
+---+---
+(0 rows)
+
DELETE FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak
+-- Create plain inheritance parent table with no access permissions
+RESET SESSION AUTHORIZATION;
+CREATE TABLE tststats.priv_test_parent_tbl (a int, b int);
+ALTER TABLE tststats.priv_test_tbl INHERIT tststats.priv_test_parent_tbl;
+-- Should not have access to parent, and should leak nothing
+SET SESSION AUTHORIZATION regress_stats_user1;
+SELECT * FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied
+ERROR: permission denied for table priv_test_parent_tbl
+SELECT * FROM tststats.priv_test_parent_tbl t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Permission denied
+ERROR: permission denied for table priv_test_parent_tbl
+DELETE FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied
+ERROR: permission denied for table priv_test_parent_tbl
+-- Grant table access to parent, but hide all data with RLS
+RESET SESSION AUTHORIZATION;
+ALTER TABLE tststats.priv_test_parent_tbl ENABLE ROW LEVEL SECURITY;
+CREATE POLICY priv_test_parent_tbl_pol ON tststats.priv_test_parent_tbl USING (2 * a < 0);
+GRANT SELECT, DELETE ON tststats.priv_test_parent_tbl TO regress_stats_user1;
+-- Should now have direct table access to parent, but see nothing and leak nothing
+SET SESSION AUTHORIZATION regress_stats_user1;
+SELECT * FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak
+ a | b
+---+---
+(0 rows)
+
+SELECT * FROM tststats.priv_test_parent_tbl t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Should not leak
+ a | b
+---+---
+(0 rows)
+
+DELETE FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak
-- privilege checks for pg_stats_ext and pg_stats_ext_exprs
RESET SESSION AUTHORIZATION;
CREATE TABLE stats_ext_tbl (id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, col TEXT);
@@ -3373,11 +3426,14 @@ ALTER TABLE sts_sch1.tbl ALTER COLUMN a TYPE SMALLINT;
-- Tidy up
DROP OPERATOR <<< (int, int);
DROP FUNCTION op_leak(int, int);
+DROP OPERATOR <<< (record, record);
+DROP FUNCTION op_leak(record, record);
RESET SESSION AUTHORIZATION;
DROP TABLE stats_ext_tbl;
DROP SCHEMA tststats CASCADE;
-NOTICE: drop cascades to 2 other objects
-DETAIL: drop cascades to table tststats.priv_test_tbl
+NOTICE: drop cascades to 3 other objects
+DETAIL: drop cascades to table tststats.priv_test_parent_tbl
+drop cascades to table tststats.priv_test_tbl
drop cascades to view tststats.priv_test_view
DROP SCHEMA sts_sch1, sts_sch2 CASCADE;
NOTICE: drop cascades to table sts_sch1.tbl
diff --git a/src/test/regress/expected/stats_ext_optimizer.out b/src/test/regress/expected/stats_ext_optimizer.out
index cf187147fb9..f528e6a19a4 100644
--- a/src/test/regress/expected/stats_ext_optimizer.out
+++ b/src/test/regress/expected/stats_ext_optimizer.out
@@ -3300,8 +3300,16 @@ CREATE FUNCTION op_leak(int, int) RETURNS bool
LANGUAGE plpgsql;
CREATE OPERATOR <<< (procedure = op_leak, leftarg = int, rightarg = int,
restrict = scalarltsel);
+CREATE FUNCTION op_leak(record, record) RETURNS bool
+ AS 'BEGIN RAISE NOTICE ''op_leak => %, %'', $1, $2; RETURN $1 < $2; END'
+ LANGUAGE plpgsql;
+CREATE OPERATOR <<< (procedure = op_leak, leftarg = record, rightarg = record,
+ restrict = scalarltsel);
SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied
ERROR: permission denied for table priv_test_tbl
+SELECT * FROM tststats.priv_test_tbl t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Permission denied
+ERROR: permission denied for table priv_test_tbl
DELETE FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied
ERROR: permission denied for table priv_test_tbl
-- Grant access via a security barrier view, but hide all data
@@ -3316,10 +3324,17 @@ SELECT * FROM tststats.priv_test_view WHERE a <<< 0 AND b <<< 0; -- Should not l
---+---
(0 rows)
+SELECT * FROM tststats.priv_test_view t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Should not leak
+ a | b
+---+---
+(0 rows)
+
DELETE FROM tststats.priv_test_view WHERE a <<< 0 AND b <<< 0; -- Should not leak
-- Grant table access, but hide all data with RLS
RESET SESSION AUTHORIZATION;
ALTER TABLE tststats.priv_test_tbl ENABLE ROW LEVEL SECURITY;
+CREATE POLICY priv_test_tbl_pol ON tststats.priv_test_tbl USING (2 * a < 0);
GRANT SELECT, DELETE ON tststats.priv_test_tbl TO regress_stats_user1;
-- Should now have direct table access, but see nothing and leak nothing
SET SESSION AUTHORIZATION regress_stats_user1;
@@ -3328,7 +3343,45 @@ SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Should not le
---+---
(0 rows)
+SELECT * FROM tststats.priv_test_tbl t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Should not leak
+ a | b
+---+---
+(0 rows)
+
DELETE FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak
+-- Create plain inheritance parent table with no access permissions
+RESET SESSION AUTHORIZATION;
+CREATE TABLE tststats.priv_test_parent_tbl (a int, b int);
+ALTER TABLE tststats.priv_test_tbl INHERIT tststats.priv_test_parent_tbl;
+-- Should not have access to parent, and should leak nothing
+SET SESSION AUTHORIZATION regress_stats_user1;
+SELECT * FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied
+ERROR: permission denied for table priv_test_parent_tbl
+SELECT * FROM tststats.priv_test_parent_tbl t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Permission denied
+ERROR: permission denied for table priv_test_parent_tbl
+DELETE FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied
+ERROR: permission denied for table priv_test_parent_tbl
+-- Grant table access to parent, but hide all data with RLS
+RESET SESSION AUTHORIZATION;
+ALTER TABLE tststats.priv_test_parent_tbl ENABLE ROW LEVEL SECURITY;
+CREATE POLICY priv_test_parent_tbl_pol ON tststats.priv_test_parent_tbl USING (2 * a < 0);
+GRANT SELECT, DELETE ON tststats.priv_test_parent_tbl TO regress_stats_user1;
+-- Should now have direct table access to parent, but see nothing and leak nothing
+SET SESSION AUTHORIZATION regress_stats_user1;
+SELECT * FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak
+ a | b
+---+---
+(0 rows)
+
+SELECT * FROM tststats.priv_test_parent_tbl t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Should not leak
+ a | b
+---+---
+(0 rows)
+
+DELETE FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak
-- privilege checks for pg_stats_ext and pg_stats_ext_exprs
RESET SESSION AUTHORIZATION;
CREATE TABLE stats_ext_tbl (id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, col TEXT);
@@ -3408,11 +3461,14 @@ ALTER TABLE sts_sch1.tbl ALTER COLUMN a TYPE SMALLINT;
-- Tidy up
DROP OPERATOR <<< (int, int);
DROP FUNCTION op_leak(int, int);
+DROP OPERATOR <<< (record, record);
+DROP FUNCTION op_leak(record, record);
RESET SESSION AUTHORIZATION;
DROP TABLE stats_ext_tbl;
DROP SCHEMA tststats CASCADE;
-NOTICE: drop cascades to 2 other objects
-DETAIL: drop cascades to table tststats.priv_test_tbl
+NOTICE: drop cascades to 3 other objects
+DETAIL: drop cascades to table tststats.priv_test_parent_tbl
+drop cascades to table tststats.priv_test_tbl
drop cascades to view tststats.priv_test_view
DROP SCHEMA sts_sch1, sts_sch2 CASCADE;
NOTICE: drop cascades to table sts_sch1.tbl
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index bf5c8067dd1..d530d14fd8b 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -348,8 +348,6 @@ CREATE VIEW atest12v AS
SELECT * FROM atest12 WHERE b <<< 5;
CREATE VIEW atest12sbv WITH (security_barrier=true) AS
SELECT * FROM atest12 WHERE b <<< 5;
-GRANT SELECT ON atest12v TO PUBLIC;
-GRANT SELECT ON atest12sbv TO PUBLIC;
-- This plan should use nestloop, knowing that few rows will be selected.
EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y WHERE x.a = y.b;
@@ -372,8 +370,16 @@ CREATE FUNCTION leak2(integer,integer) RETURNS boolean
CREATE OPERATOR >>> (procedure = leak2, leftarg = integer, rightarg = integer,
restrict = scalargtsel);
--- This should not show any "leak" notices before failing.
+-- These should not show any "leak" notices before failing.
EXPLAIN (COSTS OFF) SELECT * FROM atest12 WHERE a >>> 0;
+EXPLAIN (COSTS OFF) SELECT * FROM atest12v WHERE a >>> 0;
+EXPLAIN (COSTS OFF) SELECT * FROM atest12sbv WHERE a >>> 0;
+
+-- Now regress_priv_user1 grants access to regress_priv_user2 via the views.
+SET SESSION AUTHORIZATION regress_priv_user1;
+GRANT SELECT ON atest12v TO PUBLIC;
+GRANT SELECT ON atest12sbv TO PUBLIC;
+SET SESSION AUTHORIZATION regress_priv_user2;
-- These plans should continue to use a nestloop, since they execute with the
-- privileges of the view owner.
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 1a726d428ac..574843cc00e 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1021,6 +1021,7 @@ select \if false \\ (bogus \else \\ 42 \endif \\ forty_two;
\pset arg1 arg2
\q
\reset
+ \restrict test
\s arg1
\set arg1 arg2 arg3 arg4 arg5 arg6 arg7
\setenv arg1 arg2
@@ -1029,6 +1030,7 @@ select \if false \\ (bogus \else \\ 42 \endif \\ forty_two;
\t arg1
\T arg1
\timing arg1
+ \unrestrict not_valid
\unset arg1
\w arg1
\watch arg1 arg2
diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql
index 9f3db168808..6ffa1638bf1 100644
--- a/src/test/regress/sql/rowsecurity.sql
+++ b/src/test/regress/sql/rowsecurity.sql
@@ -2151,7 +2151,7 @@ DROP VIEW rls_view;
DROP TABLE rls_tbl;
DROP TABLE ref_tbl;
--- Leaky operator test
+-- Leaky operator tests
CREATE TABLE rls_tbl (a int);
INSERT INTO rls_tbl SELECT x/10 FROM generate_series(1, 100) x;
ANALYZE rls_tbl;
@@ -2166,9 +2166,58 @@ CREATE FUNCTION op_leak(int, int) RETURNS bool
CREATE OPERATOR <<< (procedure = op_leak, leftarg = int, rightarg = int,
restrict = scalarltsel);
SELECT * FROM rls_tbl WHERE a <<< 1000;
+RESET SESSION AUTHORIZATION;
+
+CREATE TABLE rls_child_tbl () INHERITS (rls_tbl);
+INSERT INTO rls_child_tbl SELECT x/10 FROM generate_series(1, 100) x;
+ANALYZE rls_child_tbl;
+
+CREATE TABLE rls_ptbl (a int) PARTITION BY RANGE (a);
+CREATE TABLE rls_part PARTITION OF rls_ptbl FOR VALUES FROM (-100) TO (100);
+INSERT INTO rls_ptbl SELECT x/10 FROM generate_series(1, 100) x;
+ANALYZE rls_ptbl, rls_part;
+
+ALTER TABLE rls_ptbl ENABLE ROW LEVEL SECURITY;
+ALTER TABLE rls_part ENABLE ROW LEVEL SECURITY;
+GRANT SELECT ON rls_ptbl TO regress_rls_alice;
+GRANT SELECT ON rls_part TO regress_rls_alice;
+CREATE POLICY p1 ON rls_tbl USING (a < 0);
+CREATE POLICY p2 ON rls_ptbl USING (a < 0);
+CREATE POLICY p3 ON rls_part USING (a < 0);
+
+SET SESSION AUTHORIZATION regress_rls_alice;
+SELECT * FROM rls_tbl WHERE a <<< 1000;
+SELECT * FROM rls_child_tbl WHERE a <<< 1000;
+SELECT * FROM rls_ptbl WHERE a <<< 1000;
+SELECT * FROM rls_part WHERE a <<< 1000;
+SELECT * FROM (SELECT * FROM rls_tbl UNION ALL
+ SELECT * FROM rls_tbl) t WHERE a <<< 1000;
+SELECT * FROM (SELECT * FROM rls_child_tbl UNION ALL
+ SELECT * FROM rls_child_tbl) t WHERE a <<< 1000;
+RESET SESSION AUTHORIZATION;
+
+REVOKE SELECT ON rls_tbl FROM regress_rls_alice;
+CREATE VIEW rls_tbl_view AS SELECT * FROM rls_tbl;
+
+ALTER TABLE rls_child_tbl ENABLE ROW LEVEL SECURITY;
+GRANT SELECT ON rls_child_tbl TO regress_rls_alice;
+CREATE POLICY p4 ON rls_child_tbl USING (a < 0);
+
+SET SESSION AUTHORIZATION regress_rls_alice;
+SELECT * FROM rls_tbl WHERE a <<< 1000;
+SELECT * FROM rls_tbl_view WHERE a <<< 1000;
+SELECT * FROM rls_child_tbl WHERE a <<< 1000;
+SELECT * FROM (SELECT * FROM rls_tbl UNION ALL
+ SELECT * FROM rls_tbl) t WHERE a <<< 1000;
+SELECT * FROM (SELECT * FROM rls_child_tbl UNION ALL
+ SELECT * FROM rls_child_tbl) t WHERE a <<< 1000;
DROP OPERATOR <<< (int, int);
DROP FUNCTION op_leak(int, int);
RESET SESSION AUTHORIZATION;
+DROP TABLE rls_part;
+DROP TABLE rls_ptbl;
+DROP TABLE rls_child_tbl;
+DROP VIEW rls_tbl_view;
DROP TABLE rls_tbl;
-- Bug #16006: whole-row Vars in a policy don't play nice with sub-selects
diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql
index 5ac9587c8fa..2e236e0f623 100644
--- a/src/test/regress/sql/stats_ext.sql
+++ b/src/test/regress/sql/stats_ext.sql
@@ -1648,7 +1648,14 @@ CREATE FUNCTION op_leak(int, int) RETURNS bool
LANGUAGE plpgsql;
CREATE OPERATOR <<< (procedure = op_leak, leftarg = int, rightarg = int,
restrict = scalarltsel);
+CREATE FUNCTION op_leak(record, record) RETURNS bool
+ AS 'BEGIN RAISE NOTICE ''op_leak => %, %'', $1, $2; RETURN $1 < $2; END'
+ LANGUAGE plpgsql;
+CREATE OPERATOR <<< (procedure = op_leak, leftarg = record, rightarg = record,
+ restrict = scalarltsel);
SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied
+SELECT * FROM tststats.priv_test_tbl t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Permission denied
DELETE FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied
-- Grant access via a security barrier view, but hide all data
@@ -1660,18 +1667,48 @@ GRANT SELECT, DELETE ON tststats.priv_test_view TO regress_stats_user1;
-- Should now have access via the view, but see nothing and leak nothing
SET SESSION AUTHORIZATION regress_stats_user1;
SELECT * FROM tststats.priv_test_view WHERE a <<< 0 AND b <<< 0; -- Should not leak
+SELECT * FROM tststats.priv_test_view t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Should not leak
DELETE FROM tststats.priv_test_view WHERE a <<< 0 AND b <<< 0; -- Should not leak
-- Grant table access, but hide all data with RLS
RESET SESSION AUTHORIZATION;
ALTER TABLE tststats.priv_test_tbl ENABLE ROW LEVEL SECURITY;
+CREATE POLICY priv_test_tbl_pol ON tststats.priv_test_tbl USING (2 * a < 0);
GRANT SELECT, DELETE ON tststats.priv_test_tbl TO regress_stats_user1;
-- Should now have direct table access, but see nothing and leak nothing
SET SESSION AUTHORIZATION regress_stats_user1;
SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak
+SELECT * FROM tststats.priv_test_tbl t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Should not leak
DELETE FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak
+-- Create plain inheritance parent table with no access permissions
+RESET SESSION AUTHORIZATION;
+CREATE TABLE tststats.priv_test_parent_tbl (a int, b int);
+ALTER TABLE tststats.priv_test_tbl INHERIT tststats.priv_test_parent_tbl;
+
+-- Should not have access to parent, and should leak nothing
+SET SESSION AUTHORIZATION regress_stats_user1;
+SELECT * FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied
+SELECT * FROM tststats.priv_test_parent_tbl t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Permission denied
+DELETE FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied
+
+-- Grant table access to parent, but hide all data with RLS
+RESET SESSION AUTHORIZATION;
+ALTER TABLE tststats.priv_test_parent_tbl ENABLE ROW LEVEL SECURITY;
+CREATE POLICY priv_test_parent_tbl_pol ON tststats.priv_test_parent_tbl USING (2 * a < 0);
+GRANT SELECT, DELETE ON tststats.priv_test_parent_tbl TO regress_stats_user1;
+
+-- Should now have direct table access to parent, but see nothing and leak nothing
+SET SESSION AUTHORIZATION regress_stats_user1;
+SELECT * FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak
+SELECT * FROM tststats.priv_test_parent_tbl t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Should not leak
+DELETE FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak
+
-- privilege checks for pg_stats_ext and pg_stats_ext_exprs
RESET SESSION AUTHORIZATION;
CREATE TABLE stats_ext_tbl (id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, col TEXT);
@@ -1733,6 +1770,8 @@ ALTER TABLE sts_sch1.tbl ALTER COLUMN a TYPE SMALLINT;
-- Tidy up
DROP OPERATOR <<< (int, int);
DROP FUNCTION op_leak(int, int);
+DROP OPERATOR <<< (record, record);
+DROP FUNCTION op_leak(record, record);
RESET SESSION AUTHORIZATION;
DROP TABLE stats_ext_tbl;
DROP SCHEMA tststats CASCADE;