| 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 |
| 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 |