diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/utils/adt/selfuncs.c | 128 | ||||
-rw-r--r-- | src/test/regress/expected/inherit.out | 74 | ||||
-rw-r--r-- | src/test/regress/sql/inherit.sql | 38 |
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; |