Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
pageinspect: Fix handling of page sizes and AM types
authorMichael Paquier <michael@paquier.xyz>
Wed, 16 Mar 2022 02:20:51 +0000 (11:20 +0900)
committerMichael Paquier <michael@paquier.xyz>
Wed, 16 Mar 2022 02:20:51 +0000 (11:20 +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.GA1137@telsasoft.com
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 fb32d74a66a98b7c35e762f55c9b12f6887b3eb2..733e18566140dd5e5b24d92c17d042267e76a517 100644 (file)
@@ -16,6 +16,7 @@
 #include "access/brin_tuple.h"
 #include "access/htup_details.h"
 #include "catalog/index.h"
+#include "catalog/pg_am_d.h"
 #include "catalog/pg_type.h"
 #include "funcapi.h"
 #include "lib/stringinfo.h"
@@ -31,6 +32,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;
@@ -45,8 +48,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())
@@ -54,14 +56,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))
    {
@@ -89,19 +84,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)
@@ -169,6 +152,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 e7a323044bf9ad081fc2287ba52bc6a83ad5b0ba..d6a277684d993e1441ce09b957b44b23ad4a8c4e 100644 (file)
@@ -184,8 +184,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
@@ -434,8 +436,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
@@ -522,7 +526,6 @@ bt_page_items_bytea(PG_FUNCTION_ARGS)
    Datum       result;
    FuncCallContext *fctx;
    struct user_args *uargs;
-   int         raw_page_size;
 
    if (!superuser())
        ereport(ERROR,
@@ -535,19 +538,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;
 
@@ -625,8 +621,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 17bf0c5470825611edb73643b6d04bcc5ee1b630..3e431ce3c585e5e8bf24b84d6ae2cb695308ce02 100644 (file)
@@ -64,4 +64,19 @@ 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"
+-- 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 29ea91135be9067165c4e13a41ea922bca42cdb2..74deb23c598b381ef233a5eea672da0b7752044b 100644 (file)
@@ -201,3 +201,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 099acbb2fe4b068b1b37b8ff4d1e2a81925cb523..9f596d26fdfeb3365184f9e37c485579d8c14936 100644 (file)
@@ -36,6 +36,7 @@ fsm_page_contents(PG_FUNCTION_ARGS)
 {
    bytea      *raw_page = PG_GETARG_BYTEA_P(0);
    StringInfoData sinfo;
+   Page        page;
    FSMPage     fsmpage;
    int         i;
 
@@ -44,7 +45,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 3b2f0339cfe09edea2e175af3dafe7895622b045..ed6154f23e4445abb511a02610a85271b3e9665f 100644 (file)
@@ -417,8 +417,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 c0181506a5d0b4deb1e34c92ebc0b63be0d0ee1f..32400616219b6ea66830fdd39ee215b92cde7fb6 100644 (file)
@@ -218,7 +218,6 @@ Datum
 page_header(PG_FUNCTION_ARGS)
 {
    bytea      *raw_page = PG_GETARG_BYTEA_P(0);
-   int         raw_page_size;
 
    TupleDesc   tupdesc;
 
@@ -235,18 +234,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)
@@ -299,25 +287,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 9a3bc953862b50c7dae8049ab99e3c661040ba80..bb78546e177495d65bfeaae46a0d0228703a3598 100644 (file)
@@ -80,3 +80,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