From bf15cdc8dc96af8c56f7a4e3ba0c49acf50f480f Mon Sep 17 00:00:00 2001 From: Robert Haas Date: Fri, 20 Mar 2026 11:27:05 -0400 Subject: [PATCH v1 2/3] 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. --- 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 84552e32c87..a3869072093 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -3369,7 +3369,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, @@ -3382,6 +3382,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, @@ -3500,7 +3501,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) @@ -3509,6 +3510,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, -- 2.51.0