aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/backend/utils/adt/selfuncs.c128
-rw-r--r--src/test/regress/expected/inherit.out74
-rw-r--r--src/test/regress/sql/inherit.sql38
3 files changed, 240 insertions, 0 deletions
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 26a2e3bb8b8..35dbd728ecf 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4613,6 +4613,52 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
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);
+
+ userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+
+ vardata->acl_ok =
+ rte->securityQuals == NIL &&
+ (pg_class_aclcheck(rte->relid,
+ userid,
+ ACL_SELECT) == ACLCHECK_OK);
+ }
+ }
}
else
{
@@ -4690,6 +4736,88 @@ examine_simple_variable(PlannerInfo *root, Var *var,
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;
+ ListCell *l;
+
+ parent_varattno = 1;
+ found = false;
+ foreach(l, appinfo->translated_vars)
+ {
+ Var *childvar = lfirst_node(Var, l);
+
+ /* Ignore dropped attributes of the parent. */
+ if (childvar != NULL &&
+ varattno == childvar->varattno)
+ {
+ found = true;
+ break;
+ }
+ parent_varattno++;
+ }
+
+ if (!found)
+ break;
+
+ varno = appinfo->parent_relid;
+ varattno = parent_varattno;
+
+ /* 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);
+
+ userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+
+ 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));
+ }
}
else
{
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 44d51ed7110..e4dbbad75c1 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -2335,3 +2335,77 @@ explain (costs off) select * from range_parted order by a desc,b desc,c desc;
(3 rows)
drop table range_parted;
+-- Check that we allow access to a child table's statistics when the user
+-- has permissions only for the parent table.
+create table permtest_parent (a int, b text, c text) partition by list (a);
+create table permtest_child (b text, c text, a int) partition by list (b);
+create table permtest_grandchild (c text, b text, a int);
+alter table permtest_child attach partition permtest_grandchild for values in ('a');
+alter table permtest_parent attach partition permtest_child for values in (1);
+create index on permtest_parent (left(c, 3));
+insert into permtest_parent
+ select 1, 'a', left(md5(i::text), 5) from generate_series(0, 100) i;
+analyze permtest_parent;
+create role regress_no_child_access;
+revoke all on permtest_grandchild from regress_no_child_access;
+grant select on permtest_parent to regress_no_child_access;
+set session authorization regress_no_child_access;
+-- without stats access, these queries would produce hash join plans:
+explain (costs off)
+ select * from permtest_parent p1 inner join permtest_parent p2
+ on p1.a = p2.a and p1.c ~ 'a1$';
+ QUERY PLAN
+------------------------------------------
+ Nested Loop
+ Join Filter: (p1.a = p2.a)
+ -> Seq Scan on permtest_grandchild p1
+ Filter: (c ~ 'a1$'::text)
+ -> Seq Scan on permtest_grandchild p2
+(5 rows)
+
+explain (costs off)
+ select * from permtest_parent p1 inner join permtest_parent p2
+ on p1.a = p2.a and left(p1.c, 3) ~ 'a1$';
+ QUERY PLAN
+----------------------------------------------
+ Nested Loop
+ Join Filter: (p1.a = p2.a)
+ -> Seq Scan on permtest_grandchild p1
+ Filter: ("left"(c, 3) ~ 'a1$'::text)
+ -> Seq Scan on permtest_grandchild p2
+(5 rows)
+
+reset session authorization;
+revoke all on permtest_parent from regress_no_child_access;
+grant select(a,c) on permtest_parent to regress_no_child_access;
+set session authorization regress_no_child_access;
+explain (costs off)
+ select p2.a, p1.c from permtest_parent p1 inner join permtest_parent p2
+ on p1.a = p2.a and p1.c ~ 'a1$';
+ QUERY PLAN
+------------------------------------------
+ Nested Loop
+ Join Filter: (p1.a = p2.a)
+ -> Seq Scan on permtest_grandchild p1
+ Filter: (c ~ 'a1$'::text)
+ -> Seq Scan on permtest_grandchild p2
+(5 rows)
+
+-- we will not have access to the expression index's stats here:
+explain (costs off)
+ select p2.a, p1.c from permtest_parent p1 inner join permtest_parent p2
+ on p1.a = p2.a and left(p1.c, 3) ~ 'a1$';
+ QUERY PLAN
+----------------------------------------------------
+ Hash Join
+ Hash Cond: (p2.a = p1.a)
+ -> Seq Scan on permtest_grandchild p2
+ -> Hash
+ -> Seq Scan on permtest_grandchild p1
+ Filter: ("left"(c, 3) ~ 'a1$'::text)
+(6 rows)
+
+reset session authorization;
+revoke all on permtest_parent from regress_no_child_access;
+drop role regress_no_child_access;
+drop table permtest_parent;
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index 3af1bf30a79..317f951fa55 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -845,3 +845,41 @@ explain (costs off) select * from range_parted order by a,b,c;
explain (costs off) select * from range_parted order by a desc,b desc,c desc;
drop table range_parted;
+
+-- Check that we allow access to a child table's statistics when the user
+-- has permissions only for the parent table.
+create table permtest_parent (a int, b text, c text) partition by list (a);
+create table permtest_child (b text, c text, a int) partition by list (b);
+create table permtest_grandchild (c text, b text, a int);
+alter table permtest_child attach partition permtest_grandchild for values in ('a');
+alter table permtest_parent attach partition permtest_child for values in (1);
+create index on permtest_parent (left(c, 3));
+insert into permtest_parent
+ select 1, 'a', left(md5(i::text), 5) from generate_series(0, 100) i;
+analyze permtest_parent;
+create role regress_no_child_access;
+revoke all on permtest_grandchild from regress_no_child_access;
+grant select on permtest_parent to regress_no_child_access;
+set session authorization regress_no_child_access;
+-- without stats access, these queries would produce hash join plans:
+explain (costs off)
+ select * from permtest_parent p1 inner join permtest_parent p2
+ on p1.a = p2.a and p1.c ~ 'a1$';
+explain (costs off)
+ select * from permtest_parent p1 inner join permtest_parent p2
+ on p1.a = p2.a and left(p1.c, 3) ~ 'a1$';
+reset session authorization;
+revoke all on permtest_parent from regress_no_child_access;
+grant select(a,c) on permtest_parent to regress_no_child_access;
+set session authorization regress_no_child_access;
+explain (costs off)
+ select p2.a, p1.c from permtest_parent p1 inner join permtest_parent p2
+ on p1.a = p2.a and p1.c ~ 'a1$';
+-- we will not have access to the expression index's stats here:
+explain (costs off)
+ select p2.a, p1.c from permtest_parent p1 inner join permtest_parent p2
+ on p1.a = p2.a and left(p1.c, 3) ~ 'a1$';
+reset session authorization;
+revoke all on permtest_parent from regress_no_child_access;
+drop role regress_no_child_access;
+drop table permtest_parent;