1
0
mirror of https://github.com/postgres/postgres.git synced 2025-04-21 12:05:57 +03:00

pageinspect: Fix crash with gist_page_items()

Attempting to use this function with a raw page not coming from a GiST
index would cause a crash, as it was missing the same sanity checks as
gist_page_items_bytea().  This slightly refactors the code so as all the
basic validation checks for GiST pages are done in a single routine,
in the same fashion as the pageinspect functions for hash and BRIN.

This fixes an issue similar to 076f4d9.  A test is added to stress for
this case.  While on it, I have added a similar test for
brin_page_items() with a combination make of a valid GiST index and a
raw btree page.  This one was already protected, but it was not tested.

Reported-by: Egor Chindyaskin
Author: Dmitry Koval
Discussion: https://postgr.es/m/17815-fc4a2d3b74705703@postgresql.org
Backpatch-through: 14
This commit is contained in:
Michael Paquier 2023-03-02 14:03:21 +09:00
parent 1a9356f657
commit 5ad63eee13
5 changed files with 65 additions and 59 deletions

View File

@ -48,12 +48,14 @@ SELECT * FROM brin_page_items(get_raw_page('test1_a_idx', 2), 'test1_a_idx')
1 | 0 | 1 | f | f | f | {1 .. 1} 1 | 0 | 1 | f | f | f | {1 .. 1}
(1 row) (1 row)
-- Failure for non-BRIN index. -- Mask DETAIL messages as these are not portable across architectures.
\set VERBOSITY terse
-- Failures for non-BRIN index.
CREATE INDEX test1_a_btree ON test1 (a); CREATE INDEX test1_a_btree ON test1 (a);
SELECT brin_page_items(get_raw_page('test1_a_btree', 0), 'test1_a_btree'); SELECT brin_page_items(get_raw_page('test1_a_btree', 0), 'test1_a_btree');
ERROR: "test1_a_btree" is not a BRIN index ERROR: "test1_a_btree" is not a BRIN index
-- Mask DETAIL messages as these are not portable across architectures. SELECT brin_page_items(get_raw_page('test1_a_btree', 0), 'test1_a_idx');
\set VERBOSITY terse ERROR: input page is not a valid BRIN page
-- Invalid special area size -- Invalid special area size
SELECT brin_page_type(get_raw_page('test1', 0)); SELECT brin_page_type(get_raw_page('test1', 0));
ERROR: input page is not a valid BRIN page ERROR: input page is not a valid BRIN page

View File

@ -66,14 +66,16 @@ SELECT itemoffset, ctid, itemlen FROM gist_page_items_bytea(get_raw_page('test_g
7 | (7,65535) | 40 7 | (7,65535) | 40
(7 rows) (7 rows)
-- Failure with non-GiST index.
CREATE INDEX test_gist_btree on test_gist(t);
SELECT gist_page_items(get_raw_page('test_gist_btree', 0), 'test_gist_btree');
ERROR: "test_gist_btree" is not a GiST index
-- Failure with various modes.
-- Suppress the DETAIL message, to allow the tests to work across various -- Suppress the DETAIL message, to allow the tests to work across various
-- page sizes and architectures. -- page sizes and architectures.
\set VERBOSITY terse \set VERBOSITY terse
-- Failures with non-GiST index.
CREATE INDEX test_gist_btree on test_gist(t);
SELECT gist_page_items(get_raw_page('test_gist_btree', 0), 'test_gist_btree');
ERROR: "test_gist_btree" is not a GiST index
SELECT gist_page_items(get_raw_page('test_gist_btree', 0), 'test_gist_idx');
ERROR: input page is not a valid GiST page
-- Failure with various modes.
-- invalid page size -- invalid page size
SELECT gist_page_items_bytea('aaa'::bytea); SELECT gist_page_items_bytea('aaa'::bytea);
ERROR: invalid page size ERROR: invalid page size

View File

@ -34,29 +34,20 @@ PG_FUNCTION_INFO_V1(gist_page_items_bytea);
#define ItemPointerGetDatum(X) PointerGetDatum(X) #define ItemPointerGetDatum(X) PointerGetDatum(X)
Datum static Page verify_gist_page(bytea *raw_page);
gist_page_opaque_info(PG_FUNCTION_ARGS)
/*
* Verify that the given bytea contains a GIST page or die in the attempt.
* A pointer to the page is returned.
*/
static Page
verify_gist_page(bytea *raw_page)
{ {
bytea *raw_page = PG_GETARG_BYTEA_P(0); Page page = get_page_from_raw(raw_page);
TupleDesc tupdesc;
Page page;
GISTPageOpaque opaq; GISTPageOpaque opaq;
HeapTuple resultTuple;
Datum values[4];
bool nulls[4];
Datum flags[16];
int nflags = 0;
uint16 flagbits;
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to use raw page functions")));
page = get_page_from_raw(raw_page);
if (PageIsNew(page)) if (PageIsNew(page))
PG_RETURN_NULL(); return page;
/* verify the special space has the expected size */ /* verify the special space has the expected size */
if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GISTPageOpaqueData))) if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GISTPageOpaqueData)))
@ -76,12 +67,38 @@ gist_page_opaque_info(PG_FUNCTION_ARGS)
GIST_PAGE_ID, GIST_PAGE_ID,
opaq->gist_page_id))); opaq->gist_page_id)));
return page;
}
Datum
gist_page_opaque_info(PG_FUNCTION_ARGS)
{
bytea *raw_page = PG_GETARG_BYTEA_P(0);
TupleDesc tupdesc;
Page page;
HeapTuple resultTuple;
Datum values[4];
bool nulls[4];
Datum flags[16];
int nflags = 0;
uint16 flagbits;
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to use raw page functions")));
page = verify_gist_page(raw_page);
if (PageIsNew(page))
PG_RETURN_NULL();
/* Build a tuple descriptor for our result type */ /* Build a tuple descriptor for our result type */
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
elog(ERROR, "return type must be a row type"); elog(ERROR, "return type must be a row type");
/* Convert the flags bitmask to an array of human-readable names */ /* Convert the flags bitmask to an array of human-readable names */
flagbits = opaq->flags; flagbits = GistPageGetOpaque(page)->flags;
if (flagbits & F_LEAF) if (flagbits & F_LEAF)
flags[nflags++] = CStringGetTextDatum("leaf"); flags[nflags++] = CStringGetTextDatum("leaf");
if (flagbits & F_DELETED) if (flagbits & F_DELETED)
@ -103,7 +120,7 @@ gist_page_opaque_info(PG_FUNCTION_ARGS)
values[0] = LSNGetDatum(PageGetLSN(page)); values[0] = LSNGetDatum(PageGetLSN(page));
values[1] = LSNGetDatum(GistPageGetNSN(page)); values[1] = LSNGetDatum(GistPageGetNSN(page));
values[2] = Int64GetDatum(opaq->rightlink); values[2] = Int64GetDatum(GistPageGetOpaque(page)->rightlink);
values[3] = PointerGetDatum(construct_array(flags, nflags, values[3] = PointerGetDatum(construct_array(flags, nflags,
TEXTOID, TEXTOID,
-1, false, TYPALIGN_INT)); -1, false, TYPALIGN_INT));
@ -124,7 +141,6 @@ gist_page_items_bytea(PG_FUNCTION_ARGS)
Tuplestorestate *tupstore; Tuplestorestate *tupstore;
MemoryContext oldcontext; MemoryContext oldcontext;
Page page; Page page;
GISTPageOpaque opaq;
OffsetNumber offset; OffsetNumber offset;
OffsetNumber maxoff = InvalidOffsetNumber; OffsetNumber maxoff = InvalidOffsetNumber;
@ -157,29 +173,11 @@ gist_page_items_bytea(PG_FUNCTION_ARGS)
MemoryContextSwitchTo(oldcontext); MemoryContextSwitchTo(oldcontext);
page = get_page_from_raw(raw_page); page = verify_gist_page(raw_page);
if (PageIsNew(page)) if (PageIsNew(page))
PG_RETURN_NULL(); PG_RETURN_NULL();
/* verify the special space has the expected size */
if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GISTPageOpaqueData)))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("input page is not a valid %s page", "GiST"),
errdetail("Expected special size %d, got %d.",
(int) MAXALIGN(sizeof(GISTPageOpaqueData)),
(int) PageGetSpecialSize(page))));
opaq = (GISTPageOpaque) PageGetSpecialPointer(page);
if (opaq->gist_page_id != GIST_PAGE_ID)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("input page is not a valid %s page", "GiST"),
errdetail("Expected %08x, got %08x.",
GIST_PAGE_ID,
opaq->gist_page_id)));
/* Avoid bogus PageGetMaxOffsetNumber() call with deleted pages */ /* Avoid bogus PageGetMaxOffsetNumber() call with deleted pages */
if (GistPageIsDeleted(page)) if (GistPageIsDeleted(page))
elog(NOTICE, "page is deleted"); elog(NOTICE, "page is deleted");
@ -276,7 +274,7 @@ gist_page_items(PG_FUNCTION_ARGS)
errmsg("\"%s\" is not a %s index", errmsg("\"%s\" is not a %s index",
RelationGetRelationName(indexRel), "GiST"))); RelationGetRelationName(indexRel), "GiST")));
page = get_page_from_raw(raw_page); page = verify_gist_page(raw_page);
if (PageIsNew(page)) if (PageIsNew(page))
{ {

View File

@ -15,12 +15,14 @@ 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') SELECT * FROM brin_page_items(get_raw_page('test1_a_idx', 2), 'test1_a_idx')
ORDER BY blknum, attnum LIMIT 5; 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');
-- Mask DETAIL messages as these are not portable across architectures. -- Mask DETAIL messages as these are not portable across architectures.
\set VERBOSITY terse \set VERBOSITY terse
-- Failures 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');
SELECT brin_page_items(get_raw_page('test1_a_btree', 0), 'test1_a_idx');
-- Invalid special area size -- Invalid special area size
SELECT brin_page_type(get_raw_page('test1', 0)); SELECT brin_page_type(get_raw_page('test1', 0));
SELECT * FROM brin_metapage_info(get_raw_page('test1', 0)); SELECT * FROM brin_metapage_info(get_raw_page('test1', 0));

View File

@ -26,14 +26,16 @@ 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. -- 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)); SELECT itemoffset, ctid, itemlen FROM gist_page_items_bytea(get_raw_page('test_gist_idx', 0));
-- Failure with non-GiST index.
CREATE INDEX test_gist_btree on test_gist(t);
SELECT gist_page_items(get_raw_page('test_gist_btree', 0), 'test_gist_btree');
-- Failure with various modes.
-- Suppress the DETAIL message, to allow the tests to work across various -- Suppress the DETAIL message, to allow the tests to work across various
-- page sizes and architectures. -- page sizes and architectures.
\set VERBOSITY terse \set VERBOSITY terse
-- Failures with non-GiST index.
CREATE INDEX test_gist_btree on test_gist(t);
SELECT gist_page_items(get_raw_page('test_gist_btree', 0), 'test_gist_btree');
SELECT gist_page_items(get_raw_page('test_gist_btree', 0), 'test_gist_idx');
-- Failure with various modes.
-- invalid page size -- invalid page size
SELECT gist_page_items_bytea('aaa'::bytea); SELECT gist_page_items_bytea('aaa'::bytea);
SELECT gist_page_items('aaa'::bytea, 'test_gist_idx'::regclass); SELECT gist_page_items('aaa'::bytea, 'test_gist_idx'::regclass);