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

Started by Gowdham M7 days ago3 messagesbugs
Jump to latest
#1Gowdham M
gowdham.m@zohocorp.com

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]: /messages/by-id/193261e2c4d.3dd3cd7c1842.871636075166132237@zohocorp.com

[2]: 1fe5a347e3 Fix possible crash during WindowAgg evaluation

interest, we can send an updated version of it.

[1]: /messages/by-id/193261e2c4d.3dd3cd7c1842.871636075166132237@zohocorp.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

Attachments:

t254425_1
copy_onerror_fix.patchapplication/octet-stream; name=copy_onerror_fix.patchDownload+18-1
crash_back_trace.txttext/plain; name=crash_back_trace.txtDownload
#2shihao zhong
zhong950419@gmail.com
In reply to: Gowdham M (#1)
Re: [Bug] Heap use-after-free in COPY FROM error context after ON_ERROR ignore skips a row

Hi Gowdham,

Thanks for the report and the patch. I can reproduce it on 17 through
master. It also crashes a normal release build on Linux, if the next
row is a few MB long.

I think ON_ERROR set_null in 19 hits the same bug, the attached test
is a repro for it.

Thanks,
Shihao

Attachments:

nocfbot-0002-Test-stale-COPY-error-context-after-ON_ERROR-set_nul.txttext/plain; charset=US-ASCII; name=nocfbot-0002-Test-stale-COPY-error-context-after-ON_ERROR-set_nul.txtDownload+9-1
#3Gowdham M
gowdham.m@zohocorp.com
In reply to: shihao zhong (#2)
Re: [Bug] Heap use-after-free in COPY FROM error context after ON_ERROR ignore skips a row

Hi Shihao,

Thanks for testing, and for confirming the release-build crash.

Yes, set_null hits the same stale pointer, and the patch fixes it, since the

fields are cleared before each new line is read. Your test passes with the

patch on REL_19_STABLE and master, and fails without it, so it can go on top

of the patch for 19 and master.

Thanks & Regards,

Gowdham M

From: shihao zhong <zhong950419@gmail.com>
To: "Gowdham M"<gowdham.m@zohocorp.com>
Cc: "pgsql-bugs"<pgsql-bugs@lists.postgresql.org>, "zlabs-cstore@zohocorp.com"<zlabs-cstore@zohocorp.com>
Date: Tue, 03 Nov 2026 09:10:47 +0530
Subject: Re: [Bug] Heap use-after-free in COPY FROM error context after ON_ERROR ignore skips a row

Hi Gowdham,

Thanks for the report and the patch. I can reproduce it on 17 through
master. It also crashes a normal release build on Linux, if the next
row is a few MB long.

I think ON_ERROR set_null in 19 hits the same bug, the attached test

is a repro for it.

Thanks,

Shihao