refint: Fix SQL injection and buffer overruns.

Maliciously crafted key value updates could achieve SQL injection
within check_foreign_key().  To fix, ensure new key values are
properly quoted and escaped in the internally generated SQL
statements.  While at it, avoid potential buffer overruns by
replacing the stack buffers for internally generated SQL statements
with StringInfo.

Reported-by: Nikolay Samokhvalov <nik@postgres.ai>
Author: Nathan Bossart <nathandbossart@gmail.com>
Reviewed-by: Noah Misch <noah@leadboat.com>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: Fujii Masao <masao.fujii@gmail.com>
Security: CVE-2026-6637
Backpatch-through: 14
This commit is contained in:
Nathan Bossart 2026-05-11 05:13:47 -07:00 committed by Noah Misch
parent bd48114937
commit 260e97733b

View file

@ -171,21 +171,24 @@ check_primary_key(PG_FUNCTION_ARGS)
if (plan->nplans <= 0) if (plan->nplans <= 0)
{ {
SPIPlanPtr pplan; SPIPlanPtr pplan;
char sql[8192]; StringInfoData sql;
initStringInfo(&sql);
/* /*
* Construct query: SELECT 1 FROM _referenced_relation_ WHERE Pkey1 = * Construct query: SELECT 1 FROM _referenced_relation_ WHERE Pkey1 =
* $1 [AND Pkey2 = $2 [...]] * $1 [AND Pkey2 = $2 [...]]
*/ */
snprintf(sql, sizeof(sql), "select 1 from %s where ", relname); appendStringInfo(&sql, "select 1 from %s where ", relname);
for (i = 0; i < nkeys; i++) for (i = 1; i <= nkeys; i++)
{ {
snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s = $%d %s", appendStringInfo(&sql, "%s = $%d ", args[i + nkeys], i);
args[i + nkeys + 1], i + 1, (i < nkeys - 1) ? "and " : ""); if (i < nkeys)
appendStringInfoString(&sql, "and ");
} }
/* Prepare plan for query */ /* Prepare plan for query */
pplan = SPI_prepare(sql, nkeys, argtypes); pplan = SPI_prepare(sql.data, nkeys, argtypes);
if (pplan == NULL) if (pplan == NULL)
/* internal error */ /* internal error */
elog(ERROR, "check_primary_key: SPI_prepare returned %s", SPI_result_code_string(SPI_result)); elog(ERROR, "check_primary_key: SPI_prepare returned %s", SPI_result_code_string(SPI_result));
@ -201,6 +204,8 @@ check_primary_key(PG_FUNCTION_ARGS)
sizeof(SPIPlanPtr)); sizeof(SPIPlanPtr));
*(plan->splan) = pplan; *(plan->splan) = pplan;
plan->nplans = 1; plan->nplans = 1;
pfree(sql.data);
} }
/* /*
@ -423,7 +428,6 @@ check_foreign_key(PG_FUNCTION_ARGS)
if (plan->nplans <= 0) if (plan->nplans <= 0)
{ {
SPIPlanPtr pplan; SPIPlanPtr pplan;
char sql[8192];
char **args2 = args; char **args2 = args;
plan->splan = (SPIPlanPtr *) MemoryContextAlloc(TopMemoryContext, plan->splan = (SPIPlanPtr *) MemoryContextAlloc(TopMemoryContext,
@ -431,6 +435,10 @@ check_foreign_key(PG_FUNCTION_ARGS)
for (r = 0; r < nrefs; r++) for (r = 0; r < nrefs; r++)
{ {
StringInfoData sql;
initStringInfo(&sql);
relname = args2[0]; relname = args2[0];
/*--------- /*---------
@ -444,8 +452,7 @@ check_foreign_key(PG_FUNCTION_ARGS)
*--------- *---------
*/ */
if (action == 'r') if (action == 'r')
appendStringInfo(&sql, "select 1 from %s where ", relname);
snprintf(sql, sizeof(sql), "select 1 from %s where ", relname);
/*--------- /*---------
* For 'C'ascade action we construct DELETE query * For 'C'ascade action we construct DELETE query
@ -472,42 +479,23 @@ check_foreign_key(PG_FUNCTION_ARGS)
char *nv; char *nv;
int k; int k;
snprintf(sql, sizeof(sql), "update %s set ", relname); appendStringInfo(&sql, "update %s set ", relname);
for (k = 1; k <= nkeys; k++) for (k = 1; k <= nkeys; k++)
{ {
int is_char_type = 0;
char *type;
fn = SPI_fnumber(tupdesc, args_temp[k - 1]); fn = SPI_fnumber(tupdesc, args_temp[k - 1]);
Assert(fn > 0); /* already checked above */ Assert(fn > 0); /* already checked above */
nv = SPI_getvalue(newtuple, tupdesc, fn); nv = SPI_getvalue(newtuple, tupdesc, fn);
type = SPI_gettype(tupdesc, fn);
if (strcmp(type, "text") == 0 || appendStringInfo(&sql, " %s = %s ",
strcmp(type, "varchar") == 0 || args2[k], quote_literal_cstr(nv));
strcmp(type, "char") == 0 || if (k < nkeys)
strcmp(type, "bpchar") == 0 || appendStringInfoString(&sql, ", ");
strcmp(type, "date") == 0 ||
strcmp(type, "timestamp") == 0)
is_char_type = 1;
#ifdef DEBUG_QUERY
elog(DEBUG4, "check_foreign_key Debug value %s type %s %d",
nv, type, is_char_type);
#endif
/*
* is_char_type =1 i set ' ' for define a new value
*/
snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql),
" %s = %s%s%s %s ",
args2[k], (is_char_type > 0) ? "'" : "",
nv, (is_char_type > 0) ? "'" : "", (k < nkeys) ? ", " : "");
} }
strcat(sql, " where "); appendStringInfoString(&sql, " where ");
} }
else else
/* DELETE */ /* DELETE */
snprintf(sql, sizeof(sql), "delete from %s where ", relname); appendStringInfo(&sql, "delete from %s where ", relname);
} }
/* /*
@ -518,25 +506,26 @@ check_foreign_key(PG_FUNCTION_ARGS)
*/ */
else if (action == 's') else if (action == 's')
{ {
snprintf(sql, sizeof(sql), "update %s set ", relname); appendStringInfo(&sql, "update %s set ", relname);
for (i = 1; i <= nkeys; i++) for (i = 1; i <= nkeys; i++)
{ {
snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), appendStringInfo(&sql, "%s = null", args2[i]);
"%s = null%s", if (i < nkeys)
args2[i], (i < nkeys) ? ", " : ""); appendStringInfoString(&sql, ", ");
} }
strcat(sql, " where "); appendStringInfoString(&sql, " where ");
} }
/* Construct WHERE qual */ /* Construct WHERE qual */
for (i = 1; i <= nkeys; i++) for (i = 1; i <= nkeys; i++)
{ {
snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s = $%d %s", appendStringInfo(&sql, "%s = $%d ", args2[i], i);
args2[i], i, (i < nkeys) ? "and " : ""); if (i < nkeys)
appendStringInfoString(&sql, "and ");
} }
/* Prepare plan for query */ /* Prepare plan for query */
pplan = SPI_prepare(sql, nkeys, argtypes); pplan = SPI_prepare(sql.data, nkeys, argtypes);
if (pplan == NULL) if (pplan == NULL)
/* internal error */ /* internal error */
elog(ERROR, "check_foreign_key: SPI_prepare returned %s", SPI_result_code_string(SPI_result)); elog(ERROR, "check_foreign_key: SPI_prepare returned %s", SPI_result_code_string(SPI_result));
@ -552,11 +541,14 @@ check_foreign_key(PG_FUNCTION_ARGS)
plan->splan[r] = pplan; plan->splan[r] = pplan;
args2 += nkeys + 1; /* to the next relation */ args2 += nkeys + 1; /* to the next relation */
#ifdef DEBUG_QUERY
elog(DEBUG4, "check_foreign_key Debug Query is : %s ", sql.data);
#endif
pfree(sql.data);
} }
plan->nplans = nrefs; plan->nplans = nrefs;
#ifdef DEBUG_QUERY
elog(DEBUG4, "check_foreign_key Debug Query is : %s ", sql);
#endif
} }
/* /*