Skip to content

Commit 85189f5

Browse files
committed
Fix corner-case uninitialized-variable issues in plpgsql.
If an error was raised during our initial attempt to check whether a successfully-compiled expression is "simple", subsequent calls of exec_stmt_execsql would suppose that stmt->mod_stmt was already computed when it had not been. This could lead to assertion failures in debug builds; in production builds the effect would typically be to act as if INTO STRICT had been specified even when it had not been. Of course that only matters if the subsequent attempt to execute the expression succeeds, so that the problem can only be reached by fixing a failure in some referenced, inline-able SQL function and then retrying the calling plpgsql function in the same session. (There might be even-more-obscure ways to change the expression's behavior without changing the plpgsql function, but that one seems like the only one people would be likely to hit in practice.) The most foolproof way to fix this would be to arrange for exec_prepare_plan to not set expr->plan until we've finished the subsidiary simple-expression check. But it seems hard to do that without creating reference-count leak issues. So settle for documenting the hazard in a comment and fixing exec_stmt_execsql to test separately for whether it's computed stmt->mod_stmt. (That adds a test-and-branch per execution, but hopefully that's negligible in context.) In v11 and up, also fix exec_stmt_call which had a variant of the same issue. Per bug #17113 from Alexander Lakhin. Back-patch to all supported branches. Discussion: https://postgr.es/m/17113-077605ce00e0e7ec@postgresql.org
1 parent b9a0de1 commit 85189f5

File tree

3 files changed

+57
-25
lines changed

3 files changed

+57
-25
lines changed

src/pl/plpgsql/src/pl_exec.c

Lines changed: 54 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2160,31 +2160,31 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt)
21602160
*/
21612161
if (plan == NULL || local_plan)
21622162
{
2163-
/* Don't let SPI save the plan if it's going to be local */
2164-
exec_prepare_plan(estate, expr, 0, !local_plan);
2165-
plan = expr->plan;
2166-
2167-
/*
2168-
* A CALL or DO can never be a simple expression. (If it could
2169-
* be, we'd have to worry about saving/restoring the previous
2170-
* values of the related expr fields, not just expr->plan.)
2171-
*/
2172-
Assert(!expr->expr_simple_expr);
2173-
2174-
/*
2175-
* Tell SPI to allow non-atomic execution. (The field name is a
2176-
* legacy choice.)
2177-
*/
2178-
plan->no_snapshots = true;
2179-
21802163
/*
21812164
* Force target to be recalculated whenever the plan changes, in
21822165
* case the procedure's argument list has changed.
21832166
*/
21842167
stmt->target = NULL;
21852168
cur_target = NULL;
2169+
2170+
/* Don't let SPI save the plan if it's going to be local */
2171+
exec_prepare_plan(estate, expr, 0, !local_plan);
2172+
plan = expr->plan;
21862173
}
21872174

2175+
/*
2176+
* A CALL or DO can never be a simple expression. (If it could be,
2177+
* we'd have to worry about saving/restoring the previous values of
2178+
* the related expr fields, not just expr->plan.)
2179+
*/
2180+
Assert(!expr->expr_simple_expr);
2181+
2182+
/*
2183+
* Tell SPI to allow non-atomic execution. (The field name is a
2184+
* legacy choice.)
2185+
*/
2186+
plan->no_snapshots = true;
2187+
21882188
/*
21892189
* We construct a DTYPE_ROW datum representing the plpgsql variables
21902190
* associated with the procedure's output arguments. Then we can use
@@ -4070,6 +4070,22 @@ exec_eval_cleanup(PLpgSQL_execstate *estate)
40704070

40714071
/* ----------
40724072
* Generate a prepared plan
4073+
*
4074+
* CAUTION: it is possible for this function to throw an error after it has
4075+
* built a SPIPlan and saved it in expr->plan. Therefore, be wary of doing
4076+
* additional things contingent on expr->plan being NULL. That is, given
4077+
* code like
4078+
*
4079+
* if (query->plan == NULL)
4080+
* {
4081+
* // okay to put setup code here
4082+
* exec_prepare_plan(estate, query, ...);
4083+
* // NOT okay to put more logic here
4084+
* }
4085+
*
4086+
* extra steps at the end are unsafe because they will not be executed when
4087+
* re-executing the calling statement, if exec_prepare_plan failed the first
4088+
* time. This is annoyingly error-prone, but the alternatives are worse.
40734089
* ----------
40744090
*/
40754091
static void
@@ -4099,15 +4115,15 @@ exec_prepare_plan(PLpgSQL_execstate *estate,
40994115
SPI_keepplan(plan);
41004116
expr->plan = plan;
41014117

4102-
/* Check to see if it's a simple expression */
4103-
exec_simple_check_plan(estate, expr);
4104-
41054118
/*
41064119
* Mark expression as not using a read-write param. exec_assign_value has
41074120
* to take steps to override this if appropriate; that seems cleaner than
41084121
* adding parameters to all other callers.
41094122
*/
41104123
expr->rwparam = -1;
4124+
4125+
/* Check to see if it's a simple expression */
4126+
exec_simple_check_plan(estate, expr);
41114127
}
41124128

41134129

@@ -4138,10 +4154,12 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
41384154
* whether the statement is INSERT/UPDATE/DELETE
41394155
*/
41404156
if (expr->plan == NULL)
4157+
exec_prepare_plan(estate, expr, CURSOR_OPT_PARALLEL_OK, true);
4158+
4159+
if (!stmt->mod_stmt_set)
41414160
{
41424161
ListCell *l;
41434162

4144-
exec_prepare_plan(estate, expr, CURSOR_OPT_PARALLEL_OK, true);
41454163
stmt->mod_stmt = false;
41464164
foreach(l, SPI_plan_get_plan_sources(expr->plan))
41474165
{
@@ -4162,6 +4180,7 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
41624180
break;
41634181
}
41644182
}
4183+
stmt->mod_stmt_set = true;
41654184
}
41664185

41674186
/*
@@ -4943,6 +4962,14 @@ exec_assign_expr(PLpgSQL_execstate *estate, PLpgSQL_datum *target,
49434962
* if we can pass the target variable as a read-write parameter to the
49444963
* expression. (This is a bit messy, but it seems cleaner than modifying
49454964
* the API of exec_eval_expr for the purpose.)
4965+
*
4966+
* NOTE: this coding ignores the advice given in exec_prepare_plan's
4967+
* comments, that one should not do additional setup contingent on
4968+
* expr->plan being NULL. This means that if we get an error while trying
4969+
* to check for the expression being simple, we won't check for a
4970+
* read-write parameter either, so that neither optimization will be
4971+
* applied in future executions. Nothing will fail though, so we live
4972+
* with that bit of messiness too.
49464973
*/
49474974
if (expr->plan == NULL)
49484975
{
@@ -7956,6 +7983,10 @@ get_cast_hashentry(PLpgSQL_execstate *estate,
79567983
* exec_simple_check_plan - Check if a plan is simple enough to
79577984
* be evaluated by ExecEvalExpr() instead
79587985
* of SPI.
7986+
*
7987+
* Note: the refcount manipulations in this function assume that expr->plan
7988+
* is a "saved" SPI plan. That's a bit annoying from the caller's standpoint,
7989+
* but it's otherwise difficult to avoid leaking the plan on failure.
79597990
* ----------
79607991
*/
79617992
static void
@@ -8038,7 +8069,8 @@ exec_simple_check_plan(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
80388069
* OK, we can treat it as a simple plan.
80398070
*
80408071
* Get the generic plan for the query. If replanning is needed, do that
8041-
* work in the eval_mcontext.
8072+
* work in the eval_mcontext. (Note that replanning could throw an error,
8073+
* in which case the expr is left marked "not simple", which is fine.)
80428074
*/
80438075
oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
80448076
cplan = SPI_plan_get_cached_plan(expr->plan);

src/pl/plpgsql/src/pl_gram.y

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3040,7 +3040,7 @@ make_execsql_stmt(int firsttoken, int location)
30403040

30413041
check_sql_expr(expr->query, location, 0);
30423042

3043-
execsql = palloc(sizeof(PLpgSQL_stmt_execsql));
3043+
execsql = palloc0(sizeof(PLpgSQL_stmt_execsql));
30443044
execsql->cmd_type = PLPGSQL_STMT_EXECSQL;
30453045
execsql->lineno = plpgsql_location_to_lineno(location);
30463046
execsql->stmtid = ++plpgsql_curr_compile->nstatements;

src/pl/plpgsql/src/plpgsql.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -909,10 +909,10 @@ typedef struct PLpgSQL_stmt_execsql
909909
int lineno;
910910
unsigned int stmtid;
911911
PLpgSQL_expr *sqlstmt;
912-
bool mod_stmt; /* is the stmt INSERT/UPDATE/DELETE? Note:
913-
* mod_stmt is set when we plan the query */
912+
bool mod_stmt; /* is the stmt INSERT/UPDATE/DELETE? */
914913
bool into; /* INTO supplied? */
915914
bool strict; /* INTO STRICT flag */
915+
bool mod_stmt_set; /* is mod_stmt valid yet? */
916916
PLpgSQL_variable *target; /* INTO target (record or row) */
917917
} PLpgSQL_stmt_execsql;
918918

0 commit comments

Comments
 (0)