From d2d684901b10686c5cc4b98219fdd2300683a689 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <dgustafsson@postgresql.org>
Date: Mon, 16 Jun 2025 17:28:35 +0200
Subject: [PATCH] pg_dump: Handle errors in reading ZStd streams

The read_func API is defined to always return true with errors during
reading generating a pg_fatal. The ZStd code was however not checking
if 0 returned from fread was due to error or an EOF, which would mask
errors as EOF. Also fix all internal callers to correctly use the API
and not check the returnvalue.

Reported-by: Evgeniy Gorbanev <gorbanyoves@basealt.ru>
Discussion: https://postgr.es/m/6b9817a8-88ec-4efd-b441-9e2a0439c6b8@basealt.ru
---
 src/bin/pg_dump/compress_zstd.c | 28 ++++++++++++++++++++++++----
 1 file changed, 24 insertions(+), 4 deletions(-)

diff --git a/src/bin/pg_dump/compress_zstd.c b/src/bin/pg_dump/compress_zstd.c
index cb595b10c2d..9e79619c955 100644
--- a/src/bin/pg_dump/compress_zstd.c
+++ b/src/bin/pg_dump/compress_zstd.c
@@ -272,6 +272,13 @@ Zstd_read(void *ptr, size_t size, size_t *rdsize, CompressFileHandle *CFH)
 	output->dst = ptr;
 	output->pos = 0;
 
+	/*
+	 * Clear the error/eof flag before calling fread so that we can inspect
+	 * for errors after reading.  This also protects reading against fread
+	 * implementations which won't read when set.
+	 */
+	clearerr(zstdcs->fp);
+
 	for (;;)
 	{
 		Assert(input->pos <= input->size);
@@ -296,9 +303,15 @@ Zstd_read(void *ptr, size_t size, size_t *rdsize, CompressFileHandle *CFH)
 
 			Assert(cnt <= input_allocated_size);
 
-			/* If we have no more input to consume, we're done */
+			/* check if the returned zero is due to EOF or an error */
 			if (cnt == 0)
+			{
+				if (ferror(zstdcs->fp))
+					pg_fatal("could not read from input file: %m");
+
+				/* If we have no more input to consume, we're done */
 				break;
+			}
 		}
 
 		while (input->pos < input->size)
@@ -366,8 +379,15 @@ Zstd_getc(CompressFileHandle *CFH)
 {
 	ZstdCompressorState *zstdcs = (ZstdCompressorState *) CFH->private_data;
 	int			ret;
+	size_t		readsz;
 
-	if (CFH->read_func(&ret, 1, NULL, CFH) != 1)
+	CFH->read_func(&ret, 1, &readsz, CFH);
+
+	/*
+	 * read_func will throw an error on ferror but not feof, but getc_func is
+	 * defined to throw an error on EOF so we need to test that here.
+	 */
+	if (readsz == 0)
 	{
 		if (feof(zstdcs->fp))
 			pg_fatal("could not read from input file: end of file");
@@ -392,8 +412,8 @@ Zstd_gets(char *buf, int len, CompressFileHandle *CFH)
 	{
 		size_t		readsz;
 
-		if (!CFH->read_func(&buf[i], 1, &readsz, CFH))
-			break;
+		CFH->read_func(&buf[i], 1, &readsz, CFH);
+
 		if (readsz != 1)
 			break;
 		if (buf[i] == '\n')
-- 
2.39.3 (Apple Git-146)

