postgres.git / summary / log / commit / refs

commit    5095f3f4a0dc2a91d1580598a4da8790a44aa7d2
Author:   Andrew Dunstan <andrew@dunslane.net>
Date:     Mon Mar 23 20:17:08 2026 +0000
First released in: 15.18, 16.14, 17.10, 18.4

    Fix multiple bugs in astreamer pipeline code.
    
    astreamer_tar_parser_content() sent the wrong data pointer when
    forwarding MEMBER_TRAILER padding to the next streamer.  After
    astreamer_buffer_until() buffers the padding bytes, the 'data'
    pointer has been advanced past them, but the code passed 'data'
    instead of bbs_buffer.data.  This caused the downstream consumer
    to receive bytes from after the padding rather than the padding
    itself, and could read past the end of the input buffer.
    
    astreamer_gzip_decompressor_content() only checked for
    Z_STREAM_ERROR from inflate(), silently ignoring Z_DATA_ERROR
    (corrupted data) and Z_MEM_ERROR (out of memory).  Fix by
    treating any return other than Z_OK, Z_STREAM_END, and
    Z_BUF_ERROR as fatal.
    
    astreamer_gzip_decompressor_free() missed calling inflateEnd() to
    release zlib's internal decompression state.
    
    astreamer_tar_parser_free() neglected to pfree() the streamer
    struct itself, leaking it.
    
    astreamer_extractor_content() did not check the return value of
    fclose() when closing an extracted file.  A deferred write error
    (e.g., disk full on buffered I/O) would be silently lost.
    
    Discussion: https://postgr.es/m/results/98c6b630-acbb-44a7-97fa-1692ce2b827c@dunslane.net
    
    Reviewed-By: Tom Lane <tgl@sss.pgh.pa.us>
    
    Backpatch-through: 15


src/fe_utils/astreamer_file.c | 4 +++- src/fe_utils/astreamer_gzip.c | 10 ++++++++-- src/fe_utils/astreamer_tar.c | 4 +++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/fe_utils/astreamer_file.c b/src/fe_utils/astreamer_file.c index c6856285086..b6cbd343f99 100644 --- a/src/fe_utils/astreamer_file.c +++ b/src/fe_utils/astreamer_file.c @@ -266,7 +266,9 @@ astreamer_extractor_content(astreamer *streamer, astreamer_member *member, case ASTREAMER_MEMBER_TRAILER: if (mystreamer->file == NULL) break; - fclose(mystreamer->file); + if (fclose(mystreamer->file) != 0) + pg_fatal("could not close file \"%s\": %m", + mystreamer->filename); mystreamer->file = NULL; break; diff --git a/src/fe_utils/astreamer_gzip.c b/src/fe_utils/astreamer_gzip.c index 71a5fa7874a..671275f941d 100644 --- a/src/fe_utils/astreamer_gzip.c +++ b/src/fe_utils/astreamer_gzip.c @@ -316,8 +316,9 @@ astreamer_gzip_decompressor_content(astreamer *streamer, */ res = inflate(zs, Z_NO_FLUSH); - if (res == Z_STREAM_ERROR) - pg_fatal("could not decompress data: %s", zs->msg); + if (res != Z_OK && res != Z_STREAM_END && res != Z_BUF_ERROR) + pg_fatal("could not decompress data: %s", + zs->msg ? zs->msg : "unknown error"); mystreamer->bytes_written = mystreamer->base.bbs_buffer.maxlen - zs->avail_out; @@ -362,7 +363,12 @@ astreamer_gzip_decompressor_finalize(astreamer *streamer) static void astreamer_gzip_decompressor_free(astreamer *streamer) { + astreamer_gzip_decompressor *mystreamer; + + mystreamer = (astreamer_gzip_decompressor *) streamer; + astreamer_free(streamer->bbs_next); + inflateEnd(&mystreamer->zstream); pfree(streamer->bbs_buffer.data); pfree(streamer); } diff --git a/src/fe_utils/astreamer_tar.c b/src/fe_utils/astreamer_tar.c index 088e2357920..8390c0b49f1 100644 --- a/src/fe_utils/astreamer_tar.c +++ b/src/fe_utils/astreamer_tar.c @@ -224,7 +224,8 @@ astreamer_tar_parser_content(astreamer *streamer, astreamer_member *member, /* 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); /* Expect next file header. */ @@ -342,6 +343,7 @@ astreamer_tar_parser_free(astreamer *streamer) { pfree(streamer->bbs_buffer.data); astreamer_free(streamer->bbs_next); + pfree(streamer); } /* [parent: 25b02320e133]