From bfbe165af79dbe90e6152f738921311a483ff663 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horikyoga.ntt@gmail.com>
Date: Thu, 11 Jun 2020 20:29:23 +0900
Subject: [PATCH v3] Make sure to consume stream-terminating packet

A compressed stream may end with an empty packet.  In this case
decompression finishes before reading the empty packet and the
remaining stream packet causes a failure in reading the following
data. Make sure to consume such packets after compression finishes.
---
 contrib/pgcrypto/expected/pgp-compression.out | 21 ++++++++++++++++++
 contrib/pgcrypto/pgp-compress.c               | 22 +++++++++++++++++++
 contrib/pgcrypto/sql/pgp-compression.sql      | 12 ++++++++++
 3 files changed, 55 insertions(+)

diff --git a/contrib/pgcrypto/expected/pgp-compression.out b/contrib/pgcrypto/expected/pgp-compression.out
index 32b350b8fe..84d0956070 100644
--- a/contrib/pgcrypto/expected/pgp-compression.out
+++ b/contrib/pgcrypto/expected/pgp-compression.out
@@ -48,3 +48,24 @@ select pgp_sym_decrypt(
  Secret message
 (1 row)
 
+-- check if trailing empty-packet of compressed stream is correctly read
+select setseed(0);
+ setseed 
+---------
+ 
+(1 row)
+
+select bytes =
+		pgp_sym_decrypt_bytea(
+			pgp_sym_encrypt_bytea(bytes, E'\\x12345678',
+									'compress-algo=1,compress-level=1'),
+			E'\\x12345678') from
+		(select -- generate incompressible source data with 16385 bytes 
+	     string_agg(decode(lpad(to_hex((random()*256)::int),2,'0'), 'hex'), '')
+		 as bytes
+		from generate_series(0, 16365)) t;
+ ?column? 
+----------
+ t
+(1 row)
+
diff --git a/contrib/pgcrypto/pgp-compress.c b/contrib/pgcrypto/pgp-compress.c
index 0505bdee92..296afb3324 100644
--- a/contrib/pgcrypto/pgp-compress.c
+++ b/contrib/pgcrypto/pgp-compress.c
@@ -286,7 +286,29 @@ restart:
 
 	dec->buf_data = dec->buf_len - dec->stream.avail_out;
 	if (res == Z_STREAM_END)
+	{
+		uint8 *tmp;
+
+		/*
+		 * A stream must be terminated by a normal packet. If the last stream
+		 * packet in the source stream is a full packet, a normal empty packet
+		 * must follow. Since the underlying packet reader doesn't know that
+		 * the compressed stream has been ended, we need to to consume the
+		 * terminating packet here. This read doesn't harm even if the stream
+		 * has already ended.
+		 */
+		res = pullf_read(src, 1, &tmp);
+
+		if (res < 0)
+			return res;
+		else if (res > 0)
+		{
+			px_debug("decompress_read: extra bytes after end of stream");
+			return PXE_PGP_CORRUPT_DATA;
+		}
+		
 		dec->eof = 1;
+	}
 	goto restart;
 }
 
diff --git a/contrib/pgcrypto/sql/pgp-compression.sql b/contrib/pgcrypto/sql/pgp-compression.sql
index ca9ee1fc00..7eb65048dd 100644
--- a/contrib/pgcrypto/sql/pgp-compression.sql
+++ b/contrib/pgcrypto/sql/pgp-compression.sql
@@ -28,3 +28,15 @@ select pgp_sym_decrypt(
 	pgp_sym_encrypt('Secret message', 'key',
 			'compress-algo=2, compress-level=0'),
 	'key', 'expect-compress-algo=0');
+
+-- check if trailing empty-packet of compressed stream is correctly read
+select setseed(0);
+select bytes =
+		pgp_sym_decrypt_bytea(
+			pgp_sym_encrypt_bytea(bytes, E'\\x12345678',
+									'compress-algo=1,compress-level=1'),
+			E'\\x12345678') from
+		(select -- generate incompressible source data with 16385 bytes 
+	     string_agg(decode(lpad(to_hex((random()*256)::int),2,'0'), 'hex'), '')
+		 as bytes
+		from generate_series(0, 16365)) t;
-- 
2.18.2

