Skip to content

Commit bd807be

Browse files
committed
amcheck: Add additional TOAST pointer checks.
Expand the checks of toasted attributes to complain if the rawsize is overlarge. For compressed attributes, also complain if compression appears to have expanded the attribute or if the compression method is invalid. Mark Dilger, reviewed by Justin Pryzby, Alexander Alekseev, Heikki Linnakangas, Greg Stark, and me. Discussion: http://postgr.es/m/8E42250D-586A-4A27-B317-8B062C3816A8@enterprisedb.com
1 parent db7d1a7 commit bd807be

File tree

2 files changed

+67
-3
lines changed

2 files changed

+67
-3
lines changed

contrib/amcheck/verify_heapam.c

+46
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ PG_FUNCTION_INFO_V1(verify_heapam);
3030
/* The number of columns in tuples returned by verify_heapam */
3131
#define HEAPCHECK_RELATION_COLS 4
3232

33+
/* The largest valid toast va_rawsize */
34+
#define VARLENA_SIZE_LIMIT 0x3FFFFFFF
35+
3336
/*
3437
* Despite the name, we use this for reporting problems with both XIDs and
3538
* MXIDs.
@@ -1414,6 +1417,49 @@ check_tuple_attribute(HeapCheckContext *ctx)
14141417
*/
14151418
VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
14161419

1420+
/* Toasted attributes too large to be untoasted should never be stored */
1421+
if (toast_pointer.va_rawsize > VARLENA_SIZE_LIMIT)
1422+
report_corruption(ctx,
1423+
psprintf("toast value %u rawsize %u exceeds limit %u",
1424+
toast_pointer.va_valueid,
1425+
toast_pointer.va_rawsize,
1426+
VARLENA_SIZE_LIMIT));
1427+
1428+
if (VARATT_IS_COMPRESSED(&toast_pointer))
1429+
{
1430+
ToastCompressionId cmid;
1431+
bool valid = false;
1432+
1433+
/* Compression should never expand the attribute */
1434+
if (VARATT_EXTERNAL_GET_EXTSIZE(toast_pointer) > toast_pointer.va_rawsize - VARHDRSZ)
1435+
report_corruption(ctx,
1436+
psprintf("toast value %u external size %u exceeds maximum expected for rawsize %u",
1437+
toast_pointer.va_valueid,
1438+
VARATT_EXTERNAL_GET_EXTSIZE(toast_pointer),
1439+
toast_pointer.va_rawsize));
1440+
1441+
/* Compressed attributes should have a valid compression method */
1442+
cmid = TOAST_COMPRESS_METHOD(&toast_pointer);
1443+
switch (cmid)
1444+
{
1445+
/* List of all valid compression method IDs */
1446+
case TOAST_PGLZ_COMPRESSION_ID:
1447+
case TOAST_LZ4_COMPRESSION_ID:
1448+
valid = true;
1449+
break;
1450+
1451+
/* Recognized but invalid compression method ID */
1452+
case TOAST_INVALID_COMPRESSION_ID:
1453+
break;
1454+
1455+
/* Intentionally no default here */
1456+
}
1457+
if (!valid)
1458+
report_corruption(ctx,
1459+
psprintf("toast value %u has invalid compression method id %d",
1460+
toast_pointer.va_valueid, cmid));
1461+
}
1462+
14171463
/* The tuple header better claim to contain toasted values */
14181464
if (!(infomask & HEAP_HASEXTERNAL))
14191465
{

src/bin/pg_amcheck/t/004_verify_heapam.pl

+21-3
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ sub write_tuple
218218
my $relpath = "$pgdata/$rel";
219219

220220
# Insert data and freeze public.test
221-
use constant ROWCOUNT => 16;
221+
use constant ROWCOUNT => 18;
222222
$node->safe_psql(
223223
'postgres', qq(
224224
INSERT INTO public.test (a, b, c)
@@ -297,7 +297,7 @@ sub write_tuple
297297
$node->start;
298298

299299
# Ok, Xids and page layout look ok. We can run corruption tests.
300-
plan tests => 19;
300+
plan tests => 21;
301301

302302
# Check that pg_amcheck runs against the uncorrupted table without error.
303303
$node->command_ok(
@@ -504,7 +504,7 @@ sub header
504504
push @expected,
505505
qr/${header}multitransaction ID 4 equals or exceeds next valid multitransaction ID 1/;
506506
}
507-
elsif ($offnum == 15) # Last offnum must equal ROWCOUNT
507+
elsif ($offnum == 15)
508508
{
509509
# Set both HEAP_XMAX_COMMITTED and HEAP_XMAX_IS_MULTI
510510
$tup->{t_infomask} |= HEAP_XMAX_COMMITTED;
@@ -514,6 +514,24 @@ sub header
514514
push @expected,
515515
qr/${header}multitransaction ID 4000000000 precedes relation minimum multitransaction ID threshold 1/;
516516
}
517+
elsif ($offnum == 16)
518+
{
519+
# Set raw size too large
520+
$tup->{c_va_rawsize} = 1073741824;
521+
522+
$header = header(0, $offnum, 2);
523+
push @expected,
524+
qr/${header}toast value \d+ rawsize 1073741824 exceeds limit 1073741823/;
525+
}
526+
elsif ($offnum == 17) # Last offnum should equal ROWCOUNT-1
527+
{
528+
# Set raw size too small.
529+
$tup->{c_va_rawsize} = 9998;
530+
531+
$header = header(0, $offnum, 2);
532+
push @expected,
533+
qr/${header}toast value \d+ external size 10000 exceeds maximum expected for rawsize 9998/;
534+
}
517535
write_tuple($file, $offset, $tup);
518536
}
519537
close($file)

0 commit comments

Comments
 (0)