From 75e03eabeaac8fd229b14f74de55d0e1470903c4 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Thu, 18 Apr 2019 17:53:54 -0700
Subject: [PATCH] Fix potential use-after-free for BEFORE UPDATE row triggers
 on non-core AMs.

When such a trigger returns the old row version, it naturally get
stored in the slot for the trigger result. When a table AMs doesn't
store HeapTuples internally, ExecBRUpdateTriggers() frees the old row
version passed to triggers - but before this fix it might still be
referenced by the slot holding the new tuple.

Noticed when running the out-of-core zheap AM against the in-core
version of tableam.

Author: Andres Freund
---
 src/backend/commands/trigger.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 3ae2640abd..c81ccc8fcf 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -3109,6 +3109,15 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 		{
 			ExecForceStoreHeapTuple(newtuple, newslot);
 
+			/*
+			 * If the tuple returned by the trigger / being stored, is the old
+			 * row version, and the heap tuple passed to the trigger was
+			 * allocated locally, materialize the slot. Otherwise we might
+			 * free it while still referenced by the slot.
+			 */
+			if (should_free_trig && newtuple == trigtuple)
+				ExecMaterializeSlot(newslot);
+
 			if (should_free_new)
 				heap_freetuple(oldtuple);