BUG #19452: heap-buffer-overflow in `pg_basebackup --format=tar --gzip`
The following bug has been logged on the website:
Bug reference: 19452
Logged by: Alexander Kurdakov
Email address: alexander.kurdakov@tantorlabs.ru
PostgreSQL version: 18.0
Operating system: Ubuntu 22.04.5 LTS
Description:
Build (based on Cirrus CI ASan task configuration):
./configure \
--prefix=$(pwd)/build \
--enable-cassert \
--enable-tap-tests \
--enable-debug \
CFLAGS="-Og -ggdb -fno-sanitize-recover=all -fsanitize=address" \
LDFLAGS="-fsanitize=address"
make -j$(nproc)
make install
export PATH=$(pwd)/build/bin:$PATH
export
ASAN_OPTIONS="print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0"
Reproduction:
initdb -D /tmp/pgdata
pg_ctl -D /tmp/pgdata -l /tmp/pg.log start
pg_basebackup --format=tar --gzip -X fetch --write-recovery-conf \
-D /tmp/backup --no-sync --checkpoint=fast
ASan output:
==192019==ERROR: AddressSanitizer: heap-buffer-overflow on address
0x5120000002e1 at pc 0x59338d50dc17 bp 0x7ffe6e687f50 sp 0x7ffe6e6876f8
READ of size 287 at 0x5120000002e1 thread T0
#0 __interceptor_memcpy (pg_basebackup+0x9ec16)
#1 (/lib/x86_64-linux-gnu/libz.so.1+0x12050)
#2 gzwrite (/lib/x86_64-linux-gnu/libz.so.1+0x12219)
#3 astreamer_gzip_writer_content astreamer_gzip.c:162
#4 astreamer_content astreamer.h:140
#5 astreamer_tar_archiver_content astreamer_tar.c:435
#6 astreamer_content astreamer.h:140
#7 astreamer_tar_parser_content astreamer_tar.c:225
#8 astreamer_content astreamer.h:140
#9 ReceiveArchiveStreamChunk pg_basebackup.c:1441
#10 ReceiveCopyData pg_basebackup.c:1046
0x5120000002e1 is located 0 bytes to the right of 289-byte region
allocated by pqGetCopyData3 (fe-protocol3.c:1931)
Analysis:
In astreamer_tar_parser_content(), the ASTREAMER_MEMBER_TRAILER case
passes the wrong pointer to astreamer_content().
astreamer_buffer_until() accumulates padding bytes into bbs_buffer and
advances *data past the consumed bytes. The server always sends tar
padding as a separate CopyData message (_tarWritePadding calls
bbsink_archive_contents with exactly pad_bytes bytes), so after
buffering, *data points one byte past the end of the malloc'd
PQgetCopyData() buffer.
The code passes this stale *data pointer instead of
streamer->bbs_buffer.data. The downstream gzip writer's deflate()
then reads pad_bytes bytes of out-of-bounds memory.
Any file whose size is not a multiple of 512 triggers this.
PG_VERSION (3 bytes, padding=509) and postgresql.auto.conf (88 bytes,
padding=424) are present in every initdb, so the overflow happens on
every run with the flags above.
Fix (pass bbs_buffer.data instead of data):
--- a/src/fe_utils/astreamer_tar.c
+++ b/src/fe_utils/astreamer_tar.c
@@ -222,7 +222,8 @@
/* OK, now we can send it. */
astreamer_content(mystreamer->base.bbs_next,
&mystreamer->member,
- data,
mystreamer->pad_bytes_expected,
+
mystreamer->base.bbs_buffer.data,
+
mystreamer->pad_bytes_expected,
ASTREAMER_MEMBER_TRAILER);
PG Bug reporting form <noreply@postgresql.org> writes:
In astreamer_tar_parser_content(), the ASTREAMER_MEMBER_TRAILER case
passes the wrong pointer to astreamer_content().
Indeed. I think we fixed this a few days ago in commits 01d58d7e3
et al. Could you test HEAD or v18 branch tip and verify that what
you see is fixed?
regards, tom lane
<div>> Could you test HEAD or v18 branch tip and verify that what you see is fixed?<div> </div><div>Hello Tom,<br />Thank you for looking at this!</div><div> </div><div><div>Tested on updated REL_18_STABLE, the issue no longer reproduces.</div><div><p>Best regards,<br />Alexander</p></div></div></div><div> </div>