From 48a043538028826cc6e29fa41b0b265bb29a5987 Mon Sep 17 00:00:00 2001 From: Bruce Momjian 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 #include +#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