diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql new file mode 100644 index e82a53a..c0bd6fa --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -150,7 +150,7 @@ CREATE VIEW pg_indexes AS LEFT JOIN pg_tablespace T ON (T.oid = I.reltablespace) WHERE C.relkind IN ('r', 'm') AND I.relkind = 'i'; -CREATE VIEW pg_stats AS +CREATE VIEW pg_stats WITH (security_barrier) AS SELECT nspname AS schemaname, relname AS tablename, @@ -211,7 +211,9 @@ CREATE VIEW pg_stats AS FROM pg_statistic s JOIN pg_class c ON (c.oid = s.starelid) JOIN pg_attribute a ON (c.oid = attrelid AND attnum = s.staattnum) LEFT JOIN pg_namespace n ON (n.oid = c.relnamespace) - WHERE NOT attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'); + WHERE NOT attisdropped + AND has_column_privilege(c.oid, a.attnum, 'select') + AND (c.relrowsecurity = false OR NOT row_security_active(c.oid)); REVOKE ALL on pg_statistic FROM public; diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c new file mode 100644 index aaf0061..2386cf0 --- a/src/backend/rewrite/rowsecurity.c +++ b/src/backend/rewrite/rowsecurity.c @@ -107,7 +107,6 @@ get_row_security_policies(Query *root, C Relation rel; Oid user_id; - int sec_context; int rls_status; bool defaultDeny = false; @@ -117,22 +116,13 @@ get_row_security_policies(Query *root, C *hasRowSecurity = false; *hasSubLinks = false; - /* This is just to get the security context */ - GetUserIdAndSecContext(&user_id, &sec_context); + /* If this is not a normal relation, just return immediately */ + if (rte->relkind != RELKIND_RELATION) + return; /* Switch to checkAsUser if it's set */ user_id = rte->checkAsUser ? rte->checkAsUser : GetUserId(); - /* - * If this is not a normal relation, or we have been told to explicitly - * skip RLS (perhaps because this is an FK check) then just return - * immediately. - */ - if (rte->relid < FirstNormalObjectId - || rte->relkind != RELKIND_RELATION - || (sec_context & SECURITY_ROW_LEVEL_DISABLED)) - return; - /* Determine the state of RLS for this, pass checkAsUser explicitly */ rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false); diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c new file mode 100644 index e6808e7..525794f --- a/src/backend/utils/cache/plancache.c +++ b/src/backend/utils/cache/plancache.c @@ -153,8 +153,6 @@ CreateCachedPlan(Node *raw_parse_tree, CachedPlanSource *plansource; MemoryContext source_context; MemoryContext oldcxt; - Oid user_id; - int security_context; Assert(query_string != NULL); /* required as of 8.4 */ @@ -177,8 +175,6 @@ CreateCachedPlan(Node *raw_parse_tree, */ oldcxt = MemoryContextSwitchTo(source_context); - GetUserIdAndSecContext(&user_id, &security_context); - plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource)); plansource->magic = CACHEDPLANSOURCE_MAGIC; plansource->raw_parse_tree = copyObject(raw_parse_tree); @@ -208,8 +204,7 @@ CreateCachedPlan(Node *raw_parse_tree, plansource->total_custom_cost = 0; plansource->num_custom_plans = 0; plansource->hasRowSecurity = false; - plansource->rowSecurityDisabled - = (security_context & SECURITY_ROW_LEVEL_DISABLED) != 0; + plansource->rowSecurityDisabled = InRowLevelSecurityDisabled(); plansource->row_security_env = row_security; plansource->planUserId = InvalidOid; diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c new file mode 100644 index acc4752..ac3e764 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -341,7 +341,7 @@ GetAuthenticatedUserId(void) * GetUserIdAndSecContext/SetUserIdAndSecContext - get/set the current user ID * and the SecurityRestrictionContext flags. * - * Currently there are two valid bits in SecurityRestrictionContext: + * Currently there are three valid bits in SecurityRestrictionContext: * * SECURITY_LOCAL_USERID_CHANGE indicates that we are inside an operation * that is temporarily changing CurrentUserId via these functions. This is @@ -359,6 +359,9 @@ GetAuthenticatedUserId(void) * where the called functions are really supposed to be side-effect-free * anyway, such as VACUUM/ANALYZE/REINDEX. * + * SECURITY_ROW_LEVEL_DISABLED indicates that we are inside an operation that + * needs to bypass row level security checks, for example FK checks. + * * Unlike GetUserId, GetUserIdAndSecContext does *not* Assert that the current * value of CurrentUserId is valid; nor does SetUserIdAndSecContext require * the new value to be valid. In fact, these routines had better not @@ -401,6 +404,15 @@ InSecurityRestrictedOperation(void) return (SecurityRestrictionContext & SECURITY_RESTRICTED_OPERATION) != 0; } +/* + * InRowLevelSecurityDisabled - are we inside a RLS-disabled operation? + */ +bool +InRowLevelSecurityDisabled(void) +{ + return (SecurityRestrictionContext & SECURITY_ROW_LEVEL_DISABLED) != 0; +} + /* * These are obsolete versions of Get/SetUserIdAndSecContext that are diff --git a/src/backend/utils/misc/rls.c b/src/backend/utils/misc/rls.c new file mode 100644 index 44cb374..bcdad7a --- a/src/backend/utils/misc/rls.c +++ b/src/backend/utils/misc/rls.c @@ -17,8 +17,10 @@ #include "access/htup.h" #include "access/htup_details.h" #include "catalog/pg_class.h" +#include "catalog/namespace.h" #include "miscadmin.h" #include "utils/acl.h" +#include "utils/builtins.h" #include "utils/elog.h" #include "utils/rls.h" #include "utils/syscache.h" @@ -53,6 +55,18 @@ check_enable_rls(Oid relid, Oid checkAsU bool relrowsecurity; Oid user_id = checkAsUser ? checkAsUser : GetUserId(); + /* Nothing to do for built-in relations */ + if (relid < FirstNormalObjectId) + return RLS_NONE; + + /* + * Check if we have been told to explicitly skip RLS (perhaps because this + * is a foreign key check) + */ + if (InRowLevelSecurityDisabled()) + return RLS_NONE; + + /* Check if RLS is enabled on the relation */ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); if (!HeapTupleIsValid(tuple)) return RLS_NONE; @@ -111,3 +125,37 @@ check_enable_rls(Oid relid, Oid checkAsU /* RLS should be fully enabled for this relation. */ return RLS_ENABLED; } + +/* + * row_security_active + * + * check_enable_rls wrapped as a SQL callable function except + * RLS_NONE_ENV and RLS_NONE are the same for this purpose. + */ +Datum +row_security_active(PG_FUNCTION_ARGS) +{ + /* By OID */ + Oid tableoid = PG_GETARG_OID(0); + int rls_status; + + rls_status = check_enable_rls(tableoid, InvalidOid, true); + PG_RETURN_BOOL(rls_status == RLS_ENABLED); +} + +Datum +row_security_active_name(PG_FUNCTION_ARGS) +{ + /* By qualified name */ + text *tablename = PG_GETARG_TEXT_P(0); + RangeVar *tablerel; + Oid tableoid; + int rls_status; + + /* Look up table name. Can't lock it - we might not have privileges. */ + tablerel = makeRangeVarFromNameList(textToQualifiedNameList(tablename)); + tableoid = RangeVarGetRelid(tablerel, NoLock, false); + + rls_status = check_enable_rls(tableoid, InvalidOid, true); + PG_RETURN_BOOL(rls_status == RLS_ENABLED); +} diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h new file mode 100644 index 09bf143..2563bb9 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -5343,6 +5343,12 @@ DESCR("get progress for all replication #define PROVOLATILE_STABLE 's' /* does not change within a scan */ #define PROVOLATILE_VOLATILE 'v' /* can change even within a scan */ +/* rls */ +DATA(insert OID = 3298 ( row_security_active PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 16 "26" _null_ _null_ _null_ _null_ _null_ row_security_active _null_ _null_ _null_ )); +DESCR("row security for current context active on table by table oid"); +DATA(insert OID = 3299 ( row_security_active PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 16 "25" _null_ _null_ _null_ _null_ _null_ row_security_active_name _null_ _null_ _null_ )); +DESCR("row security for current context active on table by table name"); + /* * Symbolic values for proargmodes column. Note that these must agree with * the FunctionParameterMode enum in parsenodes.h; we declare them here to diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h new file mode 100644 index b539167..e0cc69f --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -305,6 +305,7 @@ extern void GetUserIdAndSecContext(Oid * extern void SetUserIdAndSecContext(Oid userid, int sec_context); extern bool InLocalUserIdChange(void); extern bool InSecurityRestrictedOperation(void); +extern bool InRowLevelSecurityDisabled(void); extern void GetUserIdAndContext(Oid *userid, bool *sec_def_context); extern void SetUserIdAndContext(Oid userid, bool sec_def_context); extern void InitializeSessionUserId(const char *rolename, Oid useroid); diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h new file mode 100644 index 49caa56..fc1679e --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -1121,6 +1121,10 @@ extern Datum set_config_by_name(PG_FUNCT extern Datum show_all_settings(PG_FUNCTION_ARGS); extern Datum show_all_file_settings(PG_FUNCTION_ARGS); +/* rls.c */ +extern Datum row_security_active(PG_FUNCTION_ARGS); +extern Datum row_security_active_name(PG_FUNCTION_ARGS); + /* lockfuncs.c */ extern Datum pg_lock_status(PG_FUNCTION_ARGS); extern Datum pg_advisory_lock_int8(PG_FUNCTION_ARGS); diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out new file mode 100644 index e7c242c..98e36f2 --- a/src/test/regress/expected/rowsecurity.out +++ b/src/test/regress/expected/rowsecurity.out @@ -2839,10 +2839,44 @@ SELECT * FROM current_check; COMMIT; -- +-- check pg_stats view filtering +-- +SET row_security TO ON; +SET SESSION AUTHORIZATION rls_regress_user0; +ANALYZE current_check; +-- Stats visible +SELECT row_security_active('current_check'); + row_security_active +--------------------- + f +(1 row) + +SELECT most_common_vals FROM pg_stats where tablename = 'current_check'; + most_common_vals +--------------------- + + + {rls_regress_user1} +(3 rows) + +SET SESSION AUTHORIZATION rls_regress_user1; +-- Stats not visible +SELECT row_security_active('current_check'); + row_security_active +--------------------- + t +(1 row) + +SELECT most_common_vals FROM pg_stats where tablename = 'current_check'; + most_common_vals +------------------ +(0 rows) + +-- -- Collation support -- BEGIN; -SET row_security = force; +SET row_security TO FORCE; CREATE TABLE coll_t (c) AS VALUES ('bar'::text); CREATE POLICY coll_p ON coll_t USING (c < ('foo'::text COLLATE "C")); ALTER TABLE coll_t ENABLE ROW LEVEL SECURITY; diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out new file mode 100644 index 1e5b0b9..6206c81 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -2061,7 +2061,7 @@ pg_stats| SELECT n.nspname AS schemaname JOIN pg_class c ON ((c.oid = s.starelid))) JOIN pg_attribute a ON (((c.oid = a.attrelid) AND (a.attnum = s.staattnum)))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) - WHERE ((NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text)); + WHERE ((NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid)))); pg_tables| SELECT n.nspname AS schemaname, c.relname AS tablename, pg_get_userbyid(c.relowner) AS tableowner, diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql new file mode 100644 index e86f814..73cc020 --- a/src/test/regress/sql/rowsecurity.sql +++ b/src/test/regress/sql/rowsecurity.sql @@ -1141,10 +1141,25 @@ SELECT * FROM current_check; COMMIT; -- +-- check pg_stats view filtering +-- +SET row_security TO ON; +SET SESSION AUTHORIZATION rls_regress_user0; +ANALYZE current_check; +-- Stats visible +SELECT row_security_active('current_check'); +SELECT most_common_vals FROM pg_stats where tablename = 'current_check'; + +SET SESSION AUTHORIZATION rls_regress_user1; +-- Stats not visible +SELECT row_security_active('current_check'); +SELECT most_common_vals FROM pg_stats where tablename = 'current_check'; + +-- -- Collation support -- BEGIN; -SET row_security = force; +SET row_security TO FORCE; CREATE TABLE coll_t (c) AS VALUES ('bar'::text); CREATE POLICY coll_p ON coll_t USING (c < ('foo'::text COLLATE "C")); ALTER TABLE coll_t ENABLE ROW LEVEL SECURITY;