From c98ad086ad9b1ca9dbb2725f246298fa8450d82f Mon Sep 17 00:00:00 2001 From: Robert Haas Date: Tue, 24 Mar 2026 08:58:50 -0400 Subject: [PATCH] Bounds-check access to TupleDescAttr with an Assert. The second argument to TupleDescAttr should always be at least zero and less than natts; otherwise, we index outside of the attribute array. Assert that this is the case. Various violations, or possible violations, of this rule that are currently in the tree are actually harmless, because while we do call TupleDescAttr() before verifying that the argument is within range, we don't actually dereference it unless the argument was within range all along. Nonetheless, the Assert means we should be more careful, so tidy up accordingly. Reviewed-by: Tom Lane Discussion: http://postgr.es/m/CA+TgmoacixUZVvi00hOjk_d9B4iYKswWP1gNqQ8Vfray-AcOCA@mail.gmail.com --- src/backend/access/common/tupdesc.c | 27 +++++++++++++++------------ src/include/access/tupdesc.h | 2 ++ src/pl/plperl/plperl.c | 7 +++++-- src/pl/plpgsql/src/pl_exec.c | 6 ++++-- 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c index d771a265b34..196472c05d0 100644 --- a/src/backend/access/common/tupdesc.c +++ b/src/backend/access/common/tupdesc.c @@ -246,10 +246,11 @@ CreateTupleDescCopy(TupleDesc tupdesc) desc = CreateTemplateTupleDesc(tupdesc->natts); - /* Flat-copy the attribute array */ - memcpy(TupleDescAttr(desc, 0), - TupleDescAttr(tupdesc, 0), - desc->natts * sizeof(FormData_pg_attribute)); + /* Flat-copy the attribute array (unless there are no attributes) */ + if (desc->natts > 0) + memcpy(TupleDescAttr(desc, 0), + TupleDescAttr(tupdesc, 0), + desc->natts * sizeof(FormData_pg_attribute)); /* * Since we're not copying constraints and defaults, clear fields @@ -294,10 +295,11 @@ CreateTupleDescTruncatedCopy(TupleDesc tupdesc, int natts) desc = CreateTemplateTupleDesc(natts); - /* Flat-copy the attribute array */ - memcpy(TupleDescAttr(desc, 0), - TupleDescAttr(tupdesc, 0), - desc->natts * sizeof(FormData_pg_attribute)); + /* Flat-copy the attribute array (unless there are no attributes) */ + if (desc->natts > 0) + memcpy(TupleDescAttr(desc, 0), + TupleDescAttr(tupdesc, 0), + desc->natts * sizeof(FormData_pg_attribute)); /* * Since we're not copying constraints and defaults, clear fields @@ -339,10 +341,11 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc) desc = CreateTemplateTupleDesc(tupdesc->natts); - /* Flat-copy the attribute array */ - memcpy(TupleDescAttr(desc, 0), - TupleDescAttr(tupdesc, 0), - desc->natts * sizeof(FormData_pg_attribute)); + /* Flat-copy the attribute array (unless there are no attributes) */ + if (desc->natts > 0) + memcpy(TupleDescAttr(desc, 0), + TupleDescAttr(tupdesc, 0), + desc->natts * sizeof(FormData_pg_attribute)); for (i = 0; i < desc->natts; i++) { diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h index 62ef6b38497..d26287271e9 100644 --- a/src/include/access/tupdesc.h +++ b/src/include/access/tupdesc.h @@ -179,6 +179,8 @@ TupleDescAttr(TupleDesc tupdesc, int i) { FormData_pg_attribute *attrs = TupleDescAttrAddress(tupdesc); + Assert(i >= 0 && i < tupdesc->natts); + return &attrs[i]; } diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c index c5f11b874c7..06ebffa111c 100644 --- a/src/pl/plperl/plperl.c +++ b/src/pl/plperl/plperl.c @@ -1093,7 +1093,7 @@ plperl_build_tuple_result(HV *perlhash, TupleDesc td) SV *val = HeVAL(he); char *key = hek2cstr(he); int attn = SPI_fnumber(td, key); - Form_pg_attribute attr = TupleDescAttr(td, attn - 1); + Form_pg_attribute attr; if (attn == SPI_ERROR_NOATTRIBUTE) ereport(ERROR, @@ -1106,6 +1106,7 @@ plperl_build_tuple_result(HV *perlhash, TupleDesc td) errmsg("cannot set system attribute \"%s\"", key))); + attr = TupleDescAttr(td, attn - 1); values[attn - 1] = plperl_sv_to_datum(val, attr->atttypid, attr->atttypmod, @@ -1799,7 +1800,7 @@ plperl_modify_tuple(HV *hvTD, TriggerData *tdata, HeapTuple otup) char *key = hek2cstr(he); SV *val = HeVAL(he); int attn = SPI_fnumber(tupdesc, key); - Form_pg_attribute attr = TupleDescAttr(tupdesc, attn - 1); + Form_pg_attribute attr; if (attn == SPI_ERROR_NOATTRIBUTE) ereport(ERROR, @@ -1811,6 +1812,8 @@ plperl_modify_tuple(HV *hvTD, TriggerData *tdata, HeapTuple otup) (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot set system attribute \"%s\"", key))); + + attr = TupleDescAttr(tupdesc, attn - 1); if (attr->attgenerated) ereport(ERROR, (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 45d667428f4..65b0fd0790f 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -3401,7 +3401,7 @@ exec_stmt_return_next(PLpgSQL_execstate *estate, PLpgSQL_var *var = (PLpgSQL_var *) retvar; Datum retval = var->value; bool isNull = var->isnull; - Form_pg_attribute attr = TupleDescAttr(tupdesc, 0); + Form_pg_attribute attr; if (natts != 1) ereport(ERROR, @@ -3414,6 +3414,7 @@ exec_stmt_return_next(PLpgSQL_execstate *estate, var->datatype->typlen); /* coerce type if needed */ + attr = TupleDescAttr(tupdesc, 0); retval = exec_cast_value(estate, retval, &isNull, @@ -3532,7 +3533,7 @@ exec_stmt_return_next(PLpgSQL_execstate *estate, } else { - Form_pg_attribute attr = TupleDescAttr(tupdesc, 0); + Form_pg_attribute attr; /* Simple scalar result */ if (natts != 1) @@ -3541,6 +3542,7 @@ exec_stmt_return_next(PLpgSQL_execstate *estate, errmsg("wrong result type supplied in RETURN NEXT"))); /* coerce type if needed */ + attr = TupleDescAttr(tupdesc, 0); retval = exec_cast_value(estate, retval, &isNull,