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

Commit1d9056f

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 parentd0ccfa9 commit1d9056f

File tree

3 files changed

+248
-0
lines changed

3 files changed

+248
-0
lines changed

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

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4991,6 +4991,52 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
49914991
rte->securityQuals==NIL&&
49924992
(pg_class_aclcheck(rte->relid,userid,
49934993
ACL_SELECT)==ACLCHECK_OK);
4994+
4995+
/*
4996+
* If the user doesn't have permissions to
4997+
* access an inheritance child relation, check
4998+
* the permissions of the table actually
4999+
* mentioned in the query, since most likely
5000+
* the user does have that permission. Note
5001+
* that whole-table select privilege on the
5002+
* parent doesn't quite guarantee that the
5003+
* user could read all columns of the child.
5004+
* But in practice it's unlikely that any
5005+
* interesting security violation could result
5006+
* from allowing access to the expression
5007+
* index's stats, so we allow it anyway. See
5008+
* similar code in examine_simple_variable()
5009+
* for additional comments.
5010+
*/
5011+
if (!vardata->acl_ok&&
5012+
root->append_rel_array!=NULL)
5013+
{
5014+
AppendRelInfo*appinfo;
5015+
Indexvarno=index->rel->relid;
5016+
5017+
appinfo=root->append_rel_array[varno];
5018+
while (appinfo&&
5019+
planner_rt_fetch(appinfo->parent_relid,
5020+
root)->rtekind==RTE_RELATION)
5021+
{
5022+
varno=appinfo->parent_relid;
5023+
appinfo=root->append_rel_array[varno];
5024+
}
5025+
if (varno!=index->rel->relid)
5026+
{
5027+
/* Repeat access check on this rel */
5028+
rte=planner_rt_fetch(varno,root);
5029+
Assert(rte->rtekind==RTE_RELATION);
5030+
5031+
userid=rte->checkAsUser ?rte->checkAsUser :GetUserId();
5032+
5033+
vardata->acl_ok=
5034+
rte->securityQuals==NIL&&
5035+
(pg_class_aclcheck(rte->relid,
5036+
userid,
5037+
ACL_SELECT)==ACLCHECK_OK);
5038+
}
5039+
}
49945040
}
49955041
else
49965042
{
@@ -5068,6 +5114,88 @@ examine_simple_variable(PlannerInfo *root, Var *var,
50685114
ACL_SELECT)==ACLCHECK_OK)||
50695115
(pg_attribute_aclcheck(rte->relid,var->varattno,userid,
50705116
ACL_SELECT)==ACLCHECK_OK));
5117+
5118+
/*
5119+
* If the user doesn't have permissions to access an inheritance
5120+
* child relation or specifically this attribute, check the
5121+
* permissions of the table/column actually mentioned in the
5122+
* query, since most likely the user does have that permission
5123+
* (else the query will fail at runtime), and if the user can read
5124+
* the column there then he can get the values of the child table
5125+
* too. To do that, we must find out which of the root parent's
5126+
* attributes the child relation's attribute corresponds to.
5127+
*/
5128+
if (!vardata->acl_ok&&var->varattno>0&&
5129+
root->append_rel_array!=NULL)
5130+
{
5131+
AppendRelInfo*appinfo;
5132+
Indexvarno=var->varno;
5133+
intvarattno=var->varattno;
5134+
boolfound= false;
5135+
5136+
appinfo=root->append_rel_array[varno];
5137+
5138+
/*
5139+
* Partitions are mapped to their immediate parent, not the
5140+
* root parent, so must be ready to walk up multiple
5141+
* AppendRelInfos. But stop if we hit a parent that is not
5142+
* RTE_RELATION --- that's a flattened UNION ALL subquery, not
5143+
* an inheritance parent.
5144+
*/
5145+
while (appinfo&&
5146+
planner_rt_fetch(appinfo->parent_relid,
5147+
root)->rtekind==RTE_RELATION)
5148+
{
5149+
intparent_varattno;
5150+
ListCell*l;
5151+
5152+
parent_varattno=1;
5153+
found= false;
5154+
foreach(l,appinfo->translated_vars)
5155+
{
5156+
Var*childvar=lfirst_node(Var,l);
5157+
5158+
/* Ignore dropped attributes of the parent. */
5159+
if (childvar!=NULL&&
5160+
varattno==childvar->varattno)
5161+
{
5162+
found= true;
5163+
break;
5164+
}
5165+
parent_varattno++;
5166+
}
5167+
5168+
if (!found)
5169+
break;
5170+
5171+
varno=appinfo->parent_relid;
5172+
varattno=parent_varattno;
5173+
5174+
/* If the parent is itself a child, continue up. */
5175+
appinfo=root->append_rel_array[varno];
5176+
}
5177+
5178+
/*
5179+
* In rare cases, the Var may be local to the child table, in
5180+
* which case, we've got to live with having no access to this
5181+
* column's stats.
5182+
*/
5183+
if (!found)
5184+
return;
5185+
5186+
/* Repeat the access check on this parent rel & column */
5187+
rte=planner_rt_fetch(varno,root);
5188+
Assert(rte->rtekind==RTE_RELATION);
5189+
5190+
userid=rte->checkAsUser ?rte->checkAsUser :GetUserId();
5191+
5192+
vardata->acl_ok=
5193+
rte->securityQuals==NIL&&
5194+
((pg_class_aclcheck(rte->relid,userid,
5195+
ACL_SELECT)==ACLCHECK_OK)||
5196+
(pg_attribute_aclcheck(rte->relid,varattno,userid,
5197+
ACL_SELECT)==ACLCHECK_OK));
5198+
}
50715199
}
50725200
else
50735201
{

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

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2093,3 +2093,85 @@ select min(a), max(a) from parted_minmax where b = '12345';
20932093
(1 row)
20942094

20952095
drop table parted_minmax;
2096+
-- Check that we allow access to a child table's statistics when the user
2097+
-- has permissions only for the parent table.
2098+
create table permtest_parent (a int, b text, c text) partition by list (a);
2099+
create table permtest_child (b text, c text, a int) partition by list (b);
2100+
create table permtest_grandchild (c text, b text, a int);
2101+
alter table permtest_child attach partition permtest_grandchild for values in ('a');
2102+
alter table permtest_parent attach partition permtest_child for values in (1);
2103+
create index on permtest_parent (left(c, 3));
2104+
insert into permtest_parent
2105+
select 1, 'a', left(md5(i::text), 5) from generate_series(0, 100) i;
2106+
analyze permtest_parent;
2107+
create role regress_no_child_access;
2108+
revoke all on permtest_grandchild from regress_no_child_access;
2109+
grant select on permtest_parent to regress_no_child_access;
2110+
set session authorization regress_no_child_access;
2111+
-- without stats access, these queries would produce hash join plans:
2112+
explain (costs off)
2113+
select * from permtest_parent p1 inner join permtest_parent p2
2114+
on p1.a = p2.a and p1.c ~ 'a1$';
2115+
QUERY PLAN
2116+
------------------------------------------------
2117+
Nested Loop
2118+
Join Filter: (p1.a = p2.a)
2119+
-> Append
2120+
-> Seq Scan on permtest_grandchild p1
2121+
Filter: (c ~ 'a1$'::text)
2122+
-> Append
2123+
-> Seq Scan on permtest_grandchild p2
2124+
(7 rows)
2125+
2126+
explain (costs off)
2127+
select * from permtest_parent p1 inner join permtest_parent p2
2128+
on p1.a = p2.a and left(p1.c, 3) ~ 'a1$';
2129+
QUERY PLAN
2130+
----------------------------------------------------
2131+
Nested Loop
2132+
Join Filter: (p1.a = p2.a)
2133+
-> Append
2134+
-> Seq Scan on permtest_grandchild p1
2135+
Filter: ("left"(c, 3) ~ 'a1$'::text)
2136+
-> Append
2137+
-> Seq Scan on permtest_grandchild p2
2138+
(7 rows)
2139+
2140+
reset session authorization;
2141+
revoke all on permtest_parent from regress_no_child_access;
2142+
grant select(a,c) on permtest_parent to regress_no_child_access;
2143+
set session authorization regress_no_child_access;
2144+
explain (costs off)
2145+
select p2.a, p1.c from permtest_parent p1 inner join permtest_parent p2
2146+
on p1.a = p2.a and p1.c ~ 'a1$';
2147+
QUERY PLAN
2148+
------------------------------------------------
2149+
Nested Loop
2150+
Join Filter: (p1.a = p2.a)
2151+
-> Append
2152+
-> Seq Scan on permtest_grandchild p1
2153+
Filter: (c ~ 'a1$'::text)
2154+
-> Append
2155+
-> Seq Scan on permtest_grandchild p2
2156+
(7 rows)
2157+
2158+
-- we will not have access to the expression index's stats here:
2159+
explain (costs off)
2160+
select p2.a, p1.c from permtest_parent p1 inner join permtest_parent p2
2161+
on p1.a = p2.a and left(p1.c, 3) ~ 'a1$';
2162+
QUERY PLAN
2163+
----------------------------------------------------------
2164+
Hash Join
2165+
Hash Cond: (p2.a = p1.a)
2166+
-> Append
2167+
-> Seq Scan on permtest_grandchild p2
2168+
-> Hash
2169+
-> Append
2170+
-> Seq Scan on permtest_grandchild p1
2171+
Filter: ("left"(c, 3) ~ 'a1$'::text)
2172+
(8 rows)
2173+
2174+
reset session authorization;
2175+
revoke all on permtest_parent from regress_no_child_access;
2176+
drop role regress_no_child_access;
2177+
drop table permtest_parent;

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,3 +742,41 @@ insert into parted_minmax values (1,'12345');
742742
explain (costs off)selectmin(a),max(a)from parted_minmaxwhere b='12345';
743743
selectmin(a),max(a)from parted_minmaxwhere b='12345';
744744
droptable parted_minmax;
745+
746+
-- Check that we allow access to a child table's statistics when the user
747+
-- has permissions only for the parent table.
748+
createtablepermtest_parent (aint, btext, ctext) partition by list (a);
749+
createtablepermtest_child (btext, ctext, aint) partition by list (b);
750+
createtablepermtest_grandchild (ctext, btext, aint);
751+
altertable permtest_child attach partition permtest_grandchild forvaluesin ('a');
752+
altertable permtest_parent attach partition permtest_child forvaluesin (1);
753+
createindexon permtest_parent (left(c,3));
754+
insert into permtest_parent
755+
select1,'a', left(md5(i::text),5)from generate_series(0,100) i;
756+
analyze permtest_parent;
757+
create role regress_no_child_access;
758+
revoke allon permtest_grandchildfrom regress_no_child_access;
759+
grantselecton permtest_parent to regress_no_child_access;
760+
set session authorization regress_no_child_access;
761+
-- without stats access, these queries would produce hash join plans:
762+
explain (costs off)
763+
select*from permtest_parent p1inner join permtest_parent p2
764+
onp1.a=p2.aandp1.c ~'a1$';
765+
explain (costs off)
766+
select*from permtest_parent p1inner join permtest_parent p2
767+
onp1.a=p2.aand left(p1.c,3) ~'a1$';
768+
reset session authorization;
769+
revoke allon permtest_parentfrom regress_no_child_access;
770+
grantselect(a,c)on permtest_parent to regress_no_child_access;
771+
set session authorization regress_no_child_access;
772+
explain (costs off)
773+
selectp2.a,p1.cfrom permtest_parent p1inner join permtest_parent p2
774+
onp1.a=p2.aandp1.c ~'a1$';
775+
-- we will not have access to the expression index's stats here:
776+
explain (costs off)
777+
selectp2.a,p1.cfrom permtest_parent p1inner join permtest_parent p2
778+
onp1.a=p2.aand left(p1.c,3) ~'a1$';
779+
reset session authorization;
780+
revoke allon permtest_parentfrom regress_no_child_access;
781+
drop role regress_no_child_access;
782+
droptable permtest_parent;

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp