mirror of
https://github.com/postgres/postgres.git
synced 2025-06-26 12:21:12 +03:00
Restrict virtual columns to use built-in functions and types
Just like selecting from a view is exploitable (CVE-2024-7348), selecting from a table with virtual generated columns is exploitable. Users who are concerned about this can avoid selecting from views, but telling them to avoid selecting from tables is less practical. To address this, this changes it so that generation expressions for virtual generated columns are restricted to using built-in functions and types, and the columns are restricted to having a built-in type. We assume that built-in functions and types cannot be exploited for this purpose. In the future, this could be expanded by some new mechanism to declare other functions and types as safe or trusted for this purpose, but that is to be designed. (An alternative approach might have been to expand the restrict_nonsystem_relation_kind GUC to handle this, like the fix for CVE-2024-7348. But that is kind of an ugly approach. That fix had to fit in the constraints of fixing an ancient vulnerability in all branches. Since virtual generated columns are new, we're free from the constraints of the past, and we can and should use cleaner options.) Reported-by: Feike Steenbergen <feikesteenbergen@gmail.com> Reviewed-by: jian he <jian.universality@gmail.com> Discussion: https://www.postgresql.org/message-id/flat/CAK_s-G2Q7de8Q0qOYUR%3D_CTB5FzzVBm5iZjOp%2BmeVWpMpmfO0w%40mail.gmail.com
This commit is contained in:
@ -419,6 +419,16 @@ CREATE TABLE people (
|
||||
<varname>tableoid</varname>.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
A virtual generated column cannot have a user-defined type, and the
|
||||
generation expression of a virtual generated column must not reference
|
||||
user-defined functions or types, that is, it can only use built-in
|
||||
functions or types. This applies also indirectly, such as for functions
|
||||
or types that underlie operators or casts. (This restriction does not
|
||||
exist for stored generated columns.)
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
A generated column cannot have a column default or an identity definition.
|
||||
|
@ -924,6 +924,15 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
|
||||
not other generated columns. Any functions and operators used must be
|
||||
immutable. References to other tables are not allowed.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
A virtual generated column cannot have a user-defined type, and the
|
||||
generation expression of a virtual generated column must not reference
|
||||
user-defined functions or types, that is, it can only use built-in
|
||||
functions or types. This applies also indirectly, such as for functions
|
||||
or types that underlie operators or casts. (This restriction does not
|
||||
exist for stored generated columns.)
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
|
@ -664,6 +664,15 @@ CheckAttributeType(const char *attname,
|
||||
flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* For consistency with check_virtual_generated_security().
|
||||
*/
|
||||
if ((flags & CHKATYPE_IS_VIRTUAL) && atttypid >= FirstUnpinnedObjectId)
|
||||
ereport(ERROR,
|
||||
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("virtual generated column \"%s\" cannot have a user-defined type", attname),
|
||||
errdetail("Virtual generated columns that make use of user-defined types are not yet supported."));
|
||||
|
||||
/*
|
||||
* This might not be strictly invalid per SQL standard, but it is pretty
|
||||
* useless, and it cannot be dumped, so we must disallow it.
|
||||
@ -3215,6 +3224,86 @@ check_nested_generated(ParseState *pstate, Node *node)
|
||||
check_nested_generated_walker(node, pstate);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check security of virtual generated column expression.
|
||||
*
|
||||
* Just like selecting from a view is exploitable (CVE-2024-7348), selecting
|
||||
* from a table with virtual generated columns is exploitable. Users who are
|
||||
* concerned about this can avoid selecting from views, but telling them to
|
||||
* avoid selecting from tables is less practical.
|
||||
*
|
||||
* To address this, this restricts generation expressions for virtual
|
||||
* generated columns are restricted to using built-in functions and types. We
|
||||
* assume that built-in functions and types cannot be exploited for this
|
||||
* purpose. Note the overall security also requires that all functions in use
|
||||
* a immutable. (For example, there are some built-in non-immutable functions
|
||||
* that can run arbitrary SQL.) The immutability is checked elsewhere, since
|
||||
* that is a property that needs to hold independent of security
|
||||
* considerations.
|
||||
*
|
||||
* In the future, this could be expanded by some new mechanism to declare
|
||||
* other functions and types as safe or trusted for this purpose, but that is
|
||||
* to be designed.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Callback for check_functions_in_node() that determines whether a function
|
||||
* is user-defined.
|
||||
*/
|
||||
static bool
|
||||
contains_user_functions_checker(Oid func_id, void *context)
|
||||
{
|
||||
return (func_id >= FirstUnpinnedObjectId);
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks for all the things we don't want in the generation expressions of
|
||||
* virtual generated columns for security reasons. Errors out if it finds
|
||||
* one.
|
||||
*/
|
||||
static bool
|
||||
check_virtual_generated_security_walker(Node *node, void *context)
|
||||
{
|
||||
ParseState *pstate = context;
|
||||
|
||||
if (node == NULL)
|
||||
return false;
|
||||
|
||||
if (!IsA(node, List))
|
||||
{
|
||||
if (check_functions_in_node(node, contains_user_functions_checker, NULL))
|
||||
ereport(ERROR,
|
||||
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("generation expression uses user-defined function"),
|
||||
errdetail("Virtual generated columns that make use of user-defined functions are not yet supported."),
|
||||
parser_errposition(pstate, exprLocation(node)));
|
||||
|
||||
/*
|
||||
* check_functions_in_node() doesn't check some node types (see
|
||||
* comment there). We handle CoerceToDomain and MinMaxExpr by
|
||||
* checking for built-in types. The other listed node types cannot
|
||||
* call user-definable SQL-visible functions.
|
||||
*
|
||||
* We furthermore need this type check to handle built-in, immutable
|
||||
* polymorphic functions such as array_eq().
|
||||
*/
|
||||
if (exprType(node) >= FirstUnpinnedObjectId)
|
||||
ereport(ERROR,
|
||||
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("generation expression uses user-defined type"),
|
||||
errdetail("Virtual generated columns that make use of user-defined types are not yet supported."),
|
||||
parser_errposition(pstate, exprLocation(node)));
|
||||
}
|
||||
|
||||
return expression_tree_walker(node, check_virtual_generated_security_walker, context);
|
||||
}
|
||||
|
||||
static void
|
||||
check_virtual_generated_security(ParseState *pstate, Node *node)
|
||||
{
|
||||
check_virtual_generated_security_walker(node, pstate);
|
||||
}
|
||||
|
||||
/*
|
||||
* Take a raw default and convert it to a cooked format ready for
|
||||
* storage.
|
||||
@ -3254,6 +3343,10 @@ cookDefault(ParseState *pstate,
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
||||
errmsg("generation expression is not immutable")));
|
||||
|
||||
/* Check security of expressions for virtual generated column */
|
||||
if (attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
|
||||
check_virtual_generated_security(pstate, expr);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -57,6 +57,6 @@
|
||||
*/
|
||||
|
||||
/* yyyymmddN */
|
||||
#define CATALOG_VERSION_NO 202506121
|
||||
#define CATALOG_VERSION_NO 202506251
|
||||
|
||||
#endif
|
||||
|
@ -553,15 +553,11 @@ CREATE TABLE gtest4 (
|
||||
a int,
|
||||
b double_int GENERATED ALWAYS AS ((a * 2, a * 3)) VIRTUAL
|
||||
);
|
||||
INSERT INTO gtest4 VALUES (1), (6);
|
||||
SELECT * FROM gtest4;
|
||||
a | b
|
||||
---+---------
|
||||
1 | (2,3)
|
||||
6 | (12,18)
|
||||
(2 rows)
|
||||
|
||||
DROP TABLE gtest4;
|
||||
ERROR: virtual generated column "b" cannot have a user-defined type
|
||||
DETAIL: Virtual generated columns that make use of user-defined types are not yet supported.
|
||||
--INSERT INTO gtest4 VALUES (1), (6);
|
||||
--SELECT * FROM gtest4;
|
||||
--DROP TABLE gtest4;
|
||||
DROP TYPE double_int;
|
||||
-- using tableoid is allowed
|
||||
CREATE TABLE gtest_tableoid (
|
||||
@ -604,9 +600,13 @@ INSERT INTO gtest11 VALUES (1, 10), (2, 20);
|
||||
GRANT SELECT (a, c) ON gtest11 TO regress_user11;
|
||||
CREATE FUNCTION gf1(a int) RETURNS int AS $$ SELECT a * 3 $$ IMMUTABLE LANGUAGE SQL;
|
||||
REVOKE ALL ON FUNCTION gf1(int) FROM PUBLIC;
|
||||
CREATE TABLE gtest12 (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (gf1(b)) VIRTUAL);
|
||||
INSERT INTO gtest12 VALUES (1, 10), (2, 20);
|
||||
GRANT SELECT (a, c), INSERT ON gtest12 TO regress_user11;
|
||||
CREATE TABLE gtest12 (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (gf1(b)) VIRTUAL); -- fails, user-defined function
|
||||
ERROR: generation expression uses user-defined function
|
||||
LINE 1: ...nt PRIMARY KEY, b int, c int GENERATED ALWAYS AS (gf1(b)) VI...
|
||||
^
|
||||
DETAIL: Virtual generated columns that make use of user-defined functions are not yet supported.
|
||||
--INSERT INTO gtest12 VALUES (1, 10), (2, 20);
|
||||
--GRANT SELECT (a, c), INSERT ON gtest12 TO regress_user11;
|
||||
SET ROLE regress_user11;
|
||||
SELECT a, b FROM gtest11; -- not allowed
|
||||
ERROR: permission denied for table gtest11
|
||||
@ -619,15 +619,12 @@ SELECT a, c FROM gtest11; -- allowed
|
||||
|
||||
SELECT gf1(10); -- not allowed
|
||||
ERROR: permission denied for function gf1
|
||||
INSERT INTO gtest12 VALUES (3, 30), (4, 40); -- allowed (does not actually invoke the function)
|
||||
SELECT a, c FROM gtest12; -- currently not allowed because of function permissions, should arguably be allowed
|
||||
ERROR: permission denied for function gf1
|
||||
--INSERT INTO gtest12 VALUES (3, 30), (4, 40); -- allowed (does not actually invoke the function)
|
||||
--SELECT a, c FROM gtest12; -- currently not allowed because of function permissions, should arguably be allowed
|
||||
RESET ROLE;
|
||||
DROP FUNCTION gf1(int); -- fail
|
||||
ERROR: cannot drop function gf1(integer) because other objects depend on it
|
||||
DETAIL: column c of table gtest12 depends on function gf1(integer)
|
||||
HINT: Use DROP ... CASCADE to drop the dependent objects too.
|
||||
DROP TABLE gtest11, gtest12;
|
||||
--DROP FUNCTION gf1(int); -- fail
|
||||
DROP TABLE gtest11;
|
||||
--DROP TABLE gtest12;
|
||||
DROP FUNCTION gf1(int);
|
||||
DROP USER regress_user11;
|
||||
-- check constraints
|
||||
@ -811,6 +808,12 @@ CREATE TABLE gtest24nn (a int, b gtestdomainnn GENERATED ALWAYS AS (a * 2) VIRTU
|
||||
ERROR: virtual generated column "b" cannot have a domain type
|
||||
--INSERT INTO gtest24nn (a) VALUES (4); -- ok
|
||||
--INSERT INTO gtest24nn (a) VALUES (NULL); -- error
|
||||
-- using user-defined type not yet supported
|
||||
CREATE TABLE gtest24xxx (a gtestdomain1, b gtestdomain1, c int GENERATED ALWAYS AS (greatest(a, b)) VIRTUAL); -- error
|
||||
ERROR: generation expression uses user-defined type
|
||||
LINE 1: ...main1, b gtestdomain1, c int GENERATED ALWAYS AS (greatest(a...
|
||||
^
|
||||
DETAIL: Virtual generated columns that make use of user-defined types are not yet supported.
|
||||
-- typed tables (currently not supported)
|
||||
CREATE TYPE gtest_type AS (f1 integer, f2 text, f3 bigint);
|
||||
CREATE TABLE gtest28 OF gtest_type (f1 WITH OPTIONS GENERATED ALWAYS AS (f2 *2) VIRTUAL);
|
||||
|
@ -524,10 +524,16 @@ Tables from schemas:
|
||||
"testpub_rf_schema2"
|
||||
|
||||
-- fail - virtual generated column uses user-defined function
|
||||
-- (Actually, this already fails at CREATE TABLE rather than at CREATE
|
||||
-- PUBLICATION, but let's keep the test in case the former gets
|
||||
-- relaxed sometime.)
|
||||
CREATE TABLE testpub_rf_tbl6 (id int PRIMARY KEY, x int, y int GENERATED ALWAYS AS (x * testpub_rf_func2()) VIRTUAL);
|
||||
ERROR: generation expression uses user-defined function
|
||||
LINE 1: ...RIMARY KEY, x int, y int GENERATED ALWAYS AS (x * testpub_rf...
|
||||
^
|
||||
DETAIL: Virtual generated columns that make use of user-defined functions are not yet supported.
|
||||
CREATE PUBLICATION testpub7 FOR TABLE testpub_rf_tbl6 WHERE (y > 100);
|
||||
ERROR: invalid publication WHERE expression
|
||||
DETAIL: User-defined or built-in mutable functions are not allowed.
|
||||
ERROR: relation "testpub_rf_tbl6" does not exist
|
||||
-- test that SET EXPRESSION is rejected, because it could affect a row filter
|
||||
SET client_min_messages = 'ERROR';
|
||||
CREATE TABLE testpub_rf_tbl7 (id int PRIMARY KEY, x int, y int GENERATED ALWAYS AS (x * 111) VIRTUAL);
|
||||
@ -541,7 +547,7 @@ DROP TABLE testpub_rf_tbl2;
|
||||
DROP TABLE testpub_rf_tbl3;
|
||||
DROP TABLE testpub_rf_tbl4;
|
||||
DROP TABLE testpub_rf_tbl5;
|
||||
DROP TABLE testpub_rf_tbl6;
|
||||
--DROP TABLE testpub_rf_tbl6;
|
||||
DROP TABLE testpub_rf_schema1.testpub_rf_tbl5;
|
||||
DROP TABLE testpub_rf_schema2.testpub_rf_tbl6;
|
||||
DROP SCHEMA testpub_rf_schema1;
|
||||
|
@ -253,10 +253,10 @@ CREATE TABLE gtest4 (
|
||||
a int,
|
||||
b double_int GENERATED ALWAYS AS ((a * 2, a * 3)) VIRTUAL
|
||||
);
|
||||
INSERT INTO gtest4 VALUES (1), (6);
|
||||
SELECT * FROM gtest4;
|
||||
--INSERT INTO gtest4 VALUES (1), (6);
|
||||
--SELECT * FROM gtest4;
|
||||
|
||||
DROP TABLE gtest4;
|
||||
--DROP TABLE gtest4;
|
||||
DROP TYPE double_int;
|
||||
|
||||
-- using tableoid is allowed
|
||||
@ -290,20 +290,21 @@ GRANT SELECT (a, c) ON gtest11 TO regress_user11;
|
||||
CREATE FUNCTION gf1(a int) RETURNS int AS $$ SELECT a * 3 $$ IMMUTABLE LANGUAGE SQL;
|
||||
REVOKE ALL ON FUNCTION gf1(int) FROM PUBLIC;
|
||||
|
||||
CREATE TABLE gtest12 (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (gf1(b)) VIRTUAL);
|
||||
INSERT INTO gtest12 VALUES (1, 10), (2, 20);
|
||||
GRANT SELECT (a, c), INSERT ON gtest12 TO regress_user11;
|
||||
CREATE TABLE gtest12 (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (gf1(b)) VIRTUAL); -- fails, user-defined function
|
||||
--INSERT INTO gtest12 VALUES (1, 10), (2, 20);
|
||||
--GRANT SELECT (a, c), INSERT ON gtest12 TO regress_user11;
|
||||
|
||||
SET ROLE regress_user11;
|
||||
SELECT a, b FROM gtest11; -- not allowed
|
||||
SELECT a, c FROM gtest11; -- allowed
|
||||
SELECT gf1(10); -- not allowed
|
||||
INSERT INTO gtest12 VALUES (3, 30), (4, 40); -- allowed (does not actually invoke the function)
|
||||
SELECT a, c FROM gtest12; -- currently not allowed because of function permissions, should arguably be allowed
|
||||
--INSERT INTO gtest12 VALUES (3, 30), (4, 40); -- allowed (does not actually invoke the function)
|
||||
--SELECT a, c FROM gtest12; -- currently not allowed because of function permissions, should arguably be allowed
|
||||
RESET ROLE;
|
||||
|
||||
DROP FUNCTION gf1(int); -- fail
|
||||
DROP TABLE gtest11, gtest12;
|
||||
--DROP FUNCTION gf1(int); -- fail
|
||||
DROP TABLE gtest11;
|
||||
--DROP TABLE gtest12;
|
||||
DROP FUNCTION gf1(int);
|
||||
DROP USER regress_user11;
|
||||
|
||||
@ -463,6 +464,9 @@ CREATE TABLE gtest24nn (a int, b gtestdomainnn GENERATED ALWAYS AS (a * 2) VIRTU
|
||||
--INSERT INTO gtest24nn (a) VALUES (4); -- ok
|
||||
--INSERT INTO gtest24nn (a) VALUES (NULL); -- error
|
||||
|
||||
-- using user-defined type not yet supported
|
||||
CREATE TABLE gtest24xxx (a gtestdomain1, b gtestdomain1, c int GENERATED ALWAYS AS (greatest(a, b)) VIRTUAL); -- error
|
||||
|
||||
-- typed tables (currently not supported)
|
||||
CREATE TYPE gtest_type AS (f1 integer, f2 text, f3 bigint);
|
||||
CREATE TABLE gtest28 OF gtest_type (f1 WITH OPTIONS GENERATED ALWAYS AS (f2 *2) VIRTUAL);
|
||||
|
@ -262,6 +262,9 @@ ALTER PUBLICATION testpub6 SET TABLES IN SCHEMA testpub_rf_schema2, TABLE testpu
|
||||
RESET client_min_messages;
|
||||
\dRp+ testpub6
|
||||
-- fail - virtual generated column uses user-defined function
|
||||
-- (Actually, this already fails at CREATE TABLE rather than at CREATE
|
||||
-- PUBLICATION, but let's keep the test in case the former gets
|
||||
-- relaxed sometime.)
|
||||
CREATE TABLE testpub_rf_tbl6 (id int PRIMARY KEY, x int, y int GENERATED ALWAYS AS (x * testpub_rf_func2()) VIRTUAL);
|
||||
CREATE PUBLICATION testpub7 FOR TABLE testpub_rf_tbl6 WHERE (y > 100);
|
||||
-- test that SET EXPRESSION is rejected, because it could affect a row filter
|
||||
@ -276,7 +279,7 @@ DROP TABLE testpub_rf_tbl2;
|
||||
DROP TABLE testpub_rf_tbl3;
|
||||
DROP TABLE testpub_rf_tbl4;
|
||||
DROP TABLE testpub_rf_tbl5;
|
||||
DROP TABLE testpub_rf_tbl6;
|
||||
--DROP TABLE testpub_rf_tbl6;
|
||||
DROP TABLE testpub_rf_schema1.testpub_rf_tbl5;
|
||||
DROP TABLE testpub_rf_schema2.testpub_rf_tbl6;
|
||||
DROP SCHEMA testpub_rf_schema1;
|
||||
|
Reference in New Issue
Block a user