Hi, hackers!
If we trying to call pageinspect functions for pages which are filled
with nulls, we will get core dump. It happens with null pages for all
indexes in pageinspect and for page_checksum. This problem was founded
firstly by Anastasia Lubennikova, and now I continue this task.
For example, next script leads to fail.
CREATE TABLE test1 (
x bigserial,
y bigint DEFAULT 0
);
INSERT INTO test1(y) SELECT 0 FROM generate_series(1,1E6) AS x;
SELECT page_checksum(repeat(E'\\000', 8192)::bytea, 1);
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
fatal: connection to server was lost
LOG: server process (PID 16465) was terminated by signal 6
DETAIL: Failed process was running: select
page_checksum(repeat(E'\\000', 8192)::bytea, 1);
LOG: terminating any other active server processes
LOG: all server processes terminated; reinitializing
LOG: database system was interrupted; last known up at 2022-02-16
14:03:16 +05
LOG: database system was not properly shut down; automatic recovery in
progress
LOG: redo starts at 0/14F1B20
LOG: invalid record length at 0/5D40CD8: wanted 24, got 0
LOG: redo done at 0/5D40BC0 system usage: CPU: user: 0.98 s, system:
0.02 s, elapsed: 1.01 s
LOG: checkpoint starting: end-of-recovery immediate wait
LOG: checkpoint complete: wrote 5500 buffers (33.6%); 0 WAL file(s)
added, 0 removed, 4 recycled; write=0.064 s, sync=0.007 s, total=0.080
s; sync files=45, longest=0.004 s, average=0.001 s; distance=74044 kB,
estimate=74044 kB
LOG: database system is ready to accept connections
Also it is possible to use
select brin_metapage_info(repeat(E'\\000', 8192)::bytea);
or
select bt_page_items(repeat(E'\\000', 8192)::bytea);
for getting fail.
I tried to make some additional checks for null pages into pageinspect'
functions. The attached patch also contains tests for this case.
What do you think?
--
Daria Lepikhova
Postgres Professional
From 92d71d9b5c583f5f2733646a42c36e7e437ed0a1 Mon Sep 17 00:00:00 2001
From: "d.lepikhova" <d.lepikh...@postgrespro.ru>
Date: Tue, 15 Feb 2022 14:13:05 +0500
Subject: [PATCH] Add checks for null pages to pageinspect functions
---
contrib/pageinspect/brinfuncs.c | 12 ++++++++++++
contrib/pageinspect/btreefuncs.c | 12 ++++++++++++
contrib/pageinspect/expected/brin.out | 9 +++++++++
contrib/pageinspect/expected/btree.out | 3 +++
contrib/pageinspect/expected/checksum.out | 3 +++
contrib/pageinspect/expected/gin.out | 7 +++++++
contrib/pageinspect/expected/hash.out | 9 +++++++++
contrib/pageinspect/rawpage.c | 12 ++++++++++++
contrib/pageinspect/sql/brin.sql | 6 ++++++
contrib/pageinspect/sql/btree.sql | 3 +++
contrib/pageinspect/sql/checksum.sql | 4 ++++
contrib/pageinspect/sql/gin.sql | 5 +++++
contrib/pageinspect/sql/hash.sql | 6 ++++++
13 files changed, 91 insertions(+)
diff --git a/contrib/pageinspect/brinfuncs.c b/contrib/pageinspect/brinfuncs.c
index f1e64a39ef2..56129a40abb 100644
--- a/contrib/pageinspect/brinfuncs.c
+++ b/contrib/pageinspect/brinfuncs.c
@@ -63,6 +63,12 @@ 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))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("input page is empty")));
+
switch (BrinPageType(page))
{
case BRIN_PAGETYPE_META:
@@ -103,6 +109,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..7af35da19f3 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,12 @@ 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))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("input page is empty")));
+
uargs->offset = FirstOffsetNumber;
opaque = (BTPageOpaque) PageGetSpecialPointer(uargs->page);
diff --git a/contrib/pageinspect/expected/brin.out b/contrib/pageinspect/expected/brin.out
index 71eb190380c..8e309a2a5d1 100644
--- a/contrib/pageinspect/expected/brin.out
+++ b/contrib/pageinspect/expected/brin.out
@@ -48,4 +48,13 @@ 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', 8192)::bytea);
+ERROR: input page is empty
+select brin_metapage_info(repeat(E'\\000', 8192)::bytea);
+ERROR: input page is empty
+select brin_revmap_data(repeat(E'\\000', 8192)::bytea);
+ERROR: input page is empty
+select brin_page_items(repeat(E'\\000', 8192)::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..cc6c4181c85 100644
--- a/contrib/pageinspect/expected/btree.out
+++ b/contrib/pageinspect/expected/btree.out
@@ -70,4 +70,7 @@ 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', 8192)::bytea);
+ERROR: input page is empty
DROP TABLE test1;
diff --git a/contrib/pageinspect/expected/checksum.out b/contrib/pageinspect/expected/checksum.out
index a85388e158e..ff07a879216 100644
--- a/contrib/pageinspect/expected/checksum.out
+++ b/contrib/pageinspect/expected/checksum.out
@@ -38,3 +38,6 @@ 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', 8192)::bytea, 1);
+ERROR: input page is empty
diff --git a/contrib/pageinspect/expected/gin.out b/contrib/pageinspect/expected/gin.out
index ef7570b9723..ef0fdb59565 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', 8192)::bytea);
+ERROR: input page is empty
+select gin_page_opaque_info(repeat(E'\\000', 8192)::bytea);
+ERROR: input page is empty
+select gin_leafpage_items(repeat(E'\\000', 8192)::bytea);
+ERROR: input page is empty
DROP TABLE test1;
diff --git a/contrib/pageinspect/expected/hash.out b/contrib/pageinspect/expected/hash.out
index bd0628d0136..271f33bb481 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', 8192)::bytea);
+ERROR: input page is empty
+select hash_metapage_info(repeat(E'\\000', 8192)::bytea);
+ERROR: input page is empty
+select hash_page_stats(repeat(E'\\000', 8192)::bytea);
+ERROR: input page is empty
+select hash_page_items(repeat(E'\\000', 8192)::bytea);
+ERROR: input page is empty
DROP TABLE test_hash;
diff --git a/contrib/pageinspect/rawpage.c b/contrib/pageinspect/rawpage.c
index 7e41af045f3..8a7bfccfcc3 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,12 @@ 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))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("input page is empty")));
+
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..da810adf62b 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', 8192)::bytea);
+select brin_metapage_info(repeat(E'\\000', 8192)::bytea);
+select brin_revmap_data(repeat(E'\\000', 8192)::bytea);
+select brin_page_items(repeat(E'\\000', 8192)::bytea, 'test1_a_idx');
+
DROP TABLE test1;
diff --git a/contrib/pageinspect/sql/btree.sql b/contrib/pageinspect/sql/btree.sql
index 96359179597..d2d99196077 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', 8192)::bytea);
+
DROP TABLE test1;
diff --git a/contrib/pageinspect/sql/checksum.sql b/contrib/pageinspect/sql/checksum.sql
index b877db0611f..56c4f6b52ed 100644
--- a/contrib/pageinspect/sql/checksum.sql
+++ b/contrib/pageinspect/sql/checksum.sql
@@ -29,3 +29,7 @@ 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', 8192)::bytea, 1);
+
diff --git a/contrib/pageinspect/sql/gin.sql b/contrib/pageinspect/sql/gin.sql
index 423f5c57499..559c570a644 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', 8192)::bytea);
+select gin_page_opaque_info(repeat(E'\\000', 8192)::bytea);
+select gin_leafpage_items(repeat(E'\\000', 8192)::bytea);
+
DROP TABLE test1;
diff --git a/contrib/pageinspect/sql/hash.sql b/contrib/pageinspect/sql/hash.sql
index 64f33f1d52f..4ef23cb94fe 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', 8192)::bytea);
+select hash_metapage_info(repeat(E'\\000', 8192)::bytea);
+select hash_page_stats(repeat(E'\\000', 8192)::bytea);
+select hash_page_items(repeat(E'\\000', 8192)::bytea);
+
DROP TABLE test_hash;
--
2.25.1