From 48a043538028826cc6e29fa41b0b265bb29a5987 Mon Sep 17 00:00:00 2001
From: Bruce Momjian <bruce@momjian.us>
Date: Fri, 25 Jun 2021 16:57:08 -0400
Subject: [PATCH v2 12/12] cfe-12-rel_over_cfe-11-gist squash commit

---
 contrib/basic_archive/basic_archive.c |   2 +-
 contrib/bloom/blinsert.c              |   3 +
 src/backend/access/gist/gist.c        |  51 ++++----
 src/backend/access/gist/gistbuild.c   |  12 ++
 src/backend/access/hash/hashpage.c    |   1 +
 src/backend/access/heap/rewriteheap.c |   7 +
 src/backend/access/nbtree/nbtree.c    |   3 +
 src/backend/access/nbtree/nbtsort.c   |   4 +-
 src/backend/access/spgist/spginsert.c |   6 +
 src/backend/access/transam/xlog.c     |   2 +
 src/backend/bootstrap/bootstrap.c     |   1 +
 src/backend/catalog/storage.c         |   7 +-
 src/backend/crypto/Makefile           |   1 +
 src/backend/crypto/bufenc.c           | 176 ++++++++++++++++++++++++++
 src/backend/postmaster/postmaster.c   |   2 +
 src/backend/storage/buffer/bufmgr.c   |  22 +++-
 src/backend/storage/buffer/localbuf.c |   8 +-
 src/backend/storage/file/copydir.c    |  28 +++-
 src/backend/storage/file/reinit.c     |   2 +-
 src/backend/storage/page/bufpage.c    |  51 +++++++-
 src/backend/tcop/postgres.c           |   2 +
 src/include/access/gist.h             |   5 +-
 src/include/crypto/bufenc.h           |  28 ++++
 src/include/storage/bufpage.h         |  18 ++-
 src/include/storage/copydir.h         |   2 +-
 25 files changed, 396 insertions(+), 48 deletions(-)
 create mode 100644 src/backend/crypto/bufenc.c
 create mode 100644 src/include/crypto/bufenc.h

diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 9f221816bb..7a59e34a42 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -275,7 +275,7 @@ basic_archive_file_internal(const char *file, const char *path)
 	 * Copy the file to its temporary destination.  Note that this will fail
 	 * if temp already exists.
 	 */
-	copy_file(unconstify(char *, path), temp);
+	copy_file(unconstify(char *, path), temp, false);
 
 	/*
 	 * Sync the temporary file to disk and move it to its final destination.
diff --git a/contrib/bloom/blinsert.c b/contrib/bloom/blinsert.c
index dd26d6ac29..3204950817 100644
--- a/contrib/bloom/blinsert.c
+++ b/contrib/bloom/blinsert.c
@@ -176,7 +176,10 @@ blbuildempty(Relation index)
 	 * XLOG_DBASE_CREATE* or XLOG_TBLSPC_CREATE record.  Therefore, we need
 	 * this even when wal_level=minimal.
 	 */
+	PageEncryptInplace(metapage, INIT_FORKNUM, RelationIsPermanent(index),
+					   BLOOM_METAPAGE_BLKNO);
 	PageSetChecksumInplace(metapage, BLOOM_METAPAGE_BLKNO);
+
 	smgrwrite(RelationGetSmgr(index), INIT_FORKNUM, BLOOM_METAPAGE_BLKNO,
 			  (char *) metapage, true);
 	log_newpage(&(RelationGetSmgr(index))->smgr_rlocator.locator, INIT_FORKNUM,
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index 74e7e485e6..3472afe363 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -503,17 +503,14 @@ gistplacetopage(Relation rel, Size freespace, GISTSTATE *giststate,
 		if (is_build)
 			recptr = !FileEncryptionEnabled ? GistBuildLSN :
 						LSNForEncryption(RelationIsPermanent(rel));
+		else if (RelationNeedsWAL(rel))
+			recptr = gistXLogSplit(is_leaf,
+								   dist, oldrlink, oldnsn, leftchildbuf,
+								   markfollowright);
+		else if (FileEncryptionEnabled)
+			recptr = LSNForEncryption(RelationIsPermanent(rel));
 		else
-		{
-			if (RelationNeedsWAL(rel))
-				recptr = gistXLogSplit(is_leaf,
-									   dist, oldrlink, oldnsn, leftchildbuf,
-									   markfollowright);
-			else if (FileEncryptionEnabled)
-				recptr = LSNForEncryption(RelationIsPermanent(rel));
-			else
-				recptr = gistGetFakeLSN(rel);
-		}
+			recptr = gistGetFakeLSN(rel);
 
 		for (ptr = dist; ptr; ptr = ptr->next)
 			PageSetLSN(ptr->page, recptr);
@@ -573,28 +570,26 @@ gistplacetopage(Relation rel, Size freespace, GISTSTATE *giststate,
 		if (is_build)
 			recptr = !FileEncryptionEnabled ? GistBuildLSN :
 						LSNForEncryption(RelationIsPermanent(rel));
-		else
+		else if (RelationNeedsWAL(rel))
 		{
-			if (RelationNeedsWAL(rel))
-			{
-				OffsetNumber ndeloffs = 0,
-							deloffs[1];
+			OffsetNumber ndeloffs = 0,
+						deloffs[1];
 
-				if (OffsetNumberIsValid(oldoffnum))
-				{
-					deloffs[0] = oldoffnum;
-					ndeloffs = 1;
-				}
-
-				recptr = gistXLogUpdate(buffer,
-										deloffs, ndeloffs, itup, ntup,
-										leftchildbuf);
+			if (OffsetNumberIsValid(oldoffnum))
+			{
+				deloffs[0] = oldoffnum;
+				ndeloffs = 1;
 			}
-			else if (FileEncryptionEnabled)
-				recptr = LSNForEncryption(RelationIsPermanent(rel));
-			else
-				recptr = gistGetFakeLSN(rel);
+
+			recptr = gistXLogUpdate(buffer,
+									deloffs, ndeloffs, itup, ntup,
+									leftchildbuf);
 		}
+		else if (FileEncryptionEnabled)
+			recptr = LSNForEncryption(RelationIsPermanent(rel));
+		else
+			recptr = gistGetFakeLSN(rel);
+
 		PageSetLSN(page, recptr);
 
 		if (newblkno)
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index bf5162b410..b54d50c440 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -464,6 +464,12 @@ gist_indexsortbuild(GISTBuildState *state)
 	RelationGetSmgr(state->indexrel);
 	PageSetLSN(levelstate->pages[0], !FileEncryptionEnabled ? GistBuildLSN :
 			   LSNForEncryption(RelationIsPermanent(state->indexrel)));
+	/* Make sure LSNs are vaild, and if encryption, are not constant. */
+	Assert(!XLogRecPtrIsInvalid(PageGetLSN(levelstate->pages[0])) &&
+		   (!FileEncryptionEnabled ||
+			PageGetLSN(levelstate->pages[0]) != GistBuildLSN));
+	PageEncryptInplace(levelstate->pages[0], MAIN_FORKNUM, RelationIsPermanent(state->indexrel),
+					   GIST_ROOT_BLKNO);
 	PageSetChecksumInplace(levelstate->pages[0], GIST_ROOT_BLKNO);
 	smgrwrite(RelationGetSmgr(state->indexrel), MAIN_FORKNUM, GIST_ROOT_BLKNO,
 			  levelstate->pages[0], true);
@@ -662,6 +668,12 @@ gist_indexsortbuild_flush_ready_pages(GISTBuildState *state)
 
 		PageSetLSN(page, !FileEncryptionEnabled ? GistBuildLSN :
 				   LSNForEncryption(RelationIsPermanent(state->indexrel)));
+		/* Make sure LSNs are vaild, and if encryption, are not constant. */
+		Assert(!XLogRecPtrIsInvalid(PageGetLSN(page)) &&
+			   (!FileEncryptionEnabled ||
+				PageGetLSN(page) != GistBuildLSN));
+		PageEncryptInplace(page, MAIN_FORKNUM, RelationIsPermanent(state->indexrel),
+				   blkno);
 		PageSetChecksumInplace(page, blkno);
 		smgrextend(RelationGetSmgr(state->indexrel), MAIN_FORKNUM, blkno, page,
 				   true);
diff --git a/src/backend/access/hash/hashpage.c b/src/backend/access/hash/hashpage.c
index d2edcd4617..4ba41ce288 100644
--- a/src/backend/access/hash/hashpage.c
+++ b/src/backend/access/hash/hashpage.c
@@ -1025,6 +1025,7 @@ _hash_alloc_buckets(Relation rel, BlockNumber firstblock, uint32 nblocks)
 					zerobuf.data,
 					true);
 
+	PageEncryptInplace(page, MAIN_FORKNUM, RelationIsPermanent(rel), lastblock);
 	PageSetChecksumInplace(page, lastblock);
 	smgrextend(RelationGetSmgr(rel), MAIN_FORKNUM, lastblock, zerobuf.data,
 			   false);
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index a34e9b352d..c163c7ba4b 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -324,6 +324,9 @@ end_heap_rewrite(RewriteState state)
 						state->rs_buffer,
 						true);
 
+		PageEncryptInplace(state->rs_buffer, MAIN_FORKNUM,
+						   RelationIsPermanent(state->rs_new_rel),
+						   state->rs_blockno);
 		PageSetChecksumInplace(state->rs_buffer, state->rs_blockno);
 
 		smgrextend(RelationGetSmgr(state->rs_new_rel), MAIN_FORKNUM,
@@ -690,8 +693,12 @@ raw_heap_insert(RewriteState state, HeapTuple tup)
 			 * need for smgr to schedule an fsync for this write; we'll do it
 			 * ourselves in end_heap_rewrite.
 			 */
+			PageEncryptInplace(page, MAIN_FORKNUM,
+							   RelationIsPermanent(state->rs_new_rel),
+							   state->rs_blockno);
 			PageSetChecksumInplace(page, state->rs_blockno);
 
+			/* XXX - is this still needed? */
 			smgrextend(RelationGetSmgr(state->rs_new_rel), MAIN_FORKNUM,
 					   state->rs_blockno, (char *) page, true);
 
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index b52eca8f38..e60582e7e3 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -163,7 +163,10 @@ btbuildempty(Relation index)
 	 * XLOG_DBASE_CREATE* or XLOG_TBLSPC_CREATE record.  Therefore, we need
 	 * this even when wal_level=minimal.
 	 */
+	PageEncryptInplace(metapage, INIT_FORKNUM, RelationIsPermanent(index),
+					   BTREE_METAPAGE);
 	PageSetChecksumInplace(metapage, BTREE_METAPAGE);
+
 	smgrwrite(RelationGetSmgr(index), INIT_FORKNUM, BTREE_METAPAGE,
 			  (char *) metapage, true);
 	log_newpage(&RelationGetSmgr(index)->smgr_rlocator.locator, INIT_FORKNUM,
diff --git a/src/backend/access/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c
index 501e011ce1..70a94115cb 100644
--- a/src/backend/access/nbtree/nbtsort.c
+++ b/src/backend/access/nbtree/nbtsort.c
@@ -661,13 +661,15 @@ _bt_blwritepage(BTWriteState *wstate, Page page, BlockNumber blkno)
 	{
 		if (!wstate->btws_zeropage)
 			wstate->btws_zeropage = (Page) palloc0(BLCKSZ);
-		/* don't set checksum for all-zero page */
+		/* don't set checksum or encryption for all-zero page */
 		smgrextend(RelationGetSmgr(wstate->index), MAIN_FORKNUM,
 				   wstate->btws_pages_written++,
 				   (char *) wstate->btws_zeropage,
 				   true);
 	}
 
+	PageEncryptInplace(page, MAIN_FORKNUM, RelationIsPermanent(wstate->index),
+					   blkno);
 	PageSetChecksumInplace(page, blkno);
 
 	/*
diff --git a/src/backend/access/spgist/spginsert.c b/src/backend/access/spgist/spginsert.c
index c6821b5952..b36bc41854 100644
--- a/src/backend/access/spgist/spginsert.c
+++ b/src/backend/access/spgist/spginsert.c
@@ -168,6 +168,8 @@ spgbuildempty(Relation index)
 	 * of their existing content when the corresponding create records are
 	 * replayed.
 	 */
+	PageEncryptInplace(page, INIT_FORKNUM, RelationIsPermanent(index),
+					   SPGIST_METAPAGE_BLKNO);
 	PageSetChecksumInplace(page, SPGIST_METAPAGE_BLKNO);
 	smgrwrite(RelationGetSmgr(index), INIT_FORKNUM, SPGIST_METAPAGE_BLKNO,
 			  (char *) page, true);
@@ -177,6 +179,8 @@ spgbuildempty(Relation index)
 	/* Likewise for the root page. */
 	SpGistInitPage(page, SPGIST_LEAF);
 
+	PageEncryptInplace(page, INIT_FORKNUM, RelationIsPermanent(index),
+					   SPGIST_ROOT_BLKNO);
 	PageSetChecksumInplace(page, SPGIST_ROOT_BLKNO);
 	smgrwrite(RelationGetSmgr(index), INIT_FORKNUM, SPGIST_ROOT_BLKNO,
 			  (char *) page, true);
@@ -186,6 +190,8 @@ spgbuildempty(Relation index)
 	/* Likewise for the null-tuples root page. */
 	SpGistInitPage(page, SPGIST_LEAF | SPGIST_NULLS);
 
+	PageEncryptInplace(page, INIT_FORKNUM, RelationIsPermanent(index),
+					   SPGIST_NULL_BLKNO);
 	PageSetChecksumInplace(page, SPGIST_NULL_BLKNO);
 	smgrwrite(RelationGetSmgr(index), INIT_FORKNUM, SPGIST_NULL_BLKNO,
 			  (char *) page, true);
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 38e46bbbf7..fd217e97f4 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -71,6 +71,7 @@
 #include "common/file_utils.h"
 #include "crypto/kmgr.h"
 #include "executor/instrument.h"
+#include "crypto/bufenc.h"
 #include "miscadmin.h"
 #include "pg_trace.h"
 #include "pgstat.h"
@@ -4832,6 +4833,7 @@ BootStrapXLOG(void)
 	WriteControlFile();
 
 	BootStrapKmgr();
+	InitializeBufferEncryption();
 
 	if (terminal_fd != -1)
 	{
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 87918a4d28..ffaf9c7cc4 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -29,6 +29,7 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_type.h"
 #include "common/link-canary.h"
+#include "crypto/bufenc.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index d708af19ed..925195d5a3 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -486,8 +486,9 @@ RelationCopyStorage(SMgrRelation src, SMgrRelation dst,
 
 		smgrread(src, forkNum, blkno, buf.data);
 
-		if (!PageIsVerifiedExtended(page, blkno,
-									PIV_LOG_WARNING | PIV_REPORT_STAT))
+		if (!PageIsVerifiedExtended(page, forkNum,
+									relpersistence == RELPERSISTENCE_PERMANENT,
+									blkno, PIV_LOG_WARNING | PIV_REPORT_STAT))
 		{
 			/*
 			 * For paranoia's sake, capture the file path before invoking the
@@ -514,6 +515,8 @@ RelationCopyStorage(SMgrRelation src, SMgrRelation dst,
 		if (use_wal)
 			log_newpage(&dst->smgr_rlocator.locator, forkNum, blkno, page, false);
 
+		PageEncryptInplace(page, forkNum,
+						   relpersistence == RELPERSISTENCE_PERMANENT, blkno);
 		PageSetChecksumInplace(page, blkno);
 
 		/*
diff --git a/src/backend/crypto/Makefile b/src/backend/crypto/Makefile
index c27362029d..8985a66875 100644
--- a/src/backend/crypto/Makefile
+++ b/src/backend/crypto/Makefile
@@ -13,6 +13,7 @@ top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = \
+	bufenc.o \
 	kmgr.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/crypto/bufenc.c b/src/backend/crypto/bufenc.c
new file mode 100644
index 0000000000..0213fb23f5
--- /dev/null
+++ b/src/backend/crypto/bufenc.c
@@ -0,0 +1,176 @@
+/*-------------------------------------------------------------------------
+ *
+ * bufenc.c
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/crypto/bufenc.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "miscadmin.h"
+#include "lib/stringinfo.h"
+
+#include "access/gist.h"
+#include "access/xlog.h"
+#include "crypto/bufenc.h"
+#include "storage/bufpage.h"
+#include "storage/fd.h"
+
+extern XLogRecPtr LSNForEncryption(bool use_wal_lsn);
+
+/*
+ * We use the page LSN, page number, and permanent-bit to indicate if a fake
+ * LSN was used to create a nonce for each page.
+ */
+#define BUFENC_IV_SIZE		16
+
+static unsigned char buf_encryption_iv[BUFENC_IV_SIZE];
+
+PgCipherCtx *BufEncCtx = NULL;
+PgCipherCtx *BufDecCtx = NULL;
+
+static void set_buffer_encryption_iv(Page page, BlockNumber blkno,
+									 bool relation_is_permanent);
+
+void
+InitializeBufferEncryption(void)
+{
+	const CryptoKey *key;
+
+	if (!FileEncryptionEnabled)
+		return;
+
+	key = KmgrGetKey(KMGR_KEY_ID_REL);
+
+	BufEncCtx = pg_cipher_ctx_create(PG_CIPHER_AES_CTR,
+									 (unsigned char *) key->key,
+									 (key->klen), true);
+	if (!BufEncCtx)
+		elog(ERROR, "cannot intialize encryption context");
+
+	BufDecCtx = pg_cipher_ctx_create(PG_CIPHER_AES_CTR,
+									 (unsigned char *) key->key,
+									 (key->klen), false);
+	if (!BufDecCtx)
+		elog(ERROR, "cannot intialize decryption context");
+}
+
+/* Encrypt the given page with the relation key */
+void
+EncryptPage(Page page, bool relation_is_permanent, BlockNumber blkno)
+{
+	unsigned char *ptr = (unsigned char *) page + PageEncryptOffset;
+	bool		is_gist_page_or_similar;
+
+	int			enclen;
+
+	Assert(BufEncCtx != NULL);
+
+	/*
+	 * Permanent pages have valid LSNs, and non-permanent pages usually have
+	 * invalid (not set) LSNs.  (One exception are GiST fake LSNs, see below.)
+	 * However, we need valid ones on all pages for encryption.  There are too
+	 * many places that set the page LSN for permanent pages to do the same
+	 * for non-permanent pages, so we just set it here.
+	 *
+	 * Also, while permanent relations get new LSNs every time the page is
+	 * modified, for non-permanent relations do not, so we just update the LSN
+	 * here before it is encrypted.
+	 *
+	 * GiST indexes uses LSNs, which are also stored in NSN fields, to detect
+	 * page splits.  Therefore, we allow the GiST code to assign LSNs and we
+	 * don't change them here.
+	 */
+
+	/* Permanent relations should already have valid LSNs. */
+	Assert(!XLogRecPtrIsInvalid(PageGetLSN(page)) || !relation_is_permanent);
+
+	/*
+	 * Check if the page has a special size == GISTPageOpaqueData, a valid
+	 * GIST_PAGE_ID, no invalid GiST flag bits are set, and a valid LSN.  This
+	 * is true for all GiST pages, and perhaps a few pages that are not.  The
+	 * only downside of guessing wrong is that we might not update the LSN for
+	 * some non-permanent relation page changes, and therefore reuse the IV,
+	 * which seems acceptable.
+	 */
+	is_gist_page_or_similar =
+		(PageGetSpecialSize(page) == MAXALIGN(sizeof(GISTPageOpaqueData)) &&
+		 GistPageGetOpaque(page)->gist_page_id == GIST_PAGE_ID &&
+		 (GistPageGetOpaque(page)->flags & ~GIST_FLAG_BITMASK) == 0 &&
+		 !XLogRecPtrIsInvalid(PageGetLSN(page)));
+
+	if (!relation_is_permanent && !is_gist_page_or_similar)
+		PageSetLSN(page, LSNForEncryption(relation_is_permanent));
+
+	set_buffer_encryption_iv(page, blkno, relation_is_permanent);
+	if (unlikely(!pg_cipher_encrypt(BufEncCtx, PG_CIPHER_AES_CTR,
+									(const unsigned char *) ptr,	/* input  */
+									SizeOfPageEncryption,
+									ptr,	/* length */
+									&enclen,	/* resulting length */
+									buf_encryption_iv,	/* iv */
+									BUFENC_IV_SIZE,
+									NULL, 0)))
+		elog(ERROR, "cannot encrypt page %u", blkno);
+
+	Assert(enclen == SizeOfPageEncryption);
+}
+
+/* Decrypt the given page with the relation key */
+void
+DecryptPage(Page page, bool relation_is_permanent, BlockNumber blkno)
+{
+	unsigned char *ptr = (unsigned char *) page + PageEncryptOffset;
+	int			enclen;
+
+	Assert(BufDecCtx != NULL);
+
+	set_buffer_encryption_iv(page, blkno, relation_is_permanent);
+	if (unlikely(!pg_cipher_decrypt(BufDecCtx, PG_CIPHER_AES_CTR,
+									(const unsigned char *) ptr,	/* input  */
+									SizeOfPageEncryption,
+									ptr,	/* output */
+									&enclen,	/* resulting length */
+									buf_encryption_iv,	/* iv */
+									BUFENC_IV_SIZE,
+									NULL, 0)))
+		elog(ERROR, "cannot decrypt page %u", blkno);
+
+	Assert(enclen == SizeOfPageEncryption);
+}
+
+/* Construct iv for the given page */
+static void
+set_buffer_encryption_iv(Page page, BlockNumber blkno,
+						 bool relation_is_permanent)
+{
+	unsigned char *p = buf_encryption_iv;
+
+	MemSet(buf_encryption_iv, 0, BUFENC_IV_SIZE);
+
+	/* page lsn (8 byte) */
+	memcpy(p, &((PageHeader) page)->pd_lsn, sizeof(PageXLogRecPtr));
+	p += sizeof(PageXLogRecPtr);
+
+	/* block number (4 byte) */
+	memcpy(p, &blkno, sizeof(BlockNumber));
+	p += sizeof(BlockNumber);
+
+	/*
+	 * Mark use of fake LSNs in IV so if the real and fake LSN counters
+	 * overlap, the IV will remain unique.  XXX Is there a better value?
+	 */
+	if (!relation_is_permanent)
+		*p++ = 0x80;
+
+	/*
+	 * The maximum required counter for AES-CTR is 2048, which fits in the
+	 * last three bytes.
+	 */
+}
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 1673717da9..c463b27d90 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -98,6 +98,7 @@
 #include "common/ip.h"
 #include "common/pg_prng.h"
 #include "common/string.h"
+#include "crypto/bufenc.h"
 #include "crypto/kmgr.h"
 #include "lib/ilist.h"
 #include "libpq/auth.h"
@@ -1354,6 +1355,7 @@ PostmasterMain(int argc, char *argv[])
 	}
 
 	InitializeKmgr();
+	InitializeBufferEncryption();
 
 	if (terminal_fd != -1)
 		close(terminal_fd);
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 256f468474..17ae644022 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -39,6 +39,7 @@
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "crypto/bufenc.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -1029,7 +1030,9 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 			}
 
 			/* check for garbage data */
-			if (!PageIsVerifiedExtended((Page) bufBlock, blockNum,
+			if (!PageIsVerifiedExtended((Page) bufBlock, forkNum,
+										relpersistence == RELPERSISTENCE_PERMANENT,
+										blockNum,
 										PIV_LOG_WARNING | PIV_REPORT_STAT))
 			{
 				if (mode == RBM_ZERO_ON_ERROR || zero_damaged_pages)
@@ -2893,12 +2896,24 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 	 */
 	bufBlock = BufHdrGetBlock(buf);
 
+	if (FileEncryptionEnabled)
+	{
+		/*
+		 * Technically BM_PERMANENT could indicate an init fork, but that's
+		 * okay since forkNum would also tell us not to encrypt init forks.
+		 */
+		bufToWrite = PageEncryptCopy((Page) bufBlock, buf->tag.forkNum,
+									 buf_state & BM_PERMANENT, buf->tag.blockNum);
+		bufToWrite = PageSetChecksumCopy((Page) bufToWrite, buf->tag.blockNum);
+	}
+	else
+		bufToWrite = PageSetChecksumCopy((Page) bufBlock, buf->tag.blockNum);
+
 	/*
 	 * Update page checksum if desired.  Since we have only shared lock on the
 	 * buffer, other processes might be updating hint bits in it, so we must
 	 * copy the page to private storage if we do checksumming.
 	 */
-	bufToWrite = PageSetChecksumCopy((Page) bufBlock, buf->tag.blockNum);
 
 	if (track_io_timing)
 		INSTR_TIME_SET_CURRENT(io_start);
@@ -3543,6 +3558,9 @@ FlushRelationBuffers(Relation rel)
 				errcallback.previous = error_context_stack;
 				error_context_stack = &errcallback;
 
+				/* XXX should we be writing a copy of the page here? */
+				PageEncryptInplace(localpage, bufHdr->tag.forkNum,
+								   RelationIsPermanent(rel), bufHdr->tag.blockNum);
 				PageSetChecksumInplace(localpage, bufHdr->tag.blockNum);
 
 				smgrwrite(RelationGetSmgr(rel),
diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c
index 30d67d1c40..09b3ec71d9 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -159,7 +159,7 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum,
 		}
 		return bufHdr;
 	}
-
+	
 #ifdef LBDEBUG
 	fprintf(stderr, "LB ALLOC (%u,%d,%d) %d\n",
 			smgr->smgr_rlocator.locator.relNumber, forkNum, blockNum,
@@ -217,6 +217,12 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum,
 		/* Find smgr relation for buffer */
 		oreln = smgropen(BufTagGetRelFileLocator(&bufHdr->tag), MyBackendId);
 
+		/*
+		 * Technically BM_PERMANENT could indicate an init fork, but that's
+		 * okay since forkNum would also tell us not to encrypt init forks.
+		 */
+		PageEncryptInplace(localpage, bufHdr->tag.forkNum,
+					   buf_state & BM_PERMANENT, bufHdr->tag.blockNum);
 		PageSetChecksumInplace(localpage, bufHdr->tag.blockNum);
 
 		/* And write... */
diff --git a/src/backend/storage/file/copydir.c b/src/backend/storage/file/copydir.c
index 8a866191e1..d2f4d13ec0 100644
--- a/src/backend/storage/file/copydir.c
+++ b/src/backend/storage/file/copydir.c
@@ -22,12 +22,15 @@
 #include <unistd.h>
 #include <sys/stat.h>
 
+#include "crypto/bufenc.h"
 #include "common/file_utils.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "storage/copydir.h"
 #include "storage/fd.h"
 
+extern XLogRecPtr LSNForEncryption(bool use_wal_lsn);
+
 /*
  * copydir: copy a directory
  *
@@ -72,7 +75,7 @@ copydir(char *fromdir, char *todir, bool recurse)
 				copydir(fromfile, tofile, true);
 		}
 		else if (xlde_type == PGFILETYPE_REG)
-			copy_file(fromfile, tofile);
+			copy_file(fromfile, tofile, false);
 	}
 	FreeDir(xldir);
 
@@ -115,7 +118,7 @@ copydir(char *fromdir, char *todir, bool recurse)
  * copy one file
  */
 void
-copy_file(char *fromfile, char *tofile)
+copy_file(char *fromfile, char *tofile, bool encrypt_init_file)
 {
 	char	   *buffer;
 	int			srcfd;
@@ -123,9 +126,8 @@ copy_file(char *fromfile, char *tofile)
 	int			nbytes;
 	off_t		offset;
 	off_t		flush_offset;
-
 	/* Size of copy buffer (read and write requests) */
-#define COPY_BUF_SIZE (8 * BLCKSZ)
+	int			copy_buf_size = (encrypt_init_file) ? BLCKSZ : 8 * BLCKSZ;
 
 	/*
 	 * Size of data flush requests.  It seems beneficial on most platforms to
@@ -140,7 +142,7 @@ copy_file(char *fromfile, char *tofile)
 #endif
 
 	/* Use palloc to ensure we get a maxaligned buffer */
-	buffer = palloc(COPY_BUF_SIZE);
+	buffer = palloc(copy_buf_size);
 
 	/*
 	 * Open the files
@@ -178,7 +180,7 @@ copy_file(char *fromfile, char *tofile)
 		}
 
 		pgstat_report_wait_start(WAIT_EVENT_COPY_FILE_READ);
-		nbytes = read(srcfd, buffer, COPY_BUF_SIZE);
+		nbytes = read(srcfd, buffer, copy_buf_size);
 		pgstat_report_wait_end();
 		if (nbytes < 0)
 			ereport(ERROR,
@@ -186,6 +188,20 @@ copy_file(char *fromfile, char *tofile)
 					 errmsg("could not read file \"%s\": %m", fromfile)));
 		if (nbytes == 0)
 			break;
+		/*
+		 * When we copy an init fork page to be part of an empty unlogged
+		 * relation, its real LSN must be replaced with a fake one, and the
+		 * page encrypted.
+		 */
+		if (encrypt_init_file)
+		{
+			Page page = (Page) buffer;
+
+			Assert(nbytes == BLCKSZ);
+			PageSetLSN(page, LSNForEncryption(false));
+			PageEncryptInplace(page, MAIN_FORKNUM, false, offset / BLCKSZ);
+		}
+
 		errno = 0;
 		pgstat_report_wait_start(WAIT_EVENT_COPY_FILE_WRITE);
 		if ((int) write(dstfd, buffer, nbytes) != nbytes)
diff --git a/src/backend/storage/file/reinit.c b/src/backend/storage/file/reinit.c
index 647c458b52..2e57943768 100644
--- a/src/backend/storage/file/reinit.c
+++ b/src/backend/storage/file/reinit.c
@@ -312,7 +312,7 @@ ResetUnloggedRelationsInDbspaceDir(const char *dbspacedirname, int op)
 
 			/* OK, we're ready to perform the actual copy. */
 			elog(DEBUG2, "copying %s to %s", srcpath, dstpath);
-			copy_file(srcpath, dstpath);
+			copy_file(srcpath, dstpath, true);
 		}
 
 		FreeDir(dbspace_dir);
diff --git a/src/backend/storage/page/bufpage.c b/src/backend/storage/page/bufpage.c
index 8b617c7e79..1255566f57 100644
--- a/src/backend/storage/page/bufpage.c
+++ b/src/backend/storage/page/bufpage.c
@@ -17,6 +17,7 @@
 #include "access/htup_details.h"
 #include "access/itup.h"
 #include "access/xlog.h"
+#include "crypto/bufenc.h"
 #include "pgstat.h"
 #include "storage/checksum.h"
 #include "utils/memdebug.h"
@@ -85,7 +86,8 @@ PageInit(Page page, Size pageSize, Size specialSize)
  * to pgstat.
  */
 bool
-PageIsVerifiedExtended(Page page, BlockNumber blkno, int flags)
+PageIsVerifiedExtended(Page page, ForkNumber forknum, bool relation_is_permanent,
+					   BlockNumber blkno, int flags)
 {
 	PageHeader	p = (PageHeader) page;
 	size_t	   *pagebytes;
@@ -108,6 +110,8 @@ PageIsVerifiedExtended(Page page, BlockNumber blkno, int flags)
 				checksum_failure = true;
 		}
 
+		PageDecryptInplace(page, forknum, relation_is_permanent, blkno);
+
 		/*
 		 * The following checks don't prove the header is correct, only that
 		 * it looks sane enough to allow into the buffer pool. Later usage of
@@ -1544,3 +1548,48 @@ PageSetChecksumInplace(Page page, BlockNumber blkno)
 
 	((PageHeader) page)->pd_checksum = pg_checksum_page((char *) page, blkno);
 }
+
+char *
+PageEncryptCopy(Page page, ForkNumber forknum, bool relation_is_permanent,
+				BlockNumber blkno)
+{
+	static char *pageCopy = NULL;
+
+	/* If we don't need a checksum, just return the passed-in data */
+	if (PageIsNew(page) || !PageNeedsToBeEncrypted(forknum))
+		return (char *) page;
+
+	/*
+	 * We allocate the copy space once and use it over on each subsequent
+	 * call.  The point of palloc'ing here, rather than having a static char
+	 * array, is first to ensure adequate alignment for the checksumming code
+	 * and second to avoid wasting space in processes that never call this.
+	 */
+	if (pageCopy == NULL)
+		pageCopy = MemoryContextAlloc(TopMemoryContext, BLCKSZ);
+
+	memcpy(pageCopy, (char *) page, BLCKSZ);
+	EncryptPage(pageCopy, relation_is_permanent, blkno);
+	return pageCopy;
+}
+
+void
+PageEncryptInplace(Page page, ForkNumber forknum, bool relation_is_permanent,
+				   BlockNumber blkno)
+{
+	if (PageIsNew(page) || !PageNeedsToBeEncrypted(forknum))
+		return;
+
+	EncryptPage(page, relation_is_permanent, blkno);
+}
+
+
+void
+PageDecryptInplace(Page page, ForkNumber forknum, bool relation_is_permanent,
+				   BlockNumber blkno)
+{
+	if (PageIsNew(page) || !PageNeedsToBeEncrypted(forknum))
+		return;
+
+	DecryptPage(page, relation_is_permanent, blkno);
+}
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 5ee6c43394..6f52dfe7c5 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -35,6 +35,7 @@
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "common/pg_prng.h"
+#include "crypto/bufenc.h"
 #include "crypto/kmgr.h"
 #include "jit/jit.h"
 #include "libpq/libpq.h"
@@ -4076,6 +4077,7 @@ PostgresMain(const char *dbname, const char *username)
 	if (!IsUnderPostmaster)
 	{
 		InitializeKmgr();
+		InitializeBufferEncryption();
 
 		if (terminal_fd != -1)
 			close(terminal_fd);
diff --git a/src/include/access/gist.h b/src/include/access/gist.h
index a3337627b8..3e116a66d8 100644
--- a/src/include/access/gist.h
+++ b/src/include/access/gist.h
@@ -41,7 +41,7 @@
 #define GISTNProcs					11
 
 /*
- * Page opaque data in a GiST index page.
+ * Page opaque data flags in a GiST index page.
  */
 #define F_LEAF				(1 << 0)	/* leaf page */
 #define F_DELETED			(1 << 1)	/* the page has been deleted */
@@ -51,6 +51,9 @@
 #define F_HAS_GARBAGE		(1 << 4)	/* some tuples on the page are dead,
 										 * but not deleted yet */
 
+/* Specifies the bits that can be set in the GiST flags field */
+#define GIST_FLAG_BITMASK   0x1F
+
 /*
  * NSN (node sequence number) is a special-purpose LSN which is stored on each
  * index page in GISTPageOpaqueData and updated only during page splits.  By
diff --git a/src/include/crypto/bufenc.h b/src/include/crypto/bufenc.h
new file mode 100644
index 0000000000..cc86ceb821
--- /dev/null
+++ b/src/include/crypto/bufenc.h
@@ -0,0 +1,28 @@
+/*-------------------------------------------------------------------------
+ *
+ * bufenc.h
+ *
+ * Portions Copyright (c) 2021, PostgreSQL Global Development Group
+ *
+ * src/include/crypto/bufenc.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef BUFENC_H
+#define BUFENC_H
+
+#include "storage/bufmgr.h"
+#include "crypto/kmgr.h"
+
+/* Cluster encryption encrypts only main forks */
+#define PageNeedsToBeEncrypted(forknum) \
+	(FileEncryptionEnabled && (forknum) == MAIN_FORKNUM)
+
+
+extern void InitializeBufferEncryption(void);
+extern void EncryptPage(Page page, bool relation_is_permanent,
+						BlockNumber blkno);
+extern void DecryptPage(Page page, bool relation_is_permanent,
+						BlockNumber blkno);
+
+#endif							/* BUFENC_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 2708c4b683..d06c88d98c 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -15,6 +15,7 @@
 #define BUFPAGE_H
 
 #include "access/xlogdefs.h"
+#include "common/relpath.h"
 #include "storage/block.h"
 #include "storage/item.h"
 #include "storage/off.h"
@@ -168,6 +169,8 @@ typedef struct PageHeaderData
 } PageHeaderData;
 
 typedef PageHeaderData *PageHeader;
+#define PageEncryptOffset	offsetof(PageHeaderData, pd_special)
+#define SizeOfPageEncryption (BLCKSZ - PageEncryptOffset)
 
 /*
  * pd_flags contains the following flag bits.  Undefined bits are initialized
@@ -471,8 +474,8 @@ do { \
 						((overwrite) ? PAI_OVERWRITE : 0) | \
 						((is_heap) ? PAI_IS_HEAP : 0))
 
-#define PageIsVerified(page, blkno) \
-	PageIsVerifiedExtended(page, blkno, \
+#define PageIsVerified(page, relation_is_permanent, blkno) \
+	PageIsVerifiedExtended(page, MAIN_FORKNUM, relation_is_permanent, blkno, \
 						   PIV_LOG_WARNING | PIV_REPORT_STAT)
 
 /*
@@ -486,7 +489,10 @@ StaticAssertDecl(BLCKSZ == ((BLCKSZ / sizeof(size_t)) * sizeof(size_t)),
 				 "BLCKSZ has to be a multiple of sizeof(size_t)");
 
 extern void PageInit(Page page, Size pageSize, Size specialSize);
-extern bool PageIsVerifiedExtended(Page page, BlockNumber blkno, int flags);
+extern bool PageIsVerifiedExtended(Page page, ForkNumber forknum,
+								   bool relation_is_permanent,
+								   BlockNumber blkno,
+								   int flags);
 extern OffsetNumber PageAddItemExtended(Page page, Item item, Size size,
 										OffsetNumber offsetNumber, int flags);
 extern Page PageGetTempPage(Page page);
@@ -506,5 +512,11 @@ extern bool PageIndexTupleOverwrite(Page page, OffsetNumber offnum,
 									Item newtup, Size newsize);
 extern char *PageSetChecksumCopy(Page page, BlockNumber blkno);
 extern void PageSetChecksumInplace(Page page, BlockNumber blkno);
+extern char *PageEncryptCopy(Page page, ForkNumber forknum,
+							 bool relation_is_permanent, BlockNumber blkno);
+extern void PageEncryptInplace(Page page, ForkNumber forknum,
+							   bool relation_is_permanent, BlockNumber blkno);
+extern void PageDecryptInplace(Page page, ForkNumber forknum,
+							   bool relation_is_permanent, BlockNumber blkno);
 
 #endif							/* BUFPAGE_H */
diff --git a/src/include/storage/copydir.h b/src/include/storage/copydir.h
index 50a26edeb0..7fdfb71067 100644
--- a/src/include/storage/copydir.h
+++ b/src/include/storage/copydir.h
@@ -14,6 +14,6 @@
 #define COPYDIR_H
 
 extern void copydir(char *fromdir, char *todir, bool recurse);
-extern void copy_file(char *fromfile, char *tofile);
+extern void copy_file(char *fromfile, char *tofile, bool encrypt_init_file);
 
 #endif							/* COPYDIR_H */
-- 
2.31.1

