Commit a0905056 authored by Dean Rasheed's avatar Dean Rasheed

Use checkAsUser for selectivity estimator checks, if it's set.

In examine_variable() and examine_simple_variable(), when checking the
user's table and column privileges to determine whether to grant
access to the pg_statistic data, use checkAsUser for the privilege
checks, if it's set. This will be the case if we're accessing the
table via a view, to indicate that we should perform privilege checks
as the view owner rather than the current user.

This change makes this planner check consistent with the check in the
executor, so the planner will be able to make use of statistics if the
table is accessible via the view. This fixes a performance regression
introduced by commit e2d4ef8d, which affects queries against
non-security barrier views in the case where the user doesn't have
privileges on the underlying table, but the view owner does.

Note that it continues to provide the same safeguards controlling
access to pg_statistic for direct table access (in which case
checkAsUser won't be set) and for security barrier views, because of
the nearby checks on rte->security_barrier and rte->securityQuals.

Back-patch to all supported branches because e2d4ef8d was.

Dean Rasheed, reviewed by Jonathan Katz and Stephen Frost.
parent 1aebfbea
......@@ -4589,10 +4589,17 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
{
/* Get index's table for permission check */
RangeTblEntry *rte;
Oid userid;
rte = planner_rt_fetch(index->rel->relid, root);
Assert(rte->rtekind == RTE_RELATION);
/*
* Use checkAsUser if it's set, in case we're
* accessing the table via a view.
*/
userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
/*
* For simplicity, we insist on the whole
* table being selectable, rather than trying
......@@ -4604,7 +4611,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
*/
vardata->acl_ok =
rte->securityQuals == NIL &&
(pg_class_aclcheck(rte->relid, GetUserId(),
(pg_class_aclcheck(rte->relid, userid,
ACL_SELECT) == ACLCHECK_OK);
}
else
......@@ -4667,16 +4674,21 @@ examine_simple_variable(PlannerInfo *root, Var *var,
if (HeapTupleIsValid(vardata->statsTuple))
{
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.
* from security barrier views or RLS policies. Use checkAsUser
* if it's set, in case we're accessing the table via a view.
*/
userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
vardata->acl_ok =
rte->securityQuals == NIL &&
((pg_class_aclcheck(rte->relid, GetUserId(),
((pg_class_aclcheck(rte->relid, userid,
ACL_SELECT) == ACLCHECK_OK) ||
(pg_attribute_aclcheck(rte->relid, var->varattno, GetUserId(),
(pg_attribute_aclcheck(rte->relid, var->varattno, userid,
ACL_SELECT) == ACLCHECK_OK));
}
else
......
......@@ -185,7 +185,7 @@ SELECT * FROM atest1; -- ok
(2 rows)
-- test leaky-function protections in selfuncs
-- regress_priv_user1 will own a table and provide a view for it.
-- regress_priv_user1 will own a table and provide views for it.
SET SESSION AUTHORIZATION regress_priv_user1;
CREATE TABLE atest12 as
SELECT x AS a, 10001 - x AS b FROM generate_series(1,10000) x;
......@@ -197,10 +197,13 @@ CREATE FUNCTION leak(integer,integer) RETURNS boolean
LANGUAGE plpgsql immutable;
CREATE OPERATOR <<< (procedure = leak, leftarg = integer, rightarg = integer,
restrict = scalarltsel);
-- view with leaky operator
-- views with leaky operator
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
......@@ -225,6 +228,20 @@ EXPLAIN (COSTS OFF) SELECT * FROM atest12 x, atest12 y
Index Cond: (a = y.b)
(5 rows)
-- This should also be a nestloop, but the security barrier forces the inner
-- scan to be materialized
EXPLAIN (COSTS OFF) SELECT * FROM atest12sbv x, atest12sbv y WHERE x.a = y.b;
QUERY PLAN
-------------------------------------------
Nested Loop
Join Filter: (atest12.a = atest12_1.b)
-> Seq Scan on atest12
Filter: (b <<< 5)
-> Materialize
-> Seq Scan on atest12 atest12_1
Filter: (b <<< 5)
(7 rows)
-- Check if regress_priv_user2 can break security.
SET SESSION AUTHORIZATION regress_priv_user2;
CREATE FUNCTION leak2(integer,integer) RETURNS boolean
......@@ -235,24 +252,64 @@ CREATE OPERATOR >>> (procedure = leak2, leftarg = integer, rightarg = integer,
-- This should not show any "leak" notices before failing.
EXPLAIN (COSTS OFF) SELECT * FROM atest12 WHERE a >>> 0;
ERROR: permission denied for table atest12
-- This plan should use hashjoin, as it will expect many rows to be selected.
-- 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;
QUERY PLAN
-------------------------------------------------
Nested Loop
-> Seq Scan on atest12 atest12_1
Filter: (b <<< 5)
-> Index Scan using atest12_a_idx on atest12
Index Cond: (a = atest12_1.b)
Filter: (b <<< 5)
(6 rows)
EXPLAIN (COSTS OFF) SELECT * FROM atest12sbv x, atest12sbv y WHERE x.a = y.b;
QUERY PLAN
-------------------------------------------
Hash Join
Hash Cond: (atest12.a = atest12_1.b)
Nested Loop
Join Filter: (atest12.a = atest12_1.b)
-> Seq Scan on atest12
Filter: (b <<< 5)
-> Hash
-> Materialize
-> Seq Scan on atest12 atest12_1
Filter: (b <<< 5)
(7 rows)
-- A non-security barrier view does not guard against information leakage.
EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y
WHERE x.a = y.b and abs(y.a) <<< 5;
QUERY PLAN
-------------------------------------------------
Nested Loop
-> Seq Scan on atest12 atest12_1
Filter: ((b <<< 5) AND (abs(a) <<< 5))
-> Index Scan using atest12_a_idx on atest12
Index Cond: (a = atest12_1.b)
Filter: (b <<< 5)
(6 rows)
-- But a security barrier view isolates the leaky operator.
EXPLAIN (COSTS OFF) SELECT * FROM atest12sbv x, atest12sbv y
WHERE x.a = y.b and abs(y.a) <<< 5;
QUERY PLAN
-------------------------------------
Nested Loop
Join Filter: (atest12_1.a = y.b)
-> Subquery Scan on y
Filter: (abs(y.a) <<< 5)
-> Seq Scan on atest12
Filter: (b <<< 5)
-> Seq Scan on atest12 atest12_1
Filter: (b <<< 5)
(8 rows)
-- Now regress_priv_user1 grants sufficient access to regress_priv_user2.
SET SESSION AUTHORIZATION regress_priv_user1;
GRANT SELECT (a, b) ON atest12 TO PUBLIC;
SET SESSION AUTHORIZATION regress_priv_user2;
-- Now regress_priv_user2 will also get a good row estimate.
-- regress_priv_user2 should continue to get a good row estimate.
EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y WHERE x.a = y.b;
QUERY PLAN
-------------------------------------------------
......
......@@ -129,7 +129,7 @@ SELECT * FROM atest1; -- ok
-- test leaky-function protections in selfuncs
-- regress_priv_user1 will own a table and provide a view for it.
-- regress_priv_user1 will own a table and provide views for it.
SET SESSION AUTHORIZATION regress_priv_user1;
CREATE TABLE atest12 as
......@@ -144,10 +144,13 @@ CREATE FUNCTION leak(integer,integer) RETURNS boolean
CREATE OPERATOR <<< (procedure = leak, leftarg = integer, rightarg = integer,
restrict = scalarltsel);
-- view with leaky operator
-- views with leaky operator
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;
......@@ -156,6 +159,10 @@ EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y WHERE x.a = y.b;
EXPLAIN (COSTS OFF) SELECT * FROM atest12 x, atest12 y
WHERE x.a = y.b and abs(y.a) <<< 5;
-- This should also be a nestloop, but the security barrier forces the inner
-- scan to be materialized
EXPLAIN (COSTS OFF) SELECT * FROM atest12sbv x, atest12sbv y WHERE x.a = y.b;
-- Check if regress_priv_user2 can break security.
SET SESSION AUTHORIZATION regress_priv_user2;
......@@ -168,15 +175,25 @@ CREATE OPERATOR >>> (procedure = leak2, leftarg = integer, rightarg = integer,
-- This should not show any "leak" notices before failing.
EXPLAIN (COSTS OFF) SELECT * FROM atest12 WHERE a >>> 0;
-- This plan should use hashjoin, as it will expect many rows to be selected.
-- 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;
EXPLAIN (COSTS OFF) SELECT * FROM atest12sbv x, atest12sbv y WHERE x.a = y.b;
-- A non-security barrier view does not guard against information leakage.
EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y
WHERE x.a = y.b and abs(y.a) <<< 5;
-- But a security barrier view isolates the leaky operator.
EXPLAIN (COSTS OFF) SELECT * FROM atest12sbv x, atest12sbv y
WHERE x.a = y.b and abs(y.a) <<< 5;
-- Now regress_priv_user1 grants sufficient access to regress_priv_user2.
SET SESSION AUTHORIZATION regress_priv_user1;
GRANT SELECT (a, b) ON atest12 TO PUBLIC;
SET SESSION AUTHORIZATION regress_priv_user2;
-- Now regress_priv_user2 will also get a good row estimate.
-- regress_priv_user2 should continue to get a good row estimate.
EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y WHERE x.a = y.b;
-- But not for this, due to lack of table-wide permissions needed
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment