From 7dbfa7f01bf485d2812b24af012721b4a1e1e785 Mon Sep 17 00:00:00 2001
From: "d.lepikhova" <d.lepikhova@postgrespro.ru>
Date: Tue, 22 Feb 2022 16:25:31 +0500
Subject: [PATCH] Add checks for null pages into pageinspect

---
 contrib/pageinspect/brinfuncs.c           | 10 ++++++++++
 contrib/pageinspect/btreefuncs.c          | 10 ++++++++++
 contrib/pageinspect/expected/brin.out     | 13 +++++++++++++
 contrib/pageinspect/expected/btree.out    |  5 +++++
 contrib/pageinspect/expected/checksum.out |  7 +++++++
 contrib/pageinspect/expected/gin.out      |  7 +++++++
 contrib/pageinspect/expected/gist.out     |  5 +++++
 contrib/pageinspect/expected/hash.out     |  9 +++++++++
 contrib/pageinspect/rawpage.c             | 10 ++++++++++
 contrib/pageinspect/sql/brin.sql          |  6 ++++++
 contrib/pageinspect/sql/btree.sql         |  3 +++
 contrib/pageinspect/sql/checksum.sql      |  3 +++
 contrib/pageinspect/sql/gin.sql           |  5 +++++
 contrib/pageinspect/sql/gist.sql          |  5 +++++
 contrib/pageinspect/sql/hash.sql          |  6 ++++++
 15 files changed, 104 insertions(+)

diff --git a/contrib/pageinspect/brinfuncs.c b/contrib/pageinspect/brinfuncs.c
index 50892b5cc20..a2fee13e2d1 100644
--- a/contrib/pageinspect/brinfuncs.c
+++ b/contrib/pageinspect/brinfuncs.c
@@ -63,6 +63,10 @@ brin_page_type(PG_FUNCTION_ARGS)
 				 errdetail("Expected size %d, got %d",
 						   BLCKSZ, raw_page_size)));
 
+	/* check that the page is initialized before accessing any fields */
+	if (PageIsNew(page))
+		PG_RETURN_NULL();
+
 	switch (BrinPageType(page))
 	{
 		case BRIN_PAGETYPE_META:
@@ -103,6 +107,12 @@ verify_brin_page(bytea *raw_page, uint16 type, const char *strtype)
 
 	page = VARDATA(raw_page);
 
+	/* check that the page is initialized before accessing any fields */
+	if (PageIsNew(page))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("input page is empty")));
+
 	/* verify the special space says this page is what we want */
 	if (BrinPageType(page) != type)
 		ereport(ERROR,
diff --git a/contrib/pageinspect/btreefuncs.c b/contrib/pageinspect/btreefuncs.c
index 03debe336ba..5ea7af5b998 100644
--- a/contrib/pageinspect/btreefuncs.c
+++ b/contrib/pageinspect/btreefuncs.c
@@ -519,6 +519,12 @@ bt_page_items_internal(PG_FUNCTION_ARGS, enum pageinspect_version ext_version)
 		UnlockReleaseBuffer(buffer);
 		relation_close(rel, AccessShareLock);
 
+		/* check that the page is initialized before accessing any fields */
+		if (PageIsNew(uargs->page))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					errmsg("input page is empty")));
+
 		uargs->offset = FirstOffsetNumber;
 
 		opaque = (BTPageOpaque) PageGetSpecialPointer(uargs->page);
@@ -615,6 +621,10 @@ bt_page_items_bytea(PG_FUNCTION_ARGS)
 
 		uargs->page = VARDATA(raw_page);
 
+		/* check that the page is initialized before accessing any fields */
+		if (PageIsNew(uargs->page))
+			PG_RETURN_NULL();
+
 		uargs->offset = FirstOffsetNumber;
 
 		opaque = (BTPageOpaque) PageGetSpecialPointer(uargs->page);
diff --git a/contrib/pageinspect/expected/brin.out b/contrib/pageinspect/expected/brin.out
index 71eb190380c..2839ee9435b 100644
--- a/contrib/pageinspect/expected/brin.out
+++ b/contrib/pageinspect/expected/brin.out
@@ -48,4 +48,17 @@ SELECT * FROM brin_page_items(get_raw_page('test1_a_idx', 2), 'test1_a_idx')
           1 |      0 |      1 | f        | f        | f           | {1 .. 1}
 (1 row)
 
+-- Check that all functions fail gracefully with an empty page imput
+select brin_page_type(repeat(E'\\000', current_setting('block_size')::int)::bytea);
+ brin_page_type 
+----------------
+ 
+(1 row)
+
+select brin_metapage_info(repeat(E'\\000', current_setting('block_size')::int)::bytea);
+ERROR:  input page is empty
+select brin_revmap_data(repeat(E'\\000', current_setting('block_size')::int)::bytea);
+ERROR:  input page is empty
+select brin_page_items(repeat(E'\\000', current_setting('block_size')::int)::bytea, 'test1_a_idx');
+ERROR:  input page is empty
 DROP TABLE test1;
diff --git a/contrib/pageinspect/expected/btree.out b/contrib/pageinspect/expected/btree.out
index c60bc88560c..7c503877826 100644
--- a/contrib/pageinspect/expected/btree.out
+++ b/contrib/pageinspect/expected/btree.out
@@ -70,4 +70,9 @@ tids       |
 
 SELECT * FROM bt_page_items(get_raw_page('test1_a_idx', 2));
 ERROR:  block number 2 is out of range for relation "test1_a_idx"
+-- Check that bt_page_items() fails gracefully with an empty page input
+select bt_page_items(repeat(E'\\000', current_setting('block_size')::int)::bytea);
+-[ RECORD 1 ]-+-
+bt_page_items | 
+
 DROP TABLE test1;
diff --git a/contrib/pageinspect/expected/checksum.out b/contrib/pageinspect/expected/checksum.out
index a85388e158e..033eb2ca2bc 100644
--- a/contrib/pageinspect/expected/checksum.out
+++ b/contrib/pageinspect/expected/checksum.out
@@ -38,3 +38,10 @@ SELECT blkno,
    100 |        1139 |       28438 |        3648 |        -30881 |        -16305 |        -27349
 (3 rows)
 
+ --Check that page_checksum() with an empty page fails gracefully
+ select page_checksum(repeat(E'\\000', current_setting('block_size')::int)::bytea, 1);
+ page_checksum 
+---------------
+              
+(1 row)
+
diff --git a/contrib/pageinspect/expected/gin.out b/contrib/pageinspect/expected/gin.out
index ef7570b9723..a37a3749b80 100644
--- a/contrib/pageinspect/expected/gin.out
+++ b/contrib/pageinspect/expected/gin.out
@@ -35,4 +35,11 @@ FROM gin_leafpage_items(get_raw_page('test1_y_idx',
 -[ RECORD 1 ]
 ?column? | t
 
+-- Check that all functions fail gracefully with an empty page imput
+select gin_metapage_info(repeat(E'\\000', current_setting('block_size')::int)::bytea);
+ERROR:  input page is empty
+select gin_page_opaque_info(repeat(E'\\000', current_setting('block_size')::int)::bytea);
+ERROR:  input page is empty
+select gin_leafpage_items(repeat(E'\\000', current_setting('block_size')::int)::bytea);
+ERROR:  input page is empty
 DROP TABLE test1;
diff --git a/contrib/pageinspect/expected/gist.out b/contrib/pageinspect/expected/gist.out
index b26b56ba15d..d69d653abf5 100644
--- a/contrib/pageinspect/expected/gist.out
+++ b/contrib/pageinspect/expected/gist.out
@@ -64,4 +64,9 @@ SELECT itemoffset, ctid, itemlen FROM gist_page_items_bytea(get_raw_page('test_g
           6 | (6,65535) |      40
 (6 rows)
 
+-- Check that all functions fail gracefully with an empty page imput
+SELECT * FROM gist_page_opaque_info(repeat(E'\\000', current_setting('block_size')::int)::bytea);
+ERROR:  input page is empty
+SELECT * FROM gist_page_items(repeat(E'\\000', current_setting('block_size')::int)::bytea, 'test_gist_idx');
+ERROR:  input page is empty
 DROP TABLE test_gist;
diff --git a/contrib/pageinspect/expected/hash.out b/contrib/pageinspect/expected/hash.out
index bd0628d0136..f8820f07c42 100644
--- a/contrib/pageinspect/expected/hash.out
+++ b/contrib/pageinspect/expected/hash.out
@@ -163,4 +163,13 @@ SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 4));
 
 SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 5));
 ERROR:  page is not a hash bucket or overflow page
+-- Check that all functions fail gracefully with an empty page imput
+select hash_page_type(repeat(E'\\000', current_setting('block_size')::int)::bytea);
+ERROR:  input page is empty
+select hash_metapage_info(repeat(E'\\000', current_setting('block_size')::int)::bytea);
+ERROR:  input page is empty
+select hash_page_stats(repeat(E'\\000', current_setting('block_size')::int)::bytea);
+ERROR:  input page is empty
+select hash_page_items(repeat(E'\\000', current_setting('block_size')::int)::bytea);
+ERROR:  input page is empty
 DROP TABLE test_hash;
diff --git a/contrib/pageinspect/rawpage.c b/contrib/pageinspect/rawpage.c
index 7e41af045f3..e1eb80ad3d4 100644
--- a/contrib/pageinspect/rawpage.c
+++ b/contrib/pageinspect/rawpage.c
@@ -230,6 +230,12 @@ get_page_from_raw(bytea *raw_page)
 
 	memcpy(page, VARDATA_ANY(raw_page), raw_page_size);
 
+	/* check that the page is initialized */
+	if (PageIsNew((Page) page))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				errmsg("input page is empty")));
+
 	return page;
 }
 
@@ -375,6 +381,10 @@ page_checksum_internal(PG_FUNCTION_ARGS, enum pageinspect_version ext_version)
 
 	page = (PageHeader) VARDATA(raw_page);
 
+	/* check that the page is initialized */
+	if (PageIsNew((Page) page))
+		PG_RETURN_NULL();
+
 	PG_RETURN_INT16(pg_checksum_page((char *) page, blkno));
 }
 
diff --git a/contrib/pageinspect/sql/brin.sql b/contrib/pageinspect/sql/brin.sql
index 735bc3b6733..65da9403995 100644
--- a/contrib/pageinspect/sql/brin.sql
+++ b/contrib/pageinspect/sql/brin.sql
@@ -15,4 +15,10 @@ SELECT * FROM brin_revmap_data(get_raw_page('test1_a_idx', 1)) LIMIT 5;
 SELECT * FROM brin_page_items(get_raw_page('test1_a_idx', 2), 'test1_a_idx')
     ORDER BY blknum, attnum LIMIT 5;
 
+-- Check that all functions fail gracefully with an empty page imput
+select brin_page_type(repeat(E'\\000', current_setting('block_size')::int)::bytea);
+select brin_metapage_info(repeat(E'\\000', current_setting('block_size')::int)::bytea);
+select brin_revmap_data(repeat(E'\\000', current_setting('block_size')::int)::bytea);
+select brin_page_items(repeat(E'\\000', current_setting('block_size')::int)::bytea, 'test1_a_idx');
+
 DROP TABLE test1;
diff --git a/contrib/pageinspect/sql/btree.sql b/contrib/pageinspect/sql/btree.sql
index 96359179597..e35e894c531 100644
--- a/contrib/pageinspect/sql/btree.sql
+++ b/contrib/pageinspect/sql/btree.sql
@@ -21,4 +21,7 @@ SELECT * FROM bt_page_items(get_raw_page('test1_a_idx', 0));
 SELECT * FROM bt_page_items(get_raw_page('test1_a_idx', 1));
 SELECT * FROM bt_page_items(get_raw_page('test1_a_idx', 2));
 
+-- Check that bt_page_items() fails gracefully with an empty page input
+select bt_page_items(repeat(E'\\000', current_setting('block_size')::int)::bytea);
+
 DROP TABLE test1;
diff --git a/contrib/pageinspect/sql/checksum.sql b/contrib/pageinspect/sql/checksum.sql
index b877db0611f..4c1922dc09a 100644
--- a/contrib/pageinspect/sql/checksum.sql
+++ b/contrib/pageinspect/sql/checksum.sql
@@ -29,3 +29,6 @@ SELECT blkno,
     page_checksum(decode(repeat('e6d6', :block_size / 2), 'hex'), blkno) AS checksum_e6d6,
     page_checksum(decode(repeat('4a5e', :block_size / 2), 'hex'), blkno) AS checksum_4a5e
   FROM generate_series(0, 100, 50) AS a (blkno);
+
+ --Check that page_checksum() with an empty page fails gracefully
+ select page_checksum(repeat(E'\\000', current_setting('block_size')::int)::bytea, 1);
diff --git a/contrib/pageinspect/sql/gin.sql b/contrib/pageinspect/sql/gin.sql
index 423f5c57499..02826023d3e 100644
--- a/contrib/pageinspect/sql/gin.sql
+++ b/contrib/pageinspect/sql/gin.sql
@@ -18,4 +18,9 @@ FROM gin_leafpage_items(get_raw_page('test1_y_idx',
                         (pg_relation_size('test1_y_idx') /
                          current_setting('block_size')::bigint)::int - 1));
 
+-- Check that all functions fail gracefully with an empty page imput
+select gin_metapage_info(repeat(E'\\000', current_setting('block_size')::int)::bytea);
+select gin_page_opaque_info(repeat(E'\\000', current_setting('block_size')::int)::bytea);
+select gin_leafpage_items(repeat(E'\\000', current_setting('block_size')::int)::bytea);
+
 DROP TABLE test1;
diff --git a/contrib/pageinspect/sql/gist.sql b/contrib/pageinspect/sql/gist.sql
index 1560d1e15c3..d3277ba71d5 100644
--- a/contrib/pageinspect/sql/gist.sql
+++ b/contrib/pageinspect/sql/gist.sql
@@ -26,4 +26,9 @@ SELECT * FROM gist_page_items(get_raw_page('test_gist_idx', 1), 'test_gist_idx')
 -- platform-dependent (endianess), so omit the actual key data from the output.
 SELECT itemoffset, ctid, itemlen FROM gist_page_items_bytea(get_raw_page('test_gist_idx', 0));
 
+-- Check that all functions fail gracefully with an empty page imput
+SELECT * FROM gist_page_opaque_info(repeat(E'\\000', current_setting('block_size')::int)::bytea);
+SELECT * FROM gist_page_items(repeat(E'\\000', current_setting('block_size')::int)::bytea, 'test_gist_idx');
+
+
 DROP TABLE test_gist;
diff --git a/contrib/pageinspect/sql/hash.sql b/contrib/pageinspect/sql/hash.sql
index 64f33f1d52f..dba5b5f9c33 100644
--- a/contrib/pageinspect/sql/hash.sql
+++ b/contrib/pageinspect/sql/hash.sql
@@ -78,5 +78,11 @@ SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 3));
 SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 4));
 SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 5));
 
+-- Check that all functions fail gracefully with an empty page imput
+select hash_page_type(repeat(E'\\000', current_setting('block_size')::int)::bytea);
+select hash_metapage_info(repeat(E'\\000', current_setting('block_size')::int)::bytea);
+select hash_page_stats(repeat(E'\\000', current_setting('block_size')::int)::bytea);
+select hash_page_items(repeat(E'\\000', current_setting('block_size')::int)::bytea);
+
 
 DROP TABLE test_hash;
-- 
2.25.1

