Fix handling of R/W expanded datums that are passed to SQL functions.
authorTom Lane <[email protected]>
Wed, 10 Aug 2022 17:37:25 +0000 (13:37 -0400)
committerTom Lane <[email protected]>
Wed, 10 Aug 2022 17:37:25 +0000 (13:37 -0400)
fmgr_sql must make expanded-datum arguments read-only, because
it's possible that the function body will pass the argument to
more than one callee function.  If one of those functions takes
the datum's R/W property as license to scribble on it, then later
callees will see an unexpected value, leading to wrong answers.

From a performance standpoint, it'd be nice to skip this in the
common case that the argument value is passed to only one callee.
However, detecting that seems fairly hard, and certainly not
something that I care to attempt in a back-patched bug fix.

Per report from Adam Mackler.  This has been broken since we
invented expanded datums, so back-patch to all supported branches.

Discussion: https://postgr.es/m/WScDU5qfoZ7PB2gXwNqwGGgDPmWzz08VdydcPFLhOwUKZcdWbblbo-0Lku-qhuEiZoXJ82jpiQU4hOjOcrevYEDeoAvz6nR0IU4IHhXnaCA=@mackler.email
Discussion: https://postgr.es/m/187436.1660143060@sss.pgh.pa.us

src/backend/executor/functions.c
src/test/regress/expected/create_function_3.out
src/test/regress/sql/create_function_3.sql

index 3630f5d96689d7dd1168596617c58ccee60d14ac..22c167574bedf45141eb488e0e4f481c6b6f821a 100644 (file)
@@ -902,6 +902,7 @@ postquel_sub_params(SQLFunctionCachePtr fcache,
    if (nargs > 0)
    {
        ParamListInfo paramLI;
+       Oid        *argtypes = fcache->pinfo->argtypes;
        int         i;
 
        if (fcache->paramLI == NULL)
@@ -928,10 +929,24 @@ postquel_sub_params(SQLFunctionCachePtr fcache,
        {
            ParamExternData *prm = &paramLI->params[i];
 
-           prm->value = fcinfo->arg[i];
+           /*
+            * If an incoming parameter value is a R/W expanded datum, we
+            * force it to R/O.  We'd be perfectly entitled to scribble on it,
+            * but the problem is that if the parameter is referenced more
+            * than once in the function, earlier references might mutate the
+            * value seen by later references, which won't do at all.  We
+            * could do better if we could be sure of the number of Param
+            * nodes in the function's plans; but we might not have planned
+            * all the statements yet, nor do we have plan tree walker
+            * infrastructure.  (Examining the parse trees is not good enough,
+            * because of possible function inlining during planning.)
+            */
            prm->isnull = fcinfo->argnull[i];
+           prm->value = MakeExpandedObjectReadOnly(fcinfo->arg[i],
+                                                   prm->isnull,
+                                                   get_typlen(argtypes[i]));
            prm->pflags = 0;
-           prm->ptype = fcache->pinfo->argtypes[i];
+           prm->ptype = argtypes[i];
        }
    }
    else
index b5e19485e56e769394be7356419599f046df62f2..e43aebb148a15f765cf2a9d1ed4475bac8722e70 100644 (file)
@@ -227,9 +227,25 @@ ERROR:  could not find a function named "functest_b_1"
 DROP FUNCTION functest_b_2;  -- error, ambiguous
 ERROR:  function name "functest_b_2" is not unique
 HINT:  Specify the argument list to select the function unambiguously.
--- Cleanups
+-- Regression tests for bugs:
+-- Check that arguments that are R/W expanded datums aren't corrupted by
+-- multiple uses.  This test knows that array_append() returns a R/W datum
+-- and will modify a R/W array input in-place.  We use SETOF to prevent
+-- inlining of the SQL function.
+CREATE FUNCTION double_append(anyarray, anyelement) RETURNS SETOF anyarray
+LANGUAGE SQL IMMUTABLE AS
+$$ SELECT array_append($1, $2) || array_append($1, $2) $$;
+SELECT double_append(array_append(ARRAY[q1], q2), q3)
+  FROM (VALUES(1,2,3), (4,5,6)) v(q1,q2,q3);
+ double_append 
+---------------
+ {1,2,3,1,2,3}
+ {4,5,6,4,5,6}
+(2 rows)
+
+-- Cleanup
 DROP SCHEMA temp_func_test CASCADE;
-NOTICE:  drop cascades to 16 other objects
+NOTICE:  drop cascades to 17 other objects
 DETAIL:  drop cascades to function functest_a_1(text,date)
 drop cascades to function functest_a_2(text[])
 drop cascades to function functest_a_3()
@@ -246,5 +262,6 @@ drop cascades to function functext_f_2(integer)
 drop cascades to function functext_f_3(integer)
 drop cascades to function functext_f_4(integer)
 drop cascades to function functest_b_2(bigint)
+drop cascades to function double_append(anyarray,anyelement)
 DROP USER regress_unpriv_user;
 RESET search_path;
index 0a0e407aaba692948798d46b168e647e24ab36a3..645102b178c6ee20dd4e5741ed3bde26e8c518a7 100644 (file)
@@ -166,8 +166,20 @@ DROP FUNCTION functest_b_1;
 DROP FUNCTION functest_b_1;  -- error, not found
 DROP FUNCTION functest_b_2;  -- error, ambiguous
 
+-- Regression tests for bugs:
 
--- Cleanups
+-- Check that arguments that are R/W expanded datums aren't corrupted by
+-- multiple uses.  This test knows that array_append() returns a R/W datum
+-- and will modify a R/W array input in-place.  We use SETOF to prevent
+-- inlining of the SQL function.
+CREATE FUNCTION double_append(anyarray, anyelement) RETURNS SETOF anyarray
+LANGUAGE SQL IMMUTABLE AS
+$$ SELECT array_append($1, $2) || array_append($1, $2) $$;
+
+SELECT double_append(array_append(ARRAY[q1], q2), q3)
+  FROM (VALUES(1,2,3), (4,5,6)) v(q1,q2,q3);
+
+-- Cleanup
 DROP SCHEMA temp_func_test CASCADE;
 DROP USER regress_unpriv_user;
 RESET search_path;