diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c index f5e68906b2..b7ea745964 100644 --- a/contrib/amcheck/verify_heapam.c +++ b/contrib/amcheck/verify_heapam.c @@ -113,6 +113,7 @@ verify_heapam(PG_FUNCTION_ARGS) FullTransactionId nextFullXid; Buffer vmbuffer = InvalidBuffer; Oid relid; + bool fatal = false; bool on_error_stop; bool skip_all_frozen = false; bool skip_all_visible = false; @@ -208,6 +209,7 @@ verify_heapam(PG_FUNCTION_ARGS) else { /* Main relation has no associated toast relation */ + ctx.toastrel = NULL; ctx.toast_indexes = NULL; ctx.num_toast_indexes = 0; } @@ -234,15 +236,25 @@ verify_heapam(PG_FUNCTION_ARGS) confess(&ctx, psprintf("relfrozenxid %u precedes global " "oldest valid xid %u ", ctx.relfrozenxid, ctx.oldestValidXid)); - PG_RETURN_NULL(); + fatal = true; } - - if (TransactionIdIsNormal(ctx.relminmxid) && + else if (TransactionIdIsNormal(ctx.relminmxid) && TransactionIdPrecedes(ctx.relminmxid, ctx.oldestValidXid)) { confess(&ctx, psprintf("relfrozenxid %u precedes global " "oldest valid xid %u ", ctx.relfrozenxid, ctx.oldestValidXid)); + fatal = true; + } + + if (fatal) + { + if (ctx.toast_indexes) + toast_close_indexes(ctx.toast_indexes, ctx.num_toast_indexes, + ShareUpdateExclusiveLock); + if (ctx.toastrel) + table_close(ctx.toastrel, ShareUpdateExclusiveLock); + relation_close(ctx.rel, ShareUpdateExclusiveLock); PG_RETURN_NULL(); } @@ -264,6 +276,7 @@ verify_heapam(PG_FUNCTION_ARGS) { int32 mapbits; OffsetNumber maxoff; + PageHeader ph; /* Optionally skip over all-frozen or all-visible blocks */ if (skip_all_frozen || skip_all_visible) @@ -281,6 +294,7 @@ verify_heapam(PG_FUNCTION_ARGS) RBM_NORMAL, ctx.bstrategy); LockBuffer(ctx.buffer, BUFFER_LOCK_SHARE); ctx.page = BufferGetPage(ctx.buffer); + ph = (PageHeader) ctx.page; /* We must unlock the page from the prior iteration, if any */ Assert(ctx.blkno == InvalidBlockNumber || ctx.buffer != InvalidBuffer); @@ -302,12 +316,33 @@ verify_heapam(PG_FUNCTION_ARGS) { ctx.itemid = PageGetItemId(ctx.page, ctx.offnum); - /* Skip over unused/dead/redirected line pointers */ - if (!ItemIdIsUsed(ctx.itemid) || - ItemIdIsDead(ctx.itemid) || - ItemIdIsRedirected(ctx.itemid)) + /* Skip over unused/dead line pointers */ + if (!ItemIdIsUsed(ctx.itemid) || ItemIdIsDead(ctx.itemid)) continue; + /* + * If this line pointer has been redirected, check that it redirects + * to a valid offset within the line pointer array. + */ + if (ItemIdIsRedirected(ctx.itemid)) + { + uint16 redirect = ItemIdGetRedirect(ctx.itemid); + if (redirect <= SizeOfPageHeaderData || redirect >= ph->pd_lower) + { + confess(&ctx, psprintf( + "Invalid redirect line pointer offset %u out of bounds", + (unsigned) redirect)); + continue; + } + if ((redirect - SizeOfPageHeaderData) % sizeof(uint16)) + { + confess(&ctx, psprintf( + "Invalid redirect line pointer offset %u bad alignment", + (unsigned) redirect)); + } + continue; + } + /* Set up context information about this next tuple */ ctx.lp_len = ItemIdGetLength(ctx.itemid); ctx.tuphdr = (HeapTupleHeader) PageGetItem(ctx.page, ctx.itemid); @@ -637,7 +672,7 @@ check_toast_tuple(HeapTuple toasttup, HeapCheckContext * ctx) { /* should never happen */ confess(ctx, - pstrdup("toast chunk is neither short nor extended")); + pstrdup("corrupt toast chunk va_header")); return; } @@ -972,16 +1007,29 @@ check_tuple(HeapCheckContext * ctx) * length nulls bitmap field t_bits does not overflow the allowed space. * We don't know if the corruption is in the natts field or the infomask * bit HEAP_HASNULL. + * + * If the tuple does not have nulls, check that no space has been + * reserved for the null bitmap. */ - if (infomask & HEAP_HASNULL && - SizeofHeapTupleHeader + BITMAPLEN(ctx->natts) > ctx->tuphdr->t_hoff) + if (infomask & HEAP_HASNULL) { - confess(ctx, psprintf("SizeofHeapTupleHeader + " - "BITMAPLEN(natts) > t_hoff " - "(%u + %u > %u)", - (unsigned) SizeofHeapTupleHeader, - BITMAPLEN(ctx->natts), - ctx->tuphdr->t_hoff)); + if (SizeofHeapTupleHeader + BITMAPLEN(ctx->natts) > ctx->tuphdr->t_hoff) + { + confess(ctx, psprintf("SizeofHeapTupleHeader + " + "BITMAPLEN(natts) > t_hoff " + "(%u + %u > %u)", + (unsigned) SizeofHeapTupleHeader, + BITMAPLEN(ctx->natts), + ctx->tuphdr->t_hoff)); + fatal = true; + } + } + else if (MAXALIGN(ctx->tuphdr->t_hoff) != MAXALIGN(SizeofHeapTupleHeader)) + { + confess(ctx, + psprintf("t_hoff = %u in tuple without nulls (expected %u)", + (unsigned) MAXALIGN(ctx->tuphdr->t_hoff), + (unsigned) MAXALIGN(SizeofHeapTupleHeader))); fatal = true; } @@ -1031,6 +1079,4 @@ check_tuple(HeapCheckContext * ctx) if (!check_tuple_attribute(ctx)) break; } - ctx->offset = -1; - ctx->attnum = -1; } diff --git a/contrib/pg_amcheck/t/004_verify_heapam.pl b/contrib/pg_amcheck/t/004_verify_heapam.pl index b2c1f36928..08bce6e68e 100644 --- a/contrib/pg_amcheck/t/004_verify_heapam.pl +++ b/contrib/pg_amcheck/t/004_verify_heapam.pl @@ -381,8 +381,11 @@ is ($result, 0|2|8064|1|58|||tuple xmin = 4026531839 precedes relation relfrozenxid = $relfrozenxid 0|3|8000|1|58|||tuple xmax = 4026531839 precedes relation relfrozenxid = $relfrozenxid 0|4|7936|1|58|||t_hoff > lp_len (152 > 58) +0|4|7936|1|58|||t_hoff = 152 in tuple without nulls (expected 24) 0|5|7872|1|58|||t_hoff not max-aligned (27) +0|5|7872|1|58|||t_hoff = 32 in tuple without nulls (expected 24) 0|6|7808|1|58|||t_hoff < SizeofHeapTupleHeader (16 < 23) +0|6|7808|1|58|||t_hoff = 16 in tuple without nulls (expected 24) 0|7|7744|1|58|||t_hoff < SizeofHeapTupleHeader (21 < 23) 0|7|7744|1|58|||t_hoff not max-aligned (21) 0|8|7680|1|58|||relation natts < tuple natts (3 < 2047)