Skip to content

Commit df6bbe7

Browse files
committed
pageinspect: Fix handling of all-zero pages
Getting from get_raw_page() an all-zero page is considered as a valid case by the buffer manager and it can happen for example when finding a corrupted page with zero_damaged_pages enabled (using zero_damaged_pages to look at corrupted pages happens), or after a crash when a relation file is extended before any WAL for its new data is generated (before a vacuum or autovacuum job comes in to do some cleanup). However, all the functions of pageinspect, as of the index AMs (except hash that has its own idea of new pages), heap, the FSM or the page header have never worked with all-zero pages, causing various crashes when going through the page internals. This commit changes all the pageinspect functions to be compliant with all-zero pages, where the choice is made to return NULL or no rows for SRFs when finding a new page. get_raw_page() still works the same way, returning a batch of zeros in the bytea of the page retrieved. A hard error could be used but NULL, while more invasive, is useful when scanning relation files in full to get a batch of results for a single relation in one query. Tests are added for all the code paths impacted. Reported-by: Daria Lepikhova Author: Michael Paquier Discussion: https://postgr.es/m/561e187b-3549-c8d5-03f5-525c14e65bd0@postgrespro.ru Backpatch-through: 10
1 parent c590e51 commit df6bbe7

File tree

18 files changed

+190
-0
lines changed

18 files changed

+190
-0
lines changed

contrib/pageinspect/brinfuncs.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ brin_page_type(PG_FUNCTION_ARGS)
5858

5959
page = get_page_from_raw(raw_page);
6060

61+
if (PageIsNew(page))
62+
PG_RETURN_NULL();
63+
6164
/* verify the special space has the expected size */
6265
if (PageGetSpecialSize(page) != MAXALIGN(sizeof(BrinSpecialSpace)))
6366
ereport(ERROR,
@@ -95,6 +98,9 @@ verify_brin_page(bytea *raw_page, uint16 type, const char *strtype)
9598
{
9699
Page page = get_page_from_raw(raw_page);
97100

101+
if (PageIsNew(page))
102+
return page;
103+
98104
/* verify the special space has the expected size */
99105
if (PageGetSpecialSize(page) != MAXALIGN(sizeof(BrinSpecialSpace)))
100106
ereport(ERROR,
@@ -182,6 +188,13 @@ brin_page_items(PG_FUNCTION_ARGS)
182188
/* minimally verify the page we got */
183189
page = verify_brin_page(raw_page, BRIN_PAGETYPE_REGULAR, "regular");
184190

191+
if (PageIsNew(page))
192+
{
193+
brin_free_desc(bdesc);
194+
index_close(indexRel, AccessShareLock);
195+
PG_RETURN_NULL();
196+
}
197+
185198
/*
186199
* Initialize output functions for all indexed datatypes; simplifies
187200
* calling them later.
@@ -359,6 +372,9 @@ brin_metapage_info(PG_FUNCTION_ARGS)
359372

360373
page = verify_brin_page(raw_page, BRIN_PAGETYPE_META, "metapage");
361374

375+
if (PageIsNew(page))
376+
PG_RETURN_NULL();
377+
362378
/* Build a tuple descriptor for our result type */
363379
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
364380
elog(ERROR, "return type must be a row type");
@@ -410,6 +426,12 @@ brin_revmap_data(PG_FUNCTION_ARGS)
410426
/* minimally verify the page we got */
411427
page = verify_brin_page(raw_page, BRIN_PAGETYPE_REVMAP, "revmap");
412428

429+
if (PageIsNew(page))
430+
{
431+
MemoryContextSwitchTo(mctx);
432+
PG_RETURN_NULL();
433+
}
434+
413435
state = palloc(sizeof(*state));
414436
state->tids = ((RevmapContents *) PageGetContents(page))->rm_tids;
415437
state->idx = 0;

contrib/pageinspect/btreefuncs.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,12 @@ bt_page_items_bytea(PG_FUNCTION_ARGS)
611611

612612
uargs->page = get_page_from_raw(raw_page);
613613

614+
if (PageIsNew(uargs->page))
615+
{
616+
MemoryContextSwitchTo(mctx);
617+
PG_RETURN_NULL();
618+
}
619+
614620
uargs->offset = FirstOffsetNumber;
615621

616622
/* verify the special space has the expected size */

contrib/pageinspect/expected/brin.out

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,29 @@ ERROR: input page is not a valid BRIN page
6262
SELECT * FROM brin_revmap_data(get_raw_page('test1', 0));
6363
ERROR: input page is not a valid BRIN page
6464
\set VERBOSITY default
65+
-- Tests with all-zero pages.
66+
SHOW block_size \gset
67+
SELECT brin_page_type(decode(repeat('00', :block_size), 'hex'));
68+
brin_page_type
69+
----------------
70+
71+
(1 row)
72+
73+
SELECT brin_page_items(decode(repeat('00', :block_size), 'hex'), 'test1_a_idx');
74+
brin_page_items
75+
-----------------
76+
(0 rows)
77+
78+
SELECT brin_metapage_info(decode(repeat('00', :block_size), 'hex'));
79+
brin_metapage_info
80+
--------------------
81+
82+
(1 row)
83+
84+
SELECT brin_revmap_data(decode(repeat('00', :block_size), 'hex'));
85+
brin_revmap_data
86+
------------------
87+
88+
(1 row)
89+
6590
DROP TABLE test1;

contrib/pageinspect/expected/btree.out

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,10 @@ ERROR: input page is not a valid btree page
9999
SELECT bt_page_items(get_raw_page('test1_a_brin', 0));
100100
ERROR: input page is not a valid btree page
101101
\set VERBOSITY default
102+
-- Tests with all-zero pages.
103+
SHOW block_size \gset
104+
SELECT bt_page_items(decode(repeat('00', :block_size), 'hex'));
105+
-[ RECORD 1 ]-+-
106+
bt_page_items |
107+
102108
DROP TABLE test1;

contrib/pageinspect/expected/gin.out

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,18 @@ ERROR: input page is not a valid GIN data leaf page
5454
SELECT * FROM gin_leafpage_items(get_raw_page('test1', 0));
5555
ERROR: input page is not a valid GIN data leaf page
5656
\set VERBOSITY default
57+
-- Tests with all-zero pages.
58+
SHOW block_size \gset
59+
SELECT gin_leafpage_items(decode(repeat('00', :block_size), 'hex'));
60+
-[ RECORD 1 ]------+-
61+
gin_leafpage_items |
62+
63+
SELECT gin_metapage_info(decode(repeat('00', :block_size), 'hex'));
64+
-[ RECORD 1 ]-----+-
65+
gin_metapage_info |
66+
67+
SELECT gin_page_opaque_info(decode(repeat('00', :block_size), 'hex'));
68+
-[ RECORD 1 ]--------+-
69+
gin_page_opaque_info |
70+
5771
DROP TABLE test1;

contrib/pageinspect/expected/gist.out

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,22 @@ ERROR: input page is not a valid GiST page
8989
SELECT gist_page_items_bytea(get_raw_page('test_gist_btree', 0));
9090
ERROR: input page is not a valid GiST page
9191
\set VERBOSITY default
92+
-- Tests with all-zero pages.
93+
SHOW block_size \gset
94+
SELECT gist_page_items_bytea(decode(repeat('00', :block_size), 'hex'));
95+
gist_page_items_bytea
96+
-----------------------
97+
(0 rows)
98+
99+
SELECT gist_page_items(decode(repeat('00', :block_size), 'hex'), 'test_gist_idx'::regclass);
100+
gist_page_items
101+
-----------------
102+
(0 rows)
103+
104+
SELECT gist_page_opaque_info(decode(repeat('00', :block_size), 'hex'));
105+
gist_page_opaque_info
106+
-----------------------
107+
108+
(1 row)
109+
92110
DROP TABLE test_gist;

contrib/pageinspect/expected/hash.out

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,4 +190,16 @@ ERROR: input page is not a valid hash page
190190
SELECT hash_page_type(get_raw_page('test_hash', 0));
191191
ERROR: input page is not a valid hash page
192192
\set VERBOSITY default
193+
-- Tests with all-zero pages.
194+
SHOW block_size \gset
195+
SELECT hash_metapage_info(decode(repeat('00', :block_size), 'hex'));
196+
ERROR: page is not a hash meta page
197+
SELECT hash_page_items(decode(repeat('00', :block_size), 'hex'));
198+
ERROR: page is not a hash bucket or overflow page
199+
SELECT hash_page_stats(decode(repeat('00', :block_size), 'hex'));
200+
ERROR: page is not a hash bucket or overflow page
201+
SELECT hash_page_type(decode(repeat('00', :block_size), 'hex'));
202+
-[ RECORD 1 ]--+-------
203+
hash_page_type | unused
204+
193205
DROP TABLE test_hash;

contrib/pageinspect/expected/page.out

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,3 +216,23 @@ ERROR: invalid page size
216216
SELECT page_header('ccc'::bytea);
217217
ERROR: invalid page size
218218
\set VERBOSITY default
219+
-- Tests with all-zero pages.
220+
SHOW block_size \gset
221+
SELECT fsm_page_contents(decode(repeat('00', :block_size), 'hex'));
222+
fsm_page_contents
223+
-------------------
224+
225+
(1 row)
226+
227+
SELECT page_header(decode(repeat('00', :block_size), 'hex'));
228+
page_header
229+
-----------------------
230+
(0/0,0,0,0,0,0,0,0,0)
231+
(1 row)
232+
233+
SELECT page_checksum(decode(repeat('00', :block_size), 'hex'), 1);
234+
page_checksum
235+
---------------
236+
237+
(1 row)
238+

contrib/pageinspect/fsmfuncs.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ fsm_page_contents(PG_FUNCTION_ARGS)
4646
errmsg("must be superuser to use raw page functions")));
4747

4848
page = get_page_from_raw(raw_page);
49+
50+
if (PageIsNew(page))
51+
PG_RETURN_NULL();
52+
4953
fsmpage = (FSMPage) PageGetContents(page);
5054

5155
initStringInfo(&sinfo);

contrib/pageinspect/ginfuncs.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ gin_metapage_info(PG_FUNCTION_ARGS)
4949

5050
page = get_page_from_raw(raw_page);
5151

52+
if (PageIsNew(page))
53+
PG_RETURN_NULL();
54+
5255
if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GinPageOpaqueData)))
5356
ereport(ERROR,
5457
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -115,6 +118,9 @@ gin_page_opaque_info(PG_FUNCTION_ARGS)
115118

116119
page = get_page_from_raw(raw_page);
117120

121+
if (PageIsNew(page))
122+
PG_RETURN_NULL();
123+
118124
if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GinPageOpaqueData)))
119125
ereport(ERROR,
120126
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -200,6 +206,12 @@ gin_leafpage_items(PG_FUNCTION_ARGS)
200206

201207
page = get_page_from_raw(raw_page);
202208

209+
if (PageIsNew(page))
210+
{
211+
MemoryContextSwitchTo(mctx);
212+
PG_RETURN_NULL();
213+
}
214+
203215
if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GinPageOpaqueData)))
204216
ereport(ERROR,
205217
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),

contrib/pageinspect/gistfuncs.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ gist_page_opaque_info(PG_FUNCTION_ARGS)
5555

5656
page = get_page_from_raw(raw_page);
5757

58+
if (PageIsNew(page))
59+
PG_RETURN_NULL();
60+
5861
/* verify the special space has the expected size */
5962
if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GISTPageOpaqueData)))
6063
ereport(ERROR,
@@ -156,6 +159,9 @@ gist_page_items_bytea(PG_FUNCTION_ARGS)
156159

157160
page = get_page_from_raw(raw_page);
158161

162+
if (PageIsNew(page))
163+
PG_RETURN_NULL();
164+
159165
/* verify the special space has the expected size */
160166
if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GISTPageOpaqueData)))
161167
ereport(ERROR,
@@ -272,6 +278,12 @@ gist_page_items(PG_FUNCTION_ARGS)
272278

273279
page = get_page_from_raw(raw_page);
274280

281+
if (PageIsNew(page))
282+
{
283+
index_close(indexRel, AccessShareLock);
284+
PG_RETURN_NULL();
285+
}
286+
275287
/* Avoid bogus PageGetMaxOffsetNumber() call with deleted pages */
276288
if (GistPageIsDeleted(page))
277289
elog(NOTICE, "page is deleted");

contrib/pageinspect/rawpage.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,9 @@ page_checksum_internal(PG_FUNCTION_ARGS, enum pageinspect_version ext_version)
349349

350350
page = get_page_from_raw(raw_page);
351351

352+
if (PageIsNew(page))
353+
PG_RETURN_NULL();
354+
352355
PG_RETURN_INT16(pg_checksum_page((char *) page, blkno));
353356
}
354357

contrib/pageinspect/sql/brin.sql

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,11 @@ SELECT * FROM brin_metapage_info(get_raw_page('test1', 0));
2727
SELECT * FROM brin_revmap_data(get_raw_page('test1', 0));
2828
\set VERBOSITY default
2929

30+
-- Tests with all-zero pages.
31+
SHOW block_size \gset
32+
SELECT brin_page_type(decode(repeat('00', :block_size), 'hex'));
33+
SELECT brin_page_items(decode(repeat('00', :block_size), 'hex'), 'test1_a_idx');
34+
SELECT brin_metapage_info(decode(repeat('00', :block_size), 'hex'));
35+
SELECT brin_revmap_data(decode(repeat('00', :block_size), 'hex'));
36+
3037
DROP TABLE test1;

contrib/pageinspect/sql/btree.sql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,8 @@ SELECT bt_page_items(get_raw_page('test1', 0));
4444
SELECT bt_page_items(get_raw_page('test1_a_brin', 0));
4545
\set VERBOSITY default
4646

47+
-- Tests with all-zero pages.
48+
SHOW block_size \gset
49+
SELECT bt_page_items(decode(repeat('00', :block_size), 'hex'));
50+
4751
DROP TABLE test1;

contrib/pageinspect/sql/gin.sql

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,10 @@ SELECT * FROM gin_page_opaque_info(get_raw_page('test1', 0));
3232
SELECT * FROM gin_leafpage_items(get_raw_page('test1', 0));
3333
\set VERBOSITY default
3434

35+
-- Tests with all-zero pages.
36+
SHOW block_size \gset
37+
SELECT gin_leafpage_items(decode(repeat('00', :block_size), 'hex'));
38+
SELECT gin_metapage_info(decode(repeat('00', :block_size), 'hex'));
39+
SELECT gin_page_opaque_info(decode(repeat('00', :block_size), 'hex'));
40+
3541
DROP TABLE test1;

contrib/pageinspect/sql/gist.sql

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,10 @@ SELECT gist_page_items_bytea(get_raw_page('test_gist', 0));
4444
SELECT gist_page_items_bytea(get_raw_page('test_gist_btree', 0));
4545
\set VERBOSITY default
4646

47+
-- Tests with all-zero pages.
48+
SHOW block_size \gset
49+
SELECT gist_page_items_bytea(decode(repeat('00', :block_size), 'hex'));
50+
SELECT gist_page_items(decode(repeat('00', :block_size), 'hex'), 'test_gist_idx'::regclass);
51+
SELECT gist_page_opaque_info(decode(repeat('00', :block_size), 'hex'));
52+
4753
DROP TABLE test_gist;

contrib/pageinspect/sql/hash.sql

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,11 @@ SELECT hash_page_stats(get_raw_page('test_hash', 0));
9898
SELECT hash_page_type(get_raw_page('test_hash', 0));
9999
\set VERBOSITY default
100100

101+
-- Tests with all-zero pages.
102+
SHOW block_size \gset
103+
SELECT hash_metapage_info(decode(repeat('00', :block_size), 'hex'));
104+
SELECT hash_page_items(decode(repeat('00', :block_size), 'hex'));
105+
SELECT hash_page_stats(decode(repeat('00', :block_size), 'hex'));
106+
SELECT hash_page_type(decode(repeat('00', :block_size), 'hex'));
107+
101108
DROP TABLE test_hash;

contrib/pageinspect/sql/page.sql

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,9 @@ SELECT fsm_page_contents('aaa'::bytea);
9191
SELECT page_checksum('bbb'::bytea, 0);
9292
SELECT page_header('ccc'::bytea);
9393
\set VERBOSITY default
94+
95+
-- Tests with all-zero pages.
96+
SHOW block_size \gset
97+
SELECT fsm_page_contents(decode(repeat('00', :block_size), 'hex'));
98+
SELECT page_header(decode(repeat('00', :block_size), 'hex'));
99+
SELECT page_checksum(decode(repeat('00', :block_size), 'hex'), 1);

0 commit comments

Comments
 (0)