Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0cd69b3

Browse files
committed
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 <[email protected]> Reviewed-by: jian he <[email protected]> Discussion: https://www.postgresql.org/message-id/flat/CAK_s-G2Q7de8Q0qOYUR%3D_CTB5FzzVBm5iZjOp%2BmeVWpMpmfO0w%40mail.gmail.com
1 parent 69e5cdc commit 0cd69b3

File tree

8 files changed

+163
-35
lines changed

8 files changed

+163
-35
lines changed

doc/src/sgml/ddl.sgml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,16 @@ CREATE TABLE people (
419419
<varname>tableoid</varname>.
420420
</para>
421421
</listitem>
422+
<listitem>
423+
<para>
424+
A virtual generated column cannot have a user-defined type, and the
425+
generation expression of a virtual generated column must not reference
426+
user-defined functions or types, that is, it can only use built-in
427+
functions or types. This applies also indirectly, such as for functions
428+
or types that underlie operators or casts. (This restriction does not
429+
exist for stored generated columns.)
430+
</para>
431+
</listitem>
422432
<listitem>
423433
<para>
424434
A generated column cannot have a column default or an identity definition.

doc/src/sgml/ref/create_table.sgml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -924,6 +924,15 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
924924
not other generated columns. Any functions and operators used must be
925925
immutable. References to other tables are not allowed.
926926
</para>
927+
928+
<para>
929+
A virtual generated column cannot have a user-defined type, and the
930+
generation expression of a virtual generated column must not reference
931+
user-defined functions or types, that is, it can only use built-in
932+
functions or types. This applies also indirectly, such as for functions
933+
or types that underlie operators or casts. (This restriction does not
934+
exist for stored generated columns.)
935+
</para>
927936
</listitem>
928937
</varlistentry>
929938

src/backend/catalog/heap.c

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,15 @@ CheckAttributeType(const char *attname,
664664
flags);
665665
}
666666

667+
/*
668+
* For consistency with check_virtual_generated_security().
669+
*/
670+
if ((flags & CHKATYPE_IS_VIRTUAL) && atttypid >= FirstUnpinnedObjectId)
671+
ereport(ERROR,
672+
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
673+
errmsg("virtual generated column \"%s\" cannot have a user-defined type", attname),
674+
errdetail("Virtual generated columns that make use of user-defined types are not yet supported."));
675+
667676
/*
668677
* This might not be strictly invalid per SQL standard, but it is pretty
669678
* useless, and it cannot be dumped, so we must disallow it.
@@ -3215,6 +3224,86 @@ check_nested_generated(ParseState *pstate, Node *node)
32153224
check_nested_generated_walker(node, pstate);
32163225
}
32173226

3227+
/*
3228+
* Check security of virtual generated column expression.
3229+
*
3230+
* Just like selecting from a view is exploitable (CVE-2024-7348), selecting
3231+
* from a table with virtual generated columns is exploitable. Users who are
3232+
* concerned about this can avoid selecting from views, but telling them to
3233+
* avoid selecting from tables is less practical.
3234+
*
3235+
* To address this, this restricts generation expressions for virtual
3236+
* generated columns are restricted to using built-in functions and types. We
3237+
* assume that built-in functions and types cannot be exploited for this
3238+
* purpose. Note the overall security also requires that all functions in use
3239+
* a immutable. (For example, there are some built-in non-immutable functions
3240+
* that can run arbitrary SQL.) The immutability is checked elsewhere, since
3241+
* that is a property that needs to hold independent of security
3242+
* considerations.
3243+
*
3244+
* In the future, this could be expanded by some new mechanism to declare
3245+
* other functions and types as safe or trusted for this purpose, but that is
3246+
* to be designed.
3247+
*/
3248+
3249+
/*
3250+
* Callback for check_functions_in_node() that determines whether a function
3251+
* is user-defined.
3252+
*/
3253+
static bool
3254+
contains_user_functions_checker(Oid func_id, void *context)
3255+
{
3256+
return (func_id >= FirstUnpinnedObjectId);
3257+
}
3258+
3259+
/*
3260+
* Checks for all the things we don't want in the generation expressions of
3261+
* virtual generated columns for security reasons. Errors out if it finds
3262+
* one.
3263+
*/
3264+
static bool
3265+
check_virtual_generated_security_walker(Node *node, void *context)
3266+
{
3267+
ParseState *pstate = context;
3268+
3269+
if (node == NULL)
3270+
return false;
3271+
3272+
if (!IsA(node, List))
3273+
{
3274+
if (check_functions_in_node(node, contains_user_functions_checker, NULL))
3275+
ereport(ERROR,
3276+
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
3277+
errmsg("generation expression uses user-defined function"),
3278+
errdetail("Virtual generated columns that make use of user-defined functions are not yet supported."),
3279+
parser_errposition(pstate, exprLocation(node)));
3280+
3281+
/*
3282+
* check_functions_in_node() doesn't check some node types (see
3283+
* comment there). We handle CoerceToDomain and MinMaxExpr by
3284+
* checking for built-in types. The other listed node types cannot
3285+
* call user-definable SQL-visible functions.
3286+
*
3287+
* We furthermore need this type check to handle built-in, immutable
3288+
* polymorphic functions such as array_eq().
3289+
*/
3290+
if (exprType(node) >= FirstUnpinnedObjectId)
3291+
ereport(ERROR,
3292+
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
3293+
errmsg("generation expression uses user-defined type"),
3294+
errdetail("Virtual generated columns that make use of user-defined types are not yet supported."),
3295+
parser_errposition(pstate, exprLocation(node)));
3296+
}
3297+
3298+
return expression_tree_walker(node, check_virtual_generated_security_walker, context);
3299+
}
3300+
3301+
static void
3302+
check_virtual_generated_security(ParseState *pstate, Node *node)
3303+
{
3304+
check_virtual_generated_security_walker(node, pstate);
3305+
}
3306+
32183307
/*
32193308
* Take a raw default and convert it to a cooked format ready for
32203309
* storage.
@@ -3254,6 +3343,10 @@ cookDefault(ParseState *pstate,
32543343
ereport(ERROR,
32553344
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
32563345
errmsg("generation expression is not immutable")));
3346+
3347+
/* Check security of expressions for virtual generated column */
3348+
if (attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
3349+
check_virtual_generated_security(pstate, expr);
32573350
}
32583351
else
32593352
{

src/include/catalog/catversion.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,6 @@
5757
*/
5858

5959
/* yyyymmddN */
60-
#define CATALOG_VERSION_NO 202506121
60+
#define CATALOG_VERSION_NO 202506251
6161

6262
#endif

src/test/regress/expected/generated_virtual.out

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -553,15 +553,11 @@ CREATE TABLE gtest4 (
553553
a int,
554554
b double_int GENERATED ALWAYS AS ((a * 2, a * 3)) VIRTUAL
555555
);
556-
INSERT INTO gtest4 VALUES (1), (6);
557-
SELECT * FROM gtest4;
558-
a | b
559-
---+---------
560-
1 | (2,3)
561-
6 | (12,18)
562-
(2 rows)
563-
564-
DROP TABLE gtest4;
556+
ERROR: virtual generated column "b" cannot have a user-defined type
557+
DETAIL: Virtual generated columns that make use of user-defined types are not yet supported.
558+
--INSERT INTO gtest4 VALUES (1), (6);
559+
--SELECT * FROM gtest4;
560+
--DROP TABLE gtest4;
565561
DROP TYPE double_int;
566562
-- using tableoid is allowed
567563
CREATE TABLE gtest_tableoid (
@@ -604,9 +600,13 @@ INSERT INTO gtest11 VALUES (1, 10), (2, 20);
604600
GRANT SELECT (a, c) ON gtest11 TO regress_user11;
605601
CREATE FUNCTION gf1(a int) RETURNS int AS $$ SELECT a * 3 $$ IMMUTABLE LANGUAGE SQL;
606602
REVOKE ALL ON FUNCTION gf1(int) FROM PUBLIC;
607-
CREATE TABLE gtest12 (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (gf1(b)) VIRTUAL);
608-
INSERT INTO gtest12 VALUES (1, 10), (2, 20);
609-
GRANT SELECT (a, c), INSERT ON gtest12 TO regress_user11;
603+
CREATE TABLE gtest12 (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (gf1(b)) VIRTUAL); -- fails, user-defined function
604+
ERROR: generation expression uses user-defined function
605+
LINE 1: ...nt PRIMARY KEY, b int, c int GENERATED ALWAYS AS (gf1(b)) VI...
606+
^
607+
DETAIL: Virtual generated columns that make use of user-defined functions are not yet supported.
608+
--INSERT INTO gtest12 VALUES (1, 10), (2, 20);
609+
--GRANT SELECT (a, c), INSERT ON gtest12 TO regress_user11;
610610
SET ROLE regress_user11;
611611
SELECT a, b FROM gtest11; -- not allowed
612612
ERROR: permission denied for table gtest11
@@ -619,15 +619,12 @@ SELECT a, c FROM gtest11; -- allowed
619619

620620
SELECT gf1(10); -- not allowed
621621
ERROR: permission denied for function gf1
622-
INSERT INTO gtest12 VALUES (3, 30), (4, 40); -- allowed (does not actually invoke the function)
623-
SELECT a, c FROM gtest12; -- currently not allowed because of function permissions, should arguably be allowed
624-
ERROR: permission denied for function gf1
622+
--INSERT INTO gtest12 VALUES (3, 30), (4, 40); -- allowed (does not actually invoke the function)
623+
--SELECT a, c FROM gtest12; -- currently not allowed because of function permissions, should arguably be allowed
625624
RESET ROLE;
626-
DROP FUNCTION gf1(int); -- fail
627-
ERROR: cannot drop function gf1(integer) because other objects depend on it
628-
DETAIL: column c of table gtest12 depends on function gf1(integer)
629-
HINT: Use DROP ... CASCADE to drop the dependent objects too.
630-
DROP TABLE gtest11, gtest12;
625+
--DROP FUNCTION gf1(int); -- fail
626+
DROP TABLE gtest11;
627+
--DROP TABLE gtest12;
631628
DROP FUNCTION gf1(int);
632629
DROP USER regress_user11;
633630
-- check constraints
@@ -811,6 +808,12 @@ CREATE TABLE gtest24nn (a int, b gtestdomainnn GENERATED ALWAYS AS (a * 2) VIRTU
811808
ERROR: virtual generated column "b" cannot have a domain type
812809
--INSERT INTO gtest24nn (a) VALUES (4); -- ok
813810
--INSERT INTO gtest24nn (a) VALUES (NULL); -- error
811+
-- using user-defined type not yet supported
812+
CREATE TABLE gtest24xxx (a gtestdomain1, b gtestdomain1, c int GENERATED ALWAYS AS (greatest(a, b)) VIRTUAL); -- error
813+
ERROR: generation expression uses user-defined type
814+
LINE 1: ...main1, b gtestdomain1, c int GENERATED ALWAYS AS (greatest(a...
815+
^
816+
DETAIL: Virtual generated columns that make use of user-defined types are not yet supported.
814817
-- typed tables (currently not supported)
815818
CREATE TYPE gtest_type AS (f1 integer, f2 text, f3 bigint);
816819
CREATE TABLE gtest28 OF gtest_type (f1 WITH OPTIONS GENERATED ALWAYS AS (f2 *2) VIRTUAL);

src/test/regress/expected/publication.out

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -524,10 +524,16 @@ Tables from schemas:
524524
"testpub_rf_schema2"
525525

526526
-- fail - virtual generated column uses user-defined function
527+
-- (Actually, this already fails at CREATE TABLE rather than at CREATE
528+
-- PUBLICATION, but let's keep the test in case the former gets
529+
-- relaxed sometime.)
527530
CREATE TABLE testpub_rf_tbl6 (id int PRIMARY KEY, x int, y int GENERATED ALWAYS AS (x * testpub_rf_func2()) VIRTUAL);
531+
ERROR: generation expression uses user-defined function
532+
LINE 1: ...RIMARY KEY, x int, y int GENERATED ALWAYS AS (x * testpub_rf...
533+
^
534+
DETAIL: Virtual generated columns that make use of user-defined functions are not yet supported.
528535
CREATE PUBLICATION testpub7 FOR TABLE testpub_rf_tbl6 WHERE (y > 100);
529-
ERROR: invalid publication WHERE expression
530-
DETAIL: User-defined or built-in mutable functions are not allowed.
536+
ERROR: relation "testpub_rf_tbl6" does not exist
531537
-- test that SET EXPRESSION is rejected, because it could affect a row filter
532538
SET client_min_messages = 'ERROR';
533539
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;
541547
DROP TABLE testpub_rf_tbl3;
542548
DROP TABLE testpub_rf_tbl4;
543549
DROP TABLE testpub_rf_tbl5;
544-
DROP TABLE testpub_rf_tbl6;
550+
--DROP TABLE testpub_rf_tbl6;
545551
DROP TABLE testpub_rf_schema1.testpub_rf_tbl5;
546552
DROP TABLE testpub_rf_schema2.testpub_rf_tbl6;
547553
DROP SCHEMA testpub_rf_schema1;

src/test/regress/sql/generated_virtual.sql

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -253,10 +253,10 @@ CREATE TABLE gtest4 (
253253
a int,
254254
b double_int GENERATED ALWAYS AS ((a * 2, a * 3)) VIRTUAL
255255
);
256-
INSERT INTO gtest4 VALUES (1), (6);
257-
SELECT * FROM gtest4;
256+
--INSERT INTO gtest4 VALUES (1), (6);
257+
--SELECT * FROM gtest4;
258258

259-
DROP TABLE gtest4;
259+
--DROP TABLE gtest4;
260260
DROP TYPE double_int;
261261

262262
-- using tableoid is allowed
@@ -290,20 +290,21 @@ GRANT SELECT (a, c) ON gtest11 TO regress_user11;
290290
CREATE FUNCTION gf1(a int) RETURNS int AS $$ SELECT a * 3 $$ IMMUTABLE LANGUAGE SQL;
291291
REVOKE ALL ON FUNCTION gf1(int) FROM PUBLIC;
292292

293-
CREATE TABLE gtest12 (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (gf1(b)) VIRTUAL);
294-
INSERT INTO gtest12 VALUES (1, 10), (2, 20);
295-
GRANT SELECT (a, c), INSERT ON gtest12 TO regress_user11;
293+
CREATE TABLE gtest12 (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (gf1(b)) VIRTUAL); -- fails, user-defined function
294+
--INSERT INTO gtest12 VALUES (1, 10), (2, 20);
295+
--GRANT SELECT (a, c), INSERT ON gtest12 TO regress_user11;
296296

297297
SET ROLE regress_user11;
298298
SELECT a, b FROM gtest11; -- not allowed
299299
SELECT a, c FROM gtest11; -- allowed
300300
SELECT gf1(10); -- not allowed
301-
INSERT INTO gtest12 VALUES (3, 30), (4, 40); -- allowed (does not actually invoke the function)
302-
SELECT a, c FROM gtest12; -- currently not allowed because of function permissions, should arguably be allowed
301+
--INSERT INTO gtest12 VALUES (3, 30), (4, 40); -- allowed (does not actually invoke the function)
302+
--SELECT a, c FROM gtest12; -- currently not allowed because of function permissions, should arguably be allowed
303303
RESET ROLE;
304304

305-
DROP FUNCTION gf1(int); -- fail
306-
DROP TABLE gtest11, gtest12;
305+
--DROP FUNCTION gf1(int); -- fail
306+
DROP TABLE gtest11;
307+
--DROP TABLE gtest12;
307308
DROP FUNCTION gf1(int);
308309
DROP USER regress_user11;
309310

@@ -463,6 +464,9 @@ CREATE TABLE gtest24nn (a int, b gtestdomainnn GENERATED ALWAYS AS (a * 2) VIRTU
463464
--INSERT INTO gtest24nn (a) VALUES (4); -- ok
464465
--INSERT INTO gtest24nn (a) VALUES (NULL); -- error
465466

467+
-- using user-defined type not yet supported
468+
CREATE TABLE gtest24xxx (a gtestdomain1, b gtestdomain1, c int GENERATED ALWAYS AS (greatest(a, b)) VIRTUAL); -- error
469+
466470
-- typed tables (currently not supported)
467471
CREATE TYPE gtest_type AS (f1 integer, f2 text, f3 bigint);
468472
CREATE TABLE gtest28 OF gtest_type (f1 WITH OPTIONS GENERATED ALWAYS AS (f2 *2) VIRTUAL);

src/test/regress/sql/publication.sql

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,9 @@ ALTER PUBLICATION testpub6 SET TABLES IN SCHEMA testpub_rf_schema2, TABLE testpu
262262
RESET client_min_messages;
263263
\dRp+ testpub6
264264
-- fail - virtual generated column uses user-defined function
265+
-- (Actually, this already fails at CREATE TABLE rather than at CREATE
266+
-- PUBLICATION, but let's keep the test in case the former gets
267+
-- relaxed sometime.)
265268
CREATE TABLE testpub_rf_tbl6 (id int PRIMARY KEY, x int, y int GENERATED ALWAYS AS (x * testpub_rf_func2()) VIRTUAL);
266269
CREATE PUBLICATION testpub7 FOR TABLE testpub_rf_tbl6 WHERE (y > 100);
267270
-- test that SET EXPRESSION is rejected, because it could affect a row filter
@@ -276,7 +279,7 @@ DROP TABLE testpub_rf_tbl2;
276279
DROP TABLE testpub_rf_tbl3;
277280
DROP TABLE testpub_rf_tbl4;
278281
DROP TABLE testpub_rf_tbl5;
279-
DROP TABLE testpub_rf_tbl6;
282+
--DROP TABLE testpub_rf_tbl6;
280283
DROP TABLE testpub_rf_schema1.testpub_rf_tbl5;
281284
DROP TABLE testpub_rf_schema2.testpub_rf_tbl6;
282285
DROP SCHEMA testpub_rf_schema1;

0 commit comments

Comments
 (0)