Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit21a4edd

Browse files
committed
Allow access to child table statistics if user can read parent table.
The fix forCVE-2017-7484 disallowed use of pg_statistic data forplanning purposes if the user would not be able to select the associatedcolumn and a non-leakproof function is to be applied to the statisticsvalues. That turns out to disable use of pg_statistic data in somecommon cases involving inheritance/partitioning, where the user doeshave permission to select from the parent table that was actually namedin the query, but not from a child table whose stats are needed. Since,in non-corner cases, the user *can* select the child table's data viathe parent, this restriction is not actually useful from a securitystandpoint. Improve the logic so that we also check the permissions ofthe originally-named table, and allow access if select permission existsfor that.When checking access to stats for a simple child column, we can mapthe child column number back to the parent, and perform this testexactly (including not allowing access if the child column isn'texposed by the parent). For expression indexes, the current logicjust insists on whole-table select access, and this patch allowsaccess if the user can select the whole parent table. In principle,if the child table has extra columns, this might allow access tostats on columns the user can't read. In practice, it's unlikelythat the planner is going to do any stats calculations involvingexpressions that are not visible to the query, so we'll ignore thatfine point for now. Perhaps someday we'll improve that logic todetect exactly which columns are used by an expression index ...but today is not that day.Back-patch to v11. The issue was created in 9.2 and up by theCVE-2017-7484 fix, but this patch depends on the append_rel_array[]planner data structure which only exists in v11 and up. Inpractice the issue is most urgent with partitioned tables, sofixing v11 and later should satisfy much of the practical need.Dilip Kumar and Amit Langote, with some kibitzing by meDiscussion:https://postgr.es/m/3876.1531261875@sss.pgh.pa.us
1 parent1cc3a90 commit21a4edd

File tree

3 files changed

+240
-0
lines changed

3 files changed

+240
-0
lines changed

‎src/backend/utils/adt/selfuncs.c

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4631,6 +4631,52 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
46314631
rte->securityQuals==NIL&&
46324632
(pg_class_aclcheck(rte->relid,userid,
46334633
ACL_SELECT)==ACLCHECK_OK);
4634+
4635+
/*
4636+
* If the user doesn't have permissions to
4637+
* access an inheritance child relation, check
4638+
* the permissions of the table actually
4639+
* mentioned in the query, since most likely
4640+
* the user does have that permission. Note
4641+
* that whole-table select privilege on the
4642+
* parent doesn't quite guarantee that the
4643+
* user could read all columns of the child.
4644+
* But in practice it's unlikely that any
4645+
* interesting security violation could result
4646+
* from allowing access to the expression
4647+
* index's stats, so we allow it anyway. See
4648+
* similar code in examine_simple_variable()
4649+
* for additional comments.
4650+
*/
4651+
if (!vardata->acl_ok&&
4652+
root->append_rel_array!=NULL)
4653+
{
4654+
AppendRelInfo*appinfo;
4655+
Indexvarno=index->rel->relid;
4656+
4657+
appinfo=root->append_rel_array[varno];
4658+
while (appinfo&&
4659+
planner_rt_fetch(appinfo->parent_relid,
4660+
root)->rtekind==RTE_RELATION)
4661+
{
4662+
varno=appinfo->parent_relid;
4663+
appinfo=root->append_rel_array[varno];
4664+
}
4665+
if (varno!=index->rel->relid)
4666+
{
4667+
/* Repeat access check on this rel */
4668+
rte=planner_rt_fetch(varno,root);
4669+
Assert(rte->rtekind==RTE_RELATION);
4670+
4671+
userid=rte->checkAsUser ?rte->checkAsUser :GetUserId();
4672+
4673+
vardata->acl_ok=
4674+
rte->securityQuals==NIL&&
4675+
(pg_class_aclcheck(rte->relid,
4676+
userid,
4677+
ACL_SELECT)==ACLCHECK_OK);
4678+
}
4679+
}
46344680
}
46354681
else
46364682
{
@@ -4708,6 +4754,88 @@ examine_simple_variable(PlannerInfo *root, Var *var,
47084754
ACL_SELECT)==ACLCHECK_OK)||
47094755
(pg_attribute_aclcheck(rte->relid,var->varattno,userid,
47104756
ACL_SELECT)==ACLCHECK_OK));
4757+
4758+
/*
4759+
* If the user doesn't have permissions to access an inheritance
4760+
* child relation or specifically this attribute, check the
4761+
* permissions of the table/column actually mentioned in the
4762+
* query, since most likely the user does have that permission
4763+
* (else the query will fail at runtime), and if the user can read
4764+
* the column there then he can get the values of the child table
4765+
* too. To do that, we must find out which of the root parent's
4766+
* attributes the child relation's attribute corresponds to.
4767+
*/
4768+
if (!vardata->acl_ok&&var->varattno>0&&
4769+
root->append_rel_array!=NULL)
4770+
{
4771+
AppendRelInfo*appinfo;
4772+
Indexvarno=var->varno;
4773+
intvarattno=var->varattno;
4774+
boolfound= false;
4775+
4776+
appinfo=root->append_rel_array[varno];
4777+
4778+
/*
4779+
* Partitions are mapped to their immediate parent, not the
4780+
* root parent, so must be ready to walk up multiple
4781+
* AppendRelInfos. But stop if we hit a parent that is not
4782+
* RTE_RELATION --- that's a flattened UNION ALL subquery, not
4783+
* an inheritance parent.
4784+
*/
4785+
while (appinfo&&
4786+
planner_rt_fetch(appinfo->parent_relid,
4787+
root)->rtekind==RTE_RELATION)
4788+
{
4789+
intparent_varattno;
4790+
ListCell*l;
4791+
4792+
parent_varattno=1;
4793+
found= false;
4794+
foreach(l,appinfo->translated_vars)
4795+
{
4796+
Var*childvar=lfirst_node(Var,l);
4797+
4798+
/* Ignore dropped attributes of the parent. */
4799+
if (childvar!=NULL&&
4800+
varattno==childvar->varattno)
4801+
{
4802+
found= true;
4803+
break;
4804+
}
4805+
parent_varattno++;
4806+
}
4807+
4808+
if (!found)
4809+
break;
4810+
4811+
varno=appinfo->parent_relid;
4812+
varattno=parent_varattno;
4813+
4814+
/* If the parent is itself a child, continue up. */
4815+
appinfo=root->append_rel_array[varno];
4816+
}
4817+
4818+
/*
4819+
* In rare cases, the Var may be local to the child table, in
4820+
* which case, we've got to live with having no access to this
4821+
* column's stats.
4822+
*/
4823+
if (!found)
4824+
return;
4825+
4826+
/* Repeat the access check on this parent rel & column */
4827+
rte=planner_rt_fetch(varno,root);
4828+
Assert(rte->rtekind==RTE_RELATION);
4829+
4830+
userid=rte->checkAsUser ?rte->checkAsUser :GetUserId();
4831+
4832+
vardata->acl_ok=
4833+
rte->securityQuals==NIL&&
4834+
((pg_class_aclcheck(rte->relid,userid,
4835+
ACL_SELECT)==ACLCHECK_OK)||
4836+
(pg_attribute_aclcheck(rte->relid,varattno,userid,
4837+
ACL_SELECT)==ACLCHECK_OK));
4838+
}
47114839
}
47124840
else
47134841
{

‎src/test/regress/expected/inherit.out

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2335,3 +2335,77 @@ explain (costs off) select * from range_parted order by a desc,b desc,c desc;
23352335
(3 rows)
23362336

23372337
drop table range_parted;
2338+
-- Check that we allow access to a child table's statistics when the user
2339+
-- has permissions only for the parent table.
2340+
create table permtest_parent (a int, b text, c text) partition by list (a);
2341+
create table permtest_child (b text, c text, a int) partition by list (b);
2342+
create table permtest_grandchild (c text, b text, a int);
2343+
alter table permtest_child attach partition permtest_grandchild for values in ('a');
2344+
alter table permtest_parent attach partition permtest_child for values in (1);
2345+
create index on permtest_parent (left(c, 3));
2346+
insert into permtest_parent
2347+
select 1, 'a', left(md5(i::text), 5) from generate_series(0, 100) i;
2348+
analyze permtest_parent;
2349+
create role regress_no_child_access;
2350+
revoke all on permtest_grandchild from regress_no_child_access;
2351+
grant select on permtest_parent to regress_no_child_access;
2352+
set session authorization regress_no_child_access;
2353+
-- without stats access, these queries would produce hash join plans:
2354+
explain (costs off)
2355+
select * from permtest_parent p1 inner join permtest_parent p2
2356+
on p1.a = p2.a and p1.c ~ 'a1$';
2357+
QUERY PLAN
2358+
------------------------------------------
2359+
Nested Loop
2360+
Join Filter: (p1.a = p2.a)
2361+
-> Seq Scan on permtest_grandchild p1
2362+
Filter: (c ~ 'a1$'::text)
2363+
-> Seq Scan on permtest_grandchild p2
2364+
(5 rows)
2365+
2366+
explain (costs off)
2367+
select * from permtest_parent p1 inner join permtest_parent p2
2368+
on p1.a = p2.a and left(p1.c, 3) ~ 'a1$';
2369+
QUERY PLAN
2370+
----------------------------------------------
2371+
Nested Loop
2372+
Join Filter: (p1.a = p2.a)
2373+
-> Seq Scan on permtest_grandchild p1
2374+
Filter: ("left"(c, 3) ~ 'a1$'::text)
2375+
-> Seq Scan on permtest_grandchild p2
2376+
(5 rows)
2377+
2378+
reset session authorization;
2379+
revoke all on permtest_parent from regress_no_child_access;
2380+
grant select(a,c) on permtest_parent to regress_no_child_access;
2381+
set session authorization regress_no_child_access;
2382+
explain (costs off)
2383+
select p2.a, p1.c from permtest_parent p1 inner join permtest_parent p2
2384+
on p1.a = p2.a and p1.c ~ 'a1$';
2385+
QUERY PLAN
2386+
------------------------------------------
2387+
Nested Loop
2388+
Join Filter: (p1.a = p2.a)
2389+
-> Seq Scan on permtest_grandchild p1
2390+
Filter: (c ~ 'a1$'::text)
2391+
-> Seq Scan on permtest_grandchild p2
2392+
(5 rows)
2393+
2394+
-- we will not have access to the expression index's stats here:
2395+
explain (costs off)
2396+
select p2.a, p1.c from permtest_parent p1 inner join permtest_parent p2
2397+
on p1.a = p2.a and left(p1.c, 3) ~ 'a1$';
2398+
QUERY PLAN
2399+
----------------------------------------------------
2400+
Hash Join
2401+
Hash Cond: (p2.a = p1.a)
2402+
-> Seq Scan on permtest_grandchild p2
2403+
-> Hash
2404+
-> Seq Scan on permtest_grandchild p1
2405+
Filter: ("left"(c, 3) ~ 'a1$'::text)
2406+
(6 rows)
2407+
2408+
reset session authorization;
2409+
revoke all on permtest_parent from regress_no_child_access;
2410+
drop role regress_no_child_access;
2411+
drop table permtest_parent;

‎src/test/regress/sql/inherit.sql

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -845,3 +845,41 @@ explain (costs off) select * from range_parted order by a,b,c;
845845
explain (costs off)select*from range_partedorder by adesc,bdesc,cdesc;
846846

847847
droptable range_parted;
848+
849+
-- Check that we allow access to a child table's statistics when the user
850+
-- has permissions only for the parent table.
851+
createtablepermtest_parent (aint, btext, ctext) partition by list (a);
852+
createtablepermtest_child (btext, ctext, aint) partition by list (b);
853+
createtablepermtest_grandchild (ctext, btext, aint);
854+
altertable permtest_child attach partition permtest_grandchild forvaluesin ('a');
855+
altertable permtest_parent attach partition permtest_child forvaluesin (1);
856+
createindexon permtest_parent (left(c,3));
857+
insert into permtest_parent
858+
select1,'a', left(md5(i::text),5)from generate_series(0,100) i;
859+
analyze permtest_parent;
860+
create role regress_no_child_access;
861+
revoke allon permtest_grandchildfrom regress_no_child_access;
862+
grantselecton permtest_parent to regress_no_child_access;
863+
set session authorization regress_no_child_access;
864+
-- without stats access, these queries would produce hash join plans:
865+
explain (costs off)
866+
select*from permtest_parent p1inner join permtest_parent p2
867+
onp1.a=p2.aandp1.c ~'a1$';
868+
explain (costs off)
869+
select*from permtest_parent p1inner join permtest_parent p2
870+
onp1.a=p2.aand left(p1.c,3) ~'a1$';
871+
reset session authorization;
872+
revoke allon permtest_parentfrom regress_no_child_access;
873+
grantselect(a,c)on permtest_parent to regress_no_child_access;
874+
set session authorization regress_no_child_access;
875+
explain (costs off)
876+
selectp2.a,p1.cfrom permtest_parent p1inner join permtest_parent p2
877+
onp1.a=p2.aandp1.c ~'a1$';
878+
-- we will not have access to the expression index's stats here:
879+
explain (costs off)
880+
selectp2.a,p1.cfrom permtest_parent p1inner join permtest_parent p2
881+
onp1.a=p2.aand left(p1.c,3) ~'a1$';
882+
reset session authorization;
883+
revoke allon permtest_parentfrom regress_no_child_access;
884+
drop role regress_no_child_access;
885+
droptable permtest_parent;

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp