pageinspect: Fix handling of page sizes and AM types
authorMichael Paquier <[email protected]>
Wed, 16 Mar 2022 02:21:00 +0000 (11:21 +0900)
committerMichael Paquier <[email protected]>
Wed, 16 Mar 2022 02:21:00 +0000 (11:21 +0900)
This commit fixes a set of issues related to the use of the SQL
functions in this module when the caller is able to pass down raw page
data as input argument:
- The page size check was fuzzy in a couple of places, sometimes
looking after only a sub-range, but what we are looking for is an exact
match on BLCKSZ.  After considering a few options here, I have settled
down to do a generalization of get_page_from_raw().  Most of the SQL
functions already used that, and this is not strictly required if not
accessing an 8-byte-wide value from a raw page, but this feels safer in
the long run for alignment-picky environment, particularly if a code
path begins to access such values.  This also reduces the number of
strings that need to be translated.
- The BRIN function brin_page_items() uses a Relation but it did not
check the access method of the opened index, potentially leading to
crashes.  All the other functions in need of a Relation already did
that.
- Some code paths could fail on elog(), but we should to use ereport()
for failures that can be triggered by the user.

Tests are added to stress all the cases that are fixed as of this
commit, with some junk raw pages (\set VERBOSITY ensures that this works
across all page sizes) and unexpected index types when functions open
relations.

Author: Michael Paquier, Justin Prysby
Discussion: https://postgr.es/m/20220218030020[email protected]
Backpatch-through: 10

15 files changed:
contrib/pageinspect/brinfuncs.c
contrib/pageinspect/btreefuncs.c
contrib/pageinspect/expected/brin.out
contrib/pageinspect/expected/btree.out
contrib/pageinspect/expected/gin.out
contrib/pageinspect/expected/hash.out
contrib/pageinspect/expected/page.out
contrib/pageinspect/fsmfuncs.c
contrib/pageinspect/hashfuncs.c
contrib/pageinspect/rawpage.c
contrib/pageinspect/sql/brin.sql
contrib/pageinspect/sql/btree.sql
contrib/pageinspect/sql/gin.sql
contrib/pageinspect/sql/hash.sql
contrib/pageinspect/sql/page.sql

index 13da7616e75964893cc4894c2da600e0294bc8e5..64d8edb90059fd6e3252e1eec6a1dbdc5aec3592 100644 (file)
@@ -18,6 +18,7 @@
 #include "access/brin_revmap.h"
 #include "access/brin_tuple.h"
 #include "catalog/index.h"
+#include "catalog/pg_am.h"
 #include "catalog/pg_type.h"
 #include "funcapi.h"
 #include "lib/stringinfo.h"
@@ -33,6 +34,8 @@ PG_FUNCTION_INFO_V1(brin_page_items);
 PG_FUNCTION_INFO_V1(brin_metapage_info);
 PG_FUNCTION_INFO_V1(brin_revmap_data);
 
+#define IS_BRIN(r) ((r)->rd_rel->relam == BRIN_AM_OID)
+
 typedef struct brin_column_state
 {
    int         nstored;
@@ -47,8 +50,7 @@ Datum
 brin_page_type(PG_FUNCTION_ARGS)
 {
    bytea      *raw_page = PG_GETARG_BYTEA_P(0);
-   Page        page = VARDATA(raw_page);
-   int         raw_page_size;
+   Page        page;
    char       *type;
 
    if (!superuser())
@@ -56,14 +58,7 @@ brin_page_type(PG_FUNCTION_ARGS)
                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                 (errmsg("must be superuser to use raw page functions"))));
 
-   raw_page_size = VARSIZE(raw_page) - VARHDRSZ;
-
-   if (raw_page_size != BLCKSZ)
-       ereport(ERROR,
-               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                errmsg("input page too small"),
-                errdetail("Expected size %d, got %d",
-                          BLCKSZ, raw_page_size)));
+   page = get_page_from_raw(raw_page);
 
    switch (BrinPageType(page))
    {
@@ -91,19 +86,7 @@ brin_page_type(PG_FUNCTION_ARGS)
 static Page
 verify_brin_page(bytea *raw_page, uint16 type, const char *strtype)
 {
-   Page        page;
-   int         raw_page_size;
-
-   raw_page_size = VARSIZE(raw_page) - VARHDRSZ;
-
-   if (raw_page_size != BLCKSZ)
-       ereport(ERROR,
-               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                errmsg("input page too small"),
-                errdetail("Expected size %d, got %d",
-                          BLCKSZ, raw_page_size)));
-
-   page = VARDATA(raw_page);
+   Page        page = get_page_from_raw(raw_page);
 
    /* verify the special space says this page is what we want */
    if (BrinPageType(page) != type)
@@ -171,6 +154,13 @@ brin_page_items(PG_FUNCTION_ARGS)
    MemoryContextSwitchTo(oldcontext);
 
    indexRel = index_open(indexRelid, AccessShareLock);
+
+   if (!IS_BRIN(indexRel))
+       ereport(ERROR,
+               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                errmsg("\"%s\" is not a %s index",
+                       RelationGetRelationName(indexRel), "BRIN")));
+
    bdesc = brin_build_desc(indexRel);
 
    /* minimally verify the page we got */
index 4f834676ea297f1ba4e2c5a8fde10382e4edc0e9..ea445fdf31e1e9a75725a1cf6dda0bf3342f9bae 100644 (file)
@@ -181,8 +181,10 @@ bt_page_stats(PG_FUNCTION_ARGS)
    rel = relation_openrv(relrv, AccessShareLock);
 
    if (!IS_INDEX(rel) || !IS_BTREE(rel))
-       elog(ERROR, "relation \"%s\" is not a btree index",
-            RelationGetRelationName(rel));
+       ereport(ERROR,
+               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                errmsg("\"%s\" is not a %s index",
+                       RelationGetRelationName(rel), "btree")));
 
    /*
     * Reject attempts to read non-local temporary relations; we would be
@@ -335,8 +337,10 @@ bt_page_items(PG_FUNCTION_ARGS)
        rel = relation_openrv(relrv, AccessShareLock);
 
        if (!IS_INDEX(rel) || !IS_BTREE(rel))
-           elog(ERROR, "relation \"%s\" is not a btree index",
-                RelationGetRelationName(rel));
+           ereport(ERROR,
+                   (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                    errmsg("\"%s\" is not a %s index",
+                           RelationGetRelationName(rel), "btree")));
 
        /*
         * Reject attempts to read non-local temporary relations; we would be
@@ -424,7 +428,6 @@ bt_page_items_bytea(PG_FUNCTION_ARGS)
    Datum       result;
    FuncCallContext *fctx;
    struct user_args *uargs;
-   int         raw_page_size;
 
    if (!superuser())
        ereport(ERROR,
@@ -437,19 +440,12 @@ bt_page_items_bytea(PG_FUNCTION_ARGS)
        MemoryContext mctx;
        TupleDesc   tupleDesc;
 
-       raw_page_size = VARSIZE(raw_page) - VARHDRSZ;
-
-       if (raw_page_size < SizeOfPageHeaderData)
-           ereport(ERROR,
-                   (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                    errmsg("input page too small (%d bytes)", raw_page_size)));
-
        fctx = SRF_FIRSTCALL_INIT();
        mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
 
        uargs = palloc(sizeof(struct user_args));
 
-       uargs->page = VARDATA(raw_page);
+       uargs->page = get_page_from_raw(raw_page);
 
        uargs->offset = FirstOffsetNumber;
 
@@ -525,8 +521,10 @@ bt_metap(PG_FUNCTION_ARGS)
    rel = relation_openrv(relrv, AccessShareLock);
 
    if (!IS_INDEX(rel) || !IS_BTREE(rel))
-       elog(ERROR, "relation \"%s\" is not a btree index",
-            RelationGetRelationName(rel));
+       ereport(ERROR,
+               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                errmsg("\"%s\" is not a %s index",
+                       RelationGetRelationName(rel), "btree")));
 
    /*
     * Reject attempts to read non-local temporary relations; we would be
index 71eb190380cfe51a29e57ea37e67057f208122a3..10cd36c1778c1035e2ed6d677f3911757b80d5dc 100644 (file)
@@ -48,4 +48,8 @@ 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)
 
+-- Failure for non-BRIN index.
+CREATE INDEX test1_a_btree ON test1 (a);
+SELECT brin_page_items(get_raw_page('test1_a_btree', 0), 'test1_a_btree');
+ERROR:  "test1_a_btree" is not a BRIN index
 DROP TABLE test1;
index 67b103add3fef7c2dbb952716cbaa8d41bfacc1e..85dc797104aff5056bbcfc66d0292c75ff72b021 100644 (file)
@@ -55,4 +55,19 @@ data       | 01 00 00 00 00 00 00 01
 
 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"
+-- Failure when using a non-btree index.
+CREATE INDEX test1_a_hash ON test1 USING hash(a);
+SELECT bt_metap('test1_a_hash');
+ERROR:  "test1_a_hash" is not a btree index
+SELECT bt_page_stats('test1_a_hash', 0);
+ERROR:  "test1_a_hash" is not a btree index
+SELECT bt_page_items('test1_a_hash', 0);
+ERROR:  "test1_a_hash" is not a btree index
+-- Failure with incorrect page size
+-- Suppress the DETAIL message, to allow the tests to work across various
+-- page sizes.
+\set VERBOSITY terse
+SELECT bt_page_items('aaa'::bytea);
+ERROR:  invalid page size
+\set VERBOSITY default
 DROP TABLE test1;
index 82f63b23b19d7315731151567feb6a1b144c168b..c0f759509875e9881e28f1bde8514d07524febdf 100644 (file)
@@ -35,3 +35,14 @@ FROM gin_leafpage_items(get_raw_page('test1_y_idx',
 -[ RECORD 1 ]
 ?column? | t
 
+-- Failure with incorrect page size
+-- Suppress the DETAIL message, to allow the tests to work across various
+-- page sizes.
+\set VERBOSITY terse
+SELECT gin_leafpage_items('aaa'::bytea);
+ERROR:  invalid page size
+SELECT gin_metapage_info('bbb'::bytea);
+ERROR:  invalid page size
+SELECT gin_page_opaque_info('ccc'::bytea);
+ERROR:  invalid page size
+\set VERBOSITY default
index 75d7bcfad5f74bae8dbad2fe6e50cf10bf1fd9e5..12369ddfa29dede36c33599a82045a74ecb00f7c 100644 (file)
@@ -159,4 +159,21 @@ 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
+-- Failure with non-hash index
+CREATE INDEX test_hash_a_btree ON test_hash USING btree (a);
+SELECT hash_bitmap_info('test_hash_a_btree', 0);
+ERROR:  "test_hash_a_btree" is not a hash index
+-- Failure with incorrect page size
+-- Suppress the DETAIL message, to allow the tests to work across various
+-- page sizes.
+\set VERBOSITY terse
+SELECT hash_metapage_info('aaa'::bytea);
+ERROR:  invalid page size
+SELECT hash_page_items('bbb'::bytea);
+ERROR:  invalid page size
+SELECT hash_page_stats('ccc'::bytea);
+ERROR:  invalid page size
+SELECT hash_page_type('ddd'::bytea);
+ERROR:  invalid page size
+\set VERBOSITY default
 DROP TABLE test_hash;
index 8e0300a41d1f55e2e1a3e4f57d475597a7e1ffb3..f62a3f7bd56e44a0157021e63d2b5e5a0dcd4bb4 100644 (file)
@@ -109,3 +109,14 @@ select tuple_data_split('test8'::regclass, t_data, t_infomask, t_infomask2, t_bi
 (1 row)
 
 drop table test8;
+-- Failure with incorrect page size
+-- Suppress the DETAIL message, to allow the tests to work across various
+-- page sizes.
+\set VERBOSITY terse
+SELECT fsm_page_contents('aaa'::bytea);
+ERROR:  invalid page size
+SELECT page_checksum('bbb'::bytea, 0);
+ERROR:  invalid page size
+SELECT page_header('ccc'::bytea);
+ERROR:  invalid page size
+\set VERBOSITY default
index 615dab8b13b6d1c8008c70371d19325aece91fef..51a252c87e380dd59e6f6d918a3a1ec05440fea9 100644 (file)
@@ -37,6 +37,7 @@ fsm_page_contents(PG_FUNCTION_ARGS)
 {
    bytea      *raw_page = PG_GETARG_BYTEA_P(0);
    StringInfoData sinfo;
+   Page        page;
    FSMPage     fsmpage;
    int         i;
 
@@ -45,7 +46,8 @@ fsm_page_contents(PG_FUNCTION_ARGS)
                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                 (errmsg("must be superuser to use raw page functions"))));
 
-   fsmpage = (FSMPage) PageGetContents(VARDATA(raw_page));
+   page = get_page_from_raw(raw_page);
+   fsmpage = (FSMPage) PageGetContents(page);
 
    initStringInfo(&sinfo);
 
index 778e15dde4936a5bd9ebeb824c8fa1b957f9d84b..2a7aeedcecbb75f3316fc7ee7d52e059c2e1ce6f 100644 (file)
@@ -415,8 +415,10 @@ hash_bitmap_info(PG_FUNCTION_ARGS)
    indexRel = index_open(indexRelid, AccessShareLock);
 
    if (!IS_HASH(indexRel))
-       elog(ERROR, "relation \"%s\" is not a hash index",
-            RelationGetRelationName(indexRel));
+       ereport(ERROR,
+               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                errmsg("\"%s\" is not a %s index",
+                       RelationGetRelationName(indexRel), "hash")));
 
    if (RELATION_IS_OTHER_TEMP(indexRel))
        ereport(ERROR,
index e9d3131bda4cad99fa1e6a95fed8d74a37268f4d..6b3e99f8b2b629636545f6a8baf443025dce546d 100644 (file)
@@ -214,7 +214,6 @@ Datum
 page_header(PG_FUNCTION_ARGS)
 {
    bytea      *raw_page = PG_GETARG_BYTEA_P(0);
-   int         raw_page_size;
 
    TupleDesc   tupdesc;
 
@@ -231,18 +230,7 @@ page_header(PG_FUNCTION_ARGS)
                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                 (errmsg("must be superuser to use raw page functions"))));
 
-   raw_page_size = VARSIZE(raw_page) - VARHDRSZ;
-
-   /*
-    * Check that enough data was supplied, so that we don't try to access
-    * fields outside the supplied buffer.
-    */
-   if (raw_page_size < SizeOfPageHeaderData)
-       ereport(ERROR,
-               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                errmsg("input page too small (%d bytes)", raw_page_size)));
-
-   page = (PageHeader) VARDATA(raw_page);
+   page = (PageHeader) get_page_from_raw(raw_page);
 
    /* Build a tuple descriptor for our result type */
    if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
@@ -295,25 +283,14 @@ page_checksum(PG_FUNCTION_ARGS)
 {
    bytea      *raw_page = PG_GETARG_BYTEA_P(0);
    uint32      blkno = PG_GETARG_INT32(1);
-   int         raw_page_size;
-   PageHeader  page;
+   Page        page;
 
    if (!superuser())
        ereport(ERROR,
                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                 (errmsg("must be superuser to use raw page functions"))));
 
-   raw_page_size = VARSIZE(raw_page) - VARHDRSZ;
-
-   /*
-    * Check that the supplied page is of the right size.
-    */
-   if (raw_page_size != BLCKSZ)
-       ereport(ERROR,
-               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                errmsg("incorrect size of input page (%d bytes)", raw_page_size)));
-
-   page = (PageHeader) VARDATA(raw_page);
+   page = get_page_from_raw(raw_page);
 
    PG_RETURN_INT16(pg_checksum_page((char *) page, blkno));
 }
index 735bc3b6733db4ce99d4e1f501939d2b00e73a65..8717229c5d2f85fdd6930578e287c8247ca3f1c6 100644 (file)
@@ -15,4 +15,8 @@ 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;
 
+-- Failure for non-BRIN index.
+CREATE INDEX test1_a_btree ON test1 (a);
+SELECT brin_page_items(get_raw_page('test1_a_btree', 0), 'test1_a_btree');
+
 DROP TABLE test1;
index 8eac64c7b3cb7827160646e38f60819f30e1b43d..174814fb7a4590d87b7f63b9c460d27a802a34d0 100644 (file)
@@ -18,4 +18,17 @@ 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));
 
+-- Failure when using a non-btree index.
+CREATE INDEX test1_a_hash ON test1 USING hash(a);
+SELECT bt_metap('test1_a_hash');
+SELECT bt_page_stats('test1_a_hash', 0);
+SELECT bt_page_items('test1_a_hash', 0);
+
+-- Failure with incorrect page size
+-- Suppress the DETAIL message, to allow the tests to work across various
+-- page sizes.
+\set VERBOSITY terse
+SELECT bt_page_items('aaa'::bytea);
+\set VERBOSITY default
+
 DROP TABLE test1;
index d516ed3cbd4416fd86531baf358ea9147e551d3a..2a653609750f16838de86a3746c7ec4e4b03c7fd 100644 (file)
@@ -17,3 +17,12 @@ SELECT COUNT(*) > 0
 FROM gin_leafpage_items(get_raw_page('test1_y_idx',
                         (pg_relation_size('test1_y_idx') /
                          current_setting('block_size')::bigint)::int - 1));
+
+-- Failure with incorrect page size
+-- Suppress the DETAIL message, to allow the tests to work across various
+-- page sizes.
+\set VERBOSITY terse
+SELECT gin_leafpage_items('aaa'::bytea);
+SELECT gin_metapage_info('bbb'::bytea);
+SELECT gin_page_opaque_info('ccc'::bytea);
+\set VERBOSITY default
index 87ee549a7b4f56e6ff3cbbe8b3eb0ca5c2a8ff72..546c780e0e4c5d4bb7d208bb439a84d953fb0ed3 100644 (file)
@@ -76,5 +76,18 @@ 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));
 
+-- Failure with non-hash index
+CREATE INDEX test_hash_a_btree ON test_hash USING btree (a);
+SELECT hash_bitmap_info('test_hash_a_btree', 0);
+
+-- Failure with incorrect page size
+-- Suppress the DETAIL message, to allow the tests to work across various
+-- page sizes.
+\set VERBOSITY terse
+SELECT hash_metapage_info('aaa'::bytea);
+SELECT hash_page_items('bbb'::bytea);
+SELECT hash_page_stats('ccc'::bytea);
+SELECT hash_page_type('ddd'::bytea);
+\set VERBOSITY default
 
 DROP TABLE test_hash;
index eb50e65a867b03ca908abb63dd22c5845951f25b..068dc582efc30ca1583792c4173c29667ce49ea9 100644 (file)
@@ -49,3 +49,12 @@ select t_bits, t_data from heap_page_items(get_raw_page('test8', 0));
 select tuple_data_split('test8'::regclass, t_data, t_infomask, t_infomask2, t_bits)
     from heap_page_items(get_raw_page('test8', 0));
 drop table test8;
+
+-- Failure with incorrect page size
+-- Suppress the DETAIL message, to allow the tests to work across various
+-- page sizes.
+\set VERBOSITY terse
+SELECT fsm_page_contents('aaa'::bytea);
+SELECT page_checksum('bbb'::bytea, 0);
+SELECT page_header('ccc'::bytea);
+\set VERBOSITY default