Skip to content

Commit e7a4561

Browse files
committed
Fix core dump in ReorderBufferRestoreChange on alignment-picky platforms.
When re-reading an update involving both an old tuple and a new tuple from disk, reorderbuffer.c was careless about whether the new tuple is suitably aligned for direct access --- in general, it isn't. We'd missed seeing this in the buildfarm because the contrib/test_decoding tests exercise this code path only a few times, and by chance all of those cases have old tuples with length a multiple of 4, which is usually enough to make the access to the new tuple's t_len safe. For some still-not-entirely-clear reason, however, Debian's sparc build gets a bus error, as reported by Christoph Berg; perhaps it's assuming 8-byte alignment of the pointer? The lack of previous field reports is probably because you need all of these conditions to trigger a crash: an alignment-picky platform (not Intel), a transaction large enough to spill to disk, an update within that xact that changes a primary-key field and has an odd-length old tuple, and of course logical decoding tracing the transaction. Avoid the alignment assumption by using memcpy instead of fetching t_len directly, and add a test case that exposes the crash on picky platforms. Back-patch to 9.4 where the bug was introduced. Discussion: <20160413094117.GC21485@msg.credativ.de>
1 parent ce47112 commit e7a4561

File tree

3 files changed

+28
-9
lines changed

3 files changed

+28
-9
lines changed

contrib/test_decoding/expected/ddl.out

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -227,23 +227,28 @@ CREATE TABLE tr_etoomuch (id serial primary key, data int);
227227
INSERT INTO tr_etoomuch(data) SELECT g.i FROM generate_series(1, 10234) g(i);
228228
DELETE FROM tr_etoomuch WHERE id < 5000;
229229
UPDATE tr_etoomuch SET data = - data WHERE id > 5000;
230+
CREATE TABLE tr_oddlength (id text primary key, data text);
231+
INSERT INTO tr_oddlength VALUES('ab', 'foo');
230232
COMMIT;
231233
/* display results, but hide most of the output */
232234
SELECT count(*), min(data), max(data)
233235
FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1')
234236
GROUP BY substring(data, 1, 24)
235237
ORDER BY 1,2;
236-
count | min | max
237-
-------+-------------------------------------------------+------------------------------------------------------------------------
238-
1 | BEGIN | BEGIN
239-
1 | COMMIT | COMMIT
240-
20467 | table public.tr_etoomuch: DELETE: id[integer]:1 | table public.tr_etoomuch: UPDATE: id[integer]:9999 data[integer]:-9999
241-
(3 rows)
238+
count | min | max
239+
-------+-------------------------------------------------------------------+------------------------------------------------------------------------
240+
1 | BEGIN | BEGIN
241+
1 | COMMIT | COMMIT
242+
1 | table public.tr_oddlength: INSERT: id[text]:'ab' data[text]:'foo' | table public.tr_oddlength: INSERT: id[text]:'ab' data[text]:'foo'
243+
20467 | table public.tr_etoomuch: DELETE: id[integer]:1 | table public.tr_etoomuch: UPDATE: id[integer]:9999 data[integer]:-9999
244+
(4 rows)
242245

243246
-- check updates of primary keys work correctly
244247
BEGIN;
245248
CREATE TABLE spoolme AS SELECT g.i FROM generate_series(1, 5000) g(i);
246249
UPDATE tr_etoomuch SET id = -id WHERE id = 5000;
250+
UPDATE tr_oddlength SET id = 'x', data = 'quux';
251+
UPDATE tr_oddlength SET id = 'yy', data = 'a';
247252
DELETE FROM spoolme;
248253
DROP TABLE spoolme;
249254
COMMIT;
@@ -253,7 +258,9 @@ WHERE data ~ 'UPDATE';
253258
data
254259
-------------------------------------------------------------------------------------------------------------
255260
table public.tr_etoomuch: UPDATE: old-key: id[integer]:5000 new-tuple: id[integer]:-5000 data[integer]:5000
256-
(1 row)
261+
table public.tr_oddlength: UPDATE: old-key: id[text]:'ab' new-tuple: id[text]:'x' data[text]:'quux'
262+
table public.tr_oddlength: UPDATE: old-key: id[text]:'x' new-tuple: id[text]:'yy' data[text]:'a'
263+
(3 rows)
257264

258265
-- check that a large, spooled, upsert works
259266
INSERT INTO tr_etoomuch (id, data)

contrib/test_decoding/sql/ddl.sql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ CREATE TABLE tr_etoomuch (id serial primary key, data int);
115115
INSERT INTO tr_etoomuch(data) SELECT g.i FROM generate_series(1, 10234) g(i);
116116
DELETE FROM tr_etoomuch WHERE id < 5000;
117117
UPDATE tr_etoomuch SET data = - data WHERE id > 5000;
118+
CREATE TABLE tr_oddlength (id text primary key, data text);
119+
INSERT INTO tr_oddlength VALUES('ab', 'foo');
118120
COMMIT;
119121

120122
/* display results, but hide most of the output */
@@ -127,6 +129,8 @@ ORDER BY 1,2;
127129
BEGIN;
128130
CREATE TABLE spoolme AS SELECT g.i FROM generate_series(1, 5000) g(i);
129131
UPDATE tr_etoomuch SET id = -id WHERE id = 5000;
132+
UPDATE tr_oddlength SET id = 'x', data = 'quux';
133+
UPDATE tr_oddlength SET id = 'yy', data = 'a';
130134
DELETE FROM spoolme;
131135
DROP TABLE spoolme;
132136
COMMIT;

src/backend/replication/logical/reorderbuffer.c

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2347,6 +2347,10 @@ ReorderBufferRestoreChanges(ReorderBuffer *rb, ReorderBufferTXN *txn,
23472347
/*
23482348
* Convert change from its on-disk format to in-memory format and queue it onto
23492349
* the TXN's ->changes list.
2350+
*
2351+
* Note: although "data" is declared char*, at entry it points to a
2352+
* maxalign'd buffer, making it safe in most of this function to assume
2353+
* that the pointed-to data is suitably aligned for direct access.
23502354
*/
23512355
static void
23522356
ReorderBufferRestoreChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
@@ -2374,7 +2378,7 @@ ReorderBufferRestoreChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
23742378
case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT:
23752379
if (change->data.tp.oldtuple)
23762380
{
2377-
Size tuplelen = ((HeapTuple) data)->t_len;
2381+
uint32 tuplelen = ((HeapTuple) data)->t_len;
23782382

23792383
change->data.tp.oldtuple =
23802384
ReorderBufferGetTupleBuf(rb, tuplelen - SizeofHeapTupleHeader);
@@ -2395,7 +2399,11 @@ ReorderBufferRestoreChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
23952399

23962400
if (change->data.tp.newtuple)
23972401
{
2398-
Size tuplelen = ((HeapTuple) data)->t_len;
2402+
/* here, data might not be suitably aligned! */
2403+
uint32 tuplelen;
2404+
2405+
memcpy(&tuplelen, data + offsetof(HeapTupleData, t_len),
2406+
sizeof(uint32));
23992407

24002408
change->data.tp.newtuple =
24012409
ReorderBufferGetTupleBuf(rb, tuplelen - SizeofHeapTupleHeader);

0 commit comments

Comments
 (0)