Re: row_security GUC does not behave as documented - Mailing list pgsql-hackers
From | Tom Lane |
---|---|
Subject | Re: row_security GUC does not behave as documented |
Date | |
Msg-id | 26427.1451876450@sss.pgh.pa.us Whole thread Raw |
In response to | Re: row_security GUC does not behave as documented (Stephen Frost <sfrost@snowman.net>) |
Responses |
Re: row_security GUC does not behave as documented
Re: row_security GUC does not behave as documented |
List | pgsql-hackers |
Stephen Frost <sfrost@snowman.net> writes: > On Sunday, January 3, 2016, Tom Lane <tgl@sss.pgh.pa.us> wrote: >> The fine manual says that when row_security is set to off, "queries fail >> which would otherwise apply at least one policy". However, a look at >> check_enable_rls() says that that is a true statement only when the user >> is not table owner. If the user *is* table owner, turning off >> row_security seems to amount to just silently disabling RLS, even for >> tables with FORCE ROW LEVEL SECURITY. >> >> I am not sure if this is a documentation bug or a code bug, but it >> sure looks to be one or the other. > The original reason for changing how row_security works was to avoid a > change in behavior based on a GUC changing. As such, I'm thinking that has > to be a code bug, as otherwise it would be a behavior change due to a GUC > being changed in the FORCE RLS case for table owners. Well, I tried changing the code to act the way I gather it should, and it breaks a whole bunch of regression test cases. See attached. regards, tom lane diff --git a/src/backend/utils/misc/rls.c b/src/backend/utils/misc/rls.c index b6c1d75..4e7b4d1 100644 *** a/src/backend/utils/misc/rls.c --- b/src/backend/utils/misc/rls.c *************** check_enable_rls(Oid relid, Oid checkAsU *** 59,67 **** Oid user_id = checkAsUser ? checkAsUser : GetUserId(); /* Nothing to do for built-in relations */ ! if (relid < FirstNormalObjectId) return RLS_NONE; tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); if (!HeapTupleIsValid(tuple)) return RLS_NONE; --- 59,68 ---- Oid user_id = checkAsUser ? checkAsUser : GetUserId(); /* Nothing to do for built-in relations */ ! if (relid < (Oid) FirstNormalObjectId) return RLS_NONE; + /* Fetch relation's relrowsecurity and relforcerowsecurity flags */ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); if (!HeapTupleIsValid(tuple)) return RLS_NONE; *************** check_enable_rls(Oid relid, Oid checkAsU *** 88,96 **** return RLS_NONE_ENV; /* ! * Table owners generally bypass RLS, except if row_security=true and the ! * table has been set (by an owner) to FORCE ROW SECURITY, and this is not ! * a referential integrity check. * * Return RLS_NONE_ENV to indicate that this decision depends on the * environment (in this case, the user_id). --- 89,97 ---- return RLS_NONE_ENV; /* ! * Table owners generally bypass RLS, except if the table has been set (by ! * an owner) to FORCE ROW SECURITY, and this is not a referential ! * integrity check. * * Return RLS_NONE_ENV to indicate that this decision depends on the * environment (in this case, the user_id). *************** check_enable_rls(Oid relid, Oid checkAsU *** 98,128 **** if (pg_class_ownercheck(relid, user_id)) { /* ! * If row_security=true and FORCE ROW LEVEL SECURITY has been set on ! * the relation then we return RLS_ENABLED to indicate that RLS should ! * still be applied. If we are in a SECURITY_NOFORCE_RLS context or if ! * row_security=false then we return RLS_NONE_ENV. * ! * The SECURITY_NOFORCE_RLS indicates that we should not apply RLS even ! * if the table has FORCE RLS set- IF the current user is the owner. ! * This is specifically to ensure that referential integrity checks are ! * able to still run correctly. * * This is intentionally only done after we have checked that the user * is the table owner, which should always be the case for referential * integrity checks. */ ! if (row_security && relforcerowsecurity && !InNoForceRLSOperation()) ! return RLS_ENABLED; ! else return RLS_NONE_ENV; } ! /* row_security GUC says to bypass RLS, but user lacks permission */ if (!row_security && !noError) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), ! errmsg("insufficient privilege to bypass row-level security"))); /* RLS should be fully enabled for this relation. */ return RLS_ENABLED; --- 99,130 ---- if (pg_class_ownercheck(relid, user_id)) { /* ! * If FORCE ROW LEVEL SECURITY has been set on the relation then we ! * should return RLS_ENABLED to indicate that RLS should be applied. ! * If not, or if we are in an InNoForceRLSOperation context, we return ! * RLS_NONE_ENV. * ! * InNoForceRLSOperation indicates that we should not apply RLS even ! * if the table has FORCE RLS set - IF the current user is the owner. ! * This is specifically to ensure that referential integrity checks ! * are able to still run correctly. * * This is intentionally only done after we have checked that the user * is the table owner, which should always be the case for referential * integrity checks. */ ! if (!relforcerowsecurity || InNoForceRLSOperation()) return RLS_NONE_ENV; } ! /* ! * We should apply RLS. However, the user may turn off the row_security ! * GUC to get a forced error instead. ! */ if (!row_security && !noError) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), ! errmsg("insufficient privilege to bypass row-level security"))); /* RLS should be fully enabled for this relation. */ return RLS_ENABLED; *** /home/postgres/pgsql/src/test/regress/expected/rowsecurity.out Sat Dec 19 16:47:11 2015 --- /home/postgres/pgsql/src/test/regress/results/rowsecurity.out Sun Jan 3 21:56:18 2016 *************** *** 3217,3244 **** SET row_security = off; -- Shows all rows TABLE r1; ! a ! ---- ! 10 ! 20 ! (2 rows) ! -- Update all rows UPDATE r1 SET a = 1; TABLE r1; ! a ! --- ! 1 ! 1 ! (2 rows) ! -- Delete all rows DELETE FROM r1; TABLE r1; ! a ! --- ! (0 rows) ! DROP TABLE r1; -- -- FORCE ROW LEVEL SECURITY does not break RI --- 3217,3233 ---- SET row_security = off; -- Shows all rows TABLE r1; ! ERROR: insufficient privilege to bypass row-level security -- Update all rows UPDATE r1 SET a = 1; + ERROR: insufficient privilege to bypass row-level security TABLE r1; ! ERROR: insufficient privilege to bypass row-level security -- Delete all rows DELETE FROM r1; + ERROR: insufficient privilege to bypass row-level security TABLE r1; ! ERROR: insufficient privilege to bypass row-level security DROP TABLE r1; -- -- FORCE ROW LEVEL SECURITY does not break RI *************** *** 3351,3362 **** SET row_security = off; -- Rows shown now TABLE r1; ! a ! ---- ! 10 ! 20 ! (2 rows) ! SET row_security = on; -- Error INSERT INTO r1 VALUES (10), (20) RETURNING *; --- 3340,3346 ---- SET row_security = off; -- Rows shown now TABLE r1; ! ERROR: insufficient privilege to bypass row-level security SET row_security = on; -- Error INSERT INTO r1 VALUES (10), (20) RETURNING *; *************** *** 3379,3402 **** -- Show updated rows SET row_security = off; TABLE r1; ! a ! ---- ! 30 ! (1 row) ! -- reset value in r1 for test with RETURNING UPDATE r1 SET a = 10; -- Verify row reset TABLE r1; ! a ! ---- ! 10 ! (1 row) ! SET row_security = on; -- Error UPDATE r1 SET a = 30 RETURNING *; ! ERROR: new row violates row-level security policy for table "r1" DROP TABLE r1; -- Check dependency handling RESET SESSION AUTHORIZATION; --- 3363,3382 ---- -- Show updated rows SET row_security = off; TABLE r1; ! ERROR: insufficient privilege to bypass row-level security -- reset value in r1 for test with RETURNING UPDATE r1 SET a = 10; + ERROR: insufficient privilege to bypass row-level security -- Verify row reset TABLE r1; ! ERROR: insufficient privilege to bypass row-level security SET row_security = on; -- Error UPDATE r1 SET a = 30 RETURNING *; ! a ! --- ! (0 rows) ! DROP TABLE r1; -- Check dependency handling RESET SESSION AUTHORIZATION; ======================================================================
pgsql-hackers by date: