Skip to content

Commit 321633e

Browse files
committed
Fix more portability issues in new amcheck code.
verify_heapam() wasn't being careful to sanity-check tuple line pointers before using them, resulting in SIGBUS on alignment-picky architectures. Fix that, add some more test coverage. Mark Dilger, some tweaking by me Discussion: https://postgr.es/m/30B8E99A-2D9C-48D4-A55C-741C9D5F1563@enterprisedb.com
1 parent 1b62d0f commit 321633e

File tree

2 files changed

+73
-35
lines changed

2 files changed

+73
-35
lines changed

contrib/amcheck/t/001_verify_heapam.pl

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
use PostgresNode;
55
use TestLib;
66

7-
use Test::More tests => 55;
7+
use Test::More tests => 79;
88

99
my ($node, $result);
1010

@@ -109,13 +109,17 @@ sub corrupt_first_page
109109
or BAIL_OUT("open failed: $!");
110110
binmode $fh;
111111

112-
# Corrupt two line pointers. To be stable across platforms, we use
113-
# 0x55555555 and 0xAAAAAAAA for the two, which are bitwise reverses of each
114-
# other.
112+
# Corrupt some line pointers. The values are chosen to hit the
113+
# various line-pointer-corruption checks in verify_heapam.c
114+
# on both little-endian and big-endian architectures.
115115
seek($fh, 32, 0)
116116
or BAIL_OUT("seek failed: $!");
117-
syswrite($fh, pack("L*", 0x55555555, 0xAAAAAAAA))
118-
or BAIL_OUT("syswrite failed: $!");
117+
syswrite(
118+
$fh,
119+
pack("L*",
120+
0xAAA15550, 0xAAA0D550, 0x00010000,
121+
0x00008000, 0x0000800F, 0x001e8000)
122+
) or BAIL_OUT("syswrite failed: $!");
119123
close($fh)
120124
or BAIL_OUT("close failed: $!");
121125

@@ -126,17 +130,23 @@ sub detects_heap_corruption
126130
{
127131
my ($function, $testname) = @_;
128132

129-
detects_corruption($function, $testname,
130-
qr/line pointer redirection to item at offset \d+ exceeds maximum offset \d+/
133+
detects_corruption(
134+
$function,
135+
$testname,
136+
qr/line pointer redirection to item at offset \d+ precedes minimum offset \d+/,
137+
qr/line pointer redirection to item at offset \d+ exceeds maximum offset \d+/,
138+
qr/line pointer to page offset \d+ is not maximally aligned/,
139+
qr/line pointer length \d+ is less than the minimum tuple header size \d+/,
140+
qr/line pointer to page offset \d+ with length \d+ ends beyond maximum page offset \d+/,
131141
);
132142
}
133143

134144
sub detects_corruption
135145
{
136-
my ($function, $testname, $re) = @_;
146+
my ($function, $testname, @re) = @_;
137147

138148
my $result = $node->safe_psql('postgres', qq(SELECT * FROM $function));
139-
like($result, $re, $testname);
149+
like($result, $_, $testname) for (@re);
140150
}
141151

142152
sub detects_no_corruption

contrib/amcheck/verify_heapam.c

Lines changed: 53 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ typedef struct HeapCheckContext
105105
OffsetNumber offnum;
106106
ItemId itemid;
107107
uint16 lp_len;
108+
uint16 lp_off;
108109
HeapTupleHeader tuphdr;
109110
int natts;
110111

@@ -247,6 +248,13 @@ verify_heapam(PG_FUNCTION_ARGS)
247248
memset(&ctx, 0, sizeof(HeapCheckContext));
248249
ctx.cached_xid = InvalidTransactionId;
249250

251+
/*
252+
* If we report corruption when not examining some individual attribute,
253+
* we need attnum to be reported as NULL. Set that up before any
254+
* corruption reporting might happen.
255+
*/
256+
ctx.attnum = -1;
257+
250258
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
251259
old_context = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
252260
random_access = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
@@ -378,14 +386,22 @@ verify_heapam(PG_FUNCTION_ARGS)
378386

379387
/*
380388
* If this line pointer has been redirected, check that it
381-
* redirects to a valid offset within the line pointer array.
389+
* redirects to a valid offset within the line pointer array
382390
*/
383391
if (ItemIdIsRedirected(ctx.itemid))
384392
{
385393
OffsetNumber rdoffnum = ItemIdGetRedirect(ctx.itemid);
386394
ItemId rditem;
387395

388-
if (rdoffnum < FirstOffsetNumber || rdoffnum > maxoff)
396+
if (rdoffnum < FirstOffsetNumber)
397+
{
398+
report_corruption(&ctx,
399+
psprintf("line pointer redirection to item at offset %u precedes minimum offset %u",
400+
(unsigned) rdoffnum,
401+
(unsigned) FirstOffsetNumber));
402+
continue;
403+
}
404+
if (rdoffnum > maxoff)
389405
{
390406
report_corruption(&ctx,
391407
psprintf("line pointer redirection to item at offset %u exceeds maximum offset %u",
@@ -401,8 +417,36 @@ verify_heapam(PG_FUNCTION_ARGS)
401417
continue;
402418
}
403419

404-
/* Set up context information about this next tuple */
420+
/* Sanity-check the line pointer's offset and length values */
405421
ctx.lp_len = ItemIdGetLength(ctx.itemid);
422+
ctx.lp_off = ItemIdGetOffset(ctx.itemid);
423+
424+
if (ctx.lp_off != MAXALIGN(ctx.lp_off))
425+
{
426+
report_corruption(&ctx,
427+
psprintf("line pointer to page offset %u is not maximally aligned",
428+
ctx.lp_off));
429+
continue;
430+
}
431+
if (ctx.lp_len < MAXALIGN(SizeofHeapTupleHeader))
432+
{
433+
report_corruption(&ctx,
434+
psprintf("line pointer length %u is less than the minimum tuple header size %u",
435+
ctx.lp_len,
436+
(unsigned) MAXALIGN(SizeofHeapTupleHeader)));
437+
continue;
438+
}
439+
if (ctx.lp_off + ctx.lp_len > BLCKSZ)
440+
{
441+
report_corruption(&ctx,
442+
psprintf("line pointer to page offset %u with length %u ends beyond maximum page offset %u",
443+
ctx.lp_off,
444+
ctx.lp_len,
445+
(unsigned) BLCKSZ));
446+
continue;
447+
}
448+
449+
/* It should be safe to examine the tuple's header, at least */
406450
ctx.tuphdr = (HeapTupleHeader) PageGetItem(ctx.page, ctx.itemid);
407451
ctx.natts = HeapTupleHeaderGetNatts(ctx.tuphdr);
408452

@@ -1088,25 +1132,6 @@ check_tuple(HeapCheckContext *ctx)
10881132
bool fatal = false;
10891133
uint16 infomask = ctx->tuphdr->t_infomask;
10901134

1091-
/*
1092-
* If we report corruption before iterating over individual attributes, we
1093-
* need attnum to be reported as NULL. Set that up before any corruption
1094-
* reporting might happen.
1095-
*/
1096-
ctx->attnum = -1;
1097-
1098-
/*
1099-
* If the line pointer for this tuple does not reserve enough space for a
1100-
* complete tuple header, we dare not read the tuple header.
1101-
*/
1102-
if (ctx->lp_len < MAXALIGN(SizeofHeapTupleHeader))
1103-
{
1104-
report_corruption(ctx,
1105-
psprintf("line pointer length %u is less than the minimum tuple header size %u",
1106-
ctx->lp_len, (uint32) MAXALIGN(SizeofHeapTupleHeader)));
1107-
return;
1108-
}
1109-
11101135
/* If xmin is normal, it should be within valid range */
11111136
xmin = HeapTupleHeaderGetXmin(ctx->tuphdr);
11121137
switch (get_xid_status(xmin, ctx, NULL))
@@ -1256,6 +1281,9 @@ check_tuple(HeapCheckContext *ctx)
12561281
for (ctx->attnum = 0; ctx->attnum < ctx->natts; ctx->attnum++)
12571282
if (!check_tuple_attribute(ctx))
12581283
break; /* cannot continue */
1284+
1285+
/* revert attnum to -1 until we again examine individual attributes */
1286+
ctx->attnum = -1;
12591287
}
12601288

12611289
/*
@@ -1393,9 +1421,9 @@ get_xid_status(TransactionId xid, HeapCheckContext *ctx,
13931421
if (!fxid_in_cached_range(fxid, ctx))
13941422
{
13951423
/*
1396-
* We may have been checking against stale values. Update the
1397-
* cached range to be sure, and since we relied on the cached
1398-
* range when we performed the full xid conversion, reconvert.
1424+
* We may have been checking against stale values. Update the cached
1425+
* range to be sure, and since we relied on the cached range when we
1426+
* performed the full xid conversion, reconvert.
13991427
*/
14001428
update_cached_xid_range(ctx);
14011429
fxid = FullTransactionIdFromXidAndCtx(xid, ctx);

0 commit comments

Comments
 (0)