[Bug] Heap use-after-free in COPY FROM error context after ON_ERROR ignore skips a row

From: Gowdham M <gowdham(dot)m(at)zohocorp(dot)com>
To: "pgsql-bugs" <pgsql-bugs(at)lists(dot)postgresql(dot)org>
Cc: "zlabs-cstore(at)zohocorp(dot)com" <zlabs-cstore(at)zohocorp(dot)com>
Subject: [Bug] Heap use-after-free in COPY FROM error context after ON_ERROR ignore skips a row
Date: 2026-10-28 10:12:42
Message-ID: 1a181ff6be8.6d14feb261368.4036709598649280477@zohocorp.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-bugs

Hello Developers,

We have discovered a heap use-after-free in COPY FROM ... WITH (ON_ERROR

ignore). It was introduced in PG v17 together with the ON_ERROR option, and it

is still present in v18, v19 and master.

What's the issue?

When ON_ERROR ignore skips a row because a column value cannot be converted,

NextCopyFrom() (CopyFromTextLikeOneRow() in v18 and later) returns from its

per-column loop without clearing cstate->cur_attname and cstate->cur_attval.

These are only cleared at the end of each loop iteration, which this early

return skips, and cur_attval normally points into cstate->attribute_buf.

When the next line is read, CopyReadAttributesText() / CopyReadAttributesCSV()

reset attribute_buf and, if the line does not fit in it (1024 bytes initially),

enlarge it with repalloc(), which may move it and free the old buffer. If that

line then fails before any of its columns is converted, for example with "extra

data after last expected column", CopyFromErrorCallback() still sees the stale

cur_attname/cur_attval and calls CopyLimitPrintoutLength() on the stale

pointer, which reads freed memory.

If the buffer is not enlarged, the pointer points into the reused buffer, and

the error is reported with a wrong CONTEXT that names a column of the skipped

row and shows a piece of the new line.

Steps to reproduce:

    CREATE TABLE copy_uaf_repro (a int, b int);

    \! python3 -c "print('1\tx'); print('1\t2\t' + 'z' * 1100)" > /tmp/copy_uaf_repro.txt

    COPY copy_uaf_repro FROM '/tmp/copy_uaf_repro.txt' WITH (FORMAT text, ON_ERROR ignore);

Row 1 is skipped because 'x' is not a valid integer. Row 2 is longer than 1024

bytes and has an extra column. With our malloc-based allocator (see below) and

AddressSanitizer, the backend aborts with a heap-use-after-free in

CopyLimitPrintoutLength() called from CopyFromErrorCallback(); the memory was

freed by repalloc() in enlargeStringInfo() called from

CopyReadAttributesText(). The full ASan report is attached

(crash_back_trace.txt). It was captured on our PG 17.11 based build; the

CStoreProcessUtility / CallPreviousProcessUtility frames belong to our

extension's ProcessUtility hook and are not involved.

With the default allocator the freed chunk stays readable, so instead of a

crash the error is reported with a wrong or garbled CONTEXT. A short row shows

the wrong context deterministically on any build:

    CREATE TABLE t (a int, b int);

    COPY t FROM STDIN WITH (ON_ERROR ignore);

    1 x

    1 2 3

    \.

    ERROR:  extra data after last expected column

    CONTEXT:  COPY t, line 2, column b: "2"

The expected CONTEXT is:  COPY t, line 2: "1 2 3"

Fix:

The attached patch clears cur_attname and cur_attval in NextCopyFromRawFields()

(NextCopyFromRawFieldsInternal() in v18 and later) before each new line is

read, that is, before attribute_buf can be reset or enlarged.

The patch also adds a regression test to copy2.sql that fails without the fix

(it reports line 2, column k: "2") and passes with it.

How was the bug detected?

We found this using our custom malloc-based memory context, which allocates

every chunk with its own malloc() call, without rounding up the requested size,

and releases it with free() on pfree() and context reset, instead of keeping it

in AllocSet freelists. This lets AddressSanitizer catch use-after-free bugs

that AllocSet hides. We shared this allocator earlier as malloc_allocator.patch

[1]. It has already helped find three other bugs, which have been fixed

[2][3][4]. That patch no longer applies to the current master; if there is

interest, we can send an updated version of it.

[1] https://www.postgresql.org/message-id/193261e2c4d.3dd3cd7c1842.871636075166132237%40zohocorp.com

[2] 1fe5a347e3 Fix possible crash during WindowAgg evaluation

[3] ccc8194e42 Fix use-after-free in parallel_vacuum_reset_dead_items

[4] 54ab748651 Fix reuse-after-free hazard in dead_items_reset

Please let me know if you have any questions or would like further details.

Thanks & Regards,

Gowdham M

Member Technical Staff

ZOHO Corporation

Attachment Content-Type Size
copy_onerror_fix.patch application/octet-stream 2.4 KB
crash_back_trace.txt text/plain 11.1 KB

Browse pgsql-bugs by date

  From Date Subject
Next Message Amit Langote 2026-10-28 10:39:45 Re: BUG #19786: JSON_TABLE: subquery in PASSING gives "unexpected non-SELECT command in SubLink"
Previous Message Ishjaap Singh 2026-10-28 09:08:52 Re: BUG #19600: pgcrypto crypt() can return text with invalid encoding