On Fri, Apr 17, 2026 at 1:09 AM SATYANARAYANA NARLAPURAM <[email protected]> wrote: > > HI hackers, > > domain_with_constraint[] was allocated with list_length(attnumlist) > elements and indexed sequentially via foreach_current_index(), but > copyfromparse.c accesses it via attnum - 1 (physical attribute index). > With a partial column list targeting high-numbered columns, this caused > an out-of-bounds read that bypassed domain NOT NULL checks, silently > inserting NULL into NOT NULL domain columns. > > Fix by allocating with num_phys_attrs and indexing by attnum - 1, > consistent with all other per-column arrays in BeginCopyFrom(). > > Patch is attached, and added a new test case to cover this scenario. >
The patch looks good to me. I simplified the tests. -- jian https://www.enterprisedb.com/
From df7ed84769070dcb4c47b8f90cbdf0f3acf4ddeb Mon Sep 17 00:00:00 2001 From: jian he <[email protected]> Date: Fri, 17 Apr 2026 10:19:15 +0800 Subject: [PATCH v2 1/1] Fix COPY FROM ON_ERROR SET_NULL with selective column list When using COPY FROM ... ON_ERROR SET_NULL with a selective column list, the domain_with_constraint array was incorrectly allocated based on the length of the target column list. While the array was populated sequentially, CopyFromTextLikeOneRow attempted to access it using the physical attribute index (attnum - 1). This mismatch caused out-of-bounds reads when targeting high-numbered columns, allowing NULL values to bypass NOT NULL domain checks and be silently inserted. Fix by allocating the array to match the total number of physical attributes (num_phys_attrs) and indexing via attnum - 1, bringing it into alignment with other per-column arrays in BeginCopyFrom. Reported-by: SATYANARAYANA NARLAPURAM <[email protected]> Author: SATYANARAYANA NARLAPURAM <[email protected]> Reviewed-by: jian he <[email protected]> Discussion: https://postgr.es/m/cahg+qddej0c0gwji2fnbirzhgzyznpitwc1p5b_-dsnczq-...@mail.gmail.com --- src/backend/commands/copyfrom.c | 8 ++------ src/test/regress/expected/copy2.out | 4 ++++ src/test/regress/sql/copy2.sql | 4 ++++ 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c index 64ac3063c61..0087585b2c4 100644 --- a/src/backend/commands/copyfrom.c +++ b/src/backend/commands/copyfrom.c @@ -1636,8 +1636,6 @@ BeginCopyFrom(ParseState *pstate, if (cstate->opts.on_error == COPY_ON_ERROR_SET_NULL) { - int attr_count = list_length(cstate->attnumlist); - /* * When data type conversion fails and ON_ERROR is SET_NULL, we need * ensure that the input column allow null values. ExecConstraints() @@ -1646,15 +1644,13 @@ BeginCopyFrom(ParseState *pstate, * check must be performed during the initial string-to-datum * conversion (see CopyFromTextLikeOneRow()). */ - cstate->domain_with_constraint = palloc0_array(bool, attr_count); + cstate->domain_with_constraint = palloc0_array(bool, num_phys_attrs); foreach_int(attno, cstate->attnumlist) { - int i = foreach_current_index(attno); - Form_pg_attribute att = TupleDescAttr(tupDesc, attno - 1); - cstate->domain_with_constraint[i] = DomainHasConstraints(att->atttypid, NULL); + cstate->domain_with_constraint[attno - 1] = DomainHasConstraints(att->atttypid, NULL); } } diff --git a/src/test/regress/expected/copy2.out b/src/test/regress/expected/copy2.out index 7600e5239d2..919eabd5f78 100644 --- a/src/test/regress/expected/copy2.out +++ b/src/test/regress/expected/copy2.out @@ -805,6 +805,10 @@ COPY t_on_error_null FROM STDIN WITH (on_error set_null); -- fail ERROR: domain d_int_not_null does not allow null values DETAIL: ON_ERROR SET_NULL cannot be applied because column "a" (domain d_int_not_null) does not accept null values. CONTEXT: COPY t_on_error_null, line 1, column a: null input +COPY t_on_error_null(c, a) FROM STDIN WITH (on_error set_null); -- fail +ERROR: domain d_int_not_null does not allow null values +DETAIL: ON_ERROR SET_NULL cannot be applied because column "a" (domain d_int_not_null) does not accept null values. +CONTEXT: COPY t_on_error_null, line 1, column a: null input COPY t_on_error_null FROM STDIN WITH (on_error set_null); -- fail ERROR: domain d_int_not_null does not allow null values DETAIL: ON_ERROR SET_NULL cannot be applied because column "a" (domain d_int_not_null) does not accept null values. diff --git a/src/test/regress/sql/copy2.sql b/src/test/regress/sql/copy2.sql index e0810109473..f853499021d 100644 --- a/src/test/regress/sql/copy2.sql +++ b/src/test/regress/sql/copy2.sql @@ -555,6 +555,10 @@ COPY t_on_error_null FROM STDIN WITH (on_error set_null); -- fail \N 11 13 \. +COPY t_on_error_null(c, a) FROM STDIN WITH (on_error set_null); -- fail +11 \N +\. + COPY t_on_error_null FROM STDIN WITH (on_error set_null); -- fail ss 11 14 \. -- 2.34.1
