Review for "Add permission check on SELECT INTO"

Started by Albe Laurenzabout 14 years ago4 messages
#1Albe Laurenz
laurenz.albe@wien.gv.at

The patch is in context diff format and applies cleanly.

The functionality is needed because it keeps users from circumventing
privilege restrictions, as they can currently do in this case.

There is no documentation, which I think is OK since it changes
behaviour to work as documented.

The patch compiles without warning.

It contains a test case, but that test case does not test
the feature at all!

The expected (and encountered) result is:

CREATE SCHEMA selinto_schema;
CREATE USER selinto_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA selinto_schema
REVOKE INSERT ON TABLES FROM selinto_user;
SET SESSION AUTHORIZATION selinto_user;
SELECT * INTO TABLE selinto_schema.tmp1
FROM onek WHERE onek.unique1 < 2; -- Error
ERROR: permission denied for relation onek
RESET SESSION AUTHORIZATION;
DROP SCHEMA selinto_schema CASCADE;
DROP USER selinto_user;

This does not fail because "selinto_user" lacks INSERT permission
on "selinto_schema.tmp1" (he doesn't!), but because he lacks
SELECT permission on "onek" (as the error message indicates).
Setting default privileges on a schema can never revoke
default privileges.

The patch works as advertised in that it causes SELECT ... INTO
and CREATE TABLE ... AS to fail, however the error message is
misleading. Here a (correct) test case:

CREATE ROLE laurenz LOGIN;
CREATE TABLE public.test(id, val) AS VALUES (1, 'eins'), (2, 'zwei');
GRANT SELECT ON public.test TO laurenz;
ALTER DEFAULT PRIVILEGES FOR ROLE laurenz REVOKE INSERT ON TABLES FROM
laurenz;
SET ROLE laurenz;
CREATE TABLE public.copy1(a, b) AS SELECT * FROM public.test;
ERROR: whole-row update is not implemented
CREATE TABLE public.copy1(a) AS SELECT id FROM public.test;
ERROR: whole-row update is not implemented
SELECT * INTO public.copy2 FROM public.test;
ERROR: whole-row update is not implemented
RESET ROLE;
ALTER DEFAULT PRIVILEGES FOR ROLE laurenz GRANT ALL ON TABLES TO
laurenz;
DROP TABLE test;
DROP ROLE laurenz;

The correct error message would be:
ERROR: permission denied for relation copy1

It seems like a wrong code path is taken in ExecCheckRTEPerms,
maybe there's something wrong with rte->modifiedCols.

I'll mark the patch as "Waiting on Author" until these problems are
addressed.

Yours,
Laurenz Albe

#2Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Albe Laurenz (#1)
1 attachment(s)
Re: Review for "Add permission check on SELECT INTO"

Thanks for your reviewing.

The reason of this strange message was bug is the patch.

CREATE TABLE public.copy1(a, b) AS SELECT * FROM public.test;
ERROR:  whole-row update is not implemented

When it constructs a fake RangeTblEntry, it marked modifiedCols for
attribute 0 to (tupdesc->natts - 1), although it should be 1 to natts.
InvalidAttrNumber equals 0, so ExecCheckRTPerms got confused.

The attached patch is a revised version.
It fixed up this bug, and revised test cases to ensure permission
check error shall be raised due to the new table.

+SET SESSION AUTHORIZATION selinto_user;
+SELECT * INTO TABLE selinto_schema.tmp1
+	  FROM pg_class WHERE relname like '%a%';	-- Error
+ERROR:  permission denied for relation tmp1

Thanks,

2011/11/18 Albe Laurenz <laurenz.albe@wien.gv.at>:

The patch is in context diff format and applies cleanly.

The functionality is needed because it keeps users from circumventing
privilege restrictions, as they can currently do in this case.

There is no documentation, which I think is OK since it changes
behaviour to work as documented.

The patch compiles without warning.

It contains a test case, but that test case does not test
the feature at all!

The expected (and encountered) result is:

CREATE SCHEMA selinto_schema;
CREATE USER selinto_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA selinto_schema
        REVOKE INSERT ON TABLES FROM selinto_user;
SET SESSION AUTHORIZATION selinto_user;
SELECT * INTO TABLE selinto_schema.tmp1
        FROM onek WHERE onek.unique1 < 2;     -- Error
ERROR:  permission denied for relation onek
RESET SESSION AUTHORIZATION;
DROP SCHEMA selinto_schema CASCADE;
DROP USER selinto_user;

This does not fail because "selinto_user" lacks INSERT permission
on "selinto_schema.tmp1" (he doesn't!), but because he lacks
SELECT permission on "onek" (as the error message indicates).
Setting default privileges on a schema can never revoke
default privileges.

The patch works as advertised in that it causes SELECT ... INTO
and CREATE TABLE ... AS to fail, however the error message is
misleading. Here a (correct) test case:

CREATE ROLE laurenz LOGIN;
CREATE TABLE public.test(id, val) AS VALUES (1, 'eins'), (2, 'zwei');
GRANT SELECT ON public.test TO laurenz;
ALTER DEFAULT PRIVILEGES FOR ROLE laurenz REVOKE INSERT ON TABLES FROM
laurenz;
SET ROLE laurenz;
CREATE TABLE public.copy1(a, b) AS SELECT * FROM public.test;
ERROR:  whole-row update is not implemented
CREATE TABLE public.copy1(a) AS SELECT id FROM public.test;
ERROR:  whole-row update is not implemented
SELECT * INTO public.copy2 FROM public.test;
ERROR:  whole-row update is not implemented
RESET ROLE;
ALTER DEFAULT PRIVILEGES FOR ROLE laurenz GRANT ALL ON TABLES TO
laurenz;
DROP TABLE test;
DROP ROLE laurenz;

The correct error message would be:
ERROR:  permission denied for relation copy1

It seems like a wrong code path is taken in ExecCheckRTEPerms,
maybe there's something wrong with rte->modifiedCols.

I'll mark the patch as "Waiting on Author" until these problems are
addressed.

Yours,
Laurenz Albe

--
KaiGai Kohei <kaigai@kaigai.gr.jp>

Attachments:

pgsql-v9.2-add-select-into-checks.v2.patchapplication/octet-stream; name=pgsql-v9.2-add-select-into-checks.v2.patchDownload
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index fd7a9ed..708831a 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -2395,6 +2395,8 @@ OpenIntoRel(QueryDesc *queryDesc)
 	Datum		reloptions;
 	Oid			intoRelationId;
 	DR_intorel *myState;
+	RangeTblEntry  *rte;
+	AttrNumber		attnum;
 	static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
 
 	Assert(into);
@@ -2517,6 +2519,21 @@ OpenIntoRel(QueryDesc *queryDesc)
 	intoRelationDesc = heap_open(intoRelationId, AccessExclusiveLock);
 
 	/*
+	 * check INSERT permission on the constructed table.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = intoRelationId;
+	rte->relkind = RELKIND_RELATION;
+	rte->requiredPerms = ACL_INSERT;
+
+	for (attnum = 1; attnum <= queryDesc->tupDesc->natts; attnum++)
+		rte->modifiedCols = bms_add_member(rte->modifiedCols,
+				attnum - FirstLowInvalidHeapAttributeNumber);
+
+	ExecCheckRTPerms(list_make1(rte), true);
+
+	/*
 	 * Now replace the query's DestReceiver with one for SELECT INTO
 	 */
 	queryDesc->dest = CreateDestReceiver(DestIntoRel);
diff --git a/src/test/regress/expected/select_into.out b/src/test/regress/expected/select_into.out
index 503efe0..9ed4229 100644
--- a/src/test/regress/expected/select_into.out
+++ b/src/test/regress/expected/select_into.out
@@ -11,3 +11,42 @@ SELECT *
    FROM onek2
    WHERE onek2.unique1 < 2;
 DROP TABLE tmp1;
+--
+-- SELECT INTO and INSERT permission, if owner is not allowed to insert.
+--
+CREATE SCHEMA selinto_schema;
+CREATE USER selinto_user;
+ALTER DEFAULT PRIVILEGES FOR ROLE selinto_user
+	  REVOKE INSERT ON TABLES FROM selinto_user;
+GRANT ALL ON SCHEMA selinto_schema TO public;
+SET SESSION AUTHORIZATION selinto_user;
+SELECT * INTO TABLE selinto_schema.tmp1
+	  FROM pg_class WHERE relname like '%a%';	-- Error
+ERROR:  permission denied for relation tmp1
+SELECT oid AS clsoid, relname, relnatts + 10 AS x
+	  INTO selinto_schema.tmp2
+	  FROM pg_class WHERE relname like '%b%';	-- Error
+ERROR:  permission denied for relation tmp2
+CREATE TABLE selinto_schema.tmp3 (a,b,c)
+	   AS SELECT oid,relname,relacl FROM pg_class
+	   WHERE relname like '%c%';	-- Error
+ERROR:  permission denied for relation tmp3
+RESET SESSION AUTHORIZATION;
+ALTER DEFAULT PRIVILEGES FOR ROLE selinto_user
+	  GRANT INSERT ON TABLES TO selinto_user;
+SET SESSION AUTHORIZATION selinto_user;
+SELECT * INTO TABLE selinto_schema.tmp1
+	  FROM pg_class WHERE relname like '%a%';	-- OK
+SELECT oid AS clsoid, relname, relnatts + 10 AS x
+	  INTO selinto_schema.tmp2
+	  FROM pg_class WHERE relname like '%b%';	-- OK
+CREATE TABLE selinto_schema.tmp3 (a,b,c)
+	   AS SELECT oid,relname,relacl FROM pg_class
+	   WHERE relname like '%c%';	-- OK
+RESET SESSION AUTHORIZATION;
+DROP SCHEMA selinto_schema CASCADE;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to table selinto_schema.tmp1
+drop cascades to table selinto_schema.tmp2
+drop cascades to table selinto_schema.tmp3
+DROP USER selinto_user;
diff --git a/src/test/regress/sql/select_into.sql b/src/test/regress/sql/select_into.sql
index 2fa3d1e..039d35c 100644
--- a/src/test/regress/sql/select_into.sql
+++ b/src/test/regress/sql/select_into.sql
@@ -15,3 +15,40 @@ SELECT *
    WHERE onek2.unique1 < 2;
 
 DROP TABLE tmp1;
+
+--
+-- SELECT INTO and INSERT permission, if owner is not allowed to insert.
+--
+CREATE SCHEMA selinto_schema;
+CREATE USER selinto_user;
+ALTER DEFAULT PRIVILEGES FOR ROLE selinto_user
+	  REVOKE INSERT ON TABLES FROM selinto_user;
+GRANT ALL ON SCHEMA selinto_schema TO public;
+
+SET SESSION AUTHORIZATION selinto_user;
+SELECT * INTO TABLE selinto_schema.tmp1
+	  FROM pg_class WHERE relname like '%a%';	-- Error
+SELECT oid AS clsoid, relname, relnatts + 10 AS x
+	  INTO selinto_schema.tmp2
+	  FROM pg_class WHERE relname like '%b%';	-- Error
+CREATE TABLE selinto_schema.tmp3 (a,b,c)
+	   AS SELECT oid,relname,relacl FROM pg_class
+	   WHERE relname like '%c%';	-- Error
+RESET SESSION AUTHORIZATION;
+
+ALTER DEFAULT PRIVILEGES FOR ROLE selinto_user
+	  GRANT INSERT ON TABLES TO selinto_user;
+
+SET SESSION AUTHORIZATION selinto_user;
+SELECT * INTO TABLE selinto_schema.tmp1
+	  FROM pg_class WHERE relname like '%a%';	-- OK
+SELECT oid AS clsoid, relname, relnatts + 10 AS x
+	  INTO selinto_schema.tmp2
+	  FROM pg_class WHERE relname like '%b%';	-- OK
+CREATE TABLE selinto_schema.tmp3 (a,b,c)
+	   AS SELECT oid,relname,relacl FROM pg_class
+	   WHERE relname like '%c%';	-- OK
+RESET SESSION AUTHORIZATION;
+
+DROP SCHEMA selinto_schema CASCADE;
+DROP USER selinto_user;
#3Albe Laurenz
laurenz.albe@wien.gv.at
In reply to: Kohei KaiGai (#2)
Re: Review for "Add permission check on SELECT INTO"

Kohei KaiGai wrote:

The attached patch is a revised version.
It fixed up this bug, and revised test cases to ensure permission
check error shall be raised due to the new table.

Thanks.
The second patch seems fine to me, I'll mark it "ready for committer".

Yours,
Laurenz Albe

#4Robert Haas
robertmhaas@gmail.com
In reply to: Albe Laurenz (#3)
Re: Review for "Add permission check on SELECT INTO"

On Mon, Nov 21, 2011 at 6:38 AM, Albe Laurenz <laurenz.albe@wien.gv.at> wrote:

Kohei KaiGai wrote:

The attached patch is a revised version.
It fixed up this bug, and revised test cases to ensure permission
check error shall be raised due to the new table.

Thanks.
The second patch seems fine to me, I'll mark it "ready for committer".

Committed.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company